yao/service/fs/fs.go

120 lines
3.1 KiB
Go

package fs
import (
"errors"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/yaoapp/gou/application"
)
// Dir http root path
type Dir string
// DirPWA is the PWA path
type DirPWA string
// Open implements FileSystem using os.Open, opening files for reading rooted
// and relative to the directory d.
func (d Dir) Open(name string) (http.File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
dir := string(d)
if dir == "" {
dir = "."
}
name = filepath.FromSlash(path.Clean("/" + name))
relName := filepath.Join(dir, name)
// Close dir views Disable directory listing
absName := filepath.Join(application.App.Root(), relName)
stat, err := os.Stat(absName)
if err != nil {
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
}
if stat.IsDir() {
if _, err := os.Stat(filepath.Join(absName, "index.html")); os.IsNotExist(err) {
return nil, mapOpenError(fs.ErrNotExist, relName, filepath.Separator, os.Stat)
}
}
f, err := application.App.FS(string(d)).Open(name)
if err != nil {
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
}
return f, nil
}
// Open implements FileSystem using os.Open, opening files for reading rooted
// and relative to the directory d.
func (d DirPWA) Open(name string) (http.File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
dir := string(d)
if dir == "" {
dir = "."
}
name = filepath.FromSlash(path.Clean("/" + name))
relName := filepath.Join(dir, name)
if filepath.Ext(relName) == "" && relName != dir {
relName = filepath.Join(dir, "index.html")
name = filepath.Join(string(os.PathSeparator), "index.html")
}
// Close dir views Disable directory listing
absName := filepath.Join(application.App.Root(), relName)
stat, err := os.Stat(absName)
if err != nil {
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
}
if stat.IsDir() {
if _, err := os.Stat(filepath.Join(absName, "index.html")); os.IsNotExist(err) {
return nil, mapOpenError(fs.ErrNotExist, relName, filepath.Separator, os.Stat)
}
}
f, err := application.App.FS(string(d)).Open(name)
if err != nil {
return nil, mapOpenError(err, relName, filepath.Separator, os.Stat)
}
return f, nil
}
// mapOpenError 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 fs.ErrNotExist. See Issues 18984 and 49552.
func mapOpenError(originalErr error, name string, sep rune, stat func(string) (fs.FileInfo, error)) error {
if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
return originalErr
}
parts := strings.Split(name, string(sep))
for i := range parts {
if parts[i] == "" {
continue
}
fi, err := stat(strings.Join(parts[:i+1], string(sep)))
if err != nil {
return originalErr
}
if !fi.IsDir() {
return fs.ErrNotExist
}
}
return originalErr
}