AuroraRuntime/Source/IO/FS/FS.cpp

239 lines
6.7 KiB
C++
Raw Normal View History

2021-06-27 21:25:29 +00:00
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: FS.cpp
Date: 2021-6-16
Author: Reece
***/
2021-09-30 14:57:41 +00:00
#include <Source/RuntimeInternal.hpp>
2021-06-27 21:25:29 +00:00
#include "FS.hpp"
2021-09-30 14:57:41 +00:00
#include "Source/Locale/Locale.hpp"
2021-09-06 10:58:08 +00:00
2021-06-27 21:25:29 +00:00
//#include <stdlib.h>
namespace Aurora::IO::FS
{
static bool IsMagicCharacter(char c)
{
return (c == '.') || (c == '!') || (c == '~') || (c == '?');
}
2021-07-13 13:07:40 +00:00
static void ResolveAbsolutePath(bool requireMountExpand, bool requireSanitization, const AuString &path, AuString &result)
2021-06-27 21:25:29 +00:00
{
AuString buffer;
bool isResource {};
buffer.reserve(4096);
result.reserve(4096);
/**
Resolve special character prefixes
*/
2021-07-13 13:07:40 +00:00
if ((path.size() > 2) && (requireMountExpand))
2021-06-27 21:25:29 +00:00
{
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;
}
2021-07-13 13:07:40 +00:00
if (requireSanitization)
2021-06-27 21:25:29 +00:00
{
2021-07-13 13:07:40 +00:00
/**
Quickly handle the edge case in some modern UNIX derived systems, and in
Windows UNC paths, where paths may start with '//' or '\\' respectively
*/
2021-09-06 10:58:08 +00:00
if (AuStartsWith(buffer, kDoublePathSplitter))
2021-07-13 13:07:40 +00:00
{
result += kDoublePathSplitter;
}
2021-06-27 21:25:29 +00:00
2021-07-13 13:07:40 +00:00
/**
Technically, UTF-8 strings may contain the "NUL" byte.
2021-06-27 21:25:29 +00:00
2021-07-13 13:07:40 +00:00
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.
2021-06-27 21:25:29 +00:00
2021-07-13 13:07:40 +00:00
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.
*/
2021-09-06 10:58:08 +00:00
const auto parts = AuSplitString(buffer, AuString(1, kPathSplitter), true /*ignore empty tokens*/); /// zzzz im going to SLEEP
2021-07-13 13:07:40 +00:00
for (const auto &ch : parts) // can you tell why FIO shouldn't be in hot paths yet?
2021-06-27 21:25:29 +00:00
{
2021-07-13 13:07:40 +00:00
if (ch == "..")
2021-06-27 21:25:29 +00:00
{
2021-07-13 13:07:40 +00:00
auto i = result.size() - 1;
while (i >= 0 && result[i] != kPathSplitter)
{
--i;
}
2021-06-27 21:25:29 +00:00
2021-09-06 10:58:08 +00:00
if (i >= 0)
{
2021-07-13 13:07:40 +00:00
result.resize(i);
}
2021-06-27 21:25:29 +00:00
2021-07-13 13:07:40 +00:00
continue;
}
2021-06-27 21:25:29 +00:00
2021-07-13 13:07:40 +00:00
if ((ch.size() == 1) && (IsMagicCharacter(ch[0])))
{
continue;
}
result += ch;
result += kPathSplitter;
2021-06-27 21:25:29 +00:00
}
2021-07-13 13:07:40 +00:00
auto i = result.size() - 1;
if (result[i] == kPathSplitter)
{
result.resize(i);
}
2021-06-27 21:25:29 +00:00
}
2021-07-13 13:07:40 +00:00
else // !requireSanitization
2021-06-27 21:25:29 +00:00
{
2021-07-13 13:07:40 +00:00
result = buffer;
2021-06-27 21:25:29 +00:00
}
if (isResource)
{
AuString path;
if (!FS::GetSystemResourcePath(result, path))
{
result.clear();
}
else
{
result = path;
}
}
}
void /* internal, local export */ _NormalizePath(AuString &str)
{
bool requiresExpanding = false;
2021-07-13 13:07:40 +00:00
bool requiresMountUpdate = false;
2021-06-27 21:25:29 +00:00
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 == '?'))
{
2021-07-13 13:07:40 +00:00
requiresMountUpdate = true;
2021-06-27 21:25:29 +00:00
}
}
}
// worst case -> yea have fun
2021-07-13 13:07:40 +00:00
if (requiresExpanding || requiresMountUpdate)
2021-06-27 21:25:29 +00:00
{
AuString temp;
2021-07-13 13:07:40 +00:00
ResolveAbsolutePath(requiresMountUpdate, requiresExpanding, str, temp);
2021-06-27 21:25:29 +00:00
str = temp;
}
}
2021-09-06 10:58:08 +00:00
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});
2021-09-06 10:58:08 +00:00
stream->Flush();
return ok;
}
2021-06-27 21:25:29 +00:00
}