github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/node/rpcstack_test.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  	"bytes"
    21  	"fmt"
    22  	"net/http"
    23  	"net/url"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/gorilla/websocket"
    29  	"github.com/stretchr/testify/assert"
    30  
    31  	"github.com/scroll-tech/go-ethereum/internal/testlog"
    32  	"github.com/scroll-tech/go-ethereum/log"
    33  	"github.com/scroll-tech/go-ethereum/rpc"
    34  )
    35  
    36  // TestCorsHandler makes sure CORS are properly handled on the http server.
    37  func TestCorsHandler(t *testing.T) {
    38  	srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{})
    39  	defer srv.stop()
    40  	url := "http://" + srv.listenAddr()
    41  
    42  	resp := rpcRequest(t, url, "origin", "test.com")
    43  	assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
    44  
    45  	resp2 := rpcRequest(t, url, "origin", "bad")
    46  	assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
    47  }
    48  
    49  // TestVhosts makes sure vhosts are properly handled on the http server.
    50  func TestVhosts(t *testing.T) {
    51  	srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{})
    52  	defer srv.stop()
    53  	url := "http://" + srv.listenAddr()
    54  
    55  	resp := rpcRequest(t, url, "host", "test")
    56  	assert.Equal(t, resp.StatusCode, http.StatusOK)
    57  
    58  	resp2 := rpcRequest(t, url, "host", "bad")
    59  	assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
    60  }
    61  
    62  type originTest struct {
    63  	spec    string
    64  	expOk   []string
    65  	expFail []string
    66  }
    67  
    68  // splitAndTrim splits input separated by a comma
    69  // and trims excessive white space from the substrings.
    70  // Copied over from flags.go
    71  func splitAndTrim(input string) (ret []string) {
    72  	l := strings.Split(input, ",")
    73  	for _, r := range l {
    74  		r = strings.TrimSpace(r)
    75  		if len(r) > 0 {
    76  			ret = append(ret, r)
    77  		}
    78  	}
    79  	return ret
    80  }
    81  
    82  // TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
    83  func TestWebsocketOrigins(t *testing.T) {
    84  	tests := []originTest{
    85  		{
    86  			spec: "*", // allow all
    87  			expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540",
    88  				"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
    89  		},
    90  		{
    91  			spec:    "test",
    92  			expOk:   []string{"http://test", "https://test", "http://test:8540", "https://test:8540"},
    93  			expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
    94  		},
    95  		// scheme tests
    96  		{
    97  			spec:  "https://test",
    98  			expOk: []string{"https://test", "https://test:9999"},
    99  			expFail: []string{
   100  				"test",                                // no scheme, required by spec
   101  				"http://test",                         // wrong scheme
   102  				"http://test.foo", "https://a.test.x", // subdomain variatoins
   103  				"http://testx:8540", "https://xtest:8540"},
   104  		},
   105  		// ip tests
   106  		{
   107  			spec:  "https://12.34.56.78",
   108  			expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"},
   109  			expFail: []string{
   110  				"http://12.34.56.78",     // wrong scheme
   111  				"http://12.34.56.78:443", // wrong scheme
   112  				"http://1.12.34.56.78",   // wrong 'domain name'
   113  				"http://12.34.56.78.a",   // wrong 'domain name'
   114  				"https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"},
   115  		},
   116  		// port tests
   117  		{
   118  			spec:  "test:8540",
   119  			expOk: []string{"http://test:8540", "https://test:8540"},
   120  			expFail: []string{
   121  				"http://test", "https://test", // spec says port required
   122  				"http://test:8541", "https://test:8541", // wrong port
   123  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   124  		},
   125  		// scheme and port
   126  		{
   127  			spec:  "https://test:8540",
   128  			expOk: []string{"https://test:8540"},
   129  			expFail: []string{
   130  				"https://test",                          // missing port
   131  				"http://test",                           // missing port, + wrong scheme
   132  				"http://test:8540",                      // wrong scheme
   133  				"http://test:8541", "https://test:8541", // wrong port
   134  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   135  		},
   136  		// several allowed origins
   137  		{
   138  			spec: "localhost,http://127.0.0.1",
   139  			expOk: []string{"localhost", "http://localhost", "https://localhost:8443",
   140  				"http://127.0.0.1", "http://127.0.0.1:8080"},
   141  			expFail: []string{
   142  				"https://127.0.0.1", // wrong scheme
   143  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   144  		},
   145  	}
   146  	for _, tc := range tests {
   147  		srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)})
   148  		url := fmt.Sprintf("ws://%v", srv.listenAddr())
   149  		for _, origin := range tc.expOk {
   150  			if err := wsRequest(t, url, origin); err != nil {
   151  				t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err)
   152  			}
   153  		}
   154  		for _, origin := range tc.expFail {
   155  			if err := wsRequest(t, url, origin); err == nil {
   156  				t.Errorf("spec '%v', origin '%v': expected not to allow,  got ok", tc.spec, origin)
   157  			}
   158  		}
   159  		srv.stop()
   160  	}
   161  }
   162  
   163  // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly.
   164  func TestIsWebsocket(t *testing.T) {
   165  	r, _ := http.NewRequest("GET", "/", nil)
   166  
   167  	assert.False(t, isWebsocket(r))
   168  	r.Header.Set("upgrade", "websocket")
   169  	assert.False(t, isWebsocket(r))
   170  	r.Header.Set("connection", "upgrade")
   171  	assert.True(t, isWebsocket(r))
   172  	r.Header.Set("connection", "upgrade,keep-alive")
   173  	assert.True(t, isWebsocket(r))
   174  	r.Header.Set("connection", " UPGRADE,keep-alive")
   175  	assert.True(t, isWebsocket(r))
   176  }
   177  
   178  func Test_checkPath(t *testing.T) {
   179  	tests := []struct {
   180  		req      *http.Request
   181  		prefix   string
   182  		expected bool
   183  	}{
   184  		{
   185  			req:      &http.Request{URL: &url.URL{Path: "/test"}},
   186  			prefix:   "/test",
   187  			expected: true,
   188  		},
   189  		{
   190  			req:      &http.Request{URL: &url.URL{Path: "/testing"}},
   191  			prefix:   "/test",
   192  			expected: true,
   193  		},
   194  		{
   195  			req:      &http.Request{URL: &url.URL{Path: "/"}},
   196  			prefix:   "/test",
   197  			expected: false,
   198  		},
   199  		{
   200  			req:      &http.Request{URL: &url.URL{Path: "/fail"}},
   201  			prefix:   "/test",
   202  			expected: false,
   203  		},
   204  		{
   205  			req:      &http.Request{URL: &url.URL{Path: "/"}},
   206  			prefix:   "",
   207  			expected: true,
   208  		},
   209  		{
   210  			req:      &http.Request{URL: &url.URL{Path: "/fail"}},
   211  			prefix:   "",
   212  			expected: false,
   213  		},
   214  		{
   215  			req:      &http.Request{URL: &url.URL{Path: "/"}},
   216  			prefix:   "/",
   217  			expected: true,
   218  		},
   219  		{
   220  			req:      &http.Request{URL: &url.URL{Path: "/testing"}},
   221  			prefix:   "/",
   222  			expected: true,
   223  		},
   224  	}
   225  
   226  	for i, tt := range tests {
   227  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   228  			assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix))
   229  		})
   230  	}
   231  }
   232  
   233  func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer {
   234  	t.Helper()
   235  
   236  	srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts)
   237  	assert.NoError(t, srv.enableRPC(nil, *conf))
   238  	if ws {
   239  		assert.NoError(t, srv.enableWS(nil, *wsConf))
   240  	}
   241  	assert.NoError(t, srv.setListenAddr("localhost", 0))
   242  	assert.NoError(t, srv.start())
   243  	return srv
   244  }
   245  
   246  // wsRequest attempts to open a WebSocket connection to the given URL.
   247  func wsRequest(t *testing.T, url, browserOrigin string) error {
   248  	t.Helper()
   249  	t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin)
   250  
   251  	headers := make(http.Header)
   252  	if browserOrigin != "" {
   253  		headers.Set("Origin", browserOrigin)
   254  	}
   255  	conn, _, err := websocket.DefaultDialer.Dial(url, headers)
   256  	if conn != nil {
   257  		conn.Close()
   258  	}
   259  	return err
   260  }
   261  
   262  // rpcRequest performs a JSON-RPC request to the given URL.
   263  func rpcRequest(t *testing.T, url string, extraHeaders ...string) *http.Response {
   264  	t.Helper()
   265  
   266  	// Create the request.
   267  	body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`))
   268  	req, err := http.NewRequest("POST", url, body)
   269  	if err != nil {
   270  		t.Fatal("could not create http request:", err)
   271  	}
   272  	req.Header.Set("content-type", "application/json")
   273  
   274  	// Apply extra headers.
   275  	if len(extraHeaders)%2 != 0 {
   276  		panic("odd extraHeaders length")
   277  	}
   278  	for i := 0; i < len(extraHeaders); i += 2 {
   279  		key, value := extraHeaders[i], extraHeaders[i+1]
   280  		if strings.ToLower(key) == "host" {
   281  			req.Host = value
   282  		} else {
   283  			req.Header.Set(key, value)
   284  		}
   285  	}
   286  
   287  	// Perform the request.
   288  	t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders)
   289  	resp, err := http.DefaultClient.Do(req)
   290  	if err != nil {
   291  		t.Fatal(err)
   292  	}
   293  	return resp
   294  }