]> Cypherpunks repositories - gostls13.git/commitdiff
gzip deflater (i.e., writer).
authorNigel Tao <nigeltao@golang.org>
Fri, 29 Jan 2010 00:00:05 +0000 (11:00 +1100)
committerNigel Tao <nigeltao@golang.org>
Fri, 29 Jan 2010 00:00:05 +0000 (11:00 +1100)
Also, the unused Inflater.eof field was removed.
It has been unused since revision aaa0b24538.
"introduce os.EOF and io.ErrUnexpectedEOF. remove io.ErrEOF."
http://code.google.com/p/go/source/diff?spec=svnaaa0b24538ed1e3e54cbbfdd030a3c35785e74c5&r=aaa0b24538ed1e3e54cbbfdd030a3c35785e74c5&format=side&path=/src/pkg/compress/gzip/gunzip.go

R=rsc
CC=golang-dev
https://golang.org/cl/194122

src/pkg/compress/gzip/Makefile
src/pkg/compress/gzip/gunzip.go
src/pkg/compress/gzip/gunzip_test.go
src/pkg/compress/gzip/gzip.go [new file with mode: 0644]
src/pkg/compress/gzip/gzip_test.go [new file with mode: 0644]
src/pkg/compress/zlib/writer.go

index ca26333f392e9ac99546b573c41024840b536245..bb4705a8f496109c2c975dbfa4b2e8c79af9233d 100644 (file)
@@ -7,5 +7,6 @@ include ../../../Make.$(GOARCH)
 TARG=compress/gzip
 GOFILES=\
        gunzip.go\
+       gzip.go\
 
 include ../../../Make.pkg
index b2a08830c9e4a12d252368ebebe2b18ba0b88e3b..6a1b9fac37629ef6b24541a4ba256f30bc6c0237 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// The gzip package implements reading (and eventually writing) of
+// The gzip package implements reading and writing of
 // gzip format compressed files, as specified in RFC 1952.
 package gzip
 
@@ -15,6 +15,9 @@ import (
        "os"
 )
 
+// BUG(nigeltao): Comments and Names don't properly map UTF-8 character codes outside of
+// the 0x00-0x7f range to ISO 8859-1 (Latin-1).
+
 const (
        gzipID1     = 0x1f
        gzipID2     = 0x8b
@@ -36,10 +39,18 @@ func makeReader(r io.Reader) flate.Reader {
 var HeaderError os.Error = os.ErrorString("invalid gzip header")
 var ChecksumError os.Error = os.ErrorString("gzip checksum error")
 
+// The gzip file stores a header giving metadata about the compressed file.
+// That header is exposed as the fields of the Deflater and Inflater structs.
+type Header struct {
+       Comment string // comment
+       Extra   []byte // "extra data"
+       Mtime   uint32 // modification time (seconds since January 1, 1970)
+       Name    string // file name
+       OS      byte   // operating system type
+}
+
 // An Inflater is an io.Reader that can be read to retrieve
 // uncompressed data from a gzip-format compressed file.
-// The gzip file stores a header giving metadata about the compressed file.
-// That header is exposed as the fields of the Inflater struct.
 //
 // In general, a gzip file can be a concatenation of gzip files,
 // each with its own header.  Reads from the Inflater
@@ -53,12 +64,7 @@ var ChecksumError os.Error = os.ErrorString("gzip checksum error")
 // returned by Read as tentative until they receive the successful
 // (zero length, nil error) Read marking the end of the data.
 type Inflater struct {
-       Comment string // comment
-       Extra   []byte // "extra data"
-       Mtime   uint32 // modification time (seconds since January 1, 1970)
-       Name    string // file name
-       OS      byte   // operating system type
-
+       Header
        r        flate.Reader
        inflater io.ReadCloser
        digest   hash.Hash32
@@ -66,7 +72,6 @@ type Inflater struct {
        flg      byte
        buf      [512]byte
        err      os.Error
-       eof      bool
 }
 
 // NewInflater creates a new Inflater reading the given reader.
@@ -99,6 +104,8 @@ func (z *Inflater) readString() (string, os.Error) {
                        return "", err
                }
                if z.buf[i] == 0 {
+                       // GZIP (RFC 1952) specifies that strings are null-terminated ISO 8859-1 (Latin-1).
+                       // TODO(nigeltao): Convert from ISO 8859-1 (Latin-1) to UTF-8.
                        return string(z.buf[0:i]), nil
                }
        }
@@ -106,7 +113,7 @@ func (z *Inflater) readString() (string, os.Error) {
 }
 
 func (z *Inflater) read2() (uint32, os.Error) {
-       _, err := z.r.Read(z.buf[0:2])
+       _, err := io.ReadFull(z.r, z.buf[0:2])
        if err != nil {
                return 0, err
        }
@@ -183,7 +190,7 @@ func (z *Inflater) Read(p []byte) (n int, err os.Error) {
        if z.err != nil {
                return 0, z.err
        }
-       if z.eof || len(p) == 0 {
+       if len(p) == 0 {
                return 0, nil
        }
 
index 3930985e37a67ba02130f6612de0cc45d72d7b0b..a70464b3ee67e4ac312ba81ced28b43c9d4ad1fd 100644 (file)
@@ -11,7 +11,7 @@ import (
        "testing"
 )
 
-type gzipTest struct {
+type gunzipTest struct {
        name string
        desc string
        raw  string
@@ -19,8 +19,8 @@ type gzipTest struct {
        err  os.Error
 }
 
-var gzipTests = []gzipTest{
-       gzipTest{ // has 1 empty fixed-huffman block
+var gunzipTests = []gunzipTest{
+       gunzipTest{ // has 1 empty fixed-huffman block
                "empty.txt",
                "empty.txt",
                "",
@@ -32,7 +32,7 @@ var gzipTests = []gzipTest{
                },
                nil,
        },
-       gzipTest{ // has 1 non-empty fixed huffman block
+       gunzipTest{ // has 1 non-empty fixed huffman block
                "hello.txt",
                "hello.txt",
                "hello world\n",
@@ -46,7 +46,7 @@ var gzipTests = []gzipTest{
                },
                nil,
        },
-       gzipTest{ // concatenation
+       gunzipTest{ // concatenation
                "hello.txt",
                "hello.txt x2",
                "hello world\n" +
@@ -67,7 +67,7 @@ var gzipTests = []gzipTest{
                },
                nil,
        },
-       gzipTest{ // has a fixed huffman block with some length-distance pairs
+       gunzipTest{ // has a fixed huffman block with some length-distance pairs
                "shesells.txt",
                "shesells.txt",
                "she sells seashells by the seashore\n",
@@ -83,7 +83,7 @@ var gzipTests = []gzipTest{
                },
                nil,
        },
-       gzipTest{ // has dynamic huffman blocks
+       gunzipTest{ // has dynamic huffman blocks
                "gettysburg",
                "gettysburg",
                "  Four score and seven years ago our fathers brought forth on\n" +
@@ -221,7 +221,7 @@ var gzipTests = []gzipTest{
                },
                nil,
        },
-       gzipTest{ // has 1 non-empty fixed huffman block then garbage
+       gunzipTest{ // has 1 non-empty fixed huffman block then garbage
                "hello.txt",
                "hello.txt + garbage",
                "hello world\n",
@@ -235,7 +235,7 @@ var gzipTests = []gzipTest{
                },
                HeaderError,
        },
-       gzipTest{ // has 1 non-empty fixed huffman block not enough header
+       gunzipTest{ // has 1 non-empty fixed huffman block not enough header
                "hello.txt",
                "hello.txt + garbage",
                "hello world\n",
@@ -249,7 +249,7 @@ var gzipTests = []gzipTest{
                },
                io.ErrUnexpectedEOF,
        },
-       gzipTest{ // has 1 non-empty fixed huffman block but corrupt checksum
+       gunzipTest{ // has 1 non-empty fixed huffman block but corrupt checksum
                "hello.txt",
                "hello.txt + corrupt checksum",
                "hello world\n",
@@ -263,7 +263,7 @@ var gzipTests = []gzipTest{
                },
                ChecksumError,
        },
-       gzipTest{ // has 1 non-empty fixed huffman block but corrupt size
+       gunzipTest{ // has 1 non-empty fixed huffman block but corrupt size
                "hello.txt",
                "hello.txt + corrupt size",
                "hello world\n",
@@ -281,7 +281,7 @@ var gzipTests = []gzipTest{
 
 func TestInflater(t *testing.T) {
        b := new(bytes.Buffer)
-       for _, tt := range gzipTests {
+       for _, tt := range gunzipTests {
                in := bytes.NewBuffer(tt.gzip)
                gzip, err := NewInflater(in)
                if err != nil {
diff --git a/src/pkg/compress/gzip/gzip.go b/src/pkg/compress/gzip/gzip.go
new file mode 100644 (file)
index 0000000..c17e6e7
--- /dev/null
@@ -0,0 +1,187 @@
+// 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 Deflater is an io.WriteCloser that satisfies writes by compressing data written
+// to its wrapped io.Writer.
+type Deflater struct {
+       Header
+       w        io.Writer
+       level    int
+       deflater io.WriteCloser
+       digest   hash.Hash32
+       size     uint32
+       closed   bool
+       buf      [10]byte
+       err      os.Error
+}
+
+// NewDeflater calls NewDeflaterLevel with the default compression level.
+func NewDeflater(w io.Writer) (*Deflater, os.Error) {
+       return NewDeflaterLevel(w, DefaultCompression)
+}
+
+// NewDeflaterLevel creates a new Deflater writing to the given writer.
+// Writes may be buffered and not flushed until Close.
+// Callers that wish to set the fields in Deflater.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 NewDeflaterLevel(w io.Writer, level int) (*Deflater, os.Error) {
+       z := new(Deflater)
+       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 *Deflater) 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 *Deflater) writeString(s string) os.Error {
+       // GZIP (RFC 1952) specifies that strings are null-terminated ISO 8859-1 (Latin-1).
+       // TODO(nigeltao): Convert from UTF-8 to ISO 8859-1 (Latin-1).
+       for _, v := range s {
+               if v > 0x7f {
+                       return os.NewError("gzip.Write: Comment/Name character code was outside the 0x00-0x7f range")
+               }
+       }
+       _, 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 *Deflater) Write(p []byte) (int, os.Error) {
+       if z.err != nil {
+               return 0, z.err
+       }
+       var n int
+       // Write the GZIP header lazily.
+       if z.deflater == 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.deflater = flate.NewDeflater(z.w, z.level)
+       }
+       z.size += uint32(len(p))
+       z.digest.Write(p)
+       n, z.err = z.deflater.Write(p)
+       return n, z.err
+}
+
+// Calling Close does not close the wrapped io.Writer originally passed to NewDeflater.
+func (z *Deflater) Close() os.Error {
+       if z.err != nil {
+               return z.err
+       }
+       if z.closed {
+               return nil
+       }
+       z.closed = true
+       if z.deflater == nil {
+               z.Write(nil)
+               if z.err != nil {
+                       return z.err
+               }
+       }
+       z.err = z.deflater.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
+}
diff --git a/src/pkg/compress/gzip/gzip_test.go b/src/pkg/compress/gzip/gzip_test.go
new file mode 100644 (file)
index 0000000..292e2b6
--- /dev/null
@@ -0,0 +1,65 @@
+// 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 (
+       "io"
+       "io/ioutil"
+       "strings"
+       "testing"
+)
+
+// Tests that gzipping and then gunzipping is the identity function.
+func TestWriter(t *testing.T) {
+       // Set up the Pipe to do the gzip and gunzip.
+       piper, pipew := io.Pipe()
+       defer piper.Close()
+       go func() {
+               defer pipew.Close()
+               deflater, err := NewDeflater(pipew)
+               if err != nil {
+                       t.Errorf("%v", err)
+                       return
+               }
+               defer deflater.Close()
+               deflater.Comment = "comment"
+               deflater.Extra = strings.Bytes("extra")
+               deflater.Mtime = 1e8
+               deflater.Name = "name"
+               _, err = deflater.Write(strings.Bytes("payload"))
+               if err != nil {
+                       t.Errorf("%v", err)
+                       return
+               }
+       }()
+       inflater, err := NewInflater(piper)
+       if err != nil {
+               t.Errorf("%v", err)
+               return
+       }
+       defer inflater.Close()
+
+       // Read and compare to the original input.
+       b, err := ioutil.ReadAll(inflater)
+       if err != nil {
+               t.Errorf(": %v", err)
+               return
+       }
+       if string(b) != "payload" {
+               t.Fatalf("payload is %q, want %q", string(b), "payload")
+       }
+       if inflater.Comment != "comment" {
+               t.Fatalf("comment is %q, want %q", inflater.Comment, "comment")
+       }
+       if string(inflater.Extra) != "extra" {
+               t.Fatalf("extra is %q, want %q", inflater.Extra, "extra")
+       }
+       if inflater.Mtime != 1e8 {
+               t.Fatalf("mtime is %d, want %d", inflater.Mtime, uint32(1e8))
+       }
+       if inflater.Name != "name" {
+               t.Fatalf("name is %q, want %q", inflater.Name, "name")
+       }
+}
index 0441b04638772f38b3d1ef23104c074bfa0fca58..53da3990a6b7911b269b6c90b3724c04672816f3 100644 (file)
@@ -34,7 +34,7 @@ func NewDeflater(w io.Writer) (io.WriteCloser, os.Error) {
        return NewDeflaterLevel(w, DefaultCompression)
 }
 
-// NewDeflater creates a new io.WriteCloser that satisfies writes by compressing data written to w.
+// NewDeflaterLevel creates a new io.WriteCloser that satisfies writes by compressing data written to w.
 // 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).