Close the RSBK name-collision class: ASCII case folding, UTF-8 well-formedness, nested-path traversal
All three are format-locked and validated on encode and decode. Repeated known keys now reject at the root and inside an entry rather than last-wins.
This commit is contained in:
@@ -142,9 +142,7 @@ static void testEncodeRefusesWhatManifestRefuses() {
|
||||
m.entries[0].fileName = "../evil.wav";
|
||||
CHECK(!encodePackage(m).has_value());
|
||||
|
||||
// A zero-length entry cannot round-trip through the shell's filesystem
|
||||
// seam (src/shell/package's appendPayload refuses an empty payload) — the
|
||||
// format layer must never produce one.
|
||||
// See src/core/package/CLAUDE.md for the shell seam that forces this.
|
||||
PackageManifest zeroLen = fixture(1, 1);
|
||||
zeroLen.entries[1].byteLength = 0;
|
||||
CHECK(!encodePackage(zeroLen).has_value());
|
||||
@@ -185,6 +183,41 @@ static void testTruncationAtEveryByteOffsetIsMalformed() {
|
||||
CHECK(decodePackage(file, file.size() - 1).status == PackageReadability::Malformed);
|
||||
}
|
||||
|
||||
// --- header defaults ---------------------------------------------------------
|
||||
|
||||
// The 0/0 defaults are not the current ladder pair, so a header that never
|
||||
// parsed cannot be mistaken for a plausible 1/1. They mean "unset", NOT "the
|
||||
// decode failed": a decode that got past the header reports the real pair
|
||||
// alongside its Malformed verdict.
|
||||
static void testHeaderDefaultsMeanUnparsed() {
|
||||
CHECK(PackageHeader{}.formatVersion == 0);
|
||||
CHECK(PackageHeader{}.minReaderVersion == 0);
|
||||
CHECK(PackageHeader{}.writerVersion.empty());
|
||||
|
||||
// Failed before the header: bad magic, and a semver truncated mid-string.
|
||||
std::vector<std::uint8_t> badMagic = rawHeader(1, 1, "1.0.0");
|
||||
appendManifest(badMagic, "{}");
|
||||
badMagic[0] = 'Z';
|
||||
const DecodedPackage magic = decodePackage(badMagic, badMagic.size());
|
||||
CHECK(magic.status == PackageReadability::Malformed);
|
||||
CHECK(magic.header == PackageHeader{});
|
||||
|
||||
std::vector<std::uint8_t> cutSemver = rawHeader(1, 1, "1.0.0");
|
||||
cutSemver.resize(18);
|
||||
CHECK(decodePackage(cutSemver, cutSemver.size()).header == PackageHeader{});
|
||||
|
||||
// Failed after it: a same-version package with a corrupt manifest is
|
||||
// Malformed, and its header is fully populated.
|
||||
std::vector<std::uint8_t> corrupt =
|
||||
rawHeader(kPackageFormatVersion, kPackageMinReaderVersion, "1.0.0");
|
||||
appendManifest(corrupt, "not json");
|
||||
const DecodedPackage late = decodePackage(corrupt, corrupt.size());
|
||||
CHECK(late.status == PackageReadability::Malformed);
|
||||
CHECK(late.header.formatVersion == kPackageFormatVersion);
|
||||
CHECK(late.header.minReaderVersion == kPackageMinReaderVersion);
|
||||
CHECK(late.header.writerVersion == "1.0.0");
|
||||
}
|
||||
|
||||
// --- version ladder: TooNew refuses whole ------------------------------------
|
||||
|
||||
static void testTooNewProducesNoManifest() {
|
||||
@@ -365,6 +398,7 @@ int main() {
|
||||
testEncodeDecodeRoundTrip();
|
||||
testEncodeRefusesWhatManifestRefuses();
|
||||
testTruncationAtEveryByteOffsetIsMalformed();
|
||||
testHeaderDefaultsMeanUnparsed();
|
||||
testTooNewProducesNoManifest();
|
||||
testAdditiveUnparseableManifestIsTooNew();
|
||||
testNewerAdditiveFormatReads();
|
||||
|
||||
Reference in New Issue
Block a user