Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 54 additions & 11 deletions soh/soh/Enhancements/Presets/Presets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,38 @@

namespace fs = std::filesystem;

/**
* Forbidden filenames
*
* Windows, macOS, and Linux all forbid certain characters in their filenames.
*
* Replace them to prevent crashes from invalid paths (e.g, "test :)" creating an NTFS Alternate Data Stream
* instead of a regular file).
*/
static std::string SanitizeFilename(const std::string& name) {
std::string result;
result.reserve(name.size());
for (const char c : name) {
if (c == '<' || c == '>' || c == ':' || c == '"' ||
c == '/' || c == '\\' || c == '|' || c == '?' || c == '*' ||
c < 32) {
result += '_';
} else {
result += c;
}
}

while (!result.empty() && (result.back() == '.' || result.back() == ' ')) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. strip seems unnecessary, is it only to block . & ..? in that case we can just check for those explicitly in following condition

result.pop_back();
}

if (result.empty()) {
result = "Unnamed";
}

return result;
}

namespace SohGui {
extern std::shared_ptr<SohMenu> mSohMenu;
} // namespace SohGui
Expand Down Expand Up @@ -72,7 +104,7 @@ static BlockInfo blockInfo[PRESET_SECTION_MAX] = {
};

std::string FormatPresetPath(std::string name) {
return fmt::format("{}/{}.json", presetFolder, name);
return fmt::format("{}/{}.json", presetFolder, SanitizeFilename(name));
}

void applyPreset(std::string presetName, std::vector<PresetSection> includeSections) {
Expand Down Expand Up @@ -217,16 +249,19 @@ void LoadPresets() {
}
if (fs::exists(presetFolder)) {
for (auto const& preset : fs::directory_iterator(presetFolder)) {
std::ifstream ifs(preset.path());

auto json = nlohmann::json::parse(ifs);
if (!json.contains("presetName")) {
spdlog::error(fmt::format("Attempted to load file {} as a preset, but was not a preset file.",
preset.path().filename().string()));
} else {
ParsePreset(json, preset.path().filename().stem().string());
try {
std::ifstream ifs(preset.path());
if (auto json = nlohmann::json::parse(ifs); !json.contains("presetName")) {
spdlog::error(fmt::format("Attempted to load file {} as a preset, but was not a preset file.",
preset.path().filename().string()));
} else {
ParsePreset(json, preset.path().filename().stem().string());
}

ifs.close();
} catch (const std::exception& e) {
spdlog::error("Failed to load preset {}: {}", preset.path().filename().string(), e.what());
}
ifs.close();
}
}
auto initData = std::make_shared<Ship::ResourceInitData>();
Expand All @@ -252,8 +287,16 @@ void SavePreset(std::string& presetName) {
}
presets[presetName].presetValues["presetName"] = presetName;
presets[presetName].presetValues["fileType"] = FILE_TYPE_PRESET;

std::string safeFilename = SanitizeFilename(presetName);
std::ofstream file(
fmt::format("{}/{}.json", Ship::Context::GetInstance()->LocateFileAcrossAppDirs("presets"), presetName));
fmt::format("{}/{}.json", Ship::Context::GetInstance()->LocateFileAcrossAppDirs("presets"), safeFilename));

if (!file.is_open()) {
spdlog::error("Failed to save preset '{}': Could not create file", presetName);
return;
}

file << presets[presetName].presetValues.dump(4);
file.close();
LoadPresets();
Expand Down
Loading