Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade zlib to upstream #971

Merged
merged 1 commit into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions zlib/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ package zlib
import (
"bufio"
"compress/zlib"
"encoding/binary"
"hash"
"hash/adler32"
"io"

"github.com/klauspost/compress/flate"
)

const zlibDeflate = 8
const (
zlibDeflate = 8
zlibMaxWindow = 7
)

var (
// ErrChecksum is returned when reading ZLIB data that has an invalid checksum.
Expand All @@ -52,7 +56,7 @@ type reader struct {
scratch [4]byte
}

// Resetter resets a ReadCloser returned by NewReader or NewReaderDict to
// Resetter resets a ReadCloser returned by [NewReader] or [NewReaderDict]
// to switch to a new underlying Reader. This permits reusing a ReadCloser
// instead of allocating a new one.
type Resetter interface {
Expand All @@ -63,20 +67,20 @@ type Resetter interface {

// NewReader creates a new ReadCloser.
// Reads from the returned ReadCloser read and decompress data from r.
// If r does not implement io.ByteReader, the decompressor may read more
// If r does not implement [io.ByteReader], the decompressor may read more
// data than necessary from r.
// It is the caller's responsibility to call Close on the ReadCloser when done.
//
// The ReadCloser returned by NewReader also implements Resetter.
// The [io.ReadCloser] returned by NewReader also implements [Resetter].
func NewReader(r io.Reader) (io.ReadCloser, error) {
return NewReaderDict(r, nil)
}

// NewReaderDict is like NewReader but uses a preset dictionary.
// NewReaderDict is like [NewReader] but uses a preset dictionary.
// NewReaderDict ignores the dictionary if the compressed data does not refer to it.
// If the compressed data refers to a different dictionary, NewReaderDict returns ErrDictionary.
// If the compressed data refers to a different dictionary, NewReaderDict returns [ErrDictionary].
//
// The ReadCloser returned by NewReaderDict also implements Resetter.
// The ReadCloser returned by NewReaderDict also implements [Resetter].
func NewReaderDict(r io.Reader, dict []byte) (io.ReadCloser, error) {
z := new(reader)
err := z.Reset(r, dict)
Expand Down Expand Up @@ -108,17 +112,17 @@ func (z *reader) Read(p []byte) (int, error) {
return n, z.err
}
// ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
checksum := uint32(z.scratch[0])<<24 | uint32(z.scratch[1])<<16 | uint32(z.scratch[2])<<8 | uint32(z.scratch[3])
checksum := binary.BigEndian.Uint32(z.scratch[:4])
if checksum != z.digest.Sum32() {
z.err = ErrChecksum
return n, z.err
}
return n, io.EOF
}

// Calling Close does not close the wrapped io.Reader originally passed to NewReader.
// Calling Close does not close the wrapped [io.Reader] originally passed to [NewReader].
// In order for the ZLIB checksum to be verified, the reader must be
// fully consumed until the io.EOF.
// fully consumed until the [io.EOF].
func (z *reader) Close() error {
if z.err != nil && z.err != io.EOF {
return z.err
Expand All @@ -128,7 +132,7 @@ func (z *reader) Close() error {
}

func (z *reader) Reset(r io.Reader, dict []byte) error {
*z = reader{decompressor: z.decompressor, digest: z.digest}
*z = reader{decompressor: z.decompressor}
if fr, ok := r.(flate.Reader); ok {
z.r = fr
} else {
Expand All @@ -143,8 +147,8 @@ func (z *reader) Reset(r io.Reader, dict []byte) error {
}
return z.err
}
h := uint(z.scratch[0])<<8 | uint(z.scratch[1])
if (z.scratch[0]&0x0f != zlibDeflate) || (h%31 != 0) {
h := binary.BigEndian.Uint16(z.scratch[:2])
if (z.scratch[0]&0x0f != zlibDeflate) || (z.scratch[0]>>4 > zlibMaxWindow) || (h%31 != 0) {
z.err = ErrHeader
return z.err
}
Expand All @@ -157,7 +161,7 @@ func (z *reader) Reset(r io.Reader, dict []byte) error {
}
return z.err
}
checksum := uint32(z.scratch[0])<<24 | uint32(z.scratch[1])<<16 | uint32(z.scratch[2])<<8 | uint32(z.scratch[3])
checksum := binary.BigEndian.Uint32(z.scratch[:4])
if checksum != adler32.Checksum(dict) {
z.err = ErrDictionary
return z.err
Expand Down
11 changes: 9 additions & 2 deletions zlib/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type zlibTest struct {
}

// Compare-to-golden test data was generated by the ZLIB example program at
// http://www.zlib.net/zpipe.c
// https://www.zlib.net/zpipe.c

var zlibTests = []zlibTest{
{
Expand Down Expand Up @@ -65,7 +65,14 @@ var zlibTests = []zlibTest{
nil,
},
{
"bad header",
"bad header (CINFO)",
"",
[]byte{0x88, 0x98, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01},
nil,
ErrHeader,
},
{
"bad header (FCHECK)",
"",
[]byte{0x78, 0x9f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01},
nil,
Expand Down
18 changes: 6 additions & 12 deletions zlib/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package zlib

import (
"encoding/binary"
"fmt"
"hash"
"hash/adler32"
Expand All @@ -20,7 +21,7 @@ const (
BestSpeed = flate.BestSpeed
BestCompression = flate.BestCompression
DefaultCompression = flate.DefaultCompression
ConstantCompression = flate.ConstantCompression
ConstantCompression = flate.ConstantCompression // Deprecated: Use HuffmanOnly.
HuffmanOnly = flate.HuffmanOnly
)

Expand All @@ -40,7 +41,7 @@ type Writer struct {
// NewWriter creates 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 WriteCloser when done.
// It is the caller's responsibility to call Close on the Writer when done.
// Writes may be buffered and not flushed until Close.
func NewWriter(w io.Writer) *Writer {
z, _ := NewWriterLevelDict(w, DefaultCompression, nil)
Expand Down Expand Up @@ -116,17 +117,13 @@ func (z *Writer) writeHeader() (err error) {
if z.dict != nil {
z.scratch[1] |= 1 << 5
}
z.scratch[1] += uint8(31 - (uint16(z.scratch[0])<<8+uint16(z.scratch[1]))%31)
z.scratch[1] += uint8(31 - binary.BigEndian.Uint16(z.scratch[:2])%31)
if _, err = z.w.Write(z.scratch[0:2]); err != nil {
return err
}
if z.dict != nil {
// The next four bytes are the Adler-32 checksum of the dictionary.
checksum := adler32.Checksum(z.dict)
z.scratch[0] = uint8(checksum >> 24)
z.scratch[1] = uint8(checksum >> 16)
z.scratch[2] = uint8(checksum >> 8)
z.scratch[3] = uint8(checksum >> 0)
binary.BigEndian.PutUint32(z.scratch[:], adler32.Checksum(z.dict))
if _, err = z.w.Write(z.scratch[0:4]); err != nil {
return err
}
Expand Down Expand Up @@ -192,10 +189,7 @@ func (z *Writer) Close() error {
}
checksum := z.digest.Sum32()
// ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
z.scratch[0] = uint8(checksum >> 24)
z.scratch[1] = uint8(checksum >> 16)
z.scratch[2] = uint8(checksum >> 8)
z.scratch[3] = uint8(checksum >> 0)
binary.BigEndian.PutUint32(z.scratch[:], checksum)
_, z.err = z.w.Write(z.scratch[0:4])
return z.err
}
16 changes: 14 additions & 2 deletions zlib/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,24 +153,36 @@ func TestWriter(t *testing.T) {
}

func TestWriterBig(t *testing.T) {
for _, fn := range filenames {
for i, fn := range filenames {
testFileLevelDict(t, fn, DefaultCompression, "")
testFileLevelDict(t, fn, NoCompression, "")
testFileLevelDict(t, fn, HuffmanOnly, "")
for level := BestSpeed; level <= BestCompression; level++ {
testFileLevelDict(t, fn, level, "")
if level >= 1 && testing.Short() {
break
}
}
if i == 0 && testing.Short() {
break
}
}
}

func TestWriterDict(t *testing.T) {
const dictionary = "0123456789."
for _, fn := range filenames {
for i, fn := range filenames {
testFileLevelDict(t, fn, DefaultCompression, dictionary)
testFileLevelDict(t, fn, NoCompression, dictionary)
testFileLevelDict(t, fn, HuffmanOnly, dictionary)
for level := BestSpeed; level <= BestCompression; level++ {
testFileLevelDict(t, fn, level, dictionary)
if level >= 1 && testing.Short() {
break
}
}
if i == 0 && testing.Short() {
break
}
}
}
Expand Down
Loading