@@ -2,6 +2,8 @@ package fs
22
33import (
44 "archive/zip"
5+ "encoding/binary"
6+ "math"
57 "os"
68 "path/filepath"
79 "runtime"
@@ -148,6 +150,150 @@ func TestUnzip_CreatesDirectoriesAndNestedFiles(t *testing.T) {
148150 }
149151}
150152
153+ func TestUnzip_SkipsVeryLargeEntry (t * testing.T ) {
154+ dir := t .TempDir ()
155+ zipPath := filepath .Join (dir , "huge.zip" )
156+
157+ writeZip64Stub (t , zipPath , "huge.bin" , math .MaxUint64 )
158+
159+ files , skipped , err := Unzip (zipPath , filepath .Join (dir , "out" ), 0 , - 1 )
160+ assert .NoError (t , err )
161+ assert .Empty (t , files )
162+ assert .Contains (t , skipped , "huge.bin" )
163+ }
164+
165+ // writeZip64Stub writes a minimal ZIP64 archive with one stored entry and custom size values.
166+ func writeZip64Stub (t * testing.T , path , name string , size uint64 ) {
167+ t .Helper ()
168+
169+ var buf []byte
170+
171+ bw := func (data []byte ) {
172+ buf = append (buf , data ... )
173+ }
174+
175+ writeLE := func (v any ) {
176+ var b [8 ]byte
177+ switch x := v .(type ) {
178+ case uint16 :
179+ binary .LittleEndian .PutUint16 (b [:2 ], x )
180+ bw (b [:2 ])
181+ case uint32 :
182+ binary .LittleEndian .PutUint32 (b [:4 ], x )
183+ bw (b [:4 ])
184+ case uint64 :
185+ binary .LittleEndian .PutUint64 (b [:8 ], x )
186+ bw (b [:8 ])
187+ default :
188+ t .Fatalf ("unsupported type %T" , v )
189+ }
190+ }
191+
192+ filename := []byte (name )
193+ const (
194+ sigLocal = 0x04034b50
195+ sigCentral = 0x02014b50
196+ sigEnd = 0x06054b50
197+ )
198+
199+ zip64ExtraLen := uint16 (4 + 16 ) // header id + size + two uint64 values
200+ localExtraLen := zip64ExtraLen
201+ centralExtraLen := zip64ExtraLen
202+
203+ // Local file header
204+ writeLE (uint32 (sigLocal ))
205+ writeLE (uint16 (45 )) // version needed (zip64)
206+ writeLE (uint16 (0 )) // flags
207+ writeLE (uint16 (0 )) // method store
208+ writeLE (uint16 (0 )) // mod time
209+ writeLE (uint16 (0 )) // mod date
210+ writeLE (uint32 (0 )) // crc
211+ writeLE (uint32 (0xFFFFFFFF ))
212+ writeLE (uint32 (0xFFFFFFFF ))
213+ if len (filename ) > math .MaxUint16 {
214+ t .Fatalf ("filename too long" )
215+ }
216+ writeLE (uint16 (len (filename )))
217+ writeLE (localExtraLen )
218+ bw (filename )
219+ // zip64 extra
220+ writeLE (uint16 (0x0001 )) // header id
221+ writeLE (uint16 (16 )) // data size
222+ writeLE (size ) // uncompressed size
223+ writeLE (size ) // compressed size
224+ // no file data (size 0) to keep archive tiny
225+
226+ localLen := len (buf )
227+
228+ // Central directory header
229+ writeLE (uint32 (sigCentral ))
230+ writeLE (uint16 (45 )) // version made by
231+ writeLE (uint16 (45 )) // version needed
232+ writeLE (uint16 (0 )) // flags
233+ writeLE (uint16 (0 )) // method
234+ writeLE (uint16 (0 )) // time
235+ writeLE (uint16 (0 )) // date
236+ writeLE (uint32 (0 )) // crc
237+ writeLE (uint32 (0xFFFFFFFF ))
238+ writeLE (uint32 (0xFFFFFFFF ))
239+ if len (filename ) > math .MaxUint16 {
240+ t .Fatalf ("filename too long" )
241+ }
242+ writeLE (uint16 (len (filename )))
243+ writeLE (centralExtraLen )
244+ writeLE (uint16 (0 )) // comment len
245+ writeLE (uint16 (0 )) // disk start
246+ writeLE (uint16 (0 )) // int attrs
247+ writeLE (uint32 (0 )) // ext attrs
248+ writeLE (uint32 (0 )) // rel offset (zip64 overrides)
249+ bw (filename )
250+ // zip64 extra
251+ writeLE (uint16 (0x0001 ))
252+ writeLE (uint16 (16 ))
253+ writeLE (size ) // uncompressed
254+ writeLE (size ) // compressed
255+
256+ centralLen := len (buf ) - localLen
257+
258+ // End of central directory (not zip64 EOCD; minimal to satisfy reader)
259+ writeLE (uint32 (sigEnd ))
260+ writeLE (uint16 (0 )) // disk
261+ writeLE (uint16 (0 )) // start disk
262+ writeLE (uint16 (1 )) // entries this disk
263+ writeLE (uint16 (1 )) // total entries
264+ if centralLen > math .MaxUint32 || localLen > math .MaxUint32 {
265+ t .Fatalf ("central or local length exceeds uint32" )
266+ }
267+ writeLE (uint32 (centralLen ))
268+ writeLE (uint32 (localLen ))
269+ writeLE (uint16 (0 )) // comment length
270+
271+ if err := os .WriteFile (path , buf , 0o600 ); err != nil {
272+ t .Fatal (err )
273+ }
274+ }
275+
276+ func TestUnzipFileWithLimit_DetectsOverrun (t * testing.T ) {
277+ dir := t .TempDir ()
278+ zipPath := filepath .Join (dir , "small.zip" )
279+ writeZip (t , zipPath , map [string ][]byte {"a.txt" : []byte ("abc" )}) // 3 bytes
280+
281+ r , err := zip .OpenReader (zipPath )
282+ if err != nil {
283+ t .Fatal (err )
284+ }
285+ defer r .Close ()
286+
287+ if len (r .File ) != 1 {
288+ t .Fatalf ("expected one file, got %d" , len (r .File ))
289+ }
290+
291+ _ , err = unzipFileWithLimit (r .File [0 ], dir , 1 ) // limit below actual size
292+ if err == nil {
293+ t .Fatalf ("expected limit overrun error" )
294+ }
295+ }
296+
151297func TestZip (t * testing.T ) {
152298 t .Run ("Compressed" , func (t * testing.T ) {
153299 zipDir := filepath .Join (os .TempDir (), "pkg/fs" )
0 commit comments