Files
soft_quay/core/downloader/paths.go
T

61 lines
1.7 KiB
Go

package downloader
import (
"fmt"
"path/filepath"
"strings"
)
// TaskPaths are fixed local names derived only from RequestID.
type TaskPaths struct {
Root string
TasksDir string
FilesDir string
Metadata string
Backup string
Part string
Completed string
}
// DeriveTaskPaths derives all local paths without accepting remote filenames
// or Content-Disposition values.
func DeriveTaskPaths(downloadsRoot, requestID string) (TaskPaths, error) {
if downloadsRoot == "" {
return TaskPaths{}, fmt.Errorf("%w: empty downloads root", ErrInvalidTask)
}
if !ValidRequestID(requestID) {
return TaskPaths{}, fmt.Errorf("%w: invalid request_id", ErrInvalidTask)
}
absoluteRoot, err := filepath.Abs(downloadsRoot)
if err != nil {
return TaskPaths{}, fmt.Errorf("%w: resolve downloads root", ErrInvalidTask)
}
tasksDir := filepath.Join(absoluteRoot, "tasks")
filesDir := filepath.Join(absoluteRoot, "files")
paths := TaskPaths{
Root: absoluteRoot,
TasksDir: tasksDir,
FilesDir: filesDir,
Metadata: filepath.Join(tasksDir, requestID+".json"),
Backup: filepath.Join(tasksDir, requestID+".json.backup"),
Part: filepath.Join(filesDir, requestID+".part"),
Completed: filepath.Join(filesDir, requestID+".download"),
}
for _, candidate := range []string{
paths.TasksDir,
paths.FilesDir,
paths.Metadata,
paths.Backup,
paths.Part,
paths.Completed,
} {
relative, relativeErr := filepath.Rel(absoluteRoot, candidate)
if relativeErr != nil ||
relative == ".." ||
strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return TaskPaths{}, fmt.Errorf("%w: derived path escapes root", ErrInvalidTask)
}
}
return paths, nil
}