faye-websocket 0.1.2 → 0.2.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.

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