package adapters import ( "errors" "os" "path/filepath" "strings" "time" ) type LocalFsAdapter struct { roots []string } func (l LocalFsAdapter) readDir(path string, root string) (FsResponse, error) { dirInfo, err := os.Stat(path) if err != nil { return FsResponse{}, err } entries, err := os.ReadDir(path) if err != nil { return FsResponse{}, err } out := make([]FsDirEntry, 0, len(entries)) for _, e := range entries { info, _ := e.Info() entryType := "dir" if !e.IsDir() { entryType = contentTypeFor(e.Name()) } out = append(out, FsDirEntry{ Name: e.Name(), Type: entryType, IsDir: e.IsDir(), ModTime: info.ModTime(), }) } relPath, err := filepath.Rel(root, path) if err != nil { return FsResponse{}, err } if relPath == "." { relPath = "" } responsePath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), relPath)) return FsResponse{ Dir: &FsDir{ Files: out, Name: responsePath, ModTime: dirInfo.ModTime(), }, }, nil } func (l LocalFsAdapter) readFile(path string, root string) (FsResponse, error) { fi, err := os.Stat(path) if err != nil { return FsResponse{}, err } data, err := os.ReadFile(path) if err != nil { return FsResponse{}, err } relPath, err := filepath.Rel(root, path) if err != nil { return FsResponse{}, err } responsePath := "/" + filepath.ToSlash(filepath.Join(filepath.Base(root), relPath)) return FsResponse{ File: &FsFile{ Name: responsePath, Type: contentTypeFor(path), ModTime: fi.ModTime(), Content: data, }, }, nil } func (l LocalFsAdapter) Read(path string) (FsResponse, error) { if path == "/" { entries := make([]FsDirEntry, 0, len(l.roots)) var latestModTime time.Time for _, r := range l.roots { info, err := os.Stat(r) if err != nil { continue } entries = append(entries, FsDirEntry{ Name: filepath.Base(r), Type: "dir", IsDir: true, ModTime: info.ModTime(), }) if info.ModTime().After(latestModTime) { latestModTime = info.ModTime() } } return FsResponse{ Dir: &FsDir{ Files: entries, Name: "/", ModTime: latestModTime, }, }, nil } pathParts := strings.Split(strings.Trim(path, "/"), "/") if len(pathParts) == 0 || pathParts[0] == "" { return FsResponse{}, ErrNotFound } rootIdentifier := pathParts[0] var targetRoot string for _, r := range l.roots { if filepath.Base(r) == rootIdentifier { targetRoot = r break } } if targetRoot == "" { return FsResponse{}, ErrNotFound } subPath := filepath.Join(pathParts[1:]...) target := filepath.Join(targetRoot, subPath) absTarget, err := filepath.Abs(target) if err != nil { return FsResponse{}, err } absRoot, err := filepath.Abs(targetRoot) if err != nil { return FsResponse{}, err } if !strings.HasPrefix(absTarget, absRoot) { return FsResponse{}, errors.New("path escapes root") } fi, err := os.Stat(target) if err != nil { if os.IsNotExist(err) { return FsResponse{}, ErrNotFound } return FsResponse{}, err } if fi.IsDir() { return l.readDir(target, targetRoot) } return l.readFile(target, targetRoot) } func (LocalFsAdapter) Write(path string, content []byte) error { return nil } func NewLocalFsAdapter(roots []string) (FileAdapter, error) { return LocalFsAdapter{ roots: roots, }, nil }