1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package zipslicer
18
19 import (
20 "bufio"
21 "bytes"
22 "encoding/binary"
23 "errors"
24 "fmt"
25 "io"
26 "io/ioutil"
27 )
28
29 type Directory struct {
30 File []*File
31 Size int64
32 DirLoc int64
33 r io.ReaderAt
34 end64 zip64End
35 loc64 zip64Loc
36 end zipEndRecord
37 }
38
39
40 func FindDirectory(r io.ReaderAt, size int64) (int64, error) {
41 pos := size - directoryEndLen - directory64LocLen
42 var endb [directoryEndLen + directory64LocLen]byte
43 if _, err := r.ReadAt(endb[:], pos); err != nil {
44 return 0, err
45 }
46 re := bytes.NewReader(endb[:])
47 var loc64 zip64Loc
48 var end zipEndRecord
49 binary.Read(re, binary.LittleEndian, &loc64)
50 binary.Read(re, binary.LittleEndian, &end)
51 if end.Signature != directoryEndSignature {
52 return 0, errors.New("zip central directory not found")
53 }
54 if end.TotalCDCount == uint16Max || end.CDSize == uint32Max || end.CDOffset == uint32Max {
55 if loc64.Signature != directory64LocSignature {
56 return 0, errors.New("expected ZIP64 locator")
57 }
58
59 var end64b [directory64EndLen]byte
60 if _, err := r.ReadAt(end64b[:], int64(loc64.Offset)); err != nil {
61 return 0, err
62 }
63 var end64 zip64End
64 binary.Read(bytes.NewReader(end64b[:]), binary.LittleEndian, &end64)
65 if end64.Signature != directory64EndSignature {
66 return 0, errors.New("zip central directory not found")
67 }
68 return int64(end64.CDOffset), nil
69 }
70 return int64(end.CDOffset), nil
71 }
72
73
74 func ReadWithDirectory(r io.ReaderAt, size int64, cd []byte) (*Directory, error) {
75 dirLoc := size - int64(len(cd))
76 files := make([]*File, 0)
77 for {
78 if binary.LittleEndian.Uint32(cd) != directoryHeaderSignature {
79 break
80 }
81 var hdr zipCentralDir
82 binary.Read(bytes.NewReader(cd), binary.LittleEndian, &hdr)
83 f := &File{
84 CreatorVersion: hdr.CreatorVersion,
85 ReaderVersion: hdr.ReaderVersion,
86 Flags: hdr.Flags,
87 Method: hdr.Method,
88 ModifiedTime: hdr.ModifiedTime,
89 ModifiedDate: hdr.ModifiedDate,
90 CRC32: hdr.CRC32,
91 CompressedSize: uint64(hdr.CompressedSize),
92 UncompressedSize: uint64(hdr.UncompressedSize),
93 InternalAttrs: hdr.InternalAttrs,
94 ExternalAttrs: hdr.ExternalAttrs,
95 Offset: uint64(hdr.Offset),
96
97 r: r,
98 rs: size,
99 }
100 f.raw = make([]byte, directoryHeaderLen+int(hdr.FilenameLen)+int(hdr.ExtraLen)+int(hdr.CommentLen))
101 copy(f.raw, cd)
102 cd = cd[directoryHeaderLen:]
103 f.Name, cd = string(cd[:int(hdr.FilenameLen)]), cd[int(hdr.FilenameLen):]
104 f.Extra, cd = cd[:int(hdr.ExtraLen)], cd[int(hdr.ExtraLen):]
105 f.Comment, cd = cd[:int(hdr.CommentLen)], cd[int(hdr.CommentLen):]
106 needUSize := f.UncompressedSize == uint32Max
107 needCSize := f.CompressedSize == uint32Max
108 needOffset := f.Offset == uint32Max
109 extra := f.Extra
110 for len(extra) >= 4 {
111 tag := binary.LittleEndian.Uint16(extra[:2])
112 size := binary.LittleEndian.Uint16(extra[2:4])
113 if int(size) > len(extra)-4 {
114 break
115 }
116 if tag == zip64ExtraID {
117 e := extra[4 : 4+size]
118 if needUSize && size >= 8 {
119 f.UncompressedSize = binary.LittleEndian.Uint64(e)
120 needUSize = false
121 }
122 if needCSize && size >= 16 {
123 f.CompressedSize = binary.LittleEndian.Uint64(e[8:])
124 needCSize = false
125 }
126 if needOffset && size >= 24 {
127 f.Offset = binary.LittleEndian.Uint64(e[16:])
128 needOffset = false
129 }
130 break
131 }
132 extra = extra[4+size:]
133 }
134 if needCSize || needOffset {
135 return nil, errors.New("missing ZIP64 header")
136 }
137 files = append(files, f)
138 }
139 d := &Directory{
140 File: files,
141 Size: size,
142 DirLoc: dirLoc,
143 r: r,
144 }
145 rd := bytes.NewReader(cd)
146 switch binary.LittleEndian.Uint32(cd) {
147 case directory64EndSignature:
148 binary.Read(rd, binary.LittleEndian, &d.end64)
149 binary.Read(rd, binary.LittleEndian, &d.loc64)
150 case directoryEndSignature:
151 default:
152 return nil, errors.New("expected end record")
153 }
154 binary.Read(rd, binary.LittleEndian, &d.end)
155 return d, nil
156 }
157
158
159 func Read(r io.ReaderAt, size int64) (*Directory, error) {
160 loc, err := FindDirectory(r, size)
161 if err != nil {
162 return nil, err
163 }
164 cd := make([]byte, size-loc)
165 if _, err := r.ReadAt(cd, loc); err != nil {
166 return nil, err
167 }
168 return ReadWithDirectory(r, size, cd)
169 }
170
171
172
173 func ReadStream(r io.Reader, size int64, cd []byte) (*Directory, error) {
174 ra := &streamReaderAt{r: r}
175 return ReadWithDirectory(ra, size, cd)
176 }
177
178
179
180
181 func (d *Directory) Truncate(n int, body, dir io.Writer) error {
182 if body != nil {
183 for i := 0; i < n; i++ {
184 f := d.File[i]
185 fs, err := f.GetTotalSize()
186 if err != nil {
187 return err
188 }
189 if _, err := io.Copy(body, io.NewSectionReader(d.r, int64(f.Offset), fs)); err != nil {
190 return err
191 }
192 }
193 }
194 cdOffset := d.File[n].Offset
195 var size uint64
196 for i := 0; i < n; i++ {
197 blob, err := d.File[i].GetDirectoryHeader()
198 if err != nil {
199 return err
200 }
201 dir.Write(blob)
202 size += uint64(len(blob))
203 }
204 end := d.end
205 if d.end64.Signature != 0 {
206 end64 := d.end64
207 end64.DiskCDCount = uint64(n)
208 end64.TotalCDCount = uint64(n)
209 end64.CDSize = size
210 end64.CDOffset = cdOffset
211 binary.Write(dir, binary.LittleEndian, end64)
212 loc := d.loc64
213 loc.Offset = cdOffset + size
214 binary.Write(dir, binary.LittleEndian, loc)
215 } else {
216 if cdOffset >= uint32Max || n >= uint16Max {
217 return errors.New("file too big for 32-bit ZIP")
218 }
219 end.DiskCDCount = uint16(n)
220 end.TotalCDCount = uint16(n)
221 end.CDSize = uint32(size)
222 end.CDOffset = uint32(cdOffset)
223 }
224 binary.Write(dir, binary.LittleEndian, end)
225 return nil
226 }
227
228
229
230
231
232
233 func (d *Directory) GetOriginalDirectory(trim bool) (cdEntries, endOfDir []byte, err error) {
234 if d.end.Signature == 0 {
235 return nil, nil, errors.New("new zipfile, can't produce original directory")
236 }
237 var wcd, weod bytes.Buffer
238 if err := d.WriteDirectory(&wcd, nil, false); err != nil {
239 return nil, nil, err
240 }
241 end64 := d.end64
242 loc64 := d.loc64
243 end := d.end
244 if trim {
245 contentEnd, err := d.NextFileOffset()
246 if err != nil {
247 return nil, nil, err
248 }
249 delta := d.DirLoc - contentEnd
250 if delta < 0 || delta > uint32Max {
251 return nil, nil, errors.New("non-ZIP data out of bounds")
252 }
253 if end64.Signature != 0 {
254 end64.CDOffset -= uint64(delta)
255 }
256 if loc64.Signature != 0 {
257 loc64.Offset -= uint64(delta)
258 }
259 if end.CDOffset != uint32Max || loc64.Signature == 0 {
260 end.CDOffset -= uint32(delta)
261 }
262 }
263 binary.Write(&weod, binary.LittleEndian, end64)
264 binary.Write(&weod, binary.LittleEndian, loc64)
265 binary.Write(&weod, binary.LittleEndian, end)
266 return wcd.Bytes(), weod.Bytes(), nil
267 }
268
269
270
271
272
273
274 func (d *Directory) WriteDirectory(wcd, weod io.Writer, forceZip64 bool) error {
275 buf := bufio.NewWriter(wcd)
276 cdoff := d.DirLoc
277 var count, size uint64
278 minVersion := uint16(zip20)
279 for _, f := range d.File {
280 if f.ReaderVersion > minVersion {
281 minVersion = f.ReaderVersion
282 }
283 blob, err := f.GetDirectoryHeader()
284 if err != nil {
285 return err
286 }
287 if _, err := buf.Write(blob); err != nil {
288 return err
289 }
290 count++
291 size += uint64(len(blob))
292 }
293 if wcd != weod {
294 if err := buf.Flush(); err != nil {
295 return err
296 }
297 buf.Reset(weod)
298 } else if weod == nil {
299 return nil
300 }
301 var end zipEndRecord
302 if count >= uint16Max || size >= uint32Max || cdoff >= uint32Max || forceZip64 {
303 minVersion = zip45
304 }
305 if minVersion == zip45 {
306 end64off := cdoff + int64(size)
307 end64 := zip64End{
308 Signature: directory64EndSignature,
309 RecordSize: directory64EndLen - 12,
310 CreatorVersion: zip45,
311 ReaderVersion: minVersion,
312 DiskCDCount: count,
313 TotalCDCount: count,
314 CDSize: size,
315 CDOffset: uint64(cdoff),
316 }
317 if err := binary.Write(buf, binary.LittleEndian, end64); err != nil {
318 return err
319 }
320 loc64 := zip64Loc{
321 Signature: directory64LocSignature,
322 Offset: uint64(end64off),
323 DiskCount: 1,
324 }
325 if err := binary.Write(buf, binary.LittleEndian, loc64); err != nil {
326 return err
327 }
328 end = zipEndRecord{
329 Signature: directoryEndSignature,
330 DiskCDCount: uint16Max,
331 TotalCDCount: uint16Max,
332 CDSize: uint32Max,
333 CDOffset: uint32Max,
334 }
335 } else {
336 end = zipEndRecord{
337 Signature: directoryEndSignature,
338 DiskCDCount: uint16(count),
339 TotalCDCount: uint16(count),
340 CDSize: uint32(size),
341 CDOffset: uint32(cdoff),
342 }
343 }
344 if err := binary.Write(buf, binary.LittleEndian, end); err != nil {
345 return err
346 }
347 return buf.Flush()
348 }
349
350 type streamReaderAt struct {
351 r io.Reader
352 pos int64
353 }
354
355 func (r *streamReaderAt) ReadAt(d []byte, p int64) (int, error) {
356 if p > r.pos {
357 if _, err := io.CopyN(ioutil.Discard, r.r, p-r.pos); err != nil {
358 return 0, err
359 }
360 r.pos = p
361 } else if p < r.pos {
362 return 0, fmt.Errorf("attempted to seek backwards: at %d, to %d", r.pos, p)
363 }
364 n, err := r.r.Read(d)
365 r.pos += int64(n)
366 return n, err
367 }
368
369
370
371 func (d *Directory) AddFile(f *File) (*File, error) {
372 size, err := f.GetTotalSize()
373 if err != nil {
374 return nil, err
375 }
376 offset := uint64(d.DirLoc)
377 if f.Offset != offset {
378 f.raw = nil
379 }
380 f.Offset = offset
381 d.DirLoc += size
382 d.File = append(d.File, f)
383 return f, nil
384 }
385
386
387
388 func (d *Directory) NextFileOffset() (int64, error) {
389 if len(d.File) == 0 {
390 return 0, nil
391 }
392 lastFile := d.File[len(d.File)-1]
393 size, err := lastFile.GetTotalSize()
394 if err != nil {
395 return 0, err
396 }
397 return int64(lastFile.Offset) + size, nil
398 }
399
View as plain text