sprsquish-blather 0.1 → 0.2.3
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/LICENSE +2 -0
- data/README.rdoc +100 -0
- data/Rakefile +110 -0
- data/examples/drb_client.rb +5 -0
- data/examples/echo.rb +18 -0
- data/ext/extconf.rb +65 -0
- data/ext/push_parser.c +231 -0
- data/lib/blather/client.rb +219 -44
- data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
- data/lib/blather/core_ext/libxml.rb +28 -0
- data/lib/blather/errors/sasl_error.rb +87 -0
- data/lib/blather/errors/stanza_error.rb +262 -0
- data/lib/blather/errors/stream_error.rb +253 -0
- data/lib/blather/errors.rb +48 -0
- data/lib/blather/{core/jid.rb → jid.rb} +15 -26
- data/lib/blather/{core/roster.rb → roster.rb} +22 -0
- data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
- data/lib/blather/stanza/iq/disco.rb +11 -0
- data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
- data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
- data/lib/blather/stanza/iq/query.rb +51 -0
- data/lib/blather/stanza/iq/roster.rb +90 -0
- data/lib/blather/stanza/iq.rb +38 -0
- data/lib/blather/stanza/message.rb +58 -0
- data/lib/blather/stanza/presence/status.rb +78 -0
- data/lib/blather/stanza/presence/subscription.rb +72 -0
- data/lib/blather/stanza/presence.rb +45 -0
- data/lib/blather/stanza.rb +101 -0
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +70 -0
- data/lib/blather/stream/resource.rb +48 -0
- data/lib/blather/stream/sasl.rb +173 -0
- data/lib/blather/stream/session.rb +36 -0
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +33 -0
- data/lib/blather/stream.rb +249 -0
- data/lib/blather/xmpp_node.rb +199 -0
- data/lib/blather.rb +40 -41
- data/spec/blather/core_ext/libxml_spec.rb +58 -0
- data/spec/blather/errors/sasl_error_spec.rb +56 -0
- data/spec/blather/errors/stanza_error_spec.rb +148 -0
- data/spec/blather/errors/stream_error_spec.rb +114 -0
- data/spec/blather/errors_spec.rb +40 -0
- data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
- data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
- data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
- data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +34 -0
- data/spec/blather/stanza/iq/roster_spec.rb +123 -0
- data/spec/blather/stanza/iq_spec.rb +40 -0
- data/spec/blather/stanza/message_spec.rb +52 -0
- data/spec/blather/stanza/presence/status_spec.rb +102 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
- data/spec/blather/stanza/presence_spec.rb +53 -0
- data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
- data/spec/build_safe.rb +20 -0
- data/spec/spec_helper.rb +7 -17
- metadata +79 -59
- data/CHANGELOG +0 -1
- data/blather.gemspec +0 -73
- data/lib/blather/callback.rb +0 -24
- data/lib/blather/core/errors.rb +0 -24
- data/lib/blather/core/stanza.rb +0 -90
- data/lib/blather/core/stream.rb +0 -179
- data/lib/blather/core/xmpp_node.rb +0 -95
- data/lib/blather/extensions/last_activity.rb +0 -57
- data/lib/blather/extensions/version.rb +0 -85
- data/spec/blather/core/stream_spec.rb +0 -263
@@ -0,0 +1,787 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'Blather::Stream::Client' do
|
4
|
+
class MockServer; end
|
5
|
+
module ServerMock
|
6
|
+
def receive_data(data)
|
7
|
+
@server ||= MockServer.new
|
8
|
+
@server.receive_data data, self
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def mocked_server(times = nil, &block)
|
13
|
+
@client ||= mock()
|
14
|
+
@client.stubs(:stopped) unless @client.respond_to?(:stopped)
|
15
|
+
@client.stubs(:jid=) unless @client.respond_to?(:jid=)
|
16
|
+
|
17
|
+
MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
|
18
|
+
EventMachine::run {
|
19
|
+
# Mocked server
|
20
|
+
EventMachine::start_server '127.0.0.1', 12345, ServerMock
|
21
|
+
|
22
|
+
# Stream connection
|
23
|
+
EM.connect('127.0.0.1', 12345, Stream::Client, @client, @jid || JID.new('n@d/r'), 'pass') { |c| @stream = c }
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'can be started' do
|
28
|
+
client = mock()
|
29
|
+
params = [client, 'n@d/r', 'pass', 'host', 1234]
|
30
|
+
EM.expects(:connect).with do |*parms|
|
31
|
+
parms[0] == 'host' &&
|
32
|
+
parms[1] == 1234 &&
|
33
|
+
parms[3] == client &&
|
34
|
+
parms[5] == 'pass' &&
|
35
|
+
parms[4] == JID.new('n@d/r')
|
36
|
+
end
|
37
|
+
|
38
|
+
Stream::Client.start *(params)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can figure out the host to use based on the jid' do
|
42
|
+
client = mock()
|
43
|
+
params = [client, 'n@d/r', 'pass', 'd', 5222]
|
44
|
+
EM.expects(:connect).with do |*parms|
|
45
|
+
parms[0] == 'd' &&
|
46
|
+
parms[1] == 5222 &&
|
47
|
+
parms[3] == client &&
|
48
|
+
parms[5] == 'pass' &&
|
49
|
+
parms[4] == JID.new('n@d/r')
|
50
|
+
end
|
51
|
+
|
52
|
+
Stream::Client.start client, 'n@d/r', 'pass'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'starts the stream once the connection is complete' do
|
56
|
+
mocked_server(1) { |val, _| EM.stop; val.must_match(/stream:stream/) }
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'sends stanzas to the client when the stream is ready' do
|
60
|
+
@client = mock()
|
61
|
+
@client.expects(:call).with do |n|
|
62
|
+
EM.stop
|
63
|
+
n.kind_of?(Stanza::Message) && @stream.ready?.must_equal(true)
|
64
|
+
end
|
65
|
+
|
66
|
+
mocked_server(1) do |val, server|
|
67
|
+
val.must_match(/stream:stream/)
|
68
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
69
|
+
server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'puts itself in the stopped state and calls @client.stopped when stopped' do
|
74
|
+
@client = mock()
|
75
|
+
@client.expects(:stopped).at_least_once
|
76
|
+
|
77
|
+
started = false
|
78
|
+
mocked_server(2) do |val, server|
|
79
|
+
if !started
|
80
|
+
started = true
|
81
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
82
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
83
|
+
val.must_match(/stream:stream/)
|
84
|
+
|
85
|
+
else
|
86
|
+
EM.stop
|
87
|
+
@stream.stopped?.must_equal false
|
88
|
+
@stream.unbind
|
89
|
+
@stream.stopped?.must_equal true
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'will be in the negotiating state during feature negotiations' do
|
96
|
+
state = nil
|
97
|
+
@client = mock()
|
98
|
+
@client.stubs(:stream_started)
|
99
|
+
@client.expects(:call).with do |n|
|
100
|
+
EM.stop
|
101
|
+
state.must_equal(:negotiated) && @stream.negotiating?.must_equal(false)
|
102
|
+
end
|
103
|
+
|
104
|
+
mocked_server(2) do |val, server|
|
105
|
+
case state
|
106
|
+
when nil
|
107
|
+
state = :started
|
108
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
109
|
+
server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
|
110
|
+
true
|
111
|
+
|
112
|
+
when :started
|
113
|
+
state = :negotiated
|
114
|
+
@stream.negotiating?.must_equal(true)
|
115
|
+
server.send_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}' />"
|
116
|
+
server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
|
117
|
+
true
|
118
|
+
|
119
|
+
else
|
120
|
+
EM.stop
|
121
|
+
false
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'stops when sent </stream:stream>' do
|
128
|
+
state = nil
|
129
|
+
mocked_server(3) do |val, server|
|
130
|
+
case state
|
131
|
+
when nil
|
132
|
+
state = :started
|
133
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xml:lang='en'>"
|
134
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
135
|
+
val.must_match(/stream:stream/)
|
136
|
+
|
137
|
+
when :started
|
138
|
+
state = :stopped
|
139
|
+
server.send_data '</stream:stream>'
|
140
|
+
@stream.stopped?.must_equal false
|
141
|
+
|
142
|
+
when :stopped
|
143
|
+
EM.stop
|
144
|
+
@stream.stopped?.must_equal true
|
145
|
+
val.must_equal '</stream:stream>'
|
146
|
+
|
147
|
+
else
|
148
|
+
EM.stop
|
149
|
+
false
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'sends client an error on stream:error' do
|
156
|
+
@client = mock()
|
157
|
+
@client.expects(:call).with do |v|
|
158
|
+
v.must_be_instance_of(StreamError::Conflict)
|
159
|
+
v.text.must_equal 'Already signed in'
|
160
|
+
v.to_s.must_equal "Stream Error (conflict): #{v.text}"
|
161
|
+
end
|
162
|
+
|
163
|
+
state = nil
|
164
|
+
mocked_server(3) do |val, server|
|
165
|
+
case state
|
166
|
+
when nil
|
167
|
+
state = :started
|
168
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
|
169
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
170
|
+
val.must_match(/stream:stream/)
|
171
|
+
|
172
|
+
when :started
|
173
|
+
state = :stopped
|
174
|
+
server.send_data "<stream:error><conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams' />"
|
175
|
+
server.send_data "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>Already signed in</text></stream:error>"
|
176
|
+
|
177
|
+
when :stopped
|
178
|
+
EM.stop
|
179
|
+
val.must_equal "</stream:stream>"
|
180
|
+
|
181
|
+
else
|
182
|
+
EM.stop
|
183
|
+
false
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'starts TLS when asked' do
|
190
|
+
state = nil
|
191
|
+
mocked_server(3) do |val, server|
|
192
|
+
case state
|
193
|
+
when nil
|
194
|
+
state = :started
|
195
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
|
196
|
+
val.must_match(/stream:stream/)
|
197
|
+
|
198
|
+
when :started
|
199
|
+
state = :tls
|
200
|
+
@stream.expects(:start_tls)
|
201
|
+
server.send_data "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
|
202
|
+
val.must_match(/starttls/)
|
203
|
+
|
204
|
+
when :tls
|
205
|
+
EM.stop
|
206
|
+
true
|
207
|
+
|
208
|
+
else
|
209
|
+
EM.stop
|
210
|
+
false
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'will fail if TLS negotiation fails' do
|
217
|
+
state = nil
|
218
|
+
@client = mock()
|
219
|
+
@client.expects(:call).with { |v| v.must_be_kind_of TLSFailure }
|
220
|
+
mocked_server(3) do |val, server|
|
221
|
+
case state
|
222
|
+
when nil
|
223
|
+
state = :started
|
224
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
|
225
|
+
val.must_match(/stream:stream/)
|
226
|
+
|
227
|
+
when :started
|
228
|
+
state = :tls
|
229
|
+
@stream.expects(:start_tls).never
|
230
|
+
server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
|
231
|
+
val.must_match(/starttls/)
|
232
|
+
|
233
|
+
when :tls
|
234
|
+
EM.stop
|
235
|
+
val.must_equal "</stream:stream>"
|
236
|
+
|
237
|
+
else
|
238
|
+
EM.stop
|
239
|
+
false
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'will fail if a bad node comes through TLS negotiations' do
|
246
|
+
state = nil
|
247
|
+
@client = mock()
|
248
|
+
@client.expects(:call).with do |v|
|
249
|
+
v.must_be_kind_of UnknownResponse
|
250
|
+
v.node.element_name.must_equal 'foo-bar'
|
251
|
+
end
|
252
|
+
mocked_server(3) do |val, server|
|
253
|
+
case state
|
254
|
+
when nil
|
255
|
+
state = :started
|
256
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
|
257
|
+
val.must_match(/stream:stream/)
|
258
|
+
|
259
|
+
when :started
|
260
|
+
state = :tls
|
261
|
+
@stream.expects(:start_tls).never
|
262
|
+
server.send_data "<foo-bar xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
|
263
|
+
val.must_match(/starttls/)
|
264
|
+
|
265
|
+
when :tls
|
266
|
+
EM.stop
|
267
|
+
val.must_equal "</stream:stream>"
|
268
|
+
|
269
|
+
else
|
270
|
+
EM.stop
|
271
|
+
false
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'connects via SASL MD5 when asked' do
|
278
|
+
Time.any_instance.stubs(:to_f).returns(1.1)
|
279
|
+
state = nil
|
280
|
+
|
281
|
+
mocked_server(5) do |val, server|
|
282
|
+
case state
|
283
|
+
when nil
|
284
|
+
state = :started
|
285
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features>"
|
286
|
+
val.must_match(/stream:stream/)
|
287
|
+
|
288
|
+
when :started
|
289
|
+
state = :auth_sent
|
290
|
+
server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge>"
|
291
|
+
val.must_match(/auth.*DIGEST\-MD5/)
|
292
|
+
|
293
|
+
when :auth_sent
|
294
|
+
state = :response1_sent
|
295
|
+
server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>"
|
296
|
+
val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2QiLHJlc3BvbnNlPTZiNTlhY2Q1ZWJmZjhjZTA0NTYzMGFiMDU2Zjg3MTdm</response>')
|
297
|
+
|
298
|
+
when :response1_sent
|
299
|
+
state = :response2_sent
|
300
|
+
server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
|
301
|
+
val.must_match(%r{<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"\s?/>})
|
302
|
+
|
303
|
+
when :response2_sent
|
304
|
+
EM.stop
|
305
|
+
state = :complete
|
306
|
+
val.must_match(/stream:stream/)
|
307
|
+
|
308
|
+
else
|
309
|
+
EM.stop
|
310
|
+
false
|
311
|
+
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'will connect via SSL PLAIN when asked' do
|
317
|
+
state = nil
|
318
|
+
mocked_server(3) do |val, server|
|
319
|
+
case state
|
320
|
+
when nil
|
321
|
+
state = :started
|
322
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
|
323
|
+
val.must_match(/stream:stream/)
|
324
|
+
|
325
|
+
when :started
|
326
|
+
state = :auth_sent
|
327
|
+
server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
|
328
|
+
val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
|
329
|
+
|
330
|
+
when :auth_sent
|
331
|
+
EM.stop
|
332
|
+
state = :complete
|
333
|
+
val.must_match(/stream:stream/)
|
334
|
+
|
335
|
+
else
|
336
|
+
EM.stop
|
337
|
+
false
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'will connect via SSL ANONYMOUS when asked' do
|
344
|
+
state = nil
|
345
|
+
|
346
|
+
mocked_server(3) do |val, server|
|
347
|
+
case state
|
348
|
+
when nil
|
349
|
+
state = :started
|
350
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
|
351
|
+
val.must_match(/stream:stream/)
|
352
|
+
|
353
|
+
when :started
|
354
|
+
state = :auth_sent
|
355
|
+
server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
|
356
|
+
val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS">bg==</auth>')
|
357
|
+
|
358
|
+
when :auth_sent
|
359
|
+
EM.stop
|
360
|
+
state = :complete
|
361
|
+
val.must_match(/stream:stream/)
|
362
|
+
|
363
|
+
else
|
364
|
+
EM.stop
|
365
|
+
false
|
366
|
+
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'tried each possible mechanism until it fails completely' do
|
372
|
+
state = nil
|
373
|
+
@client = mock()
|
374
|
+
@client.expects(:call).with do |n|
|
375
|
+
n.must_be_kind_of(SASLError)
|
376
|
+
n.must_be_instance_of SASLError::NotAuthorized
|
377
|
+
end
|
378
|
+
|
379
|
+
mocked_server(5) do |val, server|
|
380
|
+
case state
|
381
|
+
when nil
|
382
|
+
state = :started
|
383
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
|
384
|
+
val.must_match(/stream:stream/)
|
385
|
+
|
386
|
+
when :started
|
387
|
+
state = :failed_md5
|
388
|
+
server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
|
389
|
+
val.must_match(/mechanism="DIGEST-MD5"/)
|
390
|
+
|
391
|
+
when :failed_md5
|
392
|
+
state = :failed_plain
|
393
|
+
server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
|
394
|
+
val.must_match(/mechanism="PLAIN"/)
|
395
|
+
|
396
|
+
when :failed_plain
|
397
|
+
state = :failed_anon
|
398
|
+
server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
|
399
|
+
val.must_match(/mechanism="ANONYMOUS"/)
|
400
|
+
|
401
|
+
when :failed_anon
|
402
|
+
EM.stop
|
403
|
+
state = :complete
|
404
|
+
val.must_match(/\/stream:stream/)
|
405
|
+
|
406
|
+
else
|
407
|
+
EM.stop
|
408
|
+
false
|
409
|
+
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'tries each mechanism until it succeeds' do
|
415
|
+
state = nil
|
416
|
+
mocked_server(4) do |val, server|
|
417
|
+
case state
|
418
|
+
when nil
|
419
|
+
state = :started
|
420
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
|
421
|
+
val.must_match(/stream:stream/)
|
422
|
+
|
423
|
+
when :started
|
424
|
+
state = :failed_md5
|
425
|
+
server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
|
426
|
+
val.must_match(/mechanism="DIGEST-MD5"/)
|
427
|
+
|
428
|
+
when :failed_md5
|
429
|
+
state = :plain_sent
|
430
|
+
server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
|
431
|
+
val.must_match(/mechanism="PLAIN"/)
|
432
|
+
|
433
|
+
when :plain_sent
|
434
|
+
EM.stop
|
435
|
+
val.must_match(/stream:stream/)
|
436
|
+
|
437
|
+
else
|
438
|
+
EM.stop
|
439
|
+
false
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
it 'sends client an error when an unknown mechanism is sent' do
|
446
|
+
@client = mock()
|
447
|
+
@client.expects(:call).with { |v| v.must_be_kind_of(Stream::SASL::UnknownMechanism) }
|
448
|
+
started = false
|
449
|
+
mocked_server(2) do |val, server|
|
450
|
+
if !started
|
451
|
+
started = true
|
452
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
453
|
+
server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>UNKNOWN</mechanism></mechanisms></stream:features>"
|
454
|
+
val.must_match(/stream:stream/)
|
455
|
+
|
456
|
+
else
|
457
|
+
EM.stop
|
458
|
+
val.must_match(/failure(.*)invalid\-mechanism/)
|
459
|
+
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
%w[ aborted
|
465
|
+
incorrect-encoding
|
466
|
+
invalid-authzid
|
467
|
+
invalid-mechanism
|
468
|
+
mechanism-too-weak
|
469
|
+
not-authorized
|
470
|
+
temporary-auth-failure
|
471
|
+
].each do |error_type|
|
472
|
+
it "fails on #{error_type}" do
|
473
|
+
@client = mock()
|
474
|
+
@client.expects(:call).with do |n|
|
475
|
+
n.must_be_instance_of SASLError.class_from_registration(error_type)
|
476
|
+
end
|
477
|
+
state = nil
|
478
|
+
mocked_server(3) do |val, server|
|
479
|
+
case state
|
480
|
+
when nil
|
481
|
+
state = :started
|
482
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
|
483
|
+
val.must_match(/stream:stream/)
|
484
|
+
|
485
|
+
when :started
|
486
|
+
state = :auth_sent
|
487
|
+
server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><#{error_type} /></failure>"
|
488
|
+
val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
|
489
|
+
|
490
|
+
when :auth_sent
|
491
|
+
EM.stop
|
492
|
+
state = :complete
|
493
|
+
val.must_match(/\/stream:stream/)
|
494
|
+
|
495
|
+
else
|
496
|
+
EM.stop
|
497
|
+
false
|
498
|
+
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
it 'fails when an unkown node comes through during SASL negotiation' do
|
505
|
+
@client = mock()
|
506
|
+
@client.expects(:call).with do |n|
|
507
|
+
n.must_be_instance_of UnknownResponse
|
508
|
+
n.node.element_name.must_equal 'foo-bar'
|
509
|
+
end
|
510
|
+
state = nil
|
511
|
+
mocked_server(3) do |val, server|
|
512
|
+
case state
|
513
|
+
when nil
|
514
|
+
state = :started
|
515
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
|
516
|
+
val.must_match(/stream:stream/)
|
517
|
+
|
518
|
+
when :started
|
519
|
+
state = :auth_sent
|
520
|
+
server.send_data "<foo-bar />"
|
521
|
+
val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
|
522
|
+
|
523
|
+
when :auth_sent
|
524
|
+
EM.stop
|
525
|
+
state = :complete
|
526
|
+
val.must_match(/\/stream:stream/)
|
527
|
+
|
528
|
+
else
|
529
|
+
EM.stop
|
530
|
+
false
|
531
|
+
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'will bind to a resource set by the server' do
|
537
|
+
state = nil
|
538
|
+
class Client; attr_accessor :jid; end
|
539
|
+
@client = Client.new
|
540
|
+
@jid = JID.new('n@d')
|
541
|
+
|
542
|
+
mocked_server(3) do |val, server|
|
543
|
+
case state
|
544
|
+
when nil
|
545
|
+
state = :started
|
546
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
547
|
+
val.must_match(/stream:stream/)
|
548
|
+
|
549
|
+
when :started
|
550
|
+
state = :complete
|
551
|
+
val =~ %r{<iq[^>]+id="([^"]+)"}
|
552
|
+
server.send_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}/server_resource</jid></bind></iq>"
|
553
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
554
|
+
val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s?/>})
|
555
|
+
|
556
|
+
when :complete
|
557
|
+
EM.stop
|
558
|
+
@client.jid.must_equal JID.new('n@d/server_resource')
|
559
|
+
|
560
|
+
else
|
561
|
+
EM.stop
|
562
|
+
false
|
563
|
+
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
it 'will bind to a resource set by the client' do
|
569
|
+
state = nil
|
570
|
+
class Client; attr_accessor :jid; end
|
571
|
+
@client = Client.new
|
572
|
+
@jid = JID.new('n@d/r')
|
573
|
+
|
574
|
+
mocked_server(3) do |val, server|
|
575
|
+
case state
|
576
|
+
when nil
|
577
|
+
state = :started
|
578
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
579
|
+
val.must_match(/stream:stream/)
|
580
|
+
|
581
|
+
when :started
|
582
|
+
state = :complete
|
583
|
+
val =~ %r{<iq[^>]+id="([^"]+)"}
|
584
|
+
server.send_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}</jid></bind></iq>"
|
585
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
586
|
+
val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
|
587
|
+
|
588
|
+
when :complete
|
589
|
+
EM.stop
|
590
|
+
@client.jid.must_equal JID.new('n@d/r')
|
591
|
+
|
592
|
+
else
|
593
|
+
EM.stop
|
594
|
+
false
|
595
|
+
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
it 'will return an error if resource binding errors out' do
|
601
|
+
state = nil
|
602
|
+
@client = mock()
|
603
|
+
@client.expects(:call).with do |n|
|
604
|
+
n.must_be_instance_of StanzaError::BadRequest
|
605
|
+
end
|
606
|
+
mocked_server(3) do |val, server|
|
607
|
+
case state
|
608
|
+
when nil
|
609
|
+
state = :started
|
610
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
611
|
+
val.must_match(/stream:stream/)
|
612
|
+
|
613
|
+
when :started
|
614
|
+
state = :complete
|
615
|
+
val =~ %r{<iq[^>]+id="([^"]+)"}
|
616
|
+
server.send_data "<iq type='error' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>r</resource></bind><error type='modify'><bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"
|
617
|
+
val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
|
618
|
+
|
619
|
+
when :complete
|
620
|
+
EM.stop
|
621
|
+
val.must_match(/\/stream:stream/)
|
622
|
+
|
623
|
+
else
|
624
|
+
EM.stop
|
625
|
+
false
|
626
|
+
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
it 'will return an error if an unkown node comes through during resouce binding' do
|
632
|
+
state = nil
|
633
|
+
@client = mock()
|
634
|
+
@client.expects(:call).with do |n|
|
635
|
+
n.must_be_instance_of UnknownResponse
|
636
|
+
n.node.element_name.must_equal 'foo-bar'
|
637
|
+
end
|
638
|
+
mocked_server(3) do |val, server|
|
639
|
+
case state
|
640
|
+
when nil
|
641
|
+
state = :started
|
642
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
643
|
+
val.must_match(/stream:stream/)
|
644
|
+
|
645
|
+
when :started
|
646
|
+
state = :complete
|
647
|
+
val =~ %r{<iq[^>]+id="([^"]+)"}
|
648
|
+
server.send_data "<foo-bar />"
|
649
|
+
val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
|
650
|
+
|
651
|
+
when :complete
|
652
|
+
EM.stop
|
653
|
+
val.must_match(/\/stream:stream/)
|
654
|
+
|
655
|
+
else
|
656
|
+
EM.stop
|
657
|
+
false
|
658
|
+
|
659
|
+
end
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
it 'will establish a session if requested' do
|
664
|
+
state = nil
|
665
|
+
@client = mock()
|
666
|
+
@client.expects(:stream_started)
|
667
|
+
|
668
|
+
mocked_server(3) do |val, server|
|
669
|
+
case state
|
670
|
+
when nil
|
671
|
+
state = :started
|
672
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
673
|
+
server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
|
674
|
+
val.must_match(/stream:stream/)
|
675
|
+
|
676
|
+
when :started
|
677
|
+
state = :completed
|
678
|
+
server.send_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}' />"
|
679
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
680
|
+
val.must_match(%r{<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"\s?/></iq>})
|
681
|
+
|
682
|
+
when :completed
|
683
|
+
EM.stop
|
684
|
+
true
|
685
|
+
|
686
|
+
else
|
687
|
+
EM.stop
|
688
|
+
false
|
689
|
+
|
690
|
+
end
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
it 'will return an error if session establishment errors out' do
|
695
|
+
state = nil
|
696
|
+
@client = mock()
|
697
|
+
@client.expects(:call).with do |n|
|
698
|
+
n.must_be_instance_of StanzaError::InternalServerError
|
699
|
+
end
|
700
|
+
mocked_server(3) do |val, server|
|
701
|
+
case state
|
702
|
+
when nil
|
703
|
+
state = :started
|
704
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
705
|
+
server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
|
706
|
+
val.must_match(/stream:stream/)
|
707
|
+
|
708
|
+
when :started
|
709
|
+
state = :completed
|
710
|
+
server.send_data "<iq from='d' type='error' id='#{val[/id="([^"]+)"/,1]}'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/><error type='wait'><internal-server-error xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"
|
711
|
+
val.must_match(%r{<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"\s?/></iq>})
|
712
|
+
|
713
|
+
when :completed
|
714
|
+
EM.stop
|
715
|
+
val.must_match(/\/stream:stream/)
|
716
|
+
|
717
|
+
else
|
718
|
+
EM.stop
|
719
|
+
false
|
720
|
+
|
721
|
+
end
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
it 'will return an error if an unknown node come through during session establishment' do
|
726
|
+
state = nil
|
727
|
+
@client = mock()
|
728
|
+
@client.expects(:call).with do |n|
|
729
|
+
n.must_be_instance_of UnknownResponse
|
730
|
+
n.node.element_name.must_equal 'foo-bar'
|
731
|
+
end
|
732
|
+
mocked_server(3) do |val, server|
|
733
|
+
case state
|
734
|
+
when nil
|
735
|
+
state = :started
|
736
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
|
737
|
+
server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
|
738
|
+
val.must_match(/stream:stream/)
|
739
|
+
|
740
|
+
when :started
|
741
|
+
state = :completed
|
742
|
+
server.send_data '<foo-bar />'
|
743
|
+
val.must_match(%r{<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"\s?/></iq>})
|
744
|
+
|
745
|
+
when :completed
|
746
|
+
EM.stop
|
747
|
+
val.must_match(/\/stream:stream/)
|
748
|
+
|
749
|
+
else
|
750
|
+
EM.stop
|
751
|
+
false
|
752
|
+
|
753
|
+
end
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
it 'sends client an error on parse error' do
|
758
|
+
@client = mock()
|
759
|
+
@client.expects(:call).with do |v|
|
760
|
+
v.must_be_kind_of ParseError
|
761
|
+
v.message.must_match(/generate\-parse\-error/)
|
762
|
+
end
|
763
|
+
state = nil
|
764
|
+
mocked_server(3) do |val, server|
|
765
|
+
case state
|
766
|
+
when nil
|
767
|
+
state = :started
|
768
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
|
769
|
+
server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
|
770
|
+
val.must_match(/stream:stream/)
|
771
|
+
|
772
|
+
when :started
|
773
|
+
state = :parse_error
|
774
|
+
server.send_data "</generate-parse-error>"
|
775
|
+
|
776
|
+
when :parse_error
|
777
|
+
EM.stop
|
778
|
+
val.must_equal "</stream:stream>"
|
779
|
+
|
780
|
+
else
|
781
|
+
EM.stop
|
782
|
+
false
|
783
|
+
|
784
|
+
end
|
785
|
+
end
|
786
|
+
end
|
787
|
+
end
|