use our own path cleaner (not path.Clean()) which allows buffer pooling, version bump

Signed-off-by: kim (grufwub) <grufwub@gmail.com>

Former-commit-id: ea35c82dc1151d7951964444590f771e5c822706
development
kim (grufwub) 4 years ago
parent ce73853fd0
commit 32aebac22a

@ -6,8 +6,8 @@ import (
"os"
"os/signal"
"os/user"
"path"
"strconv"
"sync"
"syscall"
"time"
@ -87,12 +87,6 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
// Parse provided config file
tree.Parse(configFile)
// Clean provided root dir
Root = path.Clean(Root)
if Root == "." {
SystemLog.Fatalf(rootDirEmptyErrStr)
}
// Setup loggers
SystemLog = setupLogger(*sysLog)
if *sysLog == *accLog {
@ -202,6 +196,11 @@ func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newLi
connBufferedWriterPool = bufpools.NewBufferedWriterPool(int(*cWriteBuf))
fileBufferedReaderPool = bufpools.NewBufferedReaderPool(int(*fReadBuf))
fileBufferPool = bufpools.NewBufferPool(int(*fReadBuf))
pathBuilderPool = &sync.Pool{
New: func() interface{} {
return newPathBuilder()
},
}
// FileSystemObject (and related) setup
fileSizeMax = int64(1048576.0 * *cacheMax) // gets megabytes value in bytes

@ -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()
}

@ -3,6 +3,7 @@ package core
import (
"os"
"regexp"
"sync"
"time"
"github.com/grufwub/go-bufpools"
@ -13,7 +14,7 @@ import (
const (
// Version holds the current version string
Version = "v3.1.5-beta"
Version = "v3.1.6-beta"
)
var (
@ -65,6 +66,7 @@ var (
connBufferedWriterPool *bufpools.BufferedWriterPool
fileBufferedReaderPool *bufpools.BufferedReaderPool
fileBufferPool *bufpools.BufferPool
pathBuilderPool *sync.Pool
// Compiled regex globals
cgiDirRegex *regexp.Regexp

Loading…
Cancel
Save