// Copyright 2010 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 gzip import ( "compress/flate" "errors" "fmt" "hash" "hash/crc32" "io" ) // These constants are copied from the flate package, so that code that imports // "compress/gzip" does not also have to import "compress/flate". const ( NoCompression = flate.NoCompression BestSpeed = flate.BestSpeed BestCompression = flate.BestCompression DefaultCompression = flate.DefaultCompression ) // A Writer is an io.WriteCloser that satisfies writes by compressing data written // to its wrapped io.Writer. type Writer struct { Header w io.Writer level int wroteHeader bool compressor *flate.Writer digest hash.Hash32 size uint32 closed bool buf [10]byte err error } // NewWriter creates a new Writer that satisfies writes by compressing data // written to w. // // It is the caller's responsibility to call Close on the WriteCloser when done. // Writes may be buffered and not flushed until Close. // // Callers that wish to set the fields in Writer.Header must do so before // the first call to Write or Close. The Comment and Name header fields are // UTF-8 strings in Go, but the underlying format requires NUL-terminated ISO // 8859-1 (Latin-1). NUL or non-Latin-1 runes in those strings will lead to an // error on Write. func NewWriter(w io.Writer) *Writer { z, _ := NewWriterLevel(w, DefaultCompression) return z } // NewWriterLevel is like NewWriter but specifies the compression level instead // of assuming DefaultCompression. // // The compression level can be DefaultCompression, NoCompression, or any // integer value between BestSpeed and BestCompression inclusive. The error // returned will be nil if the level is valid. func NewWriterLevel(w io.Writer, level int) (*Writer, error) { if level < DefaultCompression || level > BestCompression { return nil, fmt.Errorf("gzip: invalid compression level: %d", level) } z := new(Writer) z.init(w, level) return z, nil } func (z *Writer) init(w io.Writer, level int) { digest := z.digest if digest != nil { digest.Reset() } else { digest = crc32.NewIEEE() } compressor := z.compressor if compressor != nil { compressor.Reset(w) } *z = Writer{ Header: Header{ OS: 255, // unknown }, w: w, level: level, digest: digest, compressor: compressor, } } // Reset discards the Writer z's state and makes it equivalent to the // result of its original state from NewWriter or NewWriterLevel, but // writing to w instead. This permits reusing a Writer rather than // allocating a new one. func (z *Writer) Reset(w io.Writer) { z.init(w, z.level) } // GZIP (RFC 1952) is little-endian, unlike ZLIB (RFC 1950). func put2(p []byte, v uint16) { p[0] = uint8(v >> 0) p[1] = uint8(v >> 8) } func put4(p []byte, v uint32) { p[0] = uint8(v >> 0) p[1] = uint8(v >> 8) p[2] = uint8(v >> 16) p[3] = uint8(v >> 24) } // writeBytes writes a length-prefixed byte slice to z.w. func (z *Writer) writeBytes(b []byte) error { if len(b) > 0xffff { return errors.New("gzip.Write: Extra data is too large") } put2(z.buf[0:2], uint16(len(b))) _, err := z.w.Write(z.buf[0:2]) if err != nil { return err } _, err = z.w.Write(b) return err } // writeString writes a UTF-8 string s in GZIP's format to z.w. // GZIP (RFC 1952) specifies that strings are NUL-terminated ISO 8859-1 (Latin-1). func (z *Writer) writeString(s string) (err error) { // GZIP stores Latin-1 strings; error if non-Latin-1; convert if non-ASCII. needconv := false for _, v := range s { if v == 0 || v > 0xff { return errors.New("gzip.Write: non-Latin-1 header string") } if v > 0x7f { needconv = true } } if needconv { b := make([]byte, 0, len(s)) for _, v := range s { b = append(b, byte(v)) } _, err = z.w.Write(b) } else { _, err = io.WriteString(z.w, s) } if err != nil { return err } // GZIP strings are NUL-terminated. z.buf[0] = 0 _, err = z.w.Write(z.buf[0:1]) return err } // Write writes a compressed form of p to the underlying io.Writer. The // compressed bytes are not necessarily flushed until the Writer is closed. func (z *Writer) Write(p []byte) (int, error) { if z.err != nil { return 0, z.err } var n int // Write the GZIP header lazily. if !z.wroteHeader { z.wroteHeader = true z.buf[0] = gzipID1 z.buf[1] = gzipID2 z.buf[2] = gzipDeflate z.buf[3] = 0 if z.Extra != nil { z.buf[3] |= 0x04 } if z.Name != "" { z.buf[3] |= 0x08 } if z.Comment != "" { z.buf[3] |= 0x10 } put4(z.buf[4:8], uint32(z.ModTime.Unix())) if z.level == BestCompression { z.buf[8] = 2 } else if z.level == BestSpeed { z.buf[8] = 4 } else { z.buf[8] = 0 } z.buf[9] = z.OS n, z.err = z.w.Write(z.buf[0:10]) if z.err != nil { return n, z.err } if z.Extra != nil { z.err = z.writeBytes(z.Extra) if z.err != nil { return n, z.err } } if z.Name != "" { z.err = z.writeString(z.Name) if z.err != nil { return n, z.err } } if z.Comment != "" { z.err = z.writeString(z.Comment) if z.err != nil { return n, z.err } } if z.compressor == nil { z.compressor, _ = flate.NewWriter(z.w, z.level) } } z.size += uint32(len(p)) z.digest.Write(p) n, z.err = z.compressor.Write(p) return n, z.err } // Flush flushes any pending compressed data to the underlying writer. // // It is useful mainly in compressed network protocols, to ensure that // a remote reader has enough data to reconstruct a packet. Flush does // not return until the data has been written. If the underlying // writer returns an error, Flush returns that error. // // In the terminology of the zlib library, Flush is equivalent to Z_SYNC_FLUSH. func (z *Writer) Flush() error { if z.err != nil { return z.err } if z.closed { return nil } if !z.wroteHeader { z.Write(nil) if z.err != nil { return z.err } } z.err = z.compressor.Flush() return z.err } // Close closes the Writer. It does not close the underlying io.Writer. func (z *Writer) Close() error { if z.err != nil { return z.err } if z.closed { return nil } z.closed = true if !z.wroteHeader { z.Write(nil) if z.err != nil { return z.err } } z.err = z.compressor.Close() if z.err != nil { return z.err } put4(z.buf[0:4], z.digest.Sum32()) put4(z.buf[4:8], z.size) _, z.err = z.w.Write(z.buf[0:8]) return z.err }