github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/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  	"net/http"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/kisexp/xdchain/internal/testlog"
    26  	"github.com/kisexp/xdchain/log"
    27  	"github.com/kisexp/xdchain/rpc"
    28  	"github.com/gorilla/websocket"
    29  	"github.com/stretchr/testify/assert"
    30  )
    31  
    32  // TestCorsHandler makes sure CORS are properly handled on the http server.
    33  func TestCorsHandler(t *testing.T) {
    34  	srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{})
    35  	defer srv.stop()
    36  
    37  	resp := testRequest(t, "origin", "test.com", "", srv)
    38  	assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
    39  
    40  	resp2 := testRequest(t, "origin", "bad", "", srv)
    41  	assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
    42  }
    43  
    44  // TestVhosts makes sure vhosts are properly handled on the http server.
    45  func TestVhosts(t *testing.T) {
    46  	srv := createAndStartServer(t, httpConfig{Vhosts: []string{"test"}}, false, wsConfig{})
    47  	defer srv.stop()
    48  
    49  	resp := testRequest(t, "", "", "test", srv)
    50  	assert.Equal(t, resp.StatusCode, http.StatusOK)
    51  
    52  	resp2 := testRequest(t, "", "", "bad", srv)
    53  	assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
    54  }
    55  
    56  type originTest struct {
    57  	spec    string
    58  	expOk   []string
    59  	expFail []string
    60  }
    61  
    62  // splitAndTrim splits input separated by a comma
    63  // and trims excessive white space from the substrings.
    64  // Copied over from flags.go
    65  func splitAndTrim(input string) (ret []string) {
    66  	l := strings.Split(input, ",")
    67  	for _, r := range l {
    68  		r = strings.TrimSpace(r)
    69  		if len(r) > 0 {
    70  			ret = append(ret, r)
    71  		}
    72  	}
    73  	return ret
    74  }
    75  
    76  // TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
    77  func TestWebsocketOrigins(t *testing.T) {
    78  	tests := []originTest{
    79  		{
    80  			spec: "*", // allow all
    81  			expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540",
    82  				"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
    83  		},
    84  		{
    85  			spec:    "test",
    86  			expOk:   []string{"http://test", "https://test", "http://test:8540", "https://test:8540"},
    87  			expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
    88  		},
    89  		// scheme tests
    90  		{
    91  			spec:  "https://test",
    92  			expOk: []string{"https://test", "https://test:9999"},
    93  			expFail: []string{
    94  				"test",                                // no scheme, required by spec
    95  				"http://test",                         // wrong scheme
    96  				"http://test.foo", "https://a.test.x", // subdomain variatoins
    97  				"http://testx:8540", "https://xtest:8540"},
    98  		},
    99  		// ip tests
   100  		{
   101  			spec:  "https://12.34.56.78",
   102  			expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"},
   103  			expFail: []string{
   104  				"http://12.34.56.78",     // wrong scheme
   105  				"http://12.34.56.78:443", // wrong scheme
   106  				"http://1.12.34.56.78",   // wrong 'domain name'
   107  				"http://12.34.56.78.a",   // wrong 'domain name'
   108  				"https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"},
   109  		},
   110  		// port tests
   111  		{
   112  			spec:  "test:8540",
   113  			expOk: []string{"http://test:8540", "https://test:8540"},
   114  			expFail: []string{
   115  				"http://test", "https://test", // spec says port required
   116  				"http://test:8541", "https://test:8541", // wrong port
   117  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   118  		},
   119  		// scheme and port
   120  		{
   121  			spec:  "https://test:8540",
   122  			expOk: []string{"https://test:8540"},
   123  			expFail: []string{
   124  				"https://test",                          // missing port
   125  				"http://test",                           // missing port, + wrong scheme
   126  				"http://test:8540",                      // wrong scheme
   127  				"http://test:8541", "https://test:8541", // wrong port
   128  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   129  		},
   130  		// several allowed origins
   131  		{
   132  			spec: "localhost,http://127.0.0.1",
   133  			expOk: []string{"localhost", "http://localhost", "https://localhost:8443",
   134  				"http://127.0.0.1", "http://127.0.0.1:8080"},
   135  			expFail: []string{
   136  				"https://127.0.0.1", // wrong scheme
   137  				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
   138  		},
   139  	}
   140  	for _, tc := range tests {
   141  		srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: splitAndTrim(tc.spec)})
   142  		for _, origin := range tc.expOk {
   143  			if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err != nil {
   144  				t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err)
   145  			}
   146  		}
   147  		for _, origin := range tc.expFail {
   148  			if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err == nil {
   149  				t.Errorf("spec '%v', origin '%v': expected not to allow,  got ok", tc.spec, origin)
   150  			}
   151  		}
   152  		srv.stop()
   153  	}
   154  }
   155  
   156  // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly.
   157  func TestIsWebsocket(t *testing.T) {
   158  	r, _ := http.NewRequest("GET", "/", nil)
   159  
   160  	assert.False(t, isWebsocket(r))
   161  	r.Header.Set("upgrade", "websocket")
   162  	assert.False(t, isWebsocket(r))
   163  	r.Header.Set("connection", "upgrade")
   164  	assert.True(t, isWebsocket(r))
   165  	r.Header.Set("connection", "upgrade,keep-alive")
   166  	assert.True(t, isWebsocket(r))
   167  	r.Header.Set("connection", " UPGRADE,keep-alive")
   168  	assert.True(t, isWebsocket(r))
   169  }
   170  
   171  func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer {
   172  	t.Helper()
   173  
   174  	srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts)
   175  
   176  	assert.NoError(t, srv.enableRPC(nil, conf, nil))
   177  	if ws {
   178  		assert.NoError(t, srv.enableWS(nil, wsConf, nil))
   179  	}
   180  	assert.NoError(t, srv.setListenAddr("localhost", 0))
   181  	assert.NoError(t, srv.start(nil))
   182  
   183  	return srv
   184  }
   185  
   186  func attemptWebsocketConnectionFromOrigin(t *testing.T, srv *httpServer, browserOrigin string) error {
   187  	t.Helper()
   188  	dialer := websocket.DefaultDialer
   189  	_, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{
   190  		"Content-type":          []string{"application/json"},
   191  		"Sec-WebSocket-Version": []string{"13"},
   192  		"Origin":                []string{browserOrigin},
   193  	})
   194  	return err
   195  }
   196  
   197  func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response {
   198  	t.Helper()
   199  
   200  	body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`))
   201  	req, _ := http.NewRequest("POST", "http://"+srv.listenAddr(), body)
   202  	req.Header.Set("content-type", "application/json")
   203  	if key != "" && value != "" {
   204  		req.Header.Set(key, value)
   205  	}
   206  	if host != "" {
   207  		req.Host = host
   208  	}
   209  
   210  	client := http.DefaultClient
   211  	resp, err := client.Do(req)
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	return resp
   216  }