// Standalone tests for reasampler::package's format contract — no REAPER, no // test framework. Pins the version-ladder classification (both integers, every // branch) and the three naming rules: the entry-name rule, the ASCII-folding // name equivalence, and the nested-path traversal guard. #include "../src/core/package/package_format.h" #include #include using namespace reasampler::package; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // --- classifyPackageVersion -------------------------------------------------- static void testClassifyReadable() { CHECK(classifyPackageVersion(kPackageFormatVersion, kPackageMinReaderVersion) == PackageReadability::Readable); // The additive-forward-compat direction: a newer writer whose minReader // still reaches back to this build reads fine. CHECK(classifyPackageVersion(kPackageFormatVersion + 5, kPackageMinReaderVersion) == PackageReadability::Readable); // Boundary: minReader exactly this build's format version. CHECK(classifyPackageVersion(kPackageFormatVersion + 1, kPackageFormatVersion) == PackageReadability::Readable); } static void testClassifyTooNew() { // Boundary: one past this build's format version refuses. CHECK(classifyPackageVersion(kPackageFormatVersion + 1, kPackageFormatVersion + 1) == PackageReadability::TooNew); CHECK(classifyPackageVersion(99, 42) == PackageReadability::TooNew); } static void testClassifyMalformed() { // Zero versions: no honest writer emits them (the ladder starts at 1). CHECK(classifyPackageVersion(0, 0) == PackageReadability::Malformed); CHECK(classifyPackageVersion(1, 0) == PackageReadability::Malformed); CHECK(classifyPackageVersion(0, 1) == PackageReadability::Malformed); // A writer cannot require a reader newer than what it wrote. CHECK(classifyPackageVersion(1, 2) == PackageReadability::Malformed); // Incoherence outranks TooNew: even with both above this build, minReader > // formatVersion is Malformed, not a refusal message. CHECK(classifyPackageVersion(5, 9) == PackageReadability::Malformed); } // --- isValidEntryName -------------------------------------------------------- static void testEntryNameAccepts() { CHECK(isValidEntryName("kick.wav")); CHECK(isValidEntryName("Snare 03 (wet).wav")); CHECK(isValidEntryName("no-extension")); CHECK(isValidEntryName(".hidden")); // a leading dot is a bare name CHECK(isValidEntryName("a.b.c.wav")); // single dots are fine CHECK(isValidEntryName(std::string(kMaxEntryNameBytes, 'x'))); // at the cap // Legal names containing a ".." substring that is not the whole name: a // name can only ever be one path component (separators are banned), so // ".." as a component is the only expressible traversal. CHECK(isValidEntryName("take..final.wav")); CHECK(isValidEntryName("loop...wav")); CHECK(isValidEntryName("a..b")); } static void testEntryNameRejectsSeparatorsAndDots() { CHECK(!isValidEntryName("")); CHECK(!isValidEntryName(".")); CHECK(!isValidEntryName("..")); CHECK(!isValidEntryName("..\\evil.wav")); CHECK(!isValidEntryName("../evil.wav")); CHECK(!isValidEntryName("dir/inner.wav")); CHECK(!isValidEntryName("dir\\inner.wav")); CHECK(!isValidEntryName("/rooted.wav")); CHECK(!isValidEntryName("\\rooted.wav")); // Embedded NUL: every plausible filesystem call (ofstream, fopen, // CreateFileW off .c_str()) truncates at it, so two names differing only // after the NUL would collide on one file. CHECK(!isValidEntryName(std::string("a\0b.wav", 7))); // Other control bytes (newline here) are equally hostile to logs/UI. CHECK(!isValidEntryName("a\nb.wav")); } static void testEntryNameRejectsAbsolutePrefixes() { CHECK(!isValidEntryName("C:\\abs.wav")); CHECK(!isValidEntryName("C:/abs.wav")); CHECK(!isValidEntryName("c:relative-to-drive.wav")); // ':' bans drive forms CHECK(!isValidEntryName("\\\\server\\share.wav")); // UNC CHECK(!isValidEntryName(std::string(kMaxEntryNameBytes + 1, 'x'))); // over cap } static void testEntryNameRejectsWindowsHostileNames() { // Reserved characters. CHECK(!isValidEntryName("a*b.wav")); CHECK(!isValidEntryName("a?b.wav")); CHECK(!isValidEntryName("a|b.wav")); CHECK(!isValidEntryName("a.wav")); CHECK(!isValidEntryName("\"q\".wav")); // Trailing dot or space (silently stripped at creation on Windows). CHECK(!isValidEntryName("trailing ")); CHECK(!isValidEntryName("trailing.")); CHECK(!isValidEntryName(" ")); CHECK(!isValidEntryName(" ")); // DOS device names, case-insensitive, with and without an extension. CHECK(!isValidEntryName("NUL")); CHECK(!isValidEntryName("CON")); CHECK(!isValidEntryName("con.wav")); CHECK(!isValidEntryName("PRN")); CHECK(!isValidEntryName("AUX")); CHECK(!isValidEntryName("COM1")); CHECK(!isValidEntryName("com1.txt")); CHECK(!isValidEntryName("LPT1")); // The superscript device forms (COM¹ COM² COM³ LPT¹ LPT² LPT³ in UTF-8): // Windows reads those as digits in a device name, so "COM².wav" is COM2. CHECK(!isValidEntryName("COM\xC2\xB9.wav")); CHECK(!isValidEntryName("com\xC2\xB2")); CHECK(!isValidEntryName("COM\xC2\xB3.wav")); CHECK(!isValidEntryName("LPT\xC2\xB9")); CHECK(!isValidEntryName("lpt\xC2\xB2.txt")); CHECK(!isValidEntryName("LPT\xC2\xB3.wav")); // Not a device name: a real filename that merely starts with one, and the // zero forms, which Windows does not reserve. CHECK(isValidEntryName("console.wav")); CHECK(isValidEntryName("COM0.wav")); CHECK(isValidEntryName("LPT0")); // Nor does a superscript past 3 name a device. CHECK(isValidEntryName("COM\xE2\x81\xB4.wav")); // U+2074 SUPERSCRIPT FOUR } // --- isValidEntryName: UTF-8 well-formedness --------------------------------- static void testEntryNameAcceptsWellFormedUtf8() { CHECK(isValidEntryName("caf\xC3\xA9.wav")); // 2-byte: é CHECK(isValidEntryName("\xE2\x99\xAA.wav")); // 3-byte: ♪ CHECK(isValidEntryName("\xF0\x9F\x8E\xB5.wav")); // 4-byte: 🎵 CHECK(isValidEntryName("\xEF\xBB\xBF.wav")); // U+FEFF, ugly but well-formed CHECK(isValidEntryName("\xF4\x8F\xBF\xBF.wav")); // U+10FFFF, the last code point } static void testEntryNameRejectsIllFormedUtf8() { // Two names differing ONLY in their invalid bytes: a host converting to // UTF-16 substitutes U+FFFD for both by default, collapsing them onto one // file — the duplicate-name collision the manifest cannot otherwise see. CHECK(!isValidEntryName("a\x80.wav")); // stray continuation byte CHECK(!isValidEntryName("a\x81.wav")); // Structural: truncated sequences (a lead byte the name ends inside). CHECK(!isValidEntryName("a\xC3")); CHECK(!isValidEntryName("a\xE2\x99")); CHECK(!isValidEntryName("a\xF0\x9F\x8E")); // A lead byte followed by a non-continuation. CHECK(!isValidEntryName("a\xC3\x41.wav")); // Overlong encodings: an alternate spelling of an ASCII byte we ban. CHECK(!isValidEntryName("a\xC0\xAF.wav")); // overlong '/' CHECK(!isValidEntryName("a\xC0\x80.wav")); // overlong NUL CHECK(!isValidEntryName("a\xE0\x80\xAF.wav")); // overlong '/', 3-byte CHECK(!isValidEntryName("a\xF0\x80\x80\xAF.wav")); // overlong '/', 4-byte // Surrogate halves: no code point, and unrepresentable in UTF-16. CHECK(!isValidEntryName("a\xED\xA0\x80.wav")); // U+D800 CHECK(!isValidEntryName("a\xED\xBF\xBF.wav")); // U+DFFF // Past U+10FFFF, and the 5/6-byte leads that never encode anything. CHECK(!isValidEntryName("a\xF4\x90\x80\x80.wav")); // U+110000 CHECK(!isValidEntryName("a\xF5\x80\x80\x80.wav")); CHECK(!isValidEntryName("a\xFC\x80\x80\x80\x80\x80.wav")); CHECK(!isValidEntryName("a\xFF.wav")); } // --- sameEntryName ----------------------------------------------------------- static void testSameEntryNameFoldsAsciiCase() { // A bank authored on a case-sensitive filesystem produces this pair // honestly; Windows and default APFS would extract both onto one file. CHECK(sameEntryName("Kick.wav", "kick.wav")); CHECK(sameEntryName("KICK.WAV", "kick.wav")); CHECK(sameEntryName("kick.wav", "kick.wav")); CHECK(!sameEntryName("kick.wav", "snare.wav")); CHECK(!sameEntryName("kick.wav", "kick.wave")); // length alone decides CHECK(!sameEntryName("", "a")); CHECK(sameEntryName("", "")); // ASCII only: "é" vs "É" are two names here (the NFC/NFD limitation this // shares — see this directory's CLAUDE.md). CHECK(!sameEntryName("caf\xC3\xA9.wav", "caf\xC3\x89.wav")); // Only the letters fold — the bytes flanking the ASCII range must not. CHECK(!sameEntryName("a[b", "a{b")); // 0x5B vs 0x7B, 'Z'+1 and 'z'+1 CHECK(!sameEntryName("a@b", "a`b")); // 0x40 vs 0x60, 'A'-1 and 'a'-1 } // --- isValidNestedSamplePath ------------------------------------------------- static void testNestedSamplePathAcceptsRelative() { CHECK(isValidNestedSamplePath("a.wav")); CHECK(isValidNestedSamplePath("reasampler_bank/kick.wav")); CHECK(isValidNestedSamplePath("reasampler_bank\\kick.wav")); CHECK(isValidNestedSamplePath("deep/dir/tree/a.wav")); // A ".." that is not a whole component is an ordinary name. CHECK(isValidNestedSamplePath("take..final/a.wav")); CHECK(isValidNestedSamplePath("bank/..hidden")); CHECK(isValidNestedSamplePath("a..b")); } static void testNestedSamplePathRejectsTraversalAndAbsolute() { // BankModel::add catches only the absolute forms, so traversal reaches the // format unless this rule stops it. CHECK(!isValidNestedSamplePath("..")); CHECK(!isValidNestedSamplePath("../evil.wav")); CHECK(!isValidNestedSamplePath("..\\evil.wav")); CHECK(!isValidNestedSamplePath("bank/../../evil.wav")); CHECK(!isValidNestedSamplePath("bank\\..\\evil.wav")); CHECK(!isValidNestedSamplePath("bank/..")); CHECK(!isValidNestedSamplePath("bank/../")); // Everything util::isAbsolutePath already catches. CHECK(!isValidNestedSamplePath("/rooted.wav")); CHECK(!isValidNestedSamplePath("\\rooted.wav")); CHECK(!isValidNestedSamplePath("C:/abs.wav")); CHECK(!isValidNestedSamplePath("C:\\abs.wav")); CHECK(!isValidNestedSamplePath("c:relative-to-drive.wav")); CHECK(!isValidNestedSamplePath("\\\\server\\share.wav")); } int main() { testClassifyReadable(); testClassifyTooNew(); testClassifyMalformed(); testEntryNameAccepts(); testEntryNameRejectsSeparatorsAndDots(); testEntryNameRejectsAbsolutePrefixes(); testEntryNameRejectsWindowsHostileNames(); testEntryNameAcceptsWellFormedUtf8(); testEntryNameRejectsIllFormedUtf8(); testSameEntryNameFoldsAsciiCase(); testNestedSamplePathAcceptsRelative(); testNestedSamplePathRejectsTraversalAndAbsolute(); if (g_fail == 0) { std::printf("package_format_tests: all passed\n"); return 0; } std::printf("package_format_tests: %d failure(s)\n", g_fail); return 1; }