github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/safehttp/flight.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 "context" 19 "net/http" 20 ) 21 22 // A single request "flight". 23 type flight struct { 24 rw http.ResponseWriter 25 req *IncomingRequest 26 27 cfg handlerConfig 28 29 code StatusCode 30 header Header 31 32 written bool 33 } 34 35 // handlerConfig is the safe HTTP handler configuration, including the 36 // dispatcher and interceptors. 37 type handlerConfig struct { 38 Handler Handler 39 Dispatcher Dispatcher 40 Interceptors []configuredInterceptor 41 } 42 43 func processRequest(cfg handlerConfig, rw http.ResponseWriter, req *http.Request) { 44 f := &flight{ 45 cfg: cfg, 46 rw: rw, 47 header: NewHeader(rw.Header()), 48 req: NewIncomingRequest(req), 49 } 50 51 // The net/http package handles all panics. In the early days of the 52 // framework we were handling them ourselves and running interceptors after 53 // a panic happened, but this adds lots of complexity to the codebase and 54 // still isn't perfect (e.g. what if Commit panics?). Instead, we just make 55 // sure to clear all the headers and cookies. 56 defer func() { 57 if r := recover(); r != nil { 58 // Clear all headers. 59 for h := range f.rw.Header() { 60 delete(f.rw.Header(), h) 61 } 62 panic(r) 63 } 64 }() 65 66 for _, it := range f.cfg.Interceptors { 67 it.Before(f, f.req) 68 if f.written { 69 return 70 } 71 } 72 f.cfg.Handler.ServeHTTP(f, f.req) 73 if !f.written { 74 cfg.Dispatcher.Write(rw, NoContentResponse{}) 75 } 76 } 77 78 // Write dispatches the response to the Dispatcher. This will be written to the 79 // underlying http.ResponseWriter if the Dispatcher decides it's safe to do so. 80 func (f *flight) Write(resp Response) Result { 81 if f.written { 82 panic("ResponseWriter was already written to") 83 } 84 f.written = true 85 f.commitPhase(resp) 86 87 if err := f.cfg.Dispatcher.Write(f.rw, resp); err != nil { 88 panic(err) 89 } 90 return Result{} 91 } 92 93 // WriteError writes an error response (400-599) according to the provided 94 // status code. 95 // 96 // If the ResponseWriter has already been written to, then this method will panic. 97 func (f *flight) WriteError(resp ErrorResponse) Result { 98 if f.written { 99 panic("ResponseWriter was already written to") 100 } 101 f.written = true 102 f.commitPhase(resp) 103 if err := f.cfg.Dispatcher.Error(f.rw, resp); err != nil { 104 panic(err) 105 } 106 return Result{} 107 } 108 109 // Header returns the collection of headers that will be set on the response. 110 // Headers must be set before writing a response. 111 func (f *flight) Header() Header { 112 return f.header 113 } 114 115 // AddCookie adds a Set-Cookie header to the provided ResponseWriter's headers. 116 // The provided cookie must have a valid Name, otherwise an error will be 117 // returned. 118 func (f *flight) AddCookie(c *Cookie) error { 119 return f.header.addCookie(c) 120 } 121 122 // commitPhase calls the Commit phases of all the interceptors. This stage will 123 // run before a response is written to the ResponseWriter. If a response is 124 // written to the ResponseWriter in a Commit phase then the Commit phases of the 125 // remaining interceptors won'f execute. 126 func (f *flight) commitPhase(resp Response) { 127 for i := len(f.cfg.Interceptors) - 1; i >= 0; i-- { 128 f.cfg.Interceptors[i].Commit(f, f.req, resp) 129 } 130 } 131 132 // Result is the result of writing an HTTP response. 133 // 134 // Use ResponseWriter methods to obtain it. 135 type Result struct{} 136 137 // NotWritten returns a Result which indicates that nothing has been written yet. It 138 // can be used in all functions that return a Result, such as in the ServeHTTP method 139 // of a Handler or in the Before method of an Interceptor. When returned, NotWritten 140 // indicates that the writing of the response should take place later. When this 141 // is returned by the Before method in Interceptors the next Interceptor in line 142 // is run. When this is returned by a Handler, a 204 No Content response is written. 143 func NotWritten() Result { 144 return Result{} 145 } 146 147 type flightValues struct { 148 m map[interface{}]interface{} 149 } 150 151 func (fv flightValues) Put(key, value interface{}) { 152 fv.m[key] = value 153 } 154 155 func (fv flightValues) Get(key interface{}) interface{} { 156 return fv.m[key] 157 } 158 159 // Map is a key/value map. 160 type Map interface { 161 // Put inserts a key/value pair into the map. If the key already exists in 162 // the map, it's value is replaced. 163 Put(key, value interface{}) 164 // Get returns a value for a given key in the map. If the entry with a given 165 // key does not exist, nil is returned. 166 Get(key interface{}) interface{} 167 } 168 169 type flightValuesCtxKey struct{} 170 171 // FlightValues returns a map associated with the given request processing flight. 172 // Use it if your interceptors need state that has the lifetime of the request. 173 func FlightValues(ctx context.Context) Map { 174 v := ctx.Value(flightValuesCtxKey{}) 175 if v == nil { 176 return nil 177 } 178 return v.(Map) 179 }