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