kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/vfs/zip/fs.go (about)

     1  /*
     2   * Copyright 2015 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // Package zip defines a VFS implementation that understands a zip archive as an
    18  // isolated, read-only file system.
    19  package zip // import "kythe.io/kythe/go/platform/vfs/zip"
    20  
    21  import (
    22  	"archive/zip"
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"path/filepath"
    29  
    30  	"kythe.io/kythe/go/platform/vfs"
    31  )
    32  
    33  var _ vfs.Reader = FS{}
    34  
    35  // Open returns a read-only virtual file system (vfs.Reader), using the contents
    36  // a zip archive read with r.
    37  func Open(r io.ReaderAt, size int64) (FS, error) {
    38  	rc, err := zip.NewReader(r, size)
    39  	if err != nil {
    40  		return FS{}, err
    41  	}
    42  	if len(rc.File) == 0 {
    43  		return FS{}, errors.New("archive has no root directory")
    44  	}
    45  	return FS{rc}, err
    46  }
    47  
    48  // FS implements the vfs.Reader interface for zip archives.
    49  type FS struct{ Archive *zip.Reader }
    50  
    51  func (z FS) find(path string) *zip.File {
    52  	dirPath := path + string(filepath.Separator)
    53  	for _, f := range z.Archive.File {
    54  		switch f.Name {
    55  		case path, dirPath:
    56  			return f
    57  		}
    58  	}
    59  	return nil
    60  }
    61  
    62  // Stat implements part of vfs.Reader using the file metadata stored in the
    63  // zip archive.  The path must match one of the archive paths.
    64  func (z FS) Stat(_ context.Context, path string) (os.FileInfo, error) {
    65  	f := z.find(path)
    66  	if f == nil {
    67  		return nil, fmt.Errorf("path %q does not exist", path)
    68  	}
    69  	return f.FileInfo(), nil
    70  }
    71  
    72  // Open implements part of vfs.Reader, returning a vfs.FileReader owned by
    73  // the underlying zip archive. It is safe to open multiple files concurrently,
    74  // as documented by the zip package.
    75  func (z FS) Open(_ context.Context, path string) (vfs.FileReader, error) {
    76  	f := z.find(path)
    77  	if f == nil {
    78  		return nil, os.ErrNotExist
    79  	}
    80  	fo, err := f.Open()
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	return vfs.UnseekableFileReader{fo}, nil
    85  }
    86  
    87  // Glob implements part of vfs.Reader using filepath.Match to compare the
    88  // glob pattern to each archive path.
    89  func (z FS) Glob(_ context.Context, glob string) ([]string, error) {
    90  	var names []string
    91  	for _, f := range z.Archive.File {
    92  		if ok, err := filepath.Match(glob, f.Name); err != nil {
    93  			panic(fmt.Sprintf("Invalid glob pattern %q: %v", glob, err))
    94  		} else if ok {
    95  			names = append(names, f.Name)
    96  		}
    97  	}
    98  	return names, nil
    99  }