@marcel91200 / caddy Mirror · read-only / commits / e12c62e6
Fork 0

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{