perform more path cleaning, use our own path joining func (is faster)

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
development
kim (grufwub) 4 years ago
parent 45f9f92a66
commit 0e558ff28f

@ -6,6 +6,7 @@ import (
"os"
"os/signal"
"os/user"
"path"
"strconv"
"syscall"
"time"
@ -86,6 +87,12 @@ 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 {

@ -99,7 +99,7 @@ func ScanDirectory(dir *os.File, p *Path, iterator func(os.FileInfo, *Path)) *er
// Walk through the directory list using supplied iterator function
for _, name := range nameList {
// Make new Path object
fp := p.JoinPath(name)
fp := p.JoinPathUnsafe(name)
// Skip restricted files
if IsRestrictedPath(fp) || IsHiddenPath(fp) {

@ -16,13 +16,13 @@ type Path struct {
}
// NewPath returns a new Path structure based on supplied root and relative path
func NewPath(root, rel string) *Path {
func newPath(root, rel string) *Path {
return &Path{root, rel, formatSelector(rel)}
}
// 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, sanitizeRawPath(rel))
}
// buildPathuserSpacesEnabled will attempt to parse a username, and return a sanitized Path at username's
@ -72,7 +72,7 @@ func (p *Path) Relative() string {
// Absolute returns the absolute path
func (p *Path) Absolute() string {
return path.Join(p.root, p.rel)
return joinSanitizedPaths(p.root, p.rel)
}
// Selector returns the formatted selector path
@ -97,29 +97,31 @@ func (p *Path) Dir() *Path {
// JoinRelative returns a string appended to the current relative path
func (p *Path) JoinRelative(newRel string) string {
return path.Join(p.rel, newRel)
return joinSanitizedPaths(p.rel, sanitizeRawPath(newRel))
}
// JoinPath appends the supplied string to the Path's relative and selector paths
func (p *Path) JoinPath(toJoin string) *Path {
return &Path{p.root, path.Join(p.rel, toJoin), path.Join(p.sel, toJoin)}
// 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)
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 {
switch len(rel) {
case 0:
return "/"
case 1:
if rel[0] == '.' {
return "/"
}
return "/" + rel
return "/" + rel
}
// joinSanitizedPaths quickly joins two sanitized paths, whether absolute or relative
func joinSanitizedPaths(start, end string) string {
end = strings.TrimPrefix(end, "/")
switch start {
case "":
return end
case "/":
return start + end
default:
if rel[0] == '/' {
return rel
}
return "/" + rel
return strings.TrimSuffix(start, "/") + "/" + end
}
}
@ -128,12 +130,24 @@ func sanitizeRawPath(raw string) string {
// Start by cleaning
raw = path.Clean(raw)
// - absolute path --> trim '/'
// - back dir ('..') --> return root
if path.IsAbs(raw) {
// Length = 1, if starts with '.' or '/'
// we return root (empty)
if len(raw) == 1 {
if raw[0] == '.' || raw[0] == '/' {
return ""
}
return raw
}
// If second char is '.', i.e. dir back-traverse
// we also return root
if raw[0] == '.' && raw[1] == '.' {
return ""
}
// Trim any leading '/'
if raw[0] == '/' {
raw = raw[1:]
} else if strings.HasPrefix(raw, "..") {
raw = ""
}
return raw

@ -59,12 +59,12 @@ func isHex(b byte) bool {
func unHex(b byte) byte {
switch {
case 'a' <= b || b <= 'f':
return b - 'a'
case 'A' <= b || b <= 'F':
return b - 'A'
case '0' <= b || b <= '9':
case '0' <= b && b <= '9':
return b - '0'
case 'a' <= b && b <= 'f':
return b - 'a' + 10
case 'A' <= b && b <= 'F':
return b - 'A' + 10
default:
return 0
}
@ -147,14 +147,14 @@ func unescapeHost(raw string) (string, *errors.Error) {
// If not a valid % encoded hex value, return with error
if i+2 >= len(raw) || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", errors.NewError(InvalidRequestErr).Extend("escaping host info " + raw)
return "", errors.NewError(InvalidRequestErr).Extend("unescaping host info " + raw)
}
// In the host component % encoding can only be used
// for non-ASCII bytes. And rfc6874 introduces %25 for
// escaped percent sign in IPv6 literals
if unHex(raw[i+1]) < 8 && raw[i:i+3] != "%25" {
return "", errors.NewError(InvalidRequestErr).Extend("escaping host " + raw)
return "", errors.NewError(InvalidRequestErr).Extend("unescaping host " + raw)
}
// Skip iteration past the
@ -163,7 +163,7 @@ func unescapeHost(raw string) (string, *errors.Error) {
default:
// If within ASCII range, and shoud be escaped, return error
if raw[i] < 0x80 && shouldHostEscape(raw[i]) {
return "", errors.NewError(InvalidRequestErr).Extend("escaping host " + raw)
return "", errors.NewError(InvalidRequestErr).Extend("unescaping host " + raw)
}
// Iter
@ -181,27 +181,22 @@ func unescapeHost(raw string) (string, *errors.Error) {
func unescapePath(raw string) (string, *errors.Error) {
// Count all the percent signs
count := 0
for i := 0; i < len(raw); {
length := len(raw)
for i := 0; i < length; {
switch raw[i] {
case '%':
// Increase count
count++
// If not a valid % encoded hex value, return with error
if i+2 >= len(raw) || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", errors.NewError(InvalidRequestErr).Extend("escaping path " + raw)
if i+2 >= length || !isHex(raw[i+1]) || !isHex(raw[i+2]) {
return "", errors.NewError(InvalidRequestErr).Extend("unescaping path " + raw)
}
// Skip iteration past the
// hex we just confirmed
i += 3
default:
// If within ASCII range, and shoud be escaped, return error
if raw[i] < 0x80 && shouldPathEscape(raw[i]) {
return "", errors.NewError(InvalidRequestErr).Extend("escaping path " + raw)
}
// Iter
i++
}
}
@ -213,6 +208,38 @@ func unescapePath(raw string) (string, *errors.Error) {
return unescape(raw, count), nil
}
// EscapePath escapes a URL path
func EscapePath(path string) string {
const defaultBufSize = 64
const upperhex = "0123456789ABCDEF"
count := 0
for i := 0; i < len(path); i++ {
if shouldPathEscape(path[i]) {
count++
}
}
if count == 0 {
return path
}
sb := strings.Builder{}
sb.Grow(len(path) + 2*count)
for i := 0; i < len(path); i++ {
c := path[i]
if shouldPathEscape(c) {
sb.WriteByte('%')
sb.WriteByte(upperhex[c>>4])
sb.WriteByte(upperhex[c&15])
} else {
sb.WriteByte(c)
}
}
return sb.String()
}
// ParseEncodedHost parses encoded host info, safely returning unescape host and port
func ParseEncodedHost(raw string) (string, string, *errors.Error) {
// Unescape the host info

@ -103,7 +103,7 @@ func handleError(client *core.Client, err *errors.Error) {
func handleDirectory(client *core.Client, file *os.File, p *core.Path) *errors.Error {
// First check for index gem, create gem Path object
indexGem := p.JoinPath("index.gmi")
indexGem := p.JoinPathUnsafe("index.gmi")
// If index gem exists, we fetch this
fd2, err := core.OpenFile(indexGem)
@ -122,17 +122,23 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) *errors.E
// Slice to write
dirContents := make([]byte, 0)
// Escape the previous dir
dirSel := core.EscapePath(p.SelectorDir())
// Add directory heading, empty line and a back line
dirContents = append(dirContents, []byte("["+core.Hostname+p.Selector()+"]\n\n")...)
dirContents = append(dirContents, []byte("=> /"+p.RelativeDir()+" * ..\n")...)
dirContents = append(dirContents, []byte("=> /"+dirSel+" ..\n")...)
// Scan directory and build lines
err = core.ScanDirectory(
file,
p,
func(file os.FileInfo, fp *core.Path) {
// Calculate escaped selector path
sel := core.EscapePath(fp.Selector())
// Append new formatted file listing
dirContents = append(dirContents, []byte("=> "+fp.Selector()+" * "+file.Name()+"\n")...)
dirContents = append(dirContents, []byte("=> "+sel+" "+file.Name()+"\n")...)
},
)
if err != nil {

@ -63,6 +63,7 @@ func buildErrorLine(selector string) []byte {
// appendFileListing formats and appends a new file entry as part of a directory listing
func appendFileListing(b []byte, file os.FileInfo, p *core.Path) []byte {
// Handle file type
switch {
case file.Mode().IsDir():
return append(b, buildLine(typeDirectory, file.Name(), p.Selector(), core.Hostname, core.Port)...)

@ -43,7 +43,7 @@ func Run() {
footer = buildFooter(*footerText)
// Add generated policy file to cache
p := core.NewPath(core.Root, "caps.txt")
p := core.NewSanitizedPathAtRoot(core.Root, "caps.txt")
core.SystemLog.Infof("Generating policy file %s...", p.Absolute())
core.FileCache.Put(filecache.NewFile(p.Absolute(), false, &generatedFileContent{generateCapsTxt(*desc, *admin, *geo)}))

@ -71,7 +71,7 @@ func serve(client *core.Client) {
func handleDirectory(client *core.Client, file *os.File, p *core.Path) *errors.Error {
// First check for gophermap, create gophermap Path object
gophermap := p.JoinPath("gophermap")
gophermap := p.JoinPathUnsafe("gophermap")
// If gophermap exists, we fetch this
file2, err := core.OpenFile(gophermap)
@ -93,7 +93,7 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) *errors.E
// Add directory heading, empty line and a back line
dirContents = append(dirContents, buildLine(typeInfo, "[ "+core.Hostname+p.Selector()+" ]", "TITLE", nullHost, nullPort)...)
dirContents = append(dirContents, buildInfoLine("")...)
dirContents = append(dirContents, buildLine(typeDirectory, "..", "/"+p.RelativeDir(), core.Hostname, core.Port)...)
dirContents = append(dirContents, buildLine(typeDirectory, "..", p.SelectorDir(), core.Hostname, core.Port)...)
// Scan directory and build lines
err = core.ScanDirectory(

Loading…
Cancel
Save