├── .github └── workflows │ └── go.yml ├── LICENSE ├── README.md ├── bench_test.go ├── cors.go ├── cors_test.go ├── examples ├── alice │ └── server.go ├── buffalo │ └── server.go ├── chi │ └── server.go ├── default │ └── server.go ├── gin │ └── server.go ├── go.mod ├── go.sum ├── goji │ └── server.go ├── gorilla │ └── server.go ├── httprouter │ └── server.go ├── martini │ └── server.go ├── negroni │ └── server.go ├── nethttp │ └── server.go └── openbar │ └── server.go ├── go.mod ├── internal ├── sortedset.go └── sortedset_test.go ├── utils.go ├── utils_test.go └── wrapper └── gin ├── gin.go ├── gin_test.go ├── go.mod └── go.sum /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | name: Test 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | go: ["1.23"] 9 | steps: 10 | - name: Set up Go ${{ matrix.go }} 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: ${{ matrix.go }} 14 | id: go 15 | 16 | - name: Check out code 17 | uses: actions/checkout@v3 18 | 19 | - name: Get dependencies 20 | run: go get -v -t -d ./... 21 | 22 | - name: Test 23 | run: go test ./... 24 | 25 | coverage: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Update coverage report 29 | uses: ncruces/go-coverage-report@main 30 | with: 31 | report: 'true' 32 | chart: 'true' 33 | amend: 'true' 34 | continue-on-error: true 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Olivier Poitrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![Go Coverage](https://github.com/rs/cors/wiki/coverage.svg)](https://raw.githack.com/wiki/rs/cors/coverage.html) 2 | 3 | CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. 4 | 5 | ## Getting Started 6 | 7 | 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`. 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "net/http" 14 | 15 | "github.com/rs/cors" 16 | ) 17 | 18 | func main() { 19 | mux := http.NewServeMux() 20 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 21 | w.Header().Set("Content-Type", "application/json") 22 | w.Write([]byte("{\"hello\": \"world\"}")) 23 | }) 24 | 25 | // cors.Default() setup the middleware with default options being 26 | // all origins accepted with simple methods (GET, POST). See 27 | // documentation below for more options. 28 | handler := cors.Default().Handler(mux) 29 | http.ListenAndServe(":8080", handler) 30 | } 31 | ``` 32 | 33 | Install `cors`: 34 | 35 | go get github.com/rs/cors 36 | 37 | Then run your server: 38 | 39 | go run server.go 40 | 41 | The server now runs on `localhost:8080`: 42 | 43 | $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/ 44 | HTTP/1.1 200 OK 45 | Access-Control-Allow-Origin: foo.com 46 | Content-Type: application/json 47 | Date: Sat, 25 Oct 2014 03:43:57 GMT 48 | Content-Length: 18 49 | 50 | {"hello": "world"} 51 | 52 | ### Allow * With Credentials Security Protection 53 | 54 | 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). 55 | 56 | If you depend on this behavior and understand the implications, you can restore it using the `AllowOriginFunc` with `func(origin string) {return true}`. 57 | 58 | Please refer to [#55](https://github.com/rs/cors/issues/55) for more information about the security implications. 59 | 60 | ### More Examples 61 | 62 | * `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) 63 | * [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go) 64 | * [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) 65 | * [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) 66 | * [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) 67 | * [HttpRouter](https://github.com/julienschmidt/httprouter): [examples/httprouter/server.go](https://github.com/rs/cors/blob/master/examples/httprouter/server.go) 68 | * [Gorilla](http://www.gorillatoolkit.org/pkg/mux): [examples/gorilla/server.go](https://github.com/rs/cors/blob/master/examples/gorilla/server.go) 69 | * [Buffalo](https://gobuffalo.io): [examples/buffalo/server.go](https://github.com/rs/cors/blob/master/examples/buffalo/server.go) 70 | * [Gin](https://gin-gonic.github.io/gin): [examples/gin/server.go](https://github.com/rs/cors/blob/master/examples/gin/server.go) 71 | * [Chi](https://github.com/go-chi/chi): [examples/chi/server.go](https://github.com/rs/cors/blob/master/examples/chi/server.go) 72 | 73 | ## Parameters 74 | 75 | Parameters are passed to the middleware thru the `cors.New` method as follow: 76 | 77 | ```go 78 | c := cors.New(cors.Options{ 79 | AllowedOrigins: []string{"http://foo.com", "http://foo.com:8080"}, 80 | AllowCredentials: true, 81 | // Enable Debugging for testing, consider disabling in production 82 | Debug: true, 83 | }) 84 | 85 | // Insert the middleware 86 | handler = c.Handler(handler) 87 | ``` 88 | 89 | * **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 `*`. 90 | * **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. 91 | * **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. 92 | Deprecated: use `AllowOriginVaryRequestFunc` instead. 93 | * **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. 94 | * **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`). 95 | * **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. 96 | * **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification. 97 | * **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. 98 | * **AllowPrivateNetwork** `bool`: Indicates whether to accept cross-origin requests over a private network. 99 | * **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. 100 | * **OptionsPassthrough** `bool`: Instructs preflight to let other potential next handlers to process the `OPTIONS` method. Turn this on if your application handles `OPTIONS`. 101 | * **OptionsSuccessStatus** `int`: Provides a status code to use for successful OPTIONS requests. Default value is `http.StatusNoContent` (`204`). 102 | * **Debug** `bool`: Debugging flag adds additional output to debug server side CORS issues. 103 | 104 | See [API documentation](http://godoc.org/github.com/rs/cors) for more info. 105 | 106 | ## Benchmarks 107 | 108 | ``` 109 | goos: darwin 110 | goarch: arm64 111 | pkg: github.com/rs/cors 112 | BenchmarkWithout-10 135325480 8.124 ns/op 0 B/op 0 allocs/op 113 | BenchmarkDefault-10 24082140 51.40 ns/op 0 B/op 0 allocs/op 114 | BenchmarkAllowedOrigin-10 16424518 88.25 ns/op 0 B/op 0 allocs/op 115 | BenchmarkPreflight-10 8010259 147.3 ns/op 0 B/op 0 allocs/op 116 | BenchmarkPreflightHeader-10 6850962 175.0 ns/op 0 B/op 0 allocs/op 117 | BenchmarkWildcard/match-10 253275342 4.714 ns/op 0 B/op 0 allocs/op 118 | BenchmarkWildcard/too_short-10 1000000000 0.6235 ns/op 0 B/op 0 allocs/op 119 | PASS 120 | ok github.com/rs/cors 99.131s 121 | ``` 122 | 123 | ## Licenses 124 | 125 | All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). 126 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | type FakeResponse struct { 10 | header http.Header 11 | } 12 | 13 | func (r FakeResponse) Header() http.Header { 14 | return r.header 15 | } 16 | 17 | func (r FakeResponse) WriteHeader(n int) { 18 | } 19 | 20 | func (r FakeResponse) Write(b []byte) (n int, err error) { 21 | return len(b), nil 22 | } 23 | 24 | const ( 25 | headerOrigin = "Origin" 26 | headerACRM = "Access-Control-Request-Method" 27 | headerACRH = "Access-Control-Request-Headers" 28 | dummyEndpoint = "http://example.com/foo" 29 | dummyOrigin = "https://somedomain.com" 30 | ) 31 | 32 | func BenchmarkWithout(b *testing.B) { 33 | resps := makeFakeResponses(b.N) 34 | req, _ := http.NewRequest(http.MethodGet, dummyEndpoint, nil) 35 | 36 | b.ReportAllocs() 37 | b.ResetTimer() 38 | for i := 0; i < b.N; i++ { 39 | testHandler.ServeHTTP(resps[i], req) 40 | } 41 | } 42 | 43 | func BenchmarkDefault(b *testing.B) { 44 | resps := makeFakeResponses(b.N) 45 | req, _ := http.NewRequest(http.MethodGet, dummyEndpoint, nil) 46 | req.Header.Add(headerOrigin, dummyOrigin) 47 | handler := Default().Handler(testHandler) 48 | 49 | b.ReportAllocs() 50 | b.ResetTimer() 51 | for i := 0; i < b.N; i++ { 52 | handler.ServeHTTP(resps[i], req) 53 | } 54 | } 55 | 56 | func BenchmarkAllowedOrigin(b *testing.B) { 57 | resps := makeFakeResponses(b.N) 58 | req, _ := http.NewRequest(http.MethodGet, dummyEndpoint, nil) 59 | req.Header.Add(headerOrigin, dummyOrigin) 60 | c := New(Options{ 61 | AllowedOrigins: []string{dummyOrigin}, 62 | }) 63 | handler := c.Handler(testHandler) 64 | 65 | b.ReportAllocs() 66 | b.ResetTimer() 67 | for i := 0; i < b.N; i++ { 68 | handler.ServeHTTP(resps[i], req) 69 | } 70 | } 71 | 72 | func BenchmarkPreflight(b *testing.B) { 73 | resps := makeFakeResponses(b.N) 74 | req, _ := http.NewRequest(http.MethodOptions, dummyEndpoint, nil) 75 | req.Header.Add(headerOrigin, dummyOrigin) 76 | req.Header.Add(headerACRM, http.MethodGet) 77 | handler := Default().Handler(testHandler) 78 | 79 | b.ReportAllocs() 80 | b.ResetTimer() 81 | for i := 0; i < b.N; i++ { 82 | handler.ServeHTTP(resps[i], req) 83 | } 84 | } 85 | 86 | func BenchmarkPreflightHeader(b *testing.B) { 87 | resps := makeFakeResponses(b.N) 88 | req, _ := http.NewRequest(http.MethodOptions, dummyEndpoint, nil) 89 | req.Header.Add(headerOrigin, dummyOrigin) 90 | req.Header.Add(headerACRM, http.MethodGet) 91 | req.Header.Add(headerACRH, "accept") 92 | handler := Default().Handler(testHandler) 93 | 94 | b.ReportAllocs() 95 | b.ResetTimer() 96 | for i := 0; i < b.N; i++ { 97 | handler.ServeHTTP(resps[i], req) 98 | } 99 | } 100 | 101 | func BenchmarkPreflightAdversarialACRH(b *testing.B) { 102 | resps := makeFakeResponses(b.N) 103 | req, _ := http.NewRequest(http.MethodOptions, dummyEndpoint, nil) 104 | req.Header.Add(headerOrigin, dummyOrigin) 105 | req.Header.Add(headerACRM, http.MethodGet) 106 | req.Header.Add(headerACRH, strings.Repeat(",", 1024)) 107 | handler := Default().Handler(testHandler) 108 | 109 | b.ReportAllocs() 110 | b.ResetTimer() 111 | for i := 0; i < b.N; i++ { 112 | handler.ServeHTTP(resps[i], req) 113 | } 114 | } 115 | 116 | func makeFakeResponses(n int) []*FakeResponse { 117 | resps := make([]*FakeResponse, n) 118 | for i := 0; i < n; i++ { 119 | resps[i] = &FakeResponse{http.Header{ 120 | "Content-Type": []string{"text/plain"}, 121 | }} 122 | } 123 | return resps 124 | } 125 | -------------------------------------------------------------------------------- /cors.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package cors is net/http handler to handle CORS related requests 3 | as defined by http://www.w3.org/TR/cors/ 4 | 5 | You can configure it by passing an option struct to cors.New: 6 | 7 | c := cors.New(cors.Options{ 8 | AllowedOrigins: []string{"foo.com"}, 9 | AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, 10 | AllowCredentials: true, 11 | }) 12 | 13 | Then insert the handler in the chain: 14 | 15 | handler = c.Handler(handler) 16 | 17 | See Options documentation for more options. 18 | 19 | The resulting handler is a standard net/http handler. 20 | */ 21 | package cors 22 | 23 | import ( 24 | "log" 25 | "net/http" 26 | "os" 27 | "strconv" 28 | "strings" 29 | 30 | "github.com/rs/cors/internal" 31 | ) 32 | 33 | var headerVaryOrigin = []string{"Origin"} 34 | var headerOriginAll = []string{"*"} 35 | var headerTrue = []string{"true"} 36 | 37 | // Options is a configuration container to setup the CORS middleware. 38 | type Options struct { 39 | // AllowedOrigins is a list of origins a cross-domain request can be executed from. 40 | // If the special "*" value is present in the list, all origins will be allowed. 41 | // An origin may contain a wildcard (*) to replace 0 or more characters 42 | // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty. 43 | // Only one wildcard can be used per origin. 44 | // Default value is ["*"] 45 | AllowedOrigins []string 46 | // AllowOriginFunc is a custom function to validate the origin. It take the 47 | // origin as argument and returns true if allowed or false otherwise. If 48 | // this option is set, the content of `AllowedOrigins` is ignored. 49 | AllowOriginFunc func(origin string) bool 50 | // AllowOriginRequestFunc is a custom function to validate the origin. It 51 | // takes the HTTP Request object and the origin as argument and returns true 52 | // if allowed or false otherwise. If headers are used take the decision, 53 | // consider using AllowOriginVaryRequestFunc instead. If this option is set, 54 | // the contents of `AllowedOrigins`, `AllowOriginFunc` are ignored. 55 | // 56 | // Deprecated: use `AllowOriginVaryRequestFunc` instead. 57 | AllowOriginRequestFunc func(r *http.Request, origin string) bool 58 | // AllowOriginVaryRequestFunc is a custom function to validate the origin. 59 | // It takes the HTTP Request object and the origin as argument and returns 60 | // true if allowed or false otherwise with a list of headers used to take 61 | // that decision if any so they can be added to the Vary header. If this 62 | // option is set, the contents of `AllowedOrigins`, `AllowOriginFunc` and 63 | // `AllowOriginRequestFunc` are ignored. 64 | AllowOriginVaryRequestFunc func(r *http.Request, origin string) (bool, []string) 65 | // AllowedMethods is a list of methods the client is allowed to use with 66 | // cross-domain requests. Default value is simple methods (HEAD, GET and POST). 67 | AllowedMethods []string 68 | // AllowedHeaders is list of non simple headers the client is allowed to use with 69 | // cross-domain requests. 70 | // If the special "*" value is present in the list, all headers will be allowed. 71 | // Default value is []. 72 | AllowedHeaders []string 73 | // ExposedHeaders indicates which headers are safe to expose to the API of a CORS 74 | // API specification 75 | ExposedHeaders []string 76 | // MaxAge indicates how long (in seconds) the results of a preflight request 77 | // can be cached. Default value is 0, which stands for no 78 | // Access-Control-Max-Age header to be sent back, resulting in browsers 79 | // using their default value (5s by spec). If you need to force a 0 max-age, 80 | // set `MaxAge` to a negative value (ie: -1). 81 | MaxAge int 82 | // AllowCredentials indicates whether the request can include user credentials like 83 | // cookies, HTTP authentication or client side SSL certificates. 84 | AllowCredentials bool 85 | // AllowPrivateNetwork indicates whether to accept cross-origin requests over a 86 | // private network. 87 | AllowPrivateNetwork bool 88 | // OptionsPassthrough instructs preflight to let other potential next handlers to 89 | // process the OPTIONS method. Turn this on if your application handles OPTIONS. 90 | OptionsPassthrough bool 91 | // Provides a status code to use for successful OPTIONS requests. 92 | // Default value is http.StatusNoContent (204). 93 | OptionsSuccessStatus int 94 | // Debugging flag adds additional output to debug server side CORS issues 95 | Debug bool 96 | // Adds a custom logger, implies Debug is true 97 | Logger Logger 98 | } 99 | 100 | // Logger generic interface for logger 101 | type Logger interface { 102 | Printf(string, ...interface{}) 103 | } 104 | 105 | // Cors http handler 106 | type Cors struct { 107 | // Debug logger 108 | Log Logger 109 | // Normalized list of plain allowed origins 110 | allowedOrigins []string 111 | // List of allowed origins containing wildcards 112 | allowedWOrigins []wildcard 113 | // Optional origin validator function 114 | allowOriginFunc func(r *http.Request, origin string) (bool, []string) 115 | // Normalized list of allowed headers 116 | // Note: the Fetch standard guarantees that CORS-unsafe request-header names 117 | // (i.e. the values listed in the Access-Control-Request-Headers header) 118 | // are unique and sorted; 119 | // see https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names. 120 | allowedHeaders internal.SortedSet 121 | // Normalized list of allowed methods 122 | allowedMethods []string 123 | // Pre-computed normalized list of exposed headers 124 | exposedHeaders []string 125 | // Pre-computed maxAge header value 126 | maxAge []string 127 | // Set to true when allowed origins contains a "*" 128 | allowedOriginsAll bool 129 | // Set to true when allowed headers contains a "*" 130 | allowedHeadersAll bool 131 | // Status code to use for successful OPTIONS requests 132 | optionsSuccessStatus int 133 | allowCredentials bool 134 | allowPrivateNetwork bool 135 | optionPassthrough bool 136 | preflightVary []string 137 | } 138 | 139 | // New creates a new Cors handler with the provided options. 140 | func New(options Options) *Cors { 141 | c := &Cors{ 142 | allowCredentials: options.AllowCredentials, 143 | allowPrivateNetwork: options.AllowPrivateNetwork, 144 | optionPassthrough: options.OptionsPassthrough, 145 | Log: options.Logger, 146 | } 147 | if options.Debug && c.Log == nil { 148 | c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags) 149 | } 150 | 151 | // Allowed origins 152 | switch { 153 | case options.AllowOriginVaryRequestFunc != nil: 154 | c.allowOriginFunc = options.AllowOriginVaryRequestFunc 155 | case options.AllowOriginRequestFunc != nil: 156 | c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) { 157 | return options.AllowOriginRequestFunc(r, origin), nil 158 | } 159 | case options.AllowOriginFunc != nil: 160 | c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) { 161 | return options.AllowOriginFunc(origin), nil 162 | } 163 | case len(options.AllowedOrigins) == 0: 164 | if c.allowOriginFunc == nil { 165 | // Default is all origins 166 | c.allowedOriginsAll = true 167 | } 168 | default: 169 | c.allowedOrigins = []string{} 170 | c.allowedWOrigins = []wildcard{} 171 | for _, origin := range options.AllowedOrigins { 172 | // Note: for origins matching, the spec requires a case-sensitive matching. 173 | // As it may error prone, we chose to ignore the spec here. 174 | origin = strings.ToLower(origin) 175 | if origin == "*" { 176 | // If "*" is present in the list, turn the whole list into a match all 177 | c.allowedOriginsAll = true 178 | c.allowedOrigins = nil 179 | c.allowedWOrigins = nil 180 | break 181 | } else if i := strings.IndexByte(origin, '*'); i >= 0 { 182 | // Split the origin in two: start and end string without the * 183 | w := wildcard{origin[0:i], origin[i+1:]} 184 | c.allowedWOrigins = append(c.allowedWOrigins, w) 185 | } else { 186 | c.allowedOrigins = append(c.allowedOrigins, origin) 187 | } 188 | } 189 | } 190 | 191 | // Allowed Headers 192 | // Note: the Fetch standard guarantees that CORS-unsafe request-header names 193 | // (i.e. the values listed in the Access-Control-Request-Headers header) 194 | // are lowercase; see https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names. 195 | if len(options.AllowedHeaders) == 0 { 196 | // Use sensible defaults 197 | c.allowedHeaders = internal.NewSortedSet("accept", "content-type", "x-requested-with") 198 | } else { 199 | normalized := convert(options.AllowedHeaders, strings.ToLower) 200 | c.allowedHeaders = internal.NewSortedSet(normalized...) 201 | for _, h := range options.AllowedHeaders { 202 | if h == "*" { 203 | c.allowedHeadersAll = true 204 | c.allowedHeaders = internal.SortedSet{} 205 | break 206 | } 207 | } 208 | } 209 | 210 | // Allowed Methods 211 | if len(options.AllowedMethods) == 0 { 212 | // Default is spec's "simple" methods 213 | c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead} 214 | } else { 215 | c.allowedMethods = options.AllowedMethods 216 | } 217 | 218 | // Options Success Status Code 219 | if options.OptionsSuccessStatus == 0 { 220 | c.optionsSuccessStatus = http.StatusNoContent 221 | } else { 222 | c.optionsSuccessStatus = options.OptionsSuccessStatus 223 | } 224 | 225 | // Pre-compute exposed headers header value 226 | if len(options.ExposedHeaders) > 0 { 227 | c.exposedHeaders = []string{strings.Join(convert(options.ExposedHeaders, http.CanonicalHeaderKey), ", ")} 228 | } 229 | 230 | // Pre-compute prefight Vary header to save allocations 231 | if c.allowPrivateNetwork { 232 | c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network"} 233 | } else { 234 | c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"} 235 | } 236 | 237 | // Precompute max-age 238 | if options.MaxAge > 0 { 239 | c.maxAge = []string{strconv.Itoa(options.MaxAge)} 240 | } else if options.MaxAge < 0 { 241 | c.maxAge = []string{"0"} 242 | } 243 | 244 | return c 245 | } 246 | 247 | // Default creates a new Cors handler with default options. 248 | func Default() *Cors { 249 | return New(Options{}) 250 | } 251 | 252 | // AllowAll create a new Cors handler with permissive configuration allowing all 253 | // origins with all standard methods with any header and credentials. 254 | func AllowAll() *Cors { 255 | return New(Options{ 256 | AllowedOrigins: []string{"*"}, 257 | AllowedMethods: []string{ 258 | http.MethodHead, 259 | http.MethodGet, 260 | http.MethodPost, 261 | http.MethodPut, 262 | http.MethodPatch, 263 | http.MethodDelete, 264 | }, 265 | AllowedHeaders: []string{"*"}, 266 | AllowCredentials: false, 267 | }) 268 | } 269 | 270 | // Handler apply the CORS specification on the request, and add relevant CORS headers 271 | // as necessary. 272 | func (c *Cors) Handler(h http.Handler) http.Handler { 273 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 274 | if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { 275 | c.logf("Handler: Preflight request") 276 | c.handlePreflight(w, r) 277 | // Preflight requests are standalone and should stop the chain as some other 278 | // middleware may not handle OPTIONS requests correctly. One typical example 279 | // is authentication middleware ; OPTIONS requests won't carry authentication 280 | // headers (see #1) 281 | if c.optionPassthrough { 282 | h.ServeHTTP(w, r) 283 | } else { 284 | w.WriteHeader(c.optionsSuccessStatus) 285 | } 286 | } else { 287 | c.logf("Handler: Actual request") 288 | c.handleActualRequest(w, r) 289 | h.ServeHTTP(w, r) 290 | } 291 | }) 292 | } 293 | 294 | // HandlerFunc provides Martini compatible handler 295 | func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { 296 | if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { 297 | c.logf("HandlerFunc: Preflight request") 298 | c.handlePreflight(w, r) 299 | 300 | w.WriteHeader(c.optionsSuccessStatus) 301 | } else { 302 | c.logf("HandlerFunc: Actual request") 303 | c.handleActualRequest(w, r) 304 | } 305 | } 306 | 307 | // Negroni compatible interface 308 | func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 309 | if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { 310 | c.logf("ServeHTTP: Preflight request") 311 | c.handlePreflight(w, r) 312 | // Preflight requests are standalone and should stop the chain as some other 313 | // middleware may not handle OPTIONS requests correctly. One typical example 314 | // is authentication middleware ; OPTIONS requests won't carry authentication 315 | // headers (see #1) 316 | if c.optionPassthrough { 317 | next(w, r) 318 | } else { 319 | w.WriteHeader(c.optionsSuccessStatus) 320 | } 321 | } else { 322 | c.logf("ServeHTTP: Actual request") 323 | c.handleActualRequest(w, r) 324 | next(w, r) 325 | } 326 | } 327 | 328 | // handlePreflight handles pre-flight CORS requests 329 | func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { 330 | headers := w.Header() 331 | origin := r.Header.Get("Origin") 332 | 333 | if r.Method != http.MethodOptions { 334 | c.logf(" Preflight aborted: %s!=OPTIONS", r.Method) 335 | return 336 | } 337 | // Always set Vary headers 338 | // see https://github.com/rs/cors/issues/10, 339 | // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 340 | if vary, found := headers["Vary"]; found { 341 | headers["Vary"] = append(vary, c.preflightVary[0]) 342 | } else { 343 | headers["Vary"] = c.preflightVary 344 | } 345 | allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin) 346 | if len(additionalVaryHeaders) > 0 { 347 | headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", ")) 348 | } 349 | 350 | if origin == "" { 351 | c.logf(" Preflight aborted: empty origin") 352 | return 353 | } 354 | if !allowed { 355 | c.logf(" Preflight aborted: origin '%s' not allowed", origin) 356 | return 357 | } 358 | 359 | reqMethod := r.Header.Get("Access-Control-Request-Method") 360 | if !c.isMethodAllowed(reqMethod) { 361 | c.logf(" Preflight aborted: method '%s' not allowed", reqMethod) 362 | return 363 | } 364 | // Note: the Fetch standard guarantees that at most one 365 | // Access-Control-Request-Headers header is present in the preflight request; 366 | // see step 5.2 in https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. 367 | // However, some gateways split that header into multiple headers of the same name; 368 | // see https://github.com/rs/cors/issues/184. 369 | reqHeaders, found := r.Header["Access-Control-Request-Headers"] 370 | if found && !c.allowedHeadersAll && !c.allowedHeaders.Accepts(reqHeaders) { 371 | c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) 372 | return 373 | } 374 | if c.allowedOriginsAll { 375 | headers["Access-Control-Allow-Origin"] = headerOriginAll 376 | } else { 377 | headers["Access-Control-Allow-Origin"] = r.Header["Origin"] 378 | } 379 | // Spec says: Since the list of methods can be unbounded, simply returning the method indicated 380 | // by Access-Control-Request-Method (if supported) can be enough 381 | headers["Access-Control-Allow-Methods"] = r.Header["Access-Control-Request-Method"] 382 | if found && len(reqHeaders[0]) > 0 { 383 | // Spec says: Since the list of headers can be unbounded, simply returning supported headers 384 | // from Access-Control-Request-Headers can be enough 385 | headers["Access-Control-Allow-Headers"] = reqHeaders 386 | } 387 | if c.allowCredentials { 388 | headers["Access-Control-Allow-Credentials"] = headerTrue 389 | } 390 | if c.allowPrivateNetwork && r.Header.Get("Access-Control-Request-Private-Network") == "true" { 391 | headers["Access-Control-Allow-Private-Network"] = headerTrue 392 | } 393 | if len(c.maxAge) > 0 { 394 | headers["Access-Control-Max-Age"] = c.maxAge 395 | } 396 | c.logf(" Preflight response headers: %v", headers) 397 | } 398 | 399 | // handleActualRequest handles simple cross-origin requests, actual request or redirects 400 | func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { 401 | headers := w.Header() 402 | origin := r.Header.Get("Origin") 403 | 404 | allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin) 405 | 406 | // Always set Vary, see https://github.com/rs/cors/issues/10 407 | if vary := headers["Vary"]; vary == nil { 408 | headers["Vary"] = headerVaryOrigin 409 | } else { 410 | headers["Vary"] = append(vary, headerVaryOrigin[0]) 411 | } 412 | if len(additionalVaryHeaders) > 0 { 413 | headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", ")) 414 | } 415 | if origin == "" { 416 | c.logf(" Actual request no headers added: missing origin") 417 | return 418 | } 419 | if !allowed { 420 | c.logf(" Actual request no headers added: origin '%s' not allowed", origin) 421 | return 422 | } 423 | 424 | // Note that spec does define a way to specifically disallow a simple method like GET or 425 | // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the 426 | // spec doesn't instruct to check the allowed methods for simple cross-origin requests. 427 | // We think it's a nice feature to be able to have control on those methods though. 428 | if !c.isMethodAllowed(r.Method) { 429 | c.logf(" Actual request no headers added: method '%s' not allowed", r.Method) 430 | return 431 | } 432 | if c.allowedOriginsAll { 433 | headers["Access-Control-Allow-Origin"] = headerOriginAll 434 | } else { 435 | headers["Access-Control-Allow-Origin"] = r.Header["Origin"] 436 | } 437 | if len(c.exposedHeaders) > 0 { 438 | headers["Access-Control-Expose-Headers"] = c.exposedHeaders 439 | } 440 | if c.allowCredentials { 441 | headers["Access-Control-Allow-Credentials"] = headerTrue 442 | } 443 | c.logf(" Actual response added headers: %v", headers) 444 | } 445 | 446 | // convenience method. checks if a logger is set. 447 | func (c *Cors) logf(format string, a ...interface{}) { 448 | if c.Log != nil { 449 | c.Log.Printf(format, a...) 450 | } 451 | } 452 | 453 | // check the Origin of a request. No origin at all is also allowed. 454 | func (c *Cors) OriginAllowed(r *http.Request) bool { 455 | origin := r.Header.Get("Origin") 456 | allowed, _ := c.isOriginAllowed(r, origin) 457 | return allowed 458 | } 459 | 460 | // isOriginAllowed checks if a given origin is allowed to perform cross-domain requests 461 | // on the endpoint 462 | func (c *Cors) isOriginAllowed(r *http.Request, origin string) (allowed bool, varyHeaders []string) { 463 | if c.allowOriginFunc != nil { 464 | return c.allowOriginFunc(r, origin) 465 | } 466 | if c.allowedOriginsAll { 467 | return true, nil 468 | } 469 | origin = strings.ToLower(origin) 470 | for _, o := range c.allowedOrigins { 471 | if o == origin { 472 | return true, nil 473 | } 474 | } 475 | for _, w := range c.allowedWOrigins { 476 | if w.match(origin) { 477 | return true, nil 478 | } 479 | } 480 | return false, nil 481 | } 482 | 483 | // isMethodAllowed checks if a given method can be used as part of a cross-domain request 484 | // on the endpoint 485 | func (c *Cors) isMethodAllowed(method string) bool { 486 | if len(c.allowedMethods) == 0 { 487 | // If no method allowed, always return false, even for preflight request 488 | return false 489 | } 490 | if method == http.MethodOptions { 491 | // Always allow preflight requests 492 | return true 493 | } 494 | for _, m := range c.allowedMethods { 495 | if m == method { 496 | return true 497 | } 498 | } 499 | return false 500 | } 501 | -------------------------------------------------------------------------------- /cors_test.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "regexp" 9 | "slices" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | var testResponse = []byte("bar") 15 | var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | _, _ = w.Write(testResponse) 17 | }) 18 | 19 | // For each key-value pair of this map, the value indicates whether the key 20 | // is a list-based field (i.e. not a singleton field); 21 | // see https://httpwg.org/specs/rfc9110.html#abnf.extension. 22 | var allRespHeaders = map[string]bool{ 23 | // see https://www.rfc-editor.org/rfc/rfc9110#section-12.5.5 24 | "Vary": true, 25 | // see https://fetch.spec.whatwg.org/#http-new-header-syntax 26 | "Access-Control-Allow-Origin": false, 27 | "Access-Control-Allow-Credentials": false, 28 | "Access-Control-Allow-Methods": true, 29 | "Access-Control-Allow-Headers": true, 30 | "Access-Control-Max-Age": false, 31 | "Access-Control-Expose-Headers": true, 32 | // see https://wicg.github.io/private-network-access/ 33 | "Access-Control-Allow-Private-Network": false, 34 | } 35 | 36 | func assertHeaders(t *testing.T, resHeaders http.Header, expHeaders http.Header) { 37 | t.Helper() 38 | for name, listBased := range allRespHeaders { 39 | got := resHeaders[name] 40 | want := expHeaders[name] 41 | if !listBased && !slices.Equal(got, want) { 42 | t.Errorf("Response header %q = %q, want %q", name, got, want) 43 | continue 44 | } 45 | if listBased && !slices.Equal(normalize(got), normalize(want)) { 46 | t.Errorf("Response header %q = %q, want %q", name, got, want) 47 | continue 48 | } 49 | } 50 | } 51 | 52 | // normalize normalizes a list-based field value, 53 | // preserving both empty elements and the order of elements. 54 | func normalize(s []string) (res []string) { 55 | for _, v := range s { 56 | for _, e := range strings.Split(v, ",") { 57 | e = strings.Trim(e, " \t") 58 | res = append(res, e) 59 | } 60 | } 61 | return 62 | } 63 | 64 | func assertResponse(t *testing.T, res *httptest.ResponseRecorder, responseCode int) { 65 | t.Helper() 66 | if responseCode != res.Code { 67 | t.Errorf("assertResponse: expected response code to be %d but got %d. ", responseCode, res.Code) 68 | } 69 | } 70 | 71 | func TestSpec(t *testing.T) { 72 | cases := []struct { 73 | name string 74 | options Options 75 | method string 76 | reqHeaders http.Header 77 | resHeaders http.Header 78 | originAllowed bool 79 | }{ 80 | { 81 | "NoConfig", 82 | Options{ 83 | // Intentionally left blank. 84 | }, 85 | "GET", 86 | http.Header{}, 87 | http.Header{ 88 | "Vary": {"Origin"}, 89 | }, 90 | true, 91 | }, 92 | { 93 | "MatchAllOrigin", 94 | Options{ 95 | AllowedOrigins: []string{"*"}, 96 | }, 97 | "GET", 98 | http.Header{ 99 | "Origin": {"http://foobar.com"}, 100 | }, 101 | http.Header{ 102 | "Vary": {"Origin"}, 103 | "Access-Control-Allow-Origin": {"*"}, 104 | }, 105 | true, 106 | }, 107 | { 108 | "MatchAllOriginWithCredentials", 109 | Options{ 110 | AllowedOrigins: []string{"*"}, 111 | AllowCredentials: true, 112 | }, 113 | "GET", 114 | http.Header{ 115 | "Origin": {"http://foobar.com"}, 116 | }, 117 | http.Header{ 118 | "Vary": {"Origin"}, 119 | "Access-Control-Allow-Origin": {"*"}, 120 | "Access-Control-Allow-Credentials": {"true"}, 121 | }, 122 | true, 123 | }, 124 | { 125 | "AllowedOrigin", 126 | Options{ 127 | AllowedOrigins: []string{"http://foobar.com"}, 128 | }, 129 | "GET", 130 | http.Header{ 131 | "Origin": {"http://foobar.com"}, 132 | }, 133 | http.Header{ 134 | "Vary": {"Origin"}, 135 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 136 | }, 137 | true, 138 | }, 139 | { 140 | "WildcardOrigin", 141 | Options{ 142 | AllowedOrigins: []string{"http://*.bar.com"}, 143 | }, 144 | "GET", 145 | http.Header{ 146 | "Origin": {"http://foo.bar.com"}, 147 | }, 148 | http.Header{ 149 | "Vary": {"Origin"}, 150 | "Access-Control-Allow-Origin": {"http://foo.bar.com"}, 151 | }, 152 | true, 153 | }, 154 | { 155 | "DisallowedOrigin", 156 | Options{ 157 | AllowedOrigins: []string{"http://foobar.com"}, 158 | }, 159 | "GET", 160 | http.Header{ 161 | "Origin": {"http://barbaz.com"}, 162 | }, 163 | http.Header{ 164 | "Vary": {"Origin"}, 165 | }, 166 | false, 167 | }, 168 | { 169 | "DisallowedWildcardOrigin", 170 | Options{ 171 | AllowedOrigins: []string{"http://*.bar.com"}, 172 | }, 173 | "GET", 174 | http.Header{ 175 | "Origin": {"http://foo.baz.com"}, 176 | }, 177 | http.Header{ 178 | "Vary": {"Origin"}, 179 | }, 180 | false, 181 | }, 182 | { 183 | "AllowedOriginFuncMatch", 184 | Options{ 185 | AllowOriginFunc: func(o string) bool { 186 | return regexp.MustCompile("^http://foo").MatchString(o) 187 | }, 188 | }, 189 | "GET", 190 | http.Header{ 191 | "Origin": {"http://foobar.com"}, 192 | }, 193 | http.Header{ 194 | "Vary": {"Origin"}, 195 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 196 | }, 197 | true, 198 | }, 199 | { 200 | "AllowOriginRequestFuncMatch", 201 | Options{ 202 | AllowOriginRequestFunc: func(r *http.Request, o string) bool { 203 | return regexp.MustCompile("^http://foo").MatchString(o) && r.Header.Get("Authorization") == "secret" 204 | }, 205 | }, 206 | "GET", 207 | http.Header{ 208 | "Origin": {"http://foobar.com"}, 209 | "Authorization": {"secret"}, 210 | }, 211 | http.Header{ 212 | "Vary": {"Origin"}, 213 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 214 | }, 215 | true, 216 | }, 217 | { 218 | "AllowOriginVaryRequestFuncMatch", 219 | Options{ 220 | AllowOriginVaryRequestFunc: func(r *http.Request, o string) (bool, []string) { 221 | return regexp.MustCompile("^http://foo").MatchString(o) && r.Header.Get("Authorization") == "secret", []string{"Authorization"} 222 | }, 223 | }, 224 | "GET", 225 | http.Header{ 226 | "Origin": {"http://foobar.com"}, 227 | "Authorization": {"secret"}, 228 | }, 229 | http.Header{ 230 | "Vary": {"Origin, Authorization"}, 231 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 232 | }, 233 | true, 234 | }, 235 | { 236 | "AllowOriginRequestFuncNotMatch", 237 | Options{ 238 | AllowOriginRequestFunc: func(r *http.Request, o string) bool { 239 | return regexp.MustCompile("^http://foo").MatchString(o) && r.Header.Get("Authorization") == "secret" 240 | }, 241 | }, 242 | "GET", 243 | http.Header{ 244 | "Origin": {"http://foobar.com"}, 245 | "Authorization": {"not-secret"}, 246 | }, 247 | http.Header{ 248 | "Vary": {"Origin"}, 249 | }, 250 | false, 251 | }, 252 | { 253 | "MaxAge", 254 | Options{ 255 | AllowedOrigins: []string{"http://example.com"}, 256 | AllowedMethods: []string{"GET"}, 257 | MaxAge: 10, 258 | }, 259 | "OPTIONS", 260 | http.Header{ 261 | "Origin": {"http://example.com"}, 262 | "Access-Control-Request-Method": {"GET"}, 263 | }, 264 | http.Header{ 265 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 266 | "Access-Control-Allow-Origin": {"http://example.com"}, 267 | "Access-Control-Allow-Methods": {"GET"}, 268 | "Access-Control-Max-Age": {"10"}, 269 | }, 270 | true, 271 | }, 272 | { 273 | "MaxAgeNegative", 274 | Options{ 275 | AllowedOrigins: []string{"http://example.com"}, 276 | AllowedMethods: []string{"GET"}, 277 | MaxAge: -1, 278 | }, 279 | "OPTIONS", 280 | http.Header{ 281 | "Origin": {"http://example.com"}, 282 | "Access-Control-Request-Method": {"GET"}, 283 | }, 284 | http.Header{ 285 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 286 | "Access-Control-Allow-Origin": {"http://example.com"}, 287 | "Access-Control-Allow-Methods": {"GET"}, 288 | "Access-Control-Max-Age": {"0"}, 289 | }, 290 | true, 291 | }, 292 | { 293 | "AllowedMethod", 294 | Options{ 295 | AllowedOrigins: []string{"http://foobar.com"}, 296 | AllowedMethods: []string{"PUT", "DELETE"}, 297 | }, 298 | "OPTIONS", 299 | http.Header{ 300 | "Origin": {"http://foobar.com"}, 301 | "Access-Control-Request-Method": {"PUT"}, 302 | }, 303 | http.Header{ 304 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 305 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 306 | "Access-Control-Allow-Methods": {"PUT"}, 307 | }, 308 | true, 309 | }, 310 | { 311 | "DisallowedMethod", 312 | Options{ 313 | AllowedOrigins: []string{"http://foobar.com"}, 314 | AllowedMethods: []string{"PUT", "DELETE"}, 315 | }, 316 | "OPTIONS", 317 | http.Header{ 318 | "Origin": {"http://foobar.com"}, 319 | "Access-Control-Request-Method": {"PATCH"}, 320 | }, 321 | http.Header{ 322 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 323 | }, 324 | true, 325 | }, 326 | { 327 | "AllowedHeaders", 328 | Options{ 329 | AllowedOrigins: []string{"http://foobar.com"}, 330 | AllowedHeaders: []string{"X-Header-1", "x-header-2", "X-HEADER-3"}, 331 | }, 332 | "OPTIONS", 333 | http.Header{ 334 | "Origin": {"http://foobar.com"}, 335 | "Access-Control-Request-Method": {"GET"}, 336 | "Access-Control-Request-Headers": {"x-header-1,x-header-2"}, 337 | }, 338 | http.Header{ 339 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 340 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 341 | "Access-Control-Allow-Methods": {"GET"}, 342 | "Access-Control-Allow-Headers": {"x-header-1,x-header-2"}, 343 | }, 344 | true, 345 | }, 346 | { 347 | "DefaultAllowedHeaders", 348 | Options{ 349 | AllowedOrigins: []string{"http://foobar.com"}, 350 | AllowedHeaders: []string{}, 351 | }, 352 | "OPTIONS", 353 | http.Header{ 354 | "Origin": {"http://foobar.com"}, 355 | "Access-Control-Request-Method": {"GET"}, 356 | "Access-Control-Request-Headers": {"x-requested-with"}, 357 | }, 358 | http.Header{ 359 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 360 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 361 | "Access-Control-Allow-Methods": {"GET"}, 362 | "Access-Control-Allow-Headers": {"x-requested-with"}, 363 | }, 364 | true, 365 | }, 366 | { 367 | "AllowedWildcardHeader", 368 | Options{ 369 | AllowedOrigins: []string{"http://foobar.com"}, 370 | AllowedHeaders: []string{"*"}, 371 | }, 372 | "OPTIONS", 373 | http.Header{ 374 | "Origin": {"http://foobar.com"}, 375 | "Access-Control-Request-Method": {"GET"}, 376 | "Access-Control-Request-Headers": {"x-header-1,x-header-2"}, 377 | }, 378 | http.Header{ 379 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 380 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 381 | "Access-Control-Allow-Methods": {"GET"}, 382 | "Access-Control-Allow-Headers": {"x-header-1,x-header-2"}, 383 | }, 384 | true, 385 | }, 386 | { 387 | "DisallowedHeader", 388 | Options{ 389 | AllowedOrigins: []string{"http://foobar.com"}, 390 | AllowedHeaders: []string{"X-Header-1", "x-header-2"}, 391 | }, 392 | "OPTIONS", 393 | http.Header{ 394 | "Origin": {"http://foobar.com"}, 395 | "Access-Control-Request-Method": {"GET"}, 396 | "Access-Control-Request-Headers": {"x-header-1,x-header-3"}, 397 | }, 398 | http.Header{ 399 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 400 | }, 401 | true, 402 | }, 403 | { 404 | "ExposedHeader", 405 | Options{ 406 | AllowedOrigins: []string{"http://foobar.com"}, 407 | ExposedHeaders: []string{"X-Header-1", "x-header-2"}, 408 | }, 409 | "GET", 410 | http.Header{ 411 | "Origin": {"http://foobar.com"}, 412 | }, 413 | http.Header{ 414 | "Vary": {"Origin"}, 415 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 416 | "Access-Control-Expose-Headers": {"X-Header-1, X-Header-2"}, 417 | }, 418 | true, 419 | }, 420 | { 421 | "AllowedCredentials", 422 | Options{ 423 | AllowedOrigins: []string{"http://foobar.com"}, 424 | AllowCredentials: true, 425 | }, 426 | "OPTIONS", 427 | http.Header{ 428 | "Origin": {"http://foobar.com"}, 429 | "Access-Control-Request-Method": {"GET"}, 430 | }, 431 | http.Header{ 432 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 433 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 434 | "Access-Control-Allow-Methods": {"GET"}, 435 | "Access-Control-Allow-Credentials": {"true"}, 436 | }, 437 | true, 438 | }, 439 | { 440 | "AllowedPrivateNetwork", 441 | Options{ 442 | AllowedOrigins: []string{"http://foobar.com"}, 443 | AllowPrivateNetwork: true, 444 | }, 445 | "OPTIONS", 446 | http.Header{ 447 | "Origin": {"http://foobar.com"}, 448 | "Access-Control-Request-Method": {"GET"}, 449 | "Access-Control-Request-Private-Network": {"true"}, 450 | }, 451 | http.Header{ 452 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network"}, 453 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 454 | "Access-Control-Allow-Methods": {"GET"}, 455 | "Access-Control-Allow-Private-Network": {"true"}, 456 | }, 457 | true, 458 | }, 459 | { 460 | "DisallowedPrivateNetwork", 461 | Options{ 462 | AllowedOrigins: []string{"http://foobar.com"}, 463 | }, 464 | "OPTIONS", 465 | http.Header{ 466 | "Origin": {"http://foobar.com"}, 467 | "Access-Control-Request-Method": {"GET"}, 468 | "Access-Control-Request-PrivateNetwork": {"true"}, 469 | }, 470 | http.Header{ 471 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 472 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 473 | "Access-Control-Allow-Methods": {"GET"}, 474 | }, 475 | true, 476 | }, 477 | { 478 | "OptionPassthrough", 479 | Options{ 480 | OptionsPassthrough: true, 481 | }, 482 | "OPTIONS", 483 | http.Header{ 484 | "Origin": {"http://foobar.com"}, 485 | "Access-Control-Request-Method": {"GET"}, 486 | }, 487 | http.Header{ 488 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 489 | "Access-Control-Allow-Origin": {"*"}, 490 | "Access-Control-Allow-Methods": {"GET"}, 491 | }, 492 | true, 493 | }, 494 | { 495 | "NonPreflightOptions", 496 | Options{ 497 | AllowedOrigins: []string{"http://foobar.com"}, 498 | }, 499 | "OPTIONS", 500 | http.Header{ 501 | "Origin": {"http://foobar.com"}, 502 | }, 503 | http.Header{ 504 | "Vary": {"Origin"}, 505 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 506 | }, 507 | true, 508 | }, { 509 | "AllowedOriginsPlusAllowOriginFunc", 510 | Options{ 511 | AllowedOrigins: []string{"*"}, 512 | AllowOriginFunc: func(origin string) bool { 513 | return true 514 | }, 515 | }, 516 | "GET", 517 | http.Header{ 518 | "Origin": {"http://foobar.com"}, 519 | }, 520 | http.Header{ 521 | "Vary": {"Origin"}, 522 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 523 | }, 524 | true, 525 | }, 526 | { 527 | "MultipleACRHHeaders", 528 | Options{ 529 | AllowedOrigins: []string{"http://foobar.com"}, 530 | AllowedHeaders: []string{"Content-Type", "Authorization"}, 531 | }, 532 | "OPTIONS", 533 | http.Header{ 534 | "Origin": {"http://foobar.com"}, 535 | "Access-Control-Request-Method": {"GET"}, 536 | "Access-Control-Request-Headers": {"authorization", "content-type"}, 537 | }, 538 | http.Header{ 539 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 540 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 541 | "Access-Control-Allow-Methods": {"GET"}, 542 | "Access-Control-Allow-Headers": {"authorization", "content-type"}, 543 | }, 544 | true, 545 | }, 546 | { 547 | "MultipleACRHHeadersWithOWSAndEmptyElements", 548 | Options{ 549 | AllowedOrigins: []string{"http://foobar.com"}, 550 | AllowedHeaders: []string{"Content-Type", "Authorization"}, 551 | }, 552 | "OPTIONS", 553 | http.Header{ 554 | "Origin": {"http://foobar.com"}, 555 | "Access-Control-Request-Method": {"GET"}, 556 | "Access-Control-Request-Headers": {"authorization\t", " ", " content-type"}, 557 | }, 558 | http.Header{ 559 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 560 | "Access-Control-Allow-Origin": {"http://foobar.com"}, 561 | "Access-Control-Allow-Methods": {"GET"}, 562 | "Access-Control-Allow-Headers": {"authorization\t", " ", " content-type"}, 563 | }, 564 | true, 565 | }, 566 | } 567 | for i := range cases { 568 | tc := cases[i] 569 | t.Run(tc.name, func(t *testing.T) { 570 | s := New(tc.options) 571 | 572 | req, _ := http.NewRequest(tc.method, "http://example.com/foo", nil) 573 | for name, values := range tc.reqHeaders { 574 | for _, value := range values { 575 | req.Header.Add(name, value) 576 | } 577 | } 578 | 579 | t.Run("OriginAllowed", func(t *testing.T) { 580 | if have, want := s.OriginAllowed(req), tc.originAllowed; have != want { 581 | t.Errorf("OriginAllowed have: %t want: %t", have, want) 582 | } 583 | }) 584 | 585 | t.Run("Handler", func(t *testing.T) { 586 | res := httptest.NewRecorder() 587 | s.Handler(testHandler).ServeHTTP(res, req) 588 | assertHeaders(t, res.Header(), tc.resHeaders) 589 | }) 590 | t.Run("HandlerFunc", func(t *testing.T) { 591 | res := httptest.NewRecorder() 592 | s.HandlerFunc(res, req) 593 | assertHeaders(t, res.Header(), tc.resHeaders) 594 | }) 595 | t.Run("Negroni", func(t *testing.T) { 596 | res := httptest.NewRecorder() 597 | s.ServeHTTP(res, req, testHandler) 598 | assertHeaders(t, res.Header(), tc.resHeaders) 599 | }) 600 | 601 | }) 602 | } 603 | } 604 | 605 | func TestDebug(t *testing.T) { 606 | s := New(Options{ 607 | Debug: true, 608 | }) 609 | 610 | if s.Log == nil { 611 | t.Error("Logger not created when debug=true") 612 | } 613 | } 614 | 615 | type testLogger struct { 616 | buf *bytes.Buffer 617 | } 618 | 619 | func (l *testLogger) Printf(format string, v ...interface{}) { 620 | fmt.Fprintf(l.buf, format, v...) 621 | } 622 | 623 | func TestLogger(t *testing.T) { 624 | logger := &testLogger{buf: &bytes.Buffer{}} 625 | s := New(Options{ 626 | Logger: logger, 627 | }) 628 | 629 | if s.Log == nil { 630 | t.Error("Logger not created when Logger is set") 631 | } 632 | s.logf("test") 633 | if logger.buf.String() != "test" { 634 | t.Error("Logger not used") 635 | } 636 | } 637 | 638 | func TestDefault(t *testing.T) { 639 | s := Default() 640 | if s.Log != nil { 641 | t.Error("c.log should be nil when Default") 642 | } 643 | if !s.allowedOriginsAll { 644 | t.Error("c.allowedOriginsAll should be true when Default") 645 | } 646 | if s.allowedHeaders.Size() == 0 { 647 | t.Error("c.allowedHeaders should be empty when Default") 648 | } 649 | if s.allowedMethods == nil { 650 | t.Error("c.allowedMethods should be nil when Default") 651 | } 652 | } 653 | 654 | func TestHandlePreflightInvalidOriginAbortion(t *testing.T) { 655 | s := New(Options{ 656 | AllowedOrigins: []string{"http://foo.com"}, 657 | }) 658 | res := httptest.NewRecorder() 659 | req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) 660 | req.Header.Add("Origin", "http://example.com") 661 | 662 | s.handlePreflight(res, req) 663 | 664 | assertHeaders(t, res.Header(), http.Header{ 665 | "Vary": {"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"}, 666 | }) 667 | } 668 | 669 | func TestHandlePreflightNoOptionsAbortion(t *testing.T) { 670 | s := New(Options{ 671 | // Intentionally left blank. 672 | }) 673 | res := httptest.NewRecorder() 674 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 675 | 676 | s.handlePreflight(res, req) 677 | 678 | assertHeaders(t, res.Header(), http.Header{}) 679 | } 680 | 681 | func TestHandleActualRequestInvalidOriginAbortion(t *testing.T) { 682 | s := New(Options{ 683 | AllowedOrigins: []string{"http://foo.com"}, 684 | }) 685 | res := httptest.NewRecorder() 686 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 687 | req.Header.Add("Origin", "http://example.com") 688 | 689 | s.handleActualRequest(res, req) 690 | 691 | assertHeaders(t, res.Header(), http.Header{ 692 | "Vary": {"Origin"}, 693 | }) 694 | } 695 | 696 | func TestHandleActualRequestInvalidMethodAbortion(t *testing.T) { 697 | s := New(Options{ 698 | AllowedMethods: []string{"POST"}, 699 | AllowCredentials: true, 700 | }) 701 | res := httptest.NewRecorder() 702 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 703 | req.Header.Add("Origin", "http://example.com") 704 | 705 | s.handleActualRequest(res, req) 706 | 707 | assertHeaders(t, res.Header(), http.Header{ 708 | "Vary": {"Origin"}, 709 | }) 710 | } 711 | 712 | func TestIsMethodAllowedReturnsFalseWithNoMethods(t *testing.T) { 713 | s := New(Options{ 714 | // Intentionally left blank. 715 | }) 716 | s.allowedMethods = []string{} 717 | if s.isMethodAllowed("") { 718 | t.Error("IsMethodAllowed should return false when c.allowedMethods is nil.") 719 | } 720 | } 721 | 722 | func TestIsMethodAllowedReturnsTrueWithOptions(t *testing.T) { 723 | s := New(Options{ 724 | // Intentionally left blank. 725 | }) 726 | if !s.isMethodAllowed("OPTIONS") { 727 | t.Error("IsMethodAllowed should return true when c.allowedMethods is nil.") 728 | } 729 | } 730 | 731 | func TestOptionsSuccessStatusCodeDefault(t *testing.T) { 732 | s := New(Options{ 733 | // Intentionally left blank. 734 | }) 735 | 736 | req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) 737 | req.Header.Add("Access-Control-Request-Method", "GET") 738 | 739 | t.Run("Handler", func(t *testing.T) { 740 | res := httptest.NewRecorder() 741 | s.Handler(testHandler).ServeHTTP(res, req) 742 | assertResponse(t, res, http.StatusNoContent) 743 | }) 744 | t.Run("HandlerFunc", func(t *testing.T) { 745 | res := httptest.NewRecorder() 746 | s.HandlerFunc(res, req) 747 | assertResponse(t, res, http.StatusNoContent) 748 | }) 749 | t.Run("Negroni", func(t *testing.T) { 750 | res := httptest.NewRecorder() 751 | s.ServeHTTP(res, req, testHandler) 752 | assertResponse(t, res, http.StatusNoContent) 753 | }) 754 | } 755 | 756 | func TestOptionsSuccessStatusCodeOverride(t *testing.T) { 757 | s := New(Options{ 758 | OptionsSuccessStatus: http.StatusOK, 759 | }) 760 | 761 | req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) 762 | req.Header.Add("Access-Control-Request-Method", "GET") 763 | 764 | t.Run("Handler", func(t *testing.T) { 765 | res := httptest.NewRecorder() 766 | s.Handler(testHandler).ServeHTTP(res, req) 767 | assertResponse(t, res, http.StatusOK) 768 | }) 769 | t.Run("HandlerFunc", func(t *testing.T) { 770 | res := httptest.NewRecorder() 771 | s.HandlerFunc(res, req) 772 | assertResponse(t, res, http.StatusOK) 773 | }) 774 | t.Run("Negroni", func(t *testing.T) { 775 | res := httptest.NewRecorder() 776 | s.ServeHTTP(res, req, testHandler) 777 | assertResponse(t, res, http.StatusOK) 778 | }) 779 | } 780 | 781 | func TestAccessControlExposeHeadersPresence(t *testing.T) { 782 | cases := []struct { 783 | name string 784 | options Options 785 | want bool 786 | }{ 787 | { 788 | name: "omit", 789 | options: Options{}, 790 | want: false, 791 | }, 792 | { 793 | name: "include", 794 | options: Options{ 795 | ExposedHeaders: []string{"X-Something"}, 796 | }, 797 | want: true, 798 | }, 799 | } 800 | 801 | for _, tt := range cases { 802 | t.Run(tt.name, func(t *testing.T) { 803 | s := New(tt.options) 804 | 805 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 806 | req.Header.Add("Origin", "http://foobar.com") 807 | 808 | assertExposeHeaders := func(t *testing.T, resHeaders http.Header) { 809 | if _, have := resHeaders["Access-Control-Expose-Headers"]; have != tt.want { 810 | t.Errorf("Access-Control-Expose-Headers have: %t want: %t", have, tt.want) 811 | } 812 | } 813 | 814 | t.Run("Handler", func(t *testing.T) { 815 | res := httptest.NewRecorder() 816 | s.Handler(testHandler).ServeHTTP(res, req) 817 | assertExposeHeaders(t, res.Header()) 818 | }) 819 | t.Run("HandlerFunc", func(t *testing.T) { 820 | res := httptest.NewRecorder() 821 | s.HandlerFunc(res, req) 822 | assertExposeHeaders(t, res.Header()) 823 | }) 824 | t.Run("Negroni", func(t *testing.T) { 825 | res := httptest.NewRecorder() 826 | s.ServeHTTP(res, req, testHandler) 827 | assertExposeHeaders(t, res.Header()) 828 | }) 829 | }) 830 | } 831 | 832 | } 833 | -------------------------------------------------------------------------------- /examples/alice/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/justinas/alice" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func main() { 11 | c := cors.New(cors.Options{ 12 | AllowedOrigins: []string{"http://foo.com"}, 13 | }) 14 | 15 | mux := http.NewServeMux() 16 | 17 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 18 | w.Header().Set("Content-Type", "application/json") 19 | w.Write([]byte("{\"hello\": \"world\"}")) 20 | }) 21 | 22 | chain := alice.New(c.Handler).Then(mux) 23 | http.ListenAndServe(":8080", chain) 24 | } 25 | -------------------------------------------------------------------------------- /examples/buffalo/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gobuffalo/buffalo" 7 | "github.com/gobuffalo/buffalo/render" 8 | "github.com/rs/cors" 9 | ) 10 | 11 | var r *render.Engine 12 | 13 | func init() { 14 | r = render.New(render.Options{}) 15 | } 16 | 17 | func main() { 18 | app := App() 19 | if err := app.Serve(); err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func App() *buffalo.App { 25 | app := buffalo.New(buffalo.Options{ 26 | PreWares: []buffalo.PreWare{cors.Default().Handler}, 27 | }) 28 | 29 | app.GET("/", HomeHandler) 30 | 31 | return app 32 | } 33 | 34 | func HomeHandler(c buffalo.Context) error { 35 | return c.Render(200, r.JSON(map[string]string{"message": "Welcome to Buffalo!"})) 36 | } 37 | -------------------------------------------------------------------------------- /examples/chi/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/go-chi/chi" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func main() { 11 | r := chi.NewRouter() 12 | 13 | // Use default options 14 | r.Use(cors.Default().Handler) 15 | 16 | r.Get("/", func(w http.ResponseWriter, r *http.Request) { 17 | w.Write([]byte("welcome")) 18 | }) 19 | 20 | http.ListenAndServe(":8080", r) 21 | } 22 | -------------------------------------------------------------------------------- /examples/default/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | mux := http.NewServeMux() 11 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 12 | w.Header().Set("Content-Type", "application/json") 13 | w.Write([]byte("{\"hello\": \"world\"}")) 14 | }) 15 | 16 | // Use default options 17 | handler := cors.Default().Handler(mux) 18 | http.ListenAndServe(":8080", handler) 19 | } 20 | -------------------------------------------------------------------------------- /examples/gin/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | cors "github.com/rs/cors/wrapper/gin" 8 | ) 9 | 10 | func main() { 11 | router := gin.Default() 12 | 13 | router.Use(cors.Default()) 14 | router.GET("/", func(context *gin.Context) { 15 | context.JSON(http.StatusOK, gin.H{"hello": "world"}) 16 | }) 17 | 18 | router.Run(":8080") 19 | } 20 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rs/cors/examples 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/codegangsta/negroni v1.0.0 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/go-chi/chi v1.5.5 9 | github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab 10 | github.com/gobuffalo/buffalo v1.1.0 11 | github.com/gorilla/mux v1.8.1 12 | github.com/julienschmidt/httprouter v1.3.0 13 | github.com/justinas/alice v1.2.0 14 | github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 15 | github.com/rs/cors v1.11.0 16 | github.com/rs/cors/wrapper/gin v0.0.0-20240428121854-85fc0cac7b03 17 | github.com/zenazn/goji v1.0.1 18 | ) 19 | 20 | require ( 21 | github.com/BurntSushi/toml v1.2.1 // indirect 22 | github.com/aymerick/douceur v0.2.0 // indirect 23 | github.com/bytedance/sonic v1.9.1 // indirect 24 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 25 | github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect 26 | github.com/dustin/go-humanize v1.0.1 // indirect 27 | github.com/fatih/color v1.13.0 // indirect 28 | github.com/fatih/structs v1.1.0 // indirect 29 | github.com/felixge/httpsnoop v1.0.1 // indirect 30 | github.com/fsnotify/fsnotify v1.6.0 // indirect 31 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 32 | github.com/gin-contrib/sse v0.1.0 // indirect 33 | github.com/go-playground/locales v0.14.1 // indirect 34 | github.com/go-playground/universal-translator v0.18.1 // indirect 35 | github.com/go-playground/validator/v10 v10.14.0 // indirect 36 | github.com/gobuffalo/envy v1.10.2 // indirect 37 | github.com/gobuffalo/events v1.4.3 // indirect 38 | github.com/gobuffalo/flect v1.0.0 // indirect 39 | github.com/gobuffalo/github_flavored_markdown v1.1.3 // indirect 40 | github.com/gobuffalo/grift v1.5.2 // indirect 41 | github.com/gobuffalo/helpers v0.6.7 // indirect 42 | github.com/gobuffalo/logger v1.0.7 // indirect 43 | github.com/gobuffalo/meta v0.3.3 // indirect 44 | github.com/gobuffalo/nulls v0.4.2 // indirect 45 | github.com/gobuffalo/plush/v4 v4.1.18 // indirect 46 | github.com/gobuffalo/refresh v1.13.3 // indirect 47 | github.com/gobuffalo/tags/v3 v3.1.4 // indirect 48 | github.com/gobuffalo/validate/v3 v3.3.3 // indirect 49 | github.com/goccy/go-json v0.10.2 // indirect 50 | github.com/gofrs/uuid v4.2.0+incompatible // indirect 51 | github.com/gorilla/css v1.0.0 // indirect 52 | github.com/gorilla/handlers v1.5.1 // indirect 53 | github.com/gorilla/securecookie v1.1.1 // indirect 54 | github.com/gorilla/sessions v1.2.1 // indirect 55 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 56 | github.com/joho/godotenv v1.4.0 // indirect 57 | github.com/json-iterator/go v1.1.12 // indirect 58 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 59 | github.com/leodido/go-urn v1.2.4 // indirect 60 | github.com/mattn/go-colorable v0.1.9 // indirect 61 | github.com/mattn/go-isatty v0.0.19 // indirect 62 | github.com/microcosm-cc/bluemonday v1.0.20 // indirect 63 | github.com/mitchellh/go-homedir v1.1.0 // indirect 64 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 65 | github.com/modern-go/reflect2 v1.0.2 // indirect 66 | github.com/monoculum/formam v3.5.5+incompatible // indirect 67 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect 68 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 69 | github.com/rogpeppe/go-internal v1.9.0 // indirect 70 | github.com/sergi/go-diff v1.2.0 // indirect 71 | github.com/sirupsen/logrus v1.9.0 // indirect 72 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect 73 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect 74 | github.com/spf13/cobra v1.6.1 // indirect 75 | github.com/spf13/pflag v1.0.5 // indirect 76 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 77 | github.com/ugorji/go/codec v1.2.11 // indirect 78 | golang.org/x/arch v0.3.0 // indirect 79 | golang.org/x/crypto v0.21.0 // indirect 80 | golang.org/x/net v0.23.0 // indirect 81 | golang.org/x/sys v0.18.0 // indirect 82 | golang.org/x/term v0.18.0 // indirect 83 | golang.org/x/text v0.14.0 // indirect 84 | google.golang.org/protobuf v1.33.0 // indirect 85 | gopkg.in/yaml.v3 v3.0.1 // indirect 86 | ) 87 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 2 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 3 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 5 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 6 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 7 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 8 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 9 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 10 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 11 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 12 | github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= 13 | github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 14 | github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= 15 | github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 21 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 22 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 23 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 24 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 25 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 26 | github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= 27 | github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 28 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 29 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 30 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 31 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 32 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 33 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 34 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 35 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 36 | github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= 37 | github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= 38 | github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= 39 | github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= 40 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 41 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 42 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 43 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 44 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 45 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 46 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 47 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 48 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 49 | github.com/gobuffalo/buffalo v1.1.0 h1:6y1fUC47QWevaM1ImukJFHNgxiRIT+Y74VcP4ZQaz80= 50 | github.com/gobuffalo/buffalo v1.1.0/go.mod h1:lLsx9Y8bFYu9uvQyIEB3M0QA908ChHUPjwOGumZWARU= 51 | github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= 52 | github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= 53 | github.com/gobuffalo/events v1.4.3 h1:JYDq7NbozP10zaN9Ijfem6Ozox2KacU2fU38RyquXM8= 54 | github.com/gobuffalo/events v1.4.3/go.mod h1:2BwfpV5X63t8xkUcVqIv4IbyAobJazRSVu1F1pgf3rc= 55 | github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= 56 | github.com/gobuffalo/flect v1.0.0 h1:eBFmskjXZgAOagiTXJH25Nt5sdFwNRcb8DKZsIsAUQI= 57 | github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= 58 | github.com/gobuffalo/github_flavored_markdown v1.1.3 h1:rSMPtx9ePkFB22vJ+dH+m/EUBS8doQ3S8LeEXcdwZHk= 59 | github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= 60 | github.com/gobuffalo/grift v1.5.2 h1:mC0vHRs+nXz+JhkH3sv+rVnnTQRDXrUrOXOPYpgPjpo= 61 | github.com/gobuffalo/grift v1.5.2/go.mod h1:Uf/3T2AR1Vv+t84EPmxCjqQ8oyJwXs0FAoLMFUn/JVs= 62 | github.com/gobuffalo/helpers v0.6.7 h1:C9CedoRSfgWg2ZoIkVXgjI5kgmSpL34Z3qdnzpfNVd8= 63 | github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= 64 | github.com/gobuffalo/here v0.6.7/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc= 65 | github.com/gobuffalo/httptest v1.5.2 h1:GpGy520SfY1QEmyPvaqmznTpG4gEQqQ82HtHqyNEreM= 66 | github.com/gobuffalo/httptest v1.5.2/go.mod h1:FA23yjsWLGj92mVV74Qtc8eqluc11VqcWr8/C1vxt4g= 67 | github.com/gobuffalo/logger v1.0.7 h1:LTLwWelETXDYyqF/ASf0nxaIcdEOIJNxRokPcfI/xbU= 68 | github.com/gobuffalo/logger v1.0.7/go.mod h1:u40u6Bq3VVvaMcy5sRBclD8SXhBYPS0Qk95ubt+1xJM= 69 | github.com/gobuffalo/meta v0.3.3 h1:GwPWdbdnp4JrKASvMLa03OtmzISq7z/nE7T6aMqzoYM= 70 | github.com/gobuffalo/meta v0.3.3/go.mod h1:o4B099IUFUfK4555Guqxz1zHAqyuUQ/KtHXi8WvVeFE= 71 | github.com/gobuffalo/nulls v0.4.2 h1:GAqBR29R3oPY+WCC7JL9KKk9erchaNuV6unsOSZGQkw= 72 | github.com/gobuffalo/nulls v0.4.2/go.mod h1:EElw2zmBYafU2R9W4Ii1ByIj177wA/pc0JdjtD0EsH8= 73 | github.com/gobuffalo/plush/v4 v4.1.18 h1:bnPjdMTEUQHqj9TNX2Ck3mxEXYZa+0nrFMNM07kpX9g= 74 | github.com/gobuffalo/plush/v4 v4.1.18/go.mod h1:xi2tJIhFI4UdzIL8sxZtzGYOd2xbBpcFbLZlIPGGZhU= 75 | github.com/gobuffalo/refresh v1.13.3 h1:HYQlI6RiqWUf2yzCXvUHAYqm9M9/teVnox+mjzo/9rQ= 76 | github.com/gobuffalo/refresh v1.13.3/go.mod h1:NkzgLKZGk5suOvgvOD0/VALog0fH29Ib7fwym9JmRxA= 77 | github.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM= 78 | github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= 79 | github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4= 80 | github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= 81 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 82 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 83 | github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= 84 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 85 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 87 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 88 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 89 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 90 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 91 | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= 92 | github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= 93 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 94 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 95 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 96 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 97 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 98 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= 99 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 100 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 101 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 102 | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= 103 | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 104 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 105 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 106 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 107 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 108 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 109 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 110 | github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= 111 | github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= 112 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 113 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 114 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 115 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 116 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 117 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 118 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 119 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 120 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 121 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 122 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 123 | github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= 124 | github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= 125 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 126 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 127 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 128 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 129 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 130 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 131 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 132 | github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= 133 | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 134 | github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y= 135 | github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= 136 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 137 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 138 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 139 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 140 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 141 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 142 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 143 | github.com/monoculum/formam v3.5.5+incompatible h1:iPl5csfEN96G2N2mGu8V/ZB62XLf9ySTpC8KRH6qXec= 144 | github.com/monoculum/formam v3.5.5+incompatible/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= 145 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= 146 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= 147 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 148 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 149 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 150 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 151 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 152 | github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef h1:NKxTG6GVGbfMXc2mIk+KphcH6hagbVXhcFkbTgYleTI= 153 | github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI= 154 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 155 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 156 | github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= 157 | github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= 158 | github.com/rs/cors/wrapper/gin v0.0.0-20240428121854-85fc0cac7b03 h1:s7fD08L5WjfD/iFHrOOp9gmAAGcv2nWPEIJLEn07XDQ= 159 | github.com/rs/cors/wrapper/gin v0.0.0-20240428121854-85fc0cac7b03/go.mod h1:742Ialb8SOs5yB2PqRDzFcyND3280PoaS5/wcKQUQKE= 160 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 161 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 162 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 163 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 164 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 165 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= 166 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 167 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= 168 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 169 | github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 170 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 171 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 172 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 173 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 174 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 175 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 176 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 177 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 178 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 179 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 180 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 181 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 182 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 183 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 184 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 185 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 186 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 187 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 188 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 189 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 190 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 191 | github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= 192 | github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 193 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 194 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 195 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 196 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 197 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 198 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 199 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 200 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 201 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 202 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 203 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 204 | golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 205 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 206 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 207 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 208 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 209 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 210 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 211 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 212 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 213 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 217 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 218 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 219 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 220 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 221 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 222 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 223 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 224 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 225 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 226 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 227 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 228 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 229 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 230 | golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= 231 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 232 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 233 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 234 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 235 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 236 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 237 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 238 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 239 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 240 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 241 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 242 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 243 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 244 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 245 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 246 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 247 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 248 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 249 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 250 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 251 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 252 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 253 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 254 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 255 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 256 | -------------------------------------------------------------------------------- /examples/goji/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | "github.com/zenazn/goji" 8 | ) 9 | 10 | func main() { 11 | c := cors.New(cors.Options{ 12 | AllowedOrigins: []string{"http://foo.com"}, 13 | }) 14 | goji.Use(c.Handler) 15 | 16 | goji.Get("/", func(w http.ResponseWriter, r *http.Request) { 17 | w.Header().Set("Content-Type", "application/json") 18 | w.Write([]byte("{\"hello\": \"world\"}")) 19 | }) 20 | 21 | goji.Serve() 22 | } 23 | -------------------------------------------------------------------------------- /examples/gorilla/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func main() { 11 | r := mux.NewRouter() 12 | r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 13 | w.Header().Set("Content-Type", "application/json") 14 | w.Write([]byte("{\"hello\": \"world\"}")) 15 | }) 16 | 17 | // Use default options 18 | handler := cors.Default().Handler(r) 19 | http.ListenAndServe(":8080", handler) 20 | } 21 | -------------------------------------------------------------------------------- /examples/httprouter/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/julienschmidt/httprouter" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func Hello(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 11 | w.Header().Set("Content-Type", "application/json") 12 | w.Write([]byte("{\"hello\": \"world\"}")) 13 | } 14 | 15 | func main() { 16 | router := httprouter.New() 17 | router.GET("/", Hello) 18 | 19 | handler := cors.Default().Handler(router) 20 | 21 | http.ListenAndServe(":8080", handler) 22 | } 23 | -------------------------------------------------------------------------------- /examples/martini/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-martini/martini" 5 | "github.com/martini-contrib/render" 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | c := cors.New(cors.Options{ 11 | AllowedOrigins: []string{"http://foo.com"}, 12 | }) 13 | 14 | m := martini.Classic() 15 | m.Use(render.Renderer()) 16 | m.Use(c.HandlerFunc) 17 | 18 | m.Get("/", func(r render.Render) { 19 | r.JSON(200, map[string]interface{}{"hello": "world"}) 20 | }) 21 | 22 | m.Run() 23 | } 24 | -------------------------------------------------------------------------------- /examples/negroni/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/codegangsta/negroni" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func main() { 11 | c := cors.New(cors.Options{ 12 | AllowedOrigins: []string{"http://foo.com"}, 13 | }) 14 | 15 | mux := http.NewServeMux() 16 | 17 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 18 | w.Header().Set("Content-Type", "application/json") 19 | w.Write([]byte("{\"hello\": \"world\"}")) 20 | }) 21 | 22 | n := negroni.Classic() 23 | n.Use(c) 24 | n.UseHandler(mux) 25 | n.Run(":3000") 26 | } 27 | -------------------------------------------------------------------------------- /examples/nethttp/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | c := cors.New(cors.Options{ 11 | AllowedOrigins: []string{"http://foo.com"}, 12 | }) 13 | 14 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | w.Header().Set("Content-Type", "application/json") 16 | w.Write([]byte("{\"hello\": \"world\"}")) 17 | }) 18 | 19 | http.ListenAndServe(":8080", c.Handler(handler)) 20 | } 21 | -------------------------------------------------------------------------------- /examples/openbar/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | c := cors.New(cors.Options{ 11 | AllowedOrigins: []string{"*"}, 12 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, 13 | AllowCredentials: true, 14 | }) 15 | 16 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | w.Header().Set("Content-Type", "application/json") 18 | w.Write([]byte("{\"hello\": \"world\"}")) 19 | }) 20 | 21 | http.ListenAndServe(":8080", c.Handler(h)) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rs/cors 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /internal/sortedset.go: -------------------------------------------------------------------------------- 1 | // adapted from github.com/jub0bs/cors 2 | package internal 3 | 4 | import ( 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // A SortedSet represents a mathematical set of strings sorted in 10 | // lexicographical order. 11 | // Each element has a unique position ranging from 0 (inclusive) 12 | // to the set's cardinality (exclusive). 13 | // The zero value represents an empty set. 14 | type SortedSet struct { 15 | m map[string]int 16 | maxLen int 17 | } 18 | 19 | // NewSortedSet returns a SortedSet that contains all of elems, 20 | // but no other elements. 21 | func NewSortedSet(elems ...string) SortedSet { 22 | sort.Strings(elems) 23 | m := make(map[string]int) 24 | var maxLen int 25 | i := 0 26 | for _, s := range elems { 27 | if _, exists := m[s]; exists { 28 | continue 29 | } 30 | m[s] = i 31 | i++ 32 | maxLen = max(maxLen, len(s)) 33 | } 34 | return SortedSet{ 35 | m: m, 36 | maxLen: maxLen, 37 | } 38 | } 39 | 40 | // Size returns the cardinality of set. 41 | func (set SortedSet) Size() int { 42 | return len(set.m) 43 | } 44 | 45 | // String sorts joins the elements of set (in lexicographical order) 46 | // with a comma and returns the resulting string. 47 | func (set SortedSet) String() string { 48 | elems := make([]string, len(set.m)) 49 | for elem, i := range set.m { 50 | elems[i] = elem // safe indexing, by construction of SortedSet 51 | } 52 | return strings.Join(elems, ",") 53 | } 54 | 55 | // Accepts reports whether values is a sequence of list-based field values 56 | // whose elements are 57 | // - all members of set, 58 | // - sorted in lexicographical order, 59 | // - unique. 60 | func (set SortedSet) Accepts(values []string) bool { 61 | var ( // effectively constant 62 | maxLen = maxOWSBytes + set.maxLen + maxOWSBytes + 1 // +1 for comma 63 | ) 64 | var ( 65 | posOfLastNameSeen = -1 66 | name string 67 | commaFound bool 68 | emptyElements int 69 | ok bool 70 | ) 71 | for _, s := range values { 72 | for { 73 | // As a defense against maliciously long names in s, 74 | // we process only a small number of s's leading bytes per iteration. 75 | name, s, commaFound = cutAtComma(s, maxLen) 76 | name, ok = trimOWS(name, maxOWSBytes) 77 | if !ok { 78 | return false 79 | } 80 | if name == "" { 81 | // RFC 9110 requires recipients to tolerate 82 | // "a reasonable number of empty list elements"; see 83 | // https://httpwg.org/specs/rfc9110.html#abnf.extension.recipient. 84 | emptyElements++ 85 | if emptyElements > maxEmptyElements { 86 | return false 87 | } 88 | if !commaFound { // We have now exhausted the names in s. 89 | break 90 | } 91 | continue 92 | } 93 | pos, ok := set.m[name] 94 | if !ok { 95 | return false 96 | } 97 | // The names in s are expected to be sorted in lexicographical order 98 | // and to each appear at most once. 99 | // Therefore, the positions (in set) of the names that 100 | // appear in s should form a strictly increasing sequence. 101 | // If that's not actually the case, bail out. 102 | if pos <= posOfLastNameSeen { 103 | return false 104 | } 105 | posOfLastNameSeen = pos 106 | if !commaFound { // We have now exhausted the names in s. 107 | break 108 | } 109 | } 110 | } 111 | return true 112 | } 113 | 114 | const ( 115 | maxOWSBytes = 1 // number of leading/trailing OWS bytes tolerated 116 | maxEmptyElements = 16 // number of empty list elements tolerated 117 | ) 118 | 119 | func cutAtComma(s string, n int) (before, after string, found bool) { 120 | // Note: this implementation draws inspiration from strings.Cut's. 121 | end := min(len(s), n) 122 | if i := strings.IndexByte(s[:end], ','); i >= 0 { 123 | after = s[i+1:] // deal with this first to save one bounds check 124 | return s[:i], after, true 125 | } 126 | return s, "", false 127 | } 128 | 129 | // TrimOWS trims up to n bytes of [optional whitespace (OWS)] 130 | // from the start of and/or the end of s. 131 | // If no more than n bytes of OWS are found at the start of s 132 | // and no more than n bytes of OWS are found at the end of s, 133 | // it returns the trimmed result and true. 134 | // Otherwise, it returns the original string and false. 135 | // 136 | // [optional whitespace (OWS)]: https://httpwg.org/specs/rfc9110.html#whitespace 137 | func trimOWS(s string, n int) (trimmed string, ok bool) { 138 | if s == "" { 139 | return s, true 140 | } 141 | trimmed, ok = trimRightOWS(s, n) 142 | if !ok { 143 | return s, false 144 | } 145 | trimmed, ok = trimLeftOWS(trimmed, n) 146 | if !ok { 147 | return s, false 148 | } 149 | return trimmed, true 150 | } 151 | 152 | func trimLeftOWS(s string, n int) (string, bool) { 153 | sCopy := s 154 | var i int 155 | for len(s) > 0 { 156 | if i > n { 157 | return sCopy, false 158 | } 159 | if !(s[0] == ' ' || s[0] == '\t') { 160 | break 161 | } 162 | s = s[1:] 163 | i++ 164 | } 165 | return s, true 166 | } 167 | 168 | func trimRightOWS(s string, n int) (string, bool) { 169 | sCopy := s 170 | var i int 171 | for len(s) > 0 { 172 | if i > n { 173 | return sCopy, false 174 | } 175 | last := len(s) - 1 176 | if !(s[last] == ' ' || s[last] == '\t') { 177 | break 178 | } 179 | s = s[:last] 180 | i++ 181 | } 182 | return s, true 183 | } 184 | -------------------------------------------------------------------------------- /internal/sortedset_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "slices" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestSortedSet(t *testing.T) { 10 | cases := []struct { 11 | desc string 12 | elems []string 13 | // expectations 14 | size int 15 | combined string 16 | slice []string 17 | accepted [][]string 18 | rejected [][]string 19 | }{ 20 | { 21 | desc: "empty set", 22 | size: 0, 23 | combined: "", 24 | accepted: [][]string{ 25 | // some empty elements, possibly with OWS 26 | {""}, 27 | {","}, 28 | {"\t, , "}, 29 | // multiple field lines, some empty elements 30 | make([]string, maxEmptyElements), 31 | }, 32 | rejected: [][]string{ 33 | {"x-bar"}, 34 | {"x-bar,x-foo"}, 35 | // too many empty elements 36 | {strings.Repeat(",", maxEmptyElements+1)}, 37 | // multiple field lines, too many empty elements 38 | make([]string, maxEmptyElements+1), 39 | }, 40 | }, { 41 | desc: "singleton set", 42 | elems: []string{"x-foo"}, 43 | size: 1, 44 | combined: "x-foo", 45 | slice: []string{"X-Foo"}, 46 | accepted: [][]string{ 47 | {"x-foo"}, 48 | // some empty elements, possibly with OWS 49 | {""}, 50 | {","}, 51 | {"\t, , "}, 52 | {"\tx-foo ,"}, 53 | {" x-foo\t,"}, 54 | {strings.Repeat(",", maxEmptyElements) + "x-foo"}, 55 | // multiple field lines, some empty elements 56 | append(make([]string, maxEmptyElements), "x-foo"), 57 | make([]string, maxEmptyElements), 58 | }, 59 | rejected: [][]string{ 60 | {"x-bar"}, 61 | {"x-bar,x-foo"}, 62 | // too much OWS 63 | {"x-foo "}, 64 | {" x-foo "}, 65 | {" x-foo "}, 66 | {"x-foo\t\t"}, 67 | {"\tx-foo\t\t"}, 68 | {"\t\tx-foo\t\t"}, 69 | // too many empty elements 70 | {strings.Repeat(",", maxEmptyElements+1) + "x-foo"}, 71 | // multiple field lines, too many empty elements 72 | append(make([]string, maxEmptyElements+1), "x-foo"), 73 | make([]string, maxEmptyElements+1), 74 | }, 75 | }, { 76 | desc: "no dupes", 77 | elems: []string{"x-foo", "x-bar", "x-baz"}, 78 | size: 3, 79 | combined: "x-bar,x-baz,x-foo", 80 | slice: []string{"X-Bar", "X-Baz", "X-Foo"}, 81 | accepted: [][]string{ 82 | {"x-bar"}, 83 | {"x-baz"}, 84 | {"x-foo"}, 85 | {"x-bar,x-baz"}, 86 | {"x-bar,x-foo"}, 87 | {"x-baz,x-foo"}, 88 | {"x-bar,x-baz,x-foo"}, 89 | // some empty elements, possibly with OWS 90 | {""}, 91 | {","}, 92 | {"\t, , "}, 93 | {"\tx-bar ,"}, 94 | {" x-baz\t,"}, 95 | {"x-foo,"}, 96 | {"\tx-bar ,\tx-baz ,"}, 97 | {" x-bar\t, x-foo\t,"}, 98 | {"x-baz,x-foo,"}, 99 | {" x-bar , x-baz , x-foo ,"}, 100 | {"x-bar" + strings.Repeat(",", maxEmptyElements+1) + "x-foo"}, 101 | // multiple field lines 102 | {"x-bar", "x-foo"}, 103 | {"x-bar", "x-baz,x-foo"}, 104 | // multiple field lines, some empty elements 105 | append(make([]string, maxEmptyElements), "x-bar", "x-foo"), 106 | make([]string, maxEmptyElements), 107 | }, 108 | rejected: [][]string{ 109 | {"x-qux"}, 110 | {"x-bar,x-baz,x-baz"}, 111 | {"x-qux,x-baz"}, 112 | {"x-qux,x-foo"}, 113 | {"x-quxbaz,x-foo"}, 114 | // too much OWS 115 | {"x-bar "}, 116 | {" x-baz "}, 117 | {" x-foo "}, 118 | {"x-bar\t\t,x-baz"}, 119 | {"x-bar,\tx-foo\t\t"}, 120 | {"\t\tx-baz,x-foo\t\t"}, 121 | {" x-bar\t,\tx-baz\t ,x-foo"}, 122 | // too many empty elements 123 | {"x-bar" + strings.Repeat(",", maxEmptyElements+2) + "x-foo"}, 124 | // multiple field lines, elements in the wrong order 125 | {"x-foo", "x-bar"}, 126 | // multiple field lines, too many empty elements 127 | append(make([]string, maxEmptyElements+1), "x-bar", "x-foo"), 128 | make([]string, maxEmptyElements+1), 129 | }, 130 | }, { 131 | desc: "some dupes", 132 | elems: []string{"x-foo", "x-bar", "x-foo"}, 133 | size: 2, 134 | combined: "x-bar,x-foo", 135 | slice: []string{"X-Bar", "X-Foo"}, 136 | accepted: [][]string{ 137 | {"x-bar"}, 138 | {"x-foo"}, 139 | {"x-bar,x-foo"}, 140 | // some empty elements, possibly with OWS 141 | {""}, 142 | {","}, 143 | {"\t, , "}, 144 | {"\tx-bar ,"}, 145 | {" x-foo\t,"}, 146 | {"x-foo,"}, 147 | {"\tx-bar ,\tx-foo ,"}, 148 | {" x-bar\t, x-foo\t,"}, 149 | {"x-bar,x-foo,"}, 150 | {" x-bar , x-foo ,"}, 151 | {"x-bar" + strings.Repeat(",", maxEmptyElements+1) + "x-foo"}, 152 | // multiple field lines 153 | {"x-bar", "x-foo"}, 154 | // multiple field lines, some empty elements 155 | append(make([]string, maxEmptyElements), "x-bar", "x-foo"), 156 | make([]string, maxEmptyElements), 157 | }, 158 | rejected: [][]string{ 159 | {"x-qux"}, 160 | {"x-qux,x-bar"}, 161 | {"x-qux,x-foo"}, 162 | {"x-qux,x-baz,x-foo"}, 163 | // too much OWS 164 | {"x-qux "}, 165 | {"x-qux,\t\tx-bar"}, 166 | {"x-qux,x-foo\t\t"}, 167 | {"\tx-qux , x-baz\t\t,x-foo"}, 168 | // too many empty elements 169 | {"x-bar" + strings.Repeat(",", maxEmptyElements+2) + "x-foo"}, 170 | // multiple field lines, elements in the wrong order 171 | {"x-foo", "x-bar"}, 172 | // multiple field lines, too much whitespace 173 | {"x-qux", "\t\tx-bar"}, 174 | {"x-qux", "x-foo\t\t"}, 175 | {"\tx-qux ", " x-baz\t\t,x-foo"}, 176 | // multiple field lines, too many empty elements 177 | append(make([]string, maxEmptyElements+1), "x-bar", "x-foo"), 178 | make([]string, maxEmptyElements+1), 179 | }, 180 | }, 181 | } 182 | for _, tc := range cases { 183 | f := func(t *testing.T) { 184 | elems := slices.Clone(tc.elems) 185 | set := NewSortedSet(tc.elems...) 186 | size := set.Size() 187 | if set.Size() != tc.size { 188 | const tmpl = "NewSortedSet(%#v...).Size(): got %d; want %d" 189 | t.Errorf(tmpl, elems, size, tc.size) 190 | } 191 | combined := set.String() 192 | if combined != tc.combined { 193 | const tmpl = "NewSortedSet(%#v...).String(): got %q; want %q" 194 | t.Errorf(tmpl, elems, combined, tc.combined) 195 | } 196 | for _, a := range tc.accepted { 197 | if !set.Accepts(a) { 198 | const tmpl = "%q rejects %q, but should accept it" 199 | t.Errorf(tmpl, set, a) 200 | } 201 | } 202 | for _, r := range tc.rejected { 203 | if set.Accepts(r) { 204 | const tmpl = "%q accepts %q, but should reject it" 205 | t.Errorf(tmpl, set, r) 206 | } 207 | } 208 | } 209 | t.Run(tc.desc, f) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type wildcard struct { 8 | prefix string 9 | suffix string 10 | } 11 | 12 | func (w wildcard) match(s string) bool { 13 | return len(s) >= len(w.prefix)+len(w.suffix) && 14 | strings.HasPrefix(s, w.prefix) && 15 | strings.HasSuffix(s, w.suffix) 16 | } 17 | 18 | // convert converts a list of string using the passed converter function 19 | func convert(s []string, f func(string) string) []string { 20 | out := make([]string, len(s)) 21 | for i := range s { 22 | out[i] = f(s[i]) 23 | } 24 | return out 25 | } 26 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestWildcard(t *testing.T) { 9 | w := wildcard{"foo", "bar"} 10 | if !w.match("foobar") { 11 | t.Error("foo*bar should match foobar") 12 | } 13 | if !w.match("foobazbar") { 14 | t.Error("foo*bar should match foobazbar") 15 | } 16 | if w.match("foobaz") { 17 | t.Error("foo*bar should not match foobaz") 18 | } 19 | 20 | w = wildcard{"foo", "oof"} 21 | if w.match("foof") { 22 | t.Error("foo*oof should not match foof") 23 | } 24 | } 25 | 26 | func TestConvert(t *testing.T) { 27 | s := convert([]string{"A", "b", "C"}, strings.ToLower) 28 | e := []string{"a", "b", "c"} 29 | if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] { 30 | t.Errorf("%v != %v", s, e) 31 | } 32 | } 33 | 34 | func BenchmarkWildcard(b *testing.B) { 35 | w := wildcard{"foo", "bar"} 36 | b.Run("match", func(b *testing.B) { 37 | b.ReportAllocs() 38 | for i := 0; i < b.N; i++ { 39 | w.match("foobazbar") 40 | } 41 | }) 42 | b.Run("too short", func(b *testing.B) { 43 | b.ReportAllocs() 44 | for i := 0; i < b.N; i++ { 45 | w.match("fobar") 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /wrapper/gin/gin.go: -------------------------------------------------------------------------------- 1 | // Package cors/wrapper/gin provides gin.HandlerFunc to handle CORS related 2 | // requests as a wrapper of github.com/rs/cors handler. 3 | package gin 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/rs/cors" 10 | ) 11 | 12 | // Options is a configuration container to setup the CORS middleware. 13 | type Options = cors.Options 14 | 15 | // corsWrapper is a wrapper of cors.Cors handler which preserves information 16 | // about configured 'optionPassthrough' option. 17 | type corsWrapper struct { 18 | *cors.Cors 19 | optionsSuccessStatus int 20 | optionsPassthrough bool 21 | } 22 | 23 | // build transforms wrapped cors.Cors handler into Gin middleware. 24 | func (c corsWrapper) build() gin.HandlerFunc { 25 | return func(ctx *gin.Context) { 26 | c.HandlerFunc(ctx.Writer, ctx.Request) 27 | if !c.optionsPassthrough && 28 | ctx.Request.Method == http.MethodOptions && 29 | ctx.GetHeader("Access-Control-Request-Method") != "" { 30 | // Abort processing next Gin middlewares. 31 | ctx.AbortWithStatus(c.optionsSuccessStatus) 32 | } 33 | } 34 | } 35 | 36 | // AllowAll creates a new CORS Gin middleware with permissive configuration 37 | // allowing all origins with all standard methods with any header and 38 | // credentials. 39 | func AllowAll() gin.HandlerFunc { 40 | return corsWrapper{Cors: cors.AllowAll()}.build() 41 | } 42 | 43 | // Default creates a new CORS Gin middleware with default options. 44 | func Default() gin.HandlerFunc { 45 | return corsWrapper{Cors: cors.Default()}.build() 46 | } 47 | 48 | // New creates a new CORS Gin middleware with the provided options. 49 | func New(options Options) gin.HandlerFunc { 50 | status := options.OptionsSuccessStatus 51 | if status == 0 { 52 | status = http.StatusNoContent 53 | } 54 | wrapper := corsWrapper{ 55 | Cors: cors.New(options), 56 | optionsSuccessStatus: status, 57 | optionsPassthrough: options.OptionsPassthrough, 58 | } 59 | return wrapper.build() 60 | } 61 | -------------------------------------------------------------------------------- /wrapper/gin/gin_test.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func init() { 12 | gin.SetMode(gin.ReleaseMode) 13 | } 14 | 15 | func TestAllowAllNotNil(t *testing.T) { 16 | handler := AllowAll() 17 | if handler == nil { 18 | t.Error("Should not return nil Gin handler") 19 | } 20 | } 21 | 22 | func TestDefaultNotNil(t *testing.T) { 23 | handler := Default() 24 | if handler == nil { 25 | t.Error("Should not return nil Gin handler") 26 | } 27 | } 28 | 29 | func TestNewNotNil(t *testing.T) { 30 | handler := New(Options{}) 31 | if handler == nil { 32 | t.Error("Should not return nil Gin handler") 33 | } 34 | } 35 | 36 | func TestCorsWrapper_buildAbortsWhenPreflight(t *testing.T) { 37 | res := httptest.NewRecorder() 38 | ctx, _ := gin.CreateTestContext(res) 39 | ctx.Request, _ = http.NewRequest("OPTIONS", "http://example.com/foo", nil) 40 | ctx.Request.Header.Add("Origin", "http://example.org") 41 | ctx.Request.Header.Add("Access-Control-Request-Method", "POST") 42 | ctx.Status(http.StatusAccepted) 43 | res.Code = http.StatusAccepted 44 | 45 | handler := New(Options{ /* Intentionally left blank. */ }) 46 | 47 | handler(ctx) 48 | 49 | if !ctx.IsAborted() { 50 | t.Error("Should abort on preflight requests") 51 | } 52 | if res.Code != http.StatusNoContent { 53 | t.Error("Should abort with 204 Non Content status") 54 | } 55 | } 56 | 57 | func TestCorsWrapper_buildNotAbortsWhenPassthrough(t *testing.T) { 58 | res := httptest.NewRecorder() 59 | ctx, _ := gin.CreateTestContext(res) 60 | ctx.Request, _ = http.NewRequest("OPTIONS", "http://example.com/foo", nil) 61 | ctx.Request.Header.Add("Origin", "http://example.org") 62 | ctx.Request.Header.Add("Access-Control-Request-Method", "POST") 63 | 64 | handler := New(Options{ 65 | OptionsPassthrough: true, 66 | }) 67 | 68 | handler(ctx) 69 | 70 | if ctx.IsAborted() { 71 | t.Error("Should not abort when OPTIONS passthrough enabled") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /wrapper/gin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rs/cors/wrapper/gin 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/rs/cors v1.11.0 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.9.1 // indirect 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 13 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 14 | github.com/gin-contrib/sse v0.1.0 // indirect 15 | github.com/go-playground/locales v0.14.1 // indirect 16 | github.com/go-playground/universal-translator v0.18.1 // indirect 17 | github.com/go-playground/validator/v10 v10.14.0 // indirect 18 | github.com/goccy/go-json v0.10.2 // indirect 19 | github.com/json-iterator/go v1.1.12 // indirect 20 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 21 | github.com/leodido/go-urn v1.2.4 // indirect 22 | github.com/mattn/go-isatty v0.0.19 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 26 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 27 | github.com/ugorji/go/codec v1.2.11 // indirect 28 | golang.org/x/arch v0.3.0 // indirect 29 | golang.org/x/crypto v0.9.0 // indirect 30 | golang.org/x/net v0.10.0 // indirect 31 | golang.org/x/sys v0.8.0 // indirect 32 | golang.org/x/text v0.9.0 // indirect 33 | google.golang.org/protobuf v1.30.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | 37 | replace github.com/rs/cors => ../../ 38 | -------------------------------------------------------------------------------- /wrapper/gin/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 11 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 12 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 13 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 14 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 15 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 16 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 17 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 18 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 19 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 20 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 21 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 22 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 23 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 24 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 25 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 26 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 27 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 28 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 30 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 31 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 32 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 33 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 34 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 35 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 36 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 37 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 38 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 39 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 42 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 43 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 44 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 45 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 46 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 47 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 48 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 49 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 50 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 51 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 52 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 53 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 54 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 55 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 56 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 57 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 58 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 59 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 60 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 61 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 62 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 63 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 64 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 65 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 66 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 67 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 68 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 69 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 70 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 71 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 72 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 73 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 74 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 75 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 76 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 77 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 78 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 79 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 80 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 81 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 82 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 83 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 84 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 85 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 87 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 88 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 90 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 93 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 95 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 96 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 97 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 98 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 99 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 100 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 101 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 102 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 103 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 104 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 105 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 106 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 107 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 108 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 109 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 110 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 111 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 113 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 114 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 115 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 116 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 117 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 120 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 121 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 122 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 123 | --------------------------------------------------------------------------------