-
Notifications
You must be signed in to change notification settings - Fork 16
/
reader.go
112 lines (99 loc) · 3.01 KB
/
reader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Package zipstream provides support for reading ZIP archives through an io.Reader.
//
// Zip64 archives are not yet supported.
package zipstream
import (
"archive/zip"
"bufio"
"encoding/binary"
"hash/crc32"
"io"
"io/ioutil"
)
const (
readAhead = 28
maxRead = 4096
bufferSize = maxRead + readAhead
)
// A Reader provides sequential access to the contents of a zip archive.
// A zip archive consists of a sequence of files,
// The Next method advances to the next file in the archive (including the first),
// and then it can be treated as an io.Reader to access the file's data.
// The Buffered method recovers any bytes read beyond the end of the zip file,
// necessary if you plan to process anything after it that is not another zip file.
type Reader struct {
io.Reader
br *bufio.Reader
}
// NewReader creates a new Reader reading from r.
func NewReader(r io.Reader) *Reader {
return &Reader{br: bufio.NewReaderSize(r, bufferSize)}
}
// Next advances to the next entry in the zip archive.
//
// io.EOF is returned when the end of the zip file has been reached.
// If Next is called again, it will presume another zip file immediately follows
// and it will advance into it.
func (r *Reader) Next() (*zip.FileHeader, error) {
if r.Reader != nil {
if _, err := io.Copy(ioutil.Discard, r.Reader); err != nil {
return nil, err
}
}
sigBytes, err := r.br.Peek(4)
if err != nil {
return nil, err
}
switch sig := binary.LittleEndian.Uint32(sigBytes); sig {
case fileHeaderSignature:
break
case directoryHeaderSignature: // Directory appears at end of file so we are finished
return nil, discardCentralDirectory(r.br)
default:
return nil, zip.ErrFormat
}
headBuf := make([]byte, fileHeaderLen)
if _, err := io.ReadFull(r.br, headBuf); err != nil {
return nil, err
}
b := readBuf(headBuf[4:])
f := &zip.FileHeader{
ReaderVersion: b.uint16(),
Flags: b.uint16(),
Method: b.uint16(),
ModifiedTime: b.uint16(),
ModifiedDate: b.uint16(),
CRC32: b.uint32(),
CompressedSize: b.uint32(), // TODO handle zip64
UncompressedSize: b.uint32(), // TODO handle zip64
}
filenameLen := b.uint16()
extraLen := b.uint16()
d := make([]byte, filenameLen+extraLen)
if _, err := io.ReadFull(r.br, d); err != nil {
return nil, err
}
f.Name = string(d[:filenameLen])
f.Extra = d[filenameLen : filenameLen+extraLen]
dcomp := decompressor(f.Method)
if dcomp == nil {
return nil, zip.ErrAlgorithm
}
// TODO handle encryption here
crc := &crcReader{
hash: crc32.NewIEEE(),
crc: &f.CRC32,
}
if f.Flags&0x8 != 0 { // If has dataDescriptor
crc.Reader = dcomp(&descriptorReader{br: r.br, fileHeader: f})
} else {
crc.Reader = dcomp(io.LimitReader(r.br, int64(f.CompressedSize)))
crc.crc = &f.CRC32
}
r.Reader = crc
return f, nil
}
// Buffered returns any bytes beyond the end of the zip file that it may have
// read. These are necessary if you plan to process anything after it,
// that isn't another zip file.
func (r *Reader) Buffered() io.Reader { return r.br }