pax_global_header00006660000000000000000000000064126462434710014523gustar00rootroot0000000000000052 comment=8f2758070a82adb7a3ad6b223a0b91878f32d400 handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/000077500000000000000000000000001264624347100201475ustar00rootroot00000000000000handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/.travis.yml000066400000000000000000000003301264624347100222540ustar00rootroot00000000000000language: go sudo: false go: - 1.4 - 1.5 - tip install: - go get golang.org/x/tools/cmd/vet script: - go get -t -v ./... - diff -u <(echo -n) <(gofmt -d -s .) - go tool vet . - go test -v -race ./... handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/LICENSE000066400000000000000000000024371264624347100211620ustar00rootroot00000000000000Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/README.md000066400000000000000000000051061264624347100214300ustar00rootroot00000000000000gorilla/handlers ================ [![GoDoc](https://godoc.org/github.com/gorilla/handlers?status.svg)](https://godoc.org/github.com/gorilla/handlers) [![Build Status](https://travis-ci.org/gorilla/handlers.svg?branch=master)](https://travis-ci.org/gorilla/handlers) Package handlers is a collection of handlers (aka "HTTP middleware") for use with Go's `net/http` package (or any framework supporting `http.Handler`), including: * [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log Format](http://httpd.apache.org/docs/2.2/logs.html#common). * [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by both Apache and nginx. * [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses. * [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted content types. * [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a `map[string]http.Handler` * [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded` headers when running a Go server behind a HTTP reverse proxy. * [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple domains (i.e. multiple CNAME aliases). * [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics. Other handlers are documented [on the Gorilla website](http://www.gorillatoolkit.org/pkg/handlers). ## Example A simple example using `handlers.LoggingHandler` and `handlers.CompressHandler`: ```go import ( "net/http" "github.com/gorilla/handlers" ) func main() { r := http.NewServeMux() // Only log requests to our admin dashboard to stdout r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard))) r.HandleFunc("/", ShowIndex) // Wrap our server with our gzip handler to gzip compress all responses. http.ListenAndServe(":8000", handlers.CompressHandler(r)) } ``` ## License BSD licensed. See the included LICENSE file for details. handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/canonical.go000066400000000000000000000035531264624347100224330ustar00rootroot00000000000000package handlers import ( "net/http" "net/url" "strings" ) type canonical struct { h http.Handler domain string code int } // CanonicalHost is HTTP middleware that re-directs requests to the canonical // domain. It accepts a domain and a status code (e.g. 301 or 302) and // re-directs clients to this domain. The existing request path is maintained. // // Note: If the provided domain is considered invalid by url.Parse or otherwise // returns an empty scheme or host, clients are not re-directed. // // Example: // // r := mux.NewRouter() // canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302) // r.HandleFunc("/route", YourHandler) // // log.Fatal(http.ListenAndServe(":7000", canonical(r))) // func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler { fn := func(h http.Handler) http.Handler { return canonical{h, domain, code} } return fn } func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) { dest, err := url.Parse(c.domain) if err != nil { // Call the next handler if the provided domain fails to parse. c.h.ServeHTTP(w, r) return } if dest.Scheme == "" || dest.Host == "" { // Call the next handler if the scheme or host are empty. // Note that url.Parse won't fail on in this case. c.h.ServeHTTP(w, r) return } if !strings.EqualFold(cleanHost(r.Host), dest.Host) { // Re-build the destination URL dest := dest.Scheme + "://" + dest.Host + r.URL.Path http.Redirect(w, r, dest, c.code) return } c.h.ServeHTTP(w, r) } // cleanHost cleans invalid Host headers by stripping anything after '/' or ' '. // This is backported from Go 1.5 (in response to issue #11206) and attempts to // mitigate malformed Host headers that do not match the format in RFC7230. func cleanHost(in string) string { if i := strings.IndexAny(in, " /"); i != -1 { return in[:i] } return in } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/canonical_test.go000066400000000000000000000054051264624347100234700ustar00rootroot00000000000000package handlers import ( "bufio" "bytes" "log" "net/http" "net/http/httptest" "strings" "testing" ) func TestCleanHost(t *testing.T) { tests := []struct { in, want string }{ {"www.google.com", "www.google.com"}, {"www.google.com foo", "www.google.com"}, {"www.google.com/foo", "www.google.com"}, {" first character is a space", ""}, } for _, tt := range tests { got := cleanHost(tt.in) if tt.want != got { t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) } } } func TestCanonicalHost(t *testing.T) { gorilla := "http://www.gorillatoolkit.org" rr := httptest.NewRecorder() r := newRequest("GET", "http://www.example.com/") testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) // Test a re-direct: should return a 302 Found. CanonicalHost(gorilla, http.StatusFound)(testHandler).ServeHTTP(rr, r) if rr.Code != http.StatusFound { t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusFound) } if rr.Header().Get("Location") != gorilla+r.URL.Path { t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), gorilla+r.URL.Path) } } func TestBadDomain(t *testing.T) { rr := httptest.NewRecorder() r := newRequest("GET", "http://www.example.com/") testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) // Test a bad domain - should return 200 OK. CanonicalHost("%", http.StatusFound)(testHandler).ServeHTTP(rr, r) if rr.Code != http.StatusOK { t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK) } } func TestEmptyHost(t *testing.T) { rr := httptest.NewRecorder() r := newRequest("GET", "http://www.example.com/") testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) // Test a domain that returns an empty url.Host from url.Parse. CanonicalHost("hello.com", http.StatusFound)(testHandler).ServeHTTP(rr, r) if rr.Code != http.StatusOK { t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK) } } func TestHeaderWrites(t *testing.T) { gorilla := "http://www.gorillatoolkit.org" testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }) // Catch the log output to ensure we don't write multiple headers. var b bytes.Buffer buf := bufio.NewWriter(&b) tl := log.New(buf, "test: ", log.Lshortfile) srv := httptest.NewServer( CanonicalHost(gorilla, http.StatusFound)(testHandler)) defer srv.Close() srv.Config.ErrorLog = tl _, err := http.Get(srv.URL) if err != nil { t.Fatal(err) } err = buf.Flush() if err != nil { t.Fatal(err) } // We rely on the error not changing: net/http does not export it. if strings.Contains(b.String(), "multiple response.WriteHeader calls") { t.Fatalf("re-direct did not return early: multiple header writes") } } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/compress.go000066400000000000000000000051251264624347100223340ustar00rootroot00000000000000// Copyright 2013 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package handlers import ( "compress/flate" "compress/gzip" "io" "net/http" "strings" ) type compressResponseWriter struct { io.Writer http.ResponseWriter http.Hijacker } func (w *compressResponseWriter) WriteHeader(c int) { w.ResponseWriter.Header().Del("Content-Length") w.ResponseWriter.WriteHeader(c) } func (w *compressResponseWriter) Header() http.Header { return w.ResponseWriter.Header() } func (w *compressResponseWriter) Write(b []byte) (int, error) { h := w.ResponseWriter.Header() if h.Get("Content-Type") == "" { h.Set("Content-Type", http.DetectContentType(b)) } h.Del("Content-Length") return w.Writer.Write(b) } // CompressHandler gzip compresses HTTP responses for clients that support it // via the 'Accept-Encoding' header. func CompressHandler(h http.Handler) http.Handler { return CompressHandlerLevel(h, gzip.DefaultCompression) } // CompressHandlerLevel gzip compresses HTTP responses with specified compression level // for clients that support it via the 'Accept-Encoding' header. // // The compression level should be gzip.DefaultCompression, gzip.NoCompression, // or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive. // gzip.DefaultCompression is used in case of invalid compression level. func CompressHandlerLevel(h http.Handler, level int) http.Handler { if level < gzip.DefaultCompression || level > gzip.BestCompression { level = gzip.DefaultCompression } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { L: for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { switch strings.TrimSpace(enc) { case "gzip": w.Header().Set("Content-Encoding", "gzip") w.Header().Add("Vary", "Accept-Encoding") gw, _ := gzip.NewWriterLevel(w, level) defer gw.Close() h, hok := w.(http.Hijacker) if !hok { /* w is not Hijacker... oh well... */ h = nil } w = &compressResponseWriter{ Writer: gw, ResponseWriter: w, Hijacker: h, } break L case "deflate": w.Header().Set("Content-Encoding", "deflate") w.Header().Add("Vary", "Accept-Encoding") fw, _ := flate.NewWriter(w, level) defer fw.Close() h, hok := w.(http.Hijacker) if !hok { /* w is not Hijacker... oh well... */ h = nil } w = &compressResponseWriter{ Writer: fw, ResponseWriter: w, Hijacker: h, } break L } } h.ServeHTTP(w, r) }) } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/compress_test.go000066400000000000000000000056701264624347100234000ustar00rootroot00000000000000// Copyright 2013 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package handlers import ( "io" "net/http" "net/http/httptest" "strconv" "testing" ) var contentType = "text/plain; charset=utf-8" func compressedRequest(w *httptest.ResponseRecorder, compression string) { CompressHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", strconv.Itoa(9*1024)) w.Header().Set("Content-Type", contentType) for i := 0; i < 1024; i++ { io.WriteString(w, "Gorilla!\n") } })).ServeHTTP(w, &http.Request{ Method: "GET", Header: http.Header{ "Accept-Encoding": []string{compression}, }, }) } func TestCompressHandlerNoCompression(t *testing.T) { w := httptest.NewRecorder() compressedRequest(w, "") if enc := w.HeaderMap.Get("Content-Encoding"); enc != "" { t.Errorf("wrong content encoding, got %q want %q", enc, "") } if ct := w.HeaderMap.Get("Content-Type"); ct != contentType { t.Errorf("wrong content type, got %q want %q", ct, contentType) } if w.Body.Len() != 1024*9 { t.Errorf("wrong len, got %d want %d", w.Body.Len(), 1024*9) } if l := w.HeaderMap.Get("Content-Length"); l != "9216" { t.Errorf("wrong content-length. got %q expected %d", l, 1024*9) } } func TestCompressHandlerGzip(t *testing.T) { w := httptest.NewRecorder() compressedRequest(w, "gzip") if w.HeaderMap.Get("Content-Encoding") != "gzip" { t.Errorf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip") } if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { t.Errorf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") } if w.Body.Len() != 72 { t.Errorf("wrong len, got %d want %d", w.Body.Len(), 72) } if l := w.HeaderMap.Get("Content-Length"); l != "" { t.Errorf("wrong content-length. got %q expected %q", l, "") } } func TestCompressHandlerDeflate(t *testing.T) { w := httptest.NewRecorder() compressedRequest(w, "deflate") if w.HeaderMap.Get("Content-Encoding") != "deflate" { t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "deflate") } if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") } if w.Body.Len() != 54 { t.Fatalf("wrong len, got %d want %d", w.Body.Len(), 54) } } func TestCompressHandlerGzipDeflate(t *testing.T) { w := httptest.NewRecorder() compressedRequest(w, "gzip, deflate ") if w.HeaderMap.Get("Content-Encoding") != "gzip" { t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip") } if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") } } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/cors.go000066400000000000000000000201251264624347100214440ustar00rootroot00000000000000package handlers import ( "net/http" "strconv" "strings" ) // CORSOption represents a functional option for configuring the CORS middleware. type CORSOption func(*cors) error type cors struct { h http.Handler allowedHeaders []string allowedMethods []string allowedOrigins []string allowedOriginValidator originValidator exposedHeaders []string maxAge int ignoreOptions bool allowCredentials bool } // originValidator takes an origin string and returns whether or not that origin is allowed. type originValidator func(string) bool var ( defaultCorsMethods = []string{"GET", "HEAD", "POST"} defaultCorsHeaders = []string{"Accept", "Accept-Language", "Content-Language"} ) const ( corsOptionMethod string = "OPTIONS" corsAllowOriginHeader string = "Access-Control-Allow-Origin" corsExposeHeadersHeader string = "Access-Control-Expose-Headers" corsMaxAgeHeader string = "Access-Control-Max-Age" corsAllowMethodsHeader string = "Access-Control-Allow-Methods" corsAllowHeadersHeader string = "Access-Control-Allow-Headers" corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials" corsRequestMethodHeader string = "Access-Control-Request-Method" corsRequestHeadersHeader string = "Access-Control-Request-Headers" corsOriginHeader string = "Origin" corsVaryHeader string = "Vary" corsOriginMatchAll string = "*" ) func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get(corsOriginHeader) if !ch.isOriginAllowed(origin) { ch.h.ServeHTTP(w, r) return } if r.Method == corsOptionMethod { if ch.ignoreOptions { ch.h.ServeHTTP(w, r) return } if _, ok := r.Header[corsRequestMethodHeader]; !ok { w.WriteHeader(http.StatusBadRequest) return } method := r.Header.Get(corsRequestMethodHeader) if !ch.isMatch(method, ch.allowedMethods) { w.WriteHeader(http.StatusMethodNotAllowed) return } requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",") allowedHeaders := []string{} for _, v := range requestHeaders { canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v)) if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) { continue } if !ch.isMatch(canonicalHeader, ch.allowedHeaders) { w.WriteHeader(http.StatusForbidden) return } allowedHeaders = append(allowedHeaders, canonicalHeader) } if len(allowedHeaders) > 0 { w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ",")) } if ch.maxAge > 0 { w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge)) } if !ch.isMatch(method, defaultCorsMethods) { w.Header().Set(corsAllowMethodsHeader, method) } } else { if len(ch.exposedHeaders) > 0 { w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ",")) } } if ch.allowCredentials { w.Header().Set(corsAllowCredentialsHeader, "true") } if len(ch.allowedOrigins) > 1 { w.Header().Set(corsVaryHeader, corsOriginHeader) } w.Header().Set(corsAllowOriginHeader, origin) ch.h.ServeHTTP(w, r) } // CORS provides Cross-Origin Resource Sharing middleware. // Example: // // import ( // "net/http" // // "github.com/gorilla/handlers" // "github.com/gorilla/mux" // ) // // func main() { // r := mux.NewRouter() // r.HandleFunc("/users", UserEndpoint) // r.HandleFunc("/projects", ProjectEndpoint) // // // Apply the CORS middleware to our top-level router, with the defaults. // http.ListenAndServe(":8000", handlers.CORS()(r)) // } // func CORS(opts ...CORSOption) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { ch := parseCORSOptions(opts...) ch.h = h return ch } } func parseCORSOptions(opts ...CORSOption) *cors { ch := &cors{ allowedMethods: defaultCorsMethods, allowedHeaders: defaultCorsHeaders, allowedOrigins: []string{corsOriginMatchAll}, } for _, option := range opts { option(ch) } return ch } // // Functional options for configuring CORS. // // AllowedHeaders adds the provided headers to the list of allowed headers in a // CORS request. // This is an append operation so the headers Accept, Accept-Language, // and Content-Language are always allowed. // Content-Type must be explicitly declared if accepting Content-Types other than // application/x-www-form-urlencoded, multipart/form-data, or text/plain. func AllowedHeaders(headers []string) CORSOption { return func(ch *cors) error { for _, v := range headers { normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v)) if normalizedHeader == "" { continue } if !ch.isMatch(normalizedHeader, ch.allowedHeaders) { ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader) } } return nil } } // AllowedMethods can be used to explicitly allow methods in the // Access-Control-Allow-Methods header. // This is a replacement operation so you must also // pass GET, HEAD, and POST if you wish to support those methods. func AllowedMethods(methods []string) CORSOption { return func(ch *cors) error { ch.allowedMethods = []string{} for _, v := range methods { normalizedMethod := strings.ToUpper(strings.TrimSpace(v)) if normalizedMethod == "" { continue } if !ch.isMatch(normalizedMethod, ch.allowedMethods) { ch.allowedMethods = append(ch.allowedMethods, normalizedMethod) } } return nil } } // AllowedOrigins sets the allowed origins for CORS requests, as used in the // 'Allow-Access-Control-Origin' HTTP header. // Note: Passing in a []string{"*"} will allow any domain. func AllowedOrigins(origins []string) CORSOption { return func(ch *cors) error { for _, v := range origins { if v == corsOriginMatchAll { ch.allowedOrigins = []string{corsOriginMatchAll} return nil } } ch.allowedOrigins = origins return nil } } // AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the // 'Allow-Access-Control-Origin' HTTP header. func AllowedOriginValidator(fn originValidator) CORSOption { return func(ch *cors) error { ch.allowedOriginValidator = fn return nil } } // ExposeHeaders can be used to specify headers that are available // and will not be stripped out by the user-agent. func ExposedHeaders(headers []string) CORSOption { return func(ch *cors) error { ch.exposedHeaders = []string{} for _, v := range headers { normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v)) if normalizedHeader == "" { continue } if !ch.isMatch(normalizedHeader, ch.exposedHeaders) { ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader) } } return nil } } // MaxAge determines the maximum age (in seconds) between preflight requests. A // maximum of 10 minutes is allowed. An age above this value will default to 10 // minutes. func MaxAge(age int) CORSOption { return func(ch *cors) error { // Maximum of 10 minutes. if age > 600 { age = 600 } ch.maxAge = age return nil } } // IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead // passing them through to the next handler. This is useful when your application // or framework has a pre-existing mechanism for responding to OPTIONS requests. func IgnoreOptions() CORSOption { return func(ch *cors) error { ch.ignoreOptions = true return nil } } // AllowCredentials can be used to specify that the user agent may pass // authentication details along with the request. func AllowCredentials() CORSOption { return func(ch *cors) error { ch.allowCredentials = true return nil } } func (ch *cors) isOriginAllowed(origin string) bool { if origin == "" { return false } if ch.allowedOriginValidator != nil { return ch.allowedOriginValidator(origin) } for _, allowedOrigin := range ch.allowedOrigins { if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll { return true } } return false } func (ch *cors) isMatch(needle string, haystack []string) bool { for _, v := range haystack { if v == needle { return true } } return false } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/cors_test.go000066400000000000000000000230131264624347100225020ustar00rootroot00000000000000package handlers import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestDefaultCORSHandlerReturnsOk(t *testing.T) { r := newRequest("GET", "http://www.example.com/") rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS()(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusFound) } } func TestDefaultCORSHandlerReturnsOkWithOrigin(t *testing.T) { r := newRequest("GET", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS()(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusFound) } } func TestCORSHandlerIgnoreOptionsFallsThrough(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }) CORS(IgnoreOptions())(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusTeapot { t.Fatalf("bad status: got %v want %v", status, http.StatusTeapot) } } func TestCORSHandlerSetsExposedHeaders(t *testing.T) { // Test default configuration. r := newRequest("GET", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(ExposedHeaders([]string{"X-CORS-TEST"}))(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsExposeHeadersHeader) if header != "X-Cors-Test" { t.Fatal("bad header: expected X-Cors-Test header, got empty header for method.") } } func TestCORSHandlerUnsetRequestMethodForPreflightBadRequest(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(AllowedMethods([]string{"DELETE"}))(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusBadRequest { t.Fatalf("bad status: got %v want %v", status, http.StatusBadRequest) } } func TestCORSHandlerInvalidRequestMethodForPreflightMethodNotAllowed(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, "DELETE") rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS()(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusMethodNotAllowed { t.Fatalf("bad status: got %v want %v", status, http.StatusMethodNotAllowed) } } func TestCORSHandlerAllowedMethodForPreflight(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, "DELETE") rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(AllowedMethods([]string{"DELETE"}))(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsAllowMethodsHeader) if header != "DELETE" { t.Fatalf("bad header: expected DELETE method header, got empty header.") } } func TestCORSHandlerAllowMethodsNotSetForSimpleRequestPreflight(t *testing.T) { for _, method := range defaultCorsMethods { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, method) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS()(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsAllowMethodsHeader) if header != "" { t.Fatalf("bad header: expected empty method header, got %s.", header) } } } func TestCORSHandlerAllowedHeaderNotSetForSimpleRequestPreflight(t *testing.T) { for _, simpleHeader := range defaultCorsHeaders { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, "GET") r.Header.Set(corsRequestHeadersHeader, simpleHeader) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS()(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsAllowHeadersHeader) if header != "" { t.Fatalf("bad header: expected empty header, got %s.", header) } } } func TestCORSHandlerAllowedHeaderForPreflight(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, "POST") r.Header.Set(corsRequestHeadersHeader, "Content-Type") rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(AllowedHeaders([]string{"Content-Type"}))(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsAllowHeadersHeader) if header != "Content-Type" { t.Fatalf("bad header: expected Content-Type header, got empty header.") } } func TestCORSHandlerInvalidHeaderForPreflightForbidden(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, "POST") r.Header.Set(corsRequestHeadersHeader, "Content-Type") rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS()(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusForbidden { t.Fatalf("bad status: got %v want %v", status, http.StatusForbidden) } } func TestCORSHandlerMaxAgeForPreflight(t *testing.T) { r := newRequest("OPTIONS", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) r.Header.Set(corsRequestMethodHeader, "POST") rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(MaxAge(3500))(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsMaxAgeHeader) if header != "600" { t.Fatalf("bad header: expected %s to be %s, got %s.", corsMaxAgeHeader, "600", header) } } func TestCORSHandlerAllowedCredentials(t *testing.T) { r := newRequest("GET", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(AllowCredentials())(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsAllowCredentialsHeader) if header != "true" { t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowCredentialsHeader, "true", header) } } func TestCORSHandlerMultipleAllowOriginsSetsVaryHeader(t *testing.T) { r := newRequest("GET", "http://www.example.com/") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) CORS(AllowedOrigins([]string{r.URL.String(), "http://google.com"}))(testHandler).ServeHTTP(rr, r) if status := rr.Code; status != http.StatusOK { t.Fatalf("bad status: got %v want %v", status, http.StatusOK) } header := rr.HeaderMap.Get(corsVaryHeader) if header != corsOriginHeader { t.Fatalf("bad header: expected %s to be %s, got %s.", corsVaryHeader, corsOriginHeader, header) } } func TestCORSWithMultipleHandlers(t *testing.T) { var lastHandledBy string corsMiddleware := CORS() testHandler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { lastHandledBy = "testHandler1" }) testHandler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { lastHandledBy = "testHandler2" }) r1 := newRequest("GET", "http://www.example.com/") rr1 := httptest.NewRecorder() handler1 := corsMiddleware(testHandler1) corsMiddleware(testHandler2) handler1.ServeHTTP(rr1, r1) if lastHandledBy != "testHandler1" { t.Fatalf("bad CORS() registration: Handler served should be Handler registered") } } func TestCORSHandlerWithCustomValidator(t *testing.T) { r := newRequest("GET", "http://a.example.com") r.Header.Set("Origin", r.URL.String()) rr := httptest.NewRecorder() testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) originValidator := func(origin string) bool { if strings.HasSuffix(origin, ".example.com") { return true } return false } CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r) header := rr.HeaderMap.Get(corsAllowOriginHeader) if header != r.URL.String() { t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowOriginHeader, r.URL.String(), header) } } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/doc.go000066400000000000000000000005441264624347100212460ustar00rootroot00000000000000/* Package handlers is a collection of handlers (aka "HTTP middleware") for use with Go's net/http package (or any framework supporting http.Handler). The package includes handlers for logging in standardised formats, compressing HTTP responses, validating content types and other useful tools for manipulating requests and responses. */ package handlers handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/handlers.go000066400000000000000000000262111264624347100223000ustar00rootroot00000000000000// Copyright 2013 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package handlers import ( "bufio" "fmt" "io" "net" "net/http" "net/url" "sort" "strconv" "strings" "time" "unicode/utf8" ) // MethodHandler is an http.Handler that dispatches to a handler whose key in the // MethodHandler's map matches the name of the HTTP request's method, eg: GET // // If the request's method is OPTIONS and OPTIONS is not a key in the map then // the handler responds with a status of 200 and sets the Allow header to a // comma-separated list of available methods. // // If the request's method doesn't match any of its keys the handler responds // with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a // comma-separated list of available methods. type MethodHandler map[string]http.Handler func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if handler, ok := h[req.Method]; ok { handler.ServeHTTP(w, req) } else { allow := []string{} for k := range h { allow = append(allow, k) } sort.Strings(allow) w.Header().Set("Allow", strings.Join(allow, ", ")) if req.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) } else { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } } // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its // friends type loggingHandler struct { writer io.Writer handler http.Handler } // combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo // and its friends type combinedLoggingHandler struct { writer io.Writer handler http.Handler } func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { t := time.Now() logger := makeLogger(w) url := *req.URL h.handler.ServeHTTP(logger, req) writeLog(h.writer, req, url, t, logger.Status(), logger.Size()) } func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { t := time.Now() logger := makeLogger(w) url := *req.URL h.handler.ServeHTTP(logger, req) writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size()) } func makeLogger(w http.ResponseWriter) loggingResponseWriter { var logger loggingResponseWriter = &responseLogger{w: w} if _, ok := w.(http.Hijacker); ok { logger = &hijackLogger{responseLogger{w: w}} } h, ok1 := logger.(http.Hijacker) c, ok2 := w.(http.CloseNotifier) if ok1 && ok2 { return hijackCloseNotifier{logger, h, c} } if ok2 { return &closeNotifyWriter{logger, c} } return logger } type loggingResponseWriter interface { http.ResponseWriter http.Flusher Status() int Size() int } // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP // status code and body size type responseLogger struct { w http.ResponseWriter status int size int } func (l *responseLogger) Header() http.Header { return l.w.Header() } func (l *responseLogger) Write(b []byte) (int, error) { if l.status == 0 { // The status will be StatusOK if WriteHeader has not been called yet l.status = http.StatusOK } size, err := l.w.Write(b) l.size += size return size, err } func (l *responseLogger) WriteHeader(s int) { l.w.WriteHeader(s) l.status = s } func (l *responseLogger) Status() int { return l.status } func (l *responseLogger) Size() int { return l.size } func (l *responseLogger) Flush() { f, ok := l.w.(http.Flusher) if ok { f.Flush() } } type hijackLogger struct { responseLogger } func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) { h := l.responseLogger.w.(http.Hijacker) conn, rw, err := h.Hijack() if err == nil && l.responseLogger.status == 0 { // The status will be StatusSwitchingProtocols if there was no error and // WriteHeader has not been called yet l.responseLogger.status = http.StatusSwitchingProtocols } return conn, rw, err } type closeNotifyWriter struct { loggingResponseWriter http.CloseNotifier } type hijackCloseNotifier struct { loggingResponseWriter http.Hijacker http.CloseNotifier } const lowerhex = "0123456789abcdef" func appendQuoted(buf []byte, s string) []byte { var runeTmp [utf8.UTFMax]byte for width := 0; len(s) > 0; s = s[width:] { r := rune(s[0]) width = 1 if r >= utf8.RuneSelf { r, width = utf8.DecodeRuneInString(s) } if width == 1 && r == utf8.RuneError { buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) buf = append(buf, lowerhex[s[0]&0xF]) continue } if r == rune('"') || r == '\\' { // always backslashed buf = append(buf, '\\') buf = append(buf, byte(r)) continue } if strconv.IsPrint(r) { n := utf8.EncodeRune(runeTmp[:], r) buf = append(buf, runeTmp[:n]...) continue } switch r { case '\a': buf = append(buf, `\a`...) case '\b': buf = append(buf, `\b`...) case '\f': buf = append(buf, `\f`...) case '\n': buf = append(buf, `\n`...) case '\r': buf = append(buf, `\r`...) case '\t': buf = append(buf, `\t`...) case '\v': buf = append(buf, `\v`...) default: switch { case r < ' ': buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) buf = append(buf, lowerhex[s[0]&0xF]) case r > utf8.MaxRune: r = 0xFFFD fallthrough case r < 0x10000: buf = append(buf, `\u`...) for s := 12; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } default: buf = append(buf, `\U`...) for s := 28; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } } } } return buf } // buildCommonLogLine builds a log entry for req in Apache Common Log Format. // ts is the timestamp with which the entry should be logged. // status and size are used to provide the response HTTP status and size. func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte { username := "-" if url.User != nil { if name := url.User.Username(); name != "" { username = name } } host, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { host = req.RemoteAddr } uri := req.RequestURI // Requests using the CONNECT method over HTTP/2.0 must use // the authority field (aka r.Host) to identify the target. // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT if req.ProtoMajor == 2 && req.Method == "CONNECT" { uri = req.Host } if uri == "" { uri = url.RequestURI() } buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2) buf = append(buf, host...) buf = append(buf, " - "...) buf = append(buf, username...) buf = append(buf, " ["...) buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...) buf = append(buf, `] "`...) buf = append(buf, req.Method...) buf = append(buf, " "...) buf = appendQuoted(buf, uri) buf = append(buf, " "...) buf = append(buf, req.Proto...) buf = append(buf, `" `...) buf = append(buf, strconv.Itoa(status)...) buf = append(buf, " "...) buf = append(buf, strconv.Itoa(size)...) return buf } // writeLog writes a log entry for req to w in Apache Common Log Format. // ts is the timestamp with which the entry should be logged. // status and size are used to provide the response HTTP status and size. func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) { buf := buildCommonLogLine(req, url, ts, status, size) buf = append(buf, '\n') w.Write(buf) } // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format. // ts is the timestamp with which the entry should be logged. // status and size are used to provide the response HTTP status and size. func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) { buf := buildCommonLogLine(req, url, ts, status, size) buf = append(buf, ` "`...) buf = appendQuoted(buf, req.Referer()) buf = append(buf, `" "`...) buf = appendQuoted(buf, req.UserAgent()) buf = append(buf, '"', '\n') w.Write(buf) } // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in // Apache Combined Log Format. // // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format. // // LoggingHandler always sets the ident field of the log to - func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler { return combinedLoggingHandler{out, h} } // LoggingHandler return a http.Handler that wraps h and logs requests to out in // Apache Common Log Format (CLF). // // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format. // // LoggingHandler always sets the ident field of the log to - // // Example: // // r := mux.NewRouter() // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // w.Write([]byte("This is a catch-all route")) // }) // loggedRouter := handlers.LoggingHandler(os.Stdout, r) // http.ListenAndServe(":1123", loggedRouter) // func LoggingHandler(out io.Writer, h http.Handler) http.Handler { return loggingHandler{out, h} } // isContentType validates the Content-Type header matches the supplied // contentType. That is, its type and subtype match. func isContentType(h http.Header, contentType string) bool { ct := h.Get("Content-Type") if i := strings.IndexRune(ct, ';'); i != -1 { ct = ct[0:i] } return ct == contentType } // ContentTypeHandler wraps and returns a http.Handler, validating the request // content type is compatible with the contentTypes list. It writes a HTTP 415 // error if that fails. // // Only PUT, POST, and PATCH requests are considered. func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") { h.ServeHTTP(w, r) return } for _, ct := range contentTypes { if isContentType(r.Header, ct) { h.ServeHTTP(w, r) return } } http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType) }) } const ( // HTTPMethodOverrideHeader is a commonly used // http header to override a request method. HTTPMethodOverrideHeader = "X-HTTP-Method-Override" // HTTPMethodOverrideFormKey is a commonly used // HTML form key to override a request method. HTTPMethodOverrideFormKey = "_method" ) // HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for // the X-HTTP-Method-Override header or the _method form key, and overrides (if // valid) request.Method with its value. // // This is especially useful for HTTP clients that don't support many http verbs. // It isn't secure to override e.g a GET to a POST, so only POST requests are // considered. Likewise, the override method can only be a "write" method: PUT, // PATCH or DELETE. // // Form method takes precedence over header method. func HTTPMethodOverrideHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { om := r.FormValue(HTTPMethodOverrideFormKey) if om == "" { om = r.Header.Get(HTTPMethodOverrideHeader) } if om == "PUT" || om == "PATCH" || om == "DELETE" { r.Method = om } } h.ServeHTTP(w, r) }) } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/handlers_test.go000066400000000000000000000253061264624347100233430ustar00rootroot00000000000000// Copyright 2013 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package handlers import ( "bytes" "net" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" ) const ( ok = "ok\n" notAllowed = "Method not allowed\n" ) var okHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Write([]byte(ok)) }) func newRequest(method, url string) *http.Request { req, err := http.NewRequest(method, url, nil) if err != nil { panic(err) } return req } func TestMethodHandler(t *testing.T) { tests := []struct { req *http.Request handler http.Handler code int allow string // Contents of the Allow header body string }{ // No handlers {newRequest("GET", "/foo"), MethodHandler{}, http.StatusMethodNotAllowed, "", notAllowed}, {newRequest("OPTIONS", "/foo"), MethodHandler{}, http.StatusOK, "", ""}, // A single handler {newRequest("GET", "/foo"), MethodHandler{"GET": okHandler}, http.StatusOK, "", ok}, {newRequest("POST", "/foo"), MethodHandler{"GET": okHandler}, http.StatusMethodNotAllowed, "GET", notAllowed}, // Multiple handlers {newRequest("GET", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "", ok}, {newRequest("POST", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "", ok}, {newRequest("DELETE", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusMethodNotAllowed, "GET, POST", notAllowed}, {newRequest("OPTIONS", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "GET, POST", ""}, // Override OPTIONS {newRequest("OPTIONS", "/foo"), MethodHandler{"OPTIONS": okHandler}, http.StatusOK, "", ok}, } for i, test := range tests { rec := httptest.NewRecorder() test.handler.ServeHTTP(rec, test.req) if rec.Code != test.code { t.Fatalf("%d: wrong code, got %d want %d", i, rec.Code, test.code) } if allow := rec.HeaderMap.Get("Allow"); allow != test.allow { t.Fatalf("%d: wrong Allow, got %s want %s", i, allow, test.allow) } if body := rec.Body.String(); body != test.body { t.Fatalf("%d: wrong body, got %q want %q", i, body, test.body) } } } func TestWriteLog(t *testing.T) { loc, err := time.LoadLocation("Europe/Warsaw") if err != nil { panic(err) } ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc) // A typical request with an OK response req := newRequest("GET", "http://example.com") req.RemoteAddr = "192.168.100.5" buf := new(bytes.Buffer) writeLog(buf, req, *req.URL, ts, http.StatusOK, 100) log := buf.String() expected := "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // CONNECT request over http/2.0 req = &http.Request{ Method: "CONNECT", Proto: "HTTP/2.0", ProtoMajor: 2, ProtoMinor: 0, URL: &url.URL{Host: "www.example.com:443"}, Host: "www.example.com:443", RemoteAddr: "192.168.100.5", } buf = new(bytes.Buffer) writeLog(buf, req, *req.URL, ts, http.StatusOK, 100) log = buf.String() expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"CONNECT www.example.com:443 HTTP/2.0\" 200 100\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // Request with an unauthorized user req = newRequest("GET", "http://example.com") req.RemoteAddr = "192.168.100.5" req.URL.User = url.User("kamil") buf.Reset() writeLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500) log = buf.String() expected = "192.168.100.5 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 401 500\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // Request with url encoded parameters req = newRequest("GET", "http://example.com/test?abc=hello%20world&a=b%3F") req.RemoteAddr = "192.168.100.5" buf.Reset() writeLog(buf, req, *req.URL, ts, http.StatusOK, 100) log = buf.String() expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET /test?abc=hello%20world&a=b%3F HTTP/1.1\" 200 100\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } } func TestWriteCombinedLog(t *testing.T) { loc, err := time.LoadLocation("Europe/Warsaw") if err != nil { panic(err) } ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc) // A typical request with an OK response req := newRequest("GET", "http://example.com") req.RemoteAddr = "192.168.100.5" req.Header.Set("Referer", "http://example.com") req.Header.Set( "User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.33 "+ "(KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33", ) buf := new(bytes.Buffer) writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100) log := buf.String() expected := "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " + "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " + "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // CONNECT request over http/2.0 req1 := &http.Request{ Method: "CONNECT", Host: "www.example.com:443", Proto: "HTTP/2.0", ProtoMajor: 2, ProtoMinor: 0, RemoteAddr: "192.168.100.5", Header: http.Header{}, URL: &url.URL{Host: "www.example.com:443"}, } req1.Header.Set("Referer", "http://example.com") req1.Header.Set( "User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.33 "+ "(KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33", ) buf = new(bytes.Buffer) writeCombinedLog(buf, req1, *req1.URL, ts, http.StatusOK, 100) log = buf.String() expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"CONNECT www.example.com:443 HTTP/2.0\" 200 100 \"http://example.com\" " + "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " + "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // Request with an unauthorized user req.URL.User = url.User("kamil") buf.Reset() writeCombinedLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500) log = buf.String() expected = "192.168.100.5 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 401 500 \"http://example.com\" " + "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " + "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // Test with remote ipv6 address req.RemoteAddr = "::1" buf.Reset() writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100) log = buf.String() expected = "::1 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " + "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " + "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } // Test remote ipv6 addr, with port req.RemoteAddr = net.JoinHostPort("::1", "65000") buf.Reset() writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100) log = buf.String() expected = "::1 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " + "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " + "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n" if log != expected { t.Fatalf("wrong log, got %q want %q", log, expected) } } func TestLogPathRewrites(t *testing.T) { var buf bytes.Buffer handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL.Path = "/" // simulate http.StripPrefix and friends w.WriteHeader(200) }) logger := LoggingHandler(&buf, handler) logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf")) if !strings.Contains(buf.String(), "GET /subdir/asdf HTTP") { t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "GET /subdir/asdf HTTP") } } func BenchmarkWriteLog(b *testing.B) { loc, err := time.LoadLocation("Europe/Warsaw") if err != nil { b.Fatalf(err.Error()) } ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc) req := newRequest("GET", "http://example.com") req.RemoteAddr = "192.168.100.5" b.ResetTimer() buf := &bytes.Buffer{} for i := 0; i < b.N; i++ { buf.Reset() writeLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500) } } func TestContentTypeHandler(t *testing.T) { tests := []struct { Method string AllowContentTypes []string ContentType string Code int }{ {"POST", []string{"application/json"}, "application/json", http.StatusOK}, {"POST", []string{"application/json", "application/xml"}, "application/json", http.StatusOK}, {"POST", []string{"application/json"}, "application/json; charset=utf-8", http.StatusOK}, {"POST", []string{"application/json"}, "application/json+xxx", http.StatusUnsupportedMediaType}, {"POST", []string{"application/json"}, "text/plain", http.StatusUnsupportedMediaType}, {"GET", []string{"application/json"}, "", http.StatusOK}, {"GET", []string{}, "", http.StatusOK}, } for _, test := range tests { r, err := http.NewRequest(test.Method, "/", nil) if err != nil { t.Error(err) continue } h := ContentTypeHandler(okHandler, test.AllowContentTypes...) r.Header.Set("Content-Type", test.ContentType) w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != test.Code { t.Errorf("expected %d, got %d", test.Code, w.Code) } } } func TestHTTPMethodOverride(t *testing.T) { var tests = []struct { Method string OverrideMethod string ExpectedMethod string }{ {"POST", "PUT", "PUT"}, {"POST", "PATCH", "PATCH"}, {"POST", "DELETE", "DELETE"}, {"PUT", "DELETE", "PUT"}, {"GET", "GET", "GET"}, {"HEAD", "HEAD", "HEAD"}, {"GET", "PUT", "GET"}, {"HEAD", "DELETE", "HEAD"}, } for _, test := range tests { h := HTTPMethodOverrideHandler(okHandler) reqs := make([]*http.Request, 0, 2) rHeader, err := http.NewRequest(test.Method, "/", nil) if err != nil { t.Error(err) } rHeader.Header.Set(HTTPMethodOverrideHeader, test.OverrideMethod) reqs = append(reqs, rHeader) f := url.Values{HTTPMethodOverrideFormKey: []string{test.OverrideMethod}} rForm, err := http.NewRequest(test.Method, "/", strings.NewReader(f.Encode())) if err != nil { t.Error(err) } rForm.Header.Set("Content-Type", "application/x-www-form-urlencoded") reqs = append(reqs, rForm) for _, r := range reqs { w := httptest.NewRecorder() h.ServeHTTP(w, r) if r.Method != test.ExpectedMethod { t.Errorf("Expected %s, got %s", test.ExpectedMethod, r.Method) } } } } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/proxy_headers.go000066400000000000000000000101371264624347100233540ustar00rootroot00000000000000package handlers import ( "net/http" "regexp" "strings" ) var ( // De-facto standard header keys. xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") xRealIP = http.CanonicalHeaderKey("X-Real-IP") xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Scheme") ) var ( // RFC7239 defines a new "Forwarded: " header designed to replace the // existing use of X-Forwarded-* headers. // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 forwarded = http.CanonicalHeaderKey("Forwarded") // Allows for a sub-match of the first value after 'for=' to the next // comma, semi-colon or space. The match is case-insensitive. forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`) // Allows for a sub-match for the first instance of scheme (http|https) // prefixed by 'proto='. The match is case-insensitive. protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`) ) // ProxyHeaders inspects common reverse proxy headers and sets the corresponding // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP // for the remote (client) IP address, X-Forwarded-Proto for the scheme // (http|https) and the RFC7239 Forwarded header, which may include both client // IPs and schemes. // // NOTE: This middleware should only be used when behind a reverse // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are // configured not to) strip these headers from client requests, or where these // headers are accepted "as is" from a remote client (e.g. when Go is not behind // a proxy), can manifest as a vulnerability if your application uses these // headers for validating the 'trustworthiness' of a request. func ProxyHeaders(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { // Set the remote IP with the value passed from the proxy. if fwd := getIP(r); fwd != "" { r.RemoteAddr = fwd } // Set the scheme (proto) with the value passed from the proxy. if scheme := getScheme(r); scheme != "" { r.URL.Scheme = scheme } // Call the next handler in the chain. h.ServeHTTP(w, r) } return http.HandlerFunc(fn) } // getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239 // Forwarded headers (in that order). func getIP(r *http.Request) string { var addr string if fwd := r.Header.Get(xForwardedFor); fwd != "" { // Only grab the first (client) address. Note that '192.168.0.1, // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after // the first may represent forwarding proxies earlier in the chain. s := strings.Index(fwd, ", ") if s == -1 { s = len(fwd) } addr = fwd[:s] } else if fwd := r.Header.Get(xRealIP); fwd != "" { // X-Real-IP should only contain one IP address (the client making the // request). addr = fwd } else if fwd := r.Header.Get(forwarded); fwd != "" { // match should contain at least two elements if the protocol was // specified in the Forwarded header. The first element will always be // the 'for=' capture, which we ignore. In the case of multiple IP // addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only // extract the first, which should be the client IP. if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { // IPv6 addresses in Forwarded headers are quoted-strings. We strip // these quotes. addr = strings.Trim(match[1], `"`) } } return addr } // getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239 // Forwarded headers (in that order). func getScheme(r *http.Request) string { var scheme string // Retrieve the scheme from X-Forwarded-Proto. if proto := r.Header.Get(xForwardedProto); proto != "" { scheme = strings.ToLower(proto) } else if proto := r.Header.Get(forwarded); proto != "" { // match should contain at least two elements if the protocol was // specified in the Forwarded header. The first element will always be // the 'proto=' capture, which we ignore. In the case of multiple proto // parameters (invalid) we only extract the first. if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 { scheme = strings.ToLower(match[1]) } } return scheme } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/proxy_headers_test.go000066400000000000000000000063701264624347100244170ustar00rootroot00000000000000package handlers import ( "net/http" "net/http/httptest" "testing" ) type headerTable struct { key string // header key val string // header val expected string // expected result } func TestGetIP(t *testing.T) { headers := []headerTable{ {xForwardedFor, "8.8.8.8", "8.8.8.8"}, // Single address {xForwardedFor, "8.8.8.8, 8.8.4.4", "8.8.8.8"}, // Multiple {xForwardedFor, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address {xForwardedFor, "", ""}, // None {xRealIP, "8.8.8.8", "8.8.8.8"}, // Single address {xRealIP, "8.8.8.8, 8.8.4.4", "8.8.8.8, 8.8.4.4"}, // Multiple {xRealIP, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address {xRealIP, "", ""}, // None {forwarded, `for="_gazonk"`, "_gazonk"}, // Hostname {forwarded, `For="[2001:db8:cafe::17]:4711`, `[2001:db8:cafe::17]:4711`}, // IPv6 address {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, `192.0.2.60`}, // Multiple params {forwarded, `for=192.0.2.43, for=198.51.100.17`, "192.0.2.43"}, // Multiple params {forwarded, `for="workstation.local",for=198.51.100.17`, "workstation.local"}, // Hostname } for _, v := range headers { req := &http.Request{ Header: http.Header{ v.key: []string{v.val}, }} res := getIP(req) if res != v.expected { t.Fatalf("wrong header for %s: got %s want %s", v.key, res, v.expected) } } } func TestGetScheme(t *testing.T) { headers := []headerTable{ {xForwardedProto, "https", "https"}, {xForwardedProto, "http", "http"}, {xForwardedProto, "HTTP", "http"}, {forwarded, `For="[2001:db8:cafe::17]:4711`, ""}, // No proto {forwarded, `for=192.0.2.43, for=198.51.100.17;proto=https`, "https"}, // Multiple params before proto {forwarded, `for=172.32.10.15; proto=https;by=127.0.0.1`, "https"}, // Space before proto {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, "http"}, // Multiple params } for _, v := range headers { req := &http.Request{ Header: http.Header{ v.key: []string{v.val}, }, } res := getScheme(req) if res != v.expected { t.Fatalf("wrong header for %s: got %s want %s", v.key, res, v.expected) } } } // Test the middleware end-to-end func TestProxyHeaders(t *testing.T) { rr := httptest.NewRecorder() r := newRequest("GET", "/") r.Header.Set(xForwardedFor, "8.8.8.8") r.Header.Set(xForwardedProto, "https") var addr string var proto string ProxyHeaders(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { addr = r.RemoteAddr proto = r.URL.Scheme })).ServeHTTP(rr, r) if rr.Code != http.StatusOK { t.Fatalf("bad status: got %d want %d", rr.Code, http.StatusOK) } if addr != r.Header.Get(xForwardedFor) { t.Fatalf("wrong address: got %s want %s", addr, r.Header.Get(xForwardedFor)) } if proto != r.Header.Get(xForwardedProto) { t.Fatalf("wrong address: got %s want %s", proto, r.Header.Get(xForwardedProto)) } } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/recovery.go000066400000000000000000000037001264624347100223340ustar00rootroot00000000000000package handlers import ( "log" "net/http" "runtime/debug" ) type recoveryHandler struct { handler http.Handler logger *log.Logger printStack bool } // RecoveryOption provides a functional approach to define // configuration for a handler; such as setting the logging // whether or not to print strack traces on panic. type RecoveryOption func(http.Handler) func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler { for _, option := range opts { option(h) } return h } // RecoveryHandler is HTTP middleware that recovers from a panic, // logs the panic, writes http.StatusInternalServerError, and // continues to the next handler. // // Example: // // r := mux.NewRouter() // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // panic("Unexpected error!") // }) // // http.ListenAndServe(":1123", handlers.RecoveryHandler()(r)) func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler { r := &recoveryHandler{handler: h} return parseRecoveryOptions(r, opts...) } } // RecoveryLogger is a functional option to override // the default logger func RecoveryLogger(logger *log.Logger) RecoveryOption { return func(h http.Handler) { r := h.(*recoveryHandler) r.logger = logger } } // PrintRecoveryStack is a functional option to enable // or disable printing stack traces on panic. func PrintRecoveryStack(print bool) RecoveryOption { return func(h http.Handler) { r := h.(*recoveryHandler) r.printStack = print } } func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { defer func() { if err := recover(); err != nil { w.WriteHeader(http.StatusInternalServerError) h.log(err) } }() h.handler.ServeHTTP(w, req) } func (h recoveryHandler) log(message interface{}) { if h.logger != nil { h.logger.Println(message) } else { log.Println(message) } if h.printStack { debug.PrintStack() } } handlers-8f2758070a82adb7a3ad6b223a0b91878f32d400/recovery_test.go000066400000000000000000000022511264624347100233730ustar00rootroot00000000000000package handlers import ( "bytes" "log" "net/http" "net/http/httptest" "strings" "testing" ) func TestRecoveryLoggerWithDefaultOptions(t *testing.T) { var buf bytes.Buffer log.SetOutput(&buf) handler := RecoveryHandler() handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { panic("Unexpected error!") }) recovery := handler(handlerFunc) recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf")) if !strings.Contains(buf.String(), "Unexpected error!") { t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!") } } func TestRecoveryLoggerWithCustomLogger(t *testing.T) { var buf bytes.Buffer var logger = log.New(&buf, "", log.LstdFlags) handler := RecoveryHandler(RecoveryLogger(logger), PrintRecoveryStack(false)) handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { panic("Unexpected error!") }) recovery := handler(handlerFunc) recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf")) if !strings.Contains(buf.String(), "Unexpected error!") { t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!") } }