74 lines
1.9 KiB
Go
74 lines
1.9 KiB
Go
package safepaths
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Absolute must be constructed via ParseAbsolute, or other methods in this package.
|
|
// The zero value of Absolute will panic when String is called.
|
|
type Absolute struct {
|
|
path string
|
|
}
|
|
|
|
// ParseAbsolute takes a string path that may be relative and returns
|
|
// an Absolute that is guaranteed to be absolute, or an error.
|
|
func ParseAbsolute(path string) (Absolute, error) {
|
|
path, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return Absolute{}, fmt.Errorf("failed to get absolute path: %w", err)
|
|
}
|
|
|
|
return Absolute{path: path}, nil
|
|
}
|
|
|
|
// String returns a string representation of the absolute path, or panics
|
|
// if the absolute path is empty. This guards against programmer error.
|
|
func (a Absolute) String() string {
|
|
if a.path == "" {
|
|
panic("empty absolute path")
|
|
}
|
|
return a.path
|
|
}
|
|
|
|
// Join an absolute path with elements to create a new Absolute path, or error.
|
|
// A PathTraversalError will be returned if the joined path would traverse outside of
|
|
// the base Absolute path. Note that this does not handle symlinks.
|
|
func (a Absolute) Join(elem ...string) (Absolute, error) {
|
|
joinedAbsolutePath, err := ParseAbsolute(filepath.Join(append([]string{a.path}, elem...)...))
|
|
if err != nil {
|
|
return Absolute{}, fmt.Errorf("failed to parse joined path: %w", err)
|
|
}
|
|
|
|
isSubpath, err := joinedAbsolutePath.isSubpathOf(a)
|
|
if err != nil {
|
|
return Absolute{}, err
|
|
}
|
|
|
|
if !isSubpath {
|
|
return Absolute{}, PathTraversalError{
|
|
Base: a,
|
|
Elems: elem,
|
|
}
|
|
}
|
|
|
|
return joinedAbsolutePath, nil
|
|
}
|
|
|
|
func (a Absolute) isSubpathOf(dir Absolute) (bool, error) {
|
|
relativePath, err := filepath.Rel(dir.path, a.path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return !strings.HasPrefix(relativePath, ".."), nil
|
|
}
|
|
|
|
type PathTraversalError struct {
|
|
Base Absolute
|
|
Elems []string
|
|
}
|
|
|
|
func (e PathTraversalError) Error() string {
|
|
return fmt.Sprintf("joining %s and %s would be a traversal", e.Base, filepath.Join(e.Elems...))
|
|
}
|