Initial QSfera import
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
# Go CORS handler [](https://godoc.org/github.com/rs/cors) [](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [](https://raw.githack.com/wiki/rs/cors/coverage.html)
|
||||
|
||||
CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang.
|
||||
|
||||
## Getting Started
|
||||
|
||||
After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte("{\"hello\": \"world\"}"))
|
||||
})
|
||||
|
||||
// cors.Default() setup the middleware with default options being
|
||||
// all origins accepted with simple methods (GET, POST). See
|
||||
// documentation below for more options.
|
||||
handler := cors.Default().Handler(mux)
|
||||
http.ListenAndServe(":8080", handler)
|
||||
}
|
||||
```
|
||||
|
||||
Install `cors`:
|
||||
|
||||
go get github.com/rs/cors
|
||||
|
||||
Then run your server:
|
||||
|
||||
go run server.go
|
||||
|
||||
The server now runs on `localhost:8080`:
|
||||
|
||||
$ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/
|
||||
HTTP/1.1 200 OK
|
||||
Access-Control-Allow-Origin: foo.com
|
||||
Content-Type: application/json
|
||||
Date: Sat, 25 Oct 2014 03:43:57 GMT
|
||||
Content-Length: 18
|
||||
|
||||
{"hello": "world"}
|
||||
|
||||
### Allow * With Credentials Security Protection
|
||||
|
||||
This library has been modified to avoid a well known security issue when configured with `AllowedOrigins` to `*` and `AllowCredentials` to `true`. Such setup used to make the library reflects the request `Origin` header value, working around a security protection embedded into the standard that makes clients to refuse such configuration. This behavior has been removed with [#55](https://github.com/rs/cors/issues/55) and [#57](https://github.com/rs/cors/issues/57).
|
||||
|
||||
If you depend on this behavior and understand the implications, you can restore it using the `AllowOriginFunc` with `func(origin string) {return true}`.
|
||||
|
||||
Please refer to [#55](https://github.com/rs/cors/issues/55) for more information about the security implications.
|
||||
|
||||
### More Examples
|
||||
|
||||
* `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go)
|
||||
* [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go)
|
||||
* [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go)
|
||||
* [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go)
|
||||
* [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go)
|
||||
* [HttpRouter](https://github.com/julienschmidt/httprouter): [examples/httprouter/server.go](https://github.com/rs/cors/blob/master/examples/httprouter/server.go)
|
||||
* [Gorilla](http://www.gorillatoolkit.org/pkg/mux): [examples/gorilla/server.go](https://github.com/rs/cors/blob/master/examples/gorilla/server.go)
|
||||
* [Buffalo](https://gobuffalo.io): [examples/buffalo/server.go](https://github.com/rs/cors/blob/master/examples/buffalo/server.go)
|
||||
* [Gin](https://gin-gonic.github.io/gin): [examples/gin/server.go](https://github.com/rs/cors/blob/master/examples/gin/server.go)
|
||||
* [Chi](https://github.com/go-chi/chi): [examples/chi/server.go](https://github.com/rs/cors/blob/master/examples/chi/server.go)
|
||||
|
||||
## Parameters
|
||||
|
||||
Parameters are passed to the middleware thru the `cors.New` method as follow:
|
||||
|
||||
```go
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"http://foo.com", "http://foo.com:8080"},
|
||||
AllowCredentials: true,
|
||||
// Enable Debugging for testing, consider disabling in production
|
||||
Debug: true,
|
||||
})
|
||||
|
||||
// Insert the middleware
|
||||
handler = c.Handler(handler)
|
||||
```
|
||||
|
||||
* **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. An origin may contain a wildcard (`*`) to replace 0 or more characters (i.e.: `http://*.domain.com`). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin. The default value is `*`.
|
||||
* **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It takes the origin as an argument and returns true if allowed, or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored.
|
||||
* **AllowOriginRequestFunc** `func (r *http.Request, origin string) bool`: A custom function to validate the origin. It takes the HTTP Request object and the origin as argument and returns true if allowed or false otherwise. If this option is set, the contents of `AllowedOrigins` and `AllowOriginFunc` are ignored.
|
||||
Deprecated: use `AllowOriginVaryRequestFunc` instead.
|
||||
* **AllowOriginVaryRequestFunc** `func(r *http.Request, origin string) (bool, []string)`: A custom function to validate the origin. It takes the HTTP Request object and the origin as argument and returns true if allowed or false otherwise with a list of headers used to take that decision if any so they can be added to the Vary header. If this option is set, the contents of `AllowedOrigins`, `AllowOriginFunc` and `AllowOriginRequestFunc` are ignored.
|
||||
* **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`).
|
||||
* **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests.
|
||||
* **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification.
|
||||
* **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`.
|
||||
* **AllowPrivateNetwork** `bool`: Indicates whether to accept cross-origin requests over a private network.
|
||||
* **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age.
|
||||
* **OptionsPassthrough** `bool`: Instructs preflight to let other potential next handlers to process the `OPTIONS` method. Turn this on if your application handles `OPTIONS`.
|
||||
* **OptionsSuccessStatus** `int`: Provides a status code to use for successful OPTIONS requests. Default value is `http.StatusNoContent` (`204`).
|
||||
* **Debug** `bool`: Debugging flag adds additional output to debug server side CORS issues.
|
||||
|
||||
See [API documentation](http://godoc.org/github.com/rs/cors) for more info.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
```
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/rs/cors
|
||||
BenchmarkWithout-10 135325480 8.124 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkDefault-10 24082140 51.40 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAllowedOrigin-10 16424518 88.25 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPreflight-10 8010259 147.3 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPreflightHeader-10 6850962 175.0 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkWildcard/match-10 253275342 4.714 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkWildcard/too_short-10 1000000000 0.6235 ns/op 0 B/op 0 allocs/op
|
||||
PASS
|
||||
ok github.com/rs/cors 99.131s
|
||||
```
|
||||
|
||||
## Licenses
|
||||
|
||||
All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE).
|
||||
+500
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
Package cors is net/http handler to handle CORS related requests
|
||||
as defined by http://www.w3.org/TR/cors/
|
||||
|
||||
You can configure it by passing an option struct to cors.New:
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"foo.com"},
|
||||
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
AllowCredentials: true,
|
||||
})
|
||||
|
||||
Then insert the handler in the chain:
|
||||
|
||||
handler = c.Handler(handler)
|
||||
|
||||
See Options documentation for more options.
|
||||
|
||||
The resulting handler is a standard net/http handler.
|
||||
*/
|
||||
package cors
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/cors/internal"
|
||||
)
|
||||
|
||||
var headerVaryOrigin = []string{"Origin"}
|
||||
var headerOriginAll = []string{"*"}
|
||||
var headerTrue = []string{"true"}
|
||||
|
||||
// Options is a configuration container to setup the CORS middleware.
|
||||
type Options struct {
|
||||
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
|
||||
// If the special "*" value is present in the list, all origins will be allowed.
|
||||
// An origin may contain a wildcard (*) to replace 0 or more characters
|
||||
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
|
||||
// Only one wildcard can be used per origin.
|
||||
// Default value is ["*"]
|
||||
AllowedOrigins []string
|
||||
// AllowOriginFunc is a custom function to validate the origin. It take the
|
||||
// origin as argument and returns true if allowed or false otherwise. If
|
||||
// this option is set, the content of `AllowedOrigins` is ignored.
|
||||
AllowOriginFunc func(origin string) bool
|
||||
// AllowOriginRequestFunc is a custom function to validate the origin. It
|
||||
// takes the HTTP Request object and the origin as argument and returns true
|
||||
// if allowed or false otherwise. If headers are used take the decision,
|
||||
// consider using AllowOriginVaryRequestFunc instead. If this option is set,
|
||||
// the contents of `AllowedOrigins`, `AllowOriginFunc` are ignored.
|
||||
//
|
||||
// Deprecated: use `AllowOriginVaryRequestFunc` instead.
|
||||
AllowOriginRequestFunc func(r *http.Request, origin string) bool
|
||||
// AllowOriginVaryRequestFunc is a custom function to validate the origin.
|
||||
// It takes the HTTP Request object and the origin as argument and returns
|
||||
// true if allowed or false otherwise with a list of headers used to take
|
||||
// that decision if any so they can be added to the Vary header. If this
|
||||
// option is set, the contents of `AllowedOrigins`, `AllowOriginFunc` and
|
||||
// `AllowOriginRequestFunc` are ignored.
|
||||
AllowOriginVaryRequestFunc func(r *http.Request, origin string) (bool, []string)
|
||||
// AllowedMethods is a list of methods the client is allowed to use with
|
||||
// cross-domain requests. Default value is simple methods (HEAD, GET and POST).
|
||||
AllowedMethods []string
|
||||
// AllowedHeaders is list of non simple headers the client is allowed to use with
|
||||
// cross-domain requests.
|
||||
// If the special "*" value is present in the list, all headers will be allowed.
|
||||
// Default value is [].
|
||||
AllowedHeaders []string
|
||||
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
|
||||
// API specification
|
||||
ExposedHeaders []string
|
||||
// MaxAge indicates how long (in seconds) the results of a preflight request
|
||||
// can be cached. Default value is 0, which stands for no
|
||||
// Access-Control-Max-Age header to be sent back, resulting in browsers
|
||||
// using their default value (5s by spec). If you need to force a 0 max-age,
|
||||
// set `MaxAge` to a negative value (ie: -1).
|
||||
MaxAge int
|
||||
// AllowCredentials indicates whether the request can include user credentials like
|
||||
// cookies, HTTP authentication or client side SSL certificates.
|
||||
AllowCredentials bool
|
||||
// AllowPrivateNetwork indicates whether to accept cross-origin requests over a
|
||||
// private network.
|
||||
AllowPrivateNetwork bool
|
||||
// OptionsPassthrough instructs preflight to let other potential next handlers to
|
||||
// process the OPTIONS method. Turn this on if your application handles OPTIONS.
|
||||
OptionsPassthrough bool
|
||||
// Provides a status code to use for successful OPTIONS requests.
|
||||
// Default value is http.StatusNoContent (204).
|
||||
OptionsSuccessStatus int
|
||||
// Debugging flag adds additional output to debug server side CORS issues
|
||||
Debug bool
|
||||
// Adds a custom logger, implies Debug is true
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// Logger generic interface for logger
|
||||
type Logger interface {
|
||||
Printf(string, ...interface{})
|
||||
}
|
||||
|
||||
// Cors http handler
|
||||
type Cors struct {
|
||||
// Debug logger
|
||||
Log Logger
|
||||
// Normalized list of plain allowed origins
|
||||
allowedOrigins []string
|
||||
// List of allowed origins containing wildcards
|
||||
allowedWOrigins []wildcard
|
||||
// Optional origin validator function
|
||||
allowOriginFunc func(r *http.Request, origin string) (bool, []string)
|
||||
// Normalized list of allowed headers
|
||||
// Note: the Fetch standard guarantees that CORS-unsafe request-header names
|
||||
// (i.e. the values listed in the Access-Control-Request-Headers header)
|
||||
// are unique and sorted;
|
||||
// see https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names.
|
||||
allowedHeaders internal.SortedSet
|
||||
// Normalized list of allowed methods
|
||||
allowedMethods []string
|
||||
// Pre-computed normalized list of exposed headers
|
||||
exposedHeaders []string
|
||||
// Pre-computed maxAge header value
|
||||
maxAge []string
|
||||
// Set to true when allowed origins contains a "*"
|
||||
allowedOriginsAll bool
|
||||
// Set to true when allowed headers contains a "*"
|
||||
allowedHeadersAll bool
|
||||
// Status code to use for successful OPTIONS requests
|
||||
optionsSuccessStatus int
|
||||
allowCredentials bool
|
||||
allowPrivateNetwork bool
|
||||
optionPassthrough bool
|
||||
preflightVary []string
|
||||
}
|
||||
|
||||
// New creates a new Cors handler with the provided options.
|
||||
func New(options Options) *Cors {
|
||||
c := &Cors{
|
||||
allowCredentials: options.AllowCredentials,
|
||||
allowPrivateNetwork: options.AllowPrivateNetwork,
|
||||
optionPassthrough: options.OptionsPassthrough,
|
||||
Log: options.Logger,
|
||||
}
|
||||
if options.Debug && c.Log == nil {
|
||||
c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
|
||||
}
|
||||
|
||||
// Allowed origins
|
||||
switch {
|
||||
case options.AllowOriginVaryRequestFunc != nil:
|
||||
c.allowOriginFunc = options.AllowOriginVaryRequestFunc
|
||||
case options.AllowOriginRequestFunc != nil:
|
||||
c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) {
|
||||
return options.AllowOriginRequestFunc(r, origin), nil
|
||||
}
|
||||
case options.AllowOriginFunc != nil:
|
||||
c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) {
|
||||
return options.AllowOriginFunc(origin), nil
|
||||
}
|
||||
case len(options.AllowedOrigins) == 0:
|
||||
if c.allowOriginFunc == nil {
|
||||
// Default is all origins
|
||||
c.allowedOriginsAll = true
|
||||
}
|
||||
default:
|
||||
c.allowedOrigins = []string{}
|
||||
c.allowedWOrigins = []wildcard{}
|
||||
for _, origin := range options.AllowedOrigins {
|
||||
// Note: for origins matching, the spec requires a case-sensitive matching.
|
||||
// As it may error prone, we chose to ignore the spec here.
|
||||
origin = strings.ToLower(origin)
|
||||
if origin == "*" {
|
||||
// If "*" is present in the list, turn the whole list into a match all
|
||||
c.allowedOriginsAll = true
|
||||
c.allowedOrigins = nil
|
||||
c.allowedWOrigins = nil
|
||||
break
|
||||
} else if i := strings.IndexByte(origin, '*'); i >= 0 {
|
||||
// Split the origin in two: start and end string without the *
|
||||
w := wildcard{origin[0:i], origin[i+1:]}
|
||||
c.allowedWOrigins = append(c.allowedWOrigins, w)
|
||||
} else {
|
||||
c.allowedOrigins = append(c.allowedOrigins, origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allowed Headers
|
||||
// Note: the Fetch standard guarantees that CORS-unsafe request-header names
|
||||
// (i.e. the values listed in the Access-Control-Request-Headers header)
|
||||
// are lowercase; see https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names.
|
||||
if len(options.AllowedHeaders) == 0 {
|
||||
// Use sensible defaults
|
||||
c.allowedHeaders = internal.NewSortedSet("accept", "content-type", "x-requested-with")
|
||||
} else {
|
||||
normalized := convert(options.AllowedHeaders, strings.ToLower)
|
||||
c.allowedHeaders = internal.NewSortedSet(normalized...)
|
||||
for _, h := range options.AllowedHeaders {
|
||||
if h == "*" {
|
||||
c.allowedHeadersAll = true
|
||||
c.allowedHeaders = internal.SortedSet{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allowed Methods
|
||||
if len(options.AllowedMethods) == 0 {
|
||||
// Default is spec's "simple" methods
|
||||
c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead}
|
||||
} else {
|
||||
c.allowedMethods = options.AllowedMethods
|
||||
}
|
||||
|
||||
// Options Success Status Code
|
||||
if options.OptionsSuccessStatus == 0 {
|
||||
c.optionsSuccessStatus = http.StatusNoContent
|
||||
} else {
|
||||
c.optionsSuccessStatus = options.OptionsSuccessStatus
|
||||
}
|
||||
|
||||
// Pre-compute exposed headers header value
|
||||
if len(options.ExposedHeaders) > 0 {
|
||||
c.exposedHeaders = []string{strings.Join(convert(options.ExposedHeaders, http.CanonicalHeaderKey), ", ")}
|
||||
}
|
||||
|
||||
// Pre-compute prefight Vary header to save allocations
|
||||
if c.allowPrivateNetwork {
|
||||
c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network"}
|
||||
} else {
|
||||
c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}
|
||||
}
|
||||
|
||||
// Precompute max-age
|
||||
if options.MaxAge > 0 {
|
||||
c.maxAge = []string{strconv.Itoa(options.MaxAge)}
|
||||
} else if options.MaxAge < 0 {
|
||||
c.maxAge = []string{"0"}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Default creates a new Cors handler with default options.
|
||||
func Default() *Cors {
|
||||
return New(Options{})
|
||||
}
|
||||
|
||||
// AllowAll create a new Cors handler with permissive configuration allowing all
|
||||
// origins with all standard methods with any header and credentials.
|
||||
func AllowAll() *Cors {
|
||||
return New(Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
},
|
||||
AllowedHeaders: []string{"*"},
|
||||
AllowCredentials: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler apply the CORS specification on the request, and add relevant CORS headers
|
||||
// as necessary.
|
||||
func (c *Cors) Handler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||
c.logf("Handler: Preflight request")
|
||||
c.handlePreflight(w, r)
|
||||
// Preflight requests are standalone and should stop the chain as some other
|
||||
// middleware may not handle OPTIONS requests correctly. One typical example
|
||||
// is authentication middleware ; OPTIONS requests won't carry authentication
|
||||
// headers (see #1)
|
||||
if c.optionPassthrough {
|
||||
h.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.WriteHeader(c.optionsSuccessStatus)
|
||||
}
|
||||
} else {
|
||||
c.logf("Handler: Actual request")
|
||||
c.handleActualRequest(w, r)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// HandlerFunc provides Martini compatible handler
|
||||
func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||
c.logf("HandlerFunc: Preflight request")
|
||||
c.handlePreflight(w, r)
|
||||
|
||||
w.WriteHeader(c.optionsSuccessStatus)
|
||||
} else {
|
||||
c.logf("HandlerFunc: Actual request")
|
||||
c.handleActualRequest(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Negroni compatible interface
|
||||
func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||
c.logf("ServeHTTP: Preflight request")
|
||||
c.handlePreflight(w, r)
|
||||
// Preflight requests are standalone and should stop the chain as some other
|
||||
// middleware may not handle OPTIONS requests correctly. One typical example
|
||||
// is authentication middleware ; OPTIONS requests won't carry authentication
|
||||
// headers (see #1)
|
||||
if c.optionPassthrough {
|
||||
next(w, r)
|
||||
} else {
|
||||
w.WriteHeader(c.optionsSuccessStatus)
|
||||
}
|
||||
} else {
|
||||
c.logf("ServeHTTP: Actual request")
|
||||
c.handleActualRequest(w, r)
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// handlePreflight handles pre-flight CORS requests
|
||||
func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
|
||||
headers := w.Header()
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
if r.Method != http.MethodOptions {
|
||||
c.logf(" Preflight aborted: %s!=OPTIONS", r.Method)
|
||||
return
|
||||
}
|
||||
// Always set Vary headers
|
||||
// see https://github.com/rs/cors/issues/10,
|
||||
// https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
|
||||
if vary, found := headers["Vary"]; found {
|
||||
headers["Vary"] = append(vary, c.preflightVary[0])
|
||||
} else {
|
||||
headers["Vary"] = c.preflightVary
|
||||
}
|
||||
allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin)
|
||||
if len(additionalVaryHeaders) > 0 {
|
||||
headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", "))
|
||||
}
|
||||
|
||||
if origin == "" {
|
||||
c.logf(" Preflight aborted: empty origin")
|
||||
return
|
||||
}
|
||||
if !allowed {
|
||||
c.logf(" Preflight aborted: origin '%s' not allowed", origin)
|
||||
return
|
||||
}
|
||||
|
||||
reqMethod := r.Header.Get("Access-Control-Request-Method")
|
||||
if !c.isMethodAllowed(reqMethod) {
|
||||
c.logf(" Preflight aborted: method '%s' not allowed", reqMethod)
|
||||
return
|
||||
}
|
||||
// Note: the Fetch standard guarantees that at most one
|
||||
// Access-Control-Request-Headers header is present in the preflight request;
|
||||
// see step 5.2 in https://fetch.spec.whatwg.org/#cors-preflight-fetch-0.
|
||||
// However, some gateways split that header into multiple headers of the same name;
|
||||
// see https://github.com/rs/cors/issues/184.
|
||||
reqHeaders, found := r.Header["Access-Control-Request-Headers"]
|
||||
if found && !c.allowedHeadersAll && !c.allowedHeaders.Accepts(reqHeaders) {
|
||||
c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders)
|
||||
return
|
||||
}
|
||||
if c.allowedOriginsAll {
|
||||
headers["Access-Control-Allow-Origin"] = headerOriginAll
|
||||
} else {
|
||||
headers["Access-Control-Allow-Origin"] = r.Header["Origin"]
|
||||
}
|
||||
// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
|
||||
// by Access-Control-Request-Method (if supported) can be enough
|
||||
headers["Access-Control-Allow-Methods"] = r.Header["Access-Control-Request-Method"]
|
||||
if found && len(reqHeaders[0]) > 0 {
|
||||
// Spec says: Since the list of headers can be unbounded, simply returning supported headers
|
||||
// from Access-Control-Request-Headers can be enough
|
||||
headers["Access-Control-Allow-Headers"] = reqHeaders
|
||||
}
|
||||
if c.allowCredentials {
|
||||
headers["Access-Control-Allow-Credentials"] = headerTrue
|
||||
}
|
||||
if c.allowPrivateNetwork && r.Header.Get("Access-Control-Request-Private-Network") == "true" {
|
||||
headers["Access-Control-Allow-Private-Network"] = headerTrue
|
||||
}
|
||||
if len(c.maxAge) > 0 {
|
||||
headers["Access-Control-Max-Age"] = c.maxAge
|
||||
}
|
||||
c.logf(" Preflight response headers: %v", headers)
|
||||
}
|
||||
|
||||
// handleActualRequest handles simple cross-origin requests, actual request or redirects
|
||||
func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
|
||||
headers := w.Header()
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin)
|
||||
|
||||
// Always set Vary, see https://github.com/rs/cors/issues/10
|
||||
if vary := headers["Vary"]; vary == nil {
|
||||
headers["Vary"] = headerVaryOrigin
|
||||
} else {
|
||||
headers["Vary"] = append(vary, headerVaryOrigin[0])
|
||||
}
|
||||
if len(additionalVaryHeaders) > 0 {
|
||||
headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", "))
|
||||
}
|
||||
if origin == "" {
|
||||
c.logf(" Actual request no headers added: missing origin")
|
||||
return
|
||||
}
|
||||
if !allowed {
|
||||
c.logf(" Actual request no headers added: origin '%s' not allowed", origin)
|
||||
return
|
||||
}
|
||||
|
||||
// Note that spec does define a way to specifically disallow a simple method like GET or
|
||||
// POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
|
||||
// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
|
||||
// We think it's a nice feature to be able to have control on those methods though.
|
||||
if !c.isMethodAllowed(r.Method) {
|
||||
c.logf(" Actual request no headers added: method '%s' not allowed", r.Method)
|
||||
return
|
||||
}
|
||||
if c.allowedOriginsAll {
|
||||
headers["Access-Control-Allow-Origin"] = headerOriginAll
|
||||
} else {
|
||||
headers["Access-Control-Allow-Origin"] = r.Header["Origin"]
|
||||
}
|
||||
if len(c.exposedHeaders) > 0 {
|
||||
headers["Access-Control-Expose-Headers"] = c.exposedHeaders
|
||||
}
|
||||
if c.allowCredentials {
|
||||
headers["Access-Control-Allow-Credentials"] = headerTrue
|
||||
}
|
||||
c.logf(" Actual response added headers: %v", headers)
|
||||
}
|
||||
|
||||
// convenience method. checks if a logger is set.
|
||||
func (c *Cors) logf(format string, a ...interface{}) {
|
||||
if c.Log != nil {
|
||||
c.Log.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// check the Origin of a request. No origin at all is also allowed.
|
||||
func (c *Cors) OriginAllowed(r *http.Request) bool {
|
||||
origin := r.Header.Get("Origin")
|
||||
allowed, _ := c.isOriginAllowed(r, origin)
|
||||
return allowed
|
||||
}
|
||||
|
||||
// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
|
||||
// on the endpoint
|
||||
func (c *Cors) isOriginAllowed(r *http.Request, origin string) (allowed bool, varyHeaders []string) {
|
||||
if c.allowOriginFunc != nil {
|
||||
return c.allowOriginFunc(r, origin)
|
||||
}
|
||||
if c.allowedOriginsAll {
|
||||
return true, nil
|
||||
}
|
||||
origin = strings.ToLower(origin)
|
||||
for _, o := range c.allowedOrigins {
|
||||
if o == origin {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
for _, w := range c.allowedWOrigins {
|
||||
if w.match(origin) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isMethodAllowed checks if a given method can be used as part of a cross-domain request
|
||||
// on the endpoint
|
||||
func (c *Cors) isMethodAllowed(method string) bool {
|
||||
if len(c.allowedMethods) == 0 {
|
||||
// If no method allowed, always return false, even for preflight request
|
||||
return false
|
||||
}
|
||||
if method == http.MethodOptions {
|
||||
// Always allow preflight requests
|
||||
return true
|
||||
}
|
||||
for _, m := range c.allowedMethods {
|
||||
if m == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
// adapted from github.com/jub0bs/cors
|
||||
package internal
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A SortedSet represents a mathematical set of strings sorted in
|
||||
// lexicographical order.
|
||||
// Each element has a unique position ranging from 0 (inclusive)
|
||||
// to the set's cardinality (exclusive).
|
||||
// The zero value represents an empty set.
|
||||
type SortedSet struct {
|
||||
m map[string]int
|
||||
maxLen int
|
||||
}
|
||||
|
||||
// NewSortedSet returns a SortedSet that contains all of elems,
|
||||
// but no other elements.
|
||||
func NewSortedSet(elems ...string) SortedSet {
|
||||
sort.Strings(elems)
|
||||
m := make(map[string]int)
|
||||
var maxLen int
|
||||
i := 0
|
||||
for _, s := range elems {
|
||||
if _, exists := m[s]; exists {
|
||||
continue
|
||||
}
|
||||
m[s] = i
|
||||
i++
|
||||
maxLen = max(maxLen, len(s))
|
||||
}
|
||||
return SortedSet{
|
||||
m: m,
|
||||
maxLen: maxLen,
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the cardinality of set.
|
||||
func (set SortedSet) Size() int {
|
||||
return len(set.m)
|
||||
}
|
||||
|
||||
// String sorts joins the elements of set (in lexicographical order)
|
||||
// with a comma and returns the resulting string.
|
||||
func (set SortedSet) String() string {
|
||||
elems := make([]string, len(set.m))
|
||||
for elem, i := range set.m {
|
||||
elems[i] = elem // safe indexing, by construction of SortedSet
|
||||
}
|
||||
return strings.Join(elems, ",")
|
||||
}
|
||||
|
||||
// Accepts reports whether values is a sequence of list-based field values
|
||||
// whose elements are
|
||||
// - all members of set,
|
||||
// - sorted in lexicographical order,
|
||||
// - unique.
|
||||
func (set SortedSet) Accepts(values []string) bool {
|
||||
var ( // effectively constant
|
||||
maxLen = maxOWSBytes + set.maxLen + maxOWSBytes + 1 // +1 for comma
|
||||
)
|
||||
var (
|
||||
posOfLastNameSeen = -1
|
||||
name string
|
||||
commaFound bool
|
||||
emptyElements int
|
||||
ok bool
|
||||
)
|
||||
for _, s := range values {
|
||||
for {
|
||||
// As a defense against maliciously long names in s,
|
||||
// we process only a small number of s's leading bytes per iteration.
|
||||
name, s, commaFound = cutAtComma(s, maxLen)
|
||||
name, ok = trimOWS(name, maxOWSBytes)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if name == "" {
|
||||
// RFC 9110 requires recipients to tolerate
|
||||
// "a reasonable number of empty list elements"; see
|
||||
// https://httpwg.org/specs/rfc9110.html#abnf.extension.recipient.
|
||||
emptyElements++
|
||||
if emptyElements > maxEmptyElements {
|
||||
return false
|
||||
}
|
||||
if !commaFound { // We have now exhausted the names in s.
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
pos, ok := set.m[name]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// The names in s are expected to be sorted in lexicographical order
|
||||
// and to each appear at most once.
|
||||
// Therefore, the positions (in set) of the names that
|
||||
// appear in s should form a strictly increasing sequence.
|
||||
// If that's not actually the case, bail out.
|
||||
if pos <= posOfLastNameSeen {
|
||||
return false
|
||||
}
|
||||
posOfLastNameSeen = pos
|
||||
if !commaFound { // We have now exhausted the names in s.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
maxOWSBytes = 1 // number of leading/trailing OWS bytes tolerated
|
||||
maxEmptyElements = 16 // number of empty list elements tolerated
|
||||
)
|
||||
|
||||
func cutAtComma(s string, n int) (before, after string, found bool) {
|
||||
// Note: this implementation draws inspiration from strings.Cut's.
|
||||
end := min(len(s), n)
|
||||
if i := strings.IndexByte(s[:end], ','); i >= 0 {
|
||||
after = s[i+1:] // deal with this first to save one bounds check
|
||||
return s[:i], after, true
|
||||
}
|
||||
return s, "", false
|
||||
}
|
||||
|
||||
// TrimOWS trims up to n bytes of [optional whitespace (OWS)]
|
||||
// from the start of and/or the end of s.
|
||||
// If no more than n bytes of OWS are found at the start of s
|
||||
// and no more than n bytes of OWS are found at the end of s,
|
||||
// it returns the trimmed result and true.
|
||||
// Otherwise, it returns the original string and false.
|
||||
//
|
||||
// [optional whitespace (OWS)]: https://httpwg.org/specs/rfc9110.html#whitespace
|
||||
func trimOWS(s string, n int) (trimmed string, ok bool) {
|
||||
if s == "" {
|
||||
return s, true
|
||||
}
|
||||
trimmed, ok = trimRightOWS(s, n)
|
||||
if !ok {
|
||||
return s, false
|
||||
}
|
||||
trimmed, ok = trimLeftOWS(trimmed, n)
|
||||
if !ok {
|
||||
return s, false
|
||||
}
|
||||
return trimmed, true
|
||||
}
|
||||
|
||||
func trimLeftOWS(s string, n int) (string, bool) {
|
||||
sCopy := s
|
||||
var i int
|
||||
for len(s) > 0 {
|
||||
if i > n {
|
||||
return sCopy, false
|
||||
}
|
||||
if !(s[0] == ' ' || s[0] == '\t') {
|
||||
break
|
||||
}
|
||||
s = s[1:]
|
||||
i++
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
func trimRightOWS(s string, n int) (string, bool) {
|
||||
sCopy := s
|
||||
var i int
|
||||
for len(s) > 0 {
|
||||
if i > n {
|
||||
return sCopy, false
|
||||
}
|
||||
last := len(s) - 1
|
||||
if !(s[last] == ' ' || s[last] == '\t') {
|
||||
break
|
||||
}
|
||||
s = s[:last]
|
||||
i++
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
// TODO: when updating go directive to 1.21 or later,
|
||||
// use min builtin instead.
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// TODO: when updating go directive to 1.21 or later,
|
||||
// use max builtin instead.
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type wildcard struct {
|
||||
prefix string
|
||||
suffix string
|
||||
}
|
||||
|
||||
func (w wildcard) match(s string) bool {
|
||||
return len(s) >= len(w.prefix)+len(w.suffix) &&
|
||||
strings.HasPrefix(s, w.prefix) &&
|
||||
strings.HasSuffix(s, w.suffix)
|
||||
}
|
||||
|
||||
// convert converts a list of string using the passed converter function
|
||||
func convert(s []string, f func(string) string) []string {
|
||||
out := make([]string, len(s))
|
||||
for i := range s {
|
||||
out[i] = f(s[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user