mirror of
https://github.com/0x5eal/luau-unzip.git
synced 2025-04-16 03:43:44 +01:00
fix: correctly parse EoCD for misaligned comment sizes
Also adds a test case for the same.
This commit is contained in:
parent
0f5a6d035c
commit
98c23ece3e
3 changed files with 392 additions and 358 deletions
|
@ -115,6 +115,7 @@ export type ZipReader = typeof(setmetatable({} :: ZipReaderInner, { __index = Zi
|
||||||
-- stylua: ignore
|
-- stylua: ignore
|
||||||
type ZipReaderInner = {
|
type ZipReaderInner = {
|
||||||
data: buffer, -- The buffer containing the raw bytes of the ZIP
|
data: buffer, -- The buffer containing the raw bytes of the ZIP
|
||||||
|
comment: string, -- Comment associated with the ZIP
|
||||||
entries: { ZipEntry }, -- The decoded entries present
|
entries: { ZipEntry }, -- The decoded entries present
|
||||||
directories: { [string]: ZipEntry }, -- The directories and their respective entries
|
directories: { [string]: ZipEntry }, -- The directories and their respective entries
|
||||||
root: ZipEntry, -- The entry of the root directory
|
root: ZipEntry, -- The entry of the root directory
|
||||||
|
@ -143,50 +144,68 @@ function ZipReader.parseCentralDirectory(self: ZipReader): ()
|
||||||
-- ZIP files are read from the end, starting with the End of Central Directory record
|
-- ZIP files are read from the end, starting with the End of Central Directory record
|
||||||
-- The EoCD is at least 22 bytes and contains pointers to the rest of the ZIP structure
|
-- The EoCD is at least 22 bytes and contains pointers to the rest of the ZIP structure
|
||||||
local bufSize = buffer.len(self.data)
|
local bufSize = buffer.len(self.data)
|
||||||
|
|
||||||
|
-- Start from the minimum possible position of EoCD (22 bytes from end)
|
||||||
|
local minPos = math.max(0, bufSize - (22 + 65535) --[[ max comment size: 64 KiB ]])
|
||||||
local pos = bufSize - 22
|
local pos = bufSize - 22
|
||||||
|
|
||||||
-- Search backwards for the EoCD signature
|
-- Search backwards for the EoCD signature
|
||||||
while pos > 0 do
|
while pos >= minPos do
|
||||||
-- Read 4 bytes as uint32 in little-endian format
|
|
||||||
if buffer.readu32(self.data, pos) == SIGNATURES.END_OF_CENTRAL_DIR then
|
if buffer.readu32(self.data, pos) == SIGNATURES.END_OF_CENTRAL_DIR then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
pos -= 1
|
pos -= 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Central Directory offset is stored 16 bytes into the EoCD record
|
-- Verify we found the signature
|
||||||
|
if pos < minPos then
|
||||||
|
error("Could not find End of Central Directory signature")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- End of Central Directory format:
|
||||||
|
-- Offset Bytes Description
|
||||||
|
-- 0 4 End of central directory signature
|
||||||
|
-- 4 2 Number of this disk
|
||||||
|
-- 6 2 Disk where central directory starts
|
||||||
|
-- 8 2 Number of central directory records on this disk
|
||||||
|
-- 10 2 Total number of central directory records
|
||||||
|
-- 12 4 Size of central directory (bytes)
|
||||||
|
-- 16 4 Offset of start of central directory
|
||||||
|
-- 20 2 Comment length (n)
|
||||||
|
-- 22 n Comment
|
||||||
|
|
||||||
local cdOffset = buffer.readu32(self.data, pos + 16)
|
local cdOffset = buffer.readu32(self.data, pos + 16)
|
||||||
-- Number of entries is stored 10 bytes into the EoCD record
|
|
||||||
local cdEntries = buffer.readu16(self.data, pos + 10)
|
local cdEntries = buffer.readu16(self.data, pos + 10)
|
||||||
|
local cdCommentLength = buffer.readu16(self.data, pos + 20)
|
||||||
|
self.comment = buffer.readstring(self.data, pos + 22, cdCommentLength)
|
||||||
|
|
||||||
-- Process each entry in the Central Directory
|
-- Process each entry in the Central Directory
|
||||||
pos = cdOffset
|
pos = cdOffset
|
||||||
for i = 1, cdEntries do
|
for i = 1, cdEntries do
|
||||||
-- Central Directory Entry format:
|
-- Central Directory Entry format:
|
||||||
-- Offset Bytes Description
|
-- Offset Bytes Description
|
||||||
-- ------------------------------------------------
|
|
||||||
-- 0 4 Central directory entry signature
|
-- 0 4 Central directory entry signature
|
||||||
-- 8 2 General purpose bitflags
|
-- 8 2 General purpose bitflags
|
||||||
-- 10 2 Compression method (8 = DEFLATE)
|
-- 10 2 Compression method (8 = DEFLATE)
|
||||||
-- 12 4 Last mod time/date
|
-- 12 4 Last mod time/date
|
||||||
|
-- 16 4 CRC-32
|
||||||
|
-- 24 4 Uncompressed size
|
||||||
-- 28 2 File name length (n)
|
-- 28 2 File name length (n)
|
||||||
-- 30 2 Extra field length (m)
|
-- 30 2 Extra field length (m)
|
||||||
-- 32 2 Comment length (k)
|
-- 32 2 Comment length (k)
|
||||||
-- 16 4 CRC-32
|
|
||||||
-- 24 4 Uncompressed size
|
|
||||||
-- 42 4 Local header offset
|
-- 42 4 Local header offset
|
||||||
-- 46 n File name
|
-- 46 n File name
|
||||||
-- 46+n m Extra field
|
-- 46+n m Extra field
|
||||||
-- 46+n+m k Comment
|
-- 46+n+m k Comment
|
||||||
|
|
||||||
local _bitflags = buffer.readu16(self.data, pos + 8)
|
local _bitflags = buffer.readu16(self.data, pos + 8)
|
||||||
|
local timestamp = buffer.readu32(self.data, pos + 12)
|
||||||
local compressionMethod = buffer.readu16(self.data, pos + 10)
|
local compressionMethod = buffer.readu16(self.data, pos + 10)
|
||||||
|
local crc = buffer.readu32(self.data, pos + 16)
|
||||||
|
local size = buffer.readu32(self.data, pos + 24)
|
||||||
local nameLength = buffer.readu16(self.data, pos + 28)
|
local nameLength = buffer.readu16(self.data, pos + 28)
|
||||||
local extraLength = buffer.readu16(self.data, pos + 30)
|
local extraLength = buffer.readu16(self.data, pos + 30)
|
||||||
local commentLength = buffer.readu16(self.data, pos + 32)
|
local commentLength = buffer.readu16(self.data, pos + 32)
|
||||||
local timestamp = buffer.readu32(self.data, pos + 12)
|
|
||||||
local crc = buffer.readu32(self.data, pos + 16)
|
|
||||||
local size = buffer.readu32(self.data, pos + 24)
|
|
||||||
local offset = buffer.readu32(self.data, pos + 42)
|
local offset = buffer.readu32(self.data, pos + 42)
|
||||||
local name = buffer.readstring(self.data, pos + 46, nameLength)
|
local name = buffer.readstring(self.data, pos + 46, nameLength)
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,6 @@ local FALLIBLES = {
|
||||||
"chinese.zip",
|
"chinese.zip",
|
||||||
"non_utf8.zip", -- FIXME: Lune breaks for non utf8 data in process stdout
|
"non_utf8.zip", -- FIXME: Lune breaks for non utf8 data in process stdout
|
||||||
"pandoc_soft_links.zip", -- FIXME: Soft links are not handled correctly
|
"pandoc_soft_links.zip", -- FIXME: Soft links are not handled correctly
|
||||||
-- FIXME: Files with a misaligned comments are not correctly located
|
|
||||||
-- "misaligned_comment.zip",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return function(test: typeof(frktest.test))
|
return function(test: typeof(frktest.test))
|
||||||
|
|
17
tests/misaligned_comment.luau
Normal file
17
tests/misaligned_comment.luau
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
local fs = require("@lune/fs")
|
||||||
|
|
||||||
|
local frktest = require("../lune_packages/frktest")
|
||||||
|
local check = frktest.assert.check
|
||||||
|
|
||||||
|
local ZipReader = require("../lib")
|
||||||
|
|
||||||
|
return function(test: typeof(frktest.test))
|
||||||
|
test.suite("ZIP extraction tests", function()
|
||||||
|
test.case("Handles misaligned comment properly", function()
|
||||||
|
local data = fs.readFile("tests/data/misaligned_comment.zip")
|
||||||
|
local zip = ZipReader.load(buffer.fromstring(data))
|
||||||
|
|
||||||
|
check.equal(zip.comment, "short.")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue