websocket-driver 0.7.5 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 046c514660d2c0dab2cf42bce8314a17e713909377a5da470beaeadf666ff28e
4
- data.tar.gz: 9830c412394044d102dd6f313346721076ef0f97284d6182aeb06862e1f427b2
3
+ metadata.gz: bf509f60cf8b74b4a6e2dd7cf7038b53817271cfc2985d2234037946543e9fed
4
+ data.tar.gz: 3f4433596095d12a4f70b4477c20f0d6f4bfe96ef18ca711fee0996815a991a8
5
5
  SHA512:
6
- metadata.gz: cea4027f342f51a688c8004a92ca29b68af9a9395b5e7f8d2462b8742067862718d23f4be9ca6162f761774490e4cdf33111bb746f9221d5acace6546a749b35
7
- data.tar.gz: d124aa6acf1cbfa498b44d02d6aa659c99e97e177441e8cbd43958db38c6cf03d703d26ae73998a09719c0612f538554049d58b58aa8cc8ab5f0bb458e27c988
6
+ metadata.gz: a558a93b903403cc48e0683ce3247c02a613f9e4b19ee3bc476ccc621cdf3a23160a4eb2615cc259fe227a74de54f3728d11b6732fa2e88a6d1d672ba96240d4
7
+ data.tar.gz: 9f591c43f2f34de6444069d3c34ec7b5b4813cc9c2e76203f8d1d48942f6b1712c1ad2847887f47759012d9c0ac95a90f82bb144d0311fb94e4a9d9ce0eae10e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ### 0.8.2 / 2026-06-23
2
+
3
+ - Gracefully handle malformed `Host` headers in the `Server` driver
4
+
5
+ ### 0.8.1 / 2026-06-04
6
+
7
+ - Close a draft-75/76 connection if a length header grows to exceed the
8
+ configured max length
9
+ - Fail the connection if a message is larger than the configured max length
10
+ after extension processing
11
+ - Limit the total HTTP request line and headers size to 32K
12
+
13
+ ### 0.8.0 / 2025-05-25
14
+
15
+ - Emit binary message as a string with `Encoding::BINARY` instead of an array
16
+ - Add the option `:binary_data_format` to force the previous behaviour
17
+
18
+ ### 0.7.7 / 2025-01-04
19
+
20
+ - Add `base64` gem to the dependencies to support Ruby 3.4
21
+
22
+ ### 0.7.6 / 2023-07-25
23
+
24
+ - Fix handling of default ports in `Host` headers on Ruby 3.1+
25
+
1
26
  ### 0.7.5 / 2021-06-12
2
27
 
3
28
  - Do not change the encoding of strings passed to `Driver#text`
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2010-2021 James Coglan
1
+ Copyright 2010-2026 James Coglan
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4
4
  this file except in compliance with the License. You may obtain a copy of the
data/README.md CHANGED
@@ -275,6 +275,10 @@ keys:
275
275
  - `:protocols` - an array of strings representing acceptable subprotocols for
276
276
  use over the socket. The driver will negotiate one of these to use via the
277
277
  `Sec-WebSocket-Protocol` header if supported by the other peer.
278
+ - `:binary_data_format` - in older versions of this library, binary messages
279
+ were represented as arrays of bytes, whereas they're now represented as
280
+ strings with `Encoding::BINARY` for performance reasons. Set this option to
281
+ `:array` to restore the old behaviour.
278
282
 
279
283
  All drivers respond to the following API methods, but some of them are no-ops
280
284
  depending on whether the client supports the behaviour.
@@ -290,8 +294,8 @@ Adds a callback block to execute when the socket becomes open.
290
294
  #### `driver.on :message, -> (event) {}`
291
295
 
292
296
  Adds a callback block to execute when a message is received. `event` will have a
293
- `data` attribute containing either a string in the case of a text message or an
294
- array of integers in the case of a binary message.
297
+ `data` attribute whose value is a string with the encoding `Encoding::UTF_8` for
298
+ text message, and `Encoding::BINARY` for binary message.
295
299
 
296
300
  #### `driver.on :error, -> (event) {}`
297
301
 
@@ -346,11 +350,12 @@ Sends a text message over the socket. If the socket handshake is not yet
346
350
  complete, the message will be queued until it is. Returns `true` if the message
347
351
  was sent or queued, and `false` if the socket can no longer send messages.
348
352
 
349
- #### `driver.binary(array)`
353
+ #### `driver.binary(buffer)`
350
354
 
351
- Takes an array of byte-sized integers and sends them as a binary message. Will
352
- queue and return `true` or `false` the same way as the `text` method. It will
353
- also return `false` if the driver does not support binary messages.
355
+ Takes either a string with encoding `Encoding::BINARY`, or an array of
356
+ byte-sized integers, and sends it as a binary message. Will queue and return
357
+ `true` or `false` the same way as the `text` method. It will also return `false`
358
+ if the driver does not support binary messages.
354
359
 
355
360
  #### `driver.ping(string = '', &callback)`
356
361
 
@@ -23,11 +23,10 @@ module WebSocket
23
23
  raise URIError, "#{ socket.url } is not a valid WebSocket URL"
24
24
  end
25
25
 
26
- host = uri.host + (uri.port ? ":#{ uri.port }" : '')
27
26
  path = (uri.path == '') ? '/' : uri.path
28
27
  @pathname = path + (uri.query ? '?' + uri.query : '')
29
28
 
30
- @headers['Host'] = host
29
+ @headers['Host'] = Driver.host_header(uri)
31
30
  @headers['Upgrade'] = 'websocket'
32
31
  @headers['Connection'] = 'Upgrade'
33
32
  @headers['Sec-WebSocket-Key'] = @key
@@ -41,6 +41,7 @@ module WebSocket
41
41
 
42
42
  when 1 then
43
43
  @length = (octet & 0x7F) + 128 * @length
44
+ return close if @length > @max_length
44
45
 
45
46
  if @closing and @length.zero?
46
47
  return close
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  module WebSocket
2
4
  class Driver
3
5
 
@@ -161,11 +163,13 @@ module WebSocket
161
163
  message = Message.new
162
164
  frame = Frame.new
163
165
 
164
- message.rsv1 = message.rsv2 = message.rsv3 = false
165
- message.opcode = OPCODES[type || (String === buffer ? :text : :binary)]
166
+ is_binary = (Array === buffer or buffer.encoding == Encoding::BINARY)
167
+ payload = Driver.encode(buffer, is_binary ? nil : Encoding::UTF_8)
168
+ payload = [code, payload].pack('S>a*') if code
169
+ type ||= is_binary ? :binary : :text
166
170
 
167
- payload = Driver.encode(buffer)
168
- payload = [code, payload].pack('S>a*') if code
171
+ message.rsv1 = message.rsv2 = message.rsv3 = false
172
+ message.opcode = OPCODES[type]
169
173
  message.data = payload
170
174
 
171
175
  if MESSAGE_OPCODES.include?(message.opcode)
@@ -396,12 +400,20 @@ module WebSocket
396
400
 
397
401
  payload = message.data
398
402
 
403
+ if payload.bytesize > @max_length
404
+ return fail(:too_large, 'WebSocket frame length too large')
405
+ end
406
+
399
407
  case message.opcode
400
408
  when OPCODES[:text] then
401
409
  payload = Driver.encode(payload, Encoding::UTF_8)
402
410
  payload = nil unless payload.valid_encoding?
403
411
  when OPCODES[:binary]
404
- payload = payload.bytes.to_a
412
+ if @binary_data_format == :array
413
+ payload = payload.bytes.to_a
414
+ else
415
+ payload = Driver.encode(payload, Encoding::BINARY)
416
+ end
405
417
  end
406
418
 
407
419
  if payload
@@ -4,8 +4,6 @@ module WebSocket
4
4
  class Proxy
5
5
  include EventEmitter
6
6
 
7
- PORTS = { 'ws' => 80, 'wss' => 443 }
8
-
9
7
  attr_reader :status, :headers
10
8
 
11
9
  def initialize(client, origin, options)
@@ -20,7 +18,7 @@ module WebSocket
20
18
  @state = 0
21
19
 
22
20
  @headers = Headers.new
23
- @headers['Host'] = @origin.host + (@origin.port ? ":#{ @origin.port }" : '')
21
+ @headers['Host'] = Driver.host_header(@origin)
24
22
  @headers['Connection'] = 'keep-alive'
25
23
  @headers['Proxy-Connection'] = 'keep-alive'
26
24
 
@@ -42,6 +42,7 @@ module WebSocket
42
42
  end
43
43
 
44
44
  MAX_LENGTH = 0x3ffffff
45
+ PORTS = { 'ws' => 80, 'wss' => 443 }
45
46
  STATES = [:connecting, :open, :closing, :closed]
46
47
 
47
48
  ConnectEvent = Struct.new(nil)
@@ -70,7 +71,7 @@ module WebSocket
70
71
 
71
72
  def initialize(socket, options = {})
72
73
  super()
73
- Driver.validate_options(options, [:max_length, :masking, :require_masking, :protocols])
74
+ Driver.validate_options(options, [:max_length, :masking, :require_masking, :protocols, :binary_data_format])
74
75
 
75
76
  @socket = socket
76
77
  @reader = StreamReader.new
@@ -79,6 +80,8 @@ module WebSocket
79
80
  @headers = Headers.new
80
81
  @queue = []
81
82
  @ready_state = 0
83
+
84
+ @binary_data_format = options[:binary_data_format] || :string
82
85
  end
83
86
 
84
87
  def state
@@ -196,17 +199,26 @@ module WebSocket
196
199
 
197
200
  def self.encode(data, encoding = nil)
198
201
  if Array === data
202
+ data = data.pack('C*')
199
203
  encoding ||= Encoding::BINARY
200
- return data.pack('C*').force_encoding(encoding)
201
204
  end
202
205
 
203
- encoding ||= Encoding::UTF_8
206
+ return data if encoding.nil? or data.encoding == encoding
204
207
 
205
- return data if data.encoding == encoding
206
- return data.encode(encoding) unless data.encoding == Encoding::BINARY
208
+ if data.encoding == Encoding::BINARY
209
+ data = data.dup if data.frozen?
210
+ data.force_encoding(encoding)
211
+ else
212
+ data.encode(encoding)
213
+ end
214
+ end
207
215
 
208
- data = data.dup if data.frozen?
209
- data.force_encoding(encoding)
216
+ def self.host_header(uri)
217
+ host = uri.host
218
+ if uri.port and uri.port != PORTS[uri.scheme]
219
+ host += ":#{uri.port}"
220
+ end
221
+ host
210
222
  end
211
223
 
212
224
  def self.validate_options(options, valid_keys)
@@ -215,6 +227,12 @@ module WebSocket
215
227
  raise ConfigurationError, "Unrecognized option: #{ key.inspect }"
216
228
  end
217
229
  end
230
+
231
+ if options[:binary_data_format]
232
+ unless [:array, :string].include?(options[:binary_data_format])
233
+ raise ConfigurationError, "Invalid :binary_data_format: #{options[:binary_data_format].inspect}"
234
+ end
235
+ end
218
236
  end
219
237
 
220
238
  def self.websocket?(env)
@@ -2,7 +2,7 @@ module WebSocket
2
2
  module HTTP
3
3
 
4
4
  module Headers
5
- MAX_LINE_LENGTH = 4096
5
+ MAX_REQUEST_SIZE = 32768
6
6
  CR = 0x0D
7
7
  LF = 0x0A
8
8
 
@@ -38,6 +38,7 @@ module WebSocket
38
38
  attr_reader :headers
39
39
 
40
40
  def initialize
41
+ @size = 0
41
42
  @buffer = []
42
43
  @env = {}
43
44
  @headers = {}
@@ -54,6 +55,9 @@ module WebSocket
54
55
 
55
56
  def parse(chunk)
56
57
  chunk.each_byte do |octet|
58
+ @size += 1
59
+ return error if @size > MAX_REQUEST_SIZE
60
+
57
61
  if octet == LF and @stage < 2
58
62
  @buffer.pop if @buffer.last == CR
59
63
  if @buffer.empty?
@@ -71,9 +75,8 @@ module WebSocket
71
75
  end
72
76
  end
73
77
  @buffer = []
74
- else
75
- @buffer << octet if @stage >= 0
76
- error if @stage < 2 and @buffer.size > MAX_LINE_LENGTH
78
+ elsif @stage >= 0
79
+ @buffer << octet
77
80
  end
78
81
  end
79
82
  @env['rack.input'] = StringIO.new(string_buffer)
@@ -33,11 +33,18 @@ module WebSocket
33
33
  rack_name = "HTTP_#{ rack_name }" unless RESERVED_HEADERS.include?(name)
34
34
  @env[rack_name] = value
35
35
  end
36
- if host = @env['HTTP_HOST']
37
- uri = URI.parse("http://#{ host }")
38
- @env['SERVER_NAME'] = uri.host
39
- @env['SERVER_PORT'] = uri.port.to_s
40
- end
36
+
37
+ set_server_vars
38
+ end
39
+
40
+ def set_server_vars
41
+ return unless host = @env['HTTP_HOST']
42
+
43
+ uri = URI.parse("http://#{ host }")
44
+ @env['SERVER_NAME'] = uri.host
45
+ @env['SERVER_PORT'] = uri.port.to_s
46
+ rescue URI::InvalidURIError
47
+ error
41
48
  end
42
49
  end
43
50
 
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: websocket-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Coglan
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-06-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: websocket-extensions
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +93,6 @@ dependencies:
80
93
  - - ">="
81
94
  - !ruby/object:Gem::Version
82
95
  version: '0'
83
- description:
84
96
  email: jcoglan@gmail.com
85
97
  executables: []
86
98
  extensions:
@@ -115,8 +127,8 @@ files:
115
127
  homepage: https://github.com/faye/websocket-driver-ruby
116
128
  licenses:
117
129
  - Apache-2.0
118
- metadata: {}
119
- post_install_message:
130
+ metadata:
131
+ changelog_uri: https://github.com/faye/websocket-driver-ruby/blob/main/CHANGELOG.md
120
132
  rdoc_options:
121
133
  - "--main"
122
134
  - README.md
@@ -135,8 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
147
  - !ruby/object:Gem::Version
136
148
  version: '0'
137
149
  requirements: []
138
- rubygems_version: 3.1.6
139
- signing_key:
150
+ rubygems_version: 3.6.9
140
151
  specification_version: 4
141
152
  summary: WebSocket protocol handler with pluggable I/O
142
153
  test_files: []