websocket 1.2.3 → 1.2.9
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 +5 -5
- data/.codeclimate.yml +17 -0
- data/.github/workflows/publish.yml +17 -0
- data/.rubocop.yml +35 -3
- data/.travis.yml +14 -7
- data/CHANGELOG.md +24 -0
- data/Gemfile +12 -4
- data/README.md +3 -2
- data/Rakefile +8 -3
- data/lib/websocket.rb +4 -1
- data/lib/websocket/error.rb +8 -0
- data/lib/websocket/exception_handler.rb +3 -1
- data/lib/websocket/frame.rb +2 -0
- data/lib/websocket/frame/base.rb +5 -9
- data/lib/websocket/frame/data.rb +5 -2
- data/lib/websocket/frame/handler.rb +2 -0
- data/lib/websocket/frame/handler/base.rb +6 -4
- data/lib/websocket/frame/handler/handler03.rb +23 -16
- data/lib/websocket/frame/handler/handler04.rb +1 -0
- data/lib/websocket/frame/handler/handler05.rb +1 -0
- data/lib/websocket/frame/handler/handler07.rb +10 -9
- data/lib/websocket/frame/handler/handler75.rb +8 -7
- data/lib/websocket/frame/incoming.rb +2 -0
- data/lib/websocket/frame/incoming/client.rb +2 -0
- data/lib/websocket/frame/incoming/server.rb +2 -0
- data/lib/websocket/frame/outgoing.rb +3 -1
- data/lib/websocket/frame/outgoing/client.rb +2 -0
- data/lib/websocket/frame/outgoing/server.rb +2 -0
- data/lib/websocket/handshake.rb +2 -0
- data/lib/websocket/handshake/base.rb +26 -13
- data/lib/websocket/handshake/client.rb +17 -14
- data/lib/websocket/handshake/handler.rb +2 -0
- data/lib/websocket/handshake/handler/base.rb +2 -0
- data/lib/websocket/handshake/handler/client.rb +11 -0
- data/lib/websocket/handshake/handler/client01.rb +2 -0
- data/lib/websocket/handshake/handler/client04.rb +15 -4
- data/lib/websocket/handshake/handler/client11.rb +2 -0
- data/lib/websocket/handshake/handler/client75.rb +18 -2
- data/lib/websocket/handshake/handler/client76.rb +11 -5
- data/lib/websocket/handshake/handler/server.rb +2 -0
- data/lib/websocket/handshake/handler/server04.rb +12 -4
- data/lib/websocket/handshake/handler/server75.rb +21 -5
- data/lib/websocket/handshake/handler/server76.rb +14 -16
- data/lib/websocket/handshake/server.rb +7 -4
- data/lib/websocket/nice_inspect.rb +12 -0
- data/lib/websocket/version.rb +3 -1
- data/spec/frame/incoming_03_spec.rb +20 -17
- data/spec/frame/incoming_04_spec.rb +20 -17
- data/spec/frame/incoming_05_spec.rb +22 -19
- data/spec/frame/incoming_07_spec.rb +24 -21
- data/spec/frame/incoming_75_spec.rb +13 -10
- data/spec/frame/incoming_common_spec.rb +18 -8
- data/spec/frame/masking_spec.rb +3 -1
- data/spec/frame/outgoing_03_spec.rb +13 -10
- data/spec/frame/outgoing_04_spec.rb +13 -10
- data/spec/frame/outgoing_05_spec.rb +12 -9
- data/spec/frame/outgoing_07_spec.rb +13 -10
- data/spec/frame/outgoing_75_spec.rb +8 -5
- data/spec/frame/outgoing_common_spec.rb +11 -4
- data/spec/handshake/client_04_spec.rb +46 -3
- data/spec/handshake/client_11_spec.rb +5 -3
- data/spec/handshake/client_75_spec.rb +28 -1
- data/spec/handshake/client_76_spec.rb +30 -3
- data/spec/handshake/server_04_spec.rb +37 -4
- data/spec/handshake/server_75_spec.rb +25 -1
- data/spec/handshake/server_76_spec.rb +33 -9
- data/spec/spec_helper.rb +2 -2
- data/spec/support/all_client_drafts.rb +23 -21
- data/spec/support/all_server_drafts.rb +21 -16
- data/spec/support/frames_base.rb +2 -0
- data/spec/support/handshake_requests.rb +19 -17
- data/spec/support/incoming_frames.rb +46 -25
- data/spec/support/outgoing_frames.rb +30 -8
- data/spec/support/overwrites.rb +2 -0
- data/websocket.gemspec +4 -1
- metadata +7 -5
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: binary
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module WebSocket
|
4
5
|
module Frame
|
@@ -12,15 +13,15 @@ module WebSocket
|
|
12
13
|
close: 8,
|
13
14
|
ping: 9,
|
14
15
|
pong: 10
|
15
|
-
}
|
16
|
+
}.freeze
|
16
17
|
|
17
18
|
# Hash of frame opcodes and it's names
|
18
|
-
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
19
|
+
FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze
|
19
20
|
|
20
21
|
def encode_frame
|
21
22
|
if @frame.type == :close
|
22
23
|
code = @frame.code || 1000
|
23
|
-
|
24
|
+
raise WebSocket::Error::Frame::UnknownCloseCode unless valid_code?(code)
|
24
25
|
@frame.data = Data.new([code].pack('n') + @frame.data.to_s)
|
25
26
|
@frame.code = nil
|
26
27
|
end
|
@@ -32,8 +33,8 @@ module WebSocket
|
|
32
33
|
if close_code?(result)
|
33
34
|
code = result.data.slice!(0..1)
|
34
35
|
result.code = code.unpack('n').first
|
35
|
-
|
36
|
-
|
36
|
+
raise WebSocket::Error::Frame::UnknownCloseCode unless valid_code?(result.code)
|
37
|
+
raise WebSocket::Error::Frame::InvalidPayloadEncoding unless valid_encoding?(result.data)
|
37
38
|
end
|
38
39
|
result
|
39
40
|
end
|
@@ -41,14 +42,14 @@ module WebSocket
|
|
41
42
|
private
|
42
43
|
|
43
44
|
def valid_code?(code)
|
44
|
-
[1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011].include?(code) || (3000..4999).
|
45
|
+
[1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011].include?(code) || (3000..4999).cover?(code)
|
45
46
|
end
|
46
47
|
|
47
48
|
def valid_encoding?(data)
|
48
49
|
return true if data.nil?
|
49
50
|
data.encode('UTF-8')
|
50
51
|
true
|
51
|
-
rescue
|
52
|
+
rescue StandardError
|
52
53
|
false
|
53
54
|
end
|
54
55
|
|
@@ -61,7 +62,7 @@ module WebSocket
|
|
61
62
|
# @return [Integer] opcode or nil
|
62
63
|
# @raise [WebSocket::Error] if frame opcode is not known
|
63
64
|
def type_to_opcode(frame_type)
|
64
|
-
FRAME_TYPES[frame_type] ||
|
65
|
+
FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType)
|
65
66
|
end
|
66
67
|
|
67
68
|
# Convert frame opcode to type name
|
@@ -69,7 +70,7 @@ module WebSocket
|
|
69
70
|
# @return [Symbol] Frame type name or nil
|
70
71
|
# @raise [WebSocket::Error] if frame type name is not known
|
71
72
|
def opcode_to_type(opcode)
|
72
|
-
FRAME_TYPES_INVERSE[opcode] ||
|
73
|
+
FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: binary
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module WebSocket
|
4
5
|
module Frame
|
@@ -6,7 +7,7 @@ module WebSocket
|
|
6
7
|
class Handler75 < Base
|
7
8
|
# @see WebSocket::Frame::Base#supported_frames
|
8
9
|
def supported_frames
|
9
|
-
[
|
10
|
+
%i[text close]
|
10
11
|
end
|
11
12
|
|
12
13
|
# @see WebSocket::Frame::Handler::Base#encode_frame
|
@@ -17,13 +18,13 @@ module WebSocket
|
|
17
18
|
ary = ["\x00", @frame.data, "\xff"]
|
18
19
|
ary.map { |s| s.encode('UTF-8', 'UTF-8', invalid: :replace) }
|
19
20
|
ary.join
|
20
|
-
else
|
21
|
+
else raise WebSocket::Error::Frame::UnknownFrameType
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
25
|
# @see WebSocket::Frame::Handler::Base#decode_frame
|
25
26
|
def decode_frame
|
26
|
-
return if @frame.data.size
|
27
|
+
return if @frame.data.size.zero?
|
27
28
|
|
28
29
|
pointer = 0
|
29
30
|
frame_type = @frame.data.getbyte(pointer)
|
@@ -42,7 +43,7 @@ module WebSocket
|
|
42
43
|
break unless (b & 0x80) == 0x80
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
+
raise WebSocket::Error::Frame::TooLong if length > ::WebSocket.max_frame_size
|
46
47
|
|
47
48
|
unless @frame.data.getbyte(pointer + length - 1).nil?
|
48
49
|
# Straight from spec - I'm sure this isn't crazy...
|
@@ -51,17 +52,17 @@ module WebSocket
|
|
51
52
|
@frame.instance_variable_set '@data', @frame.data[(pointer + length)..-1]
|
52
53
|
|
53
54
|
# If the /frame type/ is 0xFF and the /length/ was 0, then close
|
54
|
-
if length
|
55
|
+
if length.zero?
|
55
56
|
@frame.class.new(version: @frame.version, type: :close, decoded: true)
|
56
57
|
end
|
57
58
|
end
|
58
59
|
else
|
59
60
|
# If the high-order bit of the /frame type/ byte is _not_ set
|
60
61
|
|
61
|
-
|
62
|
+
raise WebSocket::Error::Frame::Invalid if @frame.data.getbyte(0) != 0x00
|
62
63
|
|
63
64
|
# Addition to the spec to protect against malicious requests
|
64
|
-
|
65
|
+
raise WebSocket::Error::Frame::TooLong if @frame.data.size > ::WebSocket.max_frame_size
|
65
66
|
|
66
67
|
msg = @frame.data.slice!(/\A\x00[^\xff]*\xff/)
|
67
68
|
if msg
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebSocket
|
2
4
|
module Frame
|
3
5
|
# Construct or parse incoming WebSocket Frame.
|
@@ -24,7 +26,7 @@ module WebSocket
|
|
24
26
|
|
25
27
|
# Return raw frame formatted for sending.
|
26
28
|
def to_s
|
27
|
-
|
29
|
+
raise WebSocket::Error::Frame::UnknownFrameType unless supported?
|
28
30
|
@handler.encode_frame
|
29
31
|
end
|
30
32
|
rescue_method :to_s
|
data/lib/websocket/handshake.rb
CHANGED
@@ -1,21 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebSocket
|
2
4
|
module Handshake
|
3
5
|
# @abstract Subclass and override to implement custom handshakes
|
4
6
|
class Base
|
5
7
|
include ExceptionHandler
|
8
|
+
include NiceInspect
|
6
9
|
|
7
10
|
attr_reader :host, :port, :path, :query,
|
8
|
-
:state, :version, :secure,
|
11
|
+
:state, :version, :secure,
|
12
|
+
:headers, :protocols
|
9
13
|
|
10
14
|
# Initialize new WebSocket Handshake and set it's state to :new
|
11
15
|
def initialize(args = {})
|
12
|
-
args.each
|
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
|
13
24
|
|
14
25
|
@state = :new
|
15
26
|
@handler = nil
|
16
27
|
|
17
|
-
@data = ''
|
28
|
+
@data = String.new('')
|
18
29
|
@headers ||= {}
|
30
|
+
@protocols ||= []
|
19
31
|
end
|
20
32
|
|
21
33
|
# @abstract Add data to handshake
|
@@ -30,13 +42,6 @@ module WebSocket
|
|
30
42
|
end
|
31
43
|
rescue_method :to_s, return: ''
|
32
44
|
|
33
|
-
# Recreate inspect as #to_s was overwritten
|
34
|
-
def inspect
|
35
|
-
vars = instance_variables.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(', ')
|
36
|
-
insp = Kernel.format("#{self.class}:0x%08x", __id__)
|
37
|
-
"<#{insp} #{vars}>"
|
38
|
-
end
|
39
|
-
|
40
45
|
# Is parsing of data finished?
|
41
46
|
# @return [Boolena] True if request was completely parsed or error occured. False otherwise
|
42
47
|
def finished?
|
@@ -52,7 +57,7 @@ module WebSocket
|
|
52
57
|
|
53
58
|
# @abstract Should send data after parsing is finished?
|
54
59
|
def should_respond?
|
55
|
-
|
60
|
+
raise NotImplementedError
|
56
61
|
end
|
57
62
|
|
58
63
|
# Data left from parsing. Sometimes data that doesn't belong to handshake are added - use this method to retrieve them.
|
@@ -66,7 +71,7 @@ module WebSocket
|
|
66
71
|
# @example
|
67
72
|
# @handshake.uri #=> "ws://example.com/path?query=true"
|
68
73
|
def uri
|
69
|
-
uri = secure ? 'wss://' : 'ws://'
|
74
|
+
uri = String.new(secure ? 'wss://' : 'ws://')
|
70
75
|
uri << host
|
71
76
|
uri << ":#{port}" if port
|
72
77
|
uri << path
|
@@ -104,7 +109,15 @@ module WebSocket
|
|
104
109
|
|
105
110
|
lines.each do |line|
|
106
111
|
h = HEADER.match(line)
|
107
|
-
|
112
|
+
next unless h # Skip any invalid headers
|
113
|
+
key = h[1].strip.downcase
|
114
|
+
val = h[2].strip
|
115
|
+
# If the header is already set and refers to the websocket protocol, append the new value
|
116
|
+
if @headers.key?(key) && key =~ /^(sec-)?websocket-protocol$/
|
117
|
+
@headers[key] << ", #{val}"
|
118
|
+
else
|
119
|
+
@headers[key] = val
|
120
|
+
end
|
108
121
|
end
|
109
122
|
|
110
123
|
@state = :finished
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
|
3
5
|
module WebSocket
|
@@ -38,16 +40,17 @@ module WebSocket
|
|
38
40
|
#
|
39
41
|
# @param [Hash] args Arguments for client
|
40
42
|
#
|
41
|
-
# @option args [String]
|
42
|
-
# @option args [String]
|
43
|
-
# @option args [String]
|
44
|
-
# @option args [Integer]
|
45
|
-
# @option args [String]
|
46
|
-
# @option args [Boolean]
|
47
|
-
# @option args [String]
|
48
|
-
# @option args [String]
|
49
|
-
# @option args [
|
50
|
-
# @option args [
|
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
|
51
54
|
#
|
52
55
|
# @example
|
53
56
|
# Websocket::Handshake::Client.new(url: "ws://example.com/path?query=true")
|
@@ -66,7 +69,7 @@ module WebSocket
|
|
66
69
|
@path = '/' if @path.nil? || @path.empty?
|
67
70
|
@version ||= DEFAULT_VERSION
|
68
71
|
|
69
|
-
|
72
|
+
raise WebSocket::Error::Handshake::NoHostProvided unless @host
|
70
73
|
|
71
74
|
include_version
|
72
75
|
end
|
@@ -107,7 +110,7 @@ module WebSocket
|
|
107
110
|
when 1..3 then Handler::Client01.new(self)
|
108
111
|
when 4..10 then Handler::Client04.new(self)
|
109
112
|
when 11..17 then Handler::Client11.new(self)
|
110
|
-
else
|
113
|
+
else raise WebSocket::Error::Handshake::UnknownVersion
|
111
114
|
end
|
112
115
|
end
|
113
116
|
|
@@ -118,9 +121,9 @@ module WebSocket
|
|
118
121
|
# @return [Boolean] True if parsed correctly. False otherwise
|
119
122
|
def parse_first_line(line)
|
120
123
|
line_parts = line.match(FIRST_LINE)
|
121
|
-
|
124
|
+
raise WebSocket::Error::Handshake::InvalidHeader unless line_parts
|
122
125
|
status = line_parts[1]
|
123
|
-
|
126
|
+
raise WebSocket::Error::Handshake::InvalidStatusCode unless status == '101'
|
124
127
|
end
|
125
128
|
end
|
126
129
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebSocket
|
2
4
|
module Handshake
|
3
5
|
module Handler
|
@@ -15,6 +17,15 @@ module WebSocket
|
|
15
17
|
def handshake_keys
|
16
18
|
super + @handshake.headers.to_a
|
17
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 WebSocket::Error::Handshake::UnsupportedProtocol if protos.empty?
|
27
|
+
true
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
20
31
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'digest/sha1'
|
2
4
|
require 'base64'
|
3
5
|
|
@@ -7,7 +9,7 @@ module WebSocket
|
|
7
9
|
class Client04 < Client
|
8
10
|
# @see WebSocket::Handshake::Base#valid?
|
9
11
|
def valid?
|
10
|
-
super && verify_accept
|
12
|
+
super && verify_accept && verify_protocol
|
11
13
|
end
|
12
14
|
|
13
15
|
private
|
@@ -15,8 +17,8 @@ module WebSocket
|
|
15
17
|
# @see WebSocket::Handshake::Handler::Base#handshake_keys
|
16
18
|
def handshake_keys
|
17
19
|
keys = [
|
18
|
-
%w
|
19
|
-
%w
|
20
|
+
%w[Upgrade websocket],
|
21
|
+
%w[Connection Upgrade]
|
20
22
|
]
|
21
23
|
host = @handshake.host
|
22
24
|
host += ":#{@handshake.port}" if @handshake.port
|
@@ -25,6 +27,7 @@ module WebSocket
|
|
25
27
|
keys << ['Sec-WebSocket-Origin', @handshake.origin] if @handshake.origin
|
26
28
|
keys << ['Sec-WebSocket-Version', @handshake.version]
|
27
29
|
keys << ['Sec-WebSocket-Key', key]
|
30
|
+
keys << ['Sec-WebSocket-Protocol', @handshake.protocols.join(', ')] if @handshake.protocols.any?
|
28
31
|
keys
|
29
32
|
end
|
30
33
|
|
@@ -43,9 +46,17 @@ module WebSocket
|
|
43
46
|
# Verify if received header Sec-WebSocket-Accept matches generated one.
|
44
47
|
# @return [Boolean] True if accept is matching. False otherwise(appropriate error is set)
|
45
48
|
def verify_accept
|
46
|
-
|
49
|
+
raise WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.headers['sec-websocket-accept'] == accept
|
47
50
|
true
|
48
51
|
end
|
52
|
+
|
53
|
+
def supported_protocols
|
54
|
+
@handshake.protocols
|
55
|
+
end
|
56
|
+
|
57
|
+
def provided_protocols
|
58
|
+
@handshake.headers['sec-websocket-protocol'].to_s.split(/ *, */)
|
59
|
+
end
|
49
60
|
end
|
50
61
|
end
|
51
62
|
end
|
@@ -1,22 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module WebSocket
|
2
4
|
module Handshake
|
3
5
|
module Handler
|
4
6
|
class Client75 < Client
|
7
|
+
# @see WebSocket::Handshake::Base#valid?
|
8
|
+
def valid?
|
9
|
+
super && verify_protocol
|
10
|
+
end
|
11
|
+
|
5
12
|
private
|
6
13
|
|
7
14
|
# @see WebSocket::Handshake::Handler::Base#handshake_keys
|
8
15
|
def handshake_keys
|
9
16
|
keys = [
|
10
|
-
%w
|
11
|
-
%w
|
17
|
+
%w[Upgrade WebSocket],
|
18
|
+
%w[Connection Upgrade]
|
12
19
|
]
|
13
20
|
host = @handshake.host
|
14
21
|
host += ":#{@handshake.port}" if @handshake.port
|
15
22
|
keys << ['Host', host]
|
16
23
|
keys << ['Origin', @handshake.origin] if @handshake.origin
|
24
|
+
keys << ['WebSocket-Protocol', @handshake.protocols.first] if @handshake.protocols.any?
|
17
25
|
keys += super
|
18
26
|
keys
|
19
27
|
end
|
28
|
+
|
29
|
+
def supported_protocols
|
30
|
+
Array(@handshake.protocols.first)
|
31
|
+
end
|
32
|
+
|
33
|
+
def provided_protocols
|
34
|
+
Array(@handshake.headers['websocket-protocol'].to_s.strip)
|
35
|
+
end
|
20
36
|
end
|
21
37
|
end
|
22
38
|
end
|