github.com/matthieu/go-ethereum@v1.13.2/node/rpcstack.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "compress/gzip" 21 "io" 22 "io/ioutil" 23 "net" 24 "net/http" 25 "strings" 26 "sync" 27 28 "github.com/matthieu/go-ethereum/log" 29 "github.com/rs/cors" 30 ) 31 32 // NewHTTPHandlerStack returns wrapped http-related handlers 33 func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { 34 // Wrap the CORS-handler within a host-handler 35 handler := newCorsHandler(srv, cors) 36 handler = newVHostHandler(vhosts, handler) 37 return newGzipHandler(handler) 38 } 39 40 func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { 41 // disable CORS support if user has not specified a custom CORS configuration 42 if len(allowedOrigins) == 0 { 43 return srv 44 } 45 c := cors.New(cors.Options{ 46 AllowedOrigins: allowedOrigins, 47 AllowedMethods: []string{http.MethodPost, http.MethodGet}, 48 MaxAge: 600, 49 AllowedHeaders: []string{"*"}, 50 }) 51 return c.Handler(srv) 52 } 53 54 // virtualHostHandler is a handler which validates the Host-header of incoming requests. 55 // Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to 56 // the service ip address (but without CORS headers). By verifying the targeted virtual host, we can 57 // ensure that it's a destination that the node operator has defined. 58 type virtualHostHandler struct { 59 vhosts map[string]struct{} 60 next http.Handler 61 } 62 63 func newVHostHandler(vhosts []string, next http.Handler) http.Handler { 64 vhostMap := make(map[string]struct{}) 65 for _, allowedHost := range vhosts { 66 vhostMap[strings.ToLower(allowedHost)] = struct{}{} 67 } 68 return &virtualHostHandler{vhostMap, next} 69 } 70 71 // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler 72 func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 73 // if r.Host is not set, we can continue serving since a browser would set the Host header 74 if r.Host == "" { 75 h.next.ServeHTTP(w, r) 76 return 77 } 78 host, _, err := net.SplitHostPort(r.Host) 79 if err != nil { 80 // Either invalid (too many colons) or no port specified 81 host = r.Host 82 } 83 if ipAddr := net.ParseIP(host); ipAddr != nil { 84 // It's an IP address, we can serve that 85 h.next.ServeHTTP(w, r) 86 return 87 88 } 89 // Not an IP address, but a hostname. Need to validate 90 if _, exist := h.vhosts["*"]; exist { 91 h.next.ServeHTTP(w, r) 92 return 93 } 94 if _, exist := h.vhosts[host]; exist { 95 h.next.ServeHTTP(w, r) 96 return 97 } 98 http.Error(w, "invalid host specified", http.StatusForbidden) 99 } 100 101 var gzPool = sync.Pool{ 102 New: func() interface{} { 103 w := gzip.NewWriter(ioutil.Discard) 104 return w 105 }, 106 } 107 108 type gzipResponseWriter struct { 109 io.Writer 110 http.ResponseWriter 111 } 112 113 func (w *gzipResponseWriter) WriteHeader(status int) { 114 w.Header().Del("Content-Length") 115 w.ResponseWriter.WriteHeader(status) 116 } 117 118 func (w *gzipResponseWriter) Write(b []byte) (int, error) { 119 return w.Writer.Write(b) 120 } 121 122 func newGzipHandler(next http.Handler) http.Handler { 123 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 124 if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 125 next.ServeHTTP(w, r) 126 return 127 } 128 129 w.Header().Set("Content-Encoding", "gzip") 130 131 gz := gzPool.Get().(*gzip.Writer) 132 defer gzPool.Put(gz) 133 134 gz.Reset(w) 135 defer gz.Close() 136 137 next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) 138 }) 139 } 140 141 // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade 142 // request to the websocket protocol. If not, serves the the request with the http handler. 143 func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { 144 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 145 if isWebsocket(r) { 146 ws.ServeHTTP(w, r) 147 log.Debug("serving websocket request") 148 return 149 } 150 151 h.ServeHTTP(w, r) 152 }) 153 } 154 155 // isWebsocket checks the header of an http request for a websocket upgrade request. 156 func isWebsocket(r *http.Request) bool { 157 return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && 158 strings.ToLower(r.Header.Get("Connection")) == "upgrade" 159 }