websocket-driver 0.6.4 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4cf0e0cdac07ab4b3dd087f8d74b36613f3e9090
4
- data.tar.gz: 3282dd2b2f10817324192f29966b14be4611a8b9
2
+ SHA256:
3
+ metadata.gz: 11392831462e0bee833bdb3ac3f6f09de232e2e8cc7a19799ce0ebb8e7238ea1
4
+ data.tar.gz: 3b4555cc645e77d0166ff9657dc23c306bf48296e94a1520de703f9ab72120f1
5
5
  SHA512:
6
- metadata.gz: b99062bf639a33cf257613632b7f5bf9f08d514c4c7d13de6ee022e62fb749632318c534087b5800b23e958b53389b96d9efb78ca324b3a17089ab13e3817736
7
- data.tar.gz: 51dff6847b37deb3e0451dfbbfa220ab93fb149215cae124ef4fb94b52974c891c175bbab537ac3d6cc9c6e851e95fe6db805286639bb72051e6b57ca5cd01ad
6
+ metadata.gz: 378aaf1b46a663dccd4dbc8ec25e515e9acb7b2c760b7b62c199185e11466d9c2fd6482b17fb51157a03030e0c9065c7f6a6201c61a7f45fc940365cce036626
7
+ data.tar.gz: 783523c78a09ced14fe3e4be497b7fb5e6f22755d6b2dafa5b9b02ed72427512d7b70185dbff43e1df3a924fa086a28334403d7c02b5bc2762fbb33dcf339e3a
@@ -1,119 +1,147 @@
1
+ ### 0.7.3 / 2020-07-09
2
+
3
+ - Let the client accept HTTP responses that have an empty reason phrase
4
+ following the `101` status code
5
+
6
+ ### 0.7.2 / 2020-05-22
7
+
8
+ - Emit `ping` and `pong` events from the `Server` driver
9
+ - Handle draft-76 handshakes correctly if the request's body is a frozen string
10
+
11
+ ### 0.7.1 / 2019-06-10
12
+
13
+ - Catch any exceptions produced while generating a handshake response and send a
14
+ `400 Bad Request` response to the client
15
+ - Pick the RFC-6455 protocol version if the request contains any of the headers
16
+ used by that version
17
+ - Handle errors encountered while handling malformed draft-76 requests
18
+ - Change license from MIT to Apache 2.0
19
+
20
+ ### 0.7.0 / 2017-09-11
21
+
22
+ - Add `ping` and `pong` to the set of events users can listen to
23
+
24
+ ### 0.6.5 / 2017-01-22
25
+
26
+ - Provide a pure-Ruby fallback for the native unmasking code
27
+
1
28
  ### 0.6.4 / 2016-05-20
2
29
 
3
- * Amend warnings issued when running with -W2
4
- * Make sure message strings passed in by the app are transcoded to UTF-8
5
- * Copy strings if necessary for frozen-string compatibility
30
+ - Amend warnings issued when running with -W2
31
+ - Make sure message strings passed in by the app are transcoded to UTF-8
32
+ - Copy strings if necessary for frozen-string compatibility
6
33
 
7
34
  ### 0.6.3 / 2015-11-06
8
35
 
9
- * Reject draft-76 handshakes if their Sec-WebSocket-Key headers are invalid
10
- * Throw a more helpful error if a client is created with an invalid URL
36
+ - Reject draft-76 handshakes if their Sec-WebSocket-Key headers are invalid
37
+ - Throw a more helpful error if a client is created with an invalid URL
11
38
 
12
39
  ### 0.6.2 / 2015-07-18
13
40
 
14
- * When the peer sends a close frame with no error code, emit 1000
41
+ - When the peer sends a close frame with no error code, emit 1000
15
42
 
16
43
  ### 0.6.1 / 2015-07-13
17
44
 
18
- * Fix how events are stored in `EventEmitter` to fix a backward-compatibility
45
+ - Fix how events are stored in `EventEmitter` to fix a backward-compatibility
19
46
  violation introduced in the last release
20
- * Use the `Array#pack` and `String#unpack` methods for reading/writing numbers
47
+ - Use the `Array#pack` and `String#unpack` methods for reading/writing numbers
21
48
  to buffers rather than including duplicate logic for this
22
49
 
23
50
  ### 0.6.0 / 2015-07-08
24
51
 
25
- * Use `SecureRandom` to generate the `Sec-WebSocket-Key` header
26
- * Allow the parser to recover cleanly if event listeners raise an error
27
- * Let the `on()` method take a lambda as a positional argument rather than a block
28
- * Add a `pong` method for sending unsolicited pong frames
52
+ - Use `SecureRandom` to generate the `Sec-WebSocket-Key` header
53
+ - Allow the parser to recover cleanly if event listeners raise an error
54
+ - Let the `on()` method take a lambda as a positional argument rather than a
55
+ block
56
+ - Add a `pong` method for sending unsolicited pong frames
29
57
 
30
58
  ### 0.5.4 / 2015-03-29
31
59
 
32
- * Don't emit extra close frames if we receive a close frame after we already
60
+ - Don't emit extra close frames if we receive a close frame after we already
33
61
  sent one
34
- * Fail the connection when the driver receives an invalid
62
+ - Fail the connection when the driver receives an invalid
35
63
  `Sec-WebSocket-Extensions` header
36
64
 
37
65
  ### 0.5.3 / 2015-02-22
38
66
 
39
- * Don't treat incoming data as WebSocket frames if a client driver is closed
67
+ - Don't treat incoming data as WebSocket frames if a client driver is closed
40
68
  before receiving the server handshake
41
69
 
42
70
  ### 0.5.2 / 2015-02-19
43
71
 
44
- * Don't emit multiple `error` events
72
+ - Don't emit multiple `error` events
45
73
 
46
74
  ### 0.5.1 / 2014-12-18
47
75
 
48
- * Don't allow drivers to be created with unrecognized options
76
+ - Don't allow drivers to be created with unrecognized options
49
77
 
50
78
  ### 0.5.0 / 2014-12-13
51
79
 
52
- * Support protocol extensions via the websocket-extensions module
80
+ - Support protocol extensions via the websocket-extensions module
53
81
 
54
82
  ### 0.4.0 / 2014-11-08
55
83
 
56
- * Support connection via HTTP proxies using `CONNECT`
84
+ - Support connection via HTTP proxies using `CONNECT`
57
85
 
58
86
  ### 0.3.5 / 2014-10-04
59
87
 
60
- * Fix bug where the `Server` driver doesn't pass `ping` callbacks to its
88
+ - Fix bug where the `Server` driver doesn't pass `ping` callbacks to its
61
89
  delegate
62
- * Fix an arity error when calling `fail_request`
63
- * Allow `close` to be called before `start` to close the driver
90
+ - Fix an arity error when calling `fail_request`
91
+ - Allow `close` to be called before `start` to close the driver
64
92
 
65
93
  ### 0.3.4 / 2014-07-06
66
94
 
67
- * Don't hold references to frame buffers after a message has been emitted
68
- * Make sure that `protocol` and `version` are exposed properly by the TCP driver
69
- * Correct HTTP header parsing based on RFC 7230; header names cannot contain
95
+ - Don't hold references to frame buffers after a message has been emitted
96
+ - Make sure that `protocol` and `version` are exposed properly by the TCP driver
97
+ - Correct HTTP header parsing based on RFC 7230; header names cannot contain
70
98
  backslashes
71
99
 
72
100
  ### 0.3.3 / 2014-04-24
73
101
 
74
- * Fix problems with loading C and Java native extension code
75
- * Correct the acceptable characters used in the HTTP parser
76
- * Correct the draft-76 status line reason phrase
102
+ - Fix problems with loading C and Java native extension code
103
+ - Correct the acceptable characters used in the HTTP parser
104
+ - Correct the draft-76 status line reason phrase
77
105
 
78
106
  ### 0.3.2 / 2013-12-29
79
107
 
80
- * Expand `max_length` to cover sequences of continuation frames and
108
+ - Expand `max_length` to cover sequences of continuation frames and
81
109
  `draft-{75,76}`
82
- * Decrease default maximum frame buffer size to 64MB
83
- * Stop parsing when the protocol enters a failure mode, to save CPU cycles
110
+ - Decrease default maximum frame buffer size to 64MB
111
+ - Stop parsing when the protocol enters a failure mode, to save CPU cycles
84
112
 
85
113
  ### 0.3.1 / 2013-12-03
86
114
 
87
- * Add a `max_length` option to limit allowed frame size
115
+ - Add a `max_length` option to limit allowed frame size
88
116
 
89
117
  ### 0.3.0 / 2013-09-09
90
118
 
91
- * Support client URLs with Basic Auth credentials
119
+ - Support client URLs with Basic Auth credentials
92
120
 
93
121
  ### 0.2.3 / 2013-08-04
94
122
 
95
- * Fix bug in EventEmitter#emit when listeners are removed
123
+ - Fix bug in EventEmitter#emit when listeners are removed
96
124
 
97
125
  ### 0.2.2 / 2013-08-04
98
126
 
99
- * Fix bug in EventEmitter#listener_count for unregistered events
127
+ - Fix bug in EventEmitter#listener_count for unregistered events
100
128
 
101
129
  ### 0.2.1 / 2013-07-05
102
130
 
103
- * Queue sent messages if the client has not begun trying to connect
104
- * Encode all strings sent to I/O as `ASCII-8BIT`
131
+ - Queue sent messages if the client has not begun trying to connect
132
+ - Encode all strings sent to I/O as `ASCII-8BIT`
105
133
 
106
134
  ### 0.2.0 / 2013-05-12
107
135
 
108
- * Add API for setting and reading headers
109
- * Add Driver.server() method for getting a driver for TCP servers
136
+ - Add API for setting and reading headers
137
+ - Add Driver.server() method for getting a driver for TCP servers
110
138
 
111
139
  ### 0.1.0 / 2013-05-04
112
140
 
113
- * First stable release
141
+ - First stable release
114
142
 
115
143
  ### 0.0.0 / 2013-04-22
116
144
 
117
- * First release
118
- * Proof of concept for people to try out
119
- * Might be unstable
145
+ - First release
146
+ - Proof of concept for people to try out
147
+ - Might be unstable
@@ -0,0 +1,12 @@
1
+ Copyright 2010-2020 James Coglan
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4
+ this file except in compliance with the License. You may obtain a copy of the
5
+ License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software distributed
10
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
12
+ specific language governing permissions and limitations under the License.
data/README.md CHANGED
@@ -10,21 +10,21 @@ pluggable I/O.
10
10
  Due to this design, you get a lot of things for free. In particular, if you hook
11
11
  this module up to some I/O object, it will do all of this for you:
12
12
 
13
- * Select the correct server-side driver to talk to the client
14
- * Generate and send both server- and client-side handshakes
15
- * Recognize when the handshake phase completes and the WS protocol begins
16
- * Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
17
- * Negotiate and use extensions via the
13
+ - Select the correct server-side driver to talk to the client
14
+ - Generate and send both server- and client-side handshakes
15
+ - Recognize when the handshake phase completes and the WS protocol begins
16
+ - Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
17
+ - Negotiate and use extensions via the
18
18
  [websocket-extensions](https://github.com/faye/websocket-extensions-ruby)
19
19
  module
20
- * Buffer sent messages until the handshake process is finished
21
- * Deal with proxies that defer delivery of the draft-76 handshake body
22
- * Notify you when the socket is open and closed and when messages arrive
23
- * Recombine fragmented messages
24
- * Dispatch text, binary, ping, pong and close frames
25
- * Manage the socket-closing handshake process
26
- * Automatically reply to ping frames with a matching pong
27
- * Apply masking to messages sent by the client
20
+ - Buffer sent messages until the handshake process is finished
21
+ - Deal with proxies that defer delivery of the draft-76 handshake body
22
+ - Notify you when the socket is open and closed and when messages arrive
23
+ - Recombine fragmented messages
24
+ - Dispatch text, binary, ping, pong and close frames
25
+ - Manage the socket-closing handshake process
26
+ - Automatically reply to ping frames with a matching pong
27
+ - Apply masking to messages sent by the client
28
28
 
29
29
  This library was originally extracted from the [Faye](http://faye.jcoglan.com)
30
30
  project but now aims to provide simple WebSocket support for any Ruby server or
@@ -43,12 +43,12 @@ $ gem install websocket-driver
43
43
  To build either a server-side or client-side socket, the only requirement is
44
44
  that you supply a `socket` object with these methods:
45
45
 
46
- * `socket.url` - returns the full URL of the socket as a string.
47
- * `socket.write(string)` - writes the given string to a TCP stream.
46
+ - `socket.url` - returns the full URL of the socket as a string.
47
+ - `socket.write(string)` - writes the given string to a TCP stream.
48
48
 
49
49
  Server-side sockets require one additional method:
50
50
 
51
- * `socket.env` - returns a Rack-style env hash that will contain some of the
51
+ - `socket.env` - returns a Rack-style env hash that will contain some of the
52
52
  following fields. Their values are strings containing the value of the named
53
53
  header, unless stated otherwise.
54
54
  * `HTTP_CONNECTION`
@@ -193,8 +193,8 @@ and send outgoing data.
193
193
  Client drivers have two additional methods for reading the HTTP data that was
194
194
  sent back by the server:
195
195
 
196
- * `driver.status` - the integer value of the HTTP status code
197
- * `driver.headers` - a hash-like object containing the response headers
196
+ - `driver.status` - the integer value of the HTTP status code
197
+ - `driver.headers` - a hash-like object containing the response headers
198
198
 
199
199
 
200
200
  ### HTTP Proxies
@@ -261,9 +261,9 @@ frames.
261
261
  The `options` argument is optional, and is a hash. It may contain the following
262
262
  keys:
263
263
 
264
- * `:max_length` - the maximum allowed size of incoming message frames, in bytes.
264
+ - `:max_length` - the maximum allowed size of incoming message frames, in bytes.
265
265
  The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
266
- * `:protocols` - an array of strings representing acceptable subprotocols for
266
+ - `:protocols` - an array of strings representing acceptable subprotocols for
267
267
  use over the socket. The driver will negotiate one of these to use via the
268
268
  `Sec-WebSocket-Protocol` header if supported by the other peer.
269
269
 
@@ -274,27 +274,38 @@ Note that most of these methods are commands: if they produce data that should
274
274
  be sent over the socket, they will give this to you by calling
275
275
  `socket.write(string)`.
276
276
 
277
- #### `driver.on :open, -> (event) { }`
277
+ #### `driver.on :open, -> (event) {}`
278
278
 
279
279
  Adds a callback block to execute when the socket becomes open.
280
280
 
281
- #### `driver.on :message, -> (event) { }`
281
+ #### `driver.on :message, -> (event) {}`
282
282
 
283
283
  Adds a callback block to execute when a message is received. `event` will have a
284
284
  `data` attribute containing either a string in the case of a text message or an
285
285
  array of integers in the case of a binary message.
286
286
 
287
- #### `driver.on :error, -> (event) { }`
287
+ #### `driver.on :error, -> (event) {}`
288
288
 
289
289
  Adds a callback to execute when a protocol error occurs due to the other peer
290
290
  sending an invalid byte sequence. `event` will have a `message` attribute
291
291
  describing the error.
292
292
 
293
- #### `driver.on :close, -> (event) { }`
293
+ #### `driver.on :close, -> (event) {}`
294
294
 
295
295
  Adds a callback block to execute when the socket becomes closed. The `event`
296
296
  object has `code` and `reason` attributes.
297
297
 
298
+ #### `driver.on :ping, -> (event) {}`
299
+
300
+ Adds a callback block to execute when a ping is received. You do not need to
301
+ handle this by sending a pong frame yourself; the driver handles this for you.
302
+
303
+ #### `driver.on :pong, -> (event) {}`
304
+
305
+ Adds a callback block to execute when a pong is received. If this was in
306
+ response to a ping you sent, you can also handle this event via the
307
+ `driver.ping(message) { ... }` callback.
308
+
298
309
  #### `driver.add_extension(extension)`
299
310
 
300
311
  Registers a protocol extension whose operation will be negotiated via the
@@ -367,27 +378,3 @@ Returns the WebSocket version in use as a string. Will either be `hixie-75`,
367
378
  Returns a string containing the selected subprotocol, if any was agreed upon
368
379
  using the `Sec-WebSocket-Protocol` mechanism. This value becomes available after
369
380
  `emit('open')` has fired.
370
-
371
-
372
- ## License
373
-
374
- (The MIT License)
375
-
376
- Copyright (c) 2010-2016 James Coglan
377
-
378
- Permission is hereby granted, free of charge, to any person obtaining a copy of
379
- this software and associated documentation files (the 'Software'), to deal in
380
- the Software without restriction, including without limitation the rights to
381
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
382
- the Software, and to permit persons to whom the Software is furnished to do so,
383
- subject to the following conditions:
384
-
385
- The above copyright notice and this permission notice shall be included in all
386
- copies or substantial portions of the Software.
387
-
388
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
389
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
390
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
391
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
392
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
393
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,8 +1,6 @@
1
1
  package com.jcoglan.websocket;
2
2
 
3
- import java.lang.Long;
4
3
  import java.io.IOException;
5
-
6
4
  import org.jruby.Ruby;
7
5
  import org.jruby.RubyClass;
8
6
  import org.jruby.RubyModule;
@@ -15,41 +13,45 @@ import org.jruby.runtime.builtin.IRubyObject;
15
13
  import org.jruby.runtime.load.BasicLibraryService;
16
14
 
17
15
  public class WebsocketMaskService implements BasicLibraryService {
18
- private Ruby runtime;
16
+ private Ruby runtime;
19
17
 
20
- public boolean basicLoad(Ruby runtime) throws IOException {
21
- this.runtime = runtime;
22
- RubyModule websocket = runtime.defineModule("WebSocket");
18
+ public boolean basicLoad(Ruby runtime) throws IOException {
19
+ this.runtime = runtime;
23
20
 
24
- RubyClass webSocketMask = websocket.defineClassUnder("Mask", runtime.getObject(), new ObjectAllocator() {
25
- public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
26
- return new WebsocketMask(runtime, rubyClass);
27
- }
28
- });
21
+ RubyModule websocket = runtime.defineModule("WebSocket");
22
+ RubyClass webSocketMask = websocket.defineClassUnder("Mask", runtime.getObject(), getAllocator());
29
23
 
30
- webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
31
- return true;
32
- }
24
+ webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
25
+ return true;
26
+ }
33
27
 
34
- public class WebsocketMask extends RubyObject {
35
- public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
36
- super(runtime, rubyClass);
28
+ ObjectAllocator getAllocator() {
29
+ return new ObjectAllocator() {
30
+ public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
31
+ return new WebsocketMask(runtime, rubyClass);
32
+ }
33
+ };
37
34
  }
38
35
 
39
- @JRubyMethod
40
- public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
41
- if (mask.isNil()) return payload;
36
+ public class WebsocketMask extends RubyObject {
37
+ public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
38
+ super(runtime, rubyClass);
39
+ }
40
+
41
+ @JRubyMethod
42
+ public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
43
+ if (mask.isNil()) return payload;
42
44
 
43
- byte[] payload_a = ((RubyString)payload).getBytes();
44
- byte[] mask_a = ((RubyString)mask).getBytes();
45
- int i, n = payload_a.length;
45
+ byte[] payload_a = ((RubyString)payload).getBytes();
46
+ byte[] mask_a = ((RubyString)mask).getBytes();
47
+ int i, n = payload_a.length;
46
48
 
47
- if (n == 0) return payload;
49
+ if (n == 0) return payload;
48
50
 
49
- for (i = 0; i < n; i++) {
50
- payload_a[i] ^= mask_a[i % 4];
51
- }
52
- return RubyString.newStringNoCopy(runtime, payload_a);
51
+ for (i = 0; i < n; i++) {
52
+ payload_a[i] ^= mask_a[i % 4];
53
+ }
54
+ return RubyString.newStringNoCopy(runtime, payload_a);
55
+ }
53
56
  }
54
- }
55
57
  }
@@ -1,41 +1,32 @@
1
1
  #include <ruby.h>
2
2
 
3
- VALUE WebSocket = Qnil;
4
- VALUE WebSocketMask = Qnil;
5
-
6
- void Init_websocket_mask();
7
- VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask);
8
-
9
- void
10
- Init_websocket_mask()
3
+ VALUE method_websocket_mask(VALUE self, VALUE payload, VALUE mask)
11
4
  {
12
- WebSocket = rb_define_module("WebSocket");
13
- WebSocketMask = rb_define_module_under(WebSocket, "Mask");
14
- rb_define_singleton_method(WebSocketMask, "mask", method_websocket_mask, 2);
15
- }
5
+ char *payload_s, *mask_s, *unmasked_s;
6
+ long i, n;
7
+ VALUE unmasked;
16
8
 
17
- VALUE
18
- method_websocket_mask(VALUE self,
19
- VALUE payload,
20
- VALUE mask)
21
- {
22
- char *payload_s, *mask_s, *unmasked_s;
23
- long i, n;
24
- VALUE unmasked;
9
+ if (mask == Qnil || RSTRING_LEN(mask) != 4) {
10
+ return payload;
11
+ }
25
12
 
26
- if (mask == Qnil || RSTRING_LEN(mask) != 4) {
27
- return payload;
28
- }
13
+ payload_s = RSTRING_PTR(payload);
14
+ mask_s = RSTRING_PTR(mask);
15
+ n = RSTRING_LEN(payload);
29
16
 
30
- payload_s = RSTRING_PTR(payload);
31
- mask_s = RSTRING_PTR(mask);
32
- n = RSTRING_LEN(payload);
17
+ unmasked = rb_str_new(0, n);
18
+ unmasked_s = RSTRING_PTR(unmasked);
33
19
 
34
- unmasked = rb_str_new(0, n);
35
- unmasked_s = RSTRING_PTR(unmasked);
20
+ for (i = 0; i < n; i++) {
21
+ unmasked_s[i] = payload_s[i] ^ mask_s[i % 4];
22
+ }
23
+ return unmasked;
24
+ }
25
+
26
+ void Init_websocket_mask()
27
+ {
28
+ VALUE WebSocket = rb_define_module("WebSocket");
29
+ VALUE Mask = rb_define_module_under(WebSocket, "Mask");
36
30
 
37
- for (i = 0; i < n; i++) {
38
- unmasked_s[i] = payload_s[i] ^ mask_s[i % 4];
39
- }
40
- return unmasked;
31
+ rb_define_singleton_method(Mask, "mask", method_websocket_mask, 2);
41
32
  }
@@ -19,7 +19,15 @@ module WebSocket
19
19
  class Driver
20
20
 
21
21
  root = File.expand_path('../driver', __FILE__)
22
- require 'websocket_mask'
22
+
23
+ begin
24
+ # Load C native extension
25
+ require 'websocket_mask'
26
+ rescue LoadError
27
+ # Fall back to pure-Ruby implementation
28
+ require 'websocket/mask'
29
+ end
30
+
23
31
 
24
32
  if RUBY_PLATFORM =~ /java/
25
33
  require 'jruby'
@@ -42,6 +50,8 @@ module WebSocket
42
50
  ConnectEvent = Struct.new(nil)
43
51
  OpenEvent = Struct.new(nil)
44
52
  MessageEvent = Struct.new(:data)
53
+ PingEvent = Struct.new(:data)
54
+ PongEvent = Struct.new(:data)
45
55
  CloseEvent = Struct.new(:code, :reason)
46
56
 
47
57
  ProtocolError = Class.new(StandardError)
@@ -91,8 +101,17 @@ module WebSocket
91
101
 
92
102
  def start
93
103
  return false unless @ready_state == 0
94
- response = handshake_response
95
- return false unless response
104
+
105
+ unless Driver.websocket?(@socket.env)
106
+ return fail_handshake(ProtocolError.new('Not a WebSocket request'))
107
+ end
108
+
109
+ begin
110
+ response = handshake_response
111
+ rescue => error
112
+ return fail_handshake(error)
113
+ end
114
+
96
115
  @socket.write(response)
97
116
  open unless @stage == -1
98
117
  true
@@ -124,6 +143,24 @@ module WebSocket
124
143
 
125
144
  private
126
145
 
146
+ def fail_handshake(error)
147
+ headers = Headers.new
148
+ headers['Content-Type'] = 'text/plain'
149
+ headers['Content-Length'] = error.message.bytesize
150
+
151
+ headers = ['HTTP/1.1 400 Bad Request', headers.to_s, error.message]
152
+ @socket.write(headers.join("\r\n"))
153
+ fail(:protocol_error, error.message)
154
+
155
+ false
156
+ end
157
+
158
+ def fail(type, message)
159
+ @ready_state = 2
160
+ emit(:error, ProtocolError.new(message))
161
+ close
162
+ end
163
+
127
164
  def open
128
165
  @ready_state = 1
129
166
  @queue.each { |message| frame(*message) }
@@ -145,10 +182,15 @@ module WebSocket
145
182
  end
146
183
 
147
184
  def self.rack(socket, options = {})
148
- env = socket.env
149
- if env['HTTP_SEC_WEBSOCKET_VERSION']
185
+ env = socket.env
186
+ version = env['HTTP_SEC_WEBSOCKET_VERSION']
187
+ key = env['HTTP_SEC_WEBSOCKET_KEY']
188
+ key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
189
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
190
+
191
+ if version or key
150
192
  Hybi.new(socket, options.merge(:require_masking => true))
151
- elsif env['HTTP_SEC_WEBSOCKET_KEY1']
193
+ elsif key1 or key2
152
194
  Draft76.new(socket, options)
153
195
  else
154
196
  Draft75.new(socket, options)
@@ -173,7 +215,7 @@ module WebSocket
173
215
  def self.validate_options(options, valid_keys)
174
216
  options.keys.each do |key|
175
217
  unless valid_keys.include?(key)
176
- raise ConfigurationError, "Unrecognized option: #{key.inspect}"
218
+ raise ConfigurationError, "Unrecognized option: #{ key.inspect }"
177
219
  end
178
220
  end
179
221
  end
@@ -20,10 +20,10 @@ module WebSocket
20
20
 
21
21
  uri = URI.parse(@socket.url)
22
22
  unless VALID_SCHEMES.include?(uri.scheme)
23
- raise URIError, "#{socket.url} is not a valid WebSocket URL"
23
+ raise URIError, "#{ socket.url } is not a valid WebSocket URL"
24
24
  end
25
25
 
26
- host = uri.host + (uri.port ? ":#{uri.port}" : '')
26
+ host = uri.host + (uri.port ? ":#{ uri.port }" : '')
27
27
  path = (uri.path == '') ? '/' : uri.path
28
28
  @pathname = path + (uri.query ? '?' + uri.query : '')
29
29
 
@@ -31,7 +31,7 @@ module WebSocket
31
31
  @headers['Upgrade'] = 'websocket'
32
32
  @headers['Connection'] = 'Upgrade'
33
33
  @headers['Sec-WebSocket-Key'] = @key
34
- @headers['Sec-WebSocket-Version'] = '13'
34
+ @headers['Sec-WebSocket-Version'] = VERSION
35
35
 
36
36
  if @protocols.size > 0
37
37
  @headers['Sec-WebSocket-Protocol'] = @protocols * ', '
@@ -44,7 +44,7 @@ module WebSocket
44
44
  end
45
45
 
46
46
  def version
47
- 'hybi-13'
47
+ "hybi-#{ VERSION }"
48
48
  end
49
49
 
50
50
  def proxy(origin, options = {})
@@ -73,19 +73,19 @@ module WebSocket
73
73
  parse(@http.body)
74
74
  end
75
75
 
76
- private
76
+ private
77
77
 
78
78
  def handshake_request
79
79
  extensions = @extensions.generate_offer
80
80
  @headers['Sec-WebSocket-Extensions'] = extensions if extensions
81
81
 
82
- start = "GET #{@pathname} HTTP/1.1"
82
+ start = "GET #{ @pathname } HTTP/1.1"
83
83
  headers = [start, @headers.to_s, '']
84
84
  headers.join("\r\n")
85
85
  end
86
86
 
87
87
  def fail_handshake(message)
88
- message = "Error during WebSocket handshake: #{message}"
88
+ message = "Error during WebSocket handshake: #{ message }"
89
89
  @ready_state = 3
90
90
  emit(:error, ProtocolError.new(message))
91
91
  emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
@@ -96,7 +96,7 @@ module WebSocket
96
96
  @headers = Headers.new(@http.headers)
97
97
 
98
98
  unless @http.code == 101
99
- return fail_handshake("Unexpected response code: #{@http.code}")
99
+ return fail_handshake("Unexpected response code: #{ @http.code }")
100
100
  end
101
101
 
102
102
  upgrade = @http['Upgrade'] || ''
@@ -6,9 +6,10 @@ module WebSocket
6
6
 
7
7
  def initialize(socket, options = {})
8
8
  super
9
- input = @socket.env['rack.input']
9
+ input = (@socket.env['rack.input'] || StringIO.new('')).read
10
+ input = input.dup if input.frozen?
10
11
  @stage = -1
11
- @body = (input ? input.read : String.new('')).force_encoding(BINARY)
12
+ @body = input.force_encoding(BINARY)
12
13
 
13
14
  @headers.clear
14
15
  @headers['Upgrade'] = 'WebSocket'
@@ -29,7 +30,7 @@ module WebSocket
29
30
 
30
31
  def close(reason = nil, code = nil)
31
32
  return false if @ready_state == 3
32
- @socket.write([0xFF, 0x00].pack('C*'))
33
+ @socket.write([0xFF, 0x00].pack('C*')) if @ready_state == 1
33
34
  @ready_state = 3
34
35
  emit(:close, CloseEvent.new(nil, nil))
35
36
  true
@@ -39,19 +40,20 @@ module WebSocket
39
40
 
40
41
  def handshake_response
41
42
  env = @socket.env
42
-
43
43
  key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
44
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
45
+
46
+ raise ProtocolError.new('Missing required header: Sec-WebSocket-Key1') unless key1
47
+ raise ProtocolError.new('Missing required header: Sec-WebSocket-Key2') unless key2
48
+
44
49
  number1 = number_from_key(key1)
45
50
  spaces1 = spaces_in_key(key1)
46
51
 
47
- key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
48
52
  number2 = number_from_key(key2)
49
53
  spaces2 = spaces_in_key(key2)
50
54
 
51
55
  if number1 % spaces1 != 0 or number2 % spaces2 != 0
52
- emit(:error, ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers'))
53
- close
54
- return nil
56
+ raise ProtocolError.new('Client sent invalid Sec-WebSocket-Key headers')
55
57
  end
56
58
 
57
59
  @key_values = [number1 / spaces1, number2 / spaces2]
@@ -84,7 +86,8 @@ module WebSocket
84
86
  end
85
87
 
86
88
  def number_from_key(key)
87
- key.scan(/[0-9]/).join('').to_i(10)
89
+ number = key.scan(/[0-9]/).join('')
90
+ number == '' ? Float::NAN : number.to_i(10)
88
91
  end
89
92
 
90
93
  def spaces_in_key(key)
@@ -25,7 +25,7 @@ module WebSocket
25
25
  return if value.nil?
26
26
  key = HTTP.normalize_header(name)
27
27
  return unless @sent.add?(key) or ALLOWED_DUPLICATES.include?(key)
28
- @lines << "#{name.strip}: #{value.to_s.strip}\r\n"
28
+ @lines << "#{ name.strip }: #{ value.to_s.strip }\r\n"
29
29
  end
30
30
 
31
31
  def inspect
@@ -11,7 +11,8 @@ module WebSocket
11
11
  Base64.strict_encode64(Digest::SHA1.digest(key + GUID))
12
12
  end
13
13
 
14
- GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
14
+ VERSION = '13'
15
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
15
16
 
16
17
  BYTE = 0b11111111
17
18
  FIN = MASK = 0b10000000
@@ -51,7 +52,7 @@ module WebSocket
51
52
  MIN_RESERVED_ERROR = 3000
52
53
  MAX_RESERVED_ERROR = 4999
53
54
 
54
- PACK_FORMATS = {2 => 'n', 8 => 'Q>'}
55
+ PACK_FORMATS = { 2 => 'n', 8 => 'Q>' }
55
56
 
56
57
  def initialize(socket, options = {})
57
58
  super
@@ -68,22 +69,16 @@ module WebSocket
68
69
 
69
70
  return unless @socket.respond_to?(:env)
70
71
 
71
- sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
72
- protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
73
-
74
- @headers['Upgrade'] = 'websocket'
75
- @headers['Connection'] = 'Upgrade'
76
- @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
77
-
78
72
  if protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
79
73
  protos = protos.split(/ *, */) if String === protos
80
74
  @protocol = protos.find { |p| @protocols.include?(p) }
81
- @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
75
+ else
76
+ @protocol = nil
82
77
  end
83
78
  end
84
79
 
85
80
  def version
86
- "hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
81
+ "hybi-#{ VERSION }"
87
82
  end
88
83
 
89
84
  def add_extension(extension)
@@ -229,13 +224,24 @@ module WebSocket
229
224
  end
230
225
 
231
226
  def handshake_response
232
- begin
233
- extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
234
- rescue => error
235
- fail(:protocol_error, error.message)
236
- return nil
227
+ sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
228
+ version = @socket.env['HTTP_SEC_WEBSOCKET_VERSION']
229
+
230
+ unless version == VERSION
231
+ raise ProtocolError.new("Unsupported WebSocket version: #{ VERSION }")
232
+ end
233
+
234
+ unless sec_key
235
+ raise ProtocolError.new('Missing handshake request header: Sec-WebSocket-Key')
237
236
  end
238
237
 
238
+ @headers['Upgrade'] = 'websocket'
239
+ @headers['Connection'] = 'Upgrade'
240
+ @headers['Sec-WebSocket-Accept'] = Hybi.generate_accept(sec_key)
241
+
242
+ @headers['Sec-WebSocket-Protocol'] = @protocol if @protocol
243
+
244
+ extensions = @extensions.generate_response(@socket.env['HTTP_SEC_WEBSOCKET_EXTENSIONS'])
239
245
  @headers['Sec-WebSocket-Extensions'] = extensions if extensions
240
246
 
241
247
  start = 'HTTP/1.1 101 Switching Protocols'
@@ -275,17 +281,17 @@ module WebSocket
275
281
 
276
282
  unless @extensions.valid_frame_rsv?(@frame)
277
283
  return fail(:protocol_error,
278
- "One or more reserved bits are on: reserved1 = #{@frame.rsv1 ? 1 : 0}" +
279
- ", reserved2 = #{@frame.rsv2 ? 1 : 0 }" +
280
- ", reserved3 = #{@frame.rsv3 ? 1 : 0 }")
284
+ "One or more reserved bits are on: reserved1 = #{ @frame.rsv1 ? 1 : 0 }" +
285
+ ", reserved2 = #{ @frame.rsv2 ? 1 : 0 }" +
286
+ ", reserved3 = #{ @frame.rsv3 ? 1 : 0 }")
281
287
  end
282
288
 
283
289
  unless OPCODES.values.include?(@frame.opcode)
284
- return fail(:protocol_error, "Unrecognized frame opcode: #{@frame.opcode}")
290
+ return fail(:protocol_error, "Unrecognized frame opcode: #{ @frame.opcode }")
285
291
  end
286
292
 
287
293
  unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.final
288
- return fail(:protocol_error, "Received fragmented control frame: opcode = #{@frame.opcode}")
294
+ return fail(:protocol_error, "Received fragmented control frame: opcode = #{ @frame.opcode }")
289
295
  end
290
296
 
291
297
  if @message and OPENING_OPCODES.include?(@frame.opcode)
@@ -315,7 +321,7 @@ module WebSocket
315
321
  @stage = @frame.masked ? 3 : 4
316
322
 
317
323
  unless MESSAGE_OPCODES.include?(@frame.opcode) or @frame.length <= 125
318
- return fail(:protocol_error, "Received control frame having too long payload: #{@frame.length}")
324
+ return fail(:protocol_error, "Received control frame having too long payload: #{ @frame.length }")
319
325
  end
320
326
 
321
327
  return unless check_frame_length
@@ -368,12 +374,14 @@ module WebSocket
368
374
 
369
375
  when OPCODES[:ping] then
370
376
  frame(payload, :pong)
377
+ emit(:ping, PingEvent.new(payload))
371
378
 
372
379
  when OPCODES[:pong] then
373
380
  message = Driver.encode(payload, UNICODE)
374
381
  callback = @ping_callbacks[message]
375
382
  @ping_callbacks.delete(message)
376
383
  callback.call if callback
384
+ emit(:pong, PongEvent.new(payload))
377
385
  end
378
386
 
379
387
  emit_message if frame.final and MESSAGE_OPCODES.include?(opcode)
@@ -4,7 +4,7 @@ module WebSocket
4
4
  class Proxy
5
5
  include EventEmitter
6
6
 
7
- PORTS = {'ws' => 80, 'wss' => 443}
7
+ PORTS = { 'ws' => 80, 'wss' => 443 }
8
8
 
9
9
  attr_reader :status, :headers
10
10
 
@@ -20,7 +20,7 @@ module WebSocket
20
20
  @state = 0
21
21
 
22
22
  @headers = Headers.new
23
- @headers['Host'] = @origin.host + (@origin.port ? ":#{@origin.port}" : '')
23
+ @headers['Host'] = @origin.host + (@origin.port ? ":#{ @origin.port }" : '')
24
24
  @headers['Connection'] = 'keep-alive'
25
25
  @headers['Proxy-Connection'] = 'keep-alive'
26
26
 
@@ -41,7 +41,7 @@ module WebSocket
41
41
  @state = 1
42
42
 
43
43
  port = @origin.port || PORTS[@origin.scheme]
44
- start = "CONNECT #{@origin.host}:#{port} HTTP/1.1"
44
+ start = "CONNECT #{ @origin.host }:#{ port } HTTP/1.1"
45
45
  headers = [start, @headers.to_s, '']
46
46
 
47
47
  @socket.write(headers.join("\r\n"))
@@ -58,7 +58,7 @@ module WebSocket
58
58
  if @status == 200
59
59
  emit(:connect, ConnectEvent.new)
60
60
  else
61
- message = "Can't establish a connection to the server at #{@socket.url}"
61
+ message = "Can't establish a connection to the server at #{ @socket.url }"
62
62
  emit(:error, ProtocolError.new(message))
63
63
  end
64
64
  end
@@ -2,7 +2,7 @@ module WebSocket
2
2
  class Driver
3
3
 
4
4
  class Server < Driver
5
- EVENTS = %w[open message error close]
5
+ EVENTS = %w[open message error close ping pong]
6
6
 
7
7
  def initialize(socket, options = {})
8
8
  super
@@ -17,9 +17,9 @@ module WebSocket
17
17
  def url
18
18
  return nil unless e = env
19
19
 
20
- url = "ws://#{e['HTTP_HOST']}"
20
+ url = "ws://#{ e['HTTP_HOST'] }"
21
21
  url << e['PATH_INFO']
22
- url << "?#{e['QUERY_STRING']}" unless e['QUERY_STRING'] == ''
22
+ url << "?#{ e['QUERY_STRING'] }" unless e['QUERY_STRING'] == ''
23
23
  url
24
24
  end
25
25
 
@@ -30,11 +30,11 @@ module WebSocket
30
30
  super
31
31
  @headers.each do |name, value|
32
32
  rack_name = name.upcase.gsub(/-/, '_')
33
- rack_name = "HTTP_#{rack_name}" unless RESERVED_HEADERS.include?(name)
33
+ rack_name = "HTTP_#{ rack_name }" unless RESERVED_HEADERS.include?(name)
34
34
  @env[rack_name] = value
35
35
  end
36
36
  if host = @env['HTTP_HOST']
37
- uri = URI.parse("http://#{host}")
37
+ uri = URI.parse("http://#{ host }")
38
38
  @env['SERVER_NAME'] = uri.host
39
39
  @env['SERVER_PORT'] = uri.port.to_s
40
40
  end
@@ -4,7 +4,7 @@ module WebSocket
4
4
  class Response
5
5
  include Headers
6
6
 
7
- STATUS_LINE = /^(HTTP\/[0-9]+\.[0-9]+) ([0-9]{3}) ([\x20-\x7e]+)$/
7
+ STATUS_LINE = /^(HTTP\/[0-9]+\.[0-9]+) ([0-9]{3}) ([\x20-\x7e]*)$/
8
8
 
9
9
  attr_reader :code
10
10
 
@@ -0,0 +1,14 @@
1
+ module WebSocket
2
+ module Mask
3
+ def self.mask(payload, mask)
4
+ return payload if mask.nil? || payload.empty?
5
+
6
+ payload.tap do |result|
7
+ payload.bytesize.times do |i|
8
+ result.setbyte(i, payload.getbyte(i) ^ mask.getbyte(i % 4))
9
+ end
10
+ end
11
+ end
12
+
13
+ end
14
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: websocket-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Coglan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-20 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-extensions
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rake-compiler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 0.8.0
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 0.8.0
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description:
83
+ description:
84
84
  email: jcoglan@gmail.com
85
85
  executables: []
86
86
  extensions:
@@ -89,8 +89,8 @@ extra_rdoc_files:
89
89
  - README.md
90
90
  files:
91
91
  - CHANGELOG.md
92
+ - LICENSE.md
92
93
  - README.md
93
- - examples/tcp_server.rb
94
94
  - ext/websocket-driver/WebsocketMaskService.java
95
95
  - ext/websocket-driver/extconf.rb
96
96
  - ext/websocket-driver/websocket_mask.c
@@ -110,12 +110,13 @@ files:
110
110
  - lib/websocket/http/headers.rb
111
111
  - lib/websocket/http/request.rb
112
112
  - lib/websocket/http/response.rb
113
+ - lib/websocket/mask.rb
113
114
  - lib/websocket/websocket_mask.rb
114
- homepage: http://github.com/faye/websocket-driver-ruby
115
+ homepage: https://github.com/faye/websocket-driver-ruby
115
116
  licenses:
116
- - MIT
117
+ - Apache-2.0
117
118
  metadata: {}
118
- post_install_message:
119
+ post_install_message:
119
120
  rdoc_options:
120
121
  - "--main"
121
122
  - README.md
@@ -134,9 +135,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
135
  - !ruby/object:Gem::Version
135
136
  version: '0'
136
137
  requirements: []
137
- rubyforge_project:
138
- rubygems_version: 2.5.1
139
- signing_key:
138
+ rubygems_version: 3.1.2
139
+ signing_key:
140
140
  specification_version: 4
141
141
  summary: WebSocket protocol handler with pluggable I/O
142
142
  test_files: []
@@ -1,28 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
- require 'eventmachine'
4
- require 'websocket/driver'
5
- require 'permessage_deflate'
6
-
7
- module Connection
8
- def initialize
9
- @driver = WebSocket::Driver.server(self)
10
- @driver.add_extension(PermessageDeflate)
11
-
12
- @driver.on(:connect) { |e| @driver.start if WebSocket::Driver.websocket? @driver.env }
13
- @driver.on(:message) { |e| @driver.frame(e.data) }
14
- @driver.on(:close) { |e| close_connection_after_writing }
15
- end
16
-
17
- def receive_data(data)
18
- @driver.parse(data)
19
- end
20
-
21
- def write(data)
22
- send_data(data)
23
- end
24
- end
25
-
26
- EM.run {
27
- EM.start_server('127.0.0.1', ARGV[0], Connection)
28
- }