61 lines
1.7 KiB
Go
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
|
|
}
|