vfscopy/vfs.go

137 lines
3.3 KiB
Go
Raw Normal View History

2020-10-23 19:55:37 +00:00
package vfscopy
import (
"os"
"io"
"path/filepath"
"path"
"strings"
"errors"
)
2020-10-23 20:29:09 +00:00
// HTTPFileSystem is copied from http.FileSystem
type HTTPFileSystem interface {
Open(name string) (File, error)
}
// FileSystem is a Virtual FileSystem with Symlink support
2020-10-23 19:55:37 +00:00
type FileSystem interface {
Open(name string) (File, error)
2020-10-23 20:29:09 +00:00
Readlink(name string) (string, error)
EvalSymlinks(name string) (string, error)
2020-10-23 19:55:37 +00:00
}
// File is copied from http.File
type File interface {
io.Closer
io.Reader
io.Seeker
Readdir(count int) ([]os.FileInfo, error)
Stat() (os.FileInfo, error)
}
2020-10-23 20:29:09 +00:00
// VFS is a virtual FileSystem with possible Symlink support
type VFS struct {
FileSystem HTTPFileSystem
}
// Open opens a file relative to a virtual filesystem
func (v *VFS) Open(name string) (File, error) {
return v.Open(name)
}
// Readlink returns a "not implemented" error,
// which is okay because it is never called for http.FileSystem.
func (v *VFS) Readlink(name string) (string, error) {
return "", errors.New("not implemented")
}
// EvalSymlinks returns the original name,
// which is okay because it is never called for http.FileSystem.
func (v *VFS) EvalSymlinks(name string) (string, error) {
return name, nil
}
// NewVFS gives an http.FileSystem faux symlink support
func NewVFS(httpfs HTTPFileSystem) FileSystem {
return &VFS{ FileSystem: httpfs }
}
2020-10-23 19:55:37 +00:00
// Dir is an implementation of a Virtual FileSystem
type Dir string
// mapDirOpenError maps the provided non-nil error from opening name
// to a possibly better non-nil error. In particular, it turns OS-specific errors
// about opening files in non-directories into os.ErrNotExist. See Issue 18984.
func mapDirOpenError(originalErr error, name string) error {
if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
return originalErr
}
parts := strings.Split(name, string(filepath.Separator))
for i := range parts {
if parts[i] == "" {
continue
}
fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
if err != nil {
return originalErr
}
if !fi.IsDir() {
return os.ErrNotExist
}
}
return originalErr
}
2020-10-23 20:29:09 +00:00
func (d Dir) fullName(name string) (string, error) {
2020-10-23 19:55:37 +00:00
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
2020-10-23 20:29:09 +00:00
return "", errors.New("http: invalid character in file path")
2020-10-23 19:55:37 +00:00
}
dir := string(d)
if dir == "" {
dir = "."
}
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
2020-10-23 20:29:09 +00:00
return fullName, nil
}
// Open opens a file relative to a virtual filesystem
func (d Dir) Open(name string) (File, error) {
fullName, err := d.fullName(name)
if nil != err {
return nil, err
}
2020-10-23 19:55:37 +00:00
f, err := os.Open(fullName)
if err != nil {
return nil, mapDirOpenError(err, fullName)
}
return f, nil
}
2020-10-23 20:29:09 +00:00
// Readlink returns the destination of the named symbolic link.
func (d Dir) Readlink(name string) (string, error) {
name, err := d.fullName(name)
if nil != err {
return "", err
}
name, err = os.Readlink(name)
if err != nil {
return "", mapDirOpenError(err, name)
}
return name, nil
}
// EvalSymlinks returns the destination of the named symbolic link.
func (d Dir) EvalSymlinks(name string) (string, error) {
name, err := d.fullName(name)
if nil != err {
return "", err
}
name, err = filepath.EvalSymlinks(name)
if err != nil {
return "", mapDirOpenError(err, name)
}
return name, nil
}