| package web |
|
|
| import ( |
| "fmt" |
| "path/filepath" |
| "strings" |
| ) |
|
|
| |
| |
| |
| |
| |
| func SafePath(base, userPath string) (string, error) { |
| absBase, err := filepath.Abs(base) |
| if err != nil { |
| return "", fmt.Errorf("invalid base path: %w", err) |
| } |
|
|
| |
| if userPath == "" || userPath == "." { |
| return absBase, nil |
| } |
|
|
| |
| if filepath.IsAbs(userPath) || strings.HasPrefix(userPath, "/") || strings.HasPrefix(userPath, string(filepath.Separator)) { |
| return "", fmt.Errorf("absolute paths not allowed: %q", userPath) |
| } |
|
|
| |
| |
| if !filepath.IsLocal(userPath) { |
| return "", fmt.Errorf("path %q contains invalid components", userPath) |
| } |
|
|
| |
| joined := filepath.Join(absBase, userPath) |
| absPath, err := filepath.Abs(joined) |
| if err != nil { |
| return "", fmt.Errorf("invalid resolved path: %w", err) |
| } |
|
|
| |
| if !strings.HasPrefix(absPath, absBase+string(filepath.Separator)) && absPath != absBase { |
| return "", fmt.Errorf("path %q escapes base directory %q", userPath, absBase) |
| } |
|
|
| return absPath, nil |
| } |
|
|