...

Source file src/archive/zip/writer.go

Documentation: archive/zip

     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  	"io/fs"
    15  	"strings"
    16  	"unicode/utf8"
    17  )
    18  
    19  var (
    20  	errLongName  = errors.New("zip: FileHeader.Name too long")
    21  	errLongExtra = errors.New("zip: FileHeader.Extra too long")
    22  )
    23  
    24  // Writer implements a zip file writer.
    25  type Writer struct {
    26  	cw          *countWriter
    27  	dir         []*header
    28  	last        *fileWriter
    29  	closed      bool
    30  	compressors map[uint16]Compressor
    31  	comment     string
    32  
    33  	// testHookCloseSizeOffset if non-nil is called with the size
    34  	// of offset of the central directory at Close.
    35  	testHookCloseSizeOffset func(size, offset uint64)
    36  }
    37  
    38  type header struct {
    39  	*FileHeader
    40  	offset uint64
    41  	raw    bool
    42  }
    43  
    44  // NewWriter returns a new [Writer] writing a zip file to w.
    45  //
    46  // Note that the exact bytes written to w are not covered by the Go 1
    47  // compatibility promise. Callers, including tests, should not depend on the
    48  // exact written bytes.
    49  func NewWriter(w io.Writer) *Writer {
    50  	return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}}
    51  }
    52  
    53  // SetOffset sets the offset of the beginning of the zip data within the
    54  // underlying writer. It should be used when the zip data is appended to an
    55  // existing file, such as a binary executable.
    56  // It must be called before any data is written.
    57  func (w *Writer) SetOffset(n int64) {
    58  	if w.cw.count != 0 {
    59  		panic("zip: SetOffset called after data was written")
    60  	}
    61  	w.cw.count = n
    62  }
    63  
    64  // Flush flushes any buffered data to the underlying writer.
    65  // Calling Flush is not normally necessary; calling Close is sufficient.
    66  func (w *Writer) Flush() error {
    67  	return w.cw.w.(*bufio.Writer).Flush()
    68  }
    69  
    70  // SetComment sets the end-of-central-directory comment field.
    71  // It can only be called before [Writer.Close].
    72  func (w *Writer) SetComment(comment string) error {
    73  	if len(comment) > uint16max {
    74  		return errors.New("zip: Writer.Comment too long")
    75  	}
    76  	w.comment = comment
    77  	return nil
    78  }
    79  
    80  // Close finishes writing the zip file by writing the central directory.
    81  // It does not close the underlying writer.
    82  func (w *Writer) Close() error {
    83  	if w.last != nil && !w.last.closed {
    84  		if err := w.last.close(); err != nil {
    85  			return err
    86  		}
    87  		w.last = nil
    88  	}
    89  	if w.closed {
    90  		return errors.New("zip: writer closed twice")
    91  	}
    92  	w.closed = true
    93  
    94  	// write central directory
    95  	start := w.cw.count
    96  	usedZip64 := false
    97  	for _, h := range w.dir {
    98  		// For the Central Directory, we always have the correct sizes.
    99  		//
   100  		// Implementations disagree on what triggers the inclusion of a Zip64
   101  		// extra field: Info-ZIP only writes it if any size or offset EXCEEDS
   102  		// 4GiB - 1, while libarchive writes it if any size REACHES OR EXCEEDS
   103  		// 4GiB - 1, or if the offset EXCEEDS 4GiB - 1. The spec is ambiguous.
   104  		//
   105  		// We conservatively write Zip64 extra fields if any size or offset
   106  		// REACHES OR EXCEEDS 4GiB - 1, to maximize compatibility with readers.
   107  		// There is no ambiguity in parsing, so there is no downside to it.
   108  		//
   109  		// The spec is clear though that all and only the fields that REACH OR
   110  		// EXCEED 4GiB - 1 are included in the Zip64 extra, once it's present.
   111  		readerVersion := h.ReaderVersion
   112  		if h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max || h.offset >= uint32max {
   113  			usedZip64 = true
   114  			readerVersion = max(readerVersion, zipVersion45)
   115  			var size uint16
   116  			var buf [28]byte // 2x uint16 + up to 3x uint64
   117  			eb := writeBuf(buf[:])
   118  			eb.uint16(zip64ExtraID)
   119  			eb.uint16(0) // size to be filled out later
   120  			if h.UncompressedSize64 >= uint32max {
   121  				eb.uint64(h.UncompressedSize64)
   122  				size += 8
   123  			}
   124  			if h.CompressedSize64 >= uint32max {
   125  				eb.uint64(h.CompressedSize64)
   126  				size += 8
   127  			}
   128  			if h.offset >= uint32max {
   129  				eb.uint64(h.offset)
   130  				size += 8
   131  			}
   132  			sb := writeBuf(buf[2:])
   133  			sb.uint16(size)
   134  			h.Extra = append(h.Extra, buf[:4+size]...)
   135  		}
   136  
   137  		var buf [directoryHeaderLen]byte
   138  		b := writeBuf(buf[:])
   139  		b.uint32(uint32(directoryHeaderSignature))
   140  		b.uint16(h.CreatorVersion)
   141  		b.uint16(readerVersion)
   142  		b.uint16(h.Flags)
   143  		b.uint16(h.Method)
   144  		b.uint16(h.ModifiedTime)
   145  		b.uint16(h.ModifiedDate)
   146  		b.uint32(h.CRC32)
   147  		b.uint32(uint32(min(h.CompressedSize64, uint32max)))
   148  		b.uint32(uint32(min(h.UncompressedSize64, uint32max)))
   149  		b.uint16(uint16(len(h.Name)))
   150  		b.uint16(uint16(len(h.Extra)))
   151  		b.uint16(uint16(len(h.Comment)))
   152  		b = b[4:] // skip disk number start and internal file attr (2x uint16)
   153  		b.uint32(h.ExternalAttrs)
   154  		b.uint32(uint32(min(h.offset, uint32max)))
   155  		if _, err := w.cw.Write(buf[:]); err != nil {
   156  			return err
   157  		}
   158  		if _, err := io.WriteString(w.cw, h.Name); err != nil {
   159  			return err
   160  		}
   161  		if _, err := w.cw.Write(h.Extra); err != nil {
   162  			return err
   163  		}
   164  		if _, err := io.WriteString(w.cw, h.Comment); err != nil {
   165  			return err
   166  		}
   167  	}
   168  	end := w.cw.count
   169  
   170  	records := uint64(len(w.dir))
   171  	size := uint64(end - start)
   172  	offset := uint64(start)
   173  
   174  	if f := w.testHookCloseSizeOffset; f != nil {
   175  		f(size, offset)
   176  	}
   177  
   178  	// Emit the Zip64 EOCD records whenever any individual entry needed a Zip64
   179  	// extra field, even if the EOCD's own fields fit in 32 bits, matching
   180  	// Info-ZIP (but not libarchive). See APPNOTE 4.3.9.2: "when Zip64
   181  	// extensions are in use, the EOCD64 record must be present."
   182  	if usedZip64 || records >= uint16max || size >= uint32max || offset >= uint32max {
   183  		var buf [directory64EndLen + directory64LocLen]byte
   184  		b := writeBuf(buf[:])
   185  
   186  		// zip64 end of central directory record
   187  		b.uint32(directory64EndSignature)
   188  		b.uint64(directory64EndLen - 12) // length minus signature (uint32) and length fields (uint64)
   189  		b.uint16(zipVersion45)           // version made by
   190  		b.uint16(zipVersion45)           // version needed to extract
   191  		b.uint32(0)                      // number of this disk
   192  		b.uint32(0)                      // number of the disk with the start of the central directory
   193  		b.uint64(records)                // total number of entries in the central directory on this disk
   194  		b.uint64(records)                // total number of entries in the central directory
   195  		b.uint64(size)                   // size of the central directory
   196  		b.uint64(offset)                 // offset of start of central directory with respect to the starting disk number
   197  
   198  		// zip64 end of central directory locator
   199  		b.uint32(directory64LocSignature)
   200  		b.uint32(0)           // number of the disk with the start of the zip64 end of central directory
   201  		b.uint64(uint64(end)) // relative offset of the zip64 end of central directory record
   202  		b.uint32(1)           // total number of disks
   203  
   204  		if _, err := w.cw.Write(buf[:]); err != nil {
   205  			return err
   206  		}
   207  	}
   208  
   209  	// write end record
   210  	var buf [directoryEndLen]byte
   211  	b := writeBuf(buf[:])
   212  	b.uint32(uint32(directoryEndSignature))
   213  	b = b[4:]                                 // skip over disk number and first disk number (2x uint16)
   214  	b.uint16(uint16(min(uint16max, records))) // number of entries this disk
   215  	b.uint16(uint16(min(uint16max, records))) // number of entries total
   216  	b.uint32(uint32(min(uint32max, size)))    // size of directory
   217  	b.uint32(uint32(min(uint32max, offset)))  // start of directory
   218  	b.uint16(uint16(len(w.comment)))          // byte size of EOCD comment
   219  	if _, err := w.cw.Write(buf[:]); err != nil {
   220  		return err
   221  	}
   222  	if _, err := io.WriteString(w.cw, w.comment); err != nil {
   223  		return err
   224  	}
   225  
   226  	return w.cw.w.(*bufio.Writer).Flush()
   227  }
   228  
   229  // Create adds a file to the zip file using the provided name.
   230  // It returns a [Writer] to which the file contents should be written.
   231  // The file contents will be compressed using the [Deflate] method.
   232  // The name must be a relative path: it must not start with a drive
   233  // letter (e.g. C:) or leading slash, and only forward slashes are
   234  // allowed. To create a directory instead of a file, add a trailing
   235  // slash to the name. Duplicate names will not overwrite previous entries
   236  // and are appended to the zip file.
   237  // The file's contents must be written to the [io.Writer] before the next
   238  // call to [Writer.Create], [Writer.CreateHeader], or [Writer.Close].
   239  func (w *Writer) Create(name string) (io.Writer, error) {
   240  	header := &FileHeader{
   241  		Name:   name,
   242  		Method: Deflate,
   243  	}
   244  	return w.CreateHeader(header)
   245  }
   246  
   247  // detectUTF8 reports whether s is a valid UTF-8 string, and whether the string
   248  // must be considered UTF-8 encoding (i.e., not compatible with CP-437, ASCII,
   249  // or any other common encoding).
   250  func detectUTF8(s string) (valid, require bool) {
   251  	for i := 0; i < len(s); {
   252  		r, size := utf8.DecodeRuneInString(s[i:])
   253  		i += size
   254  		// Officially, ZIP uses CP-437, but many readers use the system's
   255  		// local character encoding. Most encoding are compatible with a large
   256  		// subset of CP-437, which itself is ASCII-like.
   257  		//
   258  		// Forbid 0x7e and 0x5c since EUC-KR and Shift-JIS replace those
   259  		// characters with localized currency and overline characters.
   260  		if r < 0x20 || r > 0x7d || r == 0x5c {
   261  			if !utf8.ValidRune(r) || (r == utf8.RuneError && size == 1) {
   262  				return false, false
   263  			}
   264  			require = true
   265  		}
   266  	}
   267  	return true, require
   268  }
   269  
   270  // prepare performs the bookkeeping operations required at the start of
   271  // CreateHeader and CreateRaw.
   272  func (w *Writer) prepare(fh *FileHeader) error {
   273  	if w.last != nil && !w.last.closed {
   274  		if err := w.last.close(); err != nil {
   275  			return err
   276  		}
   277  	}
   278  	if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh {
   279  		// See https://golang.org/issue/11144 confusion.
   280  		return errors.New("archive/zip: invalid duplicate FileHeader")
   281  	}
   282  	return nil
   283  }
   284  
   285  // CreateHeader adds a file to the zip archive using the provided [FileHeader]
   286  // for the file metadata. [Writer] takes ownership of fh and may mutate
   287  // its fields. The caller must not modify fh after calling [Writer.CreateHeader].
   288  //
   289  // This returns a [Writer] to which the file contents should be written.
   290  // The file's contents must be written to the io.Writer before the next
   291  // call to [Writer.Create], [Writer.CreateHeader], [Writer.CreateRaw], or [Writer.Close].
   292  func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
   293  	if err := w.prepare(fh); err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	// The ZIP format has a sad state of affairs regarding character encoding.
   298  	// Officially, the name and comment fields are supposed to be encoded
   299  	// in CP-437 (which is mostly compatible with ASCII), unless the UTF-8
   300  	// flag bit is set. However, there are several problems:
   301  	//
   302  	//	* Many ZIP readers still do not support UTF-8.
   303  	//	* If the UTF-8 flag is cleared, several readers simply interpret the
   304  	//	name and comment fields as whatever the local system encoding is.
   305  	//
   306  	// In order to avoid breaking readers without UTF-8 support,
   307  	// we avoid setting the UTF-8 flag if the strings are CP-437 compatible.
   308  	// However, if the strings require multibyte UTF-8 encoding and is a
   309  	// valid UTF-8 string, then we set the UTF-8 bit.
   310  	//
   311  	// For the case, where the user explicitly wants to specify the encoding
   312  	// as UTF-8, they will need to set the flag bit themselves.
   313  	utf8Valid1, utf8Require1 := detectUTF8(fh.Name)
   314  	utf8Valid2, utf8Require2 := detectUTF8(fh.Comment)
   315  	switch {
   316  	case fh.NonUTF8:
   317  		fh.Flags &^= 0x800
   318  	case (utf8Require1 || utf8Require2) && (utf8Valid1 && utf8Valid2):
   319  		fh.Flags |= 0x800
   320  	}
   321  
   322  	fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte
   323  	fh.ReaderVersion = zipVersion20
   324  
   325  	// If Modified is set, this takes precedence over MS-DOS timestamp fields.
   326  	if !fh.Modified.IsZero() {
   327  		// Contrary to the FileHeader.SetModTime method, we intentionally
   328  		// do not convert to UTC, because we assume the user intends to encode
   329  		// the date using the specified timezone. A user may want this control
   330  		// because many legacy ZIP readers interpret the timestamp according
   331  		// to the local timezone.
   332  		//
   333  		// The timezone is only non-UTC if a user directly sets the Modified
   334  		// field directly themselves. All other approaches sets UTC.
   335  		fh.ModifiedDate, fh.ModifiedTime = timeToMsDosTime(fh.Modified)
   336  
   337  		// Use "extended timestamp" format since this is what Info-ZIP uses.
   338  		// Nearly every major ZIP implementation uses a different format,
   339  		// but at least most seem to be able to understand the other formats.
   340  		//
   341  		// This format happens to be identical for both local and central header
   342  		// if modification time is the only timestamp being encoded.
   343  		var mbuf [9]byte // 2*SizeOf(uint16) + SizeOf(uint8) + SizeOf(uint32)
   344  		mt := uint32(fh.Modified.Unix())
   345  		eb := writeBuf(mbuf[:])
   346  		eb.uint16(extTimeExtraID)
   347  		eb.uint16(5)  // Size: SizeOf(uint8) + SizeOf(uint32)
   348  		eb.uint8(1)   // Flags: ModTime
   349  		eb.uint32(mt) // ModTime
   350  		fh.Extra = append(fh.Extra, mbuf[:]...)
   351  	}
   352  
   353  	var (
   354  		ow io.Writer
   355  		fw *fileWriter
   356  	)
   357  	h := &header{
   358  		FileHeader: fh,
   359  		offset:     uint64(w.cw.count),
   360  	}
   361  
   362  	if strings.HasSuffix(fh.Name, "/") {
   363  		// Set the compression method to Store to ensure data length is truly zero,
   364  		// which the writeHeader method always encodes for the size fields.
   365  		// This is necessary as most compression formats have non-zero lengths
   366  		// even when compressing an empty string.
   367  		fh.Method = Store
   368  		fh.Flags &^= 0x8 // we will not write a data descriptor
   369  
   370  		// Explicitly clear sizes as they have no meaning for directories.
   371  		fh.CompressedSize = 0
   372  		fh.CompressedSize64 = 0
   373  		fh.UncompressedSize = 0
   374  		fh.UncompressedSize64 = 0
   375  
   376  		ow = dirWriter{}
   377  	} else {
   378  		fh.Flags |= 0x8 // we will write a data descriptor
   379  
   380  		fw = &fileWriter{
   381  			zipw:      w.cw,
   382  			compCount: &countWriter{w: w.cw},
   383  			crc32:     crc32.NewIEEE(),
   384  		}
   385  		comp := w.compressor(fh.Method)
   386  		if comp == nil {
   387  			return nil, ErrAlgorithm
   388  		}
   389  		var err error
   390  		fw.comp, err = comp(fw.compCount)
   391  		if err != nil {
   392  			return nil, err
   393  		}
   394  		fw.rawCount = &countWriter{w: fw.comp}
   395  		fw.header = h
   396  		ow = fw
   397  	}
   398  	w.dir = append(w.dir, h)
   399  	if err := writeHeader(w.cw, h); err != nil {
   400  		return nil, err
   401  	}
   402  	// If we're creating a directory, fw is nil.
   403  	w.last = fw
   404  	return ow, nil
   405  }
   406  
   407  func writeHeader(w io.Writer, h *header) error {
   408  	const maxUint16 = 1<<16 - 1
   409  	if len(h.Name) > maxUint16 {
   410  		return errLongName
   411  	}
   412  	if len(h.Extra) > maxUint16 {
   413  		return errLongExtra
   414  	}
   415  
   416  	// The correct behavior of a streaming writer, implemented by Info-ZIP 3.0,
   417  	// would be to write 0xFFFFFFFF in the size fields and then write a Zip64
   418  	// extra field with the sizes at zero (to signal they are stored in a ZIP64
   419  	// data descriptor, in case the file is > 4GiB).
   420  	//
   421  	// We don't do that, and instead write zeroes directly in the size fields,
   422  	// because that wastes 28 bytes for every file smaller than 4GiB, and
   423  	// because it would change the encoding of nearly every zip file created by
   424  	// archive/zip. (No one should rely on it being stable, but still.)
   425  	//
   426  	// Anyway, the Local File Header is not that important, as the Central
   427  	// Directory is authoritative, and there we always write the correct sizes.
   428  	//
   429  	// If we do know the sizes, because [Writer.CreateRaw] is used and the data
   430  	// descriptor flag is not set, then we write them to the header. If either
   431  	// size reaches 4GiB, we write 0xFFFFFFFF placeholders and a Zip64 extra
   432  	// field with BOTH sizes, per the spec and matching Info-ZIP. Note this is
   433  	// different from the Central Directory Zip64 extra field logic, somehow.
   434  	//
   435  	// (One final interesting case that doesn't apply to us: if the input is
   436  	// streaming but the output is seekable, Info-ZIP always writes Zip64 extra
   437  	// fields, and then goes back and patches in the sizes, even for files < 4GiB.)
   438  
   439  	var zip64ExtraInfo []byte
   440  	readerVersion := h.ReaderVersion
   441  	noDataDescriptor := h.raw && !h.hasDataDescriptor()
   442  	if noDataDescriptor && (h.CompressedSize64 > uint32max || h.UncompressedSize64 > uint32max) {
   443  		readerVersion = max(readerVersion, zipVersion45)
   444  		zip64ExtraInfo = make([]byte, 20) // 2x uint16 + 2x uint64
   445  		b := writeBuf(zip64ExtraInfo)
   446  		b.uint16(zip64ExtraID)
   447  		b.uint16(16) // size of Zip64 extra field data
   448  		b.uint64(h.UncompressedSize64)
   449  		b.uint64(h.CompressedSize64)
   450  	}
   451  
   452  	var buf [fileHeaderLen]byte
   453  	b := writeBuf(buf[:])
   454  	b.uint32(uint32(fileHeaderSignature))
   455  	b.uint16(readerVersion)
   456  	b.uint16(h.Flags)
   457  	b.uint16(h.Method)
   458  	b.uint16(h.ModifiedTime)
   459  	b.uint16(h.ModifiedDate)
   460  	if noDataDescriptor {
   461  		b.uint32(h.CRC32)
   462  		if zip64ExtraInfo != nil {
   463  			b.uint32(uint32max)
   464  			b.uint32(uint32max)
   465  		} else {
   466  			b.uint32(uint32(h.CompressedSize64))
   467  			b.uint32(uint32(h.UncompressedSize64))
   468  		}
   469  	} else {
   470  		b.uint32(0) // crc32
   471  		b.uint32(0) // compressed size
   472  		b.uint32(0) // uncompressed size
   473  	}
   474  	b.uint16(uint16(len(h.Name)))
   475  	b.uint16(uint16(len(h.Extra) + len(zip64ExtraInfo)))
   476  	if _, err := w.Write(buf[:]); err != nil {
   477  		return err
   478  	}
   479  	if _, err := io.WriteString(w, h.Name); err != nil {
   480  		return err
   481  	}
   482  	if _, err := w.Write(h.Extra); err != nil {
   483  		return err
   484  	}
   485  	if _, err := w.Write(zip64ExtraInfo); err != nil {
   486  		return err
   487  	}
   488  	return nil
   489  }
   490  
   491  // CreateRaw adds a file to the zip archive using the provided [FileHeader] and
   492  // returns a [Writer] to which the file contents should be written. The file's
   493  // contents must be written to the io.Writer before the next call to [Writer.Create],
   494  // [Writer.CreateHeader], [Writer.CreateRaw], or [Writer.Close].
   495  //
   496  // In contrast to [Writer.CreateHeader], the bytes passed to Writer are not compressed.
   497  //
   498  // CreateRaw's argument is stored in w. If the argument is a pointer to the embedded
   499  // [FileHeader] in a [File] obtained from a [Reader] created from in-memory data,
   500  // then w will refer to all of that memory.
   501  func (w *Writer) CreateRaw(fh *FileHeader) (io.Writer, error) {
   502  	if err := w.prepare(fh); err != nil {
   503  		return nil, err
   504  	}
   505  
   506  	fh.CompressedSize = uint32(min(fh.CompressedSize64, uint32max))
   507  	fh.UncompressedSize = uint32(min(fh.UncompressedSize64, uint32max))
   508  
   509  	h := &header{
   510  		FileHeader: fh,
   511  		offset:     uint64(w.cw.count),
   512  		raw:        true,
   513  	}
   514  	w.dir = append(w.dir, h)
   515  	if err := writeHeader(w.cw, h); err != nil {
   516  		return nil, err
   517  	}
   518  
   519  	if strings.HasSuffix(fh.Name, "/") {
   520  		w.last = nil
   521  		return dirWriter{}, nil
   522  	}
   523  
   524  	fw := &fileWriter{
   525  		header: h,
   526  		zipw:   w.cw,
   527  	}
   528  	w.last = fw
   529  	return fw, nil
   530  }
   531  
   532  // Copy copies the file f (obtained from a [Reader]) into w. It copies the raw
   533  // form directly bypassing decompression, compression, and validation.
   534  func (w *Writer) Copy(f *File) error {
   535  	r, err := f.OpenRaw()
   536  	if err != nil {
   537  		return err
   538  	}
   539  	// Copy the FileHeader so w doesn't store a pointer to the data
   540  	// of f's entire archive. See #65499.
   541  	fh := f.FileHeader
   542  	fw, err := w.CreateRaw(&fh)
   543  	if err != nil {
   544  		return err
   545  	}
   546  	_, err = io.Copy(fw, r)
   547  	return err
   548  }
   549  
   550  // RegisterCompressor registers or overrides a custom compressor for a specific
   551  // method ID. If a compressor for a given method is not found, [Writer] will
   552  // default to looking up the compressor at the package level.
   553  func (w *Writer) RegisterCompressor(method uint16, comp Compressor) {
   554  	if w.compressors == nil {
   555  		w.compressors = make(map[uint16]Compressor)
   556  	}
   557  	w.compressors[method] = comp
   558  }
   559  
   560  // AddFS adds the files from fs.FS to the archive.
   561  // It walks the directory tree starting at the root of the filesystem
   562  // adding each file to the zip using deflate while maintaining the directory structure.
   563  func (w *Writer) AddFS(fsys fs.FS) error {
   564  	return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
   565  		if err != nil {
   566  			return err
   567  		}
   568  		if name == "." {
   569  			return nil
   570  		}
   571  		info, err := d.Info()
   572  		if err != nil {
   573  			return err
   574  		}
   575  		if !d.IsDir() && !info.Mode().IsRegular() {
   576  			return errors.New("zip: cannot add non-regular file")
   577  		}
   578  		h, err := FileInfoHeader(info)
   579  		if err != nil {
   580  			return err
   581  		}
   582  		h.Name = name
   583  		if d.IsDir() {
   584  			h.Name += "/"
   585  		}
   586  		h.Method = Deflate
   587  		fw, err := w.CreateHeader(h)
   588  		if err != nil {
   589  			return err
   590  		}
   591  		if d.IsDir() {
   592  			return nil
   593  		}
   594  		f, err := fsys.Open(name)
   595  		if err != nil {
   596  			return err
   597  		}
   598  		defer f.Close()
   599  		_, err = io.Copy(fw, f)
   600  		return err
   601  	})
   602  }
   603  
   604  func (w *Writer) compressor(method uint16) Compressor {
   605  	comp := w.compressors[method]
   606  	if comp == nil {
   607  		comp = compressor(method)
   608  	}
   609  	return comp
   610  }
   611  
   612  type dirWriter struct{}
   613  
   614  func (dirWriter) Write(b []byte) (int, error) {
   615  	if len(b) == 0 {
   616  		return 0, nil
   617  	}
   618  	return 0, errors.New("zip: write to directory")
   619  }
   620  
   621  type fileWriter struct {
   622  	*header
   623  	zipw      io.Writer
   624  	rawCount  *countWriter
   625  	comp      io.WriteCloser
   626  	compCount *countWriter
   627  	crc32     hash.Hash32
   628  	closed    bool
   629  }
   630  
   631  func (w *fileWriter) Write(p []byte) (int, error) {
   632  	if w.closed {
   633  		return 0, errors.New("zip: write to closed file")
   634  	}
   635  	if w.raw {
   636  		return w.zipw.Write(p)
   637  	}
   638  	w.crc32.Write(p)
   639  	return w.rawCount.Write(p)
   640  }
   641  
   642  func (w *fileWriter) close() error {
   643  	if w.closed {
   644  		return errors.New("zip: file closed twice")
   645  	}
   646  	w.closed = true
   647  	if w.raw {
   648  		return w.writeDataDescriptor()
   649  	}
   650  	if err := w.comp.Close(); err != nil {
   651  		return err
   652  	}
   653  
   654  	// update FileHeader
   655  	fh := w.header.FileHeader
   656  	fh.CRC32 = w.crc32.Sum32()
   657  	fh.CompressedSize64 = uint64(w.compCount.count)
   658  	fh.UncompressedSize64 = uint64(w.rawCount.count)
   659  
   660  	if w.CompressedSize64 > uint32max || w.UncompressedSize64 > uint32max {
   661  		fh.CompressedSize = uint32max
   662  		fh.UncompressedSize = uint32max
   663  		fh.ReaderVersion = zipVersion45 // requires 4.5 - File uses ZIP64 format extensions
   664  	} else {
   665  		fh.CompressedSize = uint32(fh.CompressedSize64)
   666  		fh.UncompressedSize = uint32(fh.UncompressedSize64)
   667  	}
   668  
   669  	return w.writeDataDescriptor()
   670  }
   671  
   672  func (w *fileWriter) writeDataDescriptor() error {
   673  	if !w.hasDataDescriptor() {
   674  		return nil
   675  	}
   676  	// See the comment in [writeHeader] about how and why we don't signal ZIP64
   677  	// mode in the local file header. If one of the sizes turns out to exceed
   678  	// 4GiB, we use the 64-bit sizes anyway, for lack of alternatives.
   679  	//
   680  	// See also https://bugs.openjdk.org/browse/JDK-7073588.
   681  	var buf []byte
   682  	if w.CompressedSize64 > uint32max || w.UncompressedSize64 > uint32max {
   683  		buf = make([]byte, dataDescriptor64Len)
   684  	} else {
   685  		buf = make([]byte, dataDescriptorLen)
   686  	}
   687  	b := writeBuf(buf)
   688  	b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X
   689  	b.uint32(w.CRC32)
   690  	if w.CompressedSize64 > uint32max || w.UncompressedSize64 > uint32max {
   691  		b.uint64(w.CompressedSize64)
   692  		b.uint64(w.UncompressedSize64)
   693  	} else {
   694  		b.uint32(w.CompressedSize)
   695  		b.uint32(w.UncompressedSize)
   696  	}
   697  	_, err := w.zipw.Write(buf)
   698  	return err
   699  }
   700  
   701  type countWriter struct {
   702  	w     io.Writer
   703  	count int64
   704  }
   705  
   706  func (w *countWriter) Write(p []byte) (int, error) {
   707  	n, err := w.w.Write(p)
   708  	w.count += int64(n)
   709  	return n, err
   710  }
   711  
   712  type nopCloser struct {
   713  	io.Writer
   714  }
   715  
   716  func (w nopCloser) Close() error {
   717  	return nil
   718  }
   719  
   720  type writeBuf []byte
   721  
   722  func (b *writeBuf) uint8(v uint8) {
   723  	(*b)[0] = v
   724  	*b = (*b)[1:]
   725  }
   726  
   727  func (b *writeBuf) uint16(v uint16) {
   728  	binary.LittleEndian.PutUint16(*b, v)
   729  	*b = (*b)[2:]
   730  }
   731  
   732  func (b *writeBuf) uint32(v uint32) {
   733  	binary.LittleEndian.PutUint32(*b, v)
   734  	*b = (*b)[4:]
   735  }
   736  
   737  func (b *writeBuf) uint64(v uint64) {
   738  	binary.LittleEndian.PutUint64(*b, v)
   739  	*b = (*b)[8:]
   740  }
   741  

View as plain text