goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/cors/cors.go (about)

     1  package cors
     2  
     3  import (
     4  	"net/http"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	"slices"
    10  )
    11  
    12  // Options holds the CORS configuration for a router.
    13  type Options struct {
    14  	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
    15  	// If the first value in the slice is "*" or if the slice is empty, all origins will be allowed.
    16  	// Default value is ["*"]
    17  	AllowedOrigins []string
    18  
    19  	// AllowedMethods is a list of methods the client is allowed to use with cross-domain requests.
    20  	// Default value is ["HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"].
    21  	AllowedMethods []string
    22  
    23  	// AllowedHeaders is a list of non simple headers the client is allowed to use with
    24  	// cross-domain requests.
    25  	// If the first value in the slice is "*", all headers will be allowed.
    26  	// If the slice is empty, the request's headers will be reflected.
    27  	// Default value is ["Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"].
    28  	AllowedHeaders []string
    29  
    30  	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
    31  	// API specification
    32  	ExposedHeaders []string
    33  
    34  	// MaxAge indicates how long the results of a preflight request can be cached.
    35  	// Default is 12 hours.
    36  	MaxAge time.Duration
    37  
    38  	// AllowCredentials indicates whether the request can include user credentials like
    39  	// cookies, HTTP authentication or client side SSL certificates.
    40  	AllowCredentials bool
    41  
    42  	// OptionsPassthrough instructs preflight to let other potential next handlers to
    43  	// process the OPTIONS method. Turn this on if your application handles OPTIONS.
    44  	OptionsPassthrough bool
    45  }
    46  
    47  // Default create new CORS options with default settings.
    48  // The returned value can be used as a starting point for
    49  // customized options.
    50  func Default() *Options {
    51  	return &Options{
    52  		AllowedOrigins: []string{"*"},
    53  		AllowedMethods: []string{
    54  			http.MethodHead,
    55  			http.MethodGet,
    56  			http.MethodPost,
    57  			http.MethodPut,
    58  			http.MethodPatch,
    59  			http.MethodDelete,
    60  		},
    61  		AllowedHeaders:   []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"},
    62  		AllowCredentials: false,
    63  		MaxAge:           time.Hour * 12,
    64  	}
    65  }
    66  
    67  // ConfigureCommon configures common headers between regular and preflight requests:
    68  // Origin, Credentials and Exposed Headers.
    69  func (o *Options) ConfigureCommon(headers http.Header, requestHeaders http.Header) {
    70  	o.configureOrigin(headers, requestHeaders)
    71  	o.configureCredentials(headers)
    72  	o.configureExposedHeaders(headers)
    73  }
    74  
    75  func (o *Options) configureOrigin(headers http.Header, requestHeaders http.Header) {
    76  	if len(o.AllowedOrigins) == 0 || o.AllowedOrigins[0] == "*" {
    77  		headers.Set("Access-Control-Allow-Origin", "*")
    78  	} else {
    79  		if o.validateOrigin(requestHeaders) {
    80  			headers.Set("Access-Control-Allow-Origin", requestHeaders.Get("Origin"))
    81  		}
    82  		headers.Add("Vary", "Origin")
    83  	}
    84  }
    85  
    86  func (o *Options) configureCredentials(headers http.Header) {
    87  	if o.AllowCredentials {
    88  		headers.Set("Access-Control-Allow-Credentials", "true")
    89  	}
    90  }
    91  
    92  func (o *Options) configureExposedHeaders(headers http.Header) {
    93  	if len(o.ExposedHeaders) > 0 {
    94  		headers.Set("Access-Control-Expose-Headers", strings.Join(o.ExposedHeaders, ", "))
    95  	}
    96  }
    97  
    98  func (o *Options) configureAllowedMethods(headers http.Header) {
    99  	headers.Set("Access-Control-Allow-Methods", strings.Join(o.AllowedMethods, ", "))
   100  }
   101  
   102  func (o *Options) configureAllowedHeaders(headers http.Header, requestHeaders http.Header) {
   103  	if len(o.AllowedHeaders) == 0 {
   104  		headers.Add("Vary", "Access-Control-Request-Headers")
   105  		headers.Set("Access-Control-Allow-Headers", requestHeaders.Get("Access-Control-Request-Headers"))
   106  	} else {
   107  		headers.Set("Access-Control-Allow-Headers", strings.Join(o.AllowedHeaders, ", "))
   108  	}
   109  
   110  }
   111  
   112  func (o *Options) configureMaxAge(headers http.Header) {
   113  	headers.Set("Access-Control-Max-Age", strconv.FormatUint(uint64(o.MaxAge.Seconds()), 10))
   114  }
   115  
   116  // HandlePreflight configures headers for preflight requests:
   117  // Allowed Methods, Allowed Headers and Max Age.
   118  func (o *Options) HandlePreflight(headers http.Header, requestHeaders http.Header) {
   119  	o.configureAllowedMethods(headers)
   120  	o.configureAllowedHeaders(headers, requestHeaders)
   121  	o.configureMaxAge(headers)
   122  }
   123  
   124  func (o *Options) validateOrigin(requestHeaders http.Header) bool {
   125  	return len(o.AllowedOrigins) == 0 ||
   126  		o.AllowedOrigins[0] == "*" ||
   127  		slices.Contains(o.AllowedOrigins, requestHeaders.Get("Origin"))
   128  }