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.
6 Package zip provides support for reading ZIP archives.
8 See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
10 This package does not support ZIP64 or disk spanning.
27 FormatError = os.NewError("not a valid zip file")
28 UnsupportedMethod = os.NewError("unsupported compression algorithm")
29 ChecksumError = os.NewError("checksum error")
38 type ReadCloser struct {
51 func (f *File) hasDataDescriptor() bool {
52 return f.Flags&0x8 != 0
55 // OpenReader will open the Zip file specified by name and return a ReaderCloser.
56 func OpenReader(name string) (*ReadCloser, os.Error) {
57 f, err := os.Open(name)
67 if err := r.init(f, fi.Size); err != nil {
74 // NewReader returns a new Reader reading from r, which is assumed to
75 // have the given size in bytes.
76 func NewReader(r io.ReaderAt, size int64) (*Reader, os.Error) {
78 if err := zr.init(r, size); err != nil {
84 func (z *Reader) init(r io.ReaderAt, size int64) os.Error {
85 end, err := readDirectoryEnd(r, size)
90 z.File = make([]*File, end.directoryRecords)
91 z.Comment = end.comment
92 rs := io.NewSectionReader(r, 0, size)
93 if _, err = rs.Seek(int64(end.directoryOffset), os.SEEK_SET); err != nil {
96 buf := bufio.NewReader(rs)
97 for i := range z.File {
98 z.File[i] = &File{zipr: r, zipsize: size}
99 if err := readDirectoryHeader(z.File[i], buf); err != nil {
106 // Close closes the Zip file, rendering it unusable for I/O.
107 func (rc *ReadCloser) Close() os.Error {
111 // Open returns a ReadCloser that provides access to the File's contents.
112 func (f *File) Open() (rc io.ReadCloser, err os.Error) {
113 off := int64(f.headerOffset)
114 if f.bodyOffset == 0 {
115 r := io.NewSectionReader(f.zipr, off, f.zipsize-off)
116 if err = readFileHeader(f, r); err != nil {
119 if f.bodyOffset, err = r.Seek(0, os.SEEK_CUR); err != nil {
123 size := int64(f.CompressedSize)
124 if f.hasDataDescriptor() {
126 // permit SectionReader to see the rest of the file
127 size = f.zipsize - (off + f.bodyOffset)
129 size += dataDescriptorLen
132 r := io.NewSectionReader(f.zipr, off+f.bodyOffset, size)
134 case 0: // store (no compression)
135 rc = ioutil.NopCloser(r)
137 rc = flate.NewReader(r)
139 err = UnsupportedMethod
142 rc = &checksumReader{rc, crc32.NewIEEE(), f, r}
147 type checksumReader struct {
151 zipr io.Reader // for reading the data descriptor
154 func (r *checksumReader) Read(b []byte) (n int, err os.Error) {
155 n, err = r.rc.Read(b)
160 if r.f.hasDataDescriptor() {
161 if err = readDataDescriptor(r.zipr, r.f); err != nil {
165 if r.hash.Sum32() != r.f.CRC32 {
171 func (r *checksumReader) Close() os.Error { return r.rc.Close() }
173 func readFileHeader(f *File, r io.Reader) (err os.Error) {
175 if rerr, ok := recover().(os.Error); ok {
181 filenameLength uint16
185 if signature != fileHeaderSignature {
188 read(r, &f.ReaderVersion)
191 read(r, &f.ModifiedTime)
192 read(r, &f.ModifiedDate)
194 read(r, &f.CompressedSize)
195 read(r, &f.UncompressedSize)
196 read(r, &filenameLength)
197 read(r, &extraLength)
198 f.Name = string(readByteSlice(r, filenameLength))
199 f.Extra = readByteSlice(r, extraLength)
203 func readDirectoryHeader(f *File, r io.Reader) (err os.Error) {
205 if rerr, ok := recover().(os.Error); ok {
211 filenameLength uint16
214 startDiskNumber uint16 // unused
215 internalAttributes uint16 // unused
216 externalAttributes uint32 // unused
219 if signature != directoryHeaderSignature {
222 read(r, &f.CreatorVersion)
223 read(r, &f.ReaderVersion)
226 read(r, &f.ModifiedTime)
227 read(r, &f.ModifiedDate)
229 read(r, &f.CompressedSize)
230 read(r, &f.UncompressedSize)
231 read(r, &filenameLength)
232 read(r, &extraLength)
233 read(r, &commentLength)
234 read(r, &startDiskNumber)
235 read(r, &internalAttributes)
236 read(r, &externalAttributes)
237 read(r, &f.headerOffset)
238 f.Name = string(readByteSlice(r, filenameLength))
239 f.Extra = readByteSlice(r, extraLength)
240 f.Comment = string(readByteSlice(r, commentLength))
244 func readDataDescriptor(r io.Reader, f *File) (err os.Error) {
246 if rerr, ok := recover().(os.Error); ok {
251 read(r, &f.CompressedSize)
252 read(r, &f.UncompressedSize)
256 func readDirectoryEnd(r io.ReaderAt, size int64) (d *directoryEnd, err os.Error) {
257 // look for directoryEndSignature in the last 1k, then in the last 65k
259 for i, bLen := range []int64{1024, 65 * 1024} {
263 b = make([]byte, int(bLen))
264 if _, err := r.ReadAt(b, size-bLen); err != nil && err != os.EOF {
267 if p := findSignatureInBlock(b); p >= 0 {
271 if i == 1 || bLen == size {
272 return nil, FormatError
276 // read header into struct
278 if rerr, ok := recover().(os.Error); ok {
283 br := bytes.NewBuffer(b[4:]) // skip over signature
284 d = new(directoryEnd)
286 read(br, &d.dirDiskNbr)
287 read(br, &d.dirRecordsThisDisk)
288 read(br, &d.directoryRecords)
289 read(br, &d.directorySize)
290 read(br, &d.directoryOffset)
291 read(br, &d.commentLen)
292 d.comment = string(readByteSlice(br, d.commentLen))
296 func findSignatureInBlock(b []byte) int {
297 const minSize = 4 + 2 + 2 + 2 + 2 + 4 + 4 + 2 // fixed part of header
298 for i := len(b) - minSize; i >= 0; i-- {
299 // defined from directoryEndSignature in struct.go
300 if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 {
301 // n is length of comment
302 n := int(b[i+minSize-2]) | int(b[i+minSize-1])<<8
303 if n+minSize+i == len(b) {
311 func read(r io.Reader, data interface{}) {
312 if err := binary.Read(r, binary.LittleEndian, data); err != nil {
317 func readByteSlice(r io.Reader, l uint16) []byte {
322 if _, err := io.ReadFull(r, b); err != nil {