rpcstack_test.go 7.2 KB

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