diff --git a/Source/Core/Common/NandPaths.cpp b/Source/Core/Common/NandPaths.cpp index ac57ccdcdc..502146f2d6 100644 --- a/Source/Core/Common/NandPaths.cpp +++ b/Source/Core/Common/NandPaths.cpp @@ -95,6 +95,13 @@ bool IsTitlePath(const std::string& path, std::optional from, u64 return true; } +static bool IsIllegalCharacter(char c) +{ + static const std::unordered_set illegal_chars = {'\"', '*', '/', ':', '<', + '>', '?', '\\', '|', '\x7f'}; + return (c >= 0 && c <= 0x1F) || illegal_chars.find(c) != illegal_chars.end(); +} + std::string EscapeFileName(const std::string& filename) { // Prevent paths from containing special names like ., .., ..., ...., and so on @@ -105,13 +112,11 @@ std::string EscapeFileName(const std::string& filename) std::string filename_with_escaped_double_underscores = ReplaceAll(filename, "__", "__5f____5f__"); // Escape all other characters that need to be escaped - static const std::unordered_set chars_to_replace = {'\"', '*', '/', ':', '<', - '>', '?', '\\', '|', '\x7f'}; std::string result; result.reserve(filename_with_escaped_double_underscores.size()); for (char c : filename_with_escaped_double_underscores) { - if ((c >= 0 && c <= 0x1F) || chars_to_replace.find(c) != chars_to_replace.end()) + if (IsIllegalCharacter(c)) result.append(fmt::format("__{:02x}__", c)); else result.push_back(c); @@ -151,4 +156,11 @@ std::string UnescapeFileName(const std::string& filename) return result; } + +bool IsFileNameSafe(const std::string_view filename) +{ + return !filename.empty() && + !std::all_of(filename.begin(), filename.end(), [](char c) { return c == '.'; }) && + std::none_of(filename.begin(), filename.end(), IsIllegalCharacter); +} } // namespace Common diff --git a/Source/Core/Common/NandPaths.h b/Source/Core/Common/NandPaths.h index 97fd0649c8..2d40301707 100644 --- a/Source/Core/Common/NandPaths.h +++ b/Source/Core/Common/NandPaths.h @@ -43,4 +43,6 @@ std::string EscapeFileName(const std::string& filename); std::string EscapePath(const std::string& path); // Reverses escaping done by EscapeFileName std::string UnescapeFileName(const std::string& filename); +// Tests for a file name being "safe" as per the escaping defined in EscapeFileName +bool IsFileNameSafe(const std::string_view filename); } // namespace Common diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index f073e70f17..065afc801e 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -905,7 +905,8 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) std::string file_name; packet >> file_name; - if (!DecompressPacketIntoFile(packet, path + DIR_SEP + file_name)) + if (!Common::IsFileNameSafe(file_name) || + !DecompressPacketIntoFile(packet, path + DIR_SEP + file_name)) { SyncSaveDataResponse(false); return 0;