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] = ®isteredHandler{ 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 }