websocket 1.0.7 → 1.1.0
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.
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -0
- data/README.md +1 -0
- data/lib/websocket.rb +15 -4
- data/lib/websocket/error.rb +85 -0
- data/lib/websocket/exception_handler.rb +36 -0
- data/lib/websocket/frame/base.rb +13 -16
- data/lib/websocket/frame/data.rb +1 -1
- data/lib/websocket/frame/handler/base.rb +6 -2
- data/lib/websocket/frame/handler/handler03.rb +49 -55
- data/lib/websocket/frame/handler/handler04.rb +1 -3
- data/lib/websocket/frame/handler/handler05.rb +2 -6
- data/lib/websocket/frame/handler/handler07.rb +5 -7
- data/lib/websocket/frame/handler/handler75.rb +15 -19
- data/lib/websocket/frame/incoming.rb +2 -1
- data/lib/websocket/frame/incoming/client.rb +1 -3
- data/lib/websocket/frame/incoming/server.rb +1 -3
- data/lib/websocket/frame/outgoing.rb +3 -2
- data/lib/websocket/frame/outgoing/client.rb +1 -3
- data/lib/websocket/frame/outgoing/server.rb +1 -3
- data/lib/websocket/handshake/base.rb +9 -5
- data/lib/websocket/handshake/client.rb +13 -12
- data/lib/websocket/handshake/handler/base.rb +10 -2
- data/lib/websocket/handshake/handler/client.rb +3 -5
- data/lib/websocket/handshake/handler/client01.rb +2 -4
- data/lib/websocket/handshake/handler/client04.rb +6 -8
- data/lib/websocket/handshake/handler/client75.rb +4 -6
- data/lib/websocket/handshake/handler/client76.rb +3 -5
- data/lib/websocket/handshake/handler/server.rb +1 -3
- data/lib/websocket/handshake/handler/server04.rb +7 -5
- data/lib/websocket/handshake/handler/server75.rb +3 -5
- data/lib/websocket/handshake/handler/server76.rb +9 -11
- data/lib/websocket/handshake/server.rb +49 -10
- data/lib/websocket/version.rb +1 -1
- data/spec/frame/incoming_03_spec.rb +3 -3
- data/spec/frame/incoming_04_spec.rb +3 -3
- data/spec/frame/incoming_05_spec.rb +3 -3
- data/spec/frame/incoming_07_spec.rb +3 -3
- data/spec/frame/incoming_75_spec.rb +2 -2
- data/spec/handshake/client_04_spec.rb +3 -3
- data/spec/handshake/client_76_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -0
- data/spec/support/all_server_drafts.rb +33 -0
- data/spec/support/frames_base.rb +1 -3
- data/spec/support/incoming_frames.rb +26 -1
- data/spec/support/overwrites.rb +9 -0
- data/websocket.gemspec +0 -2
- metadata +29 -53
@@ -3,15 +3,11 @@
|
|
3
3
|
module WebSocket
|
4
4
|
module Frame
|
5
5
|
module Handler
|
6
|
-
|
7
|
-
|
8
|
-
include Handler04
|
9
|
-
|
10
|
-
private
|
6
|
+
class Handler05 < Handler04
|
11
7
|
|
12
8
|
def encode_frame
|
13
9
|
if @code
|
14
|
-
@data = Data.new([@code].pack('n') + @data.to_s)
|
10
|
+
@frame.data = Data.new([@code].pack('n') + @frame.data.to_s)
|
15
11
|
@code = nil
|
16
12
|
end
|
17
13
|
super
|
@@ -3,11 +3,7 @@
|
|
3
3
|
module WebSocket
|
4
4
|
module Frame
|
5
5
|
module Handler
|
6
|
-
|
7
|
-
|
8
|
-
include Handler05
|
9
|
-
|
10
|
-
private
|
6
|
+
class Handler07 < Handler05
|
11
7
|
|
12
8
|
# Hash of frame names and it's opcodes
|
13
9
|
FRAME_TYPES = {
|
@@ -22,12 +18,14 @@ module WebSocket
|
|
22
18
|
# Hash of frame opcodes and it's names
|
23
19
|
FRAME_TYPES_INVERSE = FRAME_TYPES.invert
|
24
20
|
|
21
|
+
private
|
22
|
+
|
25
23
|
# Convert frame type name to opcode
|
26
24
|
# @param [Symbol] frame_type Frame type name
|
27
25
|
# @return [Integer] opcode or nil
|
28
26
|
# @raise [WebSocket::Error] if frame opcode is not known
|
29
27
|
def type_to_opcode(frame_type)
|
30
|
-
FRAME_TYPES[frame_type] || raise(WebSocket::Error
|
28
|
+
FRAME_TYPES[frame_type] || raise(WebSocket::Error::Frame::UnknownFrameType)
|
31
29
|
end
|
32
30
|
|
33
31
|
# Convert frame opcode to type name
|
@@ -35,7 +33,7 @@ module WebSocket
|
|
35
33
|
# @return [Symbol] Frame type name or nil
|
36
34
|
# @raise [WebSocket::Error] if frame type name is not known
|
37
35
|
def opcode_to_type(opcode)
|
38
|
-
FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error
|
36
|
+
FRAME_TYPES_INVERSE[opcode] || raise(WebSocket::Error::Frame::UnknownOpcode)
|
39
37
|
end
|
40
38
|
|
41
39
|
end
|
@@ -3,23 +3,19 @@
|
|
3
3
|
module WebSocket
|
4
4
|
module Frame
|
5
5
|
module Handler
|
6
|
-
|
7
|
-
|
8
|
-
include Base
|
6
|
+
class Handler75 < Base
|
9
7
|
|
10
8
|
# @see WebSocket::Frame::Base#supported_frames
|
11
9
|
def supported_frames
|
12
10
|
[:text, :close]
|
13
11
|
end
|
14
12
|
|
15
|
-
private
|
16
|
-
|
17
13
|
# @see WebSocket::Frame::Handler::Base#encode_frame
|
18
14
|
def encode_frame
|
19
|
-
case @type
|
15
|
+
case @frame.type
|
20
16
|
when :close then "\xff\x00"
|
21
17
|
when :text then
|
22
|
-
ary = ["\x00", @data, "\xff"]
|
18
|
+
ary = ["\x00", @frame.data, "\xff"]
|
23
19
|
ary.collect{ |s| s.encode('UTF-8', 'UTF-8', :invalid => :replace) if s.respond_to?(:encode) }
|
24
20
|
ary.join
|
25
21
|
end
|
@@ -27,10 +23,10 @@ module WebSocket
|
|
27
23
|
|
28
24
|
# @see WebSocket::Frame::Handler::Base#decode_frame
|
29
25
|
def decode_frame
|
30
|
-
return if @data.size == 0
|
26
|
+
return if @frame.data.size == 0
|
31
27
|
|
32
28
|
pointer = 0
|
33
|
-
frame_type = @data.getbyte(pointer)
|
29
|
+
frame_type = @frame.data.getbyte(pointer)
|
34
30
|
pointer += 1
|
35
31
|
|
36
32
|
if (frame_type & 0x80) == 0x80
|
@@ -38,40 +34,40 @@ module WebSocket
|
|
38
34
|
length = 0
|
39
35
|
|
40
36
|
loop do
|
41
|
-
return if !@data.getbyte(pointer)
|
42
|
-
b = @data.getbyte(pointer)
|
37
|
+
return if !@frame.data.getbyte(pointer)
|
38
|
+
b = @frame.data.getbyte(pointer)
|
43
39
|
pointer += 1
|
44
40
|
b_v = b & 0x7F
|
45
41
|
length = length * 128 + b_v
|
46
42
|
break unless (b & 0x80) == 0x80
|
47
43
|
end
|
48
44
|
|
49
|
-
|
45
|
+
raise WebSocket::Error::Frame::TooLong if length > ::WebSocket.max_frame_size
|
50
46
|
|
51
|
-
unless @data.getbyte(pointer+length-1) == nil
|
47
|
+
unless @frame.data.getbyte(pointer+length-1) == nil
|
52
48
|
# Straight from spec - I'm sure this isn't crazy...
|
53
49
|
# 6. Read /length/ bytes.
|
54
50
|
# 7. Discard the read bytes.
|
55
|
-
@data
|
51
|
+
@frame.instance_variable_set '@data', @frame.data[(pointer+length)..-1]
|
56
52
|
|
57
53
|
# If the /frame type/ is 0xFF and the /length/ was 0, then close
|
58
54
|
if length == 0
|
59
|
-
|
55
|
+
@frame.class.new(:version => @frame.version, :type => :close, :decoded => true)
|
60
56
|
end
|
61
57
|
end
|
62
58
|
else
|
63
59
|
# If the high-order bit of the /frame type/ byte is _not_ set
|
64
60
|
|
65
|
-
|
61
|
+
raise WebSocket::Error::Frame::Invalid if @frame.data.getbyte(0) != 0x00
|
66
62
|
|
67
63
|
# Addition to the spec to protect against malicious requests
|
68
|
-
|
64
|
+
raise WebSocket::Error::Frame::TooLong if @frame.data.size > ::WebSocket.max_frame_size
|
69
65
|
|
70
|
-
msg = @data.slice!(/\A\x00[^\xff]*\xff/)
|
66
|
+
msg = @frame.data.slice!(/\A\x00[^\xff]*\xff/)
|
71
67
|
if msg
|
72
68
|
msg.gsub!(/\A\x00|\xff\z/, '')
|
73
69
|
msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
|
74
|
-
|
70
|
+
@frame.class.new(:version => @frame.version, :type => :text, :data => msg, :decoded => true)
|
75
71
|
end
|
76
72
|
end
|
77
73
|
end
|
@@ -37,8 +37,9 @@ module WebSocket
|
|
37
37
|
# Check #error if nil received to check for eventual parsing errors
|
38
38
|
# @return [WebSocket::Frame::Incoming] Single incoming frame or nil if no complete frame is available.
|
39
39
|
def next
|
40
|
-
decode_frame unless decoded?
|
40
|
+
@handler.decode_frame unless decoded?
|
41
41
|
end
|
42
|
+
rescue_method :next
|
42
43
|
|
43
44
|
# If decoded then this will return frame content. Otherwise it will return raw frame.
|
44
45
|
# @return [String] Data of frame
|
@@ -25,9 +25,10 @@ module WebSocket
|
|
25
25
|
|
26
26
|
# Return raw frame formatted for sending.
|
27
27
|
def to_s
|
28
|
-
|
29
|
-
encode_frame
|
28
|
+
raise WebSocket::Error::Frame::UnknownFrameType unless supported?
|
29
|
+
@handler.encode_frame
|
30
30
|
end
|
31
|
+
rescue_method :to_s
|
31
32
|
|
32
33
|
end
|
33
34
|
end
|
@@ -2,13 +2,15 @@ module WebSocket
|
|
2
2
|
module Handshake
|
3
3
|
# @abstract Subclass and override to implement custom handshakes
|
4
4
|
class Base
|
5
|
+
include ExceptionHandler
|
5
6
|
|
6
7
|
attr_reader :host, :port, :path, :query,
|
7
|
-
:
|
8
|
+
:state, :version, :secure, :headers
|
8
9
|
|
9
10
|
# Initialize new WebSocket Handshake and set it's state to :new
|
10
11
|
def initialize(args = {})
|
11
12
|
@state = :new
|
13
|
+
@handler = nil
|
12
14
|
|
13
15
|
@data = ""
|
14
16
|
@headers = {}
|
@@ -22,8 +24,9 @@ module WebSocket
|
|
22
24
|
# Return textual representation of handshake request or response
|
23
25
|
# @return [String] text of response
|
24
26
|
def to_s
|
25
|
-
""
|
27
|
+
@handler ? @handler.to_s : ""
|
26
28
|
end
|
29
|
+
rescue_method :to_s, :return => ""
|
27
30
|
|
28
31
|
# Recreate inspect as #to_s was overwritten
|
29
32
|
def inspect
|
@@ -41,8 +44,9 @@ module WebSocket
|
|
41
44
|
# Is parsed data valid?
|
42
45
|
# @return [Boolean] False if some errors occured. Reason for error could be found in error method
|
43
46
|
def valid?
|
44
|
-
finished? && @error == nil
|
47
|
+
finished? && @error == nil && @handler && @handler.valid?
|
45
48
|
end
|
49
|
+
rescue_method :valid?, :return => false
|
46
50
|
|
47
51
|
# @abstract Should send data after parsing is finished?
|
48
52
|
def should_respond?
|
@@ -80,7 +84,7 @@ module WebSocket
|
|
80
84
|
# @param [String] message Error message to set
|
81
85
|
def set_error(message)
|
82
86
|
@state = :error
|
83
|
-
|
87
|
+
super
|
84
88
|
end
|
85
89
|
|
86
90
|
HEADER = /^([^:]+):\s*(.+)$/
|
@@ -94,7 +98,7 @@ module WebSocket
|
|
94
98
|
lines = header.split("\r\n")
|
95
99
|
|
96
100
|
first_line = lines.shift
|
97
|
-
|
101
|
+
parse_first_line(first_line)
|
98
102
|
|
99
103
|
lines.each do |line|
|
100
104
|
h = HEADER.match(line)
|
@@ -33,6 +33,8 @@ module WebSocket
|
|
33
33
|
#
|
34
34
|
class Client < Base
|
35
35
|
|
36
|
+
attr_reader :origin
|
37
|
+
|
36
38
|
# Initialize new WebSocket Client
|
37
39
|
#
|
38
40
|
# @param [Hash] args Arguments for client
|
@@ -72,10 +74,11 @@ module WebSocket
|
|
72
74
|
|
73
75
|
@path = '/' if @path.nil? || @path.empty?
|
74
76
|
|
75
|
-
|
77
|
+
raise WebSocket::Error::Handshake::NoHostProvided unless @host
|
76
78
|
|
77
79
|
include_version
|
78
80
|
end
|
81
|
+
rescue_method :initialize
|
79
82
|
|
80
83
|
# Add text of response from Server. This method will parse content immediately and update state and error(if neccessary)
|
81
84
|
#
|
@@ -95,6 +98,7 @@ module WebSocket
|
|
95
98
|
|
96
99
|
end
|
97
100
|
end
|
101
|
+
rescue_method :<<
|
98
102
|
|
99
103
|
# Should send content to server after finished parsing?
|
100
104
|
# @return [Boolean] false
|
@@ -107,14 +111,13 @@ module WebSocket
|
|
107
111
|
# Include set of methods for selected protocol version
|
108
112
|
# @return [Boolean] false if protocol number is unknown, otherwise true
|
109
113
|
def include_version
|
110
|
-
case @version
|
111
|
-
when 75 then
|
112
|
-
when 76, 0 then
|
113
|
-
when 1..3 then
|
114
|
-
when 4..13 then
|
115
|
-
else
|
114
|
+
@handler = case @version
|
115
|
+
when 75 then Handler::Client75.new(self)
|
116
|
+
when 76, 0 then Handler::Client76.new(self)
|
117
|
+
when 1..3 then Handler::Client01.new(self)
|
118
|
+
when 4..13 then Handler::Client04.new(self)
|
119
|
+
else raise WebSocket::Error::Handshake::UnknownVersion
|
116
120
|
end
|
117
|
-
return true
|
118
121
|
end
|
119
122
|
|
120
123
|
FIRST_LINE = /^HTTP\/1\.1 (\d{3})[\w\s]*$/
|
@@ -124,11 +127,9 @@ module WebSocket
|
|
124
127
|
# @return [Boolean] True if parsed correctly. False otherwise
|
125
128
|
def parse_first_line(line)
|
126
129
|
line_parts = line.match(FIRST_LINE)
|
127
|
-
|
130
|
+
raise WebSocket::Error::Handshake::InvalidHeader unless line_parts
|
128
131
|
status = line_parts[1]
|
129
|
-
|
130
|
-
|
131
|
-
return true
|
132
|
+
raise WebSocket::Error::Handshake::InvalidStatusCode unless status == '101'
|
132
133
|
end
|
133
134
|
|
134
135
|
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
module WebSocket
|
2
2
|
module Handshake
|
3
3
|
module Handler
|
4
|
-
# This
|
5
|
-
|
4
|
+
# This class and it's descendants are included in client or server handshake in order to extend basic functionality
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def initialize(handshake)
|
8
|
+
@handshake = handshake
|
9
|
+
end
|
6
10
|
|
7
11
|
# @see WebSocket::Handshake::Base#to_s
|
8
12
|
def to_s
|
@@ -15,6 +19,10 @@ module WebSocket
|
|
15
19
|
result.join("\r\n")
|
16
20
|
end
|
17
21
|
|
22
|
+
def valid?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
18
26
|
private
|
19
27
|
|
20
28
|
# Set first line of text representation according to specification.
|
@@ -1,16 +1,14 @@
|
|
1
1
|
module WebSocket
|
2
2
|
module Handshake
|
3
3
|
module Handler
|
4
|
-
|
5
|
-
|
6
|
-
include Base
|
4
|
+
class Client < Base
|
7
5
|
|
8
6
|
private
|
9
7
|
|
10
8
|
# @see WebSocket::Handshake::Handler::Base#header_line
|
11
9
|
def header_line
|
12
|
-
path = @path
|
13
|
-
path += "?" + @query if @query
|
10
|
+
path = @handshake.path
|
11
|
+
path += "?" + @handshake.query if @handshake.query
|
14
12
|
"GET #{path} HTTP/1.1"
|
15
13
|
end
|
16
14
|
|
@@ -3,16 +3,14 @@ require 'digest/md5'
|
|
3
3
|
module WebSocket
|
4
4
|
module Handshake
|
5
5
|
module Handler
|
6
|
-
|
7
|
-
|
8
|
-
include Client76
|
6
|
+
class Client01 < Client76
|
9
7
|
|
10
8
|
private
|
11
9
|
|
12
10
|
# @see WebSocket::Handshake::Handler::Base#handshake_keys
|
13
11
|
def handshake_keys
|
14
12
|
keys = super
|
15
|
-
keys << ['Sec-WebSocket-Draft', @version]
|
13
|
+
keys << ['Sec-WebSocket-Draft', @handshake.version]
|
16
14
|
keys
|
17
15
|
end
|
18
16
|
|
@@ -4,9 +4,7 @@ require 'base64'
|
|
4
4
|
module WebSocket
|
5
5
|
module Handshake
|
6
6
|
module Handler
|
7
|
-
|
8
|
-
|
9
|
-
include Client
|
7
|
+
class Client04 < Client
|
10
8
|
|
11
9
|
# @see WebSocket::Handshake::Base#valid?
|
12
10
|
def valid?
|
@@ -21,11 +19,11 @@ module WebSocket
|
|
21
19
|
["Upgrade", "websocket"],
|
22
20
|
["Connection", "Upgrade"]
|
23
21
|
]
|
24
|
-
host = @host
|
25
|
-
host += ":#{@port}" if @port
|
22
|
+
host = @handshake.host
|
23
|
+
host += ":#{@handshake.port}" if @handshake.port
|
26
24
|
keys << ["Host", host]
|
27
|
-
keys << ["Sec-WebSocket-Origin", @origin] if @origin
|
28
|
-
keys << ["Sec-WebSocket-Version", @version ]
|
25
|
+
keys << ["Sec-WebSocket-Origin", @handshake.origin] if @handshake.origin
|
26
|
+
keys << ["Sec-WebSocket-Version", @handshake.version ]
|
29
27
|
keys << ["Sec-WebSocket-Key", key]
|
30
28
|
keys
|
31
29
|
end
|
@@ -45,7 +43,7 @@ module WebSocket
|
|
45
43
|
# Verify if received header Sec-WebSocket-Accept matches generated one.
|
46
44
|
# @return [Boolean] True if accept is matching. False otherwise(appropriate error is set)
|
47
45
|
def verify_accept
|
48
|
-
|
46
|
+
raise WebSocket::Error::Handshake::InvalidAuthentication unless @handshake.headers['sec-websocket-accept'] == accept
|
49
47
|
true
|
50
48
|
end
|
51
49
|
|