diff --git a/scripts/generate_invalid_version_zip.luau b/scripts/generate_invalid_version_zip.luau new file mode 100644 index 0000000..22f8a55 --- /dev/null +++ b/scripts/generate_invalid_version_zip.luau @@ -0,0 +1,80 @@ +local fs = require("@lune/fs") +local stdio = require("@lune/stdio") +local process = require("@lune/process") + +-- stylua: ignore +local function createZip(version: number): buffer + version = version or math.random(63, 100) + print("Using version:", version) + + local data = buffer.create(98) + local pos = 0 + + -- Local file header (30 bytes) + buffer.writeu32(data, pos, 0x04034b50); pos += 4 + print("Local header starts at:", 0) + + -- Track rest of local header position + buffer.writeu16(data, pos, version); pos += 2 + buffer.writeu16(data, pos, 0); pos += 2 -- flags + buffer.writeu16(data, pos, 0); pos += 2 -- compression method + buffer.writeu32(data, pos, 0); pos += 4 -- timestamp + buffer.writeu32(data, pos, 0); pos += 4 -- crc32 + buffer.writeu32(data, pos, 0); pos += 4 -- compressed size + buffer.writeu32(data, pos, 0); pos += 4 -- uncompressed size + buffer.writeu16(data, pos, 0); pos += 2 -- filename length + buffer.writeu16(data, pos, 0); pos += 2 -- extra field length + + -- Central directory + local cdOffset = pos + print("CD fields:") + buffer.writeu32(data, pos, 0x02014b50); pos += 4 -- signature + print("- signature:", buffer.readu32(data, cdOffset)) + buffer.writeu16(data, pos, 20); pos += 2 -- version made by + print("- version made by:", buffer.readu16(data, cdOffset + 4)) + buffer.writeu16(data, pos, version); pos += 2 -- version needed + print("- version needed:", buffer.readu16(data, cdOffset + 6)) + print("CD additional fields:") + print("- flags:", buffer.readu16(data, cdOffset + 8)) + print("- compression:", buffer.readu16(data, cdOffset + 10)) + print("- local header offset:", buffer.readu32(data, cdOffset + 42)) + buffer.writeu16(data, pos, 0); pos += 2 -- flags + buffer.writeu16(data, pos, 0); pos += 2 -- compression + buffer.writeu32(data, pos, 0); pos += 4 -- timestamp + buffer.writeu32(data, pos, 0); pos += 4 -- crc32 + buffer.writeu32(data, pos, 0); pos += 4 -- compressed size + buffer.writeu32(data, pos, 0); pos += 4 -- uncompressed size + buffer.writeu16(data, pos, 0); pos += 2 -- filename length + buffer.writeu16(data, pos, 0); pos += 2 -- extra field length + buffer.writeu16(data, pos, 0); pos += 2 -- comment length + buffer.writeu16(data, pos, 0); pos += 2 -- disk number + buffer.writeu16(data, pos, 0); pos += 2 -- internal attrs + buffer.writeu32(data, pos, 0); pos += 4 -- external attrs + buffer.writeu32(data, pos, 0); pos += 4 -- local header offset + + -- End of central directory + print("EoCD fields:") + buffer.writeu32(data, pos, 0x06054b50); pos += 4 -- signature + buffer.writeu16(data, pos, 0); pos += 2 -- disk number + buffer.writeu16(data, pos, 0); pos += 2 -- disk with cd + buffer.writeu16(data, pos, 1); pos += 2 -- disk entries (1 file) + print("- disk entries:", buffer.readu16(data, pos - 18)) + buffer.writeu16(data, pos, 1); pos += 2 -- total entries (1 file) + print("- total entries:", buffer.readu16(data, pos - 16)) + buffer.writeu32(data, pos, 46); pos += 4 -- cd size (fixed size) + print("- cd size:", buffer.readu32(data, pos - 14)) + buffer.writeu32(data, pos, 30); pos += 4 -- cd offset (fixed offset) + print("- cd offset:", buffer.readu32(data, pos - 10)) + buffer.writeu16(data, pos, 0); pos += 2 -- comment length + + print("Final buffer size:", pos) + local result = buffer.create(pos) + buffer.copy(result, 0, data, 0, pos) + return result +end + +-- Write the invalid ZIP file +local versionNum = assert(tonumber(process.args[1] or stdio.prompt("text", "Version number:"))) +local zip = createZip(versionNum) +fs.writeFile("tests/data/invalid_version.zip", zip) +print(`Generated invalid_version.zip with version requirement {versionNum // 10}.{versionNum % 10}`) diff --git a/tests/data/invalid_version.zip b/tests/data/invalid_version.zip new file mode 100644 index 0000000..1a7400c Binary files /dev/null and b/tests/data/invalid_version.zip differ diff --git a/tests/edge_cases.luau b/tests/edge_cases.luau index 8dfecac..33e8132 100644 --- a/tests/edge_cases.luau +++ b/tests/edge_cases.luau @@ -57,5 +57,14 @@ return function(test: typeof(frktest.test)) check.equal(#entries, 3) check.table.equal(entries, { "file_こんにちは.txt", "file_你好.txt", "file_안녕하세요.txt" }) end) + + test.case("Errors on invalid extraction version requirement", function() + local data = fs.readFile("tests/data/invalid_version.zip") + local zip = ZipReader.load(buffer.fromstring(data)) + + check.should_error(function() + return zip:extractDirectory("/", { type = "text" }) + end) + end) end) end diff --git a/tests/metadata.luau b/tests/metadata.luau index 08189c5..a6c9bfd 100644 --- a/tests/metadata.luau +++ b/tests/metadata.luau @@ -13,6 +13,7 @@ local FALLIBLES = { "invalid_cde_number_of_files_allocation_smaller_offset.zip", "invalid_offset.zip", "invalid_offset2.zip", + "invalid_version.zip", "misaligned_comment.zip", "comment_garbage.zip", "chinese.zip", -- FIXME: Support encoding other than UTF-8 and ASCII using OS APIs after FFI @@ -94,8 +95,9 @@ return function(test: typeof(frktest.test)) local checkErr: ((...any) -> any?) -> nil = if table.find(FALLIBLES, file) then check.should_error else check.should_not_error + test.case(`Parsed metadata matches unzip output - {file}`, function() - checkErr(function(...) + checkErr(function() file = "tests/data/" .. file local data = fs.readFile(file) local zip = unzip.load(buffer.fromstring(data)) @@ -165,7 +167,7 @@ return function(test: typeof(frktest.test)) end end - return + return "" end) end) end