file_server: Enforce URL canonicalization (closes #2741)

This commit is contained in:
Matthew Holt 2019-09-09 08:21:45 -06:00
parent 3e9e7555ef
commit e12c62e60b
No known key found for this signature in database
GPG Key ID: 2A349DD577D586A5
1 changed files with 40 additions and 8 deletions

View File

@ -41,10 +41,11 @@ func init() {
// FileServer implements a static file server responder for Caddy.
type FileServer struct {
Root string `json:"root,omitempty"` // default is current directory
Hide []string `json:"hide,omitempty"`
IndexNames []string `json:"index_names,omitempty"`
Browse *Browse `json:"browse,omitempty"`
Root string `json:"root,omitempty"` // default is current directory
Hide []string `json:"hide,omitempty"`
IndexNames []string `json:"index_names,omitempty"`
Browse *Browse `json:"browse,omitempty"`
CanonicalURIs *bool `json:"canonical_uris,omitempty"`
}
// CaddyModule returns the Caddy module information.
@ -57,6 +58,10 @@ func (FileServer) CaddyModule() caddy.ModuleInfo {
// Provision sets up the static files responder.
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
if fsrv.Root == "" {
fsrv.Root = "{http.vars.root}"
}
if fsrv.IndexNames == nil {
fsrv.IndexNames = defaultIndexNames
}
@ -105,6 +110,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
// if the request mapped to a directory, see if
// there is an index file we can serve
var implicitIndexFile bool
if info.IsDir() && len(fsrv.IndexNames) > 0 {
for _, indexPage := range fsrv.IndexNames {
indexPath := sanitizedPathJoin(filename, indexPage)
@ -118,12 +124,17 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
continue
}
// we found an index file that might work,
// so rewrite the request path
r.URL.Path = path.Join(r.URL.Path, indexPage)
// don't rewrite the request path to append
// the index file, because we might need to
// do a canonical-URL redirect below based
// on the URL as-is
// we've chosen to use this index file,
// so replace the last file info and path
// with that of the index file
info = indexInfo
filename = indexPath
implicitIndexFile = true
break
}
}
@ -145,10 +156,22 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
return caddyhttp.Error(http.StatusNotFound, nil)
}
// if URL canonicalization is enabled, we need to enforce trailing
// slash convention: if a directory, trailing slash; if a file, no
// trailing slash - not enforcing this can break relative hrefs
// in HTML (see https://github.com/caddyserver/caddy/issues/2741)
if fsrv.CanonicalURIs == nil || *fsrv.CanonicalURIs {
if implicitIndexFile && !strings.HasSuffix(r.URL.Path, "/") {
return redirect(w, r, r.URL.Path+"/")
} else if !implicitIndexFile && strings.HasSuffix(r.URL.Path, "/") {
return redirect(w, r, r.URL.Path[:len(r.URL.Path)-1])
}
}
// open the file
file, err := fsrv.openFile(filename, w)
if err != nil {
return err
return err // error is already structured
}
defer file.Close()
@ -305,6 +328,15 @@ func calculateEtag(d os.FileInfo) string {
return `"` + t + s + `"`
}
func redirect(w http.ResponseWriter, r *http.Request, to string) error {
for strings.HasPrefix(to, "//") {
// prevent path-based open redirects
to = strings.TrimPrefix(to, "/")
}
http.Redirect(w, r, to, http.StatusPermanentRedirect)
return nil
}
var defaultIndexNames = []string{"index.html", "index.txt"}
var bufPool = sync.Pool{