summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md1
-rw-r--r--src/SMAPI/Framework/ContentPack.cs8
-rw-r--r--src/SMAPI/IContentPack.cs2
-rw-r--r--src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs12
4 files changed, 23 insertions, 0 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index fc6ea97f..09d444a3 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -5,6 +5,7 @@
* For modders:
* Added `IContentPack.WriteJsonFile` method.
+ * Fixed `IContentPack.ReadJsonFile` allowing non-relative paths.
## 2.7
* For players:
diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs
index ccb2b9a0..49285388 100644
--- a/src/SMAPI/Framework/ContentPack.cs
+++ b/src/SMAPI/Framework/ContentPack.cs
@@ -51,8 +51,12 @@ namespace StardewModdingAPI.Framework
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="path">The file path relative to the contnet directory.</param>
/// <returns>Returns the deserialised model, or <c>null</c> if the file doesn't exist or is empty.</returns>
+ /// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
public TModel ReadJsonFile<TModel>(string path) where TModel : class
{
+ if (!PathUtilities.IsSafeRelativePath(path))
+ throw new InvalidOperationException($"You must call {nameof(IContentPack)}.{nameof(this.ReadJsonFile)} with a relative path.");
+
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
return this.JsonHelper.ReadJsonFileIfExists(path, out TModel model)
? model
@@ -63,8 +67,12 @@ namespace StardewModdingAPI.Framework
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
/// <param name="path">The file path relative to the mod folder.</param>
/// <param name="data">The arbitrary data to save.</param>
+ /// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
public void WriteJsonFile<TModel>(string path, TModel data) where TModel : class
{
+ if (!PathUtilities.IsSafeRelativePath(path))
+ throw new InvalidOperationException($"You must call {nameof(IContentPack)}.{nameof(this.WriteJsonFile)} with a relative path.");
+
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
this.JsonHelper.WriteJsonFile(path, data);
}
diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs
index fa793b13..9ba32394 100644
--- a/src/SMAPI/IContentPack.cs
+++ b/src/SMAPI/IContentPack.cs
@@ -25,12 +25,14 @@ namespace StardewModdingAPI
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
/// <param name="path">The file path relative to the content pack directory.</param>
/// <returns>Returns the deserialised model, or <c>null</c> if the file doesn't exist or is empty.</returns>
+ /// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
TModel ReadJsonFile<TModel>(string path) where TModel : class;
/// <summary>Save data to a JSON file in the content pack's folder.</summary>
/// <typeparam name="TModel">The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types.</typeparam>
/// <param name="path">The file path relative to the mod folder.</param>
/// <param name="data">The arbitrary data to save.</param>
+ /// <exception cref="InvalidOperationException">The <paramref name="path"/> is not relative or contains directory climbing (../).</exception>
void WriteJsonFile<TModel>(string path, TModel data) where TModel : class;
/// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
diff --git a/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs
index b959f9b5..79748c25 100644
--- a/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs
+++ b/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs
@@ -63,6 +63,18 @@ namespace StardewModdingAPI.Toolkit.Utilities
return relative;
}
+ /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary>
+ /// <param name="path">The path to check.</param>
+ public static bool IsSafeRelativePath(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ return true;
+
+ return
+ !Path.IsPathRooted(path)
+ && PathUtilities.GetSegments(path).All(segment => segment.Trim() != "..");
+ }
+
/// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary>
/// <param name="str">The string to check.</param>
public static bool IsSlug(string str)