From 3860b235d074209c495d34a3966fc7fb2d5015a5 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 31 Jul 2020 13:55:01 -0600 Subject: fileserver: Don't assume len(str) == len(ToLower(str)) (fix #3623) We can't use a positional index on an original string that we got from its lower-cased equivalent. Implement our own IndexFold() function b/c the std lib does not have one. --- modules/caddyhttp/fileserver/matcher.go | 20 +++++++-- modules/caddyhttp/fileserver/matcher_test.go | 63 +++++++++++++++++----------- 2 files changed, 56 insertions(+), 27 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 9144ca4..1844421 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -117,11 +117,13 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return d.ArgErr() } m.TryPolicy = d.Val() - case "split": + case "split_path": m.SplitPath = d.RemainingArgs() if len(m.SplitPath) == 0 { return d.ArgErr() } + default: + return d.Errf("unrecognized subdirective: %s", d.Val()) } } } @@ -279,9 +281,8 @@ func strictFileExists(file string) bool { // in the split value. Returns the path as-is if the // path cannot be split. func (m MatchFile) firstSplit(path string) string { - lowerPath := strings.ToLower(path) for _, split := range m.SplitPath { - if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 { + if idx := indexFold(path, split); idx > -1 { pos := idx + len(split) // skip the split if it's not the final part of the filename if pos != len(path) && !strings.HasPrefix(path[pos:], "/") { @@ -293,6 +294,19 @@ func (m MatchFile) firstSplit(path string) string { return path } +// There is no strings.IndexFold() function like there is strings.EqualFold(), +// but we can use strings.EqualFold() to build our own case-insensitive +// substring search (as of Go 1.14). +func indexFold(haystack, needle string) int { + nlen := len(needle) + for i := 0; i+nlen < len(haystack); i++ { + if strings.EqualFold(haystack[i:i+nlen], needle) { + return i + } + } + return -1 +} + const ( tryPolicyFirstExist = "first_exist" tryPolicyLargestSize = "largest_size" diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go index aa84900..e3f2911 100644 --- a/modules/caddyhttp/fileserver/matcher_test.go +++ b/modules/caddyhttp/fileserver/matcher_test.go @@ -22,69 +22,84 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) -func TestPhpFileMatcher(t *testing.T) { - +func TestPHPFileMatcher(t *testing.T) { for i, tc := range []struct { - path string + path string expectedPath string - matched bool + matched bool }{ { - path: "/index.php", + path: "/index.php", expectedPath: "/index.php", - matched: true, + matched: true, }, { - path: "/index.php/somewhere", + path: "/index.php/somewhere", expectedPath: "/index.php", - matched: true, + matched: true, }, { - path: "/remote.php", + path: "/remote.php", expectedPath: "/remote.php", - matched: true, + matched: true, }, { - path: "/remote.php/somewhere", + path: "/remote.php/somewhere", expectedPath: "/remote.php", - matched: true, + matched: true, }, { - path: "/missingfile.php", + path: "/missingfile.php", matched: false, }, { - path: "/notphp.php.txt", + path: "/notphp.php.txt", expectedPath: "/notphp.php.txt", - matched: true, + matched: true, }, { - path: "/notphp.php.txt/", + path: "/notphp.php.txt/", expectedPath: "/notphp.php.txt", - matched: true, + matched: true, }, { - path: "/notphp.php.txt.suffixed", + path: "/notphp.php.txt.suffixed", matched: false, }, { - path: "/foo.php.php/index.php", + path: "/foo.php.php/index.php", expectedPath: "/foo.php.php/index.php", - matched: true, + matched: true, + }, + { + path: "/foo.php.PHP/index.php", + expectedPath: "/foo.php.PHP/index.php", + matched: true, + }, + { + // See https://github.com/caddyserver/caddy/issues/3623 + path: "/%E2%C3", + expectedPath: "/%E2%C3", + matched: false, }, } { m := &MatchFile{ Root: "./testdata", - TryFiles: []string{"{http.request.uri.path}"}, + TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php"}, SplitPath: []string{".php"}, } - req := &http.Request{URL: &url.URL{Path: tc.path}} + u, err := url.Parse(tc.path) + if err != nil { + t.Fatalf("Test %d: parsing path: %v", i, err) + } + + req := &http.Request{URL: u} repl := caddyhttp.NewTestReplacer(req) result := m.Match(req) if result != tc.matched { - t.Fatalf("Test %d: match bool result: %v, expected: %v", i, result, tc.matched) + t.Fatalf("Test %d: expected match=%t, got %t", i, tc.matched, result) } rel, ok := repl.Get("http.matchers.file.relative") @@ -99,4 +114,4 @@ func TestPhpFileMatcher(t *testing.T) { t.Fatalf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath) } } -} \ No newline at end of file +} -- cgit v1.2.3