github.com/eun/go@v0.0.0-20170811110501-92cfd07a6cfd/src/archive/zip/writer.go (about) 1 // Copyright 2011 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 package zip 6 7 import ( 8 "bufio" 9 "encoding/binary" 10 "errors" 11 "hash" 12 "hash/crc32" 13 "io" 14 "unicode/utf8" 15 ) 16 17 // Writer implements a zip file writer. 18 type Writer struct { 19 cw *countWriter 20 dir []*header 21 last *fileWriter 22 closed bool 23 compressors map[uint16]Compressor 24 25 // testHookCloseSizeOffset if non-nil is called with the size 26 // of offset of the central directory at Close. 27 testHookCloseSizeOffset func(size, offset uint64) 28 } 29 30 type header struct { 31 *FileHeader 32 offset uint64 33 } 34 35 // NewWriter returns a new Writer writing a zip file to w. 36 func NewWriter(w io.Writer) *Writer { 37 return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}} 38 } 39 40 // SetOffset sets the offset of the beginning of the zip data within the 41 // underlying writer. It should be used when the zip data is appended to an 42 // existing file, such as a binary executable. 43 // It must be called before any data is written. 44 func (w *Writer) SetOffset(n int64) { 45 if w.cw.count != 0 { 46 panic("zip: SetOffset called after data was written") 47 } 48 w.cw.count = n 49 } 50 51 // Flush flushes any buffered data to the underlying writer. 52 // Calling Flush is not normally necessary; calling Close is sufficient. 53 func (w *Writer) Flush() error { 54 return w.cw.w.(*bufio.Writer).Flush() 55 } 56 57 // Close finishes writing the zip file by writing the central directory. 58 // It does not (and cannot) close the underlying writer. 59 func (w *Writer) Close() error { 60 if w.last != nil && !w.last.closed { 61 if err := w.last.close(); err != nil { 62 return err 63 } 64 w.last = nil 65 } 66 if w.closed { 67 return errors.New("zip: writer closed twice") 68 } 69 w.closed = true 70 71 // write central directory 72 start := w.cw.count 73 for _, h := range w.dir { 74 var buf [directoryHeaderLen]byte 75 b := writeBuf(buf[:]) 76 b.uint32(uint32(directoryHeaderSignature)) 77 b.uint16(h.CreatorVersion) 78 b.uint16(h.ReaderVersion) 79 b.uint16(h.Flags) 80 b.uint16(h.Method) 81 b.uint16(h.ModifiedTime) 82 b.uint16(h.ModifiedDate) 83 b.uint32(h.CRC32) 84 if h.isZip64() || h.offset >= uint32max { 85 // the file needs a zip64 header. store maxint in both 86 // 32 bit size fields (and offset later) to signal that the 87 // zip64 extra header should be used. 88 b.uint32(uint32max) // compressed size 89 b.uint32(uint32max) // uncompressed size 90 91 // append a zip64 extra block to Extra 92 var buf [28]byte // 2x uint16 + 3x uint64 93 eb := writeBuf(buf[:]) 94 eb.uint16(zip64ExtraId) 95 eb.uint16(24) // size = 3x uint64 96 eb.uint64(h.UncompressedSize64) 97 eb.uint64(h.CompressedSize64) 98 eb.uint64(h.offset) 99 h.Extra = append(h.Extra, buf[:]...) 100 } else { 101 b.uint32(h.CompressedSize) 102 b.uint32(h.UncompressedSize) 103 } 104 105 b.uint16(uint16(len(h.Name))) 106 b.uint16(uint16(len(h.Extra))) 107 b.uint16(uint16(len(h.Comment))) 108 b = b[4:] // skip disk number start and internal file attr (2x uint16) 109 b.uint32(h.ExternalAttrs) 110 if h.offset > uint32max { 111 b.uint32(uint32max) 112 } else { 113 b.uint32(uint32(h.offset)) 114 } 115 if _, err := w.cw.Write(buf[:]); err != nil { 116 return err 117 } 118 if _, err := io.WriteString(w.cw, h.Name); err != nil { 119 return err 120 } 121 if _, err := w.cw.Write(h.Extra); err != nil { 122 return err 123 } 124 if _, err := io.WriteString(w.cw, h.Comment); err != nil { 125 return err 126 } 127 } 128 end := w.cw.count 129 130 records := uint64(len(w.dir)) 131 size := uint64(end - start) 132 offset := uint64(start) 133 134 if f := w.testHookCloseSizeOffset; f != nil { 135 f(size, offset) 136 } 137 138 if records >= uint16max || size >= uint32max || offset >= uint32max { 139 var buf [directory64EndLen + directory64LocLen]byte 140 b := writeBuf(buf[:]) 141 142 // zip64 end of central directory record 143 b.uint32(directory64EndSignature) 144 b.uint64(directory64EndLen - 12) // length minus signature (uint32) and length fields (uint64) 145 b.uint16(zipVersion45) // version made by 146 b.uint16(zipVersion45) // version needed to extract 147 b.uint32(0) // number of this disk 148 b.uint32(0) // number of the disk with the start of the central directory 149 b.uint64(records) // total number of entries in the central directory on this disk 150 b.uint64(records) // total number of entries in the central directory 151 b.uint64(size) // size of the central directory 152 b.uint64(offset) // offset of start of central directory with respect to the starting disk number 153 154 // zip64 end of central directory locator 155 b.uint32(directory64LocSignature) 156 b.uint32(0) // number of the disk with the start of the zip64 end of central directory 157 b.uint64(uint64(end)) // relative offset of the zip64 end of central directory record 158 b.uint32(1) // total number of disks 159 160 if _, err := w.cw.Write(buf[:]); err != nil { 161 return err 162 } 163 164 // store max values in the regular end record to signal that 165 // that the zip64 values should be used instead 166 records = uint16max 167 size = uint32max 168 offset = uint32max 169 } 170 171 // write end record 172 var buf [directoryEndLen]byte 173 b := writeBuf(buf[:]) 174 b.uint32(uint32(directoryEndSignature)) 175 b = b[4:] // skip over disk number and first disk number (2x uint16) 176 b.uint16(uint16(records)) // number of entries this disk 177 b.uint16(uint16(records)) // number of entries total 178 b.uint32(uint32(size)) // size of directory 179 b.uint32(uint32(offset)) // start of directory 180 // skipped size of comment (always zero) 181 if _, err := w.cw.Write(buf[:]); err != nil { 182 return err 183 } 184 185 return w.cw.w.(*bufio.Writer).Flush() 186 } 187 188 // Create adds a file to the zip file using the provided name. 189 // It returns a Writer to which the file contents should be written. 190 // The name must be a relative path: it must not start with a drive 191 // letter (e.g. C:) or leading slash, and only forward slashes are 192 // allowed. 193 // The file's contents must be written to the io.Writer before the next 194 // call to Create, CreateHeader, or Close. 195 func (w *Writer) Create(name string) (io.Writer, error) { 196 header := &FileHeader{ 197 Name: name, 198 Method: Deflate, 199 } 200 return w.CreateHeader(header) 201 } 202 203 func hasValidUTF8(s string) bool { 204 n := 0 205 for _, r := range s { 206 // By default, ZIP uses CP437, which is only identical to ASCII for the printable characters. 207 if r < 0x20 || r >= 0x7f { 208 if !utf8.ValidRune(r) { 209 return false 210 } 211 n++ 212 } 213 } 214 return n > 0 215 } 216 217 // CreateHeader adds a file to the zip file using the provided FileHeader 218 // for the file metadata. 219 // It returns a Writer to which the file contents should be written. 220 // 221 // The file's contents must be written to the io.Writer before the next 222 // call to Create, CreateHeader, or Close. The provided FileHeader fh 223 // must not be modified after a call to CreateHeader. 224 func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { 225 if w.last != nil && !w.last.closed { 226 if err := w.last.close(); err != nil { 227 return nil, err 228 } 229 } 230 if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh { 231 // See https://golang.org/issue/11144 confusion. 232 return nil, errors.New("archive/zip: invalid duplicate FileHeader") 233 } 234 235 fh.Flags |= 0x8 // we will write a data descriptor 236 237 if hasValidUTF8(fh.Name) || hasValidUTF8(fh.Comment) { 238 fh.Flags |= 0x800 // filename or comment have valid utf-8 string 239 } 240 241 fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte 242 fh.ReaderVersion = zipVersion20 243 244 fw := &fileWriter{ 245 zipw: w.cw, 246 compCount: &countWriter{w: w.cw}, 247 crc32: crc32.NewIEEE(), 248 } 249 comp := w.compressor(fh.Method) 250 if comp == nil { 251 return nil, ErrAlgorithm 252 } 253 var err error 254 fw.comp, err = comp(fw.compCount) 255 if err != nil { 256 return nil, err 257 } 258 fw.rawCount = &countWriter{w: fw.comp} 259 260 h := &header{ 261 FileHeader: fh, 262 offset: uint64(w.cw.count), 263 } 264 w.dir = append(w.dir, h) 265 fw.header = h 266 267 if err := writeHeader(w.cw, fh); err != nil { 268 return nil, err 269 } 270 271 w.last = fw 272 return fw, nil 273 } 274 275 func writeHeader(w io.Writer, h *FileHeader) error { 276 var buf [fileHeaderLen]byte 277 b := writeBuf(buf[:]) 278 b.uint32(uint32(fileHeaderSignature)) 279 b.uint16(h.ReaderVersion) 280 b.uint16(h.Flags) 281 b.uint16(h.Method) 282 b.uint16(h.ModifiedTime) 283 b.uint16(h.ModifiedDate) 284 b.uint32(0) // since we are writing a data descriptor crc32, 285 b.uint32(0) // compressed size, 286 b.uint32(0) // and uncompressed size should be zero 287 b.uint16(uint16(len(h.Name))) 288 b.uint16(uint16(len(h.Extra))) 289 if _, err := w.Write(buf[:]); err != nil { 290 return err 291 } 292 if _, err := io.WriteString(w, h.Name); err != nil { 293 return err 294 } 295 _, err := w.Write(h.Extra) 296 return err 297 } 298 299 // RegisterCompressor registers or overrides a custom compressor for a specific 300 // method ID. If a compressor for a given method is not found, Writer will 301 // default to looking up the compressor at the package level. 302 func (w *Writer) RegisterCompressor(method uint16, comp Compressor) { 303 if w.compressors == nil { 304 w.compressors = make(map[uint16]Compressor) 305 } 306 w.compressors[method] = comp 307 } 308 309 func (w *Writer) compressor(method uint16) Compressor { 310 comp := w.compressors[method] 311 if comp == nil { 312 comp = compressor(method) 313 } 314 return comp 315 } 316 317 type fileWriter struct { 318 *header 319 zipw io.Writer 320 rawCount *countWriter 321 comp io.WriteCloser 322 compCount *countWriter 323 crc32 hash.Hash32 324 closed bool 325 } 326 327 func (w *fileWriter) Write(p []byte) (int, error) { 328 if w.closed { 329 return 0, errors.New("zip: write to closed file") 330 } 331 w.crc32.Write(p) 332 return w.rawCount.Write(p) 333 } 334 335 func (w *fileWriter) close() error { 336 if w.closed { 337 return errors.New("zip: file closed twice") 338 } 339 w.closed = true 340 if err := w.comp.Close(); err != nil { 341 return err 342 } 343 344 // update FileHeader 345 fh := w.header.FileHeader 346 fh.CRC32 = w.crc32.Sum32() 347 fh.CompressedSize64 = uint64(w.compCount.count) 348 fh.UncompressedSize64 = uint64(w.rawCount.count) 349 350 if fh.isZip64() { 351 fh.CompressedSize = uint32max 352 fh.UncompressedSize = uint32max 353 fh.ReaderVersion = zipVersion45 // requires 4.5 - File uses ZIP64 format extensions 354 } else { 355 fh.CompressedSize = uint32(fh.CompressedSize64) 356 fh.UncompressedSize = uint32(fh.UncompressedSize64) 357 } 358 359 // Write data descriptor. This is more complicated than one would 360 // think, see e.g. comments in zipfile.c:putextended() and 361 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588. 362 // The approach here is to write 8 byte sizes if needed without 363 // adding a zip64 extra in the local header (too late anyway). 364 var buf []byte 365 if fh.isZip64() { 366 buf = make([]byte, dataDescriptor64Len) 367 } else { 368 buf = make([]byte, dataDescriptorLen) 369 } 370 b := writeBuf(buf) 371 b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X 372 b.uint32(fh.CRC32) 373 if fh.isZip64() { 374 b.uint64(fh.CompressedSize64) 375 b.uint64(fh.UncompressedSize64) 376 } else { 377 b.uint32(fh.CompressedSize) 378 b.uint32(fh.UncompressedSize) 379 } 380 _, err := w.zipw.Write(buf) 381 return err 382 } 383 384 type countWriter struct { 385 w io.Writer 386 count int64 387 } 388 389 func (w *countWriter) Write(p []byte) (int, error) { 390 n, err := w.w.Write(p) 391 w.count += int64(n) 392 return n, err 393 } 394 395 type nopCloser struct { 396 io.Writer 397 } 398 399 func (w nopCloser) Close() error { 400 return nil 401 } 402 403 type writeBuf []byte 404 405 func (b *writeBuf) uint16(v uint16) { 406 binary.LittleEndian.PutUint16(*b, v) 407 *b = (*b)[2:] 408 } 409 410 func (b *writeBuf) uint32(v uint32) { 411 binary.LittleEndian.PutUint32(*b, v) 412 *b = (*b)[4:] 413 } 414 415 func (b *writeBuf) uint64(v uint64) { 416 binary.LittleEndian.PutUint64(*b, v) 417 *b = (*b)[8:] 418 }