mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			312 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			312 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddyhttp
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2"
 | |
| 	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	caddy.RegisterModule(tlsPlaceholderWrapper{})
 | |
| }
 | |
| 
 | |
| // RequestMatcher is a type that can match to a request.
 | |
| // A route matcher MUST NOT modify the request, with the
 | |
| // only exception being its context.
 | |
| type RequestMatcher interface {
 | |
| 	Match(*http.Request) bool
 | |
| }
 | |
| 
 | |
| // Handler is like http.Handler except ServeHTTP may return an error.
 | |
| //
 | |
| // If any handler encounters an error, it should be returned for proper
 | |
| // handling. Return values should be propagated down the middleware chain
 | |
| // by returning it unchanged. Returned errors should not be re-wrapped
 | |
| // if they are already HandlerError values.
 | |
| type Handler interface {
 | |
| 	ServeHTTP(http.ResponseWriter, *http.Request) error
 | |
| }
 | |
| 
 | |
| // HandlerFunc is a convenience type like http.HandlerFunc.
 | |
| type HandlerFunc func(http.ResponseWriter, *http.Request) error
 | |
| 
 | |
| // ServeHTTP implements the Handler interface.
 | |
| func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
 | |
| 	return f(w, r)
 | |
| }
 | |
| 
 | |
| // Middleware chains one Handler to the next by being passed
 | |
| // the next Handler in the chain.
 | |
| type Middleware func(Handler) Handler
 | |
| 
 | |
| // MiddlewareHandler is like Handler except it takes as a third
 | |
| // argument the next handler in the chain. The next handler will
 | |
| // never be nil, but may be a no-op handler if this is the last
 | |
| // handler in the chain. Handlers which act as middleware should
 | |
| // call the next handler's ServeHTTP method so as to propagate
 | |
| // the request down the chain properly. Handlers which act as
 | |
| // responders (content origins) need not invoke the next handler,
 | |
| // since the last handler in the chain should be the first to
 | |
| // write the response.
 | |
| type MiddlewareHandler interface {
 | |
| 	ServeHTTP(http.ResponseWriter, *http.Request, Handler) error
 | |
| }
 | |
| 
 | |
| // emptyHandler is used as a no-op handler.
 | |
| var emptyHandler Handler = HandlerFunc(func(http.ResponseWriter, *http.Request) error { return nil })
 | |
| 
 | |
| // An implicit suffix middleware that, if reached, sets the StatusCode to the
 | |
| // error stored in the ErrorCtxKey. This is to prevent situations where the
 | |
| // Error chain does not actually handle the error (for instance, it matches only
 | |
| // on some errors). See #3053
 | |
| var errorEmptyHandler Handler = HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
 | |
| 	httpError := r.Context().Value(ErrorCtxKey)
 | |
| 	if handlerError, ok := httpError.(HandlerError); ok {
 | |
| 		w.WriteHeader(handlerError.StatusCode)
 | |
| 	} else {
 | |
| 		w.WriteHeader(http.StatusInternalServerError)
 | |
| 	}
 | |
| 	return nil
 | |
| })
 | |
| 
 | |
| // ResponseHandler pairs a response matcher with custom handling
 | |
| // logic. Either the status code can be changed to something else
 | |
| // while using the original response body, or, if a status code
 | |
| // is not set, it can execute a custom route list; this is useful
 | |
| // for executing handler routes based on the properties of an HTTP
 | |
| // response that has not been written out to the client yet.
 | |
| //
 | |
| // To use this type, provision it at module load time, then when
 | |
| // ready to use, match the response against its matcher; if it
 | |
| // matches (or doesn't have a matcher), change the status code on
 | |
| // the response if configured; otherwise invoke the routes by
 | |
| // calling `rh.Routes.Compile(next).ServeHTTP(rw, req)` (or similar).
 | |
| type ResponseHandler struct {
 | |
| 	// The response matcher for this handler. If empty/nil,
 | |
| 	// it always matches.
 | |
| 	Match *ResponseMatcher `json:"match,omitempty"`
 | |
| 
 | |
| 	// To write the original response body but with a different
 | |
| 	// status code, set this field to the desired status code.
 | |
| 	// If set, this takes priority over routes.
 | |
| 	StatusCode WeakString `json:"status_code,omitempty"`
 | |
| 
 | |
| 	// The list of HTTP routes to execute if no status code is
 | |
| 	// specified. If evaluated, the original response body
 | |
| 	// will not be written.
 | |
| 	Routes RouteList `json:"routes,omitempty"`
 | |
| }
 | |
| 
 | |
| // Provision sets up the routse in rh.
 | |
| func (rh *ResponseHandler) Provision(ctx caddy.Context) error {
 | |
| 	if rh.Routes != nil {
 | |
| 		err := rh.Routes.Provision(ctx)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // WeakString is a type that unmarshals any JSON value
 | |
| // as a string literal, with the following exceptions:
 | |
| //
 | |
| // 1. actual string values are decoded as strings; and
 | |
| // 2. null is decoded as empty string;
 | |
| //
 | |
| // and provides methods for getting the value as various
 | |
| // primitive types. However, using this type removes any
 | |
| // type safety as far as deserializing JSON is concerned.
 | |
| type WeakString string
 | |
| 
 | |
| // UnmarshalJSON satisfies json.Unmarshaler according to
 | |
| // this type's documentation.
 | |
| func (ws *WeakString) UnmarshalJSON(b []byte) error {
 | |
| 	if len(b) == 0 {
 | |
| 		return io.EOF
 | |
| 	}
 | |
| 	if b[0] == byte('"') && b[len(b)-1] == byte('"') {
 | |
| 		var s string
 | |
| 		err := json.Unmarshal(b, &s)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		*ws = WeakString(s)
 | |
| 		return nil
 | |
| 	}
 | |
| 	if bytes.Equal(b, []byte("null")) {
 | |
| 		return nil
 | |
| 	}
 | |
| 	*ws = WeakString(b)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // MarshalJSON marshals was a boolean if true or false,
 | |
| // a number if an integer, or a string otherwise.
 | |
| func (ws WeakString) MarshalJSON() ([]byte, error) {
 | |
| 	if ws == "true" {
 | |
| 		return []byte("true"), nil
 | |
| 	}
 | |
| 	if ws == "false" {
 | |
| 		return []byte("false"), nil
 | |
| 	}
 | |
| 	if num, err := strconv.Atoi(string(ws)); err == nil {
 | |
| 		return json.Marshal(num)
 | |
| 	}
 | |
| 	return json.Marshal(string(ws))
 | |
| }
 | |
| 
 | |
| // Int returns ws as an integer. If ws is not an
 | |
| // integer, 0 is returned.
 | |
| func (ws WeakString) Int() int {
 | |
| 	num, _ := strconv.Atoi(string(ws))
 | |
| 	return num
 | |
| }
 | |
| 
 | |
| // Float64 returns ws as a float64. If ws is not a
 | |
| // float value, the zero value is returned.
 | |
| func (ws WeakString) Float64() float64 {
 | |
| 	num, _ := strconv.ParseFloat(string(ws), 64)
 | |
| 	return num
 | |
| }
 | |
| 
 | |
| // Bool returns ws as a boolean. If ws is not a
 | |
| // boolean, false is returned.
 | |
| func (ws WeakString) Bool() bool {
 | |
| 	return string(ws) == "true"
 | |
| }
 | |
| 
 | |
| // String returns ws as a string.
 | |
| func (ws WeakString) String() string {
 | |
| 	return string(ws)
 | |
| }
 | |
| 
 | |
| // StatusCodeMatches returns true if a real HTTP status code matches
 | |
| // the configured status code, which may be either a real HTTP status
 | |
| // code or an integer representing a class of codes (e.g. 4 for all
 | |
| // 4xx statuses).
 | |
| func StatusCodeMatches(actual, configured int) bool {
 | |
| 	if actual == configured {
 | |
| 		return true
 | |
| 	}
 | |
| 	if configured < 100 &&
 | |
| 		actual >= configured*100 &&
 | |
| 		actual < (configured+1)*100 {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // SanitizedPathJoin performs filepath.Join(root, reqPath) that
 | |
| // is safe against directory traversal attacks. It uses logic
 | |
| // similar to that in the Go standard library, specifically
 | |
| // in the implementation of http.Dir. The root is assumed to
 | |
| // be a trusted path, but reqPath is not; and the output will
 | |
| // never be outside of root. The resulting path can be used
 | |
| // with the local file system.
 | |
| func SanitizedPathJoin(root, reqPath string) string {
 | |
| 	if root == "" {
 | |
| 		root = "."
 | |
| 	}
 | |
| 
 | |
| 	path := filepath.Join(root, filepath.Clean("/"+reqPath))
 | |
| 
 | |
| 	// filepath.Join also cleans the path, and cleaning strips
 | |
| 	// the trailing slash, so we need to re-add it afterwards.
 | |
| 	// if the length is 1, then it's a path to the root,
 | |
| 	// and that should return ".", so we don't append the separator.
 | |
| 	if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
 | |
| 		path += separator
 | |
| 	}
 | |
| 
 | |
| 	return path
 | |
| }
 | |
| 
 | |
| // CleanPath cleans path p according to path.Clean(), but only
 | |
| // merges repeated slashes if collapseSlashes is true, and always
 | |
| // preserves trailing slashes.
 | |
| func CleanPath(p string, collapseSlashes bool) string {
 | |
| 	if collapseSlashes {
 | |
| 		return cleanPath(p)
 | |
| 	}
 | |
| 
 | |
| 	// insert an invalid/impossible URI character into each two consecutive
 | |
| 	// slashes to expand empty path segments; then clean the path as usual,
 | |
| 	// and then remove the remaining temporary characters.
 | |
| 	const tmpCh = 0xff
 | |
| 	var sb strings.Builder
 | |
| 	for i, ch := range p {
 | |
| 		if ch == '/' && i > 0 && p[i-1] == '/' {
 | |
| 			sb.WriteByte(tmpCh)
 | |
| 		}
 | |
| 		sb.WriteRune(ch)
 | |
| 	}
 | |
| 	halfCleaned := cleanPath(sb.String())
 | |
| 	halfCleaned = strings.ReplaceAll(halfCleaned, string([]byte{tmpCh}), "")
 | |
| 
 | |
| 	return halfCleaned
 | |
| }
 | |
| 
 | |
| // cleanPath does path.Clean(p) but preserves any trailing slash.
 | |
| func cleanPath(p string) string {
 | |
| 	cleaned := path.Clean(p)
 | |
| 	if cleaned != "/" && strings.HasSuffix(p, "/") {
 | |
| 		cleaned = cleaned + "/"
 | |
| 	}
 | |
| 	return cleaned
 | |
| }
 | |
| 
 | |
| // tlsPlaceholderWrapper is a no-op listener wrapper that marks
 | |
| // where the TLS listener should be in a chain of listener wrappers.
 | |
| // It should only be used if another listener wrapper must be placed
 | |
| // in front of the TLS handshake.
 | |
| type tlsPlaceholderWrapper struct{}
 | |
| 
 | |
| func (tlsPlaceholderWrapper) CaddyModule() caddy.ModuleInfo {
 | |
| 	return caddy.ModuleInfo{
 | |
| 		ID:  "caddy.listeners.tls",
 | |
| 		New: func() caddy.Module { return new(tlsPlaceholderWrapper) },
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (tlsPlaceholderWrapper) WrapListener(ln net.Listener) net.Listener { return ln }
 | |
| 
 | |
| func (tlsPlaceholderWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil }
 | |
| 
 | |
| const (
 | |
| 	// DefaultHTTPPort is the default port for HTTP.
 | |
| 	DefaultHTTPPort = 80
 | |
| 
 | |
| 	// DefaultHTTPSPort is the default port for HTTPS.
 | |
| 	DefaultHTTPSPort = 443
 | |
| )
 | |
| 
 | |
| const separator = string(filepath.Separator)
 | |
| 
 | |
| // Interface guard
 | |
| var _ caddy.ListenerWrapper = (*tlsPlaceholderWrapper)(nil)
 | |
| var _ caddyfile.Unmarshaler = (*tlsPlaceholderWrapper)(nil)
 |