github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/vfs/zipfs/zipfs.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package zipfs file provides an implementation of the FileSystem
     6  // interface based on the contents of a .zip file.
     7  //
     8  // Assumptions:
     9  //
    10  // - The file paths stored in the zip file must use a slash ('/') as path
    11  //   separator; and they must be relative (i.e., they must not start with
    12  //   a '/' - this is usually the case if the file was created w/o special
    13  //   options).
    14  // - The zip file system treats the file paths found in the zip internally
    15  //   like absolute paths w/o a leading '/'; i.e., the paths are considered
    16  //   relative to the root of the file system.
    17  // - All path arguments to file system methods must be absolute paths.
    18  package zipfs // import "golang.org/x/tools/godoc/vfs/zipfs"
    19  
    20  import (
    21  	"archive/zip"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"path"
    26  	"sort"
    27  	"strings"
    28  	"time"
    29  
    30  	"golang.org/x/tools/godoc/vfs"
    31  )
    32  
    33  // zipFI is the zip-file based implementation of FileInfo
    34  type zipFI struct {
    35  	name string    // directory-local name
    36  	file *zip.File // nil for a directory
    37  }
    38  
    39  func (fi zipFI) Name() string {
    40  	return fi.name
    41  }
    42  
    43  func (fi zipFI) Size() int64 {
    44  	if f := fi.file; f != nil {
    45  		return int64(f.UncompressedSize)
    46  	}
    47  	return 0 // directory
    48  }
    49  
    50  func (fi zipFI) ModTime() time.Time {
    51  	if f := fi.file; f != nil {
    52  		return f.ModTime()
    53  	}
    54  	return time.Time{} // directory has no modified time entry
    55  }
    56  
    57  func (fi zipFI) Mode() os.FileMode {
    58  	if fi.file == nil {
    59  		// Unix directories typically are executable, hence 555.
    60  		return os.ModeDir | 0555
    61  	}
    62  	return 0444
    63  }
    64  
    65  func (fi zipFI) IsDir() bool {
    66  	return fi.file == nil
    67  }
    68  
    69  func (fi zipFI) Sys() interface{} {
    70  	return nil
    71  }
    72  
    73  // zipFS is the zip-file based implementation of FileSystem
    74  type zipFS struct {
    75  	*zip.ReadCloser
    76  	list zipList
    77  	name string
    78  }
    79  
    80  func (fs *zipFS) String() string {
    81  	return "zip(" + fs.name + ")"
    82  }
    83  
    84  func (fs *zipFS) Close() error {
    85  	fs.list = nil
    86  	return fs.ReadCloser.Close()
    87  }
    88  
    89  func zipPath(name string) (string, error) {
    90  	name = path.Clean(name)
    91  	if !path.IsAbs(name) {
    92  		return "", fmt.Errorf("stat: not an absolute path: %s", name)
    93  	}
    94  	return name[1:], nil // strip leading '/'
    95  }
    96  
    97  func isRoot(abspath string) bool {
    98  	return path.Clean(abspath) == "/"
    99  }
   100  
   101  func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
   102  	if isRoot(abspath) {
   103  		return 0, zipFI{
   104  			name: "",
   105  			file: nil,
   106  		}, nil
   107  	}
   108  	zippath, err := zipPath(abspath)
   109  	if err != nil {
   110  		return 0, zipFI{}, err
   111  	}
   112  	i, exact := fs.list.lookup(zippath)
   113  	if i < 0 {
   114  		// zippath has leading '/' stripped - print it explicitly
   115  		return -1, zipFI{}, fmt.Errorf("file not found: /%s", zippath)
   116  	}
   117  	_, name := path.Split(zippath)
   118  	var file *zip.File
   119  	if exact {
   120  		file = fs.list[i] // exact match found - must be a file
   121  	}
   122  	return i, zipFI{name, file}, nil
   123  }
   124  
   125  func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) {
   126  	_, fi, err := fs.stat(abspath)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	if fi.IsDir() {
   131  		return nil, fmt.Errorf("Open: %s is a directory", abspath)
   132  	}
   133  	r, err := fi.file.Open()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return &zipSeek{fi.file, r}, nil
   138  }
   139  
   140  type zipSeek struct {
   141  	file *zip.File
   142  	io.ReadCloser
   143  }
   144  
   145  func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
   146  	if whence == 0 && offset == 0 {
   147  		r, err := f.file.Open()
   148  		if err != nil {
   149  			return 0, err
   150  		}
   151  		f.Close()
   152  		f.ReadCloser = r
   153  		return 0, nil
   154  	}
   155  	return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
   156  }
   157  
   158  func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
   159  	_, fi, err := fs.stat(abspath)
   160  	return fi, err
   161  }
   162  
   163  func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) {
   164  	_, fi, err := fs.stat(abspath)
   165  	return fi, err
   166  }
   167  
   168  func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
   169  	i, fi, err := fs.stat(abspath)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	if !fi.IsDir() {
   174  		return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
   175  	}
   176  
   177  	var list []os.FileInfo
   178  
   179  	// make dirname the prefix that file names must start with to be considered
   180  	// in this directory. we must special case the root directory because, per
   181  	// the spec of this package, zip file entries MUST NOT start with /, so we
   182  	// should not append /, as we would in every other case.
   183  	var dirname string
   184  	if isRoot(abspath) {
   185  		dirname = ""
   186  	} else {
   187  		zippath, err := zipPath(abspath)
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		dirname = zippath + "/"
   192  	}
   193  	prevname := ""
   194  	for _, e := range fs.list[i:] {
   195  		if !strings.HasPrefix(e.Name, dirname) {
   196  			break // not in the same directory anymore
   197  		}
   198  		name := e.Name[len(dirname):] // local name
   199  		file := e
   200  		if i := strings.IndexRune(name, '/'); i >= 0 {
   201  			// We infer directories from files in subdirectories.
   202  			// If we have x/y, return a directory entry for x.
   203  			name = name[0:i] // keep local directory name only
   204  			file = nil
   205  		}
   206  		// If we have x/y and x/z, don't return two directory entries for x.
   207  		// TODO(gri): It should be possible to do this more efficiently
   208  		// by determining the (fs.list) range of local directory entries
   209  		// (via two binary searches).
   210  		if name != prevname {
   211  			list = append(list, zipFI{name, file})
   212  			prevname = name
   213  		}
   214  	}
   215  
   216  	return list, nil
   217  }
   218  
   219  func New(rc *zip.ReadCloser, name string) vfs.FileSystem {
   220  	list := make(zipList, len(rc.File))
   221  	copy(list, rc.File) // sort a copy of rc.File
   222  	sort.Sort(list)
   223  	return &zipFS{rc, list, name}
   224  }
   225  
   226  type zipList []*zip.File
   227  
   228  // zipList implements sort.Interface
   229  func (z zipList) Len() int           { return len(z) }
   230  func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
   231  func (z zipList) Swap(i, j int)      { z[i], z[j] = z[j], z[i] }
   232  
   233  // lookup returns the smallest index of an entry with an exact match
   234  // for name, or an inexact match starting with name/. If there is no
   235  // such entry, the result is -1, false.
   236  func (z zipList) lookup(name string) (index int, exact bool) {
   237  	// look for exact match first (name comes before name/ in z)
   238  	i := sort.Search(len(z), func(i int) bool {
   239  		return name <= z[i].Name
   240  	})
   241  	if i >= len(z) {
   242  		return -1, false
   243  	}
   244  	// 0 <= i < len(z)
   245  	if z[i].Name == name {
   246  		return i, true
   247  	}
   248  
   249  	// look for inexact match (must be in z[i:], if present)
   250  	z = z[i:]
   251  	name += "/"
   252  	j := sort.Search(len(z), func(i int) bool {
   253  		return name <= z[i].Name
   254  	})
   255  	if j >= len(z) {
   256  		return -1, false
   257  	}
   258  	// 0 <= j < len(z)
   259  	if strings.HasPrefix(z[j].Name, name) {
   260  		return i + j, false
   261  	}
   262  
   263  	return -1, false
   264  }