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
file_server: Enforce URL canonicalization (closes #2741)
Matthew Holt committed
Sep 9, 2019 at 08:21 UTC
e12c62e60b3f794630aed2fae37c4c6973e63bf4
1 file changed
+40
-8
modules/caddyhttp/fileserver/staticfiles.go
+40
-8
@@ -41,10 +41,11 @@ func init() {
41
42
// FileServer implements a static file server responder for Caddy.
43
type FileServer struct {
44
- Root string `json:"root,omitempty"` // default is current directory
45
- Hide []string `json:"hide,omitempty"`
46
- IndexNames []string `json:"index_names,omitempty"`
47
- Browse *Browse `json:"browse,omitempty"`
44
+ Root string `json:"root,omitempty"` // default is current directory
45
+ Hide []string `json:"hide,omitempty"`
46
+ IndexNames []string `json:"index_names,omitempty"`
47
+ Browse *Browse `json:"browse,omitempty"`
48
+ CanonicalURIs *bool `json:"canonical_uris,omitempty"`
49
}
50
51
// CaddyModule returns the Caddy module information.
@@ -57,6 +58,10 @@ func (FileServer) CaddyModule() caddy.ModuleInfo {
58
59
// Provision sets up the static files responder.
60
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
61
+ if fsrv.Root == "" {
62
+ fsrv.Root = "{http.vars.root}"
63
+ }
64
+
65
if fsrv.IndexNames == nil {
66
fsrv.IndexNames = defaultIndexNames
67
}
@@ -105,6 +110,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
110
111
// if the request mapped to a directory, see if
112
// there is an index file we can serve
113
+ var implicitIndexFile bool
114
if info.IsDir() && len(fsrv.IndexNames) > 0 {
115
for _, indexPage := range fsrv.IndexNames {
116
indexPath := sanitizedPathJoin(filename, indexPage)
@@ -118,12 +124,17 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
124
continue
125
}
126
121
- // we found an index file that might work,
122
- // so rewrite the request path
123
- r.URL.Path = path.Join(r.URL.Path, indexPage)
127
+ // don't rewrite the request path to append
128
+ // the index file, because we might need to
129
+ // do a canonical-URL redirect below based
130
+ // on the URL as-is
131
132
+ // we've chosen to use this index file,
133
+ // so replace the last file info and path
134
+ // with that of the index file
135
info = indexInfo
136
filename = indexPath
137
+ implicitIndexFile = true
138
break
139
}
140
}
@@ -145,10 +156,22 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
156
return caddyhttp.Error(http.StatusNotFound, nil)
157
}
158
159
+ // if URL canonicalization is enabled, we need to enforce trailing
160
+ // slash convention: if a directory, trailing slash; if a file, no
161
+ // trailing slash - not enforcing this can break relative hrefs
162
+ // in HTML (see https://github.com/caddyserver/caddy/issues/2741)
163
+ if fsrv.CanonicalURIs == nil || *fsrv.CanonicalURIs {
164
+ if implicitIndexFile && !strings.HasSuffix(r.URL.Path, "/") {
165
+ return redirect(w, r, r.URL.Path+"/")
166
+ } else if !implicitIndexFile && strings.HasSuffix(r.URL.Path, "/") {
167
+ return redirect(w, r, r.URL.Path[:len(r.URL.Path)-1])
168
+ }
169
+ }
170
+
171
// open the file
172
file, err := fsrv.openFile(filename, w)
173
if err != nil {
151
- return err
174
+ return err // error is already structured
175
}
176
defer file.Close()
177
@@ -305,6 +328,15 @@ func calculateEtag(d os.FileInfo) string {
328
return `"` + t + s + `"`
329
}
330
331
+func redirect(w http.ResponseWriter, r *http.Request, to string) error {
332
+ for strings.HasPrefix(to, "//") {
333
+ // prevent path-based open redirects
334
+ to = strings.TrimPrefix(to, "/")
335
+ }
336
+ http.Redirect(w, r, to, http.StatusPermanentRedirect)
337
+ return nil
338
+}
339
+
340
var defaultIndexNames = []string{"index.html", "index.txt"}
341
342
var bufPool = sync.Pool{