From 8f3601ed64f78712361e09307bfe98194da331d1 Mon Sep 17 00:00:00 2001 From: Armijn Hemel Date: Wed, 4 Feb 2026 20:33:58 +0100 Subject: [PATCH] add test case for files with extra data in between the last local file header and the first central directory header --- ...readme.extra_data_before_central_directory | 66 +++++ ...th_extra_data_before_central_directory.zip | Bin 0 -> 2194 bytes ...tra_data_before_central_directory.zip.snap | 271 ++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 testdata/readme.extra_data_before_central_directory create mode 100644 testdata/test_with_extra_data_before_central_directory.zip create mode 100644 ziplinter/src/snapshots/ziplinter__test__test_with_extra_data_before_central_directory.zip.snap diff --git a/testdata/readme.extra_data_before_central_directory b/testdata/readme.extra_data_before_central_directory new file mode 100644 index 0000000..135026a --- /dev/null +++ b/testdata/readme.extra_data_before_central_directory @@ -0,0 +1,66 @@ +Section 4.3.6 specifies the layout of a ZIP file: + +``` + 4.3.6 Overall .ZIP file format: + + [local file header 1] + [encryption header 1] + [file data 1] + [data descriptor 1] + . + . + . + [local file header n] + [encryption header n] + [file data n] + [data descriptor n] + [archive decryption header] + [archive extra data record] + [central directory header 1] + . + . + . + [central directory header n] + [zip64 end of central directory record] + [zip64 end of central directory locator] + [end of central directory record] +``` + +According to this layout there can be no layout between the last local file +header and the first central directory header, yet there are many files in +which this happens. As an example, Android packages store a signing block right +after the last local file header: + + + +Although this is not allowed APK files can still be unpacked, as programs use +the central directory to find the offsets to each individual file and +effectively skip the extra data. None of the regular ZIP programs seem to be +able to extract this extra data. + +To create a file with extra data only one field needs to be adapted (not +tested for ZIP64 files) in the "end of central directory" (section 4.3.16), +namely: + +``` +offset of start of central directory with respect to the starting disk number +``` + +The file `test_with_extra_data_before_central_directory.zip` is a modified +version of `test.zip` with an addition 1024 bytes added in between the last +local file header and the first central directory header. This means that the +central directory is moved an additional 1024 bytes. This can be shown by +comparing the output of `hexdump -C` for both files: + +The position of the central directory in the original file is `ba 03`: + +``` +00000470 00 00 ba 03 00 00 1a 00 54 68 69 73 20 69 73 20 |........This is | +``` + +In the new file the central directory has been moved 1024 bytes and is now +`ba 07`: + +``` +00000870 00 00 ba 07 00 00 1a 00 54 68 69 73 20 69 73 20 |........This is | +``` diff --git a/testdata/test_with_extra_data_before_central_directory.zip b/testdata/test_with_extra_data_before_central_directory.zip new file mode 100644 index 0000000000000000000000000000000000000000..57a426358245aeb50f1a986b1508a2d85e72ff55 GIT binary patch literal 2194 zcmZ{mc{tSDAIHB2lOKIgnYpYu7-bKakS&U0P|YsW7j4FG@;5KmAs zX&qhekmpqjKX()XDv?6frbba+U4#MtuuZ0YBu^1hq5z-3XFdSL?xuZ zdM5?L;R;i;axyA=1OY71T7+i=0QoU0CO>qCT_pJdU`Nt`A`nOpB@x5@$id`rT||^F zLOb+a;LkS4by)T%c=|`1OISy1F%g9wLou`s%88dHcoG)^^WriE1Lq}yKr;t302mp2 zg>gU1tL3Sd?o?-T0F_1vCjw^vWM3i-9YP2sIuQx}(P6zrV*mgZp;2ZoQ4`b*ZyRTv zTx$#C#mDXlPyB?2N=PsmhhOtP_{V_*HS~*S;&!`5ikKj%uYx7~Hj9qdNR%$2AjB>A3{mojr$!Iy*!aYA_biGZ^qI7IIvSvK^ z`iuPI^n=we-K8_%6Q??_6IGI=^YCtOBO9A?KCz#tWM$#kI8A+TN##xc`U=wxvkI@t zH5Nf>km6JUZtxFPsVqSslAwWsfrJ;kb#)pU$Q3Q?`XGbcJPw+xb1ir$(HVP{#cT`R zz`0H0JLWn2AjDs_UBg%B{L)Yn&Oox5ZiE0H3Q^yOt>h$4A;)o{Jd2W*O#|&8r#h6 z9M|i;lc}x|pSm7LvlSp%F-e+W&gSVwkxf}7iatDByIyLEJUq+pepf?g=w?UCsCKwb zFAf6|mwV299QdT-L8oIi?Vv$0F;5vJ3Np_W)!eAIyNz7!fEEbEjyUMc?N(PnfB%>( zfWpsJ`~T+0XQ9JP^QfEC*EJ~P+n=H)U#t>%?#>-I2AC1e*=H@STpKP@V7<}saAmXy z_jb08e~GSqo}Su(bo!=VrU_Ap@E zo(c2EH`=T{dZtHE+L%jOHQp|0Y~0YhpRh(ZX`-I0+Jg81TRNid znfovwoRp!A6vbGvypTycLNS_>wzQS}50kwwUg^vs-e-8-Z=NgI>oIqSipCb~7?z zjTcRx7bbiwISCfJB}lim+LxAeA6mbDH z+JpC&Zve^Ss*romj~q=({Y1s5qeZe*sxFJ#*hXLa;)3Il)^iaGC3`U3OpJGPk@jI+ zHT*WRaIqKJaZe`C3ej{0Itum85M1n7jHV1MNLx`H9%f+0v-fLlgk`hD^>p`q8;N#x zl$^lX^<~!dXlf1gZXreH;>VQN72d>^EG5>fTbed-B92C)fj>lo7A)2Z^=6M2oVJ%An>=(ny!Lx!F8J}^ z%GR%bg#B(K<{bgb{E~KP6nqv`KY3l0Joq|x&v=2A+Fbbs|I9jSi>d26dS=373Oqtm z|M5m6zQ<9*D~_{&bvnKM!~6#Lvz+t`GF-Z=vAOcKBVUswO-Y;%MoBd1{pmsYqs>!D z7TbB}=Io5w$^r8O9#&JUirGh>YEn)-e|)2ACN_}eULg4$dqd5S9zsKF+|4MV4-}`G z!Up`?+vm0?&?mH#5y%l(s~tkEsKjE$y@yp)#~6vLyDxHevh(dL!E2_y+6^pM6)#y! z$Bp~D#vZba>vhrP(6Ro03(uTXT+Y|cwY_H|rh<&L2ufw+XM~M6m8%DZYYEm#jofkd zRT$y|=BY+jX;0)DDv;I>Bv>8x?y0L%Dz-54wZw95xb-Fjrge{;V}5 z+oB3lPl3R5!we`Dl~Tou4@#X3FG3AvNPgcAnY$foOZiShy1TmLSIMJtus(5yGi$ME zF|c4M!7ggS*3mrt{5$>tsRoAvO5dS_xePp8DAL@a?8&9>YlPF)i@{t<>sd9emx*W2 zAA&mhWTl+jo@b@VL%JX;%*SKjs$!&&ruJZ?6!#Mq`ZKywlL0;q0oa0mz>=fSvbe?G z%q`u69>LIYqcEosMS?~D@geEPeUFn+ro(KMtS|>$JX5qT#b}OU+qNV14GQL)-`(uB zKvmt!JmNKbzGkDnTiHG~EdFCY#>ta*g}F4^y+x;CY_Qj`UHLlRok7%R`8TP9UcMjf z%O{;M-`YIAge&E_H{xO5;gZU)$yM3$QyNMwCR literal 0 HcmV?d00001 diff --git a/ziplinter/src/snapshots/ziplinter__test__test_with_extra_data_before_central_directory.zip.snap b/ziplinter/src/snapshots/ziplinter__test__test_with_extra_data_before_central_directory.zip.snap new file mode 100644 index 0000000..b46adb6 --- /dev/null +++ b/ziplinter/src/snapshots/ziplinter__test__test_with_extra_data_before_central_directory.zip.snap @@ -0,0 +1,271 @@ +--- +source: ziplinter/src/lib.rs +expression: result +--- +{ + "comment": "This is a zipfile comment.", + "contents": [ + { + "central": { + "comment": "", + "compressed_size": 25, + "crc32": 3287144384, + "creator_version": { + "host_system": "Unix", + "version": 30 + }, + "disk_nbr_start": 0, + "external_attrs": 2175008768, + "extra": [ + 85, + 84, + 5, + 0, + 3, + 113, + 252, + 130, + 76, + 117, + 120, + 11, + 0, + 1, + 4, + 245, + 1, + 0, + 0, + 4, + 20, + 0, + 0, + 0 + ], + "flags": 0, + "header_offset": 0, + "internal_attrs": 1, + "method": "Deflate", + "mode": 420, + "modified": "2010-09-05T02:12:01Z", + "name": "test.txt", + "reader_version": { + "host_system": "MsDos", + "version": 20 + }, + "uncompressed_size": 26 + }, + "local": { + "accessed": null, + "compressed_size": 25, + "crc32": 3287144384, + "created": null, + "extra": [ + 85, + 84, + 9, + 0, + 3, + 113, + 252, + 130, + 76, + 118, + 252, + 130, + 76, + 117, + 120, + 11, + 0, + 1, + 4, + 245, + 1, + 0, + 0, + 4, + 20, + 0, + 0, + 0 + ], + "flags": 0, + "gid": 501, + "header_offset": 0, + "method": "Deflate", + "method_specific": "None", + "mode": 0, + "modified": "2010-09-05T02:12:01Z", + "name": "test.txt", + "reader_version": { + "host_system": "MsDos", + "version": 20 + }, + "uid": 501, + "uncompressed_size": 26 + } + }, + { + "central": { + "comment": "", + "compressed_size": 785, + "crc32": 1423258110, + "creator_version": { + "host_system": "Unix", + "version": 30 + }, + "disk_nbr_start": 0, + "external_attrs": 2175008768, + "extra": [ + 85, + 84, + 5, + 0, + 3, + 58, + 48, + 131, + 76, + 117, + 120, + 11, + 0, + 1, + 4, + 245, + 1, + 0, + 0, + 4, + 20, + 0, + 0, + 0 + ], + "flags": 0, + "header_offset": 91, + "internal_attrs": 0, + "method": "Store", + "mode": 420, + "modified": "2010-09-05T05:52:58Z", + "name": "gophercolor16x16.png", + "reader_version": { + "host_system": "MsDos", + "version": 10 + }, + "uncompressed_size": 785 + }, + "local": { + "accessed": null, + "compressed_size": 785, + "crc32": 1423258110, + "created": null, + "extra": [ + 85, + 84, + 9, + 0, + 3, + 58, + 48, + 131, + 76, + 59, + 48, + 131, + 76, + 117, + 120, + 11, + 0, + 1, + 4, + 245, + 1, + 0, + 0, + 4, + 20, + 0, + 0, + 0 + ], + "flags": 0, + "gid": 501, + "header_offset": 0, + "method": "Store", + "method_specific": "None", + "mode": 0, + "modified": "2010-09-05T05:52:58Z", + "name": "gophercolor16x16.png", + "reader_version": { + "host_system": "MsDos", + "version": 10 + }, + "uid": 501, + "uncompressed_size": 785 + } + } + ], + "encoding": "Utf8", + "eocd": { + "dir": { + "inner": { + "dir_disk_nbr": 0, + "dir_records_this_disk": 2, + "directory_offset": 1978, + "directory_records": 2, + "directory_size": 168, + "disk_nbr": 0 + }, + "offset": 2146 + }, + "dir64": null, + "global_offset": 0 + }, + "parsed_ranges": [ + { + "contains": "end of central directory record", + "end": 2194, + "start": 2146 + }, + { + "contains": "central directory header", + "end": 2056, + "filename": "test.txt", + "start": 1978 + }, + { + "contains": "central directory header", + "end": 2146, + "filename": "gophercolor16x16.png", + "start": 2056 + }, + { + "contains": "local file header", + "end": 66, + "filename": "test.txt", + "start": 0 + }, + { + "contains": "file data", + "end": 91, + "filename": "test.txt", + "start": 66 + }, + { + "contains": "local file header", + "end": 169, + "filename": "gophercolor16x16.png", + "start": 91 + }, + { + "contains": "file data", + "end": 954, + "filename": "gophercolor16x16.png", + "start": 169 + } + ], + "size": 2194 +}