// 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/crc32" "io" "time" ) // 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 HuffmanOnly = flate.HuffmanOnly ) // A Writer is an io.WriteCloser. // Writes to a Writer are compressed and written to w. type Writer struct { Header // written at first call to Write, Flush, or Close w io.Writer level int wroteHeader bool compressor *flate.Writer digest uint32 // CRC-32, IEEE polynomial (section 8) size uint32 // Uncompressed size (section 2.3.1) closed bool buf [10]byte err error } // NewWriter returns a new [Writer]. // Writes to the returned writer are compressed and written to w. // // It is the caller's responsibility to call Close on the [Writer] 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, Flush, or Close. 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], [HuffmanOnly] // 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 < HuffmanOnly || 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) { compressor := z.compressor if compressor != nil { compressor.Reset(w) } *z = Writer{ Header: Header{ OS: 255, // unknown }, w: w, level: level, 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) } // 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") } le.PutUint16(z.buf[:2], uint16(len(b))) _, err := z.w.Write(z.buf[: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[: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 = [10]byte{0: gzipID1, 1: gzipID2, 2: gzipDeflate} if z.Extra != nil { z.buf[3] |= 0x04 } if z.Name != "" { z.buf[3] |= 0x08 } if z.Comment != "" { z.buf[3] |= 0x10 } if z.ModTime.After(time.Unix(0, 0)) { // Section 2.3.1, the zero value for MTIME means that the // modified time is not set. le.PutUint32(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 } z.buf[9] = z.OS _, z.err = z.w.Write(z.buf[:10]) if z.err != nil { return 0, z.err } if z.Extra != nil { z.err = z.writeBytes(z.Extra) if z.err != nil { return 0, z.err } } if z.Name != "" { z.err = z.writeString(z.Name) if z.err != nil { return 0, z.err } } if z.Comment != "" { z.err = z.writeString(z.Comment) if z.err != nil { return 0, z.err } } if z.compressor == nil { z.compressor, _ = flate.NewWriter(z.w, z.level) } } z.size += uint32(len(p)) z.digest = crc32.Update(z.digest, crc32.IEEETable, 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] by flushing any unwritten data to the underlying // [io.Writer] and writing the GZIP footer. // 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 } le.PutUint32(z.buf[:4], z.digest) le.PutUint32(z.buf[4:8], z.size) _, z.err = z.w.Write(z.buf[:8]) return z.err }