diff --git a/caddytest/integration/caddyfile_adapt/file_server_status.txt b/caddytest/integration/caddyfile_adapt/file_server_status.txt new file mode 100644 index 00000000..ede1f4ad --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/file_server_status.txt @@ -0,0 +1,112 @@ +localhost + +root * /srv + +handle /nope* { + file_server { + status 403 + } +} + +handle /custom-status* { + file_server { + status {env.CUSTOM_STATUS} + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "vars", + "root": "/srv" + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "status_code": "{env.CUSTOM_STATUS}" + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/custom-status*" + ] + } + ] + }, + { + "group": "group2", + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "file_server", + "hide": [ + "./Caddyfile" + ], + "status_code": 403 + } + ] + } + ] + } + ], + "match": [ + { + "path": [ + "/nope*" + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 2ba53f2b..447f4811 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -40,6 +40,7 @@ func init() { // index // browse [] // precompressed +// status // } // func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { @@ -65,21 +66,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) if len(fsrv.Hide) == 0 { return nil, h.ArgErr() } + case "index": fsrv.IndexNames = h.RemainingArgs() if len(fsrv.IndexNames) == 0 { return nil, h.ArgErr() } + case "root": if !h.Args(&fsrv.Root) { return nil, h.ArgErr() } + case "browse": if fsrv.Browse != nil { return nil, h.Err("browsing is already configured") } fsrv.Browse = new(Browse) h.Args(&fsrv.Browse.TemplateFile) + case "precompressed": var order []string for h.NextArg() { @@ -100,6 +105,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) order = append(order, h.Val()) } fsrv.PrecompressedOrder = order + + case "status": + if !h.NextArg() { + return nil, h.ArgErr() + } + fsrv.StatusCode = caddyhttp.WeakString(h.Val()) + default: return nil, h.Errf("unknown subdirective '%s'", h.Val()) } diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index c6707885..660e1d19 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -75,6 +75,12 @@ type FileServer struct { // remove trailing slash from URIs for files. Default is true. CanonicalURIs *bool `json:"canonical_uris,omitempty"` + // Override the status code written when successfully serving a file. + // Particularly useful when explicitly serving a file as display for + // an error, like a 404 page. A placeholder may be used. By default, + // the status code will typically be 200, or 206 for partial content. + StatusCode caddyhttp.WeakString `json:"status_code,omitempty"` + // If pass-thru mode is enabled and a requested file is not found, // it will invoke the next handler in the chain instead of returning // a 404 error. By default, this is false (disabled). @@ -345,6 +351,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c return nil } + // if a status code override is configured, write the status code + // before serving the file + if codeStr := fsrv.StatusCode.String(); codeStr != "" { + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, "")) + if err != nil { + return caddyhttp.Error(http.StatusInternalServerError, err) + } + w.WriteHeader(intVal) + } + // let the standard library do what it does best; note, however, // that errors generated by ServeContent are written immediately // to the response, so we cannot handle them (but errors there