96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package tools
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func resolvePath(baseDir, path string) (string, error) {
|
|
resolved, canonicalBase, err := resolveLexicalPath(baseDir, path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
canonical, err := filepath.EvalSymlinks(resolved)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !isWithinDir(canonical, canonicalBase) {
|
|
return "", fmt.Errorf("path escapes the project directory through a symbolic link: %s", path)
|
|
}
|
|
return canonical, nil
|
|
}
|
|
|
|
// resolvePathForWrite resolves a path that may not exist yet while preventing
|
|
// existing parent-directory symlinks from escaping the project directory.
|
|
func resolvePathForWrite(baseDir, path string) (string, error) {
|
|
resolved, canonicalBase, err := resolveLexicalPath(baseDir, path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
existing := resolved
|
|
var suffix []string
|
|
for {
|
|
_, err := os.Lstat(existing)
|
|
if err == nil {
|
|
break
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
return "", err
|
|
}
|
|
parent := filepath.Dir(existing)
|
|
if parent == existing {
|
|
return "", err
|
|
}
|
|
suffix = append(suffix, filepath.Base(existing))
|
|
existing = parent
|
|
}
|
|
|
|
canonical, err := filepath.EvalSymlinks(existing)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for i := len(suffix) - 1; i >= 0; i-- {
|
|
canonical = filepath.Join(canonical, suffix[i])
|
|
}
|
|
if !isWithinDir(canonical, canonicalBase) {
|
|
return "", fmt.Errorf("path escapes the project directory through a symbolic link: %s", path)
|
|
}
|
|
return canonical, nil
|
|
}
|
|
|
|
func resolveLexicalPath(baseDir, path string) (string, string, error) {
|
|
if path == "" {
|
|
path = "."
|
|
}
|
|
if filepath.IsAbs(path) {
|
|
return "", "", fmt.Errorf("absolute paths are not allowed; use a path relative to the project directory")
|
|
}
|
|
absBase, err := filepath.Abs(baseDir)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
canonicalBase, err := filepath.EvalSymlinks(absBase)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
resolved := filepath.Clean(filepath.Join(absBase, path))
|
|
if !isWithinDir(resolved, absBase) {
|
|
return "", "", fmt.Errorf("path escapes the project directory: %s", path)
|
|
}
|
|
return resolved, canonicalBase, nil
|
|
}
|
|
|
|
func isWithinDir(path, dir string) bool {
|
|
rel, err := filepath.Rel(dir, path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|