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 }