133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package engine
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ResourceContext is a passive copy of one acquired source's rooted filesystem
|
|
// and slash-relative base directory. FS is borrowed and opens resources only
|
|
// when the application explicitly requests them.
|
|
type ResourceContext struct {
|
|
FS fs.FS
|
|
BaseDir string
|
|
}
|
|
|
|
func cloneResourceContexts(contexts map[string]ResourceContext) map[string]ResourceContext {
|
|
if contexts == nil {
|
|
return nil
|
|
}
|
|
cloned := make(map[string]ResourceContext, len(contexts))
|
|
for id, context := range contexts {
|
|
cloned[id] = context
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
// rootedNativeFS borrows a namespace without invoking SubFS or opening it at
|
|
// query time. All resource names remain relative to its configured root.
|
|
type rootedNativeFS struct {
|
|
filesystem fs.FS
|
|
root string
|
|
}
|
|
|
|
func (r rootedNativeFS) name(name string) (string, error) {
|
|
if !fs.ValidPath(name) || strings.Contains(name, "\\") {
|
|
return "", &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
|
}
|
|
joined := path.Join(r.root, name)
|
|
if !fs.ValidPath(joined) || r.root != "." && joined != r.root && !strings.HasPrefix(joined, r.root+"/") {
|
|
return "", &fs.PathError{Op: "open", Path: name, Err: fs.ErrPermission}
|
|
}
|
|
return joined, nil
|
|
}
|
|
|
|
func (r rootedNativeFS) Open(name string) (fs.File, error) {
|
|
joined, err := r.name(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.filesystem.Open(joined)
|
|
}
|
|
|
|
func (r rootedNativeFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
|
joined, err := r.name(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fs.ReadDir(r.filesystem, joined)
|
|
}
|
|
|
|
// rootedHostFS stores only a canonical root path. No os.Root is retained after
|
|
// loading or created by a SourceContext query.
|
|
type hostResourceRoot interface {
|
|
Open(string) (*os.File, error)
|
|
Close() error
|
|
}
|
|
|
|
type rootedHostFS struct {
|
|
root string
|
|
openRoot func(string) (hostResourceRoot, error)
|
|
}
|
|
|
|
func (r rootedHostFS) Open(name string) (fs.File, error) {
|
|
if !fs.ValidPath(name) || strings.Contains(name, "\\") {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
|
}
|
|
opener := r.openRoot
|
|
if opener == nil {
|
|
opener = func(name string) (hostResourceRoot, error) { return os.OpenRoot(name) }
|
|
}
|
|
root, err := opener(r.root)
|
|
if err != nil {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
canonical, err := canonicalExistingPath(filepath.Join(r.root, filepath.FromSlash(name)))
|
|
if err != nil {
|
|
_ = root.Close()
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
if !containedPath(r.root, canonical) {
|
|
_ = root.Close()
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrPermission}
|
|
}
|
|
relative, err := filepath.Rel(r.root, canonical)
|
|
if err != nil {
|
|
_ = root.Close()
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
file, err := root.Open(relative)
|
|
if err != nil {
|
|
_ = root.Close()
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
owned := rootedHostFile{File: file, root: root}
|
|
var opened fs.File = file
|
|
if directory, ok := opened.(fs.ReadDirFile); ok {
|
|
return &rootedHostDirFile{rootedHostFile: owned, directory: directory}, nil
|
|
}
|
|
return &owned, nil
|
|
}
|
|
|
|
type rootedHostFile struct {
|
|
fs.File
|
|
root hostResourceRoot
|
|
}
|
|
|
|
func (f *rootedHostFile) Close() error {
|
|
return errors.Join(f.File.Close(), f.root.Close())
|
|
}
|
|
|
|
type rootedHostDirFile struct {
|
|
rootedHostFile
|
|
directory fs.ReadDirFile
|
|
}
|
|
|
|
func (f *rootedHostDirFile) ReadDir(count int) ([]fs.DirEntry, error) {
|
|
return f.directory.ReadDir(count)
|
|
}
|