From afff3f88851b5ea2fe1676871b0aebdc09367ba7 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 31 Aug 2015 17:15:49 -0700 Subject: [PATCH 1/2] Revert "Enable controller to have CORS" This reverts commit f39ac24e9929d17b74200aecbbb0ff79e091e781. --- pkg/controller/router.go | 3 +- vendor.json | 7 - vendor/github.com/rs/cors/LICENSE | 19 -- vendor/github.com/rs/cors/README.md | 99 ------ vendor/github.com/rs/cors/bench_test.go | 88 ------ vendor/github.com/rs/cors/cors.go | 385 ------------------------ vendor/github.com/rs/cors/cors_test.go | 371 ----------------------- vendor/github.com/rs/cors/utils.go | 70 ----- vendor/github.com/rs/cors/utils_test.go | 70 ----- 9 files changed, 1 insertion(+), 1111 deletions(-) delete mode 100644 vendor/github.com/rs/cors/LICENSE delete mode 100644 vendor/github.com/rs/cors/README.md delete mode 100644 vendor/github.com/rs/cors/bench_test.go delete mode 100644 vendor/github.com/rs/cors/cors.go delete mode 100644 vendor/github.com/rs/cors/cors_test.go delete mode 100644 vendor/github.com/rs/cors/utils.go delete mode 100644 vendor/github.com/rs/cors/utils_test.go diff --git a/pkg/controller/router.go b/pkg/controller/router.go index 5a15f9074..db38bf241 100644 --- a/pkg/controller/router.go +++ b/pkg/controller/router.go @@ -21,7 +21,6 @@ import ( router "github.com/gorilla/mux" "github.com/minio/minio/pkg/rpc" - "github.com/rs/cors" ) // getRPCHandler rpc handler @@ -40,5 +39,5 @@ func getRPCHandler() http.Handler { // registerRPC - register rpc handlers func registerRPC(mux *router.Router, s *rpc.Server) http.Handler { mux.Handle("/rpc", s) - return cors.Default().Handler(mux) + return mux } diff --git a/vendor.json b/vendor.json index 01e81e1bb..d38687627 100755 --- a/vendor.json +++ b/vendor.json @@ -72,13 +72,6 @@ "revision": "ee386baecc113eef2b8945df429120a5aec319ef", "revisionTime": "2015-08-19T11:23:55-07:00" }, - { - "canonical": "github.com/rs/cors", - "comment": "", - "local": "vendor/github.com/rs/cors", - "revision": "eb527c8097e0f19a3ff7b253a3fe70545070f420", - "revisionTime": "2015-08-29T22:34:20-07:00" - }, { "canonical": "gopkg.in/check.v1", "comment": "", diff --git a/vendor/github.com/rs/cors/LICENSE b/vendor/github.com/rs/cors/LICENSE deleted file mode 100644 index d8e2df5a4..000000000 --- a/vendor/github.com/rs/cors/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2014 Olivier Poitrey - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/rs/cors/README.md b/vendor/github.com/rs/cors/README.md deleted file mode 100644 index 521d903d1..000000000 --- a/vendor/github.com/rs/cors/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# 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) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) - -CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. - -## Getting Started - -After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. - -```go -package main - -import ( - "net/http" - - "github.com/rs/cors" -) - -func main() { - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{\"hello\": \"world\"}")) - }) - - // cors.Default() setup the middleware with default options being - // all origins accepted with simple methods (GET, POST). See - // documentation below for more options. - handler := cors.Default().Handler(mux) - http.ListenAndServe(":8080", handler) -} -``` - -Install `cors`: - - go get github.com/rs/cors - -Then run your server: - - go run server.go - -The server now runs on `localhost:8080`: - - $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/ - HTTP/1.1 200 OK - Access-Control-Allow-Origin: foo.com - Content-Type: application/json - Date: Sat, 25 Oct 2014 03:43:57 GMT - Content-Length: 18 - - {"hello": "world"} - -### More Examples - -* `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) -* [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go) -* [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) -* [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) -* [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) - -## Parameters - -Parameters are passed to the middleware thru the `cors.New` method as follow: - -```go -c := cors.New(cors.Options{ - AllowedOrigins: []string{"http://foo.com"}, - AllowCredentials: true, -}) - -// Insert the middleware -handler = c.Handler(handler) -``` - -* **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. An origin may contain a wildcard (`*`) to replace 0 or more characters (i.e.: `http://*.domain.com`). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin. The default value is `*`. -* **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It take the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored -* **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`). -* **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. -* **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification -* **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. -* **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age. -* **OptionsPassthrough** `bool`: Instructs preflight to let other potential next handlers to process the `OPTIONS` method. Turn this on if your application handles `OPTIONS`. -* **Debug** `bool`: Debugging flag adds additional output to debug server side CORS issues. - -See [API documentation](http://godoc.org/github.com/rs/cors) for more info. - -## Benchmarks - - BenchmarkWithout 20000000 64.6 ns/op 8 B/op 1 allocs/op - BenchmarkDefault 3000000 469 ns/op 114 B/op 2 allocs/op - BenchmarkAllowedOrigin 3000000 608 ns/op 114 B/op 2 allocs/op - BenchmarkPreflight 20000000 73.2 ns/op 0 B/op 0 allocs/op - BenchmarkPreflightHeader 20000000 73.6 ns/op 0 B/op 0 allocs/op - BenchmarkParseHeaderList 2000000 847 ns/op 184 B/op 6 allocs/op - BenchmarkParse…Single 5000000 290 ns/op 32 B/op 3 allocs/op - BenchmarkParse…Normalized 2000000 776 ns/op 160 B/op 6 allocs/op - -## Licenses - -All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). diff --git a/vendor/github.com/rs/cors/bench_test.go b/vendor/github.com/rs/cors/bench_test.go deleted file mode 100644 index b6e3721de..000000000 --- a/vendor/github.com/rs/cors/bench_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package cors - -import ( - "net/http" - "testing" -) - -type FakeResponse struct { - header http.Header -} - -func (r FakeResponse) Header() http.Header { - return r.header -} - -func (r FakeResponse) WriteHeader(n int) { -} - -func (r FakeResponse) Write(b []byte) (n int, err error) { - return len(b), nil -} - -func BenchmarkWithout(b *testing.B) { - res := FakeResponse{http.Header{}} - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - testHandler.ServeHTTP(res, req) - } -} - -func BenchmarkDefault(b *testing.B) { - res := FakeResponse{http.Header{}} - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "somedomain.com") - handler := Default().Handler(testHandler) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - handler.ServeHTTP(res, req) - } -} - -func BenchmarkAllowedOrigin(b *testing.B) { - res := FakeResponse{http.Header{}} - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "somedomain.com") - c := New(Options{ - AllowedOrigins: []string{"somedomain.com"}, - }) - handler := c.Handler(testHandler) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - handler.ServeHTTP(res, req) - } -} - -func BenchmarkPreflight(b *testing.B) { - res := FakeResponse{http.Header{}} - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Access-Control-Request-Method", "GET") - handler := Default().Handler(testHandler) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - handler.ServeHTTP(res, req) - } -} - -func BenchmarkPreflightHeader(b *testing.B) { - res := FakeResponse{http.Header{}} - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "Accept") - handler := Default().Handler(testHandler) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - handler.ServeHTTP(res, req) - } -} diff --git a/vendor/github.com/rs/cors/cors.go b/vendor/github.com/rs/cors/cors.go deleted file mode 100644 index dffa73fa0..000000000 --- a/vendor/github.com/rs/cors/cors.go +++ /dev/null @@ -1,385 +0,0 @@ -/* -Package cors is net/http handler to handle CORS related requests -as defined by http://www.w3.org/TR/cors/ - -You can configure it by passing an option struct to cors.New: - - c := cors.New(cors.Options{ - AllowedOrigins: []string{"foo.com"}, - AllowedMethods: []string{"GET", "POST", "DELETE"}, - AllowCredentials: true, - }) - -Then insert the handler in the chain: - - handler = c.Handler(handler) - -See Options documentation for more options. - -The resulting handler is a standard net/http handler. -*/ -package cors - -import ( - "log" - "net/http" - "os" - "strconv" - "strings" -) - -// Options is a configuration container to setup the CORS middleware. -type Options struct { - // AllowedOrigins is a list of origins a cross-domain request can be executed from. - // If the special "*" value is present in the list, all origins will be allowed. - // An origin may contain a wildcard (*) to replace 0 or more characters - // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. - // Only one wildcard can be used per origin. - // Default value is ["*"] - AllowedOrigins []string - // AllowOriginFunc is a custom function to validate the origin. It take the origin - // as argument and returns true if allowed or false otherwise. If this option is - // set, the content of AllowedOrigins is ignored. - AllowOriginFunc func(origin string) bool - // AllowedMethods is a list of methods the client is allowed to use with - // cross-domain requests. Default value is simple methods (GET and POST) - AllowedMethods []string - // AllowedHeaders is list of non simple headers the client is allowed to use with - // cross-domain requests. - // If the special "*" value is present in the list, all headers will be allowed. - // Default value is [] but "Origin" is always appended to the list. - AllowedHeaders []string - // ExposedHeaders indicates which headers are safe to expose to the API of a CORS - // API specification - ExposedHeaders []string - // AllowCredentials indicates whether the request can include user credentials like - // cookies, HTTP authentication or client side SSL certificates. - AllowCredentials bool - // MaxAge indicates how long (in seconds) the results of a preflight request - // can be cached - MaxAge int - // OptionsPassthrough instructs preflight to let other potential next handlers to - // process the OPTIONS method. Turn this on if your application handles OPTIONS. - OptionsPassthrough bool - // Debugging flag adds additional output to debug server side CORS issues - Debug bool -} - -// Cors http handler -type Cors struct { - // Debug logger - log *log.Logger - // Set to true when allowed origins contains a "*" - allowedOriginsAll bool - // Normalized list of plain allowed origins - allowedOrigins []string - // List of allowed origins containing wildcards - allowedWOrigins []wildcard - // Optional origin validator function - allowOriginFunc func(origin string) bool - // Set to true when allowed headers contains a "*" - allowedHeadersAll bool - // Normalized list of allowed headers - allowedHeaders []string - // Normalized list of allowed methods - allowedMethods []string - // Normalized list of exposed headers - exposedHeaders []string - allowCredentials bool - maxAge int - optionPassthrough bool -} - -// New creates a new Cors handler with the provided options. -func New(options Options) *Cors { - c := &Cors{ - exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), - allowOriginFunc: options.AllowOriginFunc, - allowCredentials: options.AllowCredentials, - maxAge: options.MaxAge, - optionPassthrough: options.OptionsPassthrough, - } - if options.Debug { - c.log = log.New(os.Stdout, "[cors] ", log.LstdFlags) - } - - // Normalize options - // Note: for origins and methods matching, the spec requires a case-sensitive matching. - // As it may error prone, we chose to ignore the spec here. - - // Allowed Origins - if len(options.AllowedOrigins) == 0 { - // Default is all origins - c.allowedOriginsAll = true - } else { - c.allowedOrigins = []string{} - c.allowedWOrigins = []wildcard{} - for _, origin := range options.AllowedOrigins { - // Normalize - origin = strings.ToLower(origin) - if origin == "*" { - // If "*" is present in the list, turn the whole list into a match all - c.allowedOriginsAll = true - c.allowedOrigins = nil - c.allowedWOrigins = nil - break - } else if i := strings.IndexByte(origin, '*'); i >= 0 { - // Split the origin in two: start and end string without the * - w := wildcard{origin[0:i], origin[i+1 : len(origin)]} - c.allowedWOrigins = append(c.allowedWOrigins, w) - } else { - c.allowedOrigins = append(c.allowedOrigins, origin) - } - } - } - - // Allowed Headers - if len(options.AllowedHeaders) == 0 { - // Use sensible defaults - c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"} - } else { - // Origin is always appended as some browsers will always request for this header at preflight - c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) - for _, h := range options.AllowedHeaders { - if h == "*" { - c.allowedHeadersAll = true - c.allowedHeaders = nil - break - } - } - } - - // Allowed Methods - if len(options.AllowedMethods) == 0 { - // Default is spec's "simple" methods - c.allowedMethods = []string{"GET", "POST"} - } else { - c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) - } - - return c -} - -// Default creates a new Cors handler with default options -func Default() *Cors { - return New(Options{}) -} - -// Handler apply the CORS specification on the request, and add relevant CORS headers -// as necessary. -func (c *Cors) Handler(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - c.logf("Handler: Preflight request") - c.handlePreflight(w, r) - // Preflight requests are standalone and should stop the chain as some other - // middleware may not handle OPTIONS requests correctly. One typical example - // is authentication middleware ; OPTIONS requests won't carry authentication - // headers (see #1) - if c.optionPassthrough { - h.ServeHTTP(w, r) - } - } else { - c.logf("Handler: Actual request") - c.handleActualRequest(w, r) - h.ServeHTTP(w, r) - } - }) -} - -// HandlerFunc provides Martini compatible handler -func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - c.logf("HandlerFunc: Preflight request") - c.handlePreflight(w, r) - } else { - c.logf("HandlerFunc: Actual request") - c.handleActualRequest(w, r) - } -} - -// Negroni compatible interface -func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - if r.Method == "OPTIONS" { - c.logf("ServeHTTP: Preflight request") - c.handlePreflight(w, r) - // Preflight requests are standalone and should stop the chain as some other - // middleware may not handle OPTIONS requests correctly. One typical example - // is authentication middleware ; OPTIONS requests won't carry authentication - // headers (see #1) - if c.optionPassthrough { - next(w, r) - } - } else { - c.logf("ServeHTTP: Actual request") - c.handleActualRequest(w, r) - next(w, r) - } -} - -// handlePreflight handles pre-flight CORS requests -func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { - headers := w.Header() - origin := r.Header.Get("Origin") - - if r.Method != "OPTIONS" { - c.logf(" Preflight aborted: %s!=OPTIONS", r.Method) - return - } - // Always set Vary headers - // see https://github.com/rs/cors/issues/10, - // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 - headers.Add("Vary", "Origin") - headers.Add("Vary", "Access-Control-Request-Method") - headers.Add("Vary", "Access-Control-Request-Headers") - - if origin == "" { - c.logf(" Preflight aborted: empty origin") - return - } - if !c.isOriginAllowed(origin) { - c.logf(" Preflight aborted: origin '%s' not allowed", origin) - return - } - - reqMethod := r.Header.Get("Access-Control-Request-Method") - if !c.isMethodAllowed(reqMethod) { - c.logf(" Preflight aborted: method '%s' not allowed", reqMethod) - return - } - reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers")) - if !c.areHeadersAllowed(reqHeaders) { - c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) - return - } - headers.Set("Access-Control-Allow-Origin", origin) - // Spec says: Since the list of methods can be unbounded, simply returning the method indicated - // by Access-Control-Request-Method (if supported) can be enough - headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) - if len(reqHeaders) > 0 { - - // Spec says: Since the list of headers can be unbounded, simply returning supported headers - // from Access-Control-Request-Headers can be enough - headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) - } - if c.allowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") - } - if c.maxAge > 0 { - headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge)) - } - c.logf(" Preflight response headers: %v", headers) -} - -// handleActualRequest handles simple cross-origin requests, actual request or redirects -func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { - headers := w.Header() - origin := r.Header.Get("Origin") - - if r.Method == "OPTIONS" { - c.logf(" Actual request no headers added: method == %s", r.Method) - return - } - // Always set Vary, see https://github.com/rs/cors/issues/10 - headers.Add("Vary", "Origin") - if origin == "" { - c.logf(" Actual request no headers added: missing origin") - return - } - if !c.isOriginAllowed(origin) { - c.logf(" Actual request no headers added: origin '%s' not allowed", origin) - return - } - - // Note that spec does define a way to specifically disallow a simple method like GET or - // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the - // spec doesn't instruct to check the allowed methods for simple cross-origin requests. - // We think it's a nice feature to be able to have control on those methods though. - if !c.isMethodAllowed(r.Method) { - if c.log != nil { - c.logf(" Actual request no headers added: method '%s' not allowed", - r.Method) - } - - return - } - headers.Set("Access-Control-Allow-Origin", origin) - if len(c.exposedHeaders) > 0 { - headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) - } - if c.allowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") - } - c.logf(" Actual response added headers: %v", headers) -} - -// convenience method. checks if debugging is turned on before printing -func (c *Cors) logf(format string, a ...interface{}) { - if c.log != nil { - c.log.Printf(format, a...) - } -} - -// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests -// on the endpoint -func (c *Cors) isOriginAllowed(origin string) bool { - if c.allowOriginFunc != nil { - return c.allowOriginFunc(origin) - } - if c.allowedOriginsAll { - return true - } - origin = strings.ToLower(origin) - for _, o := range c.allowedOrigins { - if o == origin { - return true - } - } - for _, w := range c.allowedWOrigins { - if w.match(origin) { - return true - } - } - return false -} - -// isMethodAllowed checks if a given method can be used as part of a cross-domain request -// on the endpoing -func (c *Cors) isMethodAllowed(method string) bool { - if len(c.allowedMethods) == 0 { - // If no method allowed, always return false, even for preflight request - return false - } - method = strings.ToUpper(method) - if method == "OPTIONS" { - // Always allow preflight requests - return true - } - for _, m := range c.allowedMethods { - if m == method { - return true - } - } - return false -} - -// areHeadersAllowed checks if a given list of headers are allowed to used within -// a cross-domain request. -func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool { - if c.allowedHeadersAll || len(requestedHeaders) == 0 { - return true - } - for _, header := range requestedHeaders { - header = http.CanonicalHeaderKey(header) - found := false - for _, h := range c.allowedHeaders { - if h == header { - found = true - } - } - if !found { - return false - } - } - return true -} diff --git a/vendor/github.com/rs/cors/cors_test.go b/vendor/github.com/rs/cors/cors_test.go deleted file mode 100644 index 28672a001..000000000 --- a/vendor/github.com/rs/cors/cors_test.go +++ /dev/null @@ -1,371 +0,0 @@ -package cors - -import ( - "net/http" - "net/http/httptest" - "regexp" - "strings" - "testing" -) - -var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("bar")) -}) - -func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) { - for name, value := range reqHeaders { - if actual := strings.Join(resHeaders[name], ", "); actual != value { - t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, actual) - } - } -} - -func TestNoConfig(t *testing.T) { - s := New(Options{ - // Intentionally left blank. - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestMatchAllOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"*"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestWildcardOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://*.bar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foo.bar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "http://foo.bar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://barbaz.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedWildcardOrigin(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://*.bar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foo.baz.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedOriginFunc(t *testing.T) { - r, _ := regexp.Compile("^http://foo") - s := New(Options{ - AllowOriginFunc: func(o string) bool { - return r.MatchString(o) - }, - }) - - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - - res := httptest.NewRecorder() - req.Header.Set("Origin", "http://foobar.com") - s.Handler(testHandler).ServeHTTP(res, req) - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "http://foobar.com", - }) - - res = httptest.NewRecorder() - req.Header.Set("Origin", "http://barfoo.com") - s.Handler(testHandler).ServeHTTP(res, req) - assertHeaders(t, res.Header(), map[string]string{ - "Access-Control-Allow-Origin": "", - }) -} - -func TestAllowedMethod(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedMethods: []string{"PUT", "DELETE"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "PUT") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "PUT", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedMethod(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedMethods: []string{"PUT", "DELETE"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "PATCH") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedHeaders: []string{"X-Header-1", "x-header-2"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "X-Header-2, X-Header-1", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestAllowedWildcardHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedHeaders: []string{"*"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "X-Header-2, X-Header-1", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestDisallowedHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowedHeaders: []string{"X-Header-1", "x-header-2"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "X-Header-3, X-Header-1") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestOriginHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - req.Header.Add("Access-Control-Request-Headers", "origin") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Origin", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} - -func TestExposedHeader(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - ExposedHeaders: []string{"X-Header-1", "x-header-2"}, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "X-Header-1, X-Header-2", - }) -} - -func TestAllowedCredentials(t *testing.T) { - s := New(Options{ - AllowedOrigins: []string{"http://foobar.com"}, - AllowCredentials: true, - }) - - res := httptest.NewRecorder() - req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) - req.Header.Add("Origin", "http://foobar.com") - req.Header.Add("Access-Control-Request-Method", "GET") - - s.Handler(testHandler).ServeHTTP(res, req) - - assertHeaders(t, res.Header(), map[string]string{ - "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", - "Access-Control-Allow-Origin": "http://foobar.com", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "", - "Access-Control-Allow-Credentials": "true", - "Access-Control-Max-Age": "", - "Access-Control-Expose-Headers": "", - }) -} diff --git a/vendor/github.com/rs/cors/utils.go b/vendor/github.com/rs/cors/utils.go deleted file mode 100644 index cd24831fc..000000000 --- a/vendor/github.com/rs/cors/utils.go +++ /dev/null @@ -1,70 +0,0 @@ -package cors - -import "strings" - -const toLower = 'a' - 'A' - -type converter func(string) string - -type wildcard struct { - prefix string - suffix string -} - -func (w wildcard) match(s string) bool { - return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) -} - -// convert converts a list of string using the passed converter function -func convert(s []string, c converter) []string { - out := []string{} - for _, i := range s { - out = append(out, c(i)) - } - return out -} - -// parseHeaderList tokenize + normalize a string containing a list of headers -func parseHeaderList(headerList string) []string { - l := len(headerList) - h := make([]byte, 0, l) - upper := true - // Estimate the number headers in order to allocate the right splice size - t := 0 - for i := 0; i < l; i++ { - if headerList[i] == ',' { - t++ - } - } - headers := make([]string, 0, t) - for i := 0; i < l; i++ { - b := headerList[i] - if b >= 'a' && b <= 'z' { - if upper { - h = append(h, b-toLower) - } else { - h = append(h, b) - } - } else if b >= 'A' && b <= 'Z' { - if !upper { - h = append(h, b+toLower) - } else { - h = append(h, b) - } - } else if b == '-' || (b >= '0' && b <= '9') { - h = append(h, b) - } - - if b == ' ' || b == ',' || i == l-1 { - if len(h) > 0 { - // Flush the found header - headers = append(headers, string(h)) - h = h[:0] - upper = true - } - } else { - upper = b == '-' - } - } - return headers -} diff --git a/vendor/github.com/rs/cors/utils_test.go b/vendor/github.com/rs/cors/utils_test.go deleted file mode 100644 index 83053b3fc..000000000 --- a/vendor/github.com/rs/cors/utils_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package cors - -import ( - "strings" - "testing" -) - -func TestWildcard(t *testing.T) { - w := wildcard{"foo", "bar"} - if !w.match("foobar") { - t.Error("foo*bar should match foobar") - } - if !w.match("foobazbar") { - t.Error("foo*bar should match foobazbar") - } - if w.match("foobaz") { - t.Error("foo*bar should not match foobaz") - } - - w = wildcard{"foo", "oof"} - if w.match("foof") { - t.Error("foo*oof should not match foof") - } -} - -func TestConvert(t *testing.T) { - s := convert([]string{"A", "b", "C"}, strings.ToLower) - e := []string{"a", "b", "c"} - if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] { - t.Errorf("%v != %v", s, e) - } -} - -func TestParseHeaderList(t *testing.T) { - h := parseHeaderList("header, second-header, THIRD-HEADER, Numb3r3d-H34d3r") - e := []string{"Header", "Second-Header", "Third-Header", "Numb3r3d-H34d3r"} - if h[0] != e[0] || h[1] != e[1] || h[2] != e[2] { - t.Errorf("%v != %v", h, e) - } -} - -func TestParseHeaderListEmpty(t *testing.T) { - if len(parseHeaderList("")) != 0 { - t.Error("should be empty sclice") - } - if len(parseHeaderList(" , ")) != 0 { - t.Error("should be empty sclice") - } -} - -func BenchmarkParseHeaderList(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - parseHeaderList("header, second-header, THIRD-HEADER") - } -} - -func BenchmarkParseHeaderListSingle(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - parseHeaderList("header") - } -} - -func BenchmarkParseHeaderListNormalized(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - parseHeaderList("Header1, Header2, Third-Header") - } -} From d999ce282ad05b2187ffff6815af76cbcf75fff7 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 31 Aug 2015 17:16:27 -0700 Subject: [PATCH 2/2] Add cors deps --- vendor.json | 7 + vendor/github.com/rs/cors/LICENSE | 19 ++ vendor/github.com/rs/cors/README.md | 99 ++++++ vendor/github.com/rs/cors/bench_test.go | 88 ++++++ vendor/github.com/rs/cors/cors.go | 385 ++++++++++++++++++++++++ vendor/github.com/rs/cors/cors_test.go | 371 +++++++++++++++++++++++ vendor/github.com/rs/cors/utils.go | 70 +++++ vendor/github.com/rs/cors/utils_test.go | 70 +++++ 8 files changed, 1109 insertions(+) create mode 100644 vendor/github.com/rs/cors/LICENSE create mode 100644 vendor/github.com/rs/cors/README.md create mode 100644 vendor/github.com/rs/cors/bench_test.go create mode 100644 vendor/github.com/rs/cors/cors.go create mode 100644 vendor/github.com/rs/cors/cors_test.go create mode 100644 vendor/github.com/rs/cors/utils.go create mode 100644 vendor/github.com/rs/cors/utils_test.go diff --git a/vendor.json b/vendor.json index d38687627..01e81e1bb 100755 --- a/vendor.json +++ b/vendor.json @@ -72,6 +72,13 @@ "revision": "ee386baecc113eef2b8945df429120a5aec319ef", "revisionTime": "2015-08-19T11:23:55-07:00" }, + { + "canonical": "github.com/rs/cors", + "comment": "", + "local": "vendor/github.com/rs/cors", + "revision": "eb527c8097e0f19a3ff7b253a3fe70545070f420", + "revisionTime": "2015-08-29T22:34:20-07:00" + }, { "canonical": "gopkg.in/check.v1", "comment": "", diff --git a/vendor/github.com/rs/cors/LICENSE b/vendor/github.com/rs/cors/LICENSE new file mode 100644 index 000000000..d8e2df5a4 --- /dev/null +++ b/vendor/github.com/rs/cors/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Olivier Poitrey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/rs/cors/README.md b/vendor/github.com/rs/cors/README.md new file mode 100644 index 000000000..521d903d1 --- /dev/null +++ b/vendor/github.com/rs/cors/README.md @@ -0,0 +1,99 @@ +# 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) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) + +CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. + +## Getting Started + +After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. + +```go +package main + +import ( + "net/http" + + "github.com/rs/cors" +) + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("{\"hello\": \"world\"}")) + }) + + // cors.Default() setup the middleware with default options being + // all origins accepted with simple methods (GET, POST). See + // documentation below for more options. + handler := cors.Default().Handler(mux) + http.ListenAndServe(":8080", handler) +} +``` + +Install `cors`: + + go get github.com/rs/cors + +Then run your server: + + go run server.go + +The server now runs on `localhost:8080`: + + $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/ + HTTP/1.1 200 OK + Access-Control-Allow-Origin: foo.com + Content-Type: application/json + Date: Sat, 25 Oct 2014 03:43:57 GMT + Content-Length: 18 + + {"hello": "world"} + +### More Examples + +* `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) +* [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go) +* [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) +* [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) +* [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) + +## Parameters + +Parameters are passed to the middleware thru the `cors.New` method as follow: + +```go +c := cors.New(cors.Options{ + AllowedOrigins: []string{"http://foo.com"}, + AllowCredentials: true, +}) + +// Insert the middleware +handler = c.Handler(handler) +``` + +* **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. An origin may contain a wildcard (`*`) to replace 0 or more characters (i.e.: `http://*.domain.com`). Usage of wildcards implies a small performance penality. Only one wildcard can be used per origin. The default value is `*`. +* **AllowOriginFunc** `func (origin string) bool`: A custom function to validate the origin. It take the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` is ignored +* **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`). +* **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. +* **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification +* **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. +* **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age. +* **OptionsPassthrough** `bool`: Instructs preflight to let other potential next handlers to process the `OPTIONS` method. Turn this on if your application handles `OPTIONS`. +* **Debug** `bool`: Debugging flag adds additional output to debug server side CORS issues. + +See [API documentation](http://godoc.org/github.com/rs/cors) for more info. + +## Benchmarks + + BenchmarkWithout 20000000 64.6 ns/op 8 B/op 1 allocs/op + BenchmarkDefault 3000000 469 ns/op 114 B/op 2 allocs/op + BenchmarkAllowedOrigin 3000000 608 ns/op 114 B/op 2 allocs/op + BenchmarkPreflight 20000000 73.2 ns/op 0 B/op 0 allocs/op + BenchmarkPreflightHeader 20000000 73.6 ns/op 0 B/op 0 allocs/op + BenchmarkParseHeaderList 2000000 847 ns/op 184 B/op 6 allocs/op + BenchmarkParse…Single 5000000 290 ns/op 32 B/op 3 allocs/op + BenchmarkParse…Normalized 2000000 776 ns/op 160 B/op 6 allocs/op + +## Licenses + +All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). diff --git a/vendor/github.com/rs/cors/bench_test.go b/vendor/github.com/rs/cors/bench_test.go new file mode 100644 index 000000000..b6e3721de --- /dev/null +++ b/vendor/github.com/rs/cors/bench_test.go @@ -0,0 +1,88 @@ +package cors + +import ( + "net/http" + "testing" +) + +type FakeResponse struct { + header http.Header +} + +func (r FakeResponse) Header() http.Header { + return r.header +} + +func (r FakeResponse) WriteHeader(n int) { +} + +func (r FakeResponse) Write(b []byte) (n int, err error) { + return len(b), nil +} + +func BenchmarkWithout(b *testing.B) { + res := FakeResponse{http.Header{}} + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + testHandler.ServeHTTP(res, req) + } +} + +func BenchmarkDefault(b *testing.B) { + res := FakeResponse{http.Header{}} + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "somedomain.com") + handler := Default().Handler(testHandler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + handler.ServeHTTP(res, req) + } +} + +func BenchmarkAllowedOrigin(b *testing.B) { + res := FakeResponse{http.Header{}} + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "somedomain.com") + c := New(Options{ + AllowedOrigins: []string{"somedomain.com"}, + }) + handler := c.Handler(testHandler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + handler.ServeHTTP(res, req) + } +} + +func BenchmarkPreflight(b *testing.B) { + res := FakeResponse{http.Header{}} + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Access-Control-Request-Method", "GET") + handler := Default().Handler(testHandler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + handler.ServeHTTP(res, req) + } +} + +func BenchmarkPreflightHeader(b *testing.B) { + res := FakeResponse{http.Header{}} + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Access-Control-Request-Method", "GET") + req.Header.Add("Access-Control-Request-Headers", "Accept") + handler := Default().Handler(testHandler) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + handler.ServeHTTP(res, req) + } +} diff --git a/vendor/github.com/rs/cors/cors.go b/vendor/github.com/rs/cors/cors.go new file mode 100644 index 000000000..dffa73fa0 --- /dev/null +++ b/vendor/github.com/rs/cors/cors.go @@ -0,0 +1,385 @@ +/* +Package cors is net/http handler to handle CORS related requests +as defined by http://www.w3.org/TR/cors/ + +You can configure it by passing an option struct to cors.New: + + c := cors.New(cors.Options{ + AllowedOrigins: []string{"foo.com"}, + AllowedMethods: []string{"GET", "POST", "DELETE"}, + AllowCredentials: true, + }) + +Then insert the handler in the chain: + + handler = c.Handler(handler) + +See Options documentation for more options. + +The resulting handler is a standard net/http handler. +*/ +package cors + +import ( + "log" + "net/http" + "os" + "strconv" + "strings" +) + +// Options is a configuration container to setup the CORS middleware. +type Options struct { + // AllowedOrigins is a list of origins a cross-domain request can be executed from. + // If the special "*" value is present in the list, all origins will be allowed. + // An origin may contain a wildcard (*) to replace 0 or more characters + // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. + // Only one wildcard can be used per origin. + // Default value is ["*"] + AllowedOrigins []string + // AllowOriginFunc is a custom function to validate the origin. It take the origin + // as argument and returns true if allowed or false otherwise. If this option is + // set, the content of AllowedOrigins is ignored. + AllowOriginFunc func(origin string) bool + // AllowedMethods is a list of methods the client is allowed to use with + // cross-domain requests. Default value is simple methods (GET and POST) + AllowedMethods []string + // AllowedHeaders is list of non simple headers the client is allowed to use with + // cross-domain requests. + // If the special "*" value is present in the list, all headers will be allowed. + // Default value is [] but "Origin" is always appended to the list. + AllowedHeaders []string + // ExposedHeaders indicates which headers are safe to expose to the API of a CORS + // API specification + ExposedHeaders []string + // AllowCredentials indicates whether the request can include user credentials like + // cookies, HTTP authentication or client side SSL certificates. + AllowCredentials bool + // MaxAge indicates how long (in seconds) the results of a preflight request + // can be cached + MaxAge int + // OptionsPassthrough instructs preflight to let other potential next handlers to + // process the OPTIONS method. Turn this on if your application handles OPTIONS. + OptionsPassthrough bool + // Debugging flag adds additional output to debug server side CORS issues + Debug bool +} + +// Cors http handler +type Cors struct { + // Debug logger + log *log.Logger + // Set to true when allowed origins contains a "*" + allowedOriginsAll bool + // Normalized list of plain allowed origins + allowedOrigins []string + // List of allowed origins containing wildcards + allowedWOrigins []wildcard + // Optional origin validator function + allowOriginFunc func(origin string) bool + // Set to true when allowed headers contains a "*" + allowedHeadersAll bool + // Normalized list of allowed headers + allowedHeaders []string + // Normalized list of allowed methods + allowedMethods []string + // Normalized list of exposed headers + exposedHeaders []string + allowCredentials bool + maxAge int + optionPassthrough bool +} + +// New creates a new Cors handler with the provided options. +func New(options Options) *Cors { + c := &Cors{ + exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), + allowOriginFunc: options.AllowOriginFunc, + allowCredentials: options.AllowCredentials, + maxAge: options.MaxAge, + optionPassthrough: options.OptionsPassthrough, + } + if options.Debug { + c.log = log.New(os.Stdout, "[cors] ", log.LstdFlags) + } + + // Normalize options + // Note: for origins and methods matching, the spec requires a case-sensitive matching. + // As it may error prone, we chose to ignore the spec here. + + // Allowed Origins + if len(options.AllowedOrigins) == 0 { + // Default is all origins + c.allowedOriginsAll = true + } else { + c.allowedOrigins = []string{} + c.allowedWOrigins = []wildcard{} + for _, origin := range options.AllowedOrigins { + // Normalize + origin = strings.ToLower(origin) + if origin == "*" { + // If "*" is present in the list, turn the whole list into a match all + c.allowedOriginsAll = true + c.allowedOrigins = nil + c.allowedWOrigins = nil + break + } else if i := strings.IndexByte(origin, '*'); i >= 0 { + // Split the origin in two: start and end string without the * + w := wildcard{origin[0:i], origin[i+1 : len(origin)]} + c.allowedWOrigins = append(c.allowedWOrigins, w) + } else { + c.allowedOrigins = append(c.allowedOrigins, origin) + } + } + } + + // Allowed Headers + if len(options.AllowedHeaders) == 0 { + // Use sensible defaults + c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"} + } else { + // Origin is always appended as some browsers will always request for this header at preflight + c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) + for _, h := range options.AllowedHeaders { + if h == "*" { + c.allowedHeadersAll = true + c.allowedHeaders = nil + break + } + } + } + + // Allowed Methods + if len(options.AllowedMethods) == 0 { + // Default is spec's "simple" methods + c.allowedMethods = []string{"GET", "POST"} + } else { + c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) + } + + return c +} + +// Default creates a new Cors handler with default options +func Default() *Cors { + return New(Options{}) +} + +// Handler apply the CORS specification on the request, and add relevant CORS headers +// as necessary. +func (c *Cors) Handler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + c.logf("Handler: Preflight request") + c.handlePreflight(w, r) + // Preflight requests are standalone and should stop the chain as some other + // middleware may not handle OPTIONS requests correctly. One typical example + // is authentication middleware ; OPTIONS requests won't carry authentication + // headers (see #1) + if c.optionPassthrough { + h.ServeHTTP(w, r) + } + } else { + c.logf("Handler: Actual request") + c.handleActualRequest(w, r) + h.ServeHTTP(w, r) + } + }) +} + +// HandlerFunc provides Martini compatible handler +func (c *Cors) HandlerFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + c.logf("HandlerFunc: Preflight request") + c.handlePreflight(w, r) + } else { + c.logf("HandlerFunc: Actual request") + c.handleActualRequest(w, r) + } +} + +// Negroni compatible interface +func (c *Cors) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + if r.Method == "OPTIONS" { + c.logf("ServeHTTP: Preflight request") + c.handlePreflight(w, r) + // Preflight requests are standalone and should stop the chain as some other + // middleware may not handle OPTIONS requests correctly. One typical example + // is authentication middleware ; OPTIONS requests won't carry authentication + // headers (see #1) + if c.optionPassthrough { + next(w, r) + } + } else { + c.logf("ServeHTTP: Actual request") + c.handleActualRequest(w, r) + next(w, r) + } +} + +// handlePreflight handles pre-flight CORS requests +func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { + headers := w.Header() + origin := r.Header.Get("Origin") + + if r.Method != "OPTIONS" { + c.logf(" Preflight aborted: %s!=OPTIONS", r.Method) + return + } + // Always set Vary headers + // see https://github.com/rs/cors/issues/10, + // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 + headers.Add("Vary", "Origin") + headers.Add("Vary", "Access-Control-Request-Method") + headers.Add("Vary", "Access-Control-Request-Headers") + + if origin == "" { + c.logf(" Preflight aborted: empty origin") + return + } + if !c.isOriginAllowed(origin) { + c.logf(" Preflight aborted: origin '%s' not allowed", origin) + return + } + + reqMethod := r.Header.Get("Access-Control-Request-Method") + if !c.isMethodAllowed(reqMethod) { + c.logf(" Preflight aborted: method '%s' not allowed", reqMethod) + return + } + reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers")) + if !c.areHeadersAllowed(reqHeaders) { + c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) + return + } + headers.Set("Access-Control-Allow-Origin", origin) + // Spec says: Since the list of methods can be unbounded, simply returning the method indicated + // by Access-Control-Request-Method (if supported) can be enough + headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) + if len(reqHeaders) > 0 { + + // Spec says: Since the list of headers can be unbounded, simply returning supported headers + // from Access-Control-Request-Headers can be enough + headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + } + if c.allowCredentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + if c.maxAge > 0 { + headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge)) + } + c.logf(" Preflight response headers: %v", headers) +} + +// handleActualRequest handles simple cross-origin requests, actual request or redirects +func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { + headers := w.Header() + origin := r.Header.Get("Origin") + + if r.Method == "OPTIONS" { + c.logf(" Actual request no headers added: method == %s", r.Method) + return + } + // Always set Vary, see https://github.com/rs/cors/issues/10 + headers.Add("Vary", "Origin") + if origin == "" { + c.logf(" Actual request no headers added: missing origin") + return + } + if !c.isOriginAllowed(origin) { + c.logf(" Actual request no headers added: origin '%s' not allowed", origin) + return + } + + // Note that spec does define a way to specifically disallow a simple method like GET or + // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the + // spec doesn't instruct to check the allowed methods for simple cross-origin requests. + // We think it's a nice feature to be able to have control on those methods though. + if !c.isMethodAllowed(r.Method) { + if c.log != nil { + c.logf(" Actual request no headers added: method '%s' not allowed", + r.Method) + } + + return + } + headers.Set("Access-Control-Allow-Origin", origin) + if len(c.exposedHeaders) > 0 { + headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) + } + if c.allowCredentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + c.logf(" Actual response added headers: %v", headers) +} + +// convenience method. checks if debugging is turned on before printing +func (c *Cors) logf(format string, a ...interface{}) { + if c.log != nil { + c.log.Printf(format, a...) + } +} + +// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests +// on the endpoint +func (c *Cors) isOriginAllowed(origin string) bool { + if c.allowOriginFunc != nil { + return c.allowOriginFunc(origin) + } + if c.allowedOriginsAll { + return true + } + origin = strings.ToLower(origin) + for _, o := range c.allowedOrigins { + if o == origin { + return true + } + } + for _, w := range c.allowedWOrigins { + if w.match(origin) { + return true + } + } + return false +} + +// isMethodAllowed checks if a given method can be used as part of a cross-domain request +// on the endpoing +func (c *Cors) isMethodAllowed(method string) bool { + if len(c.allowedMethods) == 0 { + // If no method allowed, always return false, even for preflight request + return false + } + method = strings.ToUpper(method) + if method == "OPTIONS" { + // Always allow preflight requests + return true + } + for _, m := range c.allowedMethods { + if m == method { + return true + } + } + return false +} + +// areHeadersAllowed checks if a given list of headers are allowed to used within +// a cross-domain request. +func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool { + if c.allowedHeadersAll || len(requestedHeaders) == 0 { + return true + } + for _, header := range requestedHeaders { + header = http.CanonicalHeaderKey(header) + found := false + for _, h := range c.allowedHeaders { + if h == header { + found = true + } + } + if !found { + return false + } + } + return true +} diff --git a/vendor/github.com/rs/cors/cors_test.go b/vendor/github.com/rs/cors/cors_test.go new file mode 100644 index 000000000..28672a001 --- /dev/null +++ b/vendor/github.com/rs/cors/cors_test.go @@ -0,0 +1,371 @@ +package cors + +import ( + "net/http" + "net/http/httptest" + "regexp" + "strings" + "testing" +) + +var testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("bar")) +}) + +func assertHeaders(t *testing.T, resHeaders http.Header, reqHeaders map[string]string) { + for name, value := range reqHeaders { + if actual := strings.Join(resHeaders[name], ", "); actual != value { + t.Errorf("Invalid header `%s', wanted `%s', got `%s'", name, value, actual) + } + } +} + +func TestNoConfig(t *testing.T) { + s := New(Options{ + // Intentionally left blank. + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestMatchAllOrigin(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"*"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestAllowedOrigin(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestWildcardOrigin(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://*.bar.com"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foo.bar.com") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "http://foo.bar.com", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestDisallowedOrigin(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://barbaz.com") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestDisallowedWildcardOrigin(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://*.bar.com"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foo.baz.com") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestAllowedOriginFunc(t *testing.T) { + r, _ := regexp.Compile("^http://foo") + s := New(Options{ + AllowOriginFunc: func(o string) bool { + return r.MatchString(o) + }, + }) + + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + + res := httptest.NewRecorder() + req.Header.Set("Origin", "http://foobar.com") + s.Handler(testHandler).ServeHTTP(res, req) + assertHeaders(t, res.Header(), map[string]string{ + "Access-Control-Allow-Origin": "http://foobar.com", + }) + + res = httptest.NewRecorder() + req.Header.Set("Origin", "http://barfoo.com") + s.Handler(testHandler).ServeHTTP(res, req) + assertHeaders(t, res.Header(), map[string]string{ + "Access-Control-Allow-Origin": "", + }) +} + +func TestAllowedMethod(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + AllowedMethods: []string{"PUT", "DELETE"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "PUT") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "PUT", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestDisallowedMethod(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + AllowedMethods: []string{"PUT", "DELETE"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "PATCH") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestAllowedHeader(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + AllowedHeaders: []string{"X-Header-1", "x-header-2"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "GET") + req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "X-Header-2, X-Header-1", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestAllowedWildcardHeader(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + AllowedHeaders: []string{"*"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "GET") + req.Header.Add("Access-Control-Request-Headers", "X-Header-2, X-HEADER-1") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "X-Header-2, X-Header-1", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestDisallowedHeader(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + AllowedHeaders: []string{"X-Header-1", "x-header-2"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "GET") + req.Header.Add("Access-Control-Request-Headers", "X-Header-3, X-Header-1") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestOriginHeader(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "GET") + req.Header.Add("Access-Control-Request-Headers", "origin") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "Origin", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} + +func TestExposedHeader(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + ExposedHeaders: []string{"X-Header-1", "x-header-2"}, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "X-Header-1, X-Header-2", + }) +} + +func TestAllowedCredentials(t *testing.T) { + s := New(Options{ + AllowedOrigins: []string{"http://foobar.com"}, + AllowCredentials: true, + }) + + res := httptest.NewRecorder() + req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) + req.Header.Add("Origin", "http://foobar.com") + req.Header.Add("Access-Control-Request-Method", "GET") + + s.Handler(testHandler).ServeHTTP(res, req) + + assertHeaders(t, res.Header(), map[string]string{ + "Vary": "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", + "Access-Control-Allow-Origin": "http://foobar.com", + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "", + "Access-Control-Expose-Headers": "", + }) +} diff --git a/vendor/github.com/rs/cors/utils.go b/vendor/github.com/rs/cors/utils.go new file mode 100644 index 000000000..cd24831fc --- /dev/null +++ b/vendor/github.com/rs/cors/utils.go @@ -0,0 +1,70 @@ +package cors + +import "strings" + +const toLower = 'a' - 'A' + +type converter func(string) string + +type wildcard struct { + prefix string + suffix string +} + +func (w wildcard) match(s string) bool { + return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) +} + +// convert converts a list of string using the passed converter function +func convert(s []string, c converter) []string { + out := []string{} + for _, i := range s { + out = append(out, c(i)) + } + return out +} + +// parseHeaderList tokenize + normalize a string containing a list of headers +func parseHeaderList(headerList string) []string { + l := len(headerList) + h := make([]byte, 0, l) + upper := true + // Estimate the number headers in order to allocate the right splice size + t := 0 + for i := 0; i < l; i++ { + if headerList[i] == ',' { + t++ + } + } + headers := make([]string, 0, t) + for i := 0; i < l; i++ { + b := headerList[i] + if b >= 'a' && b <= 'z' { + if upper { + h = append(h, b-toLower) + } else { + h = append(h, b) + } + } else if b >= 'A' && b <= 'Z' { + if !upper { + h = append(h, b+toLower) + } else { + h = append(h, b) + } + } else if b == '-' || (b >= '0' && b <= '9') { + h = append(h, b) + } + + if b == ' ' || b == ',' || i == l-1 { + if len(h) > 0 { + // Flush the found header + headers = append(headers, string(h)) + h = h[:0] + upper = true + } + } else { + upper = b == '-' + } + } + return headers +} diff --git a/vendor/github.com/rs/cors/utils_test.go b/vendor/github.com/rs/cors/utils_test.go new file mode 100644 index 000000000..83053b3fc --- /dev/null +++ b/vendor/github.com/rs/cors/utils_test.go @@ -0,0 +1,70 @@ +package cors + +import ( + "strings" + "testing" +) + +func TestWildcard(t *testing.T) { + w := wildcard{"foo", "bar"} + if !w.match("foobar") { + t.Error("foo*bar should match foobar") + } + if !w.match("foobazbar") { + t.Error("foo*bar should match foobazbar") + } + if w.match("foobaz") { + t.Error("foo*bar should not match foobaz") + } + + w = wildcard{"foo", "oof"} + if w.match("foof") { + t.Error("foo*oof should not match foof") + } +} + +func TestConvert(t *testing.T) { + s := convert([]string{"A", "b", "C"}, strings.ToLower) + e := []string{"a", "b", "c"} + if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] { + t.Errorf("%v != %v", s, e) + } +} + +func TestParseHeaderList(t *testing.T) { + h := parseHeaderList("header, second-header, THIRD-HEADER, Numb3r3d-H34d3r") + e := []string{"Header", "Second-Header", "Third-Header", "Numb3r3d-H34d3r"} + if h[0] != e[0] || h[1] != e[1] || h[2] != e[2] { + t.Errorf("%v != %v", h, e) + } +} + +func TestParseHeaderListEmpty(t *testing.T) { + if len(parseHeaderList("")) != 0 { + t.Error("should be empty sclice") + } + if len(parseHeaderList(" , ")) != 0 { + t.Error("should be empty sclice") + } +} + +func BenchmarkParseHeaderList(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + parseHeaderList("header, second-header, THIRD-HEADER") + } +} + +func BenchmarkParseHeaderListSingle(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + parseHeaderList("header") + } +} + +func BenchmarkParseHeaderListNormalized(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + parseHeaderList("Header1, Header2, Third-Header") + } +}