Tighten RSBK package-format validation for review remediation
Reject NUL/control bytes and Windows-hostile names in entry names, relax the over-broad ".." substring ban to component-only, close the trailing-garbage gap on empty manifests, and relocate the package CMake subdirectory to its ladder home.
This commit is contained in:
@@ -5,11 +5,6 @@
|
||||
#include "core/json/json.h"
|
||||
#include "core/package/package_format.h"
|
||||
|
||||
// Duplicate entry names are rejected in both directions: two payloads landing
|
||||
// on one destination name is incoherent, and on import it would be a silent
|
||||
// overwrite. Sample-id rules (remap, collision) are deliberately NOT enforced
|
||||
// here — they are the import plan's decisions, not the codec's.
|
||||
|
||||
namespace reasampler::package {
|
||||
|
||||
namespace {
|
||||
@@ -17,6 +12,8 @@ namespace {
|
||||
using json::numToStr;
|
||||
using ObjWriter = json::Writer;
|
||||
|
||||
// Shared by serializeManifest and deserializeManifest — see this directory's
|
||||
// CLAUDE.md for why duplicate names are rejected both ways.
|
||||
bool duplicateName(const std::vector<PackageEntry>& entries) {
|
||||
for (std::size_t i = 0; i < entries.size(); ++i)
|
||||
for (std::size_t j = i + 1; j < entries.size(); ++j)
|
||||
@@ -155,37 +152,40 @@ bool parseEntry(json::Reader& r, PackageEntry& e) {
|
||||
bool parseManifest(json::Reader& r, PackageManifest& m) {
|
||||
if (!r.consume('{')) return false;
|
||||
r.skipWs();
|
||||
if (r.consume('}')) return true; // empty object: a valid empty manifest
|
||||
bool haveEntries = false;
|
||||
if (!r.consume('}')) { // not the empty-object shortcut: parse the members
|
||||
do {
|
||||
std::string key;
|
||||
if (!r.parseKey(key)) return false;
|
||||
|
||||
do {
|
||||
std::string key;
|
||||
if (!r.parseKey(key)) return false;
|
||||
|
||||
if (key == "bankName") {
|
||||
if (!r.parseString(m.bankDisplayName)) return false;
|
||||
} else if (key == "exported") {
|
||||
if (!r.parseInt64(m.exportTimestamp)) return false;
|
||||
} else if (key == "entries") {
|
||||
if (!r.consume('[')) return false;
|
||||
r.skipWs();
|
||||
if (!r.consume(']')) {
|
||||
do {
|
||||
PackageEntry e;
|
||||
if (!parseEntry(r, e)) return false;
|
||||
m.entries.push_back(std::move(e));
|
||||
} while (r.consume(','));
|
||||
if (!r.consume(']')) return false;
|
||||
if (key == "bankName") {
|
||||
if (!r.parseString(m.bankDisplayName)) return false;
|
||||
} else if (key == "exported") {
|
||||
if (!r.parseInt64(m.exportTimestamp)) return false;
|
||||
} else if (key == "entries") {
|
||||
if (haveEntries) return false; // a repeated key must not accumulate
|
||||
haveEntries = true;
|
||||
if (!r.consume('[')) return false;
|
||||
r.skipWs();
|
||||
if (!r.consume(']')) {
|
||||
do {
|
||||
PackageEntry e;
|
||||
if (!parseEntry(r, e)) return false;
|
||||
m.entries.push_back(std::move(e));
|
||||
} while (r.consume(','));
|
||||
if (!r.consume(']')) return false;
|
||||
}
|
||||
} else if (key == "slots") {
|
||||
if (!parseSlots(r, m.slots)) return false;
|
||||
} else {
|
||||
if (!r.skipValue()) return false; // forward-compat unknown keys
|
||||
}
|
||||
} else if (key == "slots") {
|
||||
if (!parseSlots(r, m.slots)) return false;
|
||||
} else {
|
||||
if (!r.skipValue()) return false; // forward-compat unknown keys
|
||||
}
|
||||
} while (r.consume(','));
|
||||
} while (r.consume(','));
|
||||
|
||||
if (!r.consume('}')) return false;
|
||||
if (!r.consume('}')) return false;
|
||||
}
|
||||
r.skipWs();
|
||||
if (!r.eof()) return false; // trailing garbage
|
||||
if (!r.eof()) return false; // trailing garbage — even after an empty object
|
||||
return !duplicateName(m.entries);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user