Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
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.
Matthew Holt committed
Jul 31, 2020 at 13:55 UTC
3860b235d074209c495d34a3966fc7fb2d5015a5
2 files changed
+56
-27
modules/caddyhttp/fileserver/matcher.go
+17
-3
@@ -117,11 +117,13 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
117
return d.ArgErr()
118
}
119
m.TryPolicy = d.Val()
120
- case "split":
120
+ case "split_path":
121
m.SplitPath = d.RemainingArgs()
122
if len(m.SplitPath) == 0 {
123
return d.ArgErr()
124
}
125
+ default:
126
+ return d.Errf("unrecognized subdirective: %s", d.Val())
127
}
128
}
129
}
@@ -279,9 +281,8 @@ func strictFileExists(file string) bool {
281
// in the split value. Returns the path as-is if the
282
// path cannot be split.
283
func (m MatchFile) firstSplit(path string) string {
282
- lowerPath := strings.ToLower(path)
284
for _, split := range m.SplitPath {
284
- if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
285
+ if idx := indexFold(path, split); idx > -1 {
286
pos := idx + len(split)
287
// skip the split if it's not the final part of the filename
288
if pos != len(path) && !strings.HasPrefix(path[pos:], "/") {
@@ -293,6 +294,19 @@ func (m MatchFile) firstSplit(path string) string {
294
return path
295
}
296
297
+// There is no strings.IndexFold() function like there is strings.EqualFold(),
298
+// but we can use strings.EqualFold() to build our own case-insensitive
299
+// substring search (as of Go 1.14).
300
+func indexFold(haystack, needle string) int {
301
+ nlen := len(needle)
302
+ for i := 0; i+nlen < len(haystack); i++ {
303
+ if strings.EqualFold(haystack[i:i+nlen], needle) {
304
+ return i
305
+ }
306
+ }
307
+ return -1
308
+}
309
+
310
const (
311
tryPolicyFirstExist = "first_exist"
312
tryPolicyLargestSize = "largest_size"
modules/caddyhttp/fileserver/matcher_test.go
+39
-24
@@ -22,69 +22,84 @@ import (
22
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
23
)
24
25
-func TestPhpFileMatcher(t *testing.T) {
26
-
25
+func TestPHPFileMatcher(t *testing.T) {
26
for i, tc := range []struct {
28
- path string
27
+ path string
28
expectedPath string
30
- matched bool
29
+ matched bool
30
}{
31
{
33
- path: "/index.php",
32
+ path: "/index.php",
33
expectedPath: "/index.php",
35
- matched: true,
34
+ matched: true,
35
},
36
{
38
- path: "/index.php/somewhere",
37
+ path: "/index.php/somewhere",
38
expectedPath: "/index.php",
40
- matched: true,
39
+ matched: true,
40
},
41
{
43
- path: "/remote.php",
42
+ path: "/remote.php",
43
expectedPath: "/remote.php",
45
- matched: true,
44
+ matched: true,
45
},
46
{
48
- path: "/remote.php/somewhere",
47
+ path: "/remote.php/somewhere",
48
expectedPath: "/remote.php",
50
- matched: true,
49
+ matched: true,
50
},
51
{
53
- path: "/missingfile.php",
52
+ path: "/missingfile.php",
53
matched: false,
54
},
55
{
57
- path: "/notphp.php.txt",
56
+ path: "/notphp.php.txt",
57
expectedPath: "/notphp.php.txt",
59
- matched: true,
58
+ matched: true,
59
},
60
{
62
- path: "/notphp.php.txt/",
61
+ path: "/notphp.php.txt/",
62
expectedPath: "/notphp.php.txt",
64
- matched: true,
63
+ matched: true,
64
},
65
{
67
- path: "/notphp.php.txt.suffixed",
66
+ path: "/notphp.php.txt.suffixed",
67
matched: false,
68
},
69
{
71
- path: "/foo.php.php/index.php",
70
+ path: "/foo.php.php/index.php",
71
expectedPath: "/foo.php.php/index.php",
73
- matched: true,
72
+ matched: true,
73
+ },
74
+ {
75
+ path: "/foo.php.PHP/index.php",
76
+ expectedPath: "/foo.php.PHP/index.php",
77
+ matched: true,
78
+ },
79
+ {
80
+ // See https://github.com/caddyserver/caddy/issues/3623
81
+ path: "/%E2%C3",
82
+ expectedPath: "/%E2%C3",
83
+ matched: false,
84
},
85
} {
86
m := &MatchFile{
87
Root: "./testdata",
78
- TryFiles: []string{"{http.request.uri.path}"},
88
+ TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php"},
89
SplitPath: []string{".php"},
90
}
91
82
- req := &http.Request{URL: &url.URL{Path: tc.path}}
92
+ u, err := url.Parse(tc.path)
93
+ if err != nil {
94
+ t.Fatalf("Test %d: parsing path: %v", i, err)
95
+ }
96
+
97
+ req := &http.Request{URL: u}
98
repl := caddyhttp.NewTestReplacer(req)
99
100
result := m.Match(req)
101
if result != tc.matched {
87
- t.Fatalf("Test %d: match bool result: %v, expected: %v", i, result, tc.matched)
102
+ t.Fatalf("Test %d: expected match=%t, got %t", i, tc.matched, result)
103
}
104
105
rel, ok := repl.Get("http.matchers.file.relative")
@@ -99,4 +114,4 @@ func TestPhpFileMatcher(t *testing.T) {
114
t.Fatalf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath)
115
}
116
}
102
-}
\ No newline at end of file
117
+}