diff --git a/tests/test_quest.py b/tests/test_quest.py index 4a8edfd..2ff008f 100644 --- a/tests/test_quest.py +++ b/tests/test_quest.py @@ -887,10 +887,19 @@ class Round3(unittest.TestCase): sink = Sink() with zipfile.ZipFile(sink, 'w', method) as z: z.writestr('assets/streamed', content) + # Streamed with ZIP64 forced: a 64-bit data descriptor for a small file. + with z.open('assets/streamed64', 'w', force_zip64=True) as f: + f.write(content) + z.writestr('assets/after', b'next entry') path = self.tmpfile(sink.buf.getvalue()) with zipfile.ZipFile(path) as z: self.assertTrue(z.getinfo('assets/streamed').flag_bits & 0x08) + self.assertTrue(z.getinfo('assets/streamed64').flag_bits & 0x08) + self.assertLess(z.getinfo('assets/streamed64').file_size, 0xffffffff) + self.assertEqual(z.read('assets/streamed64'), content) # zipfile accepts it self.assertEqual(android.read_member(path, 'assets/streamed'), content) + self.assertEqual(android.read_member(path, 'assets/streamed64'), content) + self.assertEqual(android.read_member(path, 'assets/after'), b'next entry') def test_central_sizes_and_checksum_forged(self): # The reviewer's case: stored 'abcdef', central sizes 3 and the prefix's checksum, local untouched. diff --git a/ui/frame_android.py b/ui/frame_android.py index 3db689c..9dc5715 100644 --- a/ui/frame_android.py +++ b/ui/frame_android.py @@ -331,6 +331,16 @@ def _zip64_sizes(extra, usize, csize): return usize, csize +def _has_zip64_extra(extra): + at = 0 + while at + 4 <= len(extra): + kind, length = struct.unpack_from('= 0xffffffff or info.compress_size >= 0xffffffff else 12 - tail = raw.read(4 + want) - signed = tail[:4] == b'PK\7\x08' - body = tail[4:4 + want] if signed else tail[:want] - if len(body) < want: + # Sizes and checksum follow the data: (signature,) crc, then sizes of 4 bytes each, + # or 8 for ZIP64. Streaming writers use the ZIP64 form even for small files when + # sizes weren't known up front, so the width isn't implied by the final sizes: take + # the layout that matches the central directory and fits before the next entry, + # preferring one that ends exactly there, then the width the local header implies. + zip64 = 0xffffffff in (csize, usize) or _has_zip64_extra(extra) + tail = raw.read(24) + if len(tail) < 12: raise FrameError(f'{name}: truncated data descriptor') - crc, csize, usize = struct.unpack_from('