239 lines
6.7 KiB
C++
239 lines
6.7 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: FS.cpp
|
|
Date: 2021-6-16
|
|
Author: Reece
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "FS.hpp"
|
|
#include "Source/Locale/Locale.hpp"
|
|
|
|
|
|
//#include <stdlib.h>
|
|
|
|
namespace Aurora::IO::FS
|
|
{
|
|
static bool IsMagicCharacter(char c)
|
|
{
|
|
return (c == '.') || (c == '!') || (c == '~') || (c == '?');
|
|
}
|
|
|
|
static void ResolveAbsolutePath(bool requireMountExpand, bool requireSanitization, const AuString &path, AuString &result)
|
|
{
|
|
AuString buffer;
|
|
bool isResource {};
|
|
buffer.reserve(4096);
|
|
result.reserve(4096);
|
|
|
|
/**
|
|
Resolve special character prefixes
|
|
*/
|
|
if ((path.size() > 2) && (requireMountExpand))
|
|
{
|
|
if (path[1] == kPathSplitter)
|
|
{
|
|
// Working directory
|
|
if (path[0] == '.')
|
|
{
|
|
SysAssert(Process::GetWorkingDirectory(buffer));
|
|
buffer += kPathSplitter;
|
|
}
|
|
// Local Aurora profile
|
|
else if (path[0] == '~')
|
|
{
|
|
SysAssert(FS::GetProfileDomain(buffer));
|
|
}
|
|
// Global Aurora profile
|
|
else if (path[0] == '!')
|
|
{
|
|
SysAssert(FS::GetSystemDomain(buffer));
|
|
}
|
|
else if (path[0] == '?')
|
|
{
|
|
// ...except for this one
|
|
isResource = true;
|
|
}
|
|
else
|
|
{
|
|
buffer.insert(buffer.begin(), path.begin(), path.begin() + 2); // ???
|
|
}
|
|
|
|
buffer.insert(buffer.end(), path.begin() + 2, path.end());
|
|
}
|
|
else
|
|
{
|
|
buffer += path;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer += path;
|
|
}
|
|
|
|
if (requireSanitization)
|
|
{
|
|
/**
|
|
Quickly handle the edge case in some modern UNIX derived systems, and in
|
|
Windows UNC paths, where paths may start with '//' or '\\' respectively
|
|
*/
|
|
if (AuStartsWith(buffer, kDoublePathSplitter))
|
|
{
|
|
result += kDoublePathSplitter;
|
|
}
|
|
|
|
/**
|
|
Technically, UTF-8 strings may contain the "NUL" byte.
|
|
|
|
o Character numbers from U+0000 to U+007F (US-ASCII repertoire)
|
|
correspond to octets 00 to 7F (7 bit US-ASCII paths). A direct
|
|
consequence is that a plain ASCII string is also a valid UTF-8
|
|
string.
|
|
|
|
You know what would suck? If we were targeting an esoteric platform,
|
|
say a kernel, and we we're trying to avoid string exploits by potential
|
|
attackers. I'm sure that would never happen to poor anticheat devs.
|
|
*/
|
|
const auto parts = AuSplitString(buffer, AuString(1, kPathSplitter), true /*ignore empty tokens*/); /// zzzz im going to SLEEP
|
|
for (const auto &ch : parts) // can you tell why FIO shouldn't be in hot paths yet?
|
|
{
|
|
if (ch == "..")
|
|
{
|
|
auto i = result.size() - 1;
|
|
while (i >= 0 && result[i] != kPathSplitter)
|
|
{
|
|
--i;
|
|
}
|
|
|
|
if (i >= 0)
|
|
{
|
|
result.resize(i);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ((ch.size() == 1) && (IsMagicCharacter(ch[0])))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
result += ch;
|
|
result += kPathSplitter;
|
|
}
|
|
|
|
auto i = result.size() - 1;
|
|
if (result[i] == kPathSplitter)
|
|
{
|
|
result.resize(i);
|
|
}
|
|
}
|
|
else // !requireSanitization
|
|
{
|
|
result = buffer;
|
|
}
|
|
|
|
if (isResource)
|
|
{
|
|
AuString path;
|
|
if (!FS::GetSystemResourcePath(result, path))
|
|
{
|
|
result.clear();
|
|
}
|
|
else
|
|
{
|
|
result = path;
|
|
}
|
|
}
|
|
}
|
|
|
|
void /* internal, local export */ _NormalizePath(AuString &str)
|
|
{
|
|
bool requiresExpanding = false;
|
|
bool requiresMountUpdate = false;
|
|
|
|
requiresExpanding = str.size() && IsMagicCharacter(str[0]);
|
|
|
|
// best case -> O(n) wherein we merely check each BYTE with a handful of branch conditions
|
|
int doubleDots = 0;
|
|
int doubleSlash = 0;
|
|
for (auto &character : str)
|
|
{
|
|
if ((character == '\\') || (character == '/'))
|
|
{
|
|
character = kPathSplitter;
|
|
doubleSlash++;
|
|
}
|
|
else
|
|
{
|
|
doubleSlash = 0;
|
|
}
|
|
|
|
if (character == '.')
|
|
{
|
|
doubleDots++;
|
|
}
|
|
else
|
|
{
|
|
doubleDots = 0;
|
|
}
|
|
|
|
requiresExpanding |= doubleDots >= 2;
|
|
requiresExpanding |= doubleSlash >= 2;
|
|
}
|
|
|
|
// plus this, i guess
|
|
if (str.size() > 2)
|
|
{
|
|
if (str[1] == kPathSplitter)
|
|
{
|
|
auto c = str[0];
|
|
if ((c == '.') || (c == '~') || (c == '!') || (c == '?'))
|
|
{
|
|
requiresMountUpdate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// worst case -> yea have fun
|
|
if (requiresExpanding || requiresMountUpdate)
|
|
{
|
|
AuString temp;
|
|
ResolveAbsolutePath(requiresMountUpdate, requiresExpanding, str, temp);
|
|
str = temp;
|
|
}
|
|
}
|
|
|
|
AUKN_SYM bool ReadString(const AuString &path, AuString &buffer)
|
|
{
|
|
AuList<uint8_t> fileBuffer;
|
|
|
|
if (!ReadFile(path, fileBuffer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Locale::Encoding::DecodeUTF8(fileBuffer.data(), fileBuffer.size(), buffer, Locale::ECodePage::eUTF8).first != 0;
|
|
}
|
|
|
|
AUKN_SYM bool WriteString(const AuString &path, const AuString &str)
|
|
{
|
|
char bom[3]
|
|
{
|
|
'\xEF', '\xBB', '\xBF'
|
|
};
|
|
|
|
auto stream = FS::OpenWriteUnique(path);
|
|
if (!stream)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ok {};
|
|
ok = stream->Write(Memory::MemoryViewStreamRead{bom});
|
|
ok &= stream->Write(Memory::MemoryViewStreamRead{str});
|
|
stream->Flush();
|
|
|
|
return ok;
|
|
}
|
|
} |