github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/godoc/zip.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  // This 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  
    19  package main
    20  
    21  import (
    22  	"archive/zip"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"path"
    27  	"sort"
    28  	"strings"
    29  	"time"
    30  )
    31  
    32  // zipFI is the zip-file based implementation of FileInfo
    33  type zipFI struct {
    34  	name string    // directory-local name
    35  	file *zip.File // nil for a directory
    36  }
    37  
    38  func (fi zipFI) Name() string {
    39  	return fi.name
    40  }
    41  
    42  func (fi zipFI) Size() int64 {
    43  	if f := fi.file; f != nil {
    44  		return int64(f.UncompressedSize)
    45  	}
    46  	return 0 // directory
    47  }
    48  
    49  func (fi zipFI) ModTime() time.Time {
    50  	if f := fi.file; f != nil {
    51  		return f.ModTime()
    52  	}
    53  	return time.Time{} // directory has no modified time entry
    54  }
    55  
    56  func (fi zipFI) Mode() os.FileMode {
    57  	if fi.file == nil {
    58  		// Unix directories typically are executable, hence 555.
    59  		return os.ModeDir | 0555
    60  	}
    61  	return 0444
    62  }
    63  
    64  func (fi zipFI) IsDir() bool {
    65  	return fi.file == nil
    66  }
    67  
    68  func (fi zipFI) Sys() interface{} {
    69  	return nil
    70  }
    71  
    72  // zipFS is the zip-file based implementation of FileSystem
    73  type zipFS struct {
    74  	*zip.ReadCloser
    75  	list zipList
    76  	name string
    77  }
    78  
    79  func (fs *zipFS) String() string {
    80  	return "zip(" + fs.name + ")"
    81  }
    82  
    83  func (fs *zipFS) Close() error {
    84  	fs.list = nil
    85  	return fs.ReadCloser.Close()
    86  }
    87  
    88  func zipPath(name string) string {
    89  	name = path.Clean(name)
    90  	if !path.IsAbs(name) {
    91  		panic(fmt.Sprintf("stat: not an absolute path: %s", name))
    92  	}
    93  	return name[1:] // strip leading '/'
    94  }
    95  
    96  func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
    97  	i, exact := fs.list.lookup(abspath)
    98  	if i < 0 {
    99  		// abspath has leading '/' stripped - print it explicitly
   100  		return -1, zipFI{}, fmt.Errorf("file not found: /%s", abspath)
   101  	}
   102  	_, name := path.Split(abspath)
   103  	var file *zip.File
   104  	if exact {
   105  		file = fs.list[i] // exact match found - must be a file
   106  	}
   107  	return i, zipFI{name, file}, nil
   108  }
   109  
   110  func (fs *zipFS) Open(abspath string) (readSeekCloser, error) {
   111  	_, fi, err := fs.stat(zipPath(abspath))
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	if fi.IsDir() {
   116  		return nil, fmt.Errorf("Open: %s is a directory", abspath)
   117  	}
   118  	r, err := fi.file.Open()
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	return &zipSeek{fi.file, r}, nil
   123  }
   124  
   125  type zipSeek struct {
   126  	file *zip.File
   127  	io.ReadCloser
   128  }
   129  
   130  func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
   131  	if whence == 0 && offset == 0 {
   132  		r, err := f.file.Open()
   133  		if err != nil {
   134  			return 0, err
   135  		}
   136  		f.Close()
   137  		f.ReadCloser = r
   138  		return 0, nil
   139  	}
   140  	return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
   141  }
   142  
   143  func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
   144  	_, fi, err := fs.stat(zipPath(abspath))
   145  	return fi, err
   146  }
   147  
   148  func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) {
   149  	_, fi, err := fs.stat(zipPath(abspath))
   150  	return fi, err
   151  }
   152  
   153  func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
   154  	path := zipPath(abspath)
   155  	i, fi, err := fs.stat(path)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	if !fi.IsDir() {
   160  		return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
   161  	}
   162  
   163  	var list []os.FileInfo
   164  	dirname := path + "/"
   165  	prevname := ""
   166  	for _, e := range fs.list[i:] {
   167  		if !strings.HasPrefix(e.Name, dirname) {
   168  			break // not in the same directory anymore
   169  		}
   170  		name := e.Name[len(dirname):] // local name
   171  		file := e
   172  		if i := strings.IndexRune(name, '/'); i >= 0 {
   173  			// We infer directories from files in subdirectories.
   174  			// If we have x/y, return a directory entry for x.
   175  			name = name[0:i] // keep local directory name only
   176  			file = nil
   177  		}
   178  		// If we have x/y and x/z, don't return two directory entries for x.
   179  		// TODO(gri): It should be possible to do this more efficiently
   180  		// by determining the (fs.list) range of local directory entries
   181  		// (via two binary searches).
   182  		if name != prevname {
   183  			list = append(list, zipFI{name, file})
   184  			prevname = name
   185  		}
   186  	}
   187  
   188  	return list, nil
   189  }
   190  
   191  func NewZipFS(rc *zip.ReadCloser, name string) FileSystem {
   192  	list := make(zipList, len(rc.File))
   193  	copy(list, rc.File) // sort a copy of rc.File
   194  	sort.Sort(list)
   195  	return &zipFS{rc, list, name}
   196  }
   197  
   198  type zipList []*zip.File
   199  
   200  // zipList implements sort.Interface
   201  func (z zipList) Len() int           { return len(z) }
   202  func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
   203  func (z zipList) Swap(i, j int)      { z[i], z[j] = z[j], z[i] }
   204  
   205  // lookup returns the smallest index of an entry with an exact match
   206  // for name, or an inexact match starting with name/. If there is no
   207  // such entry, the result is -1, false.
   208  func (z zipList) lookup(name string) (index int, exact bool) {
   209  	// look for exact match first (name comes before name/ in z)
   210  	i := sort.Search(len(z), func(i int) bool {
   211  		return name <= z[i].Name
   212  	})
   213  	if i >= len(z) {
   214  		return -1, false
   215  	}
   216  	// 0 <= i < len(z)
   217  	if z[i].Name == name {
   218  		return i, true
   219  	}
   220  
   221  	// look for inexact match (must be in z[i:], if present)
   222  	z = z[i:]
   223  	name += "/"
   224  	j := sort.Search(len(z), func(i int) bool {
   225  		return name <= z[i].Name
   226  	})
   227  	if j >= len(z) {
   228  		return -1, false
   229  	}
   230  	// 0 <= j < len(z)
   231  	if strings.HasPrefix(z[j].Name, name) {
   232  		return i + j, false
   233  	}
   234  
   235  	return -1, false
   236  }