Extract Files seam, WebDAV, and Visit from CheckDNS.

Publish go-nc-files v1 with local/WebDAV Root, DefaultPathStore, and
neutral Working Folder language; depends on go-nc-exapp v0.1.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Konrad Neitzel
2026-08-26 19:16:53 +02:00
co-authored by Cursor
commit 9068d8ba8e
14 changed files with 1308 additions and 0 deletions
+191
View File
@@ -0,0 +1,191 @@
package goncfiles
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
// Sentinel errors for Visit / Working Folder resolution.
var (
ErrNotExist = errors.New("not found")
ErrForbidden = errors.New("forbidden")
ErrNotDir = errors.New("not a directory")
)
// Entry is a first-level name in a Working Folder.
type Entry struct {
Name string
IsDir bool
}
// Folder is flat storage for one Working Folder (basenames only).
type Folder interface {
List() ([]Entry, error)
Read(name string) ([]byte, error)
Write(name string, data []byte) error
Exists(name string) (bool, error)
}
// Root is User Files Root–relative folder access.
type Root interface {
// OpenFolder opens an existing folder at rel (User Files Root–relative).
OpenFolder(rel string) (Folder, error)
// EnsureFolder creates rel (and parents) if needed, then opens it.
EnsureFolder(rel string) (Folder, error)
}
// Local is a User Files Root on the local filesystem.
type Local struct {
RootDir string
}
// OpenFolder opens an existing directory under the local root.
func (l Local) OpenFolder(rel string) (Folder, error) {
dir, err := l.resolve(rel)
if err != nil {
return nil, err
}
info, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("%w: %s", ErrNotExist, rel)
}
if os.IsPermission(err) {
return nil, fmt.Errorf("%w: %s", ErrForbidden, rel)
}
return nil, err
}
if !info.IsDir() {
return nil, fmt.Errorf("%w: %s", ErrNotDir, rel)
}
return localFolder{dir: dir}, nil
}
// EnsureFolder creates the directory if missing, then opens it.
func (l Local) EnsureFolder(rel string) (Folder, error) {
dir, err := l.resolve(rel)
if err != nil {
return nil, err
}
if err := os.MkdirAll(dir, 0o755); err != nil {
if os.IsPermission(err) {
return nil, fmt.Errorf("%w: %s", ErrForbidden, rel)
}
return nil, err
}
return localFolder{dir: dir}, nil
}
func (l Local) resolve(rel string) (string, error) {
clean := CleanRel(rel)
if clean == "" {
return "", fmt.Errorf("invalid Working Folder path")
}
return filepath.Join(l.RootDir, filepath.FromSlash(clean)), nil
}
// CleanRel normalizes a User Files Root–relative path; empty means invalid.
// Absolute URLs and numeric file IDs are rejected.
func CleanRel(rel string) string {
rel = strings.TrimSpace(rel)
rel = strings.ReplaceAll(rel, "\\", "/")
lower := strings.ToLower(rel)
if strings.Contains(rel, "://") || strings.HasPrefix(lower, "http:") || strings.HasPrefix(lower, "https:") {
return ""
}
rel = strings.Trim(rel, "/")
if rel == "" || strings.Contains(rel, "..") {
return ""
}
// Bare numeric Nextcloud file IDs are not Working Folder paths.
if isAllDigits(rel) {
return ""
}
clean := path.Clean(rel)
if clean == "." || strings.HasPrefix(clean, "..") {
return ""
}
return clean
}
func isAllDigits(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r < '0' || r > '9' {
return false
}
}
return true
}
type localFolder struct {
dir string
}
// LocalFolder returns a Folder backed by an absolute local directory.
func LocalFolder(dir string) Folder {
return localFolder{dir: dir}
}
func (f localFolder) List() ([]Entry, error) {
entries, err := os.ReadDir(f.dir)
if err != nil {
return nil, err
}
out := make([]Entry, 0, len(entries))
for _, e := range entries {
out = append(out, Entry{Name: e.Name(), IsDir: e.IsDir()})
}
return out, nil
}
func (f localFolder) Read(name string) ([]byte, error) {
if err := RequireBasename(name); err != nil {
return nil, err
}
return os.ReadFile(filepath.Join(f.dir, name))
}
func (f localFolder) Write(name string, data []byte) error {
if err := RequireBasename(name); err != nil {
return err
}
return os.WriteFile(filepath.Join(f.dir, name), data, 0o644)
}
func (f localFolder) Exists(name string) (bool, error) {
if err := RequireBasename(name); err != nil {
return false, err
}
info, err := os.Stat(filepath.Join(f.dir, name))
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return !info.IsDir(), nil
}
// RequireBasename rejects path segments that are not a single basename.
func RequireBasename(name string) error {
if name == "" || name != path.Base(name) || strings.ContainsAny(name, `/\`) || strings.Contains(name, "..") {
return fmt.Errorf("name must be a basename in the Working Folder, not %q", name)
}
return nil
}
// LocalPath returns the absolute directory when f is a local folder.
func LocalPath(f Folder) (string, bool) {
lf, ok := f.(localFolder)
if !ok {
return "", false
}
return lf.dir, true
}