|
|
|
@ -22,7 +22,7 @@ func newPath(root, rel string) *Path {
|
|
|
|
|
|
|
|
|
|
// NewSanitizedPathAtRoot returns a new sanitized Path structure based on root and relative path
|
|
|
|
|
func NewSanitizedPathAtRoot(root, rel string) *Path {
|
|
|
|
|
return newPath(root, sanitizeRawPath(rel))
|
|
|
|
|
return newPath(root, sanitizePath(rel))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// buildPathuserSpacesEnabled will attempt to parse a username, and return a sanitized Path at username's
|
|
|
|
@ -34,7 +34,7 @@ func buildPathUserSpacesEnabled(rawPath string) *Path {
|
|
|
|
|
|
|
|
|
|
// Treat username as a raw path, sanitizing to check for
|
|
|
|
|
// dir traversals
|
|
|
|
|
username = sanitizeRawPath(username)
|
|
|
|
|
username = sanitizePath(username)
|
|
|
|
|
|
|
|
|
|
// Return sanitized path using user home dir as root
|
|
|
|
|
return NewSanitizedPathAtRoot("/home/"+username+"/public_"+protocol, rawPath)
|
|
|
|
@ -51,7 +51,7 @@ func buildPathUserSpacesDisabled(rawPath string) *Path {
|
|
|
|
|
|
|
|
|
|
// Remap remaps a Path to a new relative path, keeping previous selector
|
|
|
|
|
func (p *Path) Remap(newRel string) {
|
|
|
|
|
p.rel = sanitizeRawPath(newRel)
|
|
|
|
|
p.rel = sanitizePath(newRel)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RemapDirect remaps a Path to a new absolute path, keeping previous selector
|
|
|
|
@ -97,58 +97,223 @@ func (p *Path) Dir() *Path {
|
|
|
|
|
|
|
|
|
|
// JoinRelative returns a string appended to the current relative path
|
|
|
|
|
func (p *Path) JoinRelative(newRel string) string {
|
|
|
|
|
return joinSanitizedPaths(p.rel, sanitizeRawPath(newRel))
|
|
|
|
|
return joinSanitizedPaths(p.rel, sanitizePath(newRel))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// JoinPathUnsafe appends the supplied string to the Path's relative and selector paths. This is unsafe because
|
|
|
|
|
// if the toJoin is a back-traversal then you can escape root
|
|
|
|
|
func (p *Path) JoinPathUnsafe(toJoin string) *Path {
|
|
|
|
|
toJoin = sanitizeRawPath(toJoin)
|
|
|
|
|
toJoin = sanitizePath(toJoin)
|
|
|
|
|
return &Path{p.root, joinSanitizedPaths(p.rel, toJoin), joinSanitizedPaths(p.sel, toJoin)}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// formatSelector formats a relative path to a valid selector path
|
|
|
|
|
func formatSelector(rel string) string {
|
|
|
|
|
if len(rel) > 0 && rel[0] == '/' {
|
|
|
|
|
return rel
|
|
|
|
|
}
|
|
|
|
|
return "/" + rel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// joinSanitizedPaths quickly joins two sanitized paths, whether absolute or relative
|
|
|
|
|
func joinSanitizedPaths(start, end string) string {
|
|
|
|
|
end = strings.TrimPrefix(end, "/")
|
|
|
|
|
// Format the end string
|
|
|
|
|
endLen := len(end)
|
|
|
|
|
switch endLen {
|
|
|
|
|
case 0:
|
|
|
|
|
// do nothing
|
|
|
|
|
case 1:
|
|
|
|
|
// If this is '/', trim!
|
|
|
|
|
if end[0] == '/' {
|
|
|
|
|
end = ""
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
// Trim any leading '/'
|
|
|
|
|
if end[0] == '/' {
|
|
|
|
|
end = end[1:]
|
|
|
|
|
}
|
|
|
|
|
endLen--
|
|
|
|
|
|
|
|
|
|
// Trim any trailing '/'
|
|
|
|
|
if end[endLen-1] == '/' {
|
|
|
|
|
end = end[:endLen-1]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Format the start string and return
|
|
|
|
|
switch start {
|
|
|
|
|
case "":
|
|
|
|
|
return end
|
|
|
|
|
case "/":
|
|
|
|
|
return start + end
|
|
|
|
|
default:
|
|
|
|
|
return strings.TrimSuffix(start, "/") + "/" + end
|
|
|
|
|
// Ensure no final '/'
|
|
|
|
|
length := len(start)
|
|
|
|
|
if start[length-1] == '/' {
|
|
|
|
|
start = start[:length-1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return joined strings
|
|
|
|
|
return start + "/" + end
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sanitizeRawPath takes a root and relative path, and returns a sanitized relative path
|
|
|
|
|
func sanitizeRawPath(raw string) string {
|
|
|
|
|
// Start by cleaning
|
|
|
|
|
raw = path.Clean(raw)
|
|
|
|
|
// pathBuilder builds relative paths,
|
|
|
|
|
// taking backtracks ("..") into account
|
|
|
|
|
type pathBuilder struct {
|
|
|
|
|
b []byte
|
|
|
|
|
starts []int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Length = 1, if starts with '.' or '/'
|
|
|
|
|
// we return root (empty)
|
|
|
|
|
if len(raw) == 1 {
|
|
|
|
|
if raw[0] == '.' || raw[0] == '/' {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return raw
|
|
|
|
|
// newPathBuilder returns a new pathBuilder
|
|
|
|
|
func newPathBuilder() *pathBuilder {
|
|
|
|
|
return &pathBuilder{
|
|
|
|
|
b: make([]byte, 32)[:0],
|
|
|
|
|
starts: []int{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// split add's a path split to the buffer and
|
|
|
|
|
// starts tracking the start of a new path segment
|
|
|
|
|
func (pb *pathBuilder) split() {
|
|
|
|
|
// Add a '/'
|
|
|
|
|
pb.b = append(pb.b, '/')
|
|
|
|
|
|
|
|
|
|
// Set the next segment start
|
|
|
|
|
pb.starts = append(pb.starts, len(pb.b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// append adds a byte to the buffer
|
|
|
|
|
func (pb *pathBuilder) append(b byte) {
|
|
|
|
|
pb.b = append(pb.b, b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// backtrack removes the latest path element
|
|
|
|
|
func (pb *pathBuilder) backtrack() {
|
|
|
|
|
// Get segments count
|
|
|
|
|
length := len(pb.starts)
|
|
|
|
|
|
|
|
|
|
// If length = 0, just empty buffer
|
|
|
|
|
if length == 0 {
|
|
|
|
|
pb.b = pb.b[:0]
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the last segment start
|
|
|
|
|
last := pb.starts[length-1]
|
|
|
|
|
|
|
|
|
|
// Jump back to last segment
|
|
|
|
|
pb.b = pb.b[:last-1]
|
|
|
|
|
|
|
|
|
|
// Reset to previous segment start
|
|
|
|
|
pb.starts = pb.starts[:length-1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// toString gets current path as string, and resets the builder
|
|
|
|
|
func (pb *pathBuilder) toString() string {
|
|
|
|
|
// Get buffer as string, skip first
|
|
|
|
|
// char i.e. '/'
|
|
|
|
|
s := string(pb.b)
|
|
|
|
|
if len(s) > 0 {
|
|
|
|
|
s = s[1:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If second char is '.', i.e. dir back-traverse
|
|
|
|
|
// we also return root
|
|
|
|
|
if raw[0] == '.' && raw[1] == '.' {
|
|
|
|
|
// Reset buffer and list of segment starts
|
|
|
|
|
pb.b = pb.b[:0]
|
|
|
|
|
pb.starts = pb.starts[:0]
|
|
|
|
|
|
|
|
|
|
// Return string
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sanitizePath sanitizes a raw path
|
|
|
|
|
func sanitizePath(raw string) string {
|
|
|
|
|
// Empty path, nothing to do
|
|
|
|
|
if raw == "" {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trim any leading '/'
|
|
|
|
|
if raw[0] == '/' {
|
|
|
|
|
raw = raw[1:]
|
|
|
|
|
// Get necessary information beforehand
|
|
|
|
|
length := len(raw)
|
|
|
|
|
pb := pathBuilderPool.Get().(*pathBuilder)
|
|
|
|
|
defer pathBuilderPool.Put(pb)
|
|
|
|
|
|
|
|
|
|
for index := 0; index < length; {
|
|
|
|
|
// Path segment separator
|
|
|
|
|
if raw[index] == '/' {
|
|
|
|
|
index++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Segment starting '.'
|
|
|
|
|
if raw[index] == '.' {
|
|
|
|
|
// Hit the end of the path, break-out
|
|
|
|
|
if index+1 == length {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iter index
|
|
|
|
|
index++
|
|
|
|
|
|
|
|
|
|
// This segment is only '.', continue
|
|
|
|
|
if raw[index] == '/' {
|
|
|
|
|
index++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If next char is '.'
|
|
|
|
|
if raw[index] == '.' {
|
|
|
|
|
// Hit end of the path, break-out
|
|
|
|
|
if index+1 == length {
|
|
|
|
|
pb.backtrack()
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iter index
|
|
|
|
|
index++
|
|
|
|
|
|
|
|
|
|
// If next char is '/' then this is a
|
|
|
|
|
// back-track segment.
|
|
|
|
|
if raw[index] == '/' {
|
|
|
|
|
pb.backtrack()
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Split for next segment
|
|
|
|
|
pb.split()
|
|
|
|
|
|
|
|
|
|
// Else, add a '.'
|
|
|
|
|
pb.append('.')
|
|
|
|
|
} else {
|
|
|
|
|
// Split for next segment
|
|
|
|
|
pb.split()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Else, append a '.'
|
|
|
|
|
pb.append('.')
|
|
|
|
|
|
|
|
|
|
// Continue adding up to next '/'
|
|
|
|
|
for ; index < length && raw[index] != '/'; index++ {
|
|
|
|
|
pb.append(raw[index])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Final iter to skip past the final '/'
|
|
|
|
|
index++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Split for next segment
|
|
|
|
|
pb.split()
|
|
|
|
|
|
|
|
|
|
// Iter through and add up to next '/'
|
|
|
|
|
for ; index < length && raw[index] != '/'; index++ {
|
|
|
|
|
pb.append(raw[index])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Final iter to skip past the final '/'
|
|
|
|
|
index++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return raw
|
|
|
|
|
// Return built string
|
|
|
|
|
return pb.toString()
|
|
|
|
|
}
|
|
|
|
|