diff options
Diffstat (limited to 'modules/caddyhttp/fileserver/staticfiles.go')
| -rw-r--r-- | modules/caddyhttp/fileserver/staticfiles.go | 64 | 
1 files changed, 51 insertions, 13 deletions
| diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index c0fde66..0ed558e 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -29,17 +29,15 @@ import (  	"runtime"  	"strconv"  	"strings" -	"time" + +	"go.uber.org/zap"  	"github.com/caddyserver/caddy/v2"  	"github.com/caddyserver/caddy/v2/modules/caddyhttp"  	"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" -	"go.uber.org/zap"  )  func init() { -	weakrand.Seed(time.Now().UnixNano()) -  	caddy.RegisterModule(FileServer{})  } @@ -62,7 +60,23 @@ func init() {  // requested directory does not have an index file, Caddy writes a  // 404 response. Alternatively, file browsing can be enabled with  // the "browse" parameter which shows a list of files when directories -// are requested if no index file is present. +// are requested if no index file is present. If "browse" is enabled, +// Caddy may serve a JSON array of the dirctory listing when the `Accept` +// header mentions `application/json` with the following structure: +// +//	[{ +//		"name": "", +//		"size": 0, +//		"url": "", +//		"mod_time": "", +//		"mode": 0, +//		"is_dir": false, +//		"is_symlink": false +//	}] +// +// with the `url` being relative to the request path and `mod_time` in the RFC 3339 format +// with sub-second precision. For any other value for the `Accept` header, the +// respective browse template is executed with `Content-Type: text/html`.  //  // By default, this handler will canonicalize URIs so that requests to  // directories end with a slash, but requests to regular files do not. @@ -250,7 +264,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c  	root := repl.ReplaceAll(fsrv.Root, ".") -	filename := caddyhttp.SanitizedPathJoin(root, r.URL.Path) +	// remove any trailing `/` as it breaks fs.ValidPath() in the stdlib +	filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/")  	fsrv.logger.Debug("sanitized path join",  		zap.String("site_root", root), @@ -355,7 +370,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c  	}  	var file fs.File -	var etag string + +	// etag is usually unset, but if the user knows what they're doing, let them override it +	etag := w.Header().Get("Etag")  	// check for precompressed files  	for _, ae := range encode.AcceptedEncodings(r, fsrv.PrecompressedOrder) { @@ -387,7 +404,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c  		// don't assign info = compressedInfo because sidecars are kind  		// of transparent; however we do need to set the Etag:  		// https://caddy.community/t/gzipped-sidecar-file-wrong-same-etag/16793 -		etag = calculateEtag(compressedInfo) +		if etag == "" { +			etag = calculateEtag(compressedInfo) +		}  		break  	} @@ -407,20 +426,29 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c  		}  		defer file.Close() -		etag = calculateEtag(info) +		if etag == "" { +			etag = calculateEtag(info) +		}  	}  	// at this point, we're serving a file; Go std lib supports only  	// GET and HEAD, which is sensible for a static file server - reject  	// any other methods (see issue #5166)  	if r.Method != http.MethodGet && r.Method != http.MethodHead { -		w.Header().Add("Allow", "GET, HEAD") -		return caddyhttp.Error(http.StatusMethodNotAllowed, nil) +		// if we're in an error context, then it doesn't make sense +		// to repeat the error; just continue because we're probably +		// trying to write an error page response (see issue #5703) +		if _, ok := r.Context().Value(caddyhttp.ErrorCtxKey).(error); !ok { +			w.Header().Add("Allow", "GET, HEAD") +			return caddyhttp.Error(http.StatusMethodNotAllowed, nil) +		}  	}  	// set the Etag - note that a conditional If-None-Match request is handled  	// by http.ServeContent below, which checks against this Etag value -	w.Header().Set("Etag", etag) +	if etag != "" { +		w.Header().Set("Etag", etag) +	}  	if w.Header().Get("Content-Type") == "" {  		mtyp := mime.TypeByExtension(filepath.Ext(filename)) @@ -607,7 +635,11 @@ func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Request, next ca  // Prefix the etag with "W/" to convert it into a weak etag.  // See: https://tools.ietf.org/html/rfc7232#section-2.3  func calculateEtag(d os.FileInfo) string { -	t := strconv.FormatInt(d.ModTime().Unix(), 36) +	mtime := d.ModTime().Unix() +	if mtime == 0 || mtime == 1 { +		return "" // not useful anyway; see issue #5548 +	} +	t := strconv.FormatInt(mtime, 36)  	s := strconv.FormatInt(d.Size(), 36)  	return `"` + t + s + `"`  } @@ -635,6 +667,12 @@ func (wr statusOverrideResponseWriter) WriteHeader(int) {  	wr.ResponseWriter.WriteHeader(wr.code)  } +// Unwrap returns the underlying ResponseWriter, necessary for +// http.ResponseController to work correctly. +func (wr statusOverrideResponseWriter) Unwrap() http.ResponseWriter { +	return wr.ResponseWriter +} +  // osFS is a simple fs.FS implementation that uses the local  // file system. (We do not use os.DirFS because we do our own  // rooting or path prefixing without being constrained to a single | 
