Skip to content

Commit

Permalink
fileserver: Don't assume len(str) == len(ToLower(str)) (fix #3623)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mholt committed Jul 31, 2020
1 parent 6f73a35 commit 3860b23
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 27 deletions.
20 changes: 17 additions & 3 deletions modules/caddyhttp/fileserver/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
}
Expand Down Expand Up @@ -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:], "/") {
Expand All @@ -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"
Expand Down
63 changes: 39 additions & 24 deletions modules/caddyhttp/fileserver/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -99,4 +114,4 @@ func TestPhpFileMatcher(t *testing.T) {
t.Fatalf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath)
}
}
}
}

0 comments on commit 3860b23

Please sign in to comment.