mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
67 lines
1.6 KiB
Go
67 lines
1.6 KiB
Go
package pathutil
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// NormalizeDir returns a clean absolute directory path.
|
|
func NormalizeDir(dir string) (string, error) {
|
|
dir = strings.TrimSpace(dir)
|
|
if dir == "" {
|
|
return "", fmt.Errorf("directory path cannot be empty")
|
|
}
|
|
|
|
abs, err := filepath.Abs(filepath.Clean(dir))
|
|
if err != nil {
|
|
return "", fmt.Errorf("normalize directory path: %w", err)
|
|
}
|
|
return abs, nil
|
|
}
|
|
|
|
// JoinBaseFile safely joins a trusted root with a single basename child.
|
|
func JoinBaseFile(root, name string) (string, error) {
|
|
if strings.TrimSpace(name) == "" {
|
|
return "", fmt.Errorf("file name cannot be empty")
|
|
}
|
|
if filepath.Base(name) != name || name == "." || name == ".." || strings.ContainsRune(name, 0) {
|
|
return "", fmt.Errorf("unsafe file name %q", name)
|
|
}
|
|
|
|
joined := filepath.Join(root, name)
|
|
rel, err := filepath.Rel(root, joined)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve child path: %w", err)
|
|
}
|
|
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
return "", fmt.Errorf("path escapes root")
|
|
}
|
|
return joined, nil
|
|
}
|
|
|
|
// LegacySafeFilename derives a filesystem-safe legacy filename.
|
|
func LegacySafeFilename(id, suffix string) (string, bool) {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" || strings.ContainsRune(id, 0) {
|
|
return "", false
|
|
}
|
|
|
|
var b strings.Builder
|
|
for _, r := range id {
|
|
switch {
|
|
case unicode.IsLetter(r), unicode.IsDigit(r), r == '-', r == '_', r == '.':
|
|
b.WriteRune(r)
|
|
default:
|
|
b.WriteByte('_')
|
|
}
|
|
}
|
|
|
|
name := strings.Trim(b.String(), "._")
|
|
if name == "" || name == "." || name == ".." {
|
|
return "", false
|
|
}
|
|
|
|
return name + suffix, true
|
|
}
|