github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/safehttp/mux.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package safehttp
    16  
    17  import (
    18  	"fmt"
    19  	"log"
    20  	"net/http"
    21  )
    22  
    23  // The HTTP request methods defined by RFC.
    24  const (
    25  	MethodConnect = "CONNECT" // RFC 7231, 4.3.6
    26  	MethodDelete  = "DELETE"  // RFC 7231, 4.3.5
    27  	MethodGet     = "GET"     // RFC 7231, 4.3.1
    28  	MethodHead    = "HEAD"    // RFC 7231, 4.3.2
    29  	MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
    30  	MethodPatch   = "PATCH"   // RFC 5789
    31  	MethodPost    = "POST"    // RFC 7231, 4.3.3
    32  	MethodPut     = "PUT"     // RFC 7231, 4.3.4
    33  	MethodTrace   = "TRACE"   // RFC 7231, 4.3.8
    34  )
    35  
    36  // ServeMux is an HTTP request multiplexer. It matches the URL of each incoming
    37  // request against a list of registered patterns and calls the handler for
    38  // the pattern that most closely matches the URL.
    39  //
    40  // Patterns names are fixed, rooted paths, like "/favicon.ico", or rooted
    41  // subtrees like "/images/" (note the trailing slash). Longer patterns take
    42  // precedence over shorter ones, so that if there are handlers registered for
    43  // both "/images/" and "/images/thumbnails/", the latter handler will be called
    44  // for paths beginning "/images/thumbnails/" and the former will receive
    45  // requests for any other paths in the "/images/" subtree.
    46  //
    47  // Note that since a pattern ending in a slash names a rooted subtree, the
    48  // pattern "/" matches all paths not matched by other registered patterns,
    49  // not just the URL with Path == "/".
    50  //
    51  // If a subtree has been registered and a request is received naming the subtree
    52  // root without its trailing slash, ServeMux redirects that request to
    53  // the subtree root (adding the trailing slash). This behavior can be overridden
    54  // with a separate registration for the path without the trailing slash. For
    55  // example, registering "/images/" causes ServeMux to redirect a request for
    56  // "/images" to "/images/", unless "/images" has been registered separately.
    57  //
    58  // Patterns may optionally begin with a host name, restricting matches to URLs
    59  // on that host only.  Host-specific patterns take precedence over general
    60  // patterns, so that a handler might register for the two patterns "/codesearch"
    61  // and "codesearch.google.com/" without also taking over requests for
    62  // "http://www.google.com/".
    63  //
    64  // ServeMux also takes care of sanitizing the URL request path and the Host
    65  // header, stripping the port number and redirecting any request containing . or
    66  // .. elements or repeated slashes to an equivalent, cleaner URL.
    67  //
    68  // Multiple handlers can be registered for a single pattern, as long as they
    69  // handle different HTTP methods.
    70  type ServeMux struct {
    71  	mux      *http.ServeMux
    72  	handlers map[string]*registeredHandler
    73  
    74  	dispatcher       Dispatcher
    75  	interceptors     []Interceptor
    76  	methodNotAllowed handlerConfig
    77  }
    78  
    79  // ServeHTTP dispatches the request to the handler whose method matches the
    80  // incoming request and whose pattern most closely matches the request URL.
    81  //
    82  //	For each incoming request:
    83  //	- [Before Phase] Interceptor.Before methods are called for every installed
    84  //	  interceptor, until an interceptor writes to a ResponseWriter (including
    85  //	  errors) or panics,
    86  //	- the handler is called after a [Before Phase] if no writes or panics occured,
    87  //	- the handler triggers the [Commit Phase] by writing to the ResponseWriter,
    88  //	- [Commit Phase] Interceptor.Commit methods run for every interceptor whose
    89  //	  Before method was called,
    90  //	- [Dispatcher Phase] after the [Commit Phase], the Dispatcher's appropriate
    91  //	  write method is called; the Dispatcher is responsible for determining whether
    92  //	  the response is indeed safe and writing it,
    93  //	- if the handler attempts to write more than once, it is treated as an
    94  //	  unrecoverable error; the request processing ends abrubptly with a panic and
    95  //	  nothing else happens (note: this will change as soon as [After Phase] is
    96  //	  introduced)
    97  //
    98  // Interceptors should NOT rely on the order they're run.
    99  func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   100  	m.mux.ServeHTTP(w, r)
   101  }
   102  
   103  // Handle registers a handler for the given pattern and method. If a handler is
   104  // registered twice for the same pattern and method, Build will panic.
   105  //
   106  // InterceptorConfigs can be passed in order to modify the behavior of the
   107  // interceptors on a registered handler. Passing an InterceptorConfig whose
   108  // corresponding Interceptor was not installed will produce no effect. If
   109  // multiple configurations are passed for the same Interceptor, Mux will panic.
   110  func (m *ServeMux) Handle(pattern string, method string, h Handler, cfgs ...InterceptorConfig) {
   111  	if m.handlers[pattern] == nil {
   112  		m.handlers[pattern] = &registeredHandler{
   113  			pattern:          pattern,
   114  			methodNotAllowed: m.methodNotAllowed,
   115  			methods:          make(map[string]handlerConfig),
   116  		}
   117  		m.mux.Handle(pattern, m.handlers[pattern])
   118  	}
   119  	m.handlers[pattern].handleMethod(method,
   120  		handlerConfig{
   121  			Dispatcher:   m.dispatcher,
   122  			Handler:      h,
   123  			Interceptors: configureInterceptors(m.interceptors, cfgs),
   124  		})
   125  }
   126  
   127  // ServeMuxConfig is a builder for ServeMux.
   128  type ServeMuxConfig struct {
   129  	dispatcher   Dispatcher
   130  	interceptors []Interceptor
   131  
   132  	methodNotAllowed     Handler
   133  	methodNotAllowedCfgs []InterceptorConfig
   134  }
   135  
   136  // NewServeMuxConfig crates a ServeMuxConfig with the provided Dispatcher. If
   137  // the provided Dispatcher is nil, the DefaultDispatcher is used.
   138  func NewServeMuxConfig(disp Dispatcher) *ServeMuxConfig {
   139  	if disp == nil {
   140  		disp = &DefaultDispatcher{}
   141  	}
   142  	return &ServeMuxConfig{
   143  		dispatcher:       disp,
   144  		methodNotAllowed: HandlerFunc(defaultMethotNotAllowed),
   145  	}
   146  }
   147  
   148  // HandleMethodNotAllowed registers a handler that runs when a given method is
   149  // not allowed for a registered path.
   150  func (s *ServeMuxConfig) HandleMethodNotAllowed(h Handler, cfgs ...InterceptorConfig) {
   151  	s.methodNotAllowed = h
   152  	s.methodNotAllowedCfgs = cfgs
   153  }
   154  
   155  var defaultMethotNotAllowed = HandlerFunc(func(w ResponseWriter, req *IncomingRequest) Result {
   156  	return w.WriteError(StatusMethodNotAllowed)
   157  })
   158  
   159  // Intercept installs the given interceptors.
   160  //
   161  // Interceptors order is respected and interceptors are always run in the
   162  // order they've been installed.
   163  //
   164  // Calling Intercept multiple times is valid. Interceptors that are added last
   165  // will run last.
   166  func (s *ServeMuxConfig) Intercept(is ...Interceptor) {
   167  	s.interceptors = append(s.interceptors, is...)
   168  }
   169  
   170  // Mux returns the ServeMux with a copy of the current configuration.
   171  func (s *ServeMuxConfig) Mux() *ServeMux {
   172  	devMu.Lock()
   173  	freezeLocalDev = true
   174  	if isLocalDev {
   175  		log.Println("Warning: creating safehttp.Mux in dev mode. This configuration is not valid for production use")
   176  	}
   177  	devMu.Unlock()
   178  
   179  	if s.dispatcher == nil {
   180  		panic("Use NewServeMuxConfig instead of creating ServeMuxConfig using a composite literal.")
   181  	}
   182  
   183  	methodNotAllowed := handlerConfig{
   184  		Dispatcher:   s.dispatcher,
   185  		Handler:      s.methodNotAllowed,
   186  		Interceptors: configureInterceptors(s.interceptors, s.methodNotAllowedCfgs),
   187  	}
   188  
   189  	m := &ServeMux{
   190  		mux:              http.NewServeMux(),
   191  		handlers:         make(map[string]*registeredHandler),
   192  		dispatcher:       s.dispatcher,
   193  		interceptors:     s.interceptors,
   194  		methodNotAllowed: methodNotAllowed,
   195  	}
   196  	return m
   197  }
   198  
   199  // Clone creates a copy of the current config.
   200  // This can be used to create several instances of Mux that share the same set of
   201  // plugins.
   202  func (s *ServeMuxConfig) Clone() *ServeMuxConfig {
   203  	return &ServeMuxConfig{
   204  		dispatcher:           s.dispatcher,
   205  		interceptors:         append([]Interceptor(nil), s.interceptors...),
   206  		methodNotAllowed:     s.methodNotAllowed,
   207  		methodNotAllowedCfgs: append([]InterceptorConfig(nil), s.methodNotAllowedCfgs...),
   208  	}
   209  }
   210  
   211  type registeredHandler struct {
   212  	pattern          string
   213  	methods          map[string]handlerConfig
   214  	methodNotAllowed handlerConfig
   215  }
   216  
   217  func (rh *registeredHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   218  	cfg, ok := rh.methods[r.Method]
   219  	if !ok {
   220  		cfg = rh.methodNotAllowed
   221  	}
   222  	processRequest(cfg, w, r)
   223  }
   224  
   225  func (rh *registeredHandler) handleMethod(method string, cfg handlerConfig) {
   226  	if _, exists := rh.methods[method]; exists {
   227  		panic(fmt.Sprintf("double registration of (pattern = %q, method = %q)", rh.pattern, method))
   228  	}
   229  	rh.methods[method] = cfg
   230  }
   231  
   232  func configureInterceptors(interceptors []Interceptor, cfgs []InterceptorConfig) []configuredInterceptor {
   233  	var its []configuredInterceptor
   234  	for _, it := range interceptors {
   235  		var matches []InterceptorConfig
   236  		for _, c := range cfgs {
   237  			if it.Match(c) {
   238  				matches = append(matches, c)
   239  			}
   240  		}
   241  
   242  		if len(matches) > 1 {
   243  			msg := fmt.Sprintf("multiple configurations specified for interceptor %T: ", it)
   244  			for _, match := range matches {
   245  				msg += fmt.Sprintf("%#v", match)
   246  			}
   247  			panic(msg)
   248  		}
   249  
   250  		var cfg InterceptorConfig
   251  		if len(matches) == 1 {
   252  			cfg = matches[0]
   253  		}
   254  		its = append(its, configuredInterceptor{interceptor: it, config: cfg})
   255  	}
   256  	return its
   257  }