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: Improve file hiding logic for directories and prefixes
Now, a filename to hide that is specified without a path separator will count as hidden if it appears in any component of the file path (not only the last component); semantically, this means hiding a file by only its name (without any part of a path) will hide both files and folders, e.g. hiding ".git" will hide "/.git" and also "/.git/foo". We also do prefix matching so that hiding "/.git" will hide "/.git" and "/.git/foo" but not "/.gitignore". The remaining logic is a globular match like before.
Matthew Holt committed
Sep 11, 2020 at 12:20 UTC
0ee4378227cf3019dcbaddeb3977ddd28e58429a
2 files changed
+95
-17
modules/caddyhttp/fileserver/staticfiles.go
+24
-15
@@ -329,28 +329,37 @@ func sanitizedPathJoin(root, reqPath string) string {
329
// fileHidden returns true if filename is hidden
330
// according to the hide list.
331
func fileHidden(filename string, hide []string) bool {
332
- nameOnly := filepath.Base(filename)
332
sep := string(filepath.Separator)
333
+ var components []string
334
335
for _, h := range hide {
336
- // assuming h is a glob/shell-like pattern,
337
- // use it to compare the whole file path;
338
- // but if there is no separator in h, then
339
- // just compare against the file's name
340
- compare := filename
336
if !strings.Contains(h, sep) {
342
- compare = nameOnly
343
- }
344
-
345
- hidden, err := filepath.Match(h, compare)
346
- if err != nil {
347
- // malformed pattern; fallback by checking prefix
348
- if strings.HasPrefix(filename, h) {
337
+ // if there is no separator in h, then we assume the user
338
+ // wants to hide any files or folders that match that
339
+ // name; thus we have to compare against each component
340
+ // of the filename, e.g. hiding "bar" would hide "/bar"
341
+ // as well as "/foo/bar/baz" but not "/barstool".
342
+ if len(components) == 0 {
343
+ components = strings.Split(filename, sep)
344
+ }
345
+ for _, c := range components {
346
+ if c == h {
347
+ return true
348
+ }
349
+ }
350
+ } else if strings.HasPrefix(filename, h) {
351
+ // otherwise, if there is a separator in h, and
352
+ // filename is exactly prefixed with h, then we
353
+ // can do a prefix match so that "/foo" matches
354
+ // "/foo/bar" but not "/foobar".
355
+ withoutPrefix := strings.TrimPrefix(filename, h)
356
+ if strings.HasPrefix(withoutPrefix, sep) {
357
return true
358
}
359
}
352
- if hidden {
353
- // file name or path matches hide pattern
360
+
361
+ // in the general case, a glob match will suffice
362
+ if hidden, _ := filepath.Match(h, filename); hidden {
363
return true
364
}
365
}
modules/caddyhttp/fileserver/staticfiles_test.go
+71
-2
@@ -93,9 +93,78 @@ func TestSanitizedPathJoin(t *testing.T) {
93
}
94
actual := sanitizedPathJoin(tc.inputRoot, u.Path)
95
if actual != tc.expect {
96
- t.Errorf("Test %d: [%s %s] => %s (expected %s)", i, tc.inputRoot, tc.inputPath, actual, tc.expect)
96
+ t.Errorf("Test %d: [%s %s] => %s (expected %s)",
97
+ i, tc.inputRoot, tc.inputPath, actual, tc.expect)
98
}
99
}
100
}
101
101
-// TODO: test fileHidden
102
+func TestFileHidden(t *testing.T) {
103
+ for i, tc := range []struct {
104
+ inputHide []string
105
+ inputPath string
106
+ expect bool
107
+ }{
108
+ {
109
+ inputHide: nil,
110
+ inputPath: "",
111
+ expect: false,
112
+ },
113
+ {
114
+ inputHide: []string{".gitignore"},
115
+ inputPath: "/.gitignore",
116
+ expect: true,
117
+ },
118
+ {
119
+ inputHide: []string{".git"},
120
+ inputPath: "/.gitignore",
121
+ expect: false,
122
+ },
123
+ {
124
+ inputHide: []string{"/.git"},
125
+ inputPath: "/.gitignore",
126
+ expect: false,
127
+ },
128
+ {
129
+ inputHide: []string{".git"},
130
+ inputPath: "/.git",
131
+ expect: true,
132
+ },
133
+ {
134
+ inputHide: []string{".git"},
135
+ inputPath: "/.git/foo",
136
+ expect: true,
137
+ },
138
+ {
139
+ inputHide: []string{".git"},
140
+ inputPath: "/foo/.git/bar",
141
+ expect: true,
142
+ },
143
+ {
144
+ inputHide: []string{"/prefix"},
145
+ inputPath: "/prefix/foo",
146
+ expect: true,
147
+ },
148
+ {
149
+ inputHide: []string{"/foo/*/bar"},
150
+ inputPath: "/foo/asdf/bar",
151
+ expect: true,
152
+ },
153
+ {
154
+ inputHide: []string{"/foo"},
155
+ inputPath: "/foo",
156
+ expect: true,
157
+ },
158
+ {
159
+ inputHide: []string{"/foo"},
160
+ inputPath: "/foobar",
161
+ expect: false,
162
+ },
163
+ } {
164
+ actual := fileHidden(tc.inputPath, tc.inputHide)
165
+ if actual != tc.expect {
166
+ t.Errorf("Test %d: Is %s hidden in %v? Got %t but expected %t",
167
+ i, tc.inputPath, tc.inputHide, actual, tc.expect)
168
+ }
169
+ }
170
+}