// 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" "hash" "hash/crc32" "io" "os" ) // 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 Compressor is an io.WriteCloser that satisfies writes by compressing data written // to its wrapped io.Writer. type Compressor struct { Header w io.Writer level int compressor io.WriteCloser digest hash.Hash32 size uint32 closed bool buf [10]byte err os.Error } // NewWriter calls NewWriterLevel with the default compression level. func NewWriter(w io.Writer) (*Compressor, os.Error) { return NewWriterLevel(w, DefaultCompression) } // NewWriterLevel creates a new Compressor writing to the given writer. // Writes may be buffered and not flushed until Close. // Callers that wish to set the fields in Compressor.Header must // do so before the first call to Write or Close. // It is the caller's responsibility to call Close on the WriteCloser when done. // level is the compression level, which can be DefaultCompression, NoCompression, // or any integer value between BestSpeed and BestCompression (inclusive). func NewWriterLevel(w io.Writer, level int) (*Compressor, os.Error) { z := new(Compressor) z.OS = 255 // unknown z.w = w z.level = level z.digest = crc32.NewIEEE() return z, nil } // 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 *Compressor) writeBytes(b []byte) os.Error { if len(b) > 0xffff { return os.NewError("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 string (in ISO 8859-1 (Latin-1) format) to z.w. func (z *Compressor) writeString(s string) os.Error { // GZIP (RFC 1952) specifies that strings are NUL-terminated ISO 8859-1 (Latin-1). // TODO(nigeltao): Convert from UTF-8 to ISO 8859-1 (Latin-1). for _, v := range s { if v == 0 || v > 0x7f { return os.NewError("gzip.Write: non-ASCII header string") } } _, 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 } func (z *Compressor) Write(p []byte) (int, os.Error) { if z.err != nil { return 0, z.err } var n int // Write the GZIP header lazily. if z.compressor == nil { 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], z.Mtime) 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 } } 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 } // Calling Close does not close the wrapped io.Writer originally passed to NewWriter. func (z *Compressor) Close() os.Error { if z.err != nil { return z.err } if z.closed { return nil } z.closed = true if z.compressor == nil { 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 }