Skip to content

Commit

Permalink
[Fix] Removed symlink related issues on any path sanitization
Browse files Browse the repository at this point in the history
  • Loading branch information
RhenaudTheLukark committed Sep 11, 2024
1 parent 253fdbb commit bd50ae0
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 56 deletions.
98 changes: 62 additions & 36 deletions Assets/Plugins/MoonSharp/Interpreter/CoreLib/LoadModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,16 @@ public static DynValue __require_clr_impl(ScriptExecutionContext executionContex
public static string DefaultDataPath { get { return Path.Combine(DataRoot, "Default" + Path.DirectorySeparatorChar); } }
public static string ModFolder;

/// <summary>
/// Checks if a file exists in CYF's Default or Mods folder and returns a clean path to it.
/// </summary>
/// <param name="fileName">Path to the file to require, relative or absolute. Will also contain the clean path to the existing resource if found.</param>
/// <param name="pathSuffix">String to add to the tested path to check in the given folder.</param>
/// <param name="errorOnFailure">Defines whether the error screen should be displayed if the file isn't in either folder.</param>
/// <param name="needsAbsolutePath">True if you want to get the absolute path to the file, false otherwise.</param>
/// <param name="needsToExist">True if the file you're looking for needs to exist.</param>
/// <returns>True if the sanitization was successful, false otherwise.</returns>
public static bool RequireFile(ref string fileName, string pathSuffix, bool errorOnFailure = true, bool needsAbsolutePath = false, bool needsToExist = true) {
/// <summary>
/// Checks if a file exists in CYF's Default or Mods folder and returns a clean path to it.
/// </summary>
/// <param name="fileName">Path to the file to require, relative or absolute. Will also contain the clean path to the existing resource if found.</param>
/// <param name="pathSuffix">String to add to the tested path to check in the given folder.</param>
/// <param name="needsToExist">True if the file you're looking for needs to exist.</param>
/// <param name="needsAbsolutePath">True if you want to get the absolute path to the file, false otherwise.</param>
/// <param name="errorOnFailure">Defines whether the error screen should be displayed if the file isn't in either folder.</param>
/// <returns>True if the sanitization was successful, false otherwise.</returns>
public static bool RequireFile(ref string fileName, string pathSuffix, bool needsToExist = true, bool needsAbsolutePath = false, bool errorOnFailure = true) {
string baseFileName = fileName;
string fileNameMod, fileNameDefault;
// Get the presumed absolute path to the mod and default folder to this resource if it's a relative path
Expand All @@ -297,12 +297,12 @@ public static bool RequireFile(ref string fileName, string pathSuffix, bool erro
fileNameDefault = fileName;
}

string errorString = "";
// Check if the resource exists using the mod path
string error;
try {
string modPath = pathSuffix;
ExplorePath(ref fileNameMod, ref modPath);
// Keep the path to the mod folder in case of failure (used to open nonexistent files!)
// Keep the path to the mod folder in case of failure (used to open non-existent files!)
fileName = fileNameMod;
if (needsToExist && !new FileInfo(fileNameMod).Exists) throw new CYFException("The file " + fileNameMod + " doesn't exist.");

Expand All @@ -311,25 +311,33 @@ public static bool RequireFile(ref string fileName, string pathSuffix, bool erro
Uri uriRel = new Uri(modPath + Path.DirectorySeparatorChar).MakeRelativeUri(new Uri(fileName));
fileName = Uri.UnescapeDataString(uriRel.OriginalString);
return true;
} catch (Exception e) { error = e.Message; }

// Check if the resource exists using the default path
try {
string defaultPath = pathSuffix;
ExplorePath(ref fileNameDefault, ref defaultPath);
if (needsToExist && !new FileInfo(fileNameDefault).Exists) throw new CYFException("The file " + fileNameDefault + " doesn't exist.");
fileName = fileNameDefault;

if (needsAbsolutePath) return true;

Uri uriRel = new Uri(defaultPath).MakeRelativeUri(new Uri(fileName));
fileName = Uri.UnescapeDataString(uriRel.OriginalString);
return true;
} catch (Exception e) { error = "Mod path error: " + error + "\n\nDefault path error: " + e.Message; }

if (needsToExist && errorOnFailure)
throw new CYFException("Attempted to load " + baseFileName + " from either a mod or default directory, but it was missing in both.\n\n" + error);
return false;
} catch (Exception e) { errorString = e.Message; }

if (!errorString.Contains("µImportantµ")) {
// Check if the resource exists using the default path
try {
string defaultPath = pathSuffix;
ExplorePath(ref fileNameDefault, ref defaultPath);
if (needsToExist && !new FileInfo(fileNameDefault).Exists) throw new CYFException("The file " + fileNameDefault + " doesn't exist.");
fileName = fileNameDefault;

if (needsAbsolutePath) return true;

Uri uriRel = new Uri(defaultPath).MakeRelativeUri(new Uri(fileName));
fileName = Uri.UnescapeDataString(uriRel.OriginalString);
return true;
} catch (Exception e) { errorString = "Mod path error: " + errorString + "\n\nDefault path error: " + e.Message; }
}

if (needsToExist && !errorString.Contains("µImportantµ"))
errorString = "Attempted to load \"" + baseFileName + "\" from either a mod or default directory, but it was missing in both.\n\n" + errorString;
else
errorString = "Error while trying to fetch a resource with the given path \"" + baseFileName + "\".\n\n" + errorString;

// Display important errors
if ((errorOnFailure && needsToExist) || errorString.Contains("µImportantµ"))
throw new CYFException(errorString.Replace("µImportantµ", ""));
return false;
}

/// <summary>
Expand All @@ -355,25 +363,43 @@ public static void ExplorePath(ref string fullPath, ref string pathSuffix) {

fullPath = fullPath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);

// Get the folder containing the resource to load
// Get the folder containing the resource to load and check if it exists
string fileName = fullPath.Substring(fullPath.LastIndexOf(Path.DirectorySeparatorChar) + 1);
DirectoryInfo endFolder = new DirectoryInfo(fullPath.Substring(0, fullPath.LastIndexOf(Path.DirectorySeparatorChar)));
if (!endFolder.Exists)
throw new CYFException("The path \"" + endFolder.FullName + "\" (file is \"" + fullPath + "\") doesn't exist.");

// Check if the final directory is a child of CYF's root directory
if (!endFolder.FullName.StartsWith(Path.Combine(DataRoot, "Mods")) && !endFolder.FullName.StartsWith(Path.Combine(DataRoot, "Default")))
throw new CYFException("The folder \"" + endFolder.FullName + "\" isn't inside of CYF's allowed folders (CYF's path is \"" + DataRoot + "\"). Please only fetch files from inside CYF's Mods or Default folders.");
throw new CYFException("µImportantµThe folder \"" + endFolder.FullName + "\" isn't inside of CYF's allowed folders (CYF's path is \"" + DataRoot + "\"). Please only fetch files from inside CYF's Mods or Default folders.");

// Check for symlink usage, and throw an error if there's one
DirectoryInfo fullPathInfo = new DirectoryInfo(fullPath);
while (!DirectoryPathsEqual(fullPathInfo, new DirectoryInfo(DataRoot))) {
if ((fullPathInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
throw new CYFException("µImportantµSymbolic link detected in \"" + fullPathInfo.FullName + "\". Please do not use symbolic links in Create Your Frisk.");
fullPathInfo = fullPathInfo.Parent;
};

fullPath = endFolder.FullName;
if (!fullPath.EndsWith(Path.DirectorySeparatorChar.ToString())) fullPath += Path.DirectorySeparatorChar;
if (!pathSuffix.EndsWith(Path.DirectorySeparatorChar.ToString())) pathSuffix += Path.DirectorySeparatorChar;

fullPath = Path.Combine(fullPath, fileName).Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
}
}
}

public static string NormalizePath(string path) {
return Path.GetFullPath(new Uri(path).LocalPath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToUpperInvariant();
}

public static bool DirectoryPathsEqual(DirectoryInfo a, DirectoryInfo b) {
return NormalizePath(a.FullName) == NormalizePath(b.FullName);
}
}

public class CYFException : ScriptRuntimeException {
public class CYFException : ScriptRuntimeException {
public CYFException(string message) : base(message) { }
}
}
14 changes: 7 additions & 7 deletions Assets/Scripts/Device/Misc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,19 @@ public LuaSpriteShader ScreenShader {

public bool FileExists(string path) {
if (!path.StartsWith(FileLoader.DataRoot)) path = path.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
FileLoader.SanitizePath(ref path, "", false, true, false);
FileLoader.SanitizePath(ref path, "", false);
return File.Exists(path);
}

public bool DirExists(string path) {
if (!path.StartsWith(FileLoader.DataRoot)) path = path.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
FileLoader.SanitizePath(ref path, "", false, true, false);
FileLoader.SanitizePath(ref path, "", false);
return Directory.Exists(path);
}

public bool CreateDir(string path) {
if (!path.StartsWith(FileLoader.DataRoot)) path = path.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
FileLoader.SanitizePath(ref path, "", false, true, false);
FileLoader.SanitizePath(ref path, "", false);
if (Directory.Exists(path)) return false;
Directory.CreateDirectory(path);
return true;
Expand All @@ -183,15 +183,15 @@ public bool MoveDir(string path, string newPath) {
if (!newPath.StartsWith(FileLoader.DataRoot)) newPath = newPath.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
if (!DirExists(path) || DirExists(newPath) || !PathValid(path)) return false;

FileLoader.SanitizePath(ref path, "", false, true, false);
FileLoader.SanitizePath(ref newPath, "", false, true, false);
FileLoader.SanitizePath(ref path, "");
FileLoader.SanitizePath(ref newPath, "", false);
Directory.Move(path, newPath);
return true;
}

public bool RemoveDir(string path, bool force = false) {
if (!path.StartsWith(FileLoader.DataRoot)) path = path.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
FileLoader.SanitizePath(ref path, "", false, true, false);
FileLoader.SanitizePath(ref path, "");

if (!Directory.Exists(path)) return false;
try { Directory.Delete(path, force); }
Expand All @@ -205,7 +205,7 @@ public string[] ListDir(string path, bool getFolders = false) {

string origPath = path;
if (!path.StartsWith(FileLoader.DataRoot)) path = path.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
FileLoader.SanitizePath(ref path, "", false, true, false);
FileLoader.SanitizePath(ref path, "");
if (!Directory.Exists(path))
throw new CYFException("Invalid path:\n\n\"" + origPath + "\"");

Expand Down
7 changes: 3 additions & 4 deletions Assets/Scripts/Lua/CLRBindings/LuaFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ public LuaFile(string path, string mode = "rw") {
if (!path.StartsWith(FileLoader.DataRoot)) path = path.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
if (path == null) throw new CYFException("Cannot open a file with a nil path.");
if (mode != "r" && mode != "w" && mode != "rw" && mode != "wr") throw new CYFException("A file's open mode can only be \"r\" (read), \"w\" (write) or \"rw\" (read + write).");
if (!FileLoader.SanitizePath(ref path, "", true, false, false)) throw new CYFException("You can't open a file outside of CYF's folder.");
if (!FileLoader.SanitizePath(ref path, "", false, true) && mode == "r") throw new CYFException("You can't open a file that doesn't exist (" + path + ") in read-only mode.");
if (!FileLoader.SanitizePath(ref path, "", true, true, false) && mode == "r") throw new CYFException("You can't open a file that doesn't exist (" + path + ") in read-only mode.");
if (!Directory.Exists(path.Substring(0, path.Length - Path.GetFileName(path).Length))) throw new CYFException("Invalid path:\n\n\"" + path + "\"");

filePath = path;
Expand Down Expand Up @@ -108,7 +107,7 @@ public void Delete() {

public void Move(string newPath) {
string origNewPath = newPath;
FileLoader.SanitizePath(ref newPath, "", false, true, false);
FileLoader.SanitizePath(ref newPath, "", false, true);

if (!File.Exists(filePath)) throw new CYFException("The file at the path \"" + filePath + "\" doesn't exist, so you can't move it.");
if (File.Exists(newPath)) throw new CYFException("The file at the path \"" + origNewPath + "\" already exists.");
Expand All @@ -122,7 +121,7 @@ public void Move(string newPath) {

public void Copy(string newPath, bool overwrite = false) {
string origNewPath = newPath;
FileLoader.SanitizePath(ref newPath, "", false, true, false);
FileLoader.SanitizePath(ref newPath, "", false, true);

if (!File.Exists(filePath)) throw new CYFException("The file at the path \"" + filePath + "\" doesn't exist, so you can't copy it.");
if (File.Exists(newPath) && !overwrite) throw new CYFException("The file at the path \"" + origNewPath + "\" already exists.");
Expand Down
10 changes: 5 additions & 5 deletions Assets/Scripts/Lua/FileLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ public static bool GetRelativePathWithoutExtension(ref string fileName, string p
/// </summary>
/// <param name="fileName">Path to the file to require, relative or absolute. Will also contain the clean path to the existing resource if found.</param>
/// <param name="pathSuffix">String to add to the tested path to check in the given folder.</param>
/// <param name="errorOnFailure">Defines whether the error screen should be displayed if the file isn't in either folder.</param>
/// <param name="needsAbsolutePath">True if you want to get the absolute path to the file, false otherwise.</param>
/// <param name="needsToExist">True if the file you are looking for needs to exist. In this case, the function will return false if it doesn't exist.</param>
/// <param name="needsAbsolutePath">True if you want to get the absolute path to the file, false otherwise.</param>
/// <param name="errorOnFailure">Defines whether the error screen should be displayed if the file isn't in either folder.</param>
/// <returns>True if the sanitization was successful, false otherwise.</returns>
public static bool SanitizePath(ref string fileName, string pathSuffix, bool errorOnFailure = true, bool needsAbsolutePath = false, bool needsToExist = true) {
public static bool SanitizePath(ref string fileName, string pathSuffix, bool needsToExist = true, bool needsAbsolutePath = false, bool errorOnFailure = true) {
string pathToTest = fileName.Contains(DataRoot) ? fileName : pathSuffix + fileName;
fileName = fileName.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);

Expand All @@ -165,11 +165,11 @@ public static bool SanitizePath(ref string fileName, string pathSuffix, bool err

// Sanitize if path from CYF root, need to transform a relative path to an absolute path and vice-versa, or if there's an occurence of ..
bool leadingSlash = fileName.StartsWith(Path.DirectorySeparatorChar.ToString()) && !fileName.Contains(DataRoot);
if (!LoadModule.RequireFile(ref fileName, pathSuffix, !leadingSlash && errorOnFailure, needsAbsolutePath, needsToExist))
if (!LoadModule.RequireFile(ref fileName, pathSuffix, needsToExist, needsAbsolutePath, errorOnFailure))
if (leadingSlash) {
// Passthrough: Remove the leading slash if the file wasn't found
if (!fileName.StartsWith(DataRoot)) fileName = fileName.Replace('\\', '/').TrimStart('/'); // TODO: Remove this for 0.7
if (!LoadModule.RequireFile(ref fileName, pathSuffix, errorOnFailure, needsAbsolutePath, needsToExist))
if (!LoadModule.RequireFile(ref fileName, pathSuffix, needsToExist, needsAbsolutePath, errorOnFailure))
return false;
} else
return false;
Expand Down
4 changes: 2 additions & 2 deletions Assets/Scripts/Lua/StaticRegistries/AudioClipRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ private static AudioClip Get(string key, string prefix) {
key = key.TrimStart('/', '\\');
string oggKey = key + (key.ToLower().EndsWith(".ogg") ? "" : ".ogg");
string wavKey = key + (key.ToLower().EndsWith(".wav") ? "" : ".wav");
if (!FileLoader.SanitizePath(ref oggKey, "", false)) {
FileLoader.SanitizePath(ref wavKey, "", !GlobalControls.retroMode);
if (!FileLoader.SanitizePath(ref oggKey, "", true, false, false)) {
FileLoader.SanitizePath(ref wavKey, "", true, false, !GlobalControls.retroMode);
key = wavKey;
} else
key = oggKey;
Expand Down
2 changes: 1 addition & 1 deletion Assets/Scripts/Overworld/MapLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public static class MapLoader {
public static void LoadMap(string path) {
XmlDocument xml = new XmlDocument();
string xmlPath = path + ".xml";
if (FileLoader.SanitizePath(ref xmlPath, "Sprites/UI/Fonts/", false))
if (!FileLoader.SanitizePath(ref xmlPath, "Maps"))
return;
xml.Load(xmlPath);
}
Expand Down
2 changes: 1 addition & 1 deletion Assets/Scripts/Text/TextManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ private void PreCreateControlCommand(string command, bool movementCommand = fals
case "font":
UnderFont uf = SpriteFontRegistry.Get(cmds[1]);
if (uf == null) {
UnitaleUtil.DisplayLuaError("", "[font:x] usage - The font \"" + cmds[1] + "\" doesn't exist.\nYou should check if you made a typo, or if the font really is in your mod.", true);
UnitaleUtil.DisplayLuaError("[font:x] usage", "The font \"" + cmds[1] + "\" doesn't exist.\nYou should check if you made a typo, or if the font really is in your mod.", false);
break;
}
SetFont(uf, true);
Expand Down

0 comments on commit bd50ae0

Please sign in to comment.