1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
19 FormatError = os.NewError("zip: not a valid zip file")
20 UnsupportedMethod = os.NewError("zip: unsupported compression algorithm")
21 ChecksumError = os.NewError("zip: checksum error")
30 type ReadCloser struct {
42 func (f *File) hasDataDescriptor() bool {
43 return f.Flags&0x8 != 0
46 // OpenReader will open the Zip file specified by name and return a ReadCloser.
47 func OpenReader(name string) (*ReadCloser, os.Error) {
48 f, err := os.Open(name)
58 if err := r.init(f, fi.Size); err != nil {
65 // NewReader returns a new Reader reading from r, which is assumed to
66 // have the given size in bytes.
67 func NewReader(r io.ReaderAt, size int64) (*Reader, os.Error) {
69 if err := zr.init(r, size); err != nil {
75 func (z *Reader) init(r io.ReaderAt, size int64) os.Error {
76 end, err := readDirectoryEnd(r, size)
81 z.File = make([]*File, 0, end.directoryRecords)
82 z.Comment = end.comment
83 rs := io.NewSectionReader(r, 0, size)
84 if _, err = rs.Seek(int64(end.directoryOffset), os.SEEK_SET); err != nil {
87 buf := bufio.NewReader(rs)
89 // The count of files inside a zip is truncated to fit in a uint16.
90 // Gloss over this by reading headers until we encounter
91 // a bad one, and then only report a FormatError or UnexpectedEOF if
92 // the file count modulo 65536 is incorrect.
94 f := &File{zipr: r, zipsize: size}
95 err = readDirectoryHeader(f, buf)
96 if err == FormatError || err == io.ErrUnexpectedEOF {
102 z.File = append(z.File, f)
104 if uint16(len(z.File)) != end.directoryRecords {
105 // Return the readDirectoryHeader error if we read
106 // the wrong number of directory entries.
112 // Close closes the Zip file, rendering it unusable for I/O.
113 func (rc *ReadCloser) Close() os.Error {
117 // Open returns a ReadCloser that provides access to the File's contents.
118 // It is safe to Open and Read from files concurrently.
119 func (f *File) Open() (rc io.ReadCloser, err os.Error) {
120 bodyOffset, err := f.findBodyOffset()
124 size := int64(f.CompressedSize)
125 if size == 0 && f.hasDataDescriptor() {
126 // permit SectionReader to see the rest of the file
127 size = f.zipsize - (f.headerOffset + bodyOffset)
129 r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size)
131 case Store: // (no compression)
132 rc = ioutil.NopCloser(r)
134 rc = flate.NewReader(r)
136 err = UnsupportedMethod
139 rc = &checksumReader{rc, crc32.NewIEEE(), f, r}
144 type checksumReader struct {
148 zipr io.Reader // for reading the data descriptor
151 func (r *checksumReader) Read(b []byte) (n int, err os.Error) {
152 n, err = r.rc.Read(b)
157 if r.f.hasDataDescriptor() {
158 if err = readDataDescriptor(r.zipr, r.f); err != nil {
162 if r.hash.Sum32() != r.f.CRC32 {
168 func (r *checksumReader) Close() os.Error { return r.rc.Close() }
170 func readFileHeader(f *File, r io.Reader) os.Error {
171 var b [fileHeaderLen]byte
172 if _, err := io.ReadFull(r, b[:]); err != nil {
175 c := binary.LittleEndian
176 if sig := c.Uint32(b[:4]); sig != fileHeaderSignature {
179 f.ReaderVersion = c.Uint16(b[4:6])
180 f.Flags = c.Uint16(b[6:8])
181 f.Method = c.Uint16(b[8:10])
182 f.ModifiedTime = c.Uint16(b[10:12])
183 f.ModifiedDate = c.Uint16(b[12:14])
184 f.CRC32 = c.Uint32(b[14:18])
185 f.CompressedSize = c.Uint32(b[18:22])
186 f.UncompressedSize = c.Uint32(b[22:26])
187 filenameLen := int(c.Uint16(b[26:28]))
188 extraLen := int(c.Uint16(b[28:30]))
189 d := make([]byte, filenameLen+extraLen)
190 if _, err := io.ReadFull(r, d); err != nil {
193 f.Name = string(d[:filenameLen])
194 f.Extra = d[filenameLen:]
198 // findBodyOffset does the minimum work to verify the file has a header
199 // and returns the file body offset.
200 func (f *File) findBodyOffset() (int64, os.Error) {
201 r := io.NewSectionReader(f.zipr, f.headerOffset, f.zipsize-f.headerOffset)
202 var b [fileHeaderLen]byte
203 if _, err := io.ReadFull(r, b[:]); err != nil {
206 c := binary.LittleEndian
207 if sig := c.Uint32(b[:4]); sig != fileHeaderSignature {
208 return 0, FormatError
210 filenameLen := int(c.Uint16(b[26:28]))
211 extraLen := int(c.Uint16(b[28:30]))
212 return int64(fileHeaderLen + filenameLen + extraLen), nil
215 // readDirectoryHeader attempts to read a directory header from r.
216 // It returns io.ErrUnexpectedEOF if it cannot read a complete header,
217 // and FormatError if it doesn't find a valid header signature.
218 func readDirectoryHeader(f *File, r io.Reader) os.Error {
219 var b [directoryHeaderLen]byte
220 if _, err := io.ReadFull(r, b[:]); err != nil {
223 c := binary.LittleEndian
224 if sig := c.Uint32(b[:4]); sig != directoryHeaderSignature {
227 f.CreatorVersion = c.Uint16(b[4:6])
228 f.ReaderVersion = c.Uint16(b[6:8])
229 f.Flags = c.Uint16(b[8:10])
230 f.Method = c.Uint16(b[10:12])
231 f.ModifiedTime = c.Uint16(b[12:14])
232 f.ModifiedDate = c.Uint16(b[14:16])
233 f.CRC32 = c.Uint32(b[16:20])
234 f.CompressedSize = c.Uint32(b[20:24])
235 f.UncompressedSize = c.Uint32(b[24:28])
236 filenameLen := int(c.Uint16(b[28:30]))
237 extraLen := int(c.Uint16(b[30:32]))
238 commentLen := int(c.Uint16(b[32:34]))
239 // startDiskNumber := c.Uint16(b[34:36]) // Unused
240 // internalAttributes := c.Uint16(b[36:38]) // Unused
241 f.ExternalAttrs = c.Uint32(b[38:42])
242 f.headerOffset = int64(c.Uint32(b[42:46]))
243 d := make([]byte, filenameLen+extraLen+commentLen)
244 if _, err := io.ReadFull(r, d); err != nil {
247 f.Name = string(d[:filenameLen])
248 f.Extra = d[filenameLen : filenameLen+extraLen]
249 f.Comment = string(d[filenameLen+extraLen:])
253 func readDataDescriptor(r io.Reader, f *File) os.Error {
254 var b [dataDescriptorLen]byte
255 if _, err := io.ReadFull(r, b[:]); err != nil {
258 c := binary.LittleEndian
259 f.CRC32 = c.Uint32(b[:4])
260 f.CompressedSize = c.Uint32(b[4:8])
261 f.UncompressedSize = c.Uint32(b[8:12])
265 func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err os.Error) {
266 // look for directoryEndSignature in the last 1k, then in the last 65k
268 for i, bLen := range []int64{1024, 65 * 1024} {
272 b = make([]byte, int(bLen))
273 if _, err := r.ReadAt(b, size-bLen); err != nil && err != os.EOF {
276 if p := findSignatureInBlock(b); p >= 0 {
280 if i == 1 || bLen == size {
281 return nil, FormatError
285 // read header into struct
286 c := binary.LittleEndian
287 d := new(directoryEnd)
288 d.diskNbr = c.Uint16(b[4:6])
289 d.dirDiskNbr = c.Uint16(b[6:8])
290 d.dirRecordsThisDisk = c.Uint16(b[8:10])
291 d.directoryRecords = c.Uint16(b[10:12])
292 d.directorySize = c.Uint32(b[12:16])
293 d.directoryOffset = c.Uint32(b[16:20])
294 d.commentLen = c.Uint16(b[20:22])
295 d.comment = string(b[22 : 22+int(d.commentLen)])
299 func findSignatureInBlock(b []byte) int {
300 for i := len(b) - directoryEndLen; i >= 0; i-- {
301 // defined from directoryEndSignature in struct.go
302 if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 {
303 // n is length of comment
304 n := int(b[i+directoryEndLen-2]) | int(b[i+directoryEndLen-1])<<8
305 if n+directoryEndLen+i == len(b) {