package services import ( "context" "fmt" "io" "os" "path/filepath" "strings" ) type localStorageBackend struct { id string root string } func (b localStorageBackend) ID() string { return b.id } func (b localStorageBackend) Type() string { return StorageBackendLocal } func (b localStorageBackend) Put(_ context.Context, key string, body io.Reader, _ int64, _ string) error { path, err := b.path(key) if err != nil { return err } if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } target, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { return err } defer target.Close() _, err = io.Copy(target, body) return err } func (b localStorageBackend) Get(_ context.Context, key string) (StorageObject, error) { path, err := b.path(key) if err != nil { return StorageObject{}, err } source, err := os.Open(path) if err != nil { return StorageObject{}, err } stat, err := source.Stat() if err != nil { source.Close() return StorageObject{}, err } return StorageObject{Key: key, Size: stat.Size(), ModTime: stat.ModTime(), Body: source}, nil } func (b localStorageBackend) Delete(_ context.Context, key string) error { path, err := b.path(key) if err != nil { return err } if err := os.Remove(path); err != nil && !os.IsNotExist(err) { return err } return nil } func (b localStorageBackend) DeletePrefix(_ context.Context, prefix string) error { path, err := b.path(prefix) if err != nil { return err } if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) { return err } return nil } func (b localStorageBackend) Usage(_ context.Context) (int64, error) { var total int64 err := filepath.WalkDir(b.root, func(path string, entry os.DirEntry, err error) error { if err != nil { return err } if entry.IsDir() { return nil } info, err := entry.Info() if err != nil { return err } total += info.Size() return nil }) if os.IsNotExist(err) { return 0, nil } return total, err } func (b localStorageBackend) Test(ctx context.Context) error { key := ".warpbox-storage-test-" + randomID(6) if err := b.Put(ctx, key, strings.NewReader("ok"), 2, "text/plain"); err != nil { return err } return b.Delete(ctx, key) } func (b localStorageBackend) path(key string) (string, error) { key = filepath.Clean(strings.TrimPrefix(key, "/")) if key == "." || strings.HasPrefix(key, "..") || filepath.IsAbs(key) { return "", fmt.Errorf("invalid storage key") } path := filepath.Join(b.root, key) root, err := filepath.Abs(b.root) if err != nil { return "", err } abs, err := filepath.Abs(path) if err != nil { return "", err } if abs != root && !strings.HasPrefix(abs, root+string(os.PathSeparator)) { return "", fmt.Errorf("invalid storage key") } return abs, nil }