| // 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" |
| "hash" |
| "hash/crc32" |
| "io" |
| "os" |
| ) |
| |
| // TODO(adg): support zip file comments |
| // TODO(adg): support specifying deflate level |
| |
| // Writer implements a zip file writer. |
| type Writer struct { |
| *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{countWriter: &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() (err os.Error) { |
| if w.last != nil && !w.last.closed { |
| if err = w.last.close(); err != nil { |
| return |
| } |
| w.last = nil |
| } |
| if w.closed { |
| return os.NewError("zip: writer closed twice") |
| } |
| w.closed = true |
| |
| defer recoverError(&err) |
| |
| // write central directory |
| start := w.count |
| for _, h := range w.dir { |
| write(w, uint32(directoryHeaderSignature)) |
| write(w, h.CreatorVersion) |
| write(w, h.ReaderVersion) |
| write(w, h.Flags) |
| write(w, h.Method) |
| write(w, h.ModifiedTime) |
| write(w, h.ModifiedDate) |
| write(w, h.CRC32) |
| write(w, h.CompressedSize) |
| write(w, h.UncompressedSize) |
| write(w, uint16(len(h.Name))) |
| write(w, uint16(len(h.Extra))) |
| write(w, uint16(len(h.Comment))) |
| write(w, uint16(0)) // disk number start |
| write(w, uint16(0)) // internal file attributes |
| write(w, uint32(0)) // external file attributes |
| write(w, h.offset) |
| writeBytes(w, []byte(h.Name)) |
| writeBytes(w, h.Extra) |
| writeBytes(w, []byte(h.Comment)) |
| } |
| end := w.count |
| |
| // write end record |
| write(w, uint32(directoryEndSignature)) |
| write(w, uint16(0)) // disk number |
| write(w, uint16(0)) // disk number where directory starts |
| write(w, uint16(len(w.dir))) // number of entries this disk |
| write(w, uint16(len(w.dir))) // number of entries total |
| write(w, uint32(end-start)) // size of directory |
| write(w, uint32(start)) // start of directory |
| write(w, uint16(0)) // size of comment |
| |
| return w.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, os.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, os.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 = 0x14 |
| fh.ReaderVersion = 0x14 |
| |
| fw := &fileWriter{ |
| zipw: w, |
| compCount: &countWriter{w: w}, |
| crc32: crc32.NewIEEE(), |
| } |
| switch fh.Method { |
| case Store: |
| fw.comp = nopCloser{fw.compCount} |
| case Deflate: |
| fw.comp = flate.NewWriter(fw.compCount, 5) |
| default: |
| return nil, UnsupportedMethod |
| } |
| fw.rawCount = &countWriter{w: fw.comp} |
| |
| h := &header{ |
| FileHeader: fh, |
| offset: uint32(w.count), |
| } |
| w.dir = append(w.dir, h) |
| fw.header = h |
| |
| if err := writeHeader(w, fh); err != nil { |
| return nil, err |
| } |
| |
| w.last = fw |
| return fw, nil |
| } |
| |
| func writeHeader(w io.Writer, h *FileHeader) (err os.Error) { |
| defer recoverError(&err) |
| write(w, uint32(fileHeaderSignature)) |
| write(w, h.ReaderVersion) |
| write(w, h.Flags) |
| write(w, h.Method) |
| write(w, h.ModifiedTime) |
| write(w, h.ModifiedDate) |
| write(w, h.CRC32) |
| write(w, h.CompressedSize) |
| write(w, h.UncompressedSize) |
| write(w, uint16(len(h.Name))) |
| write(w, uint16(len(h.Extra))) |
| writeBytes(w, []byte(h.Name)) |
| writeBytes(w, h.Extra) |
| return nil |
| } |
| |
| 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, os.Error) { |
| if w.closed { |
| return 0, os.NewError("zip: write to closed file") |
| } |
| w.crc32.Write(p) |
| return w.rawCount.Write(p) |
| } |
| |
| func (w *fileWriter) close() (err os.Error) { |
| if w.closed { |
| return os.NewError("zip: file closed twice") |
| } |
| w.closed = true |
| if err = w.comp.Close(); err != nil { |
| return |
| } |
| |
| // 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 |
| defer recoverError(&err) |
| write(w.zipw, fh.CRC32) |
| write(w.zipw, fh.CompressedSize) |
| write(w.zipw, fh.UncompressedSize) |
| |
| return nil |
| } |
| |
| type countWriter struct { |
| w io.Writer |
| count int64 |
| } |
| |
| func (w *countWriter) Write(p []byte) (int, os.Error) { |
| n, err := w.w.Write(p) |
| w.count += int64(n) |
| return n, err |
| } |
| |
| type nopCloser struct { |
| io.Writer |
| } |
| |
| func (w nopCloser) Close() os.Error { |
| return nil |
| } |
| |
| func write(w io.Writer, data interface{}) { |
| if err := binary.Write(w, binary.LittleEndian, data); err != nil { |
| panic(err) |
| } |
| } |
| |
| func writeBytes(w io.Writer, b []byte) { |
| n, err := w.Write(b) |
| if err != nil { |
| panic(err) |
| } |
| if n != len(b) { |
| panic(io.ErrShortWrite) |
| } |
| } |