summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/em-websocket-0.5.3/lib/em-websocket/connection.rb
blob: a55881dfed1305864ebf575ae7bcc804fe0712cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
module EventMachine
  module WebSocket
    class Connection < EventMachine::Connection
      include Debugger

      attr_writer :max_frame_size

      # define WebSocket callbacks
      def onopen(&blk);     @onopen = blk;    end
      def onclose(&blk);    @onclose = blk;   end
      def onerror(&blk);    @onerror = blk;   end
      def onmessage(&blk);  @onmessage = blk; end
      def onbinary(&blk);   @onbinary = blk; end
      def onping(&blk);     @onping = blk;    end
      def onpong(&blk);     @onpong = blk;    end

      def trigger_on_message(msg)
        @onmessage.call(msg) if defined? @onmessage
      end
      def trigger_on_binary(msg)
        @onbinary.call(msg) if defined? @onbinary
      end
      def trigger_on_open(handshake)
        @onopen.call(handshake) if defined? @onopen
      end
      def trigger_on_close(event = {})
        @onclose.call(event) if defined? @onclose
      end
      def trigger_on_ping(data)
        @onping.call(data) if defined? @onping
      end
      def trigger_on_pong(data)
        @onpong.call(data) if defined? @onpong
      end
      def trigger_on_error(reason)
        return false unless defined? @onerror
        @onerror.call(reason)
        true
      end

      def initialize(options)
        @options = options
        @debug = options[:debug] || false
        @secure = options[:secure] || false
        @secure_proxy = options[:secure_proxy] || false
        @tls_options = options[:tls_options] || {}
        @close_timeout = options[:close_timeout]
        @outbound_limit = options[:outbound_limit] || 0

        @handler = nil

        debug [:initialize]
      end

      # Use this method to close the websocket connection cleanly
      # This sends a close frame and waits for acknowlegement before closing
      # the connection
      def close(code = nil, body = nil)
        if code && !acceptable_close_code?(code)
          raise "Application code may only use codes from 1000, 3000-4999"
        end

        close_websocket_private(code, body)
      end

      # Deprecated, to be removed in version 0.6
      alias :close_websocket :close

      def post_init
        start_tls(@tls_options) if @secure
      end

      def receive_data(data)
        debug [:receive_data, data]

        if @handler
          @handler.receive_data(data)
        else
          dispatch(data)
        end
      rescue => e
        debug [:error, e]

        # There is no code defined for application errors, so use 3000
        # (which is reserved for frameworks)
        close_websocket_private(3000, "Application error")

        # These are application errors - raise unless onerror defined
        trigger_on_error(e) || raise(e)
      end

      def send_data(data)
        if @outbound_limit > 0 &&
            get_outbound_data_size + data.bytesize > @outbound_limit
          abort(:outbound_limit_reached)
          return 0
        end

        super(data)
      end

      def unbind
        debug [:unbind, :connection]

        @handler.unbind if @handler
      rescue => e
        debug [:error, e]
        # These are application errors - raise unless onerror defined
        trigger_on_error(e) || raise(e)
      end

      def dispatch(data)
        if data.match(%r|^GET /healthcheck|)
          send_healthcheck_response
        elsif data.match(/\A<policy-file-request\s*\/>/)
          send_flash_cross_domain_file
        else
          @handshake ||= begin
            handshake = Handshake.new(@secure || @secure_proxy)

            handshake.callback { |upgrade_response, handler_klass|
              debug [:accepting_ws_version, handshake.protocol_version]
              debug [:upgrade_response, upgrade_response]
              self.send_data(upgrade_response)
              @handler = handler_klass.new(self, @debug)
              @handshake = nil
              trigger_on_open(handshake)
            }

            handshake.errback { |e|
              debug [:error, e]
              trigger_on_error(e)
              # Handshake errors require the connection to be aborted
              abort(:handshake_error)
            }

            handshake
          end

          @handshake.receive_data(data)
        end
      end

      def send_healthcheck_response
        debug [:healthcheck, 'OK']

        healthcheck_res = ["HTTP/1.1 200 OK"]
        healthcheck_res << "Content-Type: text/plain"
        healthcheck_res << "Content-Length: 2"

        healthcheck_res = healthcheck_res.join("\r\n") + "\r\n\r\nOK"

        send_data healthcheck_res

        # handle the healthcheck request transparently
        # no need to notify the user about this connection
        @onclose = nil
        close_connection_after_writing
      end

      def send_flash_cross_domain_file
        file =  '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
        debug [:cross_domain, file]
        send_data file

        # handle the cross-domain request transparently
        # no need to notify the user about this connection
        @onclose = nil
        close_connection_after_writing
      end

      # Cache encodings since it's moderately expensive to look them up each time
      ENCODING_SUPPORTED = "string".respond_to?(:force_encoding)
      UTF8 = Encoding.find("UTF-8") if ENCODING_SUPPORTED
      BINARY = Encoding.find("BINARY") if ENCODING_SUPPORTED

      # Send a WebSocket text frame.
      #
      # A WebSocketError may be raised if the connection is in an opening or a
      # closing state, or if the passed in data is not valid UTF-8
      #
      def send_text(data)
        # If we're using Ruby 1.9, be pedantic about encodings
        if ENCODING_SUPPORTED
          # Also accept ascii only data in other encodings for convenience
          unless (data.encoding == UTF8 && data.valid_encoding?) || data.ascii_only?
            raise WebSocketError, "Data sent to WebSocket must be valid UTF-8 but was #{data.encoding} (valid: #{data.valid_encoding?})"
          end
          # This labels the encoding as binary so that it can be combined with
          # the BINARY framing
          data.force_encoding(BINARY)
        else
          # TODO: Check that data is valid UTF-8
        end

        if @handler
          @handler.send_text_frame(data)
        else
          raise WebSocketError, "Cannot send data before onopen callback"
        end

        # Revert data back to the original encoding (which we assume is UTF-8)
        # Doing this to avoid duping the string - there may be a better way
        data.force_encoding(UTF8) if ENCODING_SUPPORTED
        return nil
      end

      alias :send :send_text

      # Send a WebSocket binary frame.
      #
      def send_binary(data)
        if @handler
          @handler.send_frame(:binary, data)
        else
          raise WebSocketError, "Cannot send binary before onopen callback"
        end
      end

      # Send a ping to the client. The client must respond with a pong.
      #
      # In the case that the client is running a WebSocket draft < 01, false
      # is returned since ping & pong are not supported
      #
      def ping(body = '')
        if @handler
          @handler.pingable? ? @handler.send_frame(:ping, body) && true : false
        else
          raise WebSocketError, "Cannot ping before onopen callback"
        end
      end

      # Send an unsolicited pong message, as allowed by the protocol. The
      # client is not expected to respond to this message.
      #
      # em-websocket automatically takes care of sending pong replies to
      # incoming ping messages, as the protocol demands.
      #
      def pong(body = '')
        if @handler
          @handler.pingable? ? @handler.send_frame(:pong, body) && true : false
        else
          raise WebSocketError, "Cannot ping before onopen callback"
        end
      end

      # Test whether the connection is pingable (i.e. the WebSocket draft in
      # use is >= 01)
      def pingable?
        if @handler
          @handler.pingable?
        else
          raise WebSocketError, "Cannot test whether pingable before onopen callback"
        end
      end

      def supports_close_codes?
        if @handler
          @handler.supports_close_codes?
        else
          raise WebSocketError, "Cannot test before onopen callback"
        end
      end

      def state
        @handler ? @handler.state : :handshake
      end

      # Returns the IP address for the remote peer
      def remote_ip
        get_peername[2,6].unpack('nC4')[1..4].join('.')
      end

      # Returns the maximum frame size which this connection is configured to
      # accept. This can be set globally or on a per connection basis, and
      # defaults to a value of 10MB if not set.
      #
      # The behaviour when a too large frame is received varies by protocol,
      # but in the newest protocols the connection will be closed with the
      # correct close code (1009) immediately after receiving the frame header
      #
      def max_frame_size
        defined?(@max_frame_size) ? @max_frame_size : WebSocket.max_frame_size
      end

      def close_timeout
        @close_timeout || WebSocket.close_timeout
      end

      private

      # As definited in draft 06 7.2.2, some failures require that the server
      # abort the websocket connection rather than close cleanly
      def abort(reason)
        debug [:abort, reason]
        close_connection
      end

      def close_websocket_private(code, body)
        if @handler
          debug [:closing, code]
          @handler.close_websocket(code, body)
        else
          # The handshake hasn't completed - should be safe to terminate
          abort(:handshake_incomplete)
        end
      end

      # Allow applications to close with 1000, 1003, 1008, 1011, 3xxx or 4xxx.
      #
      # em-websocket uses a few other codes internally which should not be
      # used by applications
      #
      # Browsers generally allow connections to be closed with code 1000,
      # 3xxx, and 4xxx. em-websocket allows closing with a few other codes
      # which seem reasonable (for discussion see
      # https://github.com/igrigorik/em-websocket/issues/98)
      #
      # Usage from the rfc:
      #
      # 1000 indicates a normal closure
      #
      # 1003 indicates that an endpoint is terminating the connection
      # because it has received a type of data it cannot accept
      #
      # 1008 indicates that an endpoint is terminating the connection because
      # it has received a message that violates its policy
      #
      # 1011 indicates that a server is terminating the connection because it
      # encountered an unexpected condition that prevented it from fulfilling
      # the request
      #
      # Status codes in the range 3000-3999 are reserved for use by libraries,
      # frameworks, and applications
      #
      # Status codes in the range 4000-4999 are reserved for private use and
      # thus can't be registered
      #
      def acceptable_close_code?(code)
        case code
        when 1000, 1003, 1008, 1011, (3000..4999)
          true
        else
          false
        end
      end
    end
  end
end