summaryrefslogtreecommitdiffstats
path: root/internal/gzip/zip.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/gzip/zip.go')
-rw-r--r--internal/gzip/zip.go118
1 files changed, 118 insertions, 0 deletions
diff --git a/internal/gzip/zip.go b/internal/gzip/zip.go
new file mode 100644
index 0000000..018c0f8
--- /dev/null
+++ b/internal/gzip/zip.go
@@ -0,0 +1,118 @@
+// Copyright 2020 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package gzip provides helper functions for interacting with gzipped streams.
+package gzip
+
+import (
+ "bufio"
+ "bytes"
+ "compress/gzip"
+ "io"
+
+ "github.com/google/go-containerregistry/internal/and"
+)
+
+// MagicHeader is the start of gzip files.
+var MagicHeader = []byte{'\x1f', '\x8b'}
+
+// ReadCloser reads uncompressed input data from the io.ReadCloser and
+// returns an io.ReadCloser from which compressed data may be read.
+// This uses gzip.BestSpeed for the compression level.
+func ReadCloser(r io.ReadCloser) io.ReadCloser {
+ return ReadCloserLevel(r, gzip.BestSpeed)
+}
+
+// ReadCloserLevel reads uncompressed input data from the io.ReadCloser and
+// returns an io.ReadCloser from which compressed data may be read.
+// Refer to compress/gzip for the level:
+// https://golang.org/pkg/compress/gzip/#pkg-constants
+func ReadCloserLevel(r io.ReadCloser, level int) io.ReadCloser {
+ pr, pw := io.Pipe()
+
+ // For highly compressible layers, gzip.Writer will output a very small
+ // number of bytes per Write(). This is normally fine, but when pushing
+ // to a registry, we want to ensure that we're taking full advantage of
+ // the available bandwidth instead of sending tons of tiny writes over
+ // the wire.
+ // 64K ought to be small enough for anybody.
+ bw := bufio.NewWriterSize(pw, 2<<16)
+
+ // Returns err so we can pw.CloseWithError(err)
+ go func() error {
+ // TODO(go1.14): Just defer {pw,gw,r}.Close like you'd expect.
+ // Context: https://golang.org/issue/24283
+ gw, err := gzip.NewWriterLevel(bw, level)
+ if err != nil {
+ return pw.CloseWithError(err)
+ }
+
+ if _, err := io.Copy(gw, r); err != nil {
+ defer r.Close()
+ defer gw.Close()
+ return pw.CloseWithError(err)
+ }
+
+ // Close gzip writer to Flush it and write gzip trailers.
+ if err := gw.Close(); err != nil {
+ return pw.CloseWithError(err)
+ }
+
+ // Flush bufio writer to ensure we write out everything.
+ if err := bw.Flush(); err != nil {
+ return pw.CloseWithError(err)
+ }
+
+ // We don't really care if these fail.
+ defer pw.Close()
+ defer r.Close()
+
+ return nil
+ }()
+
+ return pr
+}
+
+// UnzipReadCloser reads compressed input data from the io.ReadCloser and
+// returns an io.ReadCloser from which uncompressed data may be read.
+func UnzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
+ gr, err := gzip.NewReader(r)
+ if err != nil {
+ return nil, err
+ }
+ return &and.ReadCloser{
+ Reader: gr,
+ CloseFunc: func() error {
+ // If the unzip fails, then this seems to return the same
+ // error as the read. We don't want this to interfere with
+ // us closing the main ReadCloser, since this could leave
+ // an open file descriptor (fails on Windows).
+ gr.Close()
+ return r.Close()
+ },
+ }, nil
+}
+
+// Is detects whether the input stream is compressed.
+func Is(r io.Reader) (bool, error) {
+ magicHeader := make([]byte, 2)
+ n, err := r.Read(magicHeader)
+ if n == 0 && err == io.EOF {
+ return false, nil
+ }
+ if err != nil {
+ return false, err
+ }
+ return bytes.Equal(magicHeader, MagicHeader), nil
+}