github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/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  	"go/build"
    24  	"io"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"runtime"
    29  	"sort"
    30  	"strings"
    31  	"time"
    32  
    33  	"golang.org/x/tools/godoc/vfs"
    34  )
    35  
    36  // zipFI is the zip-file based implementation of FileInfo
    37  type zipFI struct {
    38  	name string    // directory-local name
    39  	file *zip.File // nil for a directory
    40  }
    41  
    42  func (fi zipFI) Name() string {
    43  	return fi.name
    44  }
    45  
    46  func (fi zipFI) Size() int64 {
    47  	if f := fi.file; f != nil {
    48  		return int64(f.UncompressedSize)
    49  	}
    50  	return 0 // directory
    51  }
    52  
    53  func (fi zipFI) ModTime() time.Time {
    54  	if f := fi.file; f != nil {
    55  		return f.ModTime()
    56  	}
    57  	return time.Time{} // directory has no modified time entry
    58  }
    59  
    60  func (fi zipFI) Mode() os.FileMode {
    61  	if fi.file == nil {
    62  		// Unix directories typically are executable, hence 555.
    63  		return os.ModeDir | 0555
    64  	}
    65  	return 0444
    66  }
    67  
    68  func (fi zipFI) IsDir() bool {
    69  	return fi.file == nil
    70  }
    71  
    72  func (fi zipFI) Sys() interface{} {
    73  	return nil
    74  }
    75  
    76  // zipFS is the zip-file based implementation of FileSystem
    77  type zipFS struct {
    78  	*zip.ReadCloser
    79  	list zipList
    80  	name string
    81  }
    82  
    83  func (fs *zipFS) String() string {
    84  	return "zip(" + fs.name + ")"
    85  }
    86  
    87  func (fs *zipFS) RootType(abspath string) vfs.RootType {
    88  	var t vfs.RootType
    89  	switch {
    90  	case abspath == runtime.GOROOT():
    91  		t = vfs.RootTypeGoRoot
    92  	case isGoPath(abspath):
    93  		t = vfs.RootTypeGoPath
    94  	}
    95  	return t
    96  }
    97  
    98  func isGoPath(path string) bool {
    99  	for _, p := range filepath.SplitList(build.Default.GOPATH) {
   100  		if p == path {
   101  			return true
   102  		}
   103  	}
   104  	return false
   105  }
   106  
   107  func (fs *zipFS) Close() error {
   108  	fs.list = nil
   109  	return fs.ReadCloser.Close()
   110  }
   111  
   112  func zipPath(name string) (string, error) {
   113  	name = path.Clean(name)
   114  	if !path.IsAbs(name) {
   115  		return "", fmt.Errorf("stat: not an absolute path: %s", name)
   116  	}
   117  	return name[1:], nil // strip leading '/'
   118  }
   119  
   120  func isRoot(abspath string) bool {
   121  	return path.Clean(abspath) == "/"
   122  }
   123  
   124  func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
   125  	if isRoot(abspath) {
   126  		return 0, zipFI{
   127  			name: "",
   128  			file: nil,
   129  		}, nil
   130  	}
   131  	zippath, err := zipPath(abspath)
   132  	if err != nil {
   133  		return 0, zipFI{}, err
   134  	}
   135  	i, exact := fs.list.lookup(zippath)
   136  	if i < 0 {
   137  		// zippath has leading '/' stripped - print it explicitly
   138  		return -1, zipFI{}, &os.PathError{Path: "/" + zippath, Err: os.ErrNotExist}
   139  	}
   140  	_, name := path.Split(zippath)
   141  	var file *zip.File
   142  	if exact {
   143  		file = fs.list[i] // exact match found - must be a file
   144  	}
   145  	return i, zipFI{name, file}, nil
   146  }
   147  
   148  func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) {
   149  	_, fi, err := fs.stat(abspath)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	if fi.IsDir() {
   154  		return nil, fmt.Errorf("Open: %s is a directory", abspath)
   155  	}
   156  	r, err := fi.file.Open()
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	return &zipSeek{fi.file, r}, nil
   161  }
   162  
   163  type zipSeek struct {
   164  	file *zip.File
   165  	io.ReadCloser
   166  }
   167  
   168  func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
   169  	if whence == 0 && offset == 0 {
   170  		r, err := f.file.Open()
   171  		if err != nil {
   172  			return 0, err
   173  		}
   174  		f.Close()
   175  		f.ReadCloser = r
   176  		return 0, nil
   177  	}
   178  	return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
   179  }
   180  
   181  func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
   182  	_, fi, err := fs.stat(abspath)
   183  	return fi, err
   184  }
   185  
   186  func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) {
   187  	_, fi, err := fs.stat(abspath)
   188  	return fi, err
   189  }
   190  
   191  func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
   192  	i, fi, err := fs.stat(abspath)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	if !fi.IsDir() {
   197  		return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
   198  	}
   199  
   200  	var list []os.FileInfo
   201  
   202  	// make dirname the prefix that file names must start with to be considered
   203  	// in this directory. we must special case the root directory because, per
   204  	// the spec of this package, zip file entries MUST NOT start with /, so we
   205  	// should not append /, as we would in every other case.
   206  	var dirname string
   207  	if isRoot(abspath) {
   208  		dirname = ""
   209  	} else {
   210  		zippath, err := zipPath(abspath)
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  		dirname = zippath + "/"
   215  	}
   216  	prevname := ""
   217  	for _, e := range fs.list[i:] {
   218  		if !strings.HasPrefix(e.Name, dirname) {
   219  			break // not in the same directory anymore
   220  		}
   221  		name := e.Name[len(dirname):] // local name
   222  		file := e
   223  		if i := strings.IndexRune(name, '/'); i >= 0 {
   224  			// We infer directories from files in subdirectories.
   225  			// If we have x/y, return a directory entry for x.
   226  			name = name[0:i] // keep local directory name only
   227  			file = nil
   228  		}
   229  		// If we have x/y and x/z, don't return two directory entries for x.
   230  		// TODO(gri): It should be possible to do this more efficiently
   231  		// by determining the (fs.list) range of local directory entries
   232  		// (via two binary searches).
   233  		if name != prevname {
   234  			list = append(list, zipFI{name, file})
   235  			prevname = name
   236  		}
   237  	}
   238  
   239  	return list, nil
   240  }
   241  
   242  func New(rc *zip.ReadCloser, name string) vfs.FileSystem {
   243  	list := make(zipList, len(rc.File))
   244  	copy(list, rc.File) // sort a copy of rc.File
   245  	sort.Sort(list)
   246  	return &zipFS{rc, list, name}
   247  }
   248  
   249  type zipList []*zip.File
   250  
   251  // zipList implements sort.Interface
   252  func (z zipList) Len() int           { return len(z) }
   253  func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
   254  func (z zipList) Swap(i, j int)      { z[i], z[j] = z[j], z[i] }
   255  
   256  // lookup returns the smallest index of an entry with an exact match
   257  // for name, or an inexact match starting with name/. If there is no
   258  // such entry, the result is -1, false.
   259  func (z zipList) lookup(name string) (index int, exact bool) {
   260  	// look for exact match first (name comes before name/ in z)
   261  	i := sort.Search(len(z), func(i int) bool {
   262  		return name <= z[i].Name
   263  	})
   264  	if i >= len(z) {
   265  		return -1, false
   266  	}
   267  	// 0 <= i < len(z)
   268  	if z[i].Name == name {
   269  		return i, true
   270  	}
   271  
   272  	// look for inexact match (must be in z[i:], if present)
   273  	z = z[i:]
   274  	name += "/"
   275  	j := sort.Search(len(z), func(i int) bool {
   276  		return name <= z[i].Name
   277  	})
   278  	if j >= len(z) {
   279  		return -1, false
   280  	}
   281  	// 0 <= j < len(z)
   282  	if strings.HasPrefix(z[j].Name, name) {
   283  		return i + j, false
   284  	}
   285  
   286  	return -1, false
   287  }