newrelic_security 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  3. data/.github/ISSUE_TEMPLATE/enhancement.md +27 -0
  4. data/.github/actions/simplecov-report/LICENSE +22 -0
  5. data/.github/actions/simplecov-report/README.md +36 -0
  6. data/.github/actions/simplecov-report/__tests__/.keep +0 -0
  7. data/.github/actions/simplecov-report/__tests__/main.test.ts +3 -0
  8. data/.github/actions/simplecov-report/action.yml +25 -0
  9. data/.github/actions/simplecov-report/dist/index.js +10238 -0
  10. data/.github/actions/simplecov-report/dummy_coverage/.last_run.json +5 -0
  11. data/.github/actions/simplecov-report/jest.config.js +11 -0
  12. data/.github/actions/simplecov-report/package.json +51 -0
  13. data/.github/actions/simplecov-report/src/main.ts +54 -0
  14. data/.github/actions/simplecov-report/src/report.ts +28 -0
  15. data/.github/actions/simplecov-report/tsconfig.json +12 -0
  16. data/.github/workflows/pr_ci.yml +77 -0
  17. data/.github/workflows/release.yml +51 -0
  18. data/.github/workflows/repolinter.yml +31 -0
  19. data/.github/workflows/rubocop.yml +17 -0
  20. data/.github/workflows/scripts/rubygems-authenticate.py +13 -0
  21. data/.github/workflows/scripts/rubygems-publish.rb +33 -0
  22. data/.gitignore +72 -0
  23. data/.rubocop.yml +9 -0
  24. data/.rubocop_todo.yml +1414 -0
  25. data/.simplecov +16 -0
  26. data/CHANGELOG.md +69 -0
  27. data/CONTRIBUTING.md +22 -0
  28. data/Gemfile +6 -0
  29. data/Gemfile_test +58 -0
  30. data/LICENSE +43 -0
  31. data/README.md +133 -0
  32. data/README_agent.md +44 -0
  33. data/Rakefile +28 -0
  34. data/THIRD_PARTY_NOTICES.md +36 -0
  35. data/lib/newrelic_security/agent/agent.rb +109 -0
  36. data/lib/newrelic_security/agent/configuration/default_source.rb +8 -0
  37. data/lib/newrelic_security/agent/configuration/environment_source.rb +8 -0
  38. data/lib/newrelic_security/agent/configuration/manager.rb +178 -0
  39. data/lib/newrelic_security/agent/configuration/manual_source.rb +8 -0
  40. data/lib/newrelic_security/agent/configuration/server_source.rb +8 -0
  41. data/lib/newrelic_security/agent/configuration/yaml_source.rb +8 -0
  42. data/lib/newrelic_security/agent/control/app_info.rb +132 -0
  43. data/lib/newrelic_security/agent/control/application_url_mappings.rb +66 -0
  44. data/lib/newrelic_security/agent/control/collector.rb +117 -0
  45. data/lib/newrelic_security/agent/control/control_command.rb +117 -0
  46. data/lib/newrelic_security/agent/control/critical_message.rb +58 -0
  47. data/lib/newrelic_security/agent/control/event.rb +149 -0
  48. data/lib/newrelic_security/agent/control/event_counter.rb +28 -0
  49. data/lib/newrelic_security/agent/control/event_processor.rb +134 -0
  50. data/lib/newrelic_security/agent/control/event_stats.rb +26 -0
  51. data/lib/newrelic_security/agent/control/event_subscriber.rb +28 -0
  52. data/lib/newrelic_security/agent/control/exit_event.rb +38 -0
  53. data/lib/newrelic_security/agent/control/fuzz_request.rb +18 -0
  54. data/lib/newrelic_security/agent/control/grpc_context.rb +57 -0
  55. data/lib/newrelic_security/agent/control/health_check.rb +136 -0
  56. data/lib/newrelic_security/agent/control/http_context.rb +73 -0
  57. data/lib/newrelic_security/agent/control/iast_client.rb +151 -0
  58. data/lib/newrelic_security/agent/control/iast_data_transfer_request.rb +32 -0
  59. data/lib/newrelic_security/agent/control/reflected_xss.rb +258 -0
  60. data/lib/newrelic_security/agent/control/websocket_client.rb +131 -0
  61. data/lib/newrelic_security/agent/logging/init_logger.rb +91 -0
  62. data/lib/newrelic_security/agent/logging/logger.rb +92 -0
  63. data/lib/newrelic_security/agent/logging/null_logger.rb +21 -0
  64. data/lib/newrelic_security/agent/resources/cert.pem +50 -0
  65. data/lib/newrelic_security/agent/utils/agent_utils.rb +219 -0
  66. data/lib/newrelic_security/agent.rb +57 -0
  67. data/lib/newrelic_security/constants.rb +67 -0
  68. data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/chain.rb +70 -0
  69. data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/instrumentation.rb +187 -0
  70. data/lib/newrelic_security/instrumentation-security/active_record/mysql2_adapter/prepend.rb +54 -0
  71. data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/chain.rb +60 -0
  72. data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/instrumentation.rb +143 -0
  73. data/lib/newrelic_security/instrumentation-security/active_record/postgresql_adapter/prepend.rb +48 -0
  74. data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/chain.rb +72 -0
  75. data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/instrumentation.rb +187 -0
  76. data/lib/newrelic_security/instrumentation-security/active_record/sqlite3_adapter/prepend.rb +54 -0
  77. data/lib/newrelic_security/instrumentation-security/async-http/chain.rb +21 -0
  78. data/lib/newrelic_security/instrumentation-security/async-http/instrumentation.rb +46 -0
  79. data/lib/newrelic_security/instrumentation-security/async-http/prepend.rb +16 -0
  80. data/lib/newrelic_security/instrumentation-security/curb/chain.rb +26 -0
  81. data/lib/newrelic_security/instrumentation-security/curb/instrumentation.rb +52 -0
  82. data/lib/newrelic_security/instrumentation-security/curb/prepend.rb +18 -0
  83. data/lib/newrelic_security/instrumentation-security/dir/chain.rb +42 -0
  84. data/lib/newrelic_security/instrumentation-security/dir/instrumentation.rb +102 -0
  85. data/lib/newrelic_security/instrumentation-security/dir/prepend.rb +28 -0
  86. data/lib/newrelic_security/instrumentation-security/ethon/chain.rb +53 -0
  87. data/lib/newrelic_security/instrumentation-security/ethon/instrumentation.rb +122 -0
  88. data/lib/newrelic_security/instrumentation-security/ethon/prepend.rb +39 -0
  89. data/lib/newrelic_security/instrumentation-security/excon/chain.rb +23 -0
  90. data/lib/newrelic_security/instrumentation-security/excon/instrumentation.rb +44 -0
  91. data/lib/newrelic_security/instrumentation-security/excon/prepend.rb +17 -0
  92. data/lib/newrelic_security/instrumentation-security/file/chain.rb +34 -0
  93. data/lib/newrelic_security/instrumentation-security/file/instrumentation.rb +62 -0
  94. data/lib/newrelic_security/instrumentation-security/file/prepend.rb +22 -0
  95. data/lib/newrelic_security/instrumentation-security/grape/chain.rb +42 -0
  96. data/lib/newrelic_security/instrumentation-security/grape/instrumentation.rb +56 -0
  97. data/lib/newrelic_security/instrumentation-security/grape/prepend.rb +30 -0
  98. data/lib/newrelic_security/instrumentation-security/grpc/client/chain.rb +47 -0
  99. data/lib/newrelic_security/instrumentation-security/grpc/client/instrumentation.rb +37 -0
  100. data/lib/newrelic_security/instrumentation-security/grpc/client/prepend.rb +36 -0
  101. data/lib/newrelic_security/instrumentation-security/grpc/server/chain.rb +62 -0
  102. data/lib/newrelic_security/instrumentation-security/grpc/server/instrumentation.rb +65 -0
  103. data/lib/newrelic_security/instrumentation-security/grpc/server/prepend.rb +46 -0
  104. data/lib/newrelic_security/instrumentation-security/httpclient/chain.rb +30 -0
  105. data/lib/newrelic_security/instrumentation-security/httpclient/instrumentation.rb +82 -0
  106. data/lib/newrelic_security/instrumentation-security/httpclient/prepend.rb +22 -0
  107. data/lib/newrelic_security/instrumentation-security/httprb/chain.rb +21 -0
  108. data/lib/newrelic_security/instrumentation-security/httprb/instrumentation.rb +44 -0
  109. data/lib/newrelic_security/instrumentation-security/httprb/prepend.rb +16 -0
  110. data/lib/newrelic_security/instrumentation-security/httpx/chain.rb +23 -0
  111. data/lib/newrelic_security/instrumentation-security/httpx/instrumentation.rb +51 -0
  112. data/lib/newrelic_security/instrumentation-security/httpx/prepend.rb +18 -0
  113. data/lib/newrelic_security/instrumentation-security/instrumentation_loader.rb +50 -0
  114. data/lib/newrelic_security/instrumentation-security/instrumentation_utils.rb +165 -0
  115. data/lib/newrelic_security/instrumentation-security/io/chain.rb +113 -0
  116. data/lib/newrelic_security/instrumentation-security/io/instrumentation.rb +300 -0
  117. data/lib/newrelic_security/instrumentation-security/io/prepend.rb +86 -0
  118. data/lib/newrelic_security/instrumentation-security/kernel/chain.rb +65 -0
  119. data/lib/newrelic_security/instrumentation-security/kernel/instrumentation.rb +167 -0
  120. data/lib/newrelic_security/instrumentation-security/kernel/prepend.rb +50 -0
  121. data/lib/newrelic_security/instrumentation-security/mongo/chain.rb +106 -0
  122. data/lib/newrelic_security/instrumentation-security/mongo/instrumentation.rb +273 -0
  123. data/lib/newrelic_security/instrumentation-security/mongo/prepend.rb +77 -0
  124. data/lib/newrelic_security/instrumentation-security/mysql2/chain.rb +53 -0
  125. data/lib/newrelic_security/instrumentation-security/mysql2/instrumentation.rb +84 -0
  126. data/lib/newrelic_security/instrumentation-security/mysql2/prepend.rb +37 -0
  127. data/lib/newrelic_security/instrumentation-security/net_http/chain.rb +21 -0
  128. data/lib/newrelic_security/instrumentation-security/net_http/instrumentation.rb +60 -0
  129. data/lib/newrelic_security/instrumentation-security/net_http/prepend.rb +16 -0
  130. data/lib/newrelic_security/instrumentation-security/net_ldap/chain.rb +21 -0
  131. data/lib/newrelic_security/instrumentation-security/net_ldap/instrumentation.rb +42 -0
  132. data/lib/newrelic_security/instrumentation-security/net_ldap/prepend.rb +16 -0
  133. data/lib/newrelic_security/instrumentation-security/nokogiri/chain.rb +46 -0
  134. data/lib/newrelic_security/instrumentation-security/nokogiri/instrumentation.rb +36 -0
  135. data/lib/newrelic_security/instrumentation-security/nokogiri/prepend.rb +31 -0
  136. data/lib/newrelic_security/instrumentation-security/padrino/chain.rb +26 -0
  137. data/lib/newrelic_security/instrumentation-security/padrino/instrumentation.rb +42 -0
  138. data/lib/newrelic_security/instrumentation-security/padrino/prepend.rb +20 -0
  139. data/lib/newrelic_security/instrumentation-security/patron/chain.rb +23 -0
  140. data/lib/newrelic_security/instrumentation-security/patron/instrumentation.rb +50 -0
  141. data/lib/newrelic_security/instrumentation-security/patron/prepend.rb +18 -0
  142. data/lib/newrelic_security/instrumentation-security/pg/chain.rb +49 -0
  143. data/lib/newrelic_security/instrumentation-security/pg/instrumentation.rb +102 -0
  144. data/lib/newrelic_security/instrumentation-security/pg/prepend.rb +36 -0
  145. data/lib/newrelic_security/instrumentation-security/pty/chain.rb +31 -0
  146. data/lib/newrelic_security/instrumentation-security/pty/instrumentation.rb +52 -0
  147. data/lib/newrelic_security/instrumentation-security/pty/prepend.rb +22 -0
  148. data/lib/newrelic_security/instrumentation-security/rails/chain.rb +46 -0
  149. data/lib/newrelic_security/instrumentation-security/rails/instrumentation.rb +67 -0
  150. data/lib/newrelic_security/instrumentation-security/rails/prepend.rb +33 -0
  151. data/lib/newrelic_security/instrumentation-security/roda/chain.rb +22 -0
  152. data/lib/newrelic_security/instrumentation-security/roda/instrumentation.rb +41 -0
  153. data/lib/newrelic_security/instrumentation-security/roda/prepend.rb +16 -0
  154. data/lib/newrelic_security/instrumentation-security/sinatra/chain.rb +29 -0
  155. data/lib/newrelic_security/instrumentation-security/sinatra/instrumentation.rb +49 -0
  156. data/lib/newrelic_security/instrumentation-security/sinatra/prepend.rb +21 -0
  157. data/lib/newrelic_security/instrumentation-security/sqlite3/chain.rb +79 -0
  158. data/lib/newrelic_security/instrumentation-security/sqlite3/instrumentation.rb +164 -0
  159. data/lib/newrelic_security/instrumentation-security/sqlite3/prepend.rb +56 -0
  160. data/lib/newrelic_security/newrelic-security-api/api.rb +72 -0
  161. data/lib/newrelic_security/version.rb +5 -0
  162. data/lib/newrelic_security/websocket-client-simple/client.rb +128 -0
  163. data/lib/newrelic_security/websocket-client-simple/event_emitter.rb +72 -0
  164. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/error.rb +129 -0
  165. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/exception_handler.rb +32 -0
  166. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/base.rb +62 -0
  167. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/data.rb +49 -0
  168. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/base.rb +41 -0
  169. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler03.rb +224 -0
  170. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler04.rb +18 -0
  171. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler05.rb +15 -0
  172. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler07.rb +78 -0
  173. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler/handler75.rb +78 -0
  174. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/handler.rb +15 -0
  175. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming/client.rb +17 -0
  176. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming/server.rb +17 -0
  177. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/incoming.rb +52 -0
  178. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing/client.rb +17 -0
  179. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing/server.rb +17 -0
  180. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame/outgoing.rb +35 -0
  181. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/frame.rb +11 -0
  182. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/base.rb +142 -0
  183. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/client.rb +130 -0
  184. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/base.rb +49 -0
  185. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client.rb +32 -0
  186. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client01.rb +20 -0
  187. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client04.rb +63 -0
  188. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client11.rb +22 -0
  189. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client75.rb +39 -0
  190. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/client76.rb +105 -0
  191. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server.rb +10 -0
  192. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server04.rb +56 -0
  193. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server75.rb +40 -0
  194. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler/server76.rb +75 -0
  195. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/handler.rb +21 -0
  196. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake/server.rb +179 -0
  197. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/handshake.rb +10 -0
  198. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/nice_inspect.rb +12 -0
  199. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket/version.rb +5 -0
  200. data/lib/newrelic_security/websocket-client-simple/websocket-ruby/lib/websocket.rb +50 -0
  201. data/lib/newrelic_security.rb +6 -0
  202. data/lib/tasks/all.rb +8 -0
  203. data/lib/tasks/coverage_report.rake +27 -0
  204. data/newrelic_security.gemspec +51 -0
  205. metadata +342 -0
@@ -0,0 +1,78 @@
1
+ # encoding: binary
2
+ # frozen_string_literal: true
3
+
4
+ module NewRelic::Security::WebSocket
5
+ module Frame
6
+ module Handler
7
+ class Handler75 < Base
8
+ # @see NewRelic::Security::WebSocket::Frame::Base#supported_frames
9
+ def supported_frames
10
+ %i[text close]
11
+ end
12
+
13
+ # @see NewRelic::Security::WebSocket::Frame::Handler::Base#encode_frame
14
+ def encode_frame
15
+ case @frame.type
16
+ when :close then "\xff\x00"
17
+ when :text then
18
+ ary = ["\x00", @frame.data, "\xff"]
19
+ ary.map { |s| s.encode('UTF-8', 'UTF-8', invalid: :replace) }
20
+ ary.join
21
+ else raise NewRelic::Security::WebSocket::Error::Frame::UnknownFrameType
22
+ end
23
+ end
24
+
25
+ # @see NewRelic::Security::WebSocket::Frame::Handler::Base#decode_frame
26
+ def decode_frame
27
+ return if @frame.data.size.zero?
28
+
29
+ pointer = 0
30
+ frame_type = @frame.data.getbyte(pointer)
31
+ pointer += 1
32
+
33
+ if (frame_type & 0x80) == 0x80
34
+ # If the high-order bit of the /frame type/ byte is set
35
+ length = 0
36
+
37
+ loop do
38
+ return unless @frame.data.getbyte(pointer)
39
+ b = @frame.data.getbyte(pointer)
40
+ pointer += 1
41
+ b_v = b & 0x7F
42
+ length = length * 128 + b_v
43
+ break unless (b & 0x80) == 0x80
44
+ end
45
+
46
+ raise NewRelic::Security::WebSocket::Error::Frame::TooLong if length > ::NewRelic::Security::WebSocket.max_frame_size
47
+
48
+ unless @frame.data.getbyte(pointer + length - 1).nil?
49
+ # Straight from spec - I'm sure this isn't crazy...
50
+ # 6. Read /length/ bytes.
51
+ # 7. Discard the read bytes.
52
+ @frame.instance_variable_set '@data', @frame.data[(pointer + length)..-1]
53
+
54
+ # If the /frame type/ is 0xFF and the /length/ was 0, then close
55
+ if length.zero?
56
+ @frame.class.new(version: @frame.version, type: :close, decoded: true)
57
+ end
58
+ end
59
+ else
60
+ # If the high-order bit of the /frame type/ byte is _not_ set
61
+
62
+ raise NewRelic::Security::WebSocket::Error::Frame::Invalid if @frame.data.getbyte(0) != 0x00
63
+
64
+ # Addition to the spec to protect against malicious requests
65
+ raise NewRelic::Security::WebSocket::Error::Frame::TooLong if @frame.data.size > ::NewRelic::Security::WebSocket.max_frame_size
66
+
67
+ msg = @frame.data.slice!(/\A\x00[^\xff]*\xff/)
68
+ if msg
69
+ msg.gsub!(/\A\x00|\xff\z/, '')
70
+ msg.force_encoding('UTF-8')
71
+ @frame.class.new(version: @frame.version, type: :text, data: msg, decoded: true)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ module Handler
6
+ autoload :Base, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/base"
7
+
8
+ autoload :Handler03, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler03"
9
+ autoload :Handler04, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler04"
10
+ autoload :Handler05, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler05"
11
+ autoload :Handler07, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler07"
12
+ autoload :Handler75, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler/handler75"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Incoming
6
+ class Client < Incoming
7
+ def incoming_masking?
8
+ false
9
+ end
10
+
11
+ def outgoing_masking?
12
+ @handler.masking?
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Incoming
6
+ class Server < Incoming
7
+ def incoming_masking?
8
+ @handler.masking?
9
+ end
10
+
11
+ def outgoing_masking?
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ # Construct or parse incoming WebSocket Frame.
6
+ # @note You should NEVER use this class directly - use Client or Server subclasses instead, as they contain additional frame options(i.e. Client-side masking in draft 04)
7
+ #
8
+ # @example
9
+ # frame = NewRelic::Security::WebSocket::Frame::Incoming::Server.new(version: @handshake.version)
10
+ # frame << "\x81\x05\x48\x65\x6c\x6c\x6f\x81\x06\x77\x6f\x72\x6c\x64\x21"
11
+ # frame.next # "Hello"
12
+ # frame.next # "world!""
13
+ class Incoming < Base
14
+ autoload :Client, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/incoming/client"
15
+ autoload :Server, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/incoming/server"
16
+
17
+ def initialize(args = {})
18
+ @decoded = args[:decoded] || false
19
+ super
20
+ end
21
+
22
+ # If data is still encoded after receiving then this is false. After calling "next" you will receive
23
+ # another instance of incoming frame, but with data decoded - this function will return true and
24
+ # to_s will return frame content instead of raw data.
25
+ # @return [Boolean] If frame already decoded?
26
+ def decoded?
27
+ @decoded
28
+ end
29
+
30
+ # Add provided string as raw incoming frame.
31
+ # @param data [String] Raw frame
32
+ def <<(data)
33
+ @data << data
34
+ end
35
+
36
+ # Return next complete frame.
37
+ # This function will merge together splitted frames and return as combined content.
38
+ # Check #error if nil received to check for eventual parsing errors
39
+ # @return [NewRelic::Security::WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available.
40
+ def next
41
+ @handler.decode_frame unless decoded?
42
+ end
43
+ rescue_method :next
44
+
45
+ # If decoded then this will return frame content. Otherwise it will return raw frame.
46
+ # @return [String] Data of frame
47
+ def to_s
48
+ @data
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Outgoing
6
+ class Client < Outgoing
7
+ def incoming_masking?
8
+ false
9
+ end
10
+
11
+ def outgoing_masking?
12
+ @handler.masking?
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ class Outgoing
6
+ class Server < Outgoing
7
+ def incoming_masking?
8
+ @handler.masking?
9
+ end
10
+
11
+ def outgoing_masking?
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ # Construct or parse outgoing WebSocket Frame.
6
+ # @note You should NEVER use this class directly - use Client or Server subclasses instead, as they contain additional frame options(i.e. Client-side masking in draft 04)
7
+ #
8
+ # @example
9
+ # frame = NewRelic::Security::WebSocket::Frame::Outgoing::Server.new(version: @handshake.version, data: "Hello", type: :text)
10
+ # frame.to_s # "\x81\x05\x48\x65\x6c\x6c\x6f"
11
+ class Outgoing < Base
12
+ autoload :Client, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/outgoing/client"
13
+ autoload :Server, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/outgoing/server"
14
+
15
+ # Is selected type supported by current draft version?
16
+ # @return [Boolean] true if frame type is supported
17
+ def supported?
18
+ support_type?
19
+ end
20
+
21
+ # Should current frame be sent? Exclude empty frames etc.
22
+ # @return [Boolean] true if frame should be sent
23
+ def require_sending?
24
+ !error?
25
+ end
26
+
27
+ # Return raw frame formatted for sending.
28
+ def to_s
29
+ raise NewRelic::Security::WebSocket::Error::Frame::UnknownFrameType unless supported?
30
+ @handler.encode_frame
31
+ end
32
+ rescue_method :to_s
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Frame
5
+ autoload :Base, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/base"
6
+ autoload :Data, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/data"
7
+ autoload :Handler, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/handler"
8
+ autoload :Incoming, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/incoming"
9
+ autoload :Outgoing, "#{NewRelic::Security::WebSocket::ROOT}/websocket/frame/outgoing"
10
+ end
11
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ # @abstract Subclass and override to implement custom handshakes
6
+ class Base
7
+ include ExceptionHandler
8
+ include NiceInspect
9
+
10
+ attr_reader :host, :path, :query,
11
+ :state, :version, :secure,
12
+ :headers, :protocols
13
+
14
+ # Initialize new WebSocket Handshake and set it's state to :new
15
+ def initialize(args = {})
16
+ args.each do |k, v|
17
+ value = begin
18
+ v.dup
19
+ rescue TypeError
20
+ v
21
+ end
22
+ instance_variable_set("@#{k}", value)
23
+ end
24
+
25
+ @state = :new
26
+ @handler = nil
27
+
28
+ @data = String.new('')
29
+ @headers ||= {}
30
+ @protocols ||= []
31
+ end
32
+
33
+ # @abstract Add data to handshake
34
+ def <<(data)
35
+ @data << data
36
+ end
37
+
38
+ # Return textual representation of handshake request or response
39
+ # @return [String] text of response
40
+ def to_s
41
+ @handler ? @handler.to_s : ''
42
+ end
43
+ rescue_method :to_s, return: ''
44
+
45
+ # Is parsing of data finished?
46
+ # @return [Boolena] True if request was completely parsed or error occured. False otherwise
47
+ def finished?
48
+ @state == :finished || @state == :error
49
+ end
50
+
51
+ # Is parsed data valid?
52
+ # @return [Boolean] False if some errors occured. Reason for error could be found in error method
53
+ def valid?
54
+ finished? && @error.nil? && @handler && @handler.valid?
55
+ end
56
+ rescue_method :valid?, return: false
57
+
58
+ # @abstract Should send data after parsing is finished?
59
+ def should_respond?
60
+ raise NotImplementedError
61
+ end
62
+
63
+ # Data left from parsing. Sometimes data that doesn't belong to handshake are added - use this method to retrieve them.
64
+ # @return [String] String if some data are available. Nil otherwise
65
+ def leftovers
66
+ (@leftovers.to_s.split("\n", reserved_leftover_lines + 1)[reserved_leftover_lines] || '').strip
67
+ end
68
+
69
+ # Return default port for protocol (80 for ws, 443 for wss)
70
+ def default_port
71
+ secure ? 443 : 80
72
+ end
73
+
74
+ # Check if provided port is a default one
75
+ def default_port?
76
+ port == default_port
77
+ end
78
+
79
+ def port
80
+ @port || default_port
81
+ end
82
+
83
+ # URI of request.
84
+ # @return [String] Full URI with protocol
85
+ # @example
86
+ # @handshake.uri #=> "ws://example.com/path?query=true"
87
+ def uri
88
+ uri = String.new(secure ? 'wss://' : 'ws://')
89
+ uri << host
90
+ uri << ":#{port}" unless default_port?
91
+ uri << path
92
+ uri << "?#{query}" if query
93
+ uri
94
+ end
95
+
96
+ private
97
+
98
+ # Number of lines after header that should be handled as belonging to handshake. Any data after those lines will be handled as leftovers.
99
+ # @return [Integer] Number of lines
100
+ def reserved_leftover_lines
101
+ 0
102
+ end
103
+
104
+ # Changes state to error and sets error message
105
+ # @param [String] message Error message to set
106
+ def error=(message)
107
+ @state = :error
108
+ super
109
+ end
110
+
111
+ HEADER = /^([^:]+):\s*(.+)$/
112
+
113
+ # Parse data imported to handshake and sets state to finished if necessary.
114
+ # @return [Boolean] True if finished parsing. False if not all data received yet.
115
+ def parse_data
116
+ header, @leftovers = @data.split("\r\n\r\n", 2)
117
+ return false unless @leftovers # The whole header has not been received yet.
118
+
119
+ lines = header.split("\r\n")
120
+
121
+ first_line = lines.shift
122
+ parse_first_line(first_line)
123
+
124
+ lines.each do |line|
125
+ h = HEADER.match(line)
126
+ next unless h # Skip any invalid headers
127
+ key = h[1].strip.downcase
128
+ val = h[2].strip
129
+ # If the header is already set and refers to the websocket protocol, append the new value
130
+ if @headers.key?(key) && key =~ /^(sec-)?websocket-protocol$/
131
+ @headers[key] << ", #{val}"
132
+ else
133
+ @headers[key] = val
134
+ end
135
+ end
136
+
137
+ @state = :finished
138
+ true
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module NewRelic::Security::WebSocket
6
+ module Handshake
7
+ # Construct or parse a client WebSocket handshake.
8
+ #
9
+ # @example
10
+ # @handshake = NewRelic::Security::WebSocket::Handshake::Client.new(url: 'ws://example.com')
11
+ #
12
+ # # Create request
13
+ # @handshake.to_s # GET /demo HTTP/1.1
14
+ # # Upgrade: websocket
15
+ # # Connection: Upgrade
16
+ # # Host: example.com
17
+ # # Origin: http://example.com
18
+ # # Sec-WebSocket-Version: 13
19
+ # # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
20
+ #
21
+ # # Parse server response
22
+ # @handshake << <<EOF
23
+ # HTTP/1.1 101 Switching Protocols\r
24
+ # Upgrade: websocket\r
25
+ # Connection: Upgrade\r
26
+ # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r
27
+ # \r
28
+ # EOF
29
+ #
30
+ # # All data received?
31
+ # @handshake.finished?
32
+ #
33
+ # # No parsing errors?
34
+ # @handshake.valid?
35
+ #
36
+ class Client < Base
37
+ attr_reader :origin, :headers
38
+
39
+ # Initialize new WebSocket Client
40
+ #
41
+ # @param [Hash] args Arguments for client
42
+ #
43
+ # @option args [String] :host Host of request. Required if no :url param was provided.
44
+ # @option args [String] :origin Origin of request. Optional, should be used mostly by browsers. Default: nil
45
+ # @option args [String] :path Path of request. Should start with '/'. Default: '/'
46
+ # @option args [Integer] :port Port of request. Default: nil
47
+ # @option args [String] :query. Query for request. Should be in format "aaa=bbb&ccc=ddd"
48
+ # @option args [Boolean] :secure Defines protocol to use. If true then wss://, otherwise ws://. This option will not change default port - it should be handled by programmer.
49
+ # @option args [String] :url URL of request. Must by in format like ws://example.com/path?query=true. Every part of this url will be overriden by more specific arguments.
50
+ # @option args [String] :uri Alias to :url
51
+ # @option args [Array<String>] :protocols An array of supported sub-protocols
52
+ # @option args [Integer] :version Version of WebSocket to use. Default: 13 (this is version from RFC)
53
+ # @option args [Hash] :headers HTTP headers to use in the handshake
54
+ #
55
+ # @example
56
+ # Websocket::Handshake::Client.new(url: "ws://example.com/path?query=true")
57
+ def initialize(args = {})
58
+ super
59
+
60
+ if @url || @uri
61
+ uri = URI.parse(@url || @uri)
62
+ @secure ||= (uri.scheme == 'wss')
63
+ @host ||= uri.host
64
+ @port ||= uri.port || default_port
65
+ @path ||= uri.path
66
+ @query ||= uri.query
67
+ end
68
+
69
+ @path = '/' if @path.nil? || @path.empty?
70
+ @version ||= DEFAULT_VERSION
71
+
72
+ raise NewRelic::Security::WebSocket::Error::Handshake::NoHostProvided unless @host
73
+
74
+ include_version
75
+ end
76
+ rescue_method :initialize
77
+
78
+ # Add text of response from Server. This method will parse content immediately and update state and error(if neccessary)
79
+ #
80
+ # @param [String] data Data to add
81
+ #
82
+ # @example
83
+ # @handshake << <<EOF
84
+ # HTTP/1.1 101 Switching Protocols
85
+ # Upgrade: websocket
86
+ # Connection: Upgrade
87
+ # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
88
+ #
89
+ # EOF
90
+ def <<(data)
91
+ super
92
+ parse_data
93
+ end
94
+ rescue_method :<<
95
+
96
+ # Should send content to server after finished parsing?
97
+ # @return [Boolean] false
98
+ def should_respond?
99
+ false
100
+ end
101
+
102
+ private
103
+
104
+ # Include set of methods for selected protocol version
105
+ # @return [Boolean] false if protocol number is unknown, otherwise true
106
+ def include_version
107
+ @handler = case @version
108
+ when 75 then Handler::Client75.new(self)
109
+ when 76, 0 then Handler::Client76.new(self)
110
+ when 1..3 then Handler::Client01.new(self)
111
+ when 4..10 then Handler::Client04.new(self)
112
+ when 11..17 then Handler::Client11.new(self)
113
+ else raise NewRelic::Security::WebSocket::Error::Handshake::UnknownVersion
114
+ end
115
+ end
116
+
117
+ FIRST_LINE = %r{^HTTP\/1\.1 (\d{3})[\w\s]*$}
118
+
119
+ # Parse first line of Server response.
120
+ # @param [String] line Line to parse
121
+ # @return [Boolean] True if parsed correctly. False otherwise
122
+ def parse_first_line(line)
123
+ line_parts = line.match(FIRST_LINE)
124
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidHeader unless line_parts
125
+ status = line_parts[1]
126
+ raise NewRelic::Security::WebSocket::Error::Handshake::InvalidStatusCode unless status == '101'
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ # This class and it's descendants are included in client or server handshake in order to extend basic functionality
7
+ class Base
8
+ def initialize(handshake)
9
+ @handshake = handshake
10
+ end
11
+
12
+ # @see NewRelic::Security::WebSocket::Handshake::Base#to_s
13
+ def to_s
14
+ result = [header_line]
15
+ handshake_keys.each do |key|
16
+ result << key.join(': ')
17
+ end
18
+ result << ''
19
+ result << finishing_line
20
+ result.join("\r\n")
21
+ end
22
+
23
+ def valid?
24
+ true
25
+ end
26
+
27
+ private
28
+
29
+ # Set first line of text representation according to specification.
30
+ # @return [String] First line of HTTP header
31
+ def header_line
32
+ ''
33
+ end
34
+
35
+ # Set handshake headers. Provided as array because some protocol version require specific order of fields.
36
+ # @return [Array] List of headers as arrays [key, value]
37
+ def handshake_keys
38
+ []
39
+ end
40
+
41
+ # Set data to send after headers. In most cases it will be blank data.
42
+ # @return [String] data
43
+ def finishing_line
44
+ ''
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NewRelic::Security::WebSocket
4
+ module Handshake
5
+ module Handler
6
+ class Client < Base
7
+ private
8
+
9
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_line
10
+ def header_line
11
+ path = @handshake.path
12
+ path += '?' + @handshake.query if @handshake.query
13
+ "GET #{path} HTTP/1.1"
14
+ end
15
+
16
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#header_handshake_keys
17
+ def handshake_keys
18
+ super + @handshake.headers.to_a
19
+ end
20
+
21
+ # Verify if received header matches with one of the sent ones
22
+ # @return [Boolean] True if matching. False otherwise(appropriate error is set)
23
+ def verify_protocol
24
+ return true if supported_protocols.empty?
25
+ protos = provided_protocols & supported_protocols
26
+ raise NewRelic::Security::WebSocket::Error::Handshake::UnsupportedProtocol if protos.empty?
27
+ true
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module NewRelic::Security::WebSocket
6
+ module Handshake
7
+ module Handler
8
+ class Client01 < Client76
9
+ private
10
+
11
+ # @see NewRelic::Security::WebSocket::Handshake::Handler::Base#handshake_keys
12
+ def handshake_keys
13
+ keys = super
14
+ keys << ['Sec-WebSocket-Draft', @handshake.version]
15
+ keys
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end