| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| (Empty) | |
| 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. | |
| 4 | |
| 5 /* | |
| 6 The zip package provides support for reading ZIP archives as described here:· | |
| 7 http://www.pkware.com/documents/casestudies/APPNOTE.TXT | |
| 8 | |
| 9 This package does not support ZIP64 or disk spanning. | |
| 10 */ | |
| 11 package zip | |
| 12 | |
| 13 import ( | |
| 14 "compress/flate" | |
| 15 "encoding/binary" | |
| 16 "io" | |
| 17 "os" | |
| 18 ) | |
| 19 | |
| 20 var ( | |
| 21 ShortReadError = os.NewError("Short read") | |
| 22 WrongSignatureError = os.NewError("Wrong signature for this header") | |
| 23 NotFoundError = os.NewError("The signature could not be found") | |
| 24 UnsupportedMethod = os.NewError("This file uses an unsupported compres sion method") | |
| 25 ) | |
| 26 | |
| 27 type Reader struct { | |
| 28 directoryEnd | |
|
rsc1
2010/09/08 14:55:50
Does this need to be in the struct?
adg
2010/09/09 10:47:44
No, removed.
| |
| 29 r io.ReaderAt | |
| 30 Files []*File | |
|
rsc1
2010/09/08 14:55:50
we're not entirely consistent about this but
most
adg
2010/09/09 10:47:44
Done.
| |
| 31 Comment string | |
| 32 } | |
| 33 | |
| 34 // File represents a single file within a Zip archive. | |
| 35 type File struct { | |
| 36 directoryHeader | |
|
rsc1
2010/09/08 14:55:50
you have to expose this if you are trying to expos
adg
2010/09/09 10:47:44
Done.
| |
| 37 r *io.SectionReader | |
| 38 header *fileHeader | |
| 39 bodyOffset int64 | |
| 40 Filename string | |
|
rsc1
2010/09/08 14:55:50
It's already in type File. s/Filename/Name/
adg
2010/09/09 10:47:44
Done.
| |
| 41 } | |
| 42 | |
| 43 // ReadFile will open the Zip file specified by name and return a Reader. | |
| 44 func ReadFile(name string) (r *Reader, err os.Error) { | |
|
rsc1
2010/09/08 14:55:50
s/ReadFile/OpenReader/?
adg
2010/09/09 10:47:44
Done.
| |
| 45 f, err := os.Open(name, os.O_RDONLY, 0644) | |
| 46 if err != nil { | |
| 47 return | |
| 48 } | |
| 49 fi, err := f.Stat() | |
| 50 if err != nil { | |
| 51 return | |
| 52 } | |
| 53 return NewReader(f, fi.Size) | |
| 54 } | |
| 55 | |
| 56 // NewReader takes a ReaderAt and stream length,· | |
|
rsc1
2010/09/08 14:55:50
You can drop the "takes" notes: it's in the code.
adg
2010/09/09 10:47:44
Done.
| |
| 57 // reads the Zip file metadata, | |
| 58 // and returns a *Reader with its Files element populated | |
| 59 // with the Zip file's contents. | |
| 60 func NewReader(r io.ReaderAt, length int64) (z *Reader, err os.Error) { | |
|
rsc1
2010/09/08 14:55:50
s/length/size/ to match os.FileInfo
adg
2010/09/09 10:47:44
Done.
| |
| 61 rs := io.NewSectionReader(r, 0, length) | |
| 62 offs, err := findDirectoryEndOffset(rs) | |
|
rsc1
2010/09/08 14:55:50
s/offs/off/
offs sounds plural
adg
2010/09/09 10:47:44
Done.
| |
| 63 if err != nil { | |
| 64 return | |
| 65 } | |
| 66 if _, err = rs.Seek(offs, 0); err != nil { | |
| 67 return | |
| 68 } | |
| 69 end, err := readDirectoryEnd(rs) | |
| 70 if err != nil { | |
| 71 return | |
| 72 } | |
| 73 z = &Reader{ | |
| 74 directoryEnd: *end, | |
| 75 r: r, | |
| 76 Files: make([]*File, end.directoryRecords), | |
| 77 Comment: end.Comment, | |
| 78 } | |
| 79 if _, err = rs.Seek(int64(z.directoryOffset), 0); err != nil { | |
| 80 return | |
| 81 } | |
| 82 for i := range z.Files { | |
| 83 dh, err := readDirectoryHeader(rs) | |
| 84 if err != nil { | |
| 85 return nil, err | |
|
rsc1
2010/09/08 14:55:50
I don't like using named return values in
anythin
adg
2010/09/09 10:47:44
Done.
| |
| 86 } | |
| 87 z.Files[i] = &File{ | |
| 88 directoryHeader: *dh, | |
| 89 Filename: dh.Filename, | |
| 90 } | |
| 91 } | |
| 92 for i, f := range z.Files { | |
| 93 offs := int64(f.fileHeaderOffset) | |
|
rsc1
2010/09/08 14:55:50
s/offs/off/
adg
2010/09/09 10:47:44
Done.
| |
| 94 var max int64 | |
| 95 if i == len(z.Files)-1 { | |
| 96 max = int64(z.directoryOffset) | |
| 97 } else { | |
| 98 max = int64(z.Files[i+1].fileHeaderOffset) | |
| 99 } | |
| 100 max -= offs | |
| 101 f.r = io.NewSectionReader(r, offs, max) | |
| 102 } | |
| 103 return | |
| 104 } | |
| 105 | |
| 106 // Open returns a ReadCloser that provides access to the File's contents. | |
| 107 func (f *File) Open() (rc io.ReadCloser, err os.Error) { | |
| 108 if f.header == nil { | |
| 109 if _, err = f.r.Seek(0, 0); err != nil { | |
| 110 return | |
| 111 } | |
| 112 if f.header, err = readFileHeader(f.r); err != nil { | |
| 113 return | |
| 114 } | |
| 115 if f.bodyOffset, err = f.r.Seek(0, 1); err != nil { | |
| 116 return | |
| 117 } | |
| 118 } | |
| 119 reader := io.NewSectionReader(f.r, f.bodyOffset, int64(f.header.Compress edSize)) | |
| 120 switch f.Method { | |
| 121 case 0: // store (no compression) | |
| 122 rc = nopCloser{reader} | |
| 123 case 8: // DEFLATE | |
| 124 rc = flate.NewReader(reader) | |
| 125 default: | |
| 126 err = UnsupportedMethod | |
| 127 } | |
| 128 return | |
| 129 } | |
| 130 | |
| 131 type nopCloser struct { | |
| 132 io.Reader | |
| 133 } | |
| 134 | |
| 135 func (f nopCloser) Close() os.Error { return nil } | |
| 136 | |
| 137 func readFileHeader(r io.Reader) (f *fileHeader, err os.Error) { | |
| 138 defer func() { | |
| 139 if rerr, ok := recover().(os.Error); rerr != nil && ok { | |
|
rsc1
2010/09/08 14:55:50
rerr != nil is implied by ok
adg
2010/09/09 10:47:44
Done.
| |
| 140 err = rerr | |
| 141 } | |
| 142 }() | |
| 143 f = new(fileHeader) | |
| 144 read(r, &f.signature) | |
| 145 if f.signature != fileHeaderSignature { | |
| 146 return nil, WrongSignatureError | |
| 147 } | |
| 148 read(r, &f.ReaderVersion) | |
| 149 read(r, &f.flags) | |
| 150 read(r, &f.Method) | |
| 151 read(r, &f.modifiedTime) | |
| 152 read(r, &f.modifiedDate) | |
| 153 read(r, &f.CRC32) | |
| 154 read(r, &f.CompressedSize) | |
| 155 read(r, &f.UncompressedSize) | |
| 156 read(r, &f.filenameLength) | |
| 157 read(r, &f.extraLength) | |
| 158 f.Filename = string(readByteSlice(r, f.filenameLength)) | |
| 159 f.Extra = readByteSlice(r, f.extraLength) | |
| 160 return | |
| 161 } | |
| 162 | |
| 163 func readDirectoryHeader(r io.Reader) (d *directoryHeader, err os.Error) { | |
| 164 defer func() { | |
| 165 if rerr, ok := recover().(os.Error); rerr != nil && ok { | |
|
rsc1
2010/09/08 14:55:50
same
adg
2010/09/09 10:47:44
Done.
| |
| 166 err = rerr | |
| 167 } | |
| 168 }() | |
| 169 d = new(directoryHeader) | |
| 170 read(r, &d.signature) | |
| 171 if d.signature != directoryHeaderSignature { | |
| 172 return nil, WrongSignatureError | |
| 173 } | |
| 174 read(r, &d.CreatorVersion) | |
| 175 read(r, &d.ReaderVersion) | |
| 176 read(r, &d.flags) | |
| 177 read(r, &d.Method) | |
| 178 read(r, &d.modifiedTime) | |
| 179 read(r, &d.modifiedDate) | |
| 180 read(r, &d.CRC32) | |
| 181 read(r, &d.CompressedSize) | |
| 182 read(r, &d.UncompressedSize) | |
| 183 read(r, &d.filenameLength) | |
| 184 read(r, &d.extraLength) | |
| 185 read(r, &d.commentLength) | |
| 186 read(r, &d.startDiskNumber) | |
| 187 read(r, &d.internalAttributes) | |
| 188 read(r, &d.externalAttributes) | |
| 189 read(r, &d.fileHeaderOffset) | |
| 190 d.Filename = string(readByteSlice(r, d.filenameLength)) | |
| 191 d.Extra = readByteSlice(r, d.extraLength) | |
| 192 d.Commment = string(readByteSlice(r, d.commentLength)) | |
| 193 return | |
| 194 } | |
| 195 | |
| 196 func readDirectoryEnd(r io.Reader) (d *directoryEnd, err os.Error) { | |
| 197 defer func() { | |
| 198 if rerr, ok := recover().(os.Error); rerr != nil && ok { | |
| 199 err = rerr | |
| 200 } | |
| 201 }() | |
| 202 d = new(directoryEnd) | |
| 203 read(r, &d.signature) | |
| 204 if d.signature != directoryEndSignature { | |
| 205 return nil, WrongSignatureError | |
| 206 } | |
| 207 read(r, &d.diskNbr) | |
| 208 read(r, &d.dirDiskNbr) | |
| 209 read(r, &d.dirRecordsThisDisk) | |
| 210 read(r, &d.directoryRecords) | |
| 211 read(r, &d.directorySize) | |
| 212 read(r, &d.directoryOffset) | |
| 213 read(r, &d.commentLen) | |
| 214 d.Comment = string(readByteSlice(r, d.commentLen)) | |
| 215 return | |
| 216 } | |
| 217 | |
| 218 func findDirectoryEndOffset(rs io.ReadSeeker) (offset int64, err os.Error) { | |
| 219 const minRecordSize = 4 + 2 + 2 + 2 + 2 + 4 + 4 + 2 | |
| 220 offset, err = rs.Seek(-minRecordSize, 2) // relative to end-of-file | |
| 221 if err != nil { | |
| 222 return | |
| 223 } | |
| 224 for ; offset >= 0; offset-- { | |
| 225 rs.Seek(offset, 0) | |
| 226 // TODO: this is very inefficient, but it works. | |
| 227 // Better to write the little-endian representation of | |
| 228 // directoryEndSignature to a []byte and then | |
| 229 // seek backwards comparing subslices of the read buffer. | |
| 230 var sig uint32 | |
| 231 err = binary.Read(rs, binary.LittleEndian, &sig) | |
| 232 if err != nil { | |
| 233 return 0, err | |
| 234 } | |
| 235 if sig == directoryEndSignature { | |
| 236 return | |
| 237 } | |
| 238 } | |
| 239 return 0, NotFoundError | |
| 240 } | |
| 241 | |
| 242 func read(r io.Reader, data interface{}) { | |
| 243 if err := binary.Read(r, binary.LittleEndian, data); err != nil { | |
| 244 panic(err) | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 func readByteSlice(r io.Reader, l uint16) []byte { | |
| 249 b := make([]byte, l) | |
| 250 if l == 0 { | |
| 251 return b | |
| 252 } | |
| 253 n, err := r.Read(b) | |
| 254 if err != nil { | |
| 255 panic(err) | |
| 256 } | |
| 257 if n != int(l) { | |
| 258 panic(ShortReadError) | |
| 259 } | |
| 260 return b | |
| 261 } | |
| OLD | NEW |