// 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) }) }