// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package node import ( "compress/gzip" "io" "io/ioutil" "net" "net/http" "strings" "sync" "github.com/ethereum/go-ethereum/log" "github.com/rs/cors" ) // NewHTTPHandlerStack returns wrapped http-related handlers func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { // Wrap the CORS-handler within a host-handler handler := newCorsHandler(srv, cors) handler = newVHostHandler(vhosts, handler) return newGzipHandler(handler) } func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { // disable CORS support if user has not specified a custom CORS configuration if len(allowedOrigins) == 0 { return srv } c := cors.New(cors.Options{ AllowedOrigins: allowedOrigins, AllowedMethods: []string{http.MethodPost, http.MethodGet}, MaxAge: 600, AllowedHeaders: []string{"*"}, }) return c.Handler(srv) } // virtualHostHandler is a handler which validates the Host-header of incoming requests. // Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to // the service ip address (but without CORS headers). By verifying the targeted virtual host, we can // ensure that it's a destination that the node operator has defined. type virtualHostHandler struct { vhosts map[string]struct{} next http.Handler } func newVHostHandler(vhosts []string, next http.Handler) http.Handler { vhostMap := make(map[string]struct{}) for _, allowedHost := range vhosts { vhostMap[strings.ToLower(allowedHost)] = struct{}{} } return &virtualHostHandler{vhostMap, next} } // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // if r.Host is not set, we can continue serving since a browser would set the Host header if r.Host == "" { h.next.ServeHTTP(w, r) return } host, _, err := net.SplitHostPort(r.Host) if err != nil { // Either invalid (too many colons) or no port specified host = r.Host } if ipAddr := net.ParseIP(host); ipAddr != nil { // It's an IP address, we can serve that h.next.ServeHTTP(w, r) return } // Not an IP address, but a hostname. Need to validate if _, exist := h.vhosts["*"]; exist { h.next.ServeHTTP(w, r) return } if _, exist := h.vhosts[host]; exist { h.next.ServeHTTP(w, r) return } http.Error(w, "invalid host specified", http.StatusForbidden) } var gzPool = sync.Pool{ New: func() interface{} { w := gzip.NewWriter(ioutil.Discard) return w }, } type gzipResponseWriter struct { io.Writer http.ResponseWriter } func (w *gzipResponseWriter) WriteHeader(status int) { w.Header().Del("Content-Length") w.ResponseWriter.WriteHeader(status) } func (w *gzipResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } func newGzipHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } w.Header().Set("Content-Encoding", "gzip") gz := gzPool.Get().(*gzip.Writer) defer gzPool.Put(gz) gz.Reset(w) defer gz.Close() next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) }) } // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade // request to the websocket protocol. If not, serves the the request with the http handler. func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if isWebsocket(r) { ws.ServeHTTP(w, r) log.Debug("serving websocket request") return } h.ServeHTTP(w, r) }) } // isWebsocket checks the header of an http request for a websocket upgrade request. func isWebsocket(r *http.Request) bool { return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && strings.ToLower(r.Header.Get("Connection")) == "upgrade" }