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  }