go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/impl/repo/processing/reader.go (about)

     1  // Copyright 2018 The LUCI Authors.
     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 processing
    16  
    17  import (
    18  	"io"
    19  	"math"
    20  
    21  	"github.com/klauspost/compress/zip"
    22  
    23  	"go.chromium.org/luci/common/errors"
    24  )
    25  
    26  // TODO(vadimsh): Share code with the client.
    27  
    28  // PackageReader knows how to extract files from CIPD packages.
    29  //
    30  // CIPD packages are actually zip archives, but we don't want to expose it
    31  // everywhere.
    32  type PackageReader struct {
    33  	zr *zip.Reader
    34  }
    35  
    36  // NewPackageReader opens the package by reading its directory.
    37  func NewPackageReader(r io.ReaderAt, size int64) (*PackageReader, error) {
    38  	zr, err := zip.NewReader(r, size)
    39  	if err != nil {
    40  		// Note: we rely here (and in other places where we return errors) on
    41  		// zip.Reader NOT wrapping errors from 'r', so they inherit transient tags
    42  		// in case of transient Google Storage errors.
    43  		return nil, err
    44  	}
    45  	return &PackageReader{zr}, nil
    46  }
    47  
    48  // Files returns names of files inside the package.
    49  func (p *PackageReader) Files() []string {
    50  	files := make([]string, len(p.zr.File))
    51  	for i, f := range p.zr.File {
    52  		files[i] = f.Name
    53  	}
    54  	return files
    55  }
    56  
    57  // Open opens some file inside the package for reading.
    58  //
    59  // Returns the ReadCloser and the uncompressed file size.
    60  func (p *PackageReader) Open(path string) (io.ReadCloser, int64, error) {
    61  	for _, f := range p.zr.File {
    62  		if f.Name == path {
    63  			if f.UncompressedSize64 > math.MaxInt64 {
    64  				return nil, 0, errors.Reason("the file %q is unbelievably huge (%d bytes)", path, f.UncompressedSize64).Err()
    65  			}
    66  			rc, err := f.Open()
    67  			if err != nil {
    68  				return nil, 0, err
    69  			}
    70  			return rc, int64(f.UncompressedSize64), nil
    71  		}
    72  	}
    73  	return nil, 0, errors.Reason("no file %q inside the package", path).Err()
    74  }