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 }