1 // Copyright 2020 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package gzip provides helper functions for interacting with gzipped streams. 16 package gzip 17 18 import ( 19 "bufio" 20 "bytes" 21 "compress/gzip" 22 "io" 23 24 "github.com/google/go-containerregistry/internal/and" 25 ) 26 27 // MagicHeader is the start of gzip files. 28 var MagicHeader = []byte{'\x1f', '\x8b'} 29 30 // ReadCloser reads uncompressed input data from the io.ReadCloser and 31 // returns an io.ReadCloser from which compressed data may be read. 32 // This uses gzip.BestSpeed for the compression level. 33 func ReadCloser(r io.ReadCloser) io.ReadCloser { 34 return ReadCloserLevel(r, gzip.BestSpeed) 35 } 36 37 // ReadCloserLevel reads uncompressed input data from the io.ReadCloser and 38 // returns an io.ReadCloser from which compressed data may be read. 39 // Refer to compress/gzip for the level: 40 // https://golang.org/pkg/compress/gzip/#pkg-constants 41 func ReadCloserLevel(r io.ReadCloser, level int) io.ReadCloser { 42 pr, pw := io.Pipe() 43 44 // For highly compressible layers, gzip.Writer will output a very small 45 // number of bytes per Write(). This is normally fine, but when pushing 46 // to a registry, we want to ensure that we're taking full advantage of 47 // the available bandwidth instead of sending tons of tiny writes over 48 // the wire. 49 // 64K ought to be small enough for anybody. 50 bw := bufio.NewWriterSize(pw, 2<<16) 51 52 // Returns err so we can pw.CloseWithError(err) 53 go func() error { 54 // TODO(go1.14): Just defer {pw,gw,r}.Close like you'd expect. 55 // Context: https://golang.org/issue/24283 56 gw, err := gzip.NewWriterLevel(bw, level) 57 if err != nil { 58 return pw.CloseWithError(err) 59 } 60 61 if _, err := io.Copy(gw, r); err != nil { 62 defer r.Close() 63 defer gw.Close() 64 return pw.CloseWithError(err) 65 } 66 67 // Close gzip writer to Flush it and write gzip trailers. 68 if err := gw.Close(); err != nil { 69 return pw.CloseWithError(err) 70 } 71 72 // Flush bufio writer to ensure we write out everything. 73 if err := bw.Flush(); err != nil { 74 return pw.CloseWithError(err) 75 } 76 77 // We don't really care if these fail. 78 defer pw.Close() 79 defer r.Close() 80 81 return nil 82 }() 83 84 return pr 85 } 86 87 // UnzipReadCloser reads compressed input data from the io.ReadCloser and 88 // returns an io.ReadCloser from which uncompressed data may be read. 89 func UnzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) { 90 gr, err := gzip.NewReader(r) 91 if err != nil { 92 return nil, err 93 } 94 return &and.ReadCloser{ 95 Reader: gr, 96 CloseFunc: func() error { 97 // If the unzip fails, then this seems to return the same 98 // error as the read. We don't want this to interfere with 99 // us closing the main ReadCloser, since this could leave 100 // an open file descriptor (fails on Windows). 101 gr.Close() 102 return r.Close() 103 }, 104 }, nil 105 } 106 107 // Is detects whether the input stream is compressed. 108 func Is(r io.Reader) (bool, error) { 109 magicHeader := make([]byte, 2) 110 n, err := r.Read(magicHeader) 111 if n == 0 && err == io.EOF { 112 return false, nil 113 } 114 if err != nil { 115 return false, err 116 } 117 return bytes.Equal(magicHeader, MagicHeader), nil 118 } 119