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  }