Browse Source

Default error handler; rename StaticFiles -> FileServer

master
Matthew Holt 6 years ago
parent
commit
a969872850
  1. 2
      cmd/caddy2/main.go
  2. 28
      modules/caddyhttp/fileserver/browse.go
  3. 6
      modules/caddyhttp/fileserver/browselisting.go
  4. 2
      modules/caddyhttp/fileserver/browsetpl.go
  5. 2
      modules/caddyhttp/fileserver/matcher.go
  6. 112
      modules/caddyhttp/fileserver/staticfiles.go
  7. 5
      modules/caddyhttp/fileserver/staticfiles_test.go
  8. 23
      modules/caddyhttp/server.go
  9. 16
      modules/caddyhttp/staticresp.go

2
cmd/caddy2/main.go

@ -6,9 +6,9 @@ import (
// this is where modules get plugged in // this is where modules get plugged in
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/fileserver"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/headers" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/headers"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
) )

28
modules/caddyhttp/staticfiles/browse.go → modules/caddyhttp/fileserver/browse.go

@ -1,4 +1,4 @@
package staticfiles package fileserver
import ( import (
"bytes" "bytes"
@ -20,8 +20,8 @@ type Browse struct {
template *template.Template template *template.Template
} }
func (sf *StaticFiles) serveBrowse(dirPath string, w http.ResponseWriter, r *http.Request) error { func (fsrv *FileServer) serveBrowse(dirPath string, w http.ResponseWriter, r *http.Request) error {
dir, err := sf.openFile(dirPath, w) dir, err := fsrv.openFile(dirPath, w)
if err != nil { if err != nil {
return err return err
} }
@ -29,7 +29,7 @@ func (sf *StaticFiles) serveBrowse(dirPath string, w http.ResponseWriter, r *htt
repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer) repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
listing, err := sf.loadDirectoryContents(dir, r.URL.Path, repl) listing, err := fsrv.loadDirectoryContents(dir, r.URL.Path, repl)
switch { switch {
case os.IsPermission(err): case os.IsPermission(err):
return caddyhttp.Error(http.StatusForbidden, err) return caddyhttp.Error(http.StatusForbidden, err)
@ -39,18 +39,18 @@ func (sf *StaticFiles) serveBrowse(dirPath string, w http.ResponseWriter, r *htt
return caddyhttp.Error(http.StatusInternalServerError, err) return caddyhttp.Error(http.StatusInternalServerError, err)
} }
sf.browseApplyQueryParams(w, r, &listing) fsrv.browseApplyQueryParams(w, r, &listing)
// write response as either JSON or HTML // write response as either JSON or HTML
var buf *bytes.Buffer var buf *bytes.Buffer
acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ",")) acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))
if strings.Contains(acceptHeader, "application/json") { if strings.Contains(acceptHeader, "application/json") {
if buf, err = sf.browseWriteJSON(listing); err != nil { if buf, err = fsrv.browseWriteJSON(listing); err != nil {
return caddyhttp.Error(http.StatusInternalServerError, err) return caddyhttp.Error(http.StatusInternalServerError, err)
} }
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
} else { } else {
if buf, err = sf.browseWriteHTML(listing); err != nil { if buf, err = fsrv.browseWriteHTML(listing); err != nil {
return caddyhttp.Error(http.StatusInternalServerError, err) return caddyhttp.Error(http.StatusInternalServerError, err)
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
@ -75,7 +75,7 @@ func (sf *StaticFiles) serveBrowse(dirPath string, w http.ResponseWriter, r *htt
// return b.ServeListing(w, r, requestedFilepath, bc) // return b.ServeListing(w, r, requestedFilepath, bc)
} }
func (sf *StaticFiles) loadDirectoryContents(dir *os.File, urlPath string, repl caddy2.Replacer) (browseListing, error) { func (fsrv *FileServer) loadDirectoryContents(dir *os.File, urlPath string, repl caddy2.Replacer) (browseListing, error) {
files, err := dir.Readdir(-1) files, err := dir.Readdir(-1)
if err != nil { if err != nil {
return browseListing{}, err return browseListing{}, err
@ -83,14 +83,14 @@ func (sf *StaticFiles) loadDirectoryContents(dir *os.File, urlPath string, repl
// determine if user can browse up another folder // determine if user can browse up another folder
curPathDir := path.Dir(strings.TrimSuffix(urlPath, "/")) curPathDir := path.Dir(strings.TrimSuffix(urlPath, "/"))
canGoUp := strings.HasPrefix(curPathDir, sf.Root) canGoUp := strings.HasPrefix(curPathDir, fsrv.Root)
return sf.directoryListing(files, canGoUp, urlPath, repl), nil return fsrv.directoryListing(files, canGoUp, urlPath, repl), nil
} }
// browseApplyQueryParams applies query parameters to the listing. // browseApplyQueryParams applies query parameters to the listing.
// It mutates the listing and may set cookies. // It mutates the listing and may set cookies.
func (sf *StaticFiles) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseListing) { func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseListing) {
sortParam := r.URL.Query().Get("sort") sortParam := r.URL.Query().Get("sort")
orderParam := r.URL.Query().Get("order") orderParam := r.URL.Query().Get("order")
limitParam := r.URL.Query().Get("limit") limitParam := r.URL.Query().Get("limit")
@ -121,15 +121,15 @@ func (sf *StaticFiles) browseApplyQueryParams(w http.ResponseWriter, r *http.Req
listing.applySortAndLimit(sortParam, orderParam, limitParam) listing.applySortAndLimit(sortParam, orderParam, limitParam)
} }
func (sf *StaticFiles) browseWriteJSON(listing browseListing) (*bytes.Buffer, error) { func (fsrv *FileServer) browseWriteJSON(listing browseListing) (*bytes.Buffer, error) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(listing.Items) err := json.NewEncoder(buf).Encode(listing.Items)
return buf, err return buf, err
} }
func (sf *StaticFiles) browseWriteHTML(listing browseListing) (*bytes.Buffer, error) { func (fsrv *FileServer) browseWriteHTML(listing browseListing) (*bytes.Buffer, error) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err := sf.Browse.template.Execute(buf, listing) err := fsrv.Browse.template.Execute(buf, listing)
return buf, err return buf, err
} }

6
modules/caddyhttp/staticfiles/browselisting.go → modules/caddyhttp/fileserver/browselisting.go

@ -1,4 +1,4 @@
package staticfiles package fileserver
import ( import (
"net/url" "net/url"
@ -13,8 +13,8 @@ import (
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
) )
func (sf *StaticFiles) directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, repl caddy2.Replacer) browseListing { func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, repl caddy2.Replacer) browseListing {
filesToHide := sf.transformHidePaths(repl) filesToHide := fsrv.transformHidePaths(repl)
var ( var (
fileInfos []fileInfo fileInfos []fileInfo

2
modules/caddyhttp/staticfiles/browsetpl.go → modules/caddyhttp/fileserver/browsetpl.go

@ -1,4 +1,4 @@
package staticfiles package fileserver
const defaultBrowseTemplate = `<!DOCTYPE html> const defaultBrowseTemplate = `<!DOCTYPE html>
<html> <html>

2
modules/caddyhttp/staticfiles/matcher.go → modules/caddyhttp/fileserver/matcher.go

@ -1,4 +1,4 @@
package staticfiles package fileserver
import ( import (
"net/http" "net/http"

112
modules/caddyhttp/staticfiles/staticfiles.go → modules/caddyhttp/fileserver/staticfiles.go

@ -1,4 +1,4 @@
package staticfiles package fileserver
import ( import (
"fmt" "fmt"
@ -20,13 +20,13 @@ func init() {
weakrand.Seed(time.Now().UnixNano()) weakrand.Seed(time.Now().UnixNano())
caddy2.RegisterModule(caddy2.Module{ caddy2.RegisterModule(caddy2.Module{
Name: "http.responders.static_files", Name: "http.responders.file_server",
New: func() (interface{}, error) { return new(StaticFiles), nil }, New: func() (interface{}, error) { return new(FileServer), nil },
}) })
} }
// StaticFiles implements a static file server responder for Caddy. // FileServer implements a static file server responder for Caddy.
type StaticFiles struct { type FileServer struct {
Root string `json:"root"` // default is current directory Root string `json:"root"` // default is current directory
Hide []string `json:"hide"` Hide []string `json:"hide"`
IndexNames []string `json:"index_names"` IndexNames []string `json:"index_names"`
@ -40,23 +40,23 @@ type StaticFiles struct {
} }
// Provision sets up the static files responder. // Provision sets up the static files responder.
func (sf *StaticFiles) Provision(ctx caddy2.Context) error { func (fsrv *FileServer) Provision(ctx caddy2.Context) error {
if sf.Fallback != nil { if fsrv.Fallback != nil {
err := sf.Fallback.Provision(ctx) err := fsrv.Fallback.Provision(ctx)
if err != nil { if err != nil {
return fmt.Errorf("setting up fallback routes: %v", err) return fmt.Errorf("setting up fallback routes: %v", err)
} }
} }
if sf.IndexNames == nil { if fsrv.IndexNames == nil {
sf.IndexNames = defaultIndexNames fsrv.IndexNames = defaultIndexNames
} }
if sf.Browse != nil { if fsrv.Browse != nil {
var tpl *template.Template var tpl *template.Template
var err error var err error
if sf.Browse.TemplateFile != "" { if fsrv.Browse.TemplateFile != "" {
tpl, err = template.ParseFiles(sf.Browse.TemplateFile) tpl, err = template.ParseFiles(fsrv.Browse.TemplateFile)
if err != nil { if err != nil {
return fmt.Errorf("parsing browse template file: %v", err) return fmt.Errorf("parsing browse template file: %v", err)
} }
@ -66,7 +66,7 @@ func (sf *StaticFiles) Provision(ctx caddy2.Context) error {
return fmt.Errorf("parsing default browse template: %v", err) return fmt.Errorf("parsing default browse template: %v", err)
} }
} }
sf.Browse.template = tpl fsrv.Browse.template = tpl
} }
return nil return nil
@ -80,29 +80,31 @@ const (
) )
// Validate ensures that sf has a valid configuration. // Validate ensures that sf has a valid configuration.
func (sf *StaticFiles) Validate() error { func (fsrv *FileServer) Validate() error {
switch sf.SelectionPolicy { switch fsrv.SelectionPolicy {
case "", case "",
selectionPolicyFirstExisting, selectionPolicyFirstExisting,
selectionPolicyLargestSize, selectionPolicyLargestSize,
selectionPolicySmallestSize, selectionPolicySmallestSize,
selectionPolicyRecentlyMod: selectionPolicyRecentlyMod:
default: default:
return fmt.Errorf("unknown selection policy %s", sf.SelectionPolicy) return fmt.Errorf("unknown selection policy %s", fsrv.SelectionPolicy)
} }
return nil return nil
} }
func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error { func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer) repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
filesToHide := fsrv.transformHidePaths(repl)
// map the request to a filename // map the request to a filename
pathBefore := r.URL.Path pathBefore := r.URL.Path
filename := sf.selectFile(r, repl) filename := fsrv.selectFile(r, repl, filesToHide)
if filename == "" { if filename == "" {
// no files worked, so resort to fallback // no files worked, so resort to fallback
if sf.Fallback != nil { if fsrv.Fallback != nil {
fallback := sf.Fallback.BuildCompositeRoute(w, r) fallback := fsrv.Fallback.BuildCompositeRoute(w, r)
return fallback.ServeHTTP(w, r) return fallback.ServeHTTP(w, r)
} }
return caddyhttp.Error(http.StatusNotFound, nil) return caddyhttp.Error(http.StatusNotFound, nil)
@ -111,7 +113,7 @@ func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// if the ultimate destination has changed, submit // if the ultimate destination has changed, submit
// this request for a rehandling (internal redirect) // this request for a rehandling (internal redirect)
// if configured to do so // if configured to do so
if r.URL.Path != pathBefore && sf.Rehandle { if r.URL.Path != pathBefore && fsrv.Rehandle {
return caddyhttp.ErrRehandle return caddyhttp.ErrRehandle
} }
@ -130,10 +132,8 @@ func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// if the request mapped to a directory, see if // if the request mapped to a directory, see if
// there is an index file we can serve // there is an index file we can serve
if info.IsDir() && len(sf.IndexNames) > 0 { if info.IsDir() && len(fsrv.IndexNames) > 0 {
filesToHide := sf.transformHidePaths(repl) for _, indexPage := range fsrv.IndexNames {
for _, indexPage := range sf.IndexNames {
indexPath := sanitizedPathJoin(filename, indexPage) indexPath := sanitizedPathJoin(filename, indexPage)
if fileHidden(indexPath, filesToHide) { if fileHidden(indexPath, filesToHide) {
// pretend this file doesn't exist // pretend this file doesn't exist
@ -149,7 +149,7 @@ func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// so rewrite the request path and, if // so rewrite the request path and, if
// configured, do an internal redirect // configured, do an internal redirect
r.URL.Path = path.Join(r.URL.Path, indexPage) r.URL.Path = path.Join(r.URL.Path, indexPage)
if sf.Rehandle { if fsrv.Rehandle {
return caddyhttp.ErrRehandle return caddyhttp.ErrRehandle
} }
@ -162,16 +162,22 @@ func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// if still referencing a directory, delegate // if still referencing a directory, delegate
// to browse or return an error // to browse or return an error
if info.IsDir() { if info.IsDir() {
if sf.Browse != nil { if fsrv.Browse != nil && !fileHidden(filename, filesToHide) {
return sf.serveBrowse(filename, w, r) return fsrv.serveBrowse(filename, w, r)
} }
return caddyhttp.Error(http.StatusNotFound, nil) return caddyhttp.Error(http.StatusNotFound, nil)
} }
// TODO: content negotiation (brotli sidecar files, etc...) // TODO: content negotiation (brotli sidecar files, etc...)
// one last check to ensure the file isn't hidden (we might
// have changed the filename from when we last checked)
if fileHidden(filename, filesToHide) {
return caddyhttp.Error(http.StatusNotFound, nil)
}
// open the file // open the file
file, err := sf.openFile(filename, w) file, err := fsrv.openFile(filename, w)
if err != nil { if err != nil {
return err return err
} }
@ -179,6 +185,8 @@ func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// TODO: Etag // TODO: Etag
// TODO: Disable content-type sniffing by setting a content-type...
// let the standard library do what it does best; note, however, // let the standard library do what it does best; note, however,
// that errors generated by ServeContent are written immediately // that errors generated by ServeContent are written immediately
// to the response, so we cannot handle them (but errors here // to the response, so we cannot handle them (but errors here
@ -192,7 +200,7 @@ func (sf *StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// the response is configured to inform the client how to best handle it // the response is configured to inform the client how to best handle it
// and a well-described handler error is returned (do not wrap the // and a well-described handler error is returned (do not wrap the
// returned error value). // returned error value).
func (sf *StaticFiles) openFile(filename string, w http.ResponseWriter) (*os.File, error) { func (fsrv *FileServer) openFile(filename string, w http.ResponseWriter) (*os.File, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
err = mapDirOpenError(err, filename) err = mapDirOpenError(err, filename)
@ -239,11 +247,11 @@ func mapDirOpenError(originalErr error, name string) error {
} }
// transformHidePaths performs replacements for all the elements of // transformHidePaths performs replacements for all the elements of
// sf.Hide and returns a new list of the transformed values. // fsrv.Hide and returns a new list of the transformed values.
func (sf *StaticFiles) transformHidePaths(repl caddy2.Replacer) []string { func (fsrv *FileServer) transformHidePaths(repl caddy2.Replacer) []string {
hide := make([]string, len(sf.Hide)) hide := make([]string, len(fsrv.Hide))
for i := range sf.Hide { for i := range fsrv.Hide {
hide[i] = repl.ReplaceAll(sf.Hide[i], "") hide[i] = repl.ReplaceAll(fsrv.Hide[i], "")
} }
return hide return hide
} }
@ -251,7 +259,8 @@ func (sf *StaticFiles) transformHidePaths(repl caddy2.Replacer) []string {
// sanitizedPathJoin performs filepath.Join(root, reqPath) that // sanitizedPathJoin performs filepath.Join(root, reqPath) that
// is safe against directory traversal attacks. It uses logic // is safe against directory traversal attacks. It uses logic
// similar to that in the Go standard library, specifically // similar to that in the Go standard library, specifically
// in the implementation of http.Dir. // in the implementation of http.Dir. The root is assumed to
// be a trusted path, but reqPath is not.
func sanitizedPathJoin(root, reqPath string) string { func sanitizedPathJoin(root, reqPath string) string {
// TODO: Caddy 1 uses this: // TODO: Caddy 1 uses this:
// prevent absolute path access on Windows, e.g. http://localhost:5000/C:\Windows\notepad.exe // prevent absolute path access on Windows, e.g. http://localhost:5000/C:\Windows\notepad.exe
@ -276,17 +285,17 @@ func sanitizedPathJoin(root, reqPath string) string {
// by default) to map the request r to a filename. The full path to // by default) to map the request r to a filename. The full path to
// the file is returned if one is found; otherwise, an empty string // the file is returned if one is found; otherwise, an empty string
// is returned. // is returned.
func (sf *StaticFiles) selectFile(r *http.Request, repl caddy2.Replacer) string { func (fsrv *FileServer) selectFile(r *http.Request, repl caddy2.Replacer, filesToHide []string) string {
root := repl.ReplaceAll(sf.Root, "") root := repl.ReplaceAll(fsrv.Root, "")
if sf.Files == nil { if fsrv.Files == nil {
return sanitizedPathJoin(root, r.URL.Path) return sanitizedPathJoin(root, r.URL.Path)
} }
switch sf.SelectionPolicy { switch fsrv.SelectionPolicy {
case "", selectionPolicyFirstExisting: case "", selectionPolicyFirstExisting:
filesToHide := sf.transformHidePaths(repl) filesToHide := fsrv.transformHidePaths(repl)
for _, f := range sf.Files { for _, f := range fsrv.Files {
suffix := repl.ReplaceAll(f, "") suffix := repl.ReplaceAll(f, "")
fullpath := sanitizedPathJoin(root, suffix) fullpath := sanitizedPathJoin(root, suffix)
if !fileHidden(fullpath, filesToHide) && fileExists(fullpath) { if !fileHidden(fullpath, filesToHide) && fileExists(fullpath) {
@ -299,9 +308,12 @@ func (sf *StaticFiles) selectFile(r *http.Request, repl caddy2.Replacer) string
var largestSize int64 var largestSize int64
var largestFilename string var largestFilename string
var largestSuffix string var largestSuffix string
for _, f := range sf.Files { for _, f := range fsrv.Files {
suffix := repl.ReplaceAll(f, "") suffix := repl.ReplaceAll(f, "")
fullpath := sanitizedPathJoin(root, suffix) fullpath := sanitizedPathJoin(root, suffix)
if fileHidden(fullpath, filesToHide) {
continue
}
info, err := os.Stat(fullpath) info, err := os.Stat(fullpath)
if err == nil && info.Size() > largestSize { if err == nil && info.Size() > largestSize {
largestSize = info.Size() largestSize = info.Size()
@ -316,9 +328,12 @@ func (sf *StaticFiles) selectFile(r *http.Request, repl caddy2.Replacer) string
var smallestSize int64 var smallestSize int64
var smallestFilename string var smallestFilename string
var smallestSuffix string var smallestSuffix string
for _, f := range sf.Files { for _, f := range fsrv.Files {
suffix := repl.ReplaceAll(f, "") suffix := repl.ReplaceAll(f, "")
fullpath := sanitizedPathJoin(root, suffix) fullpath := sanitizedPathJoin(root, suffix)
if fileHidden(fullpath, filesToHide) {
continue
}
info, err := os.Stat(fullpath) info, err := os.Stat(fullpath)
if err == nil && (smallestSize == 0 || info.Size() < smallestSize) { if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
smallestSize = info.Size() smallestSize = info.Size()
@ -333,9 +348,12 @@ func (sf *StaticFiles) selectFile(r *http.Request, repl caddy2.Replacer) string
var recentDate time.Time var recentDate time.Time
var recentFilename string var recentFilename string
var recentSuffix string var recentSuffix string
for _, f := range sf.Files { for _, f := range fsrv.Files {
suffix := repl.ReplaceAll(f, "") suffix := repl.ReplaceAll(f, "")
fullpath := sanitizedPathJoin(root, suffix) fullpath := sanitizedPathJoin(root, suffix)
if fileHidden(fullpath, filesToHide) {
continue
}
info, err := os.Stat(fullpath) info, err := os.Stat(fullpath)
if err == nil && if err == nil &&
(recentDate.IsZero() || info.ModTime().After(recentDate)) { (recentDate.IsZero() || info.ModTime().After(recentDate)) {
@ -395,4 +413,4 @@ var defaultIndexNames = []string{"index.html"}
const minBackoff, maxBackoff = 2, 5 const minBackoff, maxBackoff = 2, 5
// Interface guard // Interface guard
var _ caddyhttp.Handler = (*StaticFiles)(nil) var _ caddyhttp.Handler = (*FileServer)(nil)

5
modules/caddyhttp/staticfiles/staticfiles_test.go → modules/caddyhttp/fileserver/staticfiles_test.go

@ -1,4 +1,4 @@
package staticfiles package fileserver
import ( import (
"net/url" "net/url"
@ -60,6 +60,7 @@ func TestSanitizedPathJoin(t *testing.T) {
inputPath: "/%2e%2e%2f%2e%2e%2f", inputPath: "/%2e%2e%2f%2e%2e%2f",
expect: "/a/b", expect: "/a/b",
}, },
// TODO: test windows paths... on windows... sigh.
} { } {
// we don't *need* to use an actual parsed URL, but it // we don't *need* to use an actual parsed URL, but it
// adds some authenticity to the tests since real-world // adds some authenticity to the tests since real-world
@ -76,3 +77,5 @@ func TestSanitizedPathJoin(t *testing.T) {
} }
} }
} }
// TODO: test fileHidden

23
modules/caddyhttp/server.go

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strconv"
"bitbucket.org/lightcodelabs/caddy2" "bitbucket.org/lightcodelabs/caddy2"
"bitbucket.org/lightcodelabs/caddy2/modules/caddytls" "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
@ -41,15 +42,27 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
stack := s.Routes.BuildCompositeRoute(w, r) stack := s.Routes.BuildCompositeRoute(w, r)
err := s.executeCompositeRoute(w, r, stack) err := s.executeCompositeRoute(w, r, stack)
if err != nil { if err != nil {
// add the error value to the request context so // add the raw error value to the request context
// it can be accessed by error handlers // so it can be accessed by error handlers
c := context.WithValue(r.Context(), ErrorCtxKey, err) c := context.WithValue(r.Context(), ErrorCtxKey, err)
r = r.WithContext(c) r = r.WithContext(c)
// TODO: add error values to Replacer
// add error values to the replacer
repl.Set("http.error", err.Error())
if handlerErr, ok := err.(HandlerError); ok {
repl.Set("http.error.status_code", strconv.Itoa(handlerErr.StatusCode))
repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode))
repl.Set("http.error.message", handlerErr.Message)
repl.Set("http.error.trace", handlerErr.Trace)
repl.Set("http.error.id", handlerErr.ID)
}
if len(s.Errors.Routes) == 0 { if len(s.Errors.Routes) == 0 {
// TODO: implement a default error handler? // TODO: polish the default error handling
log.Printf("[ERROR] %s", err) log.Printf("[ERROR] Handler: %s", err)
if handlerErr, ok := err.(HandlerError); ok {
w.WriteHeader(handlerErr.StatusCode)
}
} else { } else {
errStack := s.Errors.Routes.BuildCompositeRoute(w, r) errStack := s.Errors.Routes.BuildCompositeRoute(w, r)
err := s.executeCompositeRoute(w, r, errStack) err := s.executeCompositeRoute(w, r, errStack)

16
modules/caddyhttp/staticresp.go

@ -3,6 +3,7 @@ package caddyhttp
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"bitbucket.org/lightcodelabs/caddy2" "bitbucket.org/lightcodelabs/caddy2"
) )
@ -16,10 +17,11 @@ func init() {
// Static implements a simple responder for static responses. // Static implements a simple responder for static responses.
type Static struct { type Static struct {
StatusCode int `json:"status_code"` StatusCode int `json:"status_code"`
Headers http.Header `json:"headers"` StatusCodeStr string `json:"status_code_str"`
Body string `json:"body"` Headers http.Header `json:"headers"`
Close bool `json:"close"` Body string `json:"body"`
Close bool `json:"close"`
} }
func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error { func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
@ -39,6 +41,12 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
// write the headers with a status code // write the headers with a status code
statusCode := s.StatusCode statusCode := s.StatusCode
if statusCode == 0 && s.StatusCodeStr != "" {
intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCodeStr, ""))
if err == nil {
statusCode = intVal
}
}
if statusCode == 0 { if statusCode == 0 {
statusCode = http.StatusOK statusCode = http.StatusOK
} }

Loading…
Cancel
Save