| // 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 |
| } |