Browse Source
fileserver: properly handle escaped/non-ascii paths (#4332)
* fileserver: properly handle escaped/non-ascii paths
* fileserver: tests: accommodate Windows hate of colons in files names
master
Mohammed Al Sahaf
3 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with
62 additions and
3 deletions
-
modules/caddyhttp/caddyhttp.go
-
modules/caddyhttp/fileserver/browsetplcontext.go
-
modules/caddyhttp/fileserver/matcher_test.go
-
modules/caddyhttp/fileserver/staticfiles.go
-
modules/caddyhttp/fileserver/testdata/%D9%85%D9%84%D9%81.txt
-
modules/caddyhttp/fileserver/testdata/ملف.txt
|
|
@ -20,6 +20,7 @@ import ( |
|
|
|
"io" |
|
|
|
"net" |
|
|
|
"net/http" |
|
|
|
"net/url" |
|
|
|
"path/filepath" |
|
|
|
"strconv" |
|
|
|
"strings" |
|
|
@ -227,6 +228,7 @@ func StatusCodeMatches(actual, configured int) bool { |
|
|
|
// never be outside of root. The resulting path can be used
|
|
|
|
// with the local file system.
|
|
|
|
func SanitizedPathJoin(root, reqPath string) string { |
|
|
|
reqPath, _ = url.PathUnescape(reqPath) |
|
|
|
if root == "" { |
|
|
|
root = "." |
|
|
|
} |
|
|
|
|
|
@ -42,15 +42,16 @@ func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, root |
|
|
|
|
|
|
|
isDir := f.IsDir() || isSymlinkTargetDir(f, root, urlPath) |
|
|
|
|
|
|
|
u := url.URL{Path: url.PathEscape(name)} |
|
|
|
|
|
|
|
// add the slash after the escape of path to avoid escaping the slash as well
|
|
|
|
if isDir { |
|
|
|
name += "/" |
|
|
|
u.Path += "/" |
|
|
|
dirCount++ |
|
|
|
} else { |
|
|
|
fileCount++ |
|
|
|
} |
|
|
|
|
|
|
|
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
|
|
|
|
|
|
|
fileInfos = append(fileInfos, fileInfo{ |
|
|
|
IsDir: isDir, |
|
|
|
IsSymlink: isSymlink(f), |
|
|
|
|
|
@ -17,12 +17,31 @@ package fileserver |
|
|
|
import ( |
|
|
|
"net/http" |
|
|
|
"net/url" |
|
|
|
"os" |
|
|
|
"runtime" |
|
|
|
"testing" |
|
|
|
|
|
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp" |
|
|
|
) |
|
|
|
|
|
|
|
func TestFileMatcher(t *testing.T) { |
|
|
|
|
|
|
|
// Windows doesn't like colons in files names
|
|
|
|
isWindows := runtime.GOOS == "windows" |
|
|
|
if !isWindows { |
|
|
|
filename := "with:in-name.txt" |
|
|
|
f, err := os.Create("./testdata/" + filename) |
|
|
|
if err != nil { |
|
|
|
t.Fail() |
|
|
|
return |
|
|
|
} |
|
|
|
t.Cleanup(func() { |
|
|
|
os.Remove("./testdata/" + filename) |
|
|
|
}) |
|
|
|
f.WriteString(filename) |
|
|
|
f.Close() |
|
|
|
} |
|
|
|
|
|
|
|
for i, tc := range []struct { |
|
|
|
path string |
|
|
|
expectedPath string |
|
|
@ -63,6 +82,30 @@ func TestFileMatcher(t *testing.T) { |
|
|
|
path: "/missingfile.php", |
|
|
|
matched: false, |
|
|
|
}, |
|
|
|
{ |
|
|
|
path: "ملف.txt", // the path file name is not escaped
|
|
|
|
expectedPath: "ملف.txt", |
|
|
|
expectedType: "file", |
|
|
|
matched: true, |
|
|
|
}, |
|
|
|
{ |
|
|
|
path: url.PathEscape("ملف.txt"), // singly-escaped path
|
|
|
|
expectedPath: "ملف.txt", |
|
|
|
expectedType: "file", |
|
|
|
matched: true, |
|
|
|
}, |
|
|
|
{ |
|
|
|
path: url.PathEscape(url.PathEscape("ملف.txt")), // doubly-escaped path
|
|
|
|
expectedPath: "%D9%85%D9%84%D9%81.txt", |
|
|
|
expectedType: "file", |
|
|
|
matched: true, |
|
|
|
}, |
|
|
|
{ |
|
|
|
path: "./with:in-name.txt", // browsers send the request with the path as such
|
|
|
|
expectedPath: "with:in-name.txt", |
|
|
|
expectedType: "file", |
|
|
|
matched: !isWindows, |
|
|
|
}, |
|
|
|
} { |
|
|
|
m := &MatchFile{ |
|
|
|
Root: "./testdata", |
|
|
|
|
|
@ -19,6 +19,7 @@ import ( |
|
|
|
weakrand "math/rand" |
|
|
|
"mime" |
|
|
|
"net/http" |
|
|
|
"net/url" |
|
|
|
"os" |
|
|
|
"path" |
|
|
|
"path/filepath" |
|
|
@ -165,6 +166,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c |
|
|
|
filesToHide := fsrv.transformHidePaths(repl) |
|
|
|
|
|
|
|
root := repl.ReplaceAll(fsrv.Root, ".") |
|
|
|
// PathUnescape returns an error if the escapes aren't well-formed,
|
|
|
|
// meaning the count % matches the RFC. Return early if the escape is
|
|
|
|
// improper.
|
|
|
|
if _, err := url.PathUnescape(r.URL.Path); err != nil { |
|
|
|
fsrv.logger.Debug("improper path escape", |
|
|
|
zap.String("site_root", root), |
|
|
|
zap.String("request_path", r.URL.Path), |
|
|
|
zap.Error(err)) |
|
|
|
return err |
|
|
|
} |
|
|
|
filename := caddyhttp.SanitizedPathJoin(root, r.URL.Path) |
|
|
|
|
|
|
|
fsrv.logger.Debug("sanitized path join", |
|
|
|
|
|
@ -0,0 +1 @@ |
|
|
|
%D9%85%D9%84%D9%81.txt |