websocket 1.2.3 → 1.2.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|