mirror of
https://github.com/golang/go
synced 2024-11-06 12:26:11 -07:00
3cea4131df
We now always write the "optional" streaming data descriptor signature, which turns out to be required for OS X. Also, handle reading the data descriptor with or without the signature, per the spec's recommendation. Fix data descriptor reading bugs found in the process. Fixes #3252 R=golang-dev, alex.brainman, nigeltao, rsc CC=golang-dev https://golang.org/cl/5787062
265 lines
6.0 KiB
Go
265 lines
6.0 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package zip
|
|
|
|
import (
|
|
"bufio"
|
|
"compress/flate"
|
|
"encoding/binary"
|
|
"errors"
|
|
"hash"
|
|
"hash/crc32"
|
|
"io"
|
|
)
|
|
|
|
// TODO(adg): support zip file comments
|
|
// TODO(adg): support specifying deflate level
|
|
|
|
// Writer implements a zip file writer.
|
|
type Writer struct {
|
|
cw *countWriter
|
|
dir []*header
|
|
last *fileWriter
|
|
closed bool
|
|
}
|
|
|
|
type header struct {
|
|
*FileHeader
|
|
offset uint32
|
|
}
|
|
|
|
// NewWriter returns a new Writer writing a zip file to w.
|
|
func NewWriter(w io.Writer) *Writer {
|
|
return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}}
|
|
}
|
|
|
|
// Close finishes writing the zip file by writing the central directory.
|
|
// It does not (and can not) close the underlying writer.
|
|
func (w *Writer) Close() error {
|
|
if w.last != nil && !w.last.closed {
|
|
if err := w.last.close(); err != nil {
|
|
return err
|
|
}
|
|
w.last = nil
|
|
}
|
|
if w.closed {
|
|
return errors.New("zip: writer closed twice")
|
|
}
|
|
w.closed = true
|
|
|
|
// write central directory
|
|
start := w.cw.count
|
|
for _, h := range w.dir {
|
|
var buf [directoryHeaderLen]byte
|
|
b := writeBuf(buf[:])
|
|
b.uint32(uint32(directoryHeaderSignature))
|
|
b.uint16(h.CreatorVersion)
|
|
b.uint16(h.ReaderVersion)
|
|
b.uint16(h.Flags)
|
|
b.uint16(h.Method)
|
|
b.uint16(h.ModifiedTime)
|
|
b.uint16(h.ModifiedDate)
|
|
b.uint32(h.CRC32)
|
|
b.uint32(h.CompressedSize)
|
|
b.uint32(h.UncompressedSize)
|
|
b.uint16(uint16(len(h.Name)))
|
|
b.uint16(uint16(len(h.Extra)))
|
|
b.uint16(uint16(len(h.Comment)))
|
|
b = b[4:] // skip disk number start and internal file attr (2x uint16)
|
|
b.uint32(h.ExternalAttrs)
|
|
b.uint32(h.offset)
|
|
if _, err := w.cw.Write(buf[:]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.WriteString(w.cw, h.Name); err != nil {
|
|
return err
|
|
}
|
|
if _, err := w.cw.Write(h.Extra); err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.WriteString(w.cw, h.Comment); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
end := w.cw.count
|
|
|
|
// write end record
|
|
var buf [directoryEndLen]byte
|
|
b := writeBuf(buf[:])
|
|
b.uint32(uint32(directoryEndSignature))
|
|
b = b[4:] // skip over disk number and first disk number (2x uint16)
|
|
b.uint16(uint16(len(w.dir))) // number of entries this disk
|
|
b.uint16(uint16(len(w.dir))) // number of entries total
|
|
b.uint32(uint32(end - start)) // size of directory
|
|
b.uint32(uint32(start)) // start of directory
|
|
// skipped size of comment (always zero)
|
|
if _, err := w.cw.Write(buf[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
return w.cw.w.(*bufio.Writer).Flush()
|
|
}
|
|
|
|
// Create adds a file to the zip file using the provided name.
|
|
// It returns a Writer to which the file contents should be written.
|
|
// The file's contents must be written to the io.Writer before the next
|
|
// call to Create, CreateHeader, or Close.
|
|
func (w *Writer) Create(name string) (io.Writer, error) {
|
|
header := &FileHeader{
|
|
Name: name,
|
|
Method: Deflate,
|
|
}
|
|
return w.CreateHeader(header)
|
|
}
|
|
|
|
// CreateHeader adds a file to the zip file using the provided FileHeader
|
|
// for the file metadata.
|
|
// It returns a Writer to which the file contents should be written.
|
|
// The file's contents must be written to the io.Writer before the next
|
|
// call to Create, CreateHeader, or Close.
|
|
func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
|
|
if w.last != nil && !w.last.closed {
|
|
if err := w.last.close(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
fh.Flags |= 0x8 // we will write a data descriptor
|
|
fh.CreatorVersion = fh.CreatorVersion&0xff00 | 0x14
|
|
fh.ReaderVersion = 0x14
|
|
|
|
fw := &fileWriter{
|
|
zipw: w.cw,
|
|
compCount: &countWriter{w: w.cw},
|
|
crc32: crc32.NewIEEE(),
|
|
}
|
|
switch fh.Method {
|
|
case Store:
|
|
fw.comp = nopCloser{fw.compCount}
|
|
case Deflate:
|
|
var err error
|
|
fw.comp, err = flate.NewWriter(fw.compCount, 5)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, ErrAlgorithm
|
|
}
|
|
fw.rawCount = &countWriter{w: fw.comp}
|
|
|
|
h := &header{
|
|
FileHeader: fh,
|
|
offset: uint32(w.cw.count),
|
|
}
|
|
w.dir = append(w.dir, h)
|
|
fw.header = h
|
|
|
|
if err := writeHeader(w.cw, fh); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.last = fw
|
|
return fw, nil
|
|
}
|
|
|
|
func writeHeader(w io.Writer, h *FileHeader) error {
|
|
var buf [fileHeaderLen]byte
|
|
b := writeBuf(buf[:])
|
|
b.uint32(uint32(fileHeaderSignature))
|
|
b.uint16(h.ReaderVersion)
|
|
b.uint16(h.Flags)
|
|
b.uint16(h.Method)
|
|
b.uint16(h.ModifiedTime)
|
|
b.uint16(h.ModifiedDate)
|
|
b.uint32(h.CRC32)
|
|
b.uint32(h.CompressedSize)
|
|
b.uint32(h.UncompressedSize)
|
|
b.uint16(uint16(len(h.Name)))
|
|
b.uint16(uint16(len(h.Extra)))
|
|
if _, err := w.Write(buf[:]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.WriteString(w, h.Name); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(h.Extra)
|
|
return err
|
|
}
|
|
|
|
type fileWriter struct {
|
|
*header
|
|
zipw io.Writer
|
|
rawCount *countWriter
|
|
comp io.WriteCloser
|
|
compCount *countWriter
|
|
crc32 hash.Hash32
|
|
closed bool
|
|
}
|
|
|
|
func (w *fileWriter) Write(p []byte) (int, error) {
|
|
if w.closed {
|
|
return 0, errors.New("zip: write to closed file")
|
|
}
|
|
w.crc32.Write(p)
|
|
return w.rawCount.Write(p)
|
|
}
|
|
|
|
func (w *fileWriter) close() error {
|
|
if w.closed {
|
|
return errors.New("zip: file closed twice")
|
|
}
|
|
w.closed = true
|
|
if err := w.comp.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// update FileHeader
|
|
fh := w.header.FileHeader
|
|
fh.CRC32 = w.crc32.Sum32()
|
|
fh.CompressedSize = uint32(w.compCount.count)
|
|
fh.UncompressedSize = uint32(w.rawCount.count)
|
|
|
|
// write data descriptor
|
|
var buf [dataDescriptorLen]byte
|
|
b := writeBuf(buf[:])
|
|
b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X
|
|
b.uint32(fh.CRC32)
|
|
b.uint32(fh.CompressedSize)
|
|
b.uint32(fh.UncompressedSize)
|
|
_, err := w.zipw.Write(buf[:])
|
|
return err
|
|
}
|
|
|
|
type countWriter struct {
|
|
w io.Writer
|
|
count int64
|
|
}
|
|
|
|
func (w *countWriter) Write(p []byte) (int, error) {
|
|
n, err := w.w.Write(p)
|
|
w.count += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
type nopCloser struct {
|
|
io.Writer
|
|
}
|
|
|
|
func (w nopCloser) Close() error {
|
|
return nil
|
|
}
|
|
|
|
type writeBuf []byte
|
|
|
|
func (b *writeBuf) uint16(v uint16) {
|
|
binary.LittleEndian.PutUint16(*b, v)
|
|
*b = (*b)[2:]
|
|
}
|
|
|
|
func (b *writeBuf) uint32(v uint32) {
|
|
binary.LittleEndian.PutUint32(*b, v)
|
|
*b = (*b)[4:]
|
|
}
|