package http import ( "io/fs" "net/http" "os" "path" "strings" "time" ) type ( FS struct { fs http.FileSystem modtime time.Time prefix string indexFile string denyDirectory bool } File struct { fp http.File modtime time.Time } FileInfo struct { name string size int64 mode fs.FileMode isDir bool modtime time.Time } ) func (fi *FileInfo) Name() string { return fi.name } func (fi *FileInfo) Size() int64 { return fi.size } func (fi *FileInfo) Mode() fs.FileMode { return fi.mode } func (fi *FileInfo) ModTime() time.Time { return fi.modtime } func (fi *FileInfo) IsDir() bool { return fi.isDir } func (fi *FileInfo) Sys() any { return nil } func (file *File) Close() error { return file.fp.Close() } func (file *File) Read(p []byte) (n int, err error) { return file.fp.Read(p) } func (file *File) Seek(offset int64, whence int) (int64, error) { return file.fp.Seek(offset, whence) } func (file *File) Readdir(count int) ([]fs.FileInfo, error) { return file.fp.Readdir(count) } func (file *File) Stat() (fs.FileInfo, error) { fi, err := file.fp.Stat() if err != nil { return nil, err } return newFileInfo(fi, file.modtime), nil } func (fs *FS) DenyAccessDirectory() { fs.denyDirectory = true } func (fs *FS) SetPrefix(prefix string) { if prefix != "" { if prefix[0] != '/' { prefix = "/" + prefix } prefix = strings.TrimRight(prefix, "/") fs.prefix = prefix } } func (fs *FS) SetIndexFile(indexFile string) { fs.indexFile = indexFile } func (fs *FS) Open(name string) (http.File, error) { var ( needRetry bool ) if name == "" || name == "/" { needRetry = true } if fs.prefix != "" { if !strings.HasPrefix(name, fs.prefix) { name = path.Join(fs.prefix, name) } } fp, err := fs.fs.Open(name) if err != nil { return nil, err } if fs.denyDirectory { state, err := fp.Stat() if err != nil { return nil, err } if state.IsDir() { if needRetry { if fs.indexFile != "" { return fs.Open(path.Join(name, fs.indexFile)) } } return nil, os.ErrPermission } } return &File{fp: fp, modtime: fs.modtime}, nil } func newFS(modtime time.Time, fs http.FileSystem) *FS { return &FS{ fs: fs, modtime: modtime, } } func newFileInfo(fi fs.FileInfo, modtime time.Time) *FileInfo { return &FileInfo{ name: fi.Name(), size: fi.Size(), mode: fi.Mode(), isDir: fi.IsDir(), modtime: modtime, } }