...

Source file src/github.com/google/go-containerregistry/internal/compression/compression.go

Documentation: github.com/google/go-containerregistry/internal/compression

     1  // Copyright 2022 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 compression abstracts over gzip and zstd.
    16  package compression
    17  
    18  import (
    19  	"bufio"
    20  	"bytes"
    21  	"io"
    22  
    23  	"github.com/google/go-containerregistry/internal/gzip"
    24  	"github.com/google/go-containerregistry/internal/zstd"
    25  	"github.com/google/go-containerregistry/pkg/compression"
    26  )
    27  
    28  // Opener represents e.g. opening a file.
    29  type Opener = func() (io.ReadCloser, error)
    30  
    31  // GetCompression detects whether an Opener is compressed and which algorithm is used.
    32  func GetCompression(opener Opener) (compression.Compression, error) {
    33  	rc, err := opener()
    34  	if err != nil {
    35  		return compression.None, err
    36  	}
    37  	defer rc.Close()
    38  
    39  	cp, _, err := PeekCompression(rc)
    40  	if err != nil {
    41  		return compression.None, err
    42  	}
    43  
    44  	return cp, nil
    45  }
    46  
    47  // PeekCompression detects whether the input stream is compressed and which algorithm is used.
    48  //
    49  // If r implements Peek, we will use that directly, otherwise a small number
    50  // of bytes are buffered to Peek at the gzip/zstd header, and the returned
    51  // PeekReader can be used as a replacement for the consumed input io.Reader.
    52  func PeekCompression(r io.Reader) (compression.Compression, PeekReader, error) {
    53  	pr := intoPeekReader(r)
    54  
    55  	if isGZip, _, err := checkHeader(pr, gzip.MagicHeader); err != nil {
    56  		return compression.None, pr, err
    57  	} else if isGZip {
    58  		return compression.GZip, pr, nil
    59  	}
    60  
    61  	if isZStd, _, err := checkHeader(pr, zstd.MagicHeader); err != nil {
    62  		return compression.None, pr, err
    63  	} else if isZStd {
    64  		return compression.ZStd, pr, nil
    65  	}
    66  
    67  	return compression.None, pr, nil
    68  }
    69  
    70  // PeekReader is an io.Reader that also implements Peek a la bufio.Reader.
    71  type PeekReader interface {
    72  	io.Reader
    73  	Peek(n int) ([]byte, error)
    74  }
    75  
    76  // IntoPeekReader creates a PeekReader from an io.Reader.
    77  // If the reader already has a Peek method, it will just return the passed reader.
    78  func intoPeekReader(r io.Reader) PeekReader {
    79  	if p, ok := r.(PeekReader); ok {
    80  		return p
    81  	}
    82  
    83  	return bufio.NewReader(r)
    84  }
    85  
    86  // CheckHeader checks whether the first bytes from a PeekReader match an expected header
    87  func checkHeader(pr PeekReader, expectedHeader []byte) (bool, PeekReader, error) {
    88  	header, err := pr.Peek(len(expectedHeader))
    89  	if err != nil {
    90  		// https://github.com/google/go-containerregistry/issues/367
    91  		if err == io.EOF {
    92  			return false, pr, nil
    93  		}
    94  		return false, pr, err
    95  	}
    96  	return bytes.Equal(header, expectedHeader), pr, nil
    97  }
    98  

View as plain text