summaryrefslogtreecommitdiff
path: root/src/cmd/godoc/httpzip.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/godoc/httpzip.go')
-rw-r--r--src/cmd/godoc/httpzip.go181
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)}
+}