caddy/caddyhttp/httpserver/middleware.go
Matt Holt d5371aff22 httpserver/all: Clean up and standardize request URL handling (#1633)
* httpserver/all: Clean up and standardize request URL handling

The HTTP server now always creates a context value on the request which
is a copy of the request's URL struct. It should not be modified by
middlewares, but it is safe to get the value out of the request and make
changes to it locally-scoped. Thus, the value in the context always
stores the original request URL information as it was received. Any
rewrites that happen will be to the request's URL field directly.

The HTTP server no longer cleans /sanitizes the request URL. It made too
many strong assumptions and ended up making a lot of middleware more
complicated, including upstream proxying (and fastcgi). To alleviate
this complexity, we no longer change the request URL. Middlewares are
responsible to access the disk safely by using http.Dir or, if not
actually opening files, they can use httpserver.SafePath().

I'm hoping this will address issues with #1624, #1584, #1582, and others.

* staticfiles: Fix test on Windows

@abiosoft: I still can't figure out exactly what this is for. 😅

* Use (potentially) changed URL for browse redirects, as before

* Use filepath.ToSlash, clean up a couple proxy test cases

* Oops, fix variable name
2017-05-01 23:11:10 -06:00

209 lines
6.7 KiB
Go

package httpserver
import (
"fmt"
"net/http"
"os"
"path"
"time"
"github.com/mholt/caddy"
)
func init() {
initCaseSettings()
}
type (
// Middleware is the middle layer which represents the traditional
// idea of middleware: it chains one Handler to the next by being
// passed the next Handler in the chain.
Middleware func(Handler) Handler
// ListenerMiddleware is similar to the Middleware type, except it
// chains one net.Listener to the next.
ListenerMiddleware func(caddy.Listener) caddy.Listener
// Handler is like http.Handler except ServeHTTP may return a status
// code and/or error.
//
// If ServeHTTP writes the response header, it should return a status
// code of 0. This signals to other handlers before it that the response
// is already handled, and that they should not write to it also. Keep
// in mind that writing to the response body writes the header, too.
//
// If ServeHTTP encounters an error, it should return the error value
// so it can be logged by designated error-handling middleware.
//
// If writing a response after calling the next ServeHTTP method, the
// returned status code SHOULD be used when writing the response.
//
// If handling errors after calling the next ServeHTTP method, the
// returned error value SHOULD be logged or handled accordingly.
//
// Otherwise, return values should be propagated down the middleware
// chain by returning them unchanged.
Handler interface {
ServeHTTP(http.ResponseWriter, *http.Request) (int, error)
}
// HandlerFunc is a convenience type like http.HandlerFunc, except
// ServeHTTP returns a status code and an error. See Handler
// documentation for more information.
HandlerFunc func(http.ResponseWriter, *http.Request) (int, error)
// RequestMatcher checks to see if current request should be handled
// by underlying handler.
RequestMatcher interface {
Match(r *http.Request) bool
}
// HandlerConfig is a middleware configuration.
// This makes it possible for middlewares to have a common
// configuration interface.
//
// TODO The long term plan is to get all middleware implement this
// interface for configurations.
HandlerConfig interface {
RequestMatcher
BasePath() string
}
// ConfigSelector selects a configuration.
ConfigSelector []HandlerConfig
)
// ServeHTTP implements the Handler interface.
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
return f(w, r)
}
// Select selects a Config.
// This chooses the config with the longest length.
func (c ConfigSelector) Select(r *http.Request) (config HandlerConfig) {
for i := range c {
if !c[i].Match(r) {
continue
}
if config == nil || len(c[i].BasePath()) > len(config.BasePath()) {
config = c[i]
}
}
return config
}
// IndexFile looks for a file in /root/fpath/indexFile for each string
// in indexFiles. If an index file is found, it returns the root-relative
// path to the file and true. If no index file is found, empty string
// and false is returned. fpath must end in a forward slash '/'
// otherwise no index files will be tried (directory paths must end
// in a forward slash according to HTTP).
//
// All paths passed into and returned from this function use '/' as the
// path separator, just like URLs. IndexFle handles path manipulation
// internally for systems that use different path separators.
func IndexFile(root http.FileSystem, fpath string, indexFiles []string) (string, bool) {
if fpath[len(fpath)-1] != '/' || root == nil {
return "", false
}
for _, indexFile := range indexFiles {
// func (http.FileSystem).Open wants all paths separated by "/",
// regardless of operating system convention, so use
// path.Join instead of filepath.Join
fp := path.Join(fpath, indexFile)
f, err := root.Open(fp)
if err == nil {
f.Close()
return fp, true
}
}
return "", false
}
// SetLastModifiedHeader checks if the provided modTime is valid and if it is sets it
// as a Last-Modified header to the ResponseWriter. If the modTime is in the future
// the current time is used instead.
func SetLastModifiedHeader(w http.ResponseWriter, modTime time.Time) {
if modTime.IsZero() || modTime.Equal(time.Unix(0, 0)) {
// the time does not appear to be valid. Don't put it in the response
return
}
// RFC 2616 - Section 14.29 - Last-Modified:
// An origin server MUST NOT send a Last-Modified date which is later than the
// server's time of message origination. In such cases, where the resource's last
// modification would indicate some time in the future, the server MUST replace
// that date with the message origination date.
now := currentTime()
if modTime.After(now) {
modTime = now
}
w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat))
}
// CaseSensitivePath determines if paths should be case sensitive.
// This is configurable via CASE_SENSITIVE_PATH environment variable.
var CaseSensitivePath = true
const caseSensitivePathEnv = "CASE_SENSITIVE_PATH"
// initCaseSettings loads case sensitivity config from environment variable.
//
// This could have been in init, but init cannot be called from tests.
func initCaseSettings() {
switch os.Getenv(caseSensitivePathEnv) {
case "0", "false":
CaseSensitivePath = false
default:
CaseSensitivePath = true
}
}
// MergeRequestMatchers merges multiple RequestMatchers into one.
// This allows a middleware to use multiple RequestMatchers.
func MergeRequestMatchers(matchers ...RequestMatcher) RequestMatcher {
return requestMatchers(matchers)
}
type requestMatchers []RequestMatcher
// Match satisfies RequestMatcher interface.
func (m requestMatchers) Match(r *http.Request) bool {
for _, matcher := range m {
if !matcher.Match(r) {
return false
}
}
return true
}
// currentTime, as it is defined here, returns time.Now().
// It's defined as a variable for mocking time in tests.
var currentTime = func() time.Time { return time.Now() }
// EmptyNext is a no-op function that can be passed into
// Middleware functions so that the assignment to the
// Next field of the Handler can be tested.
//
// Used primarily for testing but needs to be exported so
// plugins can use this as a convenience.
var EmptyNext = HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return 0, nil })
// SameNext does a pointer comparison between next1 and next2.
//
// Used primarily for testing but needs to be exported so
// plugins can use this as a convenience.
func SameNext(next1, next2 Handler) bool {
return fmt.Sprintf("%v", next1) == fmt.Sprintf("%v", next2)
}
// Context key constants.
const (
// RemoteUserCtxKey is the key for the remote user of the request, if any (basicauth).
RemoteUserCtxKey caddy.CtxKey = "remote_user"
// MitmCtxKey is the key for the result of MITM detection
MitmCtxKey caddy.CtxKey = "mitm"
)