diff options
Diffstat (limited to 'src/cmd/godoc/httpzip.go')
-rw-r--r-- | src/cmd/godoc/httpzip.go | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/cmd/godoc/httpzip.go b/src/cmd/godoc/httpzip.go new file mode 100644 index 000000000..cb8322ee4 --- /dev/null +++ b/src/cmd/godoc/httpzip.go @@ -0,0 +1,181 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file provides an implementation of the http.FileSystem +// interface based on the contents of a .zip file. +// +// Assumptions: +// +// - The file paths stored in the zip file must use a slash ('/') as path +// separator; and they must be relative (i.e., they must not start with +// a '/' - this is usually the case if the file was created w/o special +// options). +// - The zip file system treats the file paths found in the zip internally +// like absolute paths w/o a leading '/'; i.e., the paths are considered +// relative to the root of the file system. +// - All path arguments to file system methods are considered relative to +// the root specified with NewHttpZipFS (even if the paths start with a '/'). + +// TODO(gri) Should define a commonly used FileSystem API that is the same +// for http and godoc. Then we only need one zip-file based file +// system implementation. + +package main + +import ( + "archive/zip" + "fmt" + "http" + "io" + "os" + "path" + "sort" + "strings" +) + +// We cannot import syscall on app engine. +// TODO(gri) Once we have a truly abstract FileInfo implementation +// this won't be needed anymore. +const ( + S_IFDIR = 0x4000 // == syscall.S_IFDIR + S_IFREG = 0x8000 // == syscall.S_IFREG +) + +// httpZipFile is the zip-file based implementation of http.File +type httpZipFile struct { + path string // absolute path within zip FS without leading '/' + info os.FileInfo + io.ReadCloser // nil for directory + list zipList +} + +func (f *httpZipFile) Close() os.Error { + if f.info.IsRegular() { + return f.ReadCloser.Close() + } + f.list = nil + return nil +} + +func (f *httpZipFile) Stat() (*os.FileInfo, os.Error) { + return &f.info, nil +} + +func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, os.Error) { + var list []os.FileInfo + dirname := f.path + "/" + prevname := "" + for i, e := range f.list { + if count == 0 { + f.list = f.list[i:] + break + } + if !strings.HasPrefix(e.Name, dirname) { + f.list = nil + break // not in the same directory anymore + } + name := e.Name[len(dirname):] // local name + var mode uint32 + var size, mtime_ns int64 + if i := strings.IndexRune(name, '/'); i >= 0 { + // We infer directories from files in subdirectories. + // If we have x/y, return a directory entry for x. + name = name[0:i] // keep local directory name only + mode = S_IFDIR + // no size or mtime_ns for directories + } else { + mode = S_IFREG + size = int64(e.UncompressedSize) + mtime_ns = e.Mtime_ns() + } + // If we have x/y and x/z, don't return two directory entries for x. + // TODO(gri): It should be possible to do this more efficiently + // by determining the (fs.list) range of local directory entries + // (via two binary searches). + if name != prevname { + list = append(list, os.FileInfo{ + Name: name, + Mode: mode, + Size: size, + Mtime_ns: mtime_ns, + }) + prevname = name + count-- + } + } + + if count >= 0 && len(list) == 0 { + return nil, os.EOF + } + + return list, nil +} + +func (f *httpZipFile) Seek(offset int64, whence int) (int64, os.Error) { + return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.info.Name) +} + +// httpZipFS is the zip-file based implementation of http.FileSystem +type httpZipFS struct { + *zip.ReadCloser + list zipList + root string +} + +func (fs *httpZipFS) Open(name string) (http.File, os.Error) { + // fs.root does not start with '/'. + path := path.Join(fs.root, name) // path is clean + index, exact := fs.list.lookup(path) + if index < 0 || !strings.HasPrefix(path, fs.root) { + // file not found or not under root + return nil, fmt.Errorf("file not found: %s", name) + } + + if exact { + // exact match found - must be a file + f := fs.list[index] + rc, err := f.Open() + if err != nil { + return nil, err + } + return &httpZipFile{ + path, + os.FileInfo{ + Name: name, + Mode: S_IFREG, + Size: int64(f.UncompressedSize), + Mtime_ns: f.Mtime_ns(), + }, + rc, + nil, + }, nil + } + + // not an exact match - must be a directory + return &httpZipFile{ + path, + os.FileInfo{ + Name: name, + Mode: S_IFDIR, + // no size or mtime_ns for directories + }, + nil, + fs.list[index:], + }, nil +} + +func (fs *httpZipFS) Close() os.Error { + fs.list = nil + return fs.ReadCloser.Close() +} + +// NewHttpZipFS creates a new http.FileSystem based on the contents of +// the zip file rc restricted to the directory tree specified by root; +// root must be an absolute path. +func NewHttpZipFS(rc *zip.ReadCloser, root string) http.FileSystem { + list := make(zipList, len(rc.File)) + copy(list, rc.File) // sort a copy of rc.File + sort.Sort(list) + return &httpZipFS{rc, list, zipPath(root)} +} |