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