mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Mods: a resource default must exist and stay inside its package
The manifest check on `default` is text-only; a symlink the package ships could still point outside it. The default now resolves only when it exists with the resource's kind (folder / file) and its canonical path stays under the package's canonical root; otherwise the resource is unset and a required one blocks launch. mod_packages_test covers a cleared or removed selection restoring the default, a missing default folder, and a default that is a symlink out of the package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -873,9 +873,13 @@ A `file`, `directory` or `folder` resource may name a package-relative
|
||||
`default` (for example `default = "pack"`): a file or folder the package ships
|
||||
beside its manifest. It is used while the player has selected nothing, so a
|
||||
required resource with a default resolves out of the box; a selected path
|
||||
always wins. The default must be a safe relative path inside the package
|
||||
(no absolute paths or `..`) and cannot be combined with verified media
|
||||
(`size`/`sha256`), `input_only` or `shared_source`.
|
||||
always wins, and clearing the selection restores the default. The default
|
||||
must be a safe relative path inside the package (no absolute paths or `..`)
|
||||
and cannot be combined with verified media (`size`/`sha256`), `input_only`
|
||||
or `shared_source`. At resolve time it counts only when it exists with the
|
||||
resource's kind (folder or file) and its real path, symlinks resolved, stays
|
||||
inside the package; otherwise the resource is unset (a required one blocks
|
||||
launch).
|
||||
|
||||
|
||||
Format 8 adds engine-verified donor media to the same picker and state format:
|
||||
|
||||
@@ -861,6 +861,26 @@ std::string effective_option_value(const ModPackage& package,
|
||||
return option ? option->default_value : std::string();
|
||||
}
|
||||
|
||||
/* A resource's package-relative default, only when it exists with the
|
||||
* resource's kind (folder or file) and its real path (symlinks resolved)
|
||||
* stays inside the package: the manifest check is text-only, and a symlink
|
||||
* the package ships could point anywhere. {} otherwise (the resource is then
|
||||
* unset, so a required one blocks launch as before). */
|
||||
static fs::path package_default_path(const ModPackage& package, const ModResource& resource) {
|
||||
std::error_code ec;
|
||||
const fs::path candidate = package.root / fs::u8path(resource.default_path);
|
||||
const bool folder = resource.format == "directory" || resource.format == "folder";
|
||||
if (folder ? !fs::is_directory(candidate, ec) : !fs::is_regular_file(candidate, ec))
|
||||
return {};
|
||||
const fs::path real = fs::canonical(candidate, ec);
|
||||
if (ec) return {};
|
||||
const fs::path base = fs::canonical(package.root, ec);
|
||||
if (ec) return {};
|
||||
const auto rel = real.lexically_relative(base);
|
||||
if (rel.empty() || rel.is_absolute() || *rel.begin() == "..") return {};
|
||||
return candidate;
|
||||
}
|
||||
|
||||
fs::path effective_resource_path(const ModPackage& package,
|
||||
const ModSelection& selection,
|
||||
const std::string& feature_id,
|
||||
@@ -876,7 +896,7 @@ fs::path effective_resource_path(const ModPackage& package,
|
||||
}
|
||||
const ModResource* declared = find_resource(package, feature_id, id);
|
||||
if (declared && !declared->default_path.empty() && !package.root.empty())
|
||||
return package.root / fs::u8path(declared->default_path);
|
||||
return package_default_path(package, *declared);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -706,6 +706,46 @@ int main() {
|
||||
check(default_resource.feature_resource_path(
|
||||
"default-resource.mod", "pack", "pack") == root / "selected/pack",
|
||||
"a selected resource path must override the package default");
|
||||
{
|
||||
/* Clearing the selection (state.toml holding an empty path, or no
|
||||
* entry at all) restores the package default. */
|
||||
auto sel = default_resource.selections();
|
||||
sel["default-resource.mod"].features["pack"].resources["pack"] = "";
|
||||
auto kept = default_resource.exchange_selections(sel);
|
||||
check(default_resource.feature_resource_path(
|
||||
"default-resource.mod", "pack", "pack") == default_pack,
|
||||
"a cleared selection must restore the package default");
|
||||
sel["default-resource.mod"].features["pack"].resources.erase("pack");
|
||||
default_resource.exchange_selections(sel);
|
||||
check(default_resource.feature_resource_path(
|
||||
"default-resource.mod", "pack", "pack") == default_pack,
|
||||
"a removed selection must restore the package default");
|
||||
default_resource.exchange_selections(kept);
|
||||
}
|
||||
{
|
||||
/* The default must exist, and its real path must stay inside the
|
||||
* package: a shipped symlink pointing outside is not followed. */
|
||||
ModPackageManager probe(root);
|
||||
fs::remove_all(default_pack, ec);
|
||||
check(probe.scan(&error), error.c_str());
|
||||
check(probe.feature_resource_path("default-resource.mod", "pack", "pack").empty(),
|
||||
"a missing default folder must not count as resolved");
|
||||
check(!probe.resolve("SLUS-TEST").ok,
|
||||
"a required resource whose default is missing must block launch");
|
||||
const fs::path outside = root / "outside-pack";
|
||||
fs::create_directories(outside, ec);
|
||||
fs::create_directory_symlink(outside, default_pack, ec);
|
||||
if (!ec) {
|
||||
ModPackageManager linked(root);
|
||||
check(linked.scan(&error), error.c_str());
|
||||
check(linked.feature_resource_path("default-resource.mod", "pack", "pack").empty(),
|
||||
"a default that is a symlink out of the package must be rejected");
|
||||
check(!linked.resolve("SLUS-TEST").ok,
|
||||
"a default escaping the package must not resolve");
|
||||
fs::remove(default_pack, ec);
|
||||
}
|
||||
fs::remove_all(outside, ec);
|
||||
}
|
||||
fs::remove_all(root / "installed/default-resource.mod", ec);
|
||||
{
|
||||
const fs::path unsafe_root = root / "unsafe-default";
|
||||
|
||||
Reference in New Issue
Block a user