Initial commit with project setup and basic structure established.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/config"
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/models"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidPath = errors.New("path must be an absolute accessible Windows path")
|
||||
ErrOutsideRoot = errors.New("path is outside AGENT_FILE_ROOT")
|
||||
)
|
||||
|
||||
func AllowedPath(root, raw string, requireExisting bool) (string, error) {
|
||||
if strings.TrimSpace(raw) == "" || !filepath.IsAbs(raw) {
|
||||
return "", ErrInvalidPath
|
||||
}
|
||||
clean := filepath.Clean(raw)
|
||||
if requireExisting {
|
||||
resolved, err := CanonicalExistingPath(clean)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
clean = resolved
|
||||
}
|
||||
if root != "" && !isWithin(root, clean) {
|
||||
return "", ErrOutsideRoot
|
||||
}
|
||||
return clean, nil
|
||||
}
|
||||
|
||||
func CanonicalExistingPath(path string) (string, error) {
|
||||
abs, err := filepath.Abs(filepath.Clean(path))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(abs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolved, err := filepath.EvalSymlinks(abs)
|
||||
if err == nil {
|
||||
return filepath.Abs(resolved)
|
||||
}
|
||||
return abs, nil
|
||||
}
|
||||
|
||||
func ParseDepth(raw string) (int, error) {
|
||||
if raw == "" {
|
||||
return 0, nil
|
||||
}
|
||||
depth, err := strconv.Atoi(raw)
|
||||
if err != nil || (depth != 0 && depth != 1) {
|
||||
return 0, errors.New("depth must be 0 or 1")
|
||||
}
|
||||
return depth, nil
|
||||
}
|
||||
|
||||
func ListDirectory(dir string, depth int) ([]models.FileItem, error) {
|
||||
result := make([]models.FileItem, 0)
|
||||
var walk func(string, string, int) error
|
||||
walk = func(current, relative string, remaining int) error {
|
||||
entries, err := os.ReadDir(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if len(result) >= config.MaxListEntries {
|
||||
return errors.New("directory listing exceeds entry limit")
|
||||
}
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entryPath := filepath.Join(current, entry.Name())
|
||||
rel := filepath.Join(relative, entry.Name())
|
||||
kind := "file"
|
||||
if info.IsDir() {
|
||||
kind = "dir"
|
||||
}
|
||||
result = append(result, models.FileItem{
|
||||
Name: entry.Name(),
|
||||
Path: rel,
|
||||
Type: kind,
|
||||
Size: info.Size(),
|
||||
Modified: info.ModTime().UTC(),
|
||||
})
|
||||
if remaining > 0 && info.IsDir() && entry.Type()&os.ModeSymlink == 0 {
|
||||
if err := walk(entryPath, rel, remaining-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := walk(dir, "", depth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return strings.ToLower(result[i].Path) < strings.ToLower(result[j].Path)
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func WritePathError(w http.ResponseWriter, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrInvalidPath):
|
||||
helpers.WriteError(w, http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, ErrOutsideRoot), errors.Is(err, os.ErrPermission):
|
||||
helpers.WriteError(w, http.StatusForbidden, "path is not accessible")
|
||||
case errors.Is(err, os.ErrNotExist):
|
||||
helpers.WriteError(w, http.StatusNotFound, "path does not exist")
|
||||
default:
|
||||
helpers.WriteError(w, http.StatusBadRequest, "path is not accessible")
|
||||
}
|
||||
}
|
||||
|
||||
func isWithin(root, candidate string) bool {
|
||||
rel, err := filepath.Rel(root, candidate)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) && !filepath.IsAbs(rel)
|
||||
}
|
||||
Reference in New Issue
Block a user