faye-websocket 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye-websocket might be problematic. Click here for more details.

@@ -0,0 +1,78 @@
1
+ module Faye
2
+ class WebSocket
3
+ class HybiParser
4
+
5
+ class Handshake
6
+ GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
7
+
8
+ attr_reader :protocol
9
+
10
+ def initialize(uri, protocols)
11
+ @uri = uri
12
+ @protocols = protocols
13
+ @key = Base64.encode64((1..16).map { rand(255).chr } * '').strip
14
+ @accept = Base64.encode64(Digest::SHA1.digest(@key + GUID)).strip
15
+ @buffer = []
16
+ end
17
+
18
+ def request_data
19
+ hostname = @uri.host + (@uri.port ? ":#{@uri.port}" : '')
20
+
21
+ headers = [
22
+ "GET #{@uri.path}#{@uri.query ? '?' : ''}#{@uri.query} HTTP/1.1",
23
+ "Host: #{hostname}",
24
+ "Upgrade: websocket",
25
+ "Connection: Upgrade",
26
+ "Sec-WebSocket-Key: #{@key}",
27
+ "Sec-WebSocket-Version: 13"
28
+ ]
29
+
30
+ if @protocols
31
+ headers << "Sec-WebSocket-Protocol: #{@protocols * ', '}"
32
+ end
33
+
34
+ (headers + ['','']).join("\r\n")
35
+ end
36
+
37
+ def parse(data)
38
+ message = []
39
+ complete = false
40
+ data.each_byte do |byte|
41
+ if complete
42
+ message << byte
43
+ else
44
+ @buffer << byte
45
+ complete ||= complete?
46
+ end
47
+ end
48
+ message
49
+ end
50
+
51
+ def complete?
52
+ @buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
53
+ end
54
+
55
+ def valid?
56
+ data = WebSocket.encode(@buffer)
57
+
58
+ response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
59
+ return false unless response.code.to_i == 101
60
+
61
+ upgrade = response['Upgrade']
62
+ connection = response['Connection']
63
+ protocol = response['Sec-WebSocket-Protocol']
64
+
65
+ @protocol = @protocols && @protocols.include?(protocol) ?
66
+ protocol :
67
+ nil
68
+
69
+ upgrade and upgrade =~ /^websocket$/i and
70
+ connection and connection.split(/\s*,\s*/).include?('Upgrade') and
71
+ ((!@protocols and !protocol) or @protocol) and
72
+ response['Sec-WebSocket-Accept'] == @accept
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,29 @@
1
+ module Faye
2
+ class WebSocket
3
+ class HybiParser
4
+
5
+ class StreamReader
6
+ def initialize
7
+ @queue = []
8
+ end
9
+
10
+ def read(length)
11
+ read_bytes(length)
12
+ end
13
+
14
+ def put(bytes)
15
+ return unless bytes and bytes.size > 0
16
+ @queue.concat(bytes)
17
+ end
18
+
19
+ private
20
+
21
+ def read_bytes(length)
22
+ return nil if length > @queue.size
23
+ @queue.shift(length)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -15,7 +15,7 @@ WebSocketSteps = EM::RSpec.async_steps do
15
15
  EM.next_tick(&callback)
16
16
  end
17
17
 
18
- def open_socket(url, &callback)
18
+ def open_socket(url, protocols, &callback)
19
19
  done = false
20
20
 
21
21
  resume = lambda do |open|
@@ -26,7 +26,7 @@ WebSocketSteps = EM::RSpec.async_steps do
26
26
  end
27
27
  end
28
28
 
29
- @ws = Faye::WebSocket::Client.new(url)
29
+ @ws = Faye::WebSocket::Client.new(url, protocols)
30
30
 
31
31
  @ws.onopen = lambda { |e| resume.call(true) }
32
32
  @ws.onclose = lambda { |e| resume.call(false) }
@@ -50,6 +50,11 @@ WebSocketSteps = EM::RSpec.async_steps do
50
50
  callback.call
51
51
  end
52
52
 
53
+ def check_protocol(protocol, &callback)
54
+ @ws.protocol.should == protocol
55
+ callback.call
56
+ end
57
+
53
58
  def listen_for_message(&callback)
54
59
  @ws.add_event_listener('message', lambda { |e| @message = e.data })
55
60
  callback.call
@@ -74,6 +79,7 @@ end
74
79
  describe Faye::WebSocket::Client do
75
80
  include WebSocketSteps
76
81
 
82
+ let(:protocols) { ["foo", "echo"] }
77
83
  let(:plain_text_url) { "ws://0.0.0.0:8000/" }
78
84
  let(:secure_url) { "wss://0.0.0.0:8000/" }
79
85
 
@@ -84,23 +90,29 @@ describe Faye::WebSocket::Client do
84
90
 
85
91
  shared_examples_for "socket client" do
86
92
  it "can open a connection" do
87
- open_socket(socket_url)
93
+ open_socket(socket_url, protocols)
88
94
  check_open
95
+ check_protocol("echo")
89
96
  end
90
97
 
91
98
  it "cannot open a connection to the wrong host" do
92
- open_socket(blocked_url)
99
+ open_socket(blocked_url, protocols)
100
+ check_closed
101
+ end
102
+
103
+ it "cannot open a connection with unacceptable protocols" do
104
+ open_socket(socket_url, ["foo"])
93
105
  check_closed
94
106
  end
95
107
 
96
108
  it "can close the connection" do
97
- open_socket(socket_url)
109
+ open_socket(socket_url, protocols)
98
110
  close_socket
99
111
  check_closed
100
112
  end
101
113
 
102
114
  describe "in the OPEN state" do
103
- before { open_socket(socket_url) }
115
+ before { open_socket(socket_url, protocols) }
104
116
 
105
117
  it "can send and receive messages" do
106
118
  listen_for_message
@@ -111,7 +123,7 @@ describe Faye::WebSocket::Client do
111
123
 
112
124
  describe "in the CLOSED state" do
113
125
  before do
114
- open_socket(socket_url)
126
+ open_socket(socket_url, protocols)
115
127
  close_socket
116
128
  end
117
129
 
@@ -11,21 +11,51 @@ describe Faye::WebSocket::Draft75Parser do
11
11
  end
12
12
 
13
13
  describe :parse do
14
- it "parses text frames" do
15
- @web_socket.should_receive(:receive).with("Hello")
16
- parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
14
+ shared_examples_for "draft-75 parser" do
15
+ it "parses text frames" do
16
+ @web_socket.should_receive(:receive).with("Hello")
17
+ parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
18
+ end
19
+
20
+ it "parses multiple frames from the same packet" do
21
+ @web_socket.should_receive(:receive).with("Hello").exactly(2)
22
+ parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
23
+ end
24
+
25
+ it "parses text frames beginning 0x00-0x7F" do
26
+ @web_socket.should_receive(:receive).with("Hello")
27
+ parse [0x66, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
28
+ end
29
+
30
+ it "ignores frames with a length header" do
31
+ @web_socket.should_not_receive(:receive)
32
+ parse [0x80, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
33
+ end
34
+
35
+ it "parses text following an ignored block" do
36
+ @web_socket.should_receive(:receive).with("Hello")
37
+ parse [0x80, 0x02, 0x48, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
38
+ end
39
+
40
+ it "parses multibyte text frames" do
41
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
42
+ parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
43
+ end
44
+
45
+ it "parses frames received in several packets" do
46
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
47
+ parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65]
48
+ parse [0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
49
+ end
50
+
51
+ it "parses fragmented frames" do
52
+ @web_socket.should_receive(:receive).with("Hello")
53
+ parse [0x00, 0x48, 0x65, 0x6c]
54
+ parse [0x6c, 0x6f, 0xff]
55
+ end
17
56
  end
18
57
 
19
- it "parses multibyte text frames" do
20
- @web_socket.should_receive(:receive).with(encode "Apple = ")
21
- parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
22
- end
23
-
24
- it "parses fragmented frames" do
25
- @web_socket.should_receive(:receive).with("Hello")
26
- parse [0x00, 0x48, 0x65, 0x6c]
27
- parse [0x6c, 0x6f, 0xff]
28
- end
58
+ it_should_behave_like "draft-75 parser"
29
59
  end
30
60
 
31
61
  describe :frame do
@@ -0,0 +1,34 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Faye::WebSocket::Draft76Parser do
6
+ include EncodingHelper
7
+
8
+ before do
9
+ @web_socket = mock Faye::WebSocket
10
+ @parser = Faye::WebSocket::Draft76Parser.new(@web_socket)
11
+ @parser.instance_eval { @handshake_complete = true }
12
+ end
13
+
14
+ describe :parse do
15
+ it_should_behave_like "draft-75 parser"
16
+
17
+ it "closes the socket if a close frame is received" do
18
+ @web_socket.should_receive(:close)
19
+ parse [0xFF, 0x00]
20
+ end
21
+ end
22
+
23
+ describe :frame do
24
+ it "returns the given string formatted as a WebSocket frame" do
25
+ bytes(@parser.frame "Hello").should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
26
+ end
27
+
28
+ it "encodes multibyte characters correctly" do
29
+ message = encode "Apple = "
30
+ bytes(@parser.frame message).should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
31
+ end
32
+ end
33
+ end
34
+
@@ -2,17 +2,17 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Faye::WebSocket::Protocol8Parser do
5
+ describe Faye::WebSocket::HybiParser do
6
6
  include EncodingHelper
7
-
7
+
8
8
  before do
9
9
  @web_socket = mock Faye::WebSocket
10
- @parser = Faye::WebSocket::Protocol8Parser.new(@web_socket)
10
+ @parser = Faye::WebSocket::HybiParser.new(@web_socket)
11
11
  end
12
-
12
+
13
13
  describe :parse do
14
14
  let(:mask) { (1..4).map { rand 255 } }
15
-
15
+
16
16
  def mask_message(*bytes)
17
17
  output = []
18
18
  bytes.each_with_index do |byte, i|
@@ -20,124 +20,135 @@ describe Faye::WebSocket::Protocol8Parser do
20
20
  end
21
21
  output
22
22
  end
23
-
23
+
24
24
  it "parses unmasked text frames" do
25
25
  @web_socket.should_receive(:receive).with("Hello")
26
26
  parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
27
27
  end
28
-
28
+
29
+ it "parses multiple frames from the same packet" do
30
+ @web_socket.should_receive(:receive).with("Hello").exactly(2)
31
+ parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
32
+ end
33
+
29
34
  it "parses empty text frames" do
30
35
  @web_socket.should_receive(:receive).with("")
31
36
  parse [0x81, 0x00]
32
37
  end
33
-
38
+
34
39
  it "parses fragmented text frames" do
35
40
  @web_socket.should_receive(:receive).with("Hello")
36
41
  parse [0x01, 0x03, 0x48, 0x65, 0x6c]
37
42
  parse [0x80, 0x02, 0x6c, 0x6f]
38
43
  end
39
-
44
+
40
45
  it "parses masked text frames" do
41
46
  @web_socket.should_receive(:receive).with("Hello")
42
47
  parse [0x81, 0x85] + mask + mask_message(0x48, 0x65, 0x6c, 0x6c, 0x6f)
43
48
  end
44
-
49
+
45
50
  it "parses masked empty text frames" do
46
51
  @web_socket.should_receive(:receive).with("")
47
52
  parse [0x81, 0x80] + mask + mask_message()
48
53
  end
49
-
54
+
50
55
  it "parses masked fragmented text frames" do
51
56
  @web_socket.should_receive(:receive).with("Hello")
52
57
  parse [0x01, 0x81] + mask + mask_message(0x48)
53
58
  parse [0x80, 0x84] + mask + mask_message(0x65, 0x6c, 0x6c, 0x6f)
54
59
  end
55
-
60
+
56
61
  it "closes the socket if the frame has an unrecognized opcode" do
57
62
  @web_socket.should_receive(:close).with(1002, nil, false)
58
63
  parse [0x83, 0x00]
59
64
  end
60
-
65
+
61
66
  it "closes the socket if a close frame is received" do
62
67
  @web_socket.should_receive(:close).with(1000, "Hello", false)
63
68
  parse [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
64
69
  end
65
-
70
+
66
71
  it "parses unmasked multibyte text frames" do
67
72
  @web_socket.should_receive(:receive).with(encode "Apple = ")
68
73
  parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
69
74
  end
70
-
75
+
76
+ it "parses frames received in several packets" do
77
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
78
+ parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c]
79
+ parse [0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
80
+ end
81
+
71
82
  it "parses fragmented multibyte text frames" do
72
83
  @web_socket.should_receive(:receive).with(encode "Apple = ")
73
84
  parse [0x01, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3]
74
85
  parse [0x80, 0x01, 0xbf]
75
86
  end
76
-
87
+
77
88
  it "parses masked multibyte text frames" do
78
89
  @web_socket.should_receive(:receive).with(encode "Apple = ")
79
90
  parse [0x81, 0x8b] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf)
80
91
  end
81
-
92
+
82
93
  it "parses masked fragmented multibyte text frames" do
83
94
  @web_socket.should_receive(:receive).with(encode "Apple = ")
84
95
  parse [0x01, 0x8a] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3)
85
96
  parse [0x80, 0x81] + mask + mask_message(0xbf)
86
97
  end
87
-
98
+
88
99
  it "parses unmasked medium-length text frames" do
89
100
  @web_socket.should_receive(:receive).with("Hello" * 40)
90
101
  parse [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
91
102
  end
92
-
103
+
93
104
  it "parses masked medium-length text frames" do
94
105
  @web_socket.should_receive(:receive).with("Hello" * 40)
95
106
  parse [0x81, 0xfe, 0x00, 0xc8] + mask + mask_message(*([0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40))
96
107
  end
97
-
108
+
98
109
  it "replies to pings with a pong" do
99
110
  @web_socket.should_receive(:send).with([0x4f, 0x48, 0x41, 0x49], :pong)
100
111
  parse [0x89, 0x04, 0x4f, 0x48, 0x41, 0x49]
101
112
  end
102
113
  end
103
-
114
+
104
115
  describe :frame do
105
116
  it "returns the given string formatted as a WebSocket frame" do
106
117
  bytes(@parser.frame "Hello").should == [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
107
118
  end
108
-
119
+
109
120
  it "encodes multibyte characters correctly" do
110
121
  message = encode "Apple = "
111
122
  bytes(@parser.frame message).should == [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
112
123
  end
113
-
124
+
114
125
  it "encodes medium-length strings using extra length bytes" do
115
126
  message = "Hello" * 40
116
127
  bytes(@parser.frame message).should == [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
117
128
  end
118
-
129
+
119
130
  it "encodes long strings using extra length bytes" do
120
131
  message = "Hello" * 13108
121
132
  bytes(@parser.frame message).should == [0x81, 0x7f] +
122
133
  [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04] +
123
134
  [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 13108
124
135
  end
125
-
136
+
126
137
  it "encodes close frames with an error code" do
127
138
  frame = @parser.frame "Hello", :close, 1002
128
139
  bytes(frame).should == [0x88, 0x07, 0x03, 0xea, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
129
140
  end
130
-
141
+
131
142
  it "encodes pong frames" do
132
143
  bytes(@parser.frame '', :pong).should == [0x8a, 0x00]
133
144
  end
134
145
  end
135
-
146
+
136
147
  describe :utf8 do
137
148
  it "detects valid UTF-8" do
138
149
  Faye::WebSocket.valid_utf8?( [72, 101, 108, 108, 111, 45, 194, 181, 64, 195, 159, 195, 182, 195, 164, 195, 188, 195, 160, 195, 161, 45, 85, 84, 70, 45, 56, 33, 33] ).should be_true
139
150
  end
140
-
151
+
141
152
  it "detects invalid UTF-8" do
142
153
  Faye::WebSocket.valid_utf8?( [206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181, 237, 160, 128, 101, 100, 105, 116, 101, 100] ).should be_false
143
154
  end