aboutsummaryrefslogtreecommitdiff
path: root/internal/zlibx
diff options
context:
space:
mode:
authorGravatar Runxi Yu2025-11-19 08:00:00 +0800
committerGravatar Runxi Yu2025-11-19 08:00:00 +0800
commited0a113f034aa42aea23471c4bc0d7af159b7002 (patch)
tree7e828011b9e213499ce382eb17e2552da6e48de4 /internal/zlibx
parentRemove some redundant code (diff)
signatureNo signature
Probably should name the custom packages specially
Diffstat (limited to 'internal/zlibx')
-rw-r--r--internal/zlibx/LICENSE27
-rw-r--r--internal/zlibx/decompress.go69
-rw-r--r--internal/zlibx/decompress_test.go84
-rw-r--r--internal/zlibx/reader.go198
4 files changed, 378 insertions, 0 deletions
diff --git a/internal/zlibx/LICENSE b/internal/zlibx/LICENSE
new file mode 100644
index 00000000..2a7cf70d
--- /dev/null
+++ b/internal/zlibx/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2009 The Go Authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google LLC nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/internal/zlibx/decompress.go b/internal/zlibx/decompress.go
new file mode 100644
index 00000000..34e62c6f
--- /dev/null
+++ b/internal/zlibx/decompress.go
@@ -0,0 +1,69 @@
+package zlibx
+
+import (
+ "encoding/binary"
+ "io"
+
+ "git.sr.ht/~runxiyu/furgit/internal/adler32"
+ "git.sr.ht/~runxiyu/furgit/internal/bufpool"
+ "git.sr.ht/~runxiyu/furgit/internal/flatex"
+)
+
+// Decompress inflates the provided zlib wrapped stream and returns the
+// uncompressed data inside a pooled bufpool.Buffer.
+func Decompress(src []byte) (bufpool.Buffer, error) {
+ return DecompressDict(src, nil)
+}
+
+// DecompressDict is like Decompress but accepts a preset dictionary. The
+// dictionary must match the checksum embedded in the stream if the dictionary
+// flag is present.
+func DecompressDict(src []byte, dict []byte) (bufpool.Buffer, error) {
+ if len(src) < 6 {
+ return bufpool.Buffer{}, io.ErrUnexpectedEOF
+ }
+
+ cmf := src[0]
+ flg := src[1]
+ if (cmf&0x0f != zlibDeflate) || (cmf>>4 > zlibMaxWindow) || (binary.BigEndian.Uint16(src[:2])%31 != 0) {
+ return bufpool.Buffer{}, ErrHeader
+ }
+
+ offset := 2
+ haveDict := flg&0x20 != 0
+ if haveDict {
+ if len(src) < offset+4 {
+ return bufpool.Buffer{}, io.ErrUnexpectedEOF
+ }
+ if dict == nil {
+ return bufpool.Buffer{}, ErrDictionary
+ }
+ checksum := binary.BigEndian.Uint32(src[offset : offset+4])
+ if checksum != adler32.Checksum(dict) {
+ return bufpool.Buffer{}, ErrDictionary
+ }
+ offset += 4
+ }
+
+ if len(src[offset:]) < 4 {
+ return bufpool.Buffer{}, io.ErrUnexpectedEOF
+ }
+
+ deflateData := src[offset:]
+ out, consumed, err := flatex.DecompressDict(deflateData, dict)
+ if err != nil {
+ return bufpool.Buffer{}, err
+ }
+
+ checksumPos := offset + consumed
+ if checksumPos+4 > len(src) {
+ out.Release()
+ return bufpool.Buffer{}, io.ErrUnexpectedEOF
+ }
+ expected := binary.BigEndian.Uint32(src[checksumPos : checksumPos+4])
+ if expected != adler32.Checksum(out.Bytes()) {
+ out.Release()
+ return bufpool.Buffer{}, ErrChecksum
+ }
+ return out, nil
+}
diff --git a/internal/zlibx/decompress_test.go b/internal/zlibx/decompress_test.go
new file mode 100644
index 00000000..a4e9c608
--- /dev/null
+++ b/internal/zlibx/decompress_test.go
@@ -0,0 +1,84 @@
+package zlibx
+
+import (
+ "bytes"
+ stdzlib "compress/zlib"
+ "testing"
+)
+
+func compressZlib(t *testing.T, payload, dict []byte) []byte {
+ t.Helper()
+ var buf bytes.Buffer
+ var (
+ w *stdzlib.Writer
+ err error
+ )
+ if dict != nil {
+ w, err = stdzlib.NewWriterLevelDict(&buf, stdzlib.DefaultCompression, dict)
+ } else {
+ w = stdzlib.NewWriter(&buf)
+ }
+ if err != nil {
+ t.Fatalf("NewWriter: %v", err)
+ }
+ if _, err := w.Write(payload); err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ if err := w.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+ return buf.Bytes()
+}
+
+func TestDecompress(t *testing.T) {
+ payload := []byte("hello, zlib world!")
+ compressed := compressZlib(t, payload, nil)
+
+ out, err := Decompress(compressed)
+ if err != nil {
+ t.Fatalf("Decompress: %v", err)
+ }
+ defer out.Release()
+
+ if !bytes.Equal(out.Bytes(), payload) {
+ t.Fatalf("unexpected payload %q", out.Bytes())
+ }
+}
+
+func TestDecompressDict(t *testing.T) {
+ dict := []byte("git dictionary for zlib")
+ payload := append([]byte(nil), dict...)
+ payload = append(payload, []byte(" -- extended body -- extended body")...)
+ compressed := compressZlib(t, payload, dict)
+
+ out, err := DecompressDict(compressed, dict)
+ if err != nil {
+ t.Fatalf("DecompressDict: %v", err)
+ }
+ defer out.Release()
+
+ if !bytes.Equal(out.Bytes(), payload) {
+ t.Fatalf("unexpected payload %q", out.Bytes())
+ }
+}
+
+func TestDecompressDictMissing(t *testing.T) {
+ dict := []byte("preset dictionary")
+ payload := append([]byte(nil), dict...)
+ payload = append(payload, []byte(" .. more data ..")...)
+ compressed := compressZlib(t, payload, dict)
+
+ if _, err := Decompress(compressed); err != ErrDictionary {
+ t.Fatalf("expected ErrDictionary, got %v", err)
+ }
+}
+
+func TestDecompressChecksumError(t *testing.T) {
+ payload := []byte("checksum check")
+ compressed := compressZlib(t, payload, nil)
+ compressed[len(compressed)-1] ^= 0xff
+
+ if _, err := Decompress(compressed); err != ErrChecksum {
+ t.Fatalf("expected ErrChecksum, got %v", err)
+ }
+}
diff --git a/internal/zlibx/reader.go b/internal/zlibx/reader.go
new file mode 100644
index 00000000..80bbbb58
--- /dev/null
+++ b/internal/zlibx/reader.go
@@ -0,0 +1,198 @@
+// Copyright 2009 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 zlib implements reading and writing of zlib format compressed data,
+as specified in RFC 1950.
+
+This package differs from the standard library's compress/zlib package
+in that it pools readers to reduce allocations. Writers are unchanged.
+
+Note that closing the reader causes it to be returned to a pool for
+reuse. Therefore, the caller must not retain references to the
+reader after closing it; in the standard library's compress/zlib package,
+it is legal to Reset a closed reader and continue using it; that is
+not allowed here, so there is simply no Resetter interface.
+
+The implementation provides filters that uncompress during reading
+and compress during writing. For example, to write compressed data
+to a buffer:
+
+ var b bytes.Buffer
+ w := zlib.NewWriter(&b)
+ w.Write([]byte("hello, world\n"))
+ w.Close()
+
+and to read that data back:
+
+ r, err := zlib.NewReader(&b)
+ io.Copy(os.Stdout, r)
+ r.Close()
+*/
+package zlibx
+
+import (
+ "bufio"
+ "encoding/binary"
+ "errors"
+ "hash"
+ "io"
+ "sync"
+
+ "git.sr.ht/~runxiyu/furgit/internal/adler32"
+ "git.sr.ht/~runxiyu/furgit/internal/flatex"
+)
+
+const (
+ zlibDeflate = 8
+ zlibMaxWindow = 7
+)
+
+var (
+ // ErrChecksum is returned when reading ZLIB data that has an invalid checksum.
+ ErrChecksum = errors.New("zlib: invalid checksum")
+ // ErrDictionary is returned when reading ZLIB data that has an invalid dictionary.
+ ErrDictionary = errors.New("zlib: invalid dictionary")
+ // ErrHeader is returned when reading ZLIB data that has an invalid header.
+ ErrHeader = errors.New("zlib: invalid header")
+)
+
+var pool = sync.Pool{
+ New: func() any {
+ r := new(reader)
+ return r
+ },
+}
+
+type reader struct {
+ r flatex.Reader
+ decompressor io.ReadCloser
+ digest hash.Hash32
+ err error
+ scratch [4]byte
+}
+
+// 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
+// data than necessary from r.
+// It is the caller's responsibility to call Close on the ReadCloser when done.
+func NewReader(r io.Reader) (io.ReadCloser, error) {
+ return NewReaderDict(r, nil)
+}
+
+// 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].
+func NewReaderDict(r io.Reader, dict []byte) (io.ReadCloser, error) {
+ v := pool.Get()
+ z, ok := v.(*reader)
+ if !ok {
+ panic("zlib: pool returned unexpected type")
+ }
+ err := z.Reset(r, dict)
+ if err != nil {
+ return nil, err
+ }
+ return z, nil
+}
+
+func (z *reader) Read(p []byte) (int, error) {
+ if z.err != nil {
+ return 0, z.err
+ }
+
+ var n int
+ n, z.err = z.decompressor.Read(p)
+ z.digest.Write(p[0:n])
+ if z.err != io.EOF {
+ // In the normal case we return here.
+ return n, z.err
+ }
+
+ // Finished file; check checksum.
+ if _, err := io.ReadFull(z.r, z.scratch[0:4]); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ z.err = err
+ return n, z.err
+ }
+ // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
+ 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].
+// In order for the ZLIB checksum to be verified, the reader must be
+// fully consumed until the [io.EOF].
+func (z *reader) Close() error {
+ if z.err != nil && z.err != io.EOF {
+ return z.err
+ }
+ z.err = z.decompressor.Close()
+ if z.err != nil {
+ return z.err
+ }
+
+ pool.Put(z)
+ return nil
+}
+
+func (z *reader) Reset(r io.Reader, dict []byte) error {
+ *z = reader{decompressor: z.decompressor}
+ if fr, ok := r.(flatex.Reader); ok {
+ z.r = fr
+ } else {
+ z.r = bufio.NewReader(r)
+ }
+
+ // Read the header (RFC 1950 section 2.2.).
+ _, z.err = io.ReadFull(z.r, z.scratch[0:2])
+ if z.err != nil {
+ if z.err == io.EOF {
+ z.err = io.ErrUnexpectedEOF
+ }
+ return z.err
+ }
+ 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
+ }
+ haveDict := z.scratch[1]&0x20 != 0
+ if haveDict {
+ _, z.err = io.ReadFull(z.r, z.scratch[0:4])
+ if z.err != nil {
+ if z.err == io.EOF {
+ z.err = io.ErrUnexpectedEOF
+ }
+ return z.err
+ }
+ checksum := binary.BigEndian.Uint32(z.scratch[:4])
+ if checksum != adler32.Checksum(dict) {
+ z.err = ErrDictionary
+ return z.err
+ }
+ }
+
+ if z.decompressor == nil {
+ if haveDict {
+ z.decompressor = flatex.NewReaderDict(z.r, dict)
+ } else {
+ z.decompressor = flatex.NewReader(z.r)
+ }
+ } else {
+ z.err = z.decompressor.(flatex.Resetter).Reset(z.r, dict)
+ if z.err != nil {
+ return z.err
+ }
+ }
+ z.digest = adler32.New()
+ return nil
+}