mirror of
https://github.com/golang/go
synced 2024-10-05 18:31:28 -06:00
dcef981217
Fixes a problem where Readdir would always return empty directories (Readdir is only called by godoc if the usual directory handler is commented out in godoc.go, and if a zip file system is provided; thus, this bug never manifested itself in godoc). Also: - better choice of variable/field names - simplified error handling a bit - better comments R=bradfitz CC=golang-dev https://golang.org/cl/4813047
182 lines
4.7 KiB
Go
182 lines
4.7 KiB
Go
// 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)}
|
|
}
|