Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7e1f86979 | |||
| 3c0fda5b21 | |||
| 5891358639 | |||
| 84b869effb | |||
| 6ea62a9965 | |||
| 1d80737dce | |||
| a0f23c5d3c | |||
| 8b6c706737 | |||
| 402222fdd9 | |||
| aa156a439d | |||
| 8174a4a0f9 | |||
| 96059802a6 | |||
| bd6d0acc01 | |||
| a8c847876b | |||
| 242f007cb9 | |||
| 249bcc106f | |||
| 738a0af1dd | |||
| 68c1b21129 | |||
| 8ee09b2924 | |||
| 7e64cfb6ca | |||
| 1a441fac80 |
@@ -0,0 +1,34 @@
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruby:
|
||||
- ruby-2.0
|
||||
- ruby-2.1
|
||||
# - ruby-2.2
|
||||
- ruby-2.3
|
||||
- ruby-2.4
|
||||
- ruby-2.5
|
||||
- ruby-2.6
|
||||
- ruby-2.7
|
||||
- ruby-3.0
|
||||
- ruby-3.1
|
||||
- ruby-3.2
|
||||
- jruby-9.1
|
||||
- jruby-9.2
|
||||
- jruby-9.3
|
||||
- jruby-9.4
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler-cache: true
|
||||
- run: ruby --version
|
||||
- run: bundle exec rspec
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
sudo: false
|
||||
language: ruby
|
||||
|
||||
rvm:
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1.10
|
||||
- 2.2.8
|
||||
- 2.3.5
|
||||
- 2.4.2
|
||||
- jruby-19mode
|
||||
- jruby-head
|
||||
|
||||
before_install:
|
||||
- '[[ "$(ruby --version)" == *"1.9.3"* ]] && gem update --system 2.4.8 || true'
|
||||
|
||||
script:
|
||||
- bundle exec rspec -c spec
|
||||
+13
-4
@@ -1,15 +1,24 @@
|
||||
### 0.1.5 / 2020-06-02
|
||||
|
||||
- Remove a ReDoS vulnerability in the header parser (CVE-2020-7663)
|
||||
|
||||
### 0.1.4 / 2019-06-10
|
||||
|
||||
- Fix a deprecation warning for using the `=~` operator on `true`
|
||||
- Change license from MIT to Apache 2.0
|
||||
|
||||
### 0.1.3 / 2017-11-11
|
||||
|
||||
* Accept extension names and parameters including uppercase letters
|
||||
- Accept extension names and parameters including uppercase letters
|
||||
|
||||
### 0.1.2 / 2015-02-19
|
||||
|
||||
* Make it safe to call `Extensions#close` if the handshake is not complete
|
||||
- Make it safe to call `Extensions#close` if the handshake is not complete
|
||||
|
||||
### 0.1.1 / 2014-12-14
|
||||
|
||||
* Explicitly require `strscan` which is not loaded in a vanilla Ruby environment
|
||||
- Explicitly require `strscan` which is not loaded in a vanilla Ruby environment
|
||||
|
||||
### 0.1.0 / 2014-12-13
|
||||
|
||||
* Initial release
|
||||
- Initial release
|
||||
|
||||
+9
-17
@@ -1,20 +1,12 @@
|
||||
# The MIT license
|
||||
Copyright 2014-2020 James Coglan
|
||||
|
||||
Copyright (c) 2014-2017 James Coglan
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the 'Software'), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# websocket-extensions [](http://travis-ci.org/faye/websocket-extensions-ruby)
|
||||
# websocket-extensions
|
||||
|
||||
A minimal framework that supports the implementation of WebSocket extensions in
|
||||
a way that's decoupled from the main protocol. This library aims to allow a
|
||||
@@ -227,8 +227,8 @@ then the `permessage-deflate` extension will receive the call:
|
||||
|
||||
```rb
|
||||
ext.create_server_session([
|
||||
{'server_no_context_takeover' => true, 'server_max_window_bits' => 8},
|
||||
{'server_max_window_bits' => 15}
|
||||
{ 'server_no_context_takeover' => true, 'server_max_window_bits' => 8 },
|
||||
{ 'server_max_window_bits' => 15 }
|
||||
])
|
||||
```
|
||||
|
||||
@@ -244,8 +244,8 @@ implement the following methods, as well as the *Session* API listed below.
|
||||
```rb
|
||||
client_session.generate_offer
|
||||
# e.g. -> [
|
||||
# {'server_no_context_takeover' => true, 'server_max_window_bits' => 8},
|
||||
# {'server_max_window_bits' => 15}
|
||||
# { 'server_no_context_takeover' => true, 'server_max_window_bits' => 8 },
|
||||
# { 'server_max_window_bits' => 15 }
|
||||
# ]
|
||||
```
|
||||
|
||||
@@ -270,7 +270,7 @@ must implement the following methods, as well as the *Session* API listed below.
|
||||
|
||||
```rb
|
||||
server_session.generate_response
|
||||
# e.g. -> {'server_max_window_bits' => 8}
|
||||
# e.g. -> { 'server_max_window_bits' => 8 }
|
||||
```
|
||||
|
||||
This returns the set of parameters the server session wants to send in its
|
||||
@@ -309,5 +309,5 @@ the session to release any resources it's using.
|
||||
|
||||
## Examples
|
||||
|
||||
* Consumer: [websocket-driver](https://github.com/faye/websocket-driver-ruby)
|
||||
* Provider: [permessage-deflate](https://github.com/faye/permessage-deflate-ruby)
|
||||
- Consumer: [websocket-driver](https://github.com/faye/websocket-driver-ruby)
|
||||
- Provider: [permessage-deflate](https://github.com/faye/permessage-deflate-ruby)
|
||||
|
||||
@@ -38,7 +38,7 @@ module WebSocket
|
||||
end
|
||||
|
||||
if @by_name.has_key?(ext.name)
|
||||
raise TypeError, %Q{An extension with name "#{ext.name}" is already registered}
|
||||
raise TypeError, %Q{An extension with name "#{ ext.name }" is already registered}
|
||||
end
|
||||
|
||||
@by_name[ext.name] = ext
|
||||
@@ -78,18 +78,18 @@ module WebSocket
|
||||
|
||||
responses.each_offer do |name, params|
|
||||
unless record = @index[name]
|
||||
raise ExtensionError, %Q{Server sent am extension response for unknown extension "#{name}"}
|
||||
raise ExtensionError, %Q{Server sent am extension response for unknown extension "#{ name } }
|
||||
end
|
||||
|
||||
ext, session = *record
|
||||
|
||||
if reserved = reserved?(ext)
|
||||
raise ExtensionError, %Q{Server sent two extension responses that use the RSV#{reserved[0]} } +
|
||||
%Q{ bit: "#{reserved[1]}" and "#{ext.name}"}
|
||||
raise ExtensionError, %Q{Server sent two extension responses that use the RSV#{ reserved[0] }} +
|
||||
%Q{bit: "#{ reserved[1] }" and "#{ ext.name }"}
|
||||
end
|
||||
|
||||
unless session.activate(params) == true
|
||||
raise ExtensionError, %Q{Server send unacceptable extension parameters: #{Parser.serialize_params(name, params)}}
|
||||
raise ExtensionError, %Q{Server send unacceptable extension parameters: #{ Parser.serialize_params(name, params) }}
|
||||
end
|
||||
|
||||
reserve(ext)
|
||||
@@ -118,7 +118,7 @@ module WebSocket
|
||||
end
|
||||
|
||||
def valid_frame_rsv(frame)
|
||||
allowed = {:rsv1 => false, :rsv2 => false, :rsv3 => false}
|
||||
allowed = { :rsv1 => false, :rsv2 => false, :rsv3 => false }
|
||||
|
||||
if MESSAGE_OPCODES.include?(frame.opcode)
|
||||
@sessions.each do |ext, session|
|
||||
|
||||
@@ -6,10 +6,10 @@ module WebSocket
|
||||
class Parser
|
||||
TOKEN = /([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)/
|
||||
NOTOKEN = /([^!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z])/
|
||||
QUOTED = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"])*)"/
|
||||
PARAM = %r{#{TOKEN.source}(?:=(?:#{TOKEN.source}|#{QUOTED.source}))?}
|
||||
EXT = %r{#{TOKEN.source}(?: *; *#{PARAM.source})*}
|
||||
EXT_LIST = %r{^#{EXT.source}(?: *, *#{EXT.source})*$}
|
||||
QUOTED = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)"/
|
||||
PARAM = %r{#{ TOKEN.source }(?:=(?:#{ TOKEN.source }|#{ QUOTED.source }))?}
|
||||
EXT = %r{#{ TOKEN.source }(?: *; *#{ PARAM.source })*}
|
||||
EXT_LIST = %r{^#{ EXT.source }(?: *, *#{ EXT.source })*$}
|
||||
NUMBER = /^-?(0|[1-9][0-9]*)(\.[0-9]+)?$/
|
||||
|
||||
ParseError = Class.new(ArgumentError)
|
||||
@@ -19,7 +19,7 @@ module WebSocket
|
||||
return offers if header == '' or header.nil?
|
||||
|
||||
unless header =~ EXT_LIST
|
||||
raise ParseError, "Invalid Sec-WebSocket-Extensions header: #{header}"
|
||||
raise ParseError, "Invalid Sec-WebSocket-Extensions header: #{ header }"
|
||||
end
|
||||
|
||||
scanner = StringScanner.new(header)
|
||||
@@ -38,7 +38,7 @@ module WebSocket
|
||||
else
|
||||
data = true
|
||||
end
|
||||
if data =~ NUMBER
|
||||
if data != true and data =~ NUMBER
|
||||
data = data =~ /\./ ? data.to_f : data.to_i(10)
|
||||
end
|
||||
|
||||
|
||||
@@ -20,62 +20,68 @@ describe WebSocket::Extensions::Parser do
|
||||
|
||||
it "parses one offer with no params" do
|
||||
expect(parse 'a').to eq [
|
||||
{:name => "a", :params => {}}
|
||||
{ :name => "a", :params => {} }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses two offers with no params" do
|
||||
expect(parse 'a, b').to eq [
|
||||
{:name => "a", :params => {}}, {:name => "b", :params => {}}
|
||||
{ :name => "a", :params => {} }, { :name => "b", :params => {} }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses a duplicate offer name" do
|
||||
expect(parse 'a, a').to eq [
|
||||
{:name => "a", :params => {}},
|
||||
{:name => "a", :params => {}}
|
||||
{ :name => "a", :params => {} },
|
||||
{ :name => "a", :params => {} }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses a flag" do
|
||||
expect(parse 'a; b').to eq [
|
||||
{:name => "a", :params => {"b" => true}}
|
||||
{ :name => "a", :params => { "b" => true } }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses an unquoted param" do
|
||||
expect(parse 'a; b=1').to eq [
|
||||
{:name => "a", :params => {"b" => 1}}
|
||||
{ :name => "a", :params => { "b" => 1 } }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses a quoted param" do
|
||||
expect(parse 'a; b="hi, \"there"').to eq [
|
||||
{:name => "a", :params => {"b" => 'hi, "there'}}
|
||||
{ :name => "a", :params => { "b" => 'hi, "there' } }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses multiple params" do
|
||||
expect(parse 'a; b; c=1; d="hi"').to eq [
|
||||
{:name => "a", :params => {"b" => true, "c" => 1, "d" => "hi"}}
|
||||
{ :name => "a", :params => { "b" => true, "c" => 1, "d" => "hi" } }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses duplicate params" do
|
||||
expect(parse 'a; b; c=1; b="hi"').to eq [
|
||||
{:name => "a", :params => {"b" => [true, "hi"], "c" => 1}}
|
||||
{ :name => "a", :params => { "b" => [true, "hi"], "c" => 1 } }
|
||||
]
|
||||
end
|
||||
|
||||
it "parses multiple complex offers" do
|
||||
expect(parse 'a; b=1, c, b; d, c; e="hi, there"; e, a; b').to eq [
|
||||
{:name => "a", :params => {"b" => 1}},
|
||||
{:name => "c", :params => {}},
|
||||
{:name => "b", :params => {"d" => true}},
|
||||
{:name => "c", :params => {"e" => ['hi, there', true]}},
|
||||
{:name => "a", :params => {"b" => true}}
|
||||
{ :name => "a", :params => { "b" => 1 } },
|
||||
{ :name => "c", :params => {} },
|
||||
{ :name => "b", :params => { "d" => true } },
|
||||
{ :name => "c", :params => { "e" => ['hi, there', true] } },
|
||||
{ :name => "a", :params => { "b" => true } }
|
||||
]
|
||||
end
|
||||
|
||||
it "rejects a string missing its closing quote" do
|
||||
expect {
|
||||
parse "foo; bar=\"fooa\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a"
|
||||
}.to raise_error(WebSocket::Extensions::Parser::ParseError)
|
||||
end
|
||||
end
|
||||
|
||||
describe :serialize_params do
|
||||
|
||||
@@ -39,7 +39,7 @@ describe WebSocket::Extensions do
|
||||
|
||||
describe "client sessions" do
|
||||
before do
|
||||
@offer = {"mode" => "compress"}
|
||||
@offer = { "mode" => "compress" }
|
||||
allow(@ext).to receive(:create_client_session).and_return(@session)
|
||||
allow(@session).to receive(:generate_offer).and_return(@offer)
|
||||
@extensions.add(@ext)
|
||||
@@ -134,18 +134,18 @@ describe WebSocket::Extensions do
|
||||
end
|
||||
|
||||
it "activates one session with a boolean param" do
|
||||
expect(@session).to receive(:activate).with("gzip" => true).exactly(1).and_return(true)
|
||||
expect(@session).to receive(:activate).with({ "gzip" => true }).exactly(1).and_return(true)
|
||||
@extensions.activate("deflate; gzip")
|
||||
end
|
||||
|
||||
it "activates one session with a string param" do
|
||||
expect(@session).to receive(:activate).with("mode" => "compress").exactly(1).and_return(true)
|
||||
expect(@session).to receive(:activate).with({ "mode" => "compress" }).exactly(1).and_return(true)
|
||||
@extensions.activate("deflate; mode=compress")
|
||||
end
|
||||
|
||||
it "activate multiple sessions" do
|
||||
expect(@session).to receive(:activate).with("a" => true).exactly(1).and_return(true)
|
||||
expect(@nonconflict_session).to receive(:activate).with("b" => true).exactly(1).and_return(true)
|
||||
expect(@session).to receive(:activate).with({ "a" => true }).exactly(1).and_return(true)
|
||||
expect(@nonconflict_session).to receive(:activate).with({ "b" => true }).exactly(1).and_return(true)
|
||||
@extensions.activate("deflate; a, reverse; b")
|
||||
end
|
||||
|
||||
@@ -248,7 +248,7 @@ describe WebSocket::Extensions do
|
||||
|
||||
describe "server sessions" do
|
||||
before do
|
||||
@response = {"mode" => "compress"}
|
||||
@response = { "mode" => "compress" }
|
||||
allow(@ext).to receive(:create_server_session).and_return(@session)
|
||||
allow(@session).to receive(:generate_response).and_return(@response)
|
||||
|
||||
@@ -269,12 +269,12 @@ describe WebSocket::Extensions do
|
||||
|
||||
describe :generate_response do
|
||||
it "asks the extension for a server session with the offer" do
|
||||
expect(@ext).to receive(:create_server_session).with([{"flag" => true}]).exactly(1).and_return(@session)
|
||||
expect(@ext).to receive(:create_server_session).with([{ "flag" => true }]).exactly(1).and_return(@session)
|
||||
@extensions.generate_response("deflate; flag")
|
||||
end
|
||||
|
||||
it "asks the extension for a server session with multiple offers" do
|
||||
expect(@ext).to receive(:create_server_session).with([{"a" => true}, {"b" => true}]).exactly(1).and_return(@session)
|
||||
expect(@ext).to receive(:create_server_session).with([{ "a" => true }, { "b" => true }]).exactly(1).and_return(@session)
|
||||
@extensions.generate_response("deflate; a, deflate; b")
|
||||
end
|
||||
|
||||
@@ -326,7 +326,7 @@ describe WebSocket::Extensions do
|
||||
end
|
||||
|
||||
it "raises an error if the header is invalid" do
|
||||
expect { @extensions.generate_response("x-webkit- -frame") }.to raise_error
|
||||
expect { @extensions.generate_response("x-webkit- -frame") }.to raise_error(WebSocket::Extensions::Parser::ParseError)
|
||||
end
|
||||
|
||||
it "returns a response for potentially conflicting extensions if their preceding extensions don't build a session" do
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'websocket-extensions'
|
||||
s.version = '0.1.3'
|
||||
s.version = '0.1.5'
|
||||
s.summary = 'Generic extension manager for WebSocket connections'
|
||||
s.author = 'James Coglan'
|
||||
s.email = 'jcoglan@gmail.com'
|
||||
s.homepage = 'https://github.com/faye/websocket-extensions-ruby'
|
||||
s.license = 'MIT'
|
||||
s.license = 'Apache-2.0'
|
||||
|
||||
s.extra_rdoc_files = %w[README.md]
|
||||
s.rdoc_options = %w[--main README.md --markup markdown]
|
||||
|
||||
Reference in New Issue
Block a user