tp-blather 0.8.2

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.
Files changed (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ def x_xml
4
+ <<-XML
5
+ <x xmlns='jabber:x:data'
6
+ type='form'>
7
+ <title/>
8
+ <instructions/>
9
+ <field var='field-name'
10
+ type='text-single'
11
+ label='description' />
12
+ <field var='field-name2'
13
+ type='text-single'
14
+ label='description' />
15
+ <field var='field-name3'
16
+ type='text-single'
17
+ label='description' />
18
+ <field var='field-name4'
19
+ type='list-multi'
20
+ label='description'>
21
+ <desc/>
22
+ <required/>
23
+ <value>field-value4</value>
24
+ <option label='option-label'><value>option-value</value></option>
25
+ <option label='option-label'><value>option-value</value></option>
26
+ </field>
27
+ </x>
28
+ XML
29
+ end
30
+
31
+ describe Blather::Stanza::X do
32
+
33
+ it 'can be created from an XML string' do
34
+ x = Blather::Stanza::X.new parse_stanza(x_xml).root
35
+ x.type.should == :form
36
+ x.should be_instance_of Blather::Stanza::X
37
+ end
38
+
39
+ [:cancel, :form, :result, :submit].each do |type|
40
+ it "type can be set as \"#{type}\"" do
41
+ x = Blather::Stanza::X.new type
42
+ x.type.should == type
43
+ end
44
+ end
45
+
46
+ it 'is constructed properly' do
47
+ n = Blather::Stanza::X.new :form
48
+ n.find("/ns:x[@type='form']", :ns => Blather::Stanza::X.registered_ns).should_not be_empty
49
+ end
50
+
51
+ it 'has an action attribute' do
52
+ n = Blather::Stanza::X.new :form
53
+ n.type.should == :form
54
+ n.type = :submit
55
+ n.type.should == :submit
56
+ end
57
+
58
+ it 'has a title attribute' do
59
+ n = Blather::Stanza::X.new :form
60
+ n.title.should == nil
61
+ n.title = "Hello World!"
62
+ n.title.should == "Hello World!"
63
+ n.title = "goodbye"
64
+ n.title.should == "goodbye"
65
+ end
66
+
67
+ it 'has an instructions attribute' do
68
+ n = Blather::Stanza::X.new :form
69
+ n.instructions.should == nil
70
+ n.instructions = "Please fill in this form"
71
+ n.instructions.should == "Please fill in this form"
72
+ n.instructions = "goodbye"
73
+ n.instructions.should == "goodbye"
74
+ end
75
+
76
+ it 'inherits a list of fields' do
77
+ n = Blather::Stanza::Iq::Command.new
78
+ n.command << parse_stanza(x_xml).root
79
+ r = Blather::Stanza::X.new.inherit n.form
80
+ r.fields.size.should == 4
81
+ r.fields.map { |f| f.class }.uniq.should == [Blather::Stanza::X::Field]
82
+ end
83
+
84
+ it 'returns a field object for a particular var' do
85
+ x = Blather::Stanza::X.new parse_stanza(x_xml).root
86
+ f = x.field 'field-name4'
87
+ f.should be_instance_of Blather::Stanza::X::Field
88
+ f.value.should == 'field-value4'
89
+ end
90
+
91
+ it 'takes a list of hashes for fields' do
92
+ fields = [
93
+ {:label => 'label', :type => 'text-single', :var => 'var'},
94
+ {:label => 'label1', :type => 'text-single', :var => 'var1'},
95
+ ]
96
+
97
+ control = [ Blather::Stanza::X::Field.new(*%w[var text-single label]),
98
+ Blather::Stanza::X::Field.new(*%w[var1 text-single label1])]
99
+
100
+ di = Blather::Stanza::X.new nil, fields
101
+ di.fields.size.should == 2
102
+ di.fields.each { |f| control.include?(f).should == true }
103
+ end
104
+
105
+ it 'takes a list of Field objects as fields' do
106
+ control = [ Blather::Stanza::X::Field.new(*%w[var text-single label1]),
107
+ Blather::Stanza::X::Field.new(*%w[var1 text-single label1])]
108
+
109
+ di = Blather::Stanza::X.new nil, control
110
+ di.fields.size.should == 2
111
+ di.fields.each { |f| control.include?(f).should == true }
112
+ end
113
+
114
+ it 'takes a mix of hashes and field objects as fields' do
115
+ fields = [
116
+ {:label => 'label', :type => 'text-single', :var => 'var'},
117
+ Blather::Stanza::X::Field.new(*%w[var1 text-single label1]),
118
+ ]
119
+
120
+ control = [ Blather::Stanza::X::Field.new(*%w[var text-single label]),
121
+ Blather::Stanza::X::Field.new(*%w[var1 text-single label1])]
122
+
123
+ di = Blather::Stanza::X.new nil, fields
124
+ di.fields.size.should == 2
125
+ di.fields.each { |f| control.include?(f).should == true }
126
+ end
127
+
128
+ it 'allows adding of fields' do
129
+ di = Blather::Stanza::X.new nil
130
+ di.fields.size.should == 0
131
+ di.fields = [{:label => 'label', :type => 'text-single', :var => 'var', :required => true}]
132
+ di.fields.size.should == 1
133
+ di.fields += [Blather::Stanza::X::Field.new(*%w[var1 text-single label1])]
134
+ di.fields.size.should == 2
135
+ end
136
+
137
+ end
138
+
139
+ describe Blather::Stanza::X::Field do
140
+ subject { Blather::Stanza::X::Field.new nil }
141
+
142
+ its(:namespace) { subject.href.should be == 'jabber:x:data' }
143
+
144
+ it 'will auto-inherit nodes' do
145
+ n = parse_stanza "<field type='text-single' var='music' label='Music from the time of Shakespeare' />"
146
+ i = Blather::Stanza::X::Field.new n.root
147
+ i.type.should == 'text-single'
148
+ i.var.should == 'music'
149
+ i.label.should == 'Music from the time of Shakespeare'
150
+ end
151
+
152
+ it 'has a type attribute' do
153
+ n = Blather::Stanza::X::Field.new 'var', 'text-single'
154
+ n.type.should == 'text-single'
155
+ n.type = 'hidden'
156
+ n.type.should == 'hidden'
157
+ end
158
+
159
+ it 'has a var attribute' do
160
+ n = Blather::Stanza::X::Field.new 'name', 'text-single'
161
+ n.var.should == 'name'
162
+ n.var = 'email'
163
+ n.var.should == 'email'
164
+ end
165
+
166
+ it 'has a label attribute' do
167
+ n = Blather::Stanza::X::Field.new 'subject', 'text-single', 'Music from the time of Shakespeare'
168
+ n.label.should == 'Music from the time of Shakespeare'
169
+ n.label = 'Books by and about Shakespeare'
170
+ n.label.should == 'Books by and about Shakespeare'
171
+ end
172
+
173
+ it 'has a desc attribute' do
174
+ n = Blather::Stanza::X::Field.new 'subject', 'text-single', 'Music from the time of Shakespeare'
175
+ n.desc.should == nil
176
+ n.desc = 'Books by and about Shakespeare'
177
+ n.desc.should == 'Books by and about Shakespeare'
178
+ n.desc = 'goodbye'
179
+ n.desc.should == 'goodbye'
180
+ end
181
+
182
+ it 'has a required? attribute' do
183
+ n = Blather::Stanza::X::Field.new 'subject', 'text-single', 'Music from the time of Shakespeare'
184
+ n.required?.should == false
185
+ n.required = true
186
+ n.required?.should == true
187
+ n.required = false
188
+ n.required?.should == false
189
+ end
190
+
191
+ it 'has a value attribute' do
192
+ n = Blather::Stanza::X::Field.new 'subject', 'text-single', 'Music from the time of Shakespeare'
193
+ n.value.should == nil
194
+ n.value = 'book1'
195
+ n.value.should == 'book1'
196
+ n.value = 'book2'
197
+ n.value.should == 'book2'
198
+ end
199
+
200
+ it 'allows setting options' do
201
+ di = Blather::Stanza::X::Field.new nil
202
+ di.options.size.should == 0
203
+ di.options = [{:label => 'Person', :value => 'person'}, Blather::Stanza::X::Field::Option.new(*%w[person1 Person1])]
204
+ di.options.size.should == 2
205
+ end
206
+
207
+ it 'can determine equality' do
208
+ a = Blather::Stanza::X::Field.new('subject', 'text-single')
209
+ a.should == Blather::Stanza::X::Field.new('subject', 'text-single')
210
+ a.should_not equal Blather::Stanza::X::Field.new('subject1', 'text-single')
211
+ end
212
+ end
213
+
214
+ describe Blather::Stanza::X::Field::Option do
215
+
216
+ it 'has a value attribute' do
217
+ n = Blather::Stanza::X::Field::Option.new 'person1', 'Person 1'
218
+ n.value.should == 'person1'
219
+ n.value = 'book1'
220
+ n.value.should == 'book1'
221
+ end
222
+
223
+ it 'has a label attribute' do
224
+ n = Blather::Stanza::X::Field::Option.new 'person1', 'Person 1'
225
+ n.label.should == 'Person 1'
226
+ n.label = 'Book 1'
227
+ n.label.should == 'Book 1'
228
+ n.label = 'Book 2'
229
+ n.label.should == 'Book 2'
230
+ end
231
+ end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blather::Stanza do
4
+ it 'provides .next_id helper for generating new IDs' do
5
+ proc { Blather::Stanza.next_id }.should change Blather::Stanza, :next_id
6
+ end
7
+
8
+ it 'provides a handler registration mechanism' do
9
+ class Registration < Blather::Stanza; register :handler_test, :handler, 'test:namespace'; end
10
+ Registration.handler_hierarchy.should include :handler_test
11
+ Blather::Stanza.handler_list.should include :handler_test
12
+ end
13
+
14
+ it 'can register based on handler' do
15
+ class RegisterHandler < Blather::Stanza; register :register_handler; end
16
+ Blather::Stanza.class_from_registration(:register_handler, nil).should == RegisterHandler
17
+ end
18
+
19
+ it 'can register based on given name' do
20
+ class RegisterName < Blather::Stanza; register :handler, :registered_name; end
21
+ Blather::Stanza.class_from_registration(:registered_name, nil).should == RegisterName
22
+ end
23
+
24
+ it 'can register subclass handlers' do
25
+ class SuperClassRegister < Blather::Stanza; register :super_class; end
26
+ class SubClassRegister < SuperClassRegister; register :sub_class; end
27
+ SuperClassRegister.handler_hierarchy.should_not include :sub_class
28
+ SubClassRegister.handler_hierarchy.should include :super_class
29
+ end
30
+
31
+ it 'can import a node' do
32
+ s = Blather::Stanza.import Blather::XMPPNode.new('foo')
33
+ s.element_name.should == 'foo'
34
+ end
35
+
36
+ it 'provides an #error? helper' do
37
+ s = Blather::Stanza.new('message')
38
+ s.error?.should == false
39
+ s.type = :error
40
+ s.error?.should == true
41
+ end
42
+
43
+ it 'will generate a reply' do
44
+ s = Blather::Stanza.new('message')
45
+ s.from = f = Blather::JID.new('n@d/r')
46
+ s.to = t = Blather::JID.new('d@n/r')
47
+
48
+ r = s.reply
49
+ r.object_id.should_not equal s.object_id
50
+ r.from.should == t
51
+ r.to.should == f
52
+ end
53
+
54
+ it 'convert to a reply' do
55
+ s = Blather::Stanza.new('message')
56
+ s.from = f = Blather::JID.new('n@d/r')
57
+ s.to = t = Blather::JID.new('d@n/r')
58
+
59
+ r = s.reply!
60
+ r.object_id.should == s.object_id
61
+ r.from.should == t
62
+ r.to.should == f
63
+ end
64
+
65
+ it 'does not remove the body when replying' do
66
+ s = Blather::Stanza.new('message')
67
+ s.from = f = Blather::JID.new('n@d/r')
68
+ s.to = t = Blather::JID.new('d@n/r')
69
+ s << Blather::XMPPNode.new('query', s.document)
70
+ r = s.reply
71
+ r.children.empty?.should == false
72
+ end
73
+
74
+ it 'removes the body when replying if we ask to remove it' do
75
+ s = Blather::Stanza.new('message')
76
+ s.from = f = Blather::JID.new('n@d/r')
77
+ s.to = t = Blather::JID.new('d@n/r')
78
+ s << Blather::XMPPNode.new('query', s.document)
79
+ r = s.reply :remove_children => true
80
+ r.children.empty?.should == true
81
+ end
82
+
83
+ it 'provides "attr_accessor" for id' do
84
+ s = Blather::Stanza.new('message')
85
+ s.id.should be_nil
86
+ s[:id].should be_nil
87
+
88
+ s.id = '123'
89
+ s.id.should == '123'
90
+ s[:id].should == '123'
91
+ end
92
+
93
+ it 'provides "attr_accessor" for to' do
94
+ s = Blather::Stanza.new('message')
95
+ s.to.should be_nil
96
+ s[:to].should be_nil
97
+
98
+ s.to = Blather::JID.new('n@d/r')
99
+ s.to.should_not be_nil
100
+ s.to.should be_kind_of Blather::JID
101
+
102
+ s[:to].should_not be_nil
103
+ s[:to].should == 'n@d/r'
104
+ end
105
+
106
+ it 'provides "attr_accessor" for from' do
107
+ s = Blather::Stanza.new('message')
108
+ s.from.should be_nil
109
+ s[:from].should be_nil
110
+
111
+ s.from = Blather::JID.new('n@d/r')
112
+ s.from.should_not be_nil
113
+ s.from.should be_kind_of Blather::JID
114
+
115
+ s[:from].should_not be_nil
116
+ s[:from].should == 'n@d/r'
117
+ end
118
+
119
+ it 'provides "attr_accessor" for type' do
120
+ s = Blather::Stanza.new('message')
121
+ s.type.should be_nil
122
+ s[:type].should be_nil
123
+
124
+ s.type = 'testing'
125
+ s.type.should_not be_nil
126
+ s[:type].should_not be_nil
127
+ end
128
+
129
+ it 'can be converted into an error by error name' do
130
+ s = Blather::Stanza.new('message')
131
+ err = s.as_error 'internal-server-error', 'cancel'
132
+ err.name.should == :internal_server_error
133
+ end
134
+ end
@@ -0,0 +1,1090 @@
1
+ require 'resolv'
2
+ require 'spec_helper'
3
+
4
+ describe Blather::Stream::Client do
5
+ class MockServer; end
6
+ module ServerMock
7
+ def receive_data(data)
8
+ @server ||= MockServer.new
9
+ @server.receive_data data, self
10
+ end
11
+ end
12
+
13
+ def mocked_server(times = nil, &block)
14
+ @client ||= mock()
15
+ @client.stubs(:unbind) unless @client.respond_to?(:unbind)
16
+ @client.stubs(:post_init) unless @client.respond_to?(:post_init)
17
+ @client.stubs(:jid=) unless @client.respond_to?(:jid=)
18
+
19
+ port = 50000 - rand(1000)
20
+
21
+ MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
22
+ EventMachine::run {
23
+ EM.add_timer(0.5) { EM.stop if EM.reactor_running? }
24
+
25
+ # Mocked server
26
+ EventMachine::start_server '127.0.0.1', port, ServerMock
27
+
28
+ # Blather::Stream connection
29
+ EM.connect('127.0.0.1', port, Blather::Stream::Client, @client, @jid || Blather::JID.new('n@d/r'), 'pass') { |c| @stream = c }
30
+ }
31
+ end
32
+
33
+
34
+ it 'can be started' do
35
+ client = mock()
36
+ params = [client, 'n@d/r', 'pass', 'host', 1234]
37
+ EM.expects(:connect).with do |*parms|
38
+ parms[0].should == 'host'
39
+ parms[1].should == 1234
40
+ parms[3].should == client
41
+ parms[5].should == 'pass'
42
+ parms[4].should == Blather::JID.new('n@d/r')
43
+ end
44
+
45
+ Blather::Stream::Client.start *params
46
+ end
47
+
48
+ it 'attempts to find the SRV record if a host is not provided' do
49
+ dns = mock(:sort! => nil, :empty? => false)
50
+ dns.expects(:detect).yields(mock({
51
+ :target => 'd',
52
+ :port => 5222
53
+ }))
54
+ Resolv::DNS.expects(:open).yields(mock(:getresources => dns))
55
+
56
+ client = Class.new
57
+ EM.expects(:connect).with do |*parms|
58
+ parms[0].should == 'd'
59
+ parms[1].should == 5222
60
+ parms[3].should == client
61
+ parms[5].should == 'pass'
62
+ parms[4].should == Blather::JID.new('n@d/r')
63
+ end
64
+
65
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
66
+ end
67
+
68
+ it 'will attempt as many connections as it takes' do
69
+ dns = [mock(:target => 'd', :port => 5222), mock(:target => 'g', :port => 1234)]
70
+ dns.stubs(:sort!) #ignore sorting
71
+ Resolv::DNS.expects(:open).yields(mock(:getresources => dns))
72
+
73
+ client = Class.new
74
+ EM.expects(:connect).with do |*parms|
75
+ raise Blather::Stream::NoConnection if parms[0] == 'd'
76
+ parms[0].should == 'g'
77
+ parms[1].should == 1234
78
+ parms[3].should == client
79
+ parms[5].should == 'pass'
80
+ parms[4].should == Blather::JID.new('n@d/r')
81
+ end
82
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
83
+ end
84
+
85
+ it 'will not attempt to connect more often than necessary' do
86
+ dns = [mock(:target => 'd', :port => 5222), mock()]
87
+ dns.stubs(:sort!) #ignore sorting
88
+ Resolv::DNS.expects(:open).yields(mock(:getresources => dns))
89
+
90
+ client = Class.new
91
+ EM.expects(:connect).with do |*parms|
92
+ parms[0].should == 'd'
93
+ parms[1].should == 5222
94
+ parms[3].should == client
95
+ parms[5].should == 'pass'
96
+ parms[4].should == Blather::JID.new('n@d/r')
97
+ end
98
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
99
+ end
100
+
101
+ it 'can figure out the host to use based on the jid' do
102
+ Resolv::DNS.expects(:open).yields(mock(:getresources => mock(:empty? => true)))
103
+ client = Class.new
104
+ params = [client, 'n@d/r', 'pass', nil, 5222]
105
+ EM.expects(:connect).with do |*parms|
106
+ parms[0].should == 'd'
107
+ parms[1].should == 5222
108
+ parms[3].should == client
109
+ parms[5].should == 'pass'
110
+ parms[4].should == Blather::JID.new('n@d/r')
111
+ end
112
+
113
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
114
+ end
115
+
116
+ it 'raises a NoConnection exception if the connection is unbound before it can be completed' do
117
+ proc do
118
+ EventMachine::run {
119
+ EM.add_timer(0.5) { EM.stop if EM.reactor_running? }
120
+
121
+ Blather::Stream::Client.start @client, @jid || Blather::JID.new('n@d/r'), 'pass', '127.0.0.1', 50000 - rand(1000)
122
+ }
123
+ end.should raise_error Blather::Stream::ConnectionFailed
124
+ end
125
+
126
+ it 'starts the stream once the connection is complete' do
127
+ mocked_server(1) { |val, _| EM.stop; val.should match(/stream:stream/) }
128
+ end
129
+
130
+ it 'sends stanzas to the client when the stream is ready' do
131
+ @client = mock()
132
+ @client.expects(:receive_data).with do |n|
133
+ EM.stop
134
+ n.should be_kind_of Blather::Stanza::Message
135
+ end
136
+
137
+ mocked_server(1) do |val, server|
138
+ val.should match(/stream:stream/)
139
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
140
+ server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
141
+ end
142
+ end
143
+
144
+ it 'puts itself in the stopped state and calls @client.unbind when unbound' do
145
+ @client = mock()
146
+ @client.expects(:unbind).at_least_once
147
+
148
+ started = false
149
+ mocked_server(2) do |val, server|
150
+ if !started
151
+ started = true
152
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
153
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
154
+ val.should match(/stream:stream/)
155
+
156
+ else
157
+ EM.stop
158
+ @stream.stopped?.should == false
159
+ @stream.unbind
160
+ @stream.stopped?.should == true
161
+
162
+ end
163
+ end
164
+ end
165
+
166
+ it 'will be in the negotiating state during feature negotiations' do
167
+ state = nil
168
+ @client = mock()
169
+ @client.expects(:receive_data).with do |n|
170
+ EM.stop
171
+ state.should ==(:negotiated) && @stream.negotiating?.should ==(false)
172
+ end
173
+
174
+ mocked_server(2) do |val, server|
175
+ case state
176
+ when nil
177
+ state = :started
178
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
179
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
180
+ true
181
+
182
+ when :started
183
+ state = :negotiated
184
+ @stream.negotiating?.should == true
185
+ server.send_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}' />"
186
+ server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
187
+ true
188
+
189
+ else
190
+ EM.stop
191
+ false
192
+
193
+ end
194
+ end
195
+ end
196
+
197
+ it 'stops when sent </stream:stream>' do
198
+ state = nil
199
+ mocked_server(3) do |val, server|
200
+ case state
201
+ when nil
202
+ state = :started
203
+ 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'>"
204
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
205
+ val.should match(/stream:stream/)
206
+
207
+ when :started
208
+ state = :stopped
209
+ server.send_data '</stream:stream>'
210
+ @stream.stopped?.should == false
211
+
212
+ when :stopped
213
+ EM.stop
214
+ @stream.stopped?.should == true
215
+ val.should == '</stream:stream>'
216
+
217
+ else
218
+ EM.stop
219
+ false
220
+
221
+ end
222
+ end
223
+ end
224
+
225
+ it 'sends client an error on stream:error' do
226
+ @client = mock()
227
+ @client.expects(:receive_data).with do |v|
228
+ v.name.should == :conflict
229
+ v.text.should == 'Already signed in'
230
+ v.to_s.should == "Stream Error (conflict): #{v.text}"
231
+ end
232
+
233
+ state = nil
234
+ mocked_server(3) do |val, server|
235
+ case state
236
+ when nil
237
+ state = :started
238
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
239
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
240
+ val.should match(/stream:stream/)
241
+
242
+ when :started
243
+ state = :stopped
244
+ server.send_data "<stream:error><conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams' />"
245
+ server.send_data "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>Already signed in</text></stream:error>"
246
+
247
+ when :stopped
248
+ EM.stop
249
+ val.should == "</stream:stream>"
250
+
251
+ else
252
+ EM.stop
253
+ false
254
+
255
+ end
256
+ end
257
+ end
258
+
259
+ it 'skips features it is unable to handle' do
260
+ state = nil
261
+ mocked_server() do |val, server|
262
+ case state
263
+ when nil
264
+ state = :started
265
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
266
+ server.send_data "<stream:features><auth xmlns='http://jabber.org/features/iq-auth'/><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
267
+ val.should match(/stream:stream/)
268
+
269
+ when :started
270
+ EM.stop
271
+ val.should match(/starttls/)
272
+
273
+ else
274
+ EM.stop
275
+ false
276
+
277
+ end
278
+ end
279
+ end
280
+
281
+ it 'starts TLS when asked' do
282
+ state = nil
283
+ mocked_server(3) do |val, server|
284
+ case state
285
+ when nil
286
+ state = :started
287
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
288
+ server.send_data "<stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
289
+ val.should match(/stream:stream/)
290
+
291
+ when :started
292
+ state = :tls
293
+ @stream.expects(:start_tls)
294
+ server.send_data "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
295
+ val.should match(/starttls/)
296
+
297
+ when :tls
298
+ EM.stop
299
+ true
300
+
301
+ else
302
+ EM.stop
303
+ false
304
+
305
+ end
306
+ end
307
+ end
308
+
309
+ it 'will fail if TLS negotiation fails' do
310
+ state = nil
311
+ @client = mock()
312
+ @client.expects(:receive_data).with { |v| v.should be_kind_of Blather::Stream::TLS::TLSFailure }
313
+ mocked_server(3) do |val, server|
314
+ case state
315
+ when nil
316
+ state = :started
317
+ 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>"
318
+ val.should match(/stream:stream/)
319
+
320
+ when :started
321
+ state = :tls
322
+ @stream.expects(:start_tls).never
323
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
324
+ val.should match(/starttls/)
325
+
326
+ when :tls
327
+ EM.stop
328
+ val.should == "</stream:stream>"
329
+
330
+ else
331
+ EM.stop
332
+ false
333
+
334
+ end
335
+ end
336
+ end
337
+
338
+ it 'will fail if a bad node comes through TLS negotiations' do
339
+ state = nil
340
+ @client = mock()
341
+ @client.expects(:receive_data).with do |v|
342
+ v.should be_kind_of Blather::Stream::TLS::TLSFailure
343
+ end
344
+ mocked_server(3) do |val, server|
345
+ case state
346
+ when nil
347
+ state = :started
348
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
349
+ server.send_data "<stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
350
+ val.should match(/stream:stream/)
351
+
352
+ when :started
353
+ state = :tls
354
+ @stream.expects(:start_tls).never
355
+ server.send_data "<foo-bar xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
356
+ val.should match(/starttls/)
357
+
358
+ when :tls
359
+ EM.stop
360
+ val.should == "</stream:stream>"
361
+
362
+ else
363
+ EM.stop
364
+ false
365
+
366
+ end
367
+ end
368
+ end
369
+
370
+ it 'connects via SASL MD5 when asked' do
371
+ Time.any_instance.stubs(:to_f).returns(1.1)
372
+ state = nil
373
+
374
+ mocked_server(5) do |val, server|
375
+ case state
376
+ when nil
377
+ state = :started
378
+ 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>"
379
+ val.should match(/stream:stream/)
380
+
381
+ when :started
382
+ state = :auth_sent
383
+ server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge>"
384
+ val.should match(/auth.*DIGEST\-MD5/)
385
+
386
+ when :auth_sent
387
+ state = :response1_sent
388
+ server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>"
389
+ val.should ==('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2QiLHJlc3BvbnNlPTZiNTlhY2Q1ZWJmZjhjZTA0NTYzMGFiMDU2Zjg3MTdm</response>')
390
+
391
+ when :response1_sent
392
+ state = :response2_sent
393
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
394
+ val.should match(%r{<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"\s?/>})
395
+
396
+ when :response2_sent
397
+ EM.stop
398
+ state = :complete
399
+ val.should match(/stream:stream/)
400
+
401
+ else
402
+ EM.stop
403
+ false
404
+
405
+ end
406
+ end
407
+ end
408
+
409
+ it 'will connect via SSL PLAIN when asked' do
410
+ state = nil
411
+ mocked_server(3) do |val, server|
412
+ case state
413
+ when nil
414
+ state = :started
415
+ 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>"
416
+ val.should match(/stream:stream/)
417
+
418
+ when :started
419
+ state = :auth_sent
420
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
421
+ val.should ==('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
422
+
423
+ when :auth_sent
424
+ EM.stop
425
+ state = :complete
426
+ val.should match(/stream:stream/)
427
+
428
+ else
429
+ EM.stop
430
+ false
431
+
432
+ end
433
+ end
434
+ end
435
+
436
+ it 'will connect via SSL ANONYMOUS when asked' do
437
+ state = nil
438
+
439
+ mocked_server(3) do |val, server|
440
+ case state
441
+ when nil
442
+ state = :started
443
+ 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>"
444
+ val.should match(/stream:stream/)
445
+
446
+ when :started
447
+ state = :auth_sent
448
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
449
+ val.should ==('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS"/>')
450
+
451
+ when :auth_sent
452
+ EM.stop
453
+ state = :complete
454
+ val.should match(/stream:stream/)
455
+
456
+ else
457
+ EM.stop
458
+ false
459
+
460
+ end
461
+ end
462
+ end
463
+
464
+ it 'connects via ANONYMOUS if the Blather::JID has a blank node' do
465
+ state = nil
466
+ @jid = Blather::JID.new '@d'
467
+
468
+ mocked_server(3) do |val, server|
469
+ case state
470
+ when nil
471
+ state = :started
472
+ 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>"
473
+ val.should match(/stream:stream/)
474
+
475
+ when :started
476
+ state = :auth_sent
477
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
478
+ val.should ==('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS"/>')
479
+
480
+ when :auth_sent
481
+ EM.stop
482
+ state = :complete
483
+ val.should match(/stream:stream/)
484
+
485
+ else
486
+ EM.stop
487
+ false
488
+
489
+ end
490
+ end
491
+ end
492
+
493
+ it 'fails if asked to connect via ANONYMOUS but the server does not support it' do
494
+ state = nil
495
+ @jid = Blather::JID.new '@d'
496
+ @client = mock()
497
+ @client.expects(:receive_data).with { |s| s.should be_instance_of Blather::BlatherError }
498
+
499
+ mocked_server(2) do |val, server|
500
+ case state
501
+ when nil
502
+ state = :started
503
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
504
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
505
+ val.should match(/stream:stream/)
506
+
507
+ when :started
508
+ EM.stop
509
+ val.should match(/stream:stream/)
510
+
511
+ else
512
+ EM.stop
513
+ false
514
+
515
+ end
516
+ end
517
+ end
518
+
519
+ it 'tries each possible mechanism until it fails completely' do
520
+ state = nil
521
+ @client = mock()
522
+ @client.expects(:receive_data).with do |n|
523
+ n.should be_kind_of(Blather::SASLError)
524
+ n.name.should == :not_authorized
525
+ end
526
+
527
+ mocked_server(5) do |val, server|
528
+ case state
529
+ when nil
530
+ state = :started
531
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
532
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
533
+ val.should match(/stream:stream/)
534
+
535
+ when :started
536
+ state = :failed_md5
537
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
538
+ val.should match(/mechanism="DIGEST-MD5"/)
539
+
540
+ when :failed_md5
541
+ state = :failed_plain
542
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
543
+ val.should match(/mechanism="PLAIN"/)
544
+
545
+ when :failed_plain
546
+ state = :failed_anon
547
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
548
+ val.should match(/mechanism="ANONYMOUS"/)
549
+
550
+ when :failed_anon
551
+ EM.stop
552
+ state = :complete
553
+ val.should match(/\/stream:stream/)
554
+
555
+ else
556
+ EM.stop
557
+ false
558
+
559
+ end
560
+ end
561
+ end
562
+
563
+ it 'tries each mechanism until it succeeds' do
564
+ state = nil
565
+ mocked_server(4) do |val, server|
566
+ case state
567
+ when nil
568
+ state = :started
569
+ 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>"
570
+ val.should match(/stream:stream/)
571
+
572
+ when :started
573
+ state = :failed_md5
574
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
575
+ val.should match(/mechanism="DIGEST-MD5"/)
576
+
577
+ when :failed_md5
578
+ state = :plain_sent
579
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
580
+ val.should match(/mechanism="PLAIN"/)
581
+
582
+ when :plain_sent
583
+ EM.stop
584
+ val.should match(/stream:stream/)
585
+
586
+ else
587
+ EM.stop
588
+ false
589
+
590
+ end
591
+ end
592
+ end
593
+
594
+ it 'will ignore methods it does not understand' do
595
+ state = nil
596
+ mocked_server(3) do |val, server|
597
+ case state
598
+ when nil
599
+ state = :started
600
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
601
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>CRAM-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
602
+ val.should match(/stream:stream/)
603
+
604
+ when :started
605
+ state = :auth_sent
606
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
607
+ val.should ==('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
608
+
609
+ when :auth_sent
610
+ EM.stop
611
+ state = :complete
612
+ val.should match(/stream:stream/)
613
+
614
+ else
615
+ EM.stop
616
+ false
617
+
618
+ end
619
+ end
620
+ end
621
+ =begin
622
+ it 'sends client an error when an unknown mechanism is sent' do
623
+ @client = mock()
624
+ @client.expects(:receive_data).with { |v| v.should be_kind_of(Blather::Stream::SASL::UnknownMechanism) }
625
+ started = false
626
+ mocked_server(2) do |val, server|
627
+ if !started
628
+ started = true
629
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
630
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>UNKNOWN</mechanism></mechanisms></stream:features>"
631
+ val.should match(/stream:stream/)
632
+
633
+ else
634
+ EM.stop
635
+ val.should match(/failure(.*)invalid\-mechanism/)
636
+
637
+ end
638
+ end
639
+ end
640
+ =end
641
+ %w[ aborted
642
+ incorrect-encoding
643
+ invalid-authzid
644
+ invalid-mechanism
645
+ mechanism-too-weak
646
+ not-authorized
647
+ temporary-auth-failure
648
+ ].each do |error_type|
649
+ it "fails on #{error_type}" do
650
+ @client = mock()
651
+ @client.expects(:receive_data).with do |n|
652
+ n.name.should == error_type.gsub('-','_').to_sym
653
+ end
654
+ state = nil
655
+ mocked_server(3) do |val, server|
656
+ case state
657
+ when nil
658
+ state = :started
659
+ 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>"
660
+ val.should match(/stream:stream/)
661
+
662
+ when :started
663
+ state = :auth_sent
664
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><#{error_type} /></failure>"
665
+ val.should ==('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
666
+
667
+ when :auth_sent
668
+ EM.stop
669
+ state = :complete
670
+ val.should match(/\/stream:stream/)
671
+
672
+ else
673
+ EM.stop
674
+ false
675
+
676
+ end
677
+ end
678
+ end
679
+ end
680
+
681
+ it 'fails when an unknown node comes through during SASL negotiation' do
682
+ @client = mock()
683
+ @client.expects(:receive_data).with do |n|
684
+ n.should be_instance_of Blather::UnknownResponse
685
+ n.node.element_name.should == 'foo-bar'
686
+ end
687
+ state = nil
688
+ mocked_server(3) do |val, server|
689
+ case state
690
+ when nil
691
+ state = :started
692
+ 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>"
693
+ val.should match(/stream:stream/)
694
+
695
+ when :started
696
+ state = :auth_sent
697
+ server.send_data "<foo-bar />"
698
+ val.should ==('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
699
+
700
+ when :auth_sent
701
+ EM.stop
702
+ state = :complete
703
+ val.should match(/\/stream:stream/)
704
+
705
+ else
706
+ EM.stop
707
+ false
708
+
709
+ end
710
+ end
711
+ end
712
+
713
+ it 'will bind to a resource set by the server' do
714
+ state = nil
715
+ class Client; attr_accessor :jid; end
716
+ @client = Client.new
717
+ @jid = Blather::JID.new('n@d')
718
+
719
+ mocked_server(3) do |val, server|
720
+ case state
721
+ when nil
722
+ state = :started
723
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
724
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
725
+ val.should match(/stream:stream/)
726
+
727
+ when :started
728
+ state = :complete
729
+ val =~ %r{<iq[^>]+id="([^"]+)"}
730
+ server.send_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}/server_resource</jid></bind></iq>"
731
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
732
+ val.should match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s?/>})
733
+
734
+ when :complete
735
+ EM.stop
736
+ @stream.jid.should == Blather::JID.new('n@d/server_resource')
737
+
738
+ else
739
+ EM.stop
740
+ false
741
+
742
+ end
743
+ end
744
+ end
745
+
746
+ it 'will bind to a resource set by the client' do
747
+ state = nil
748
+ class Client; attr_accessor :jid; end
749
+ @client = Client.new
750
+ @jid = Blather::JID.new('n@d/r')
751
+
752
+ mocked_server(3) do |val, server|
753
+ case state
754
+ when nil
755
+ state = :started
756
+ 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>"
757
+ val.should match(/stream:stream/)
758
+
759
+ when :started
760
+ state = :complete
761
+ doc = parse_stanza val
762
+ doc.xpath('/iq/bind_ns:bind/bind_ns:resource[.="r"]', :bind_ns => Blather::Stream::Resource::BIND_NS).should_not be_empty
763
+
764
+ server.send_data "<iq type='result' id='#{doc.find_first('iq')['id']}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}</jid></bind></iq>"
765
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
766
+
767
+ when :complete
768
+ EM.stop
769
+ @stream.jid.should == Blather::JID.new('n@d/r')
770
+
771
+ else
772
+ EM.stop
773
+ false
774
+
775
+ end
776
+ end
777
+ end
778
+
779
+ it 'will error out if the bind ID mismatches' do
780
+ state = nil
781
+ @jid = Blather::JID.new('n@d')
782
+ @client = mock()
783
+
784
+ mocked_server(3) do |val, server|
785
+ case state
786
+ when nil
787
+ state = :started
788
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
789
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
790
+ val.should match(/stream:stream/)
791
+
792
+ when :started
793
+ state = :complete
794
+ val =~ %r{<iq[^>]+id="([^"]+)"}
795
+ @client.expects(:receive_data).with("BIND result ID mismatch. Expected: #{$1}. Received: #{$1}-bad")
796
+ server.send_data "<iq type='result' id='#{$1}-bad'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}/server_resource</jid></bind></iq>"
797
+ val.should match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s?/>})
798
+
799
+ when :complete
800
+ EM.stop
801
+ true
802
+
803
+ else
804
+ EM.stop
805
+ false
806
+
807
+ end
808
+ end
809
+ end
810
+
811
+ it 'will return an error if resource binding errors out' do
812
+ state = nil
813
+ @client = mock()
814
+ @client.expects(:receive_data).with do |n|
815
+ n.name.should == :bad_request
816
+ end
817
+ mocked_server(3) do |val, server|
818
+ case state
819
+ when nil
820
+ state = :started
821
+ 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>"
822
+ val.should match(/stream:stream/)
823
+
824
+ when :started
825
+ state = :complete
826
+ doc = parse_stanza val
827
+ doc.xpath('/iq/bind_ns:bind/bind_ns:resource[.="r"]', :bind_ns => Blather::Stream::Resource::BIND_NS).should_not be_empty
828
+ server.send_data "<iq type='error' id='#{doc.find_first('iq')['id']}'><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>"
829
+
830
+ when :complete
831
+ EM.stop
832
+ val.should match(/\/stream:stream/)
833
+
834
+ else
835
+ EM.stop
836
+ false
837
+
838
+ end
839
+ end
840
+ end
841
+
842
+ it 'will return an error if an unknown node comes through during resouce binding' do
843
+ state = nil
844
+ @client = mock()
845
+ @client.expects(:receive_data).with do |n|
846
+ n.should be_instance_of Blather::UnknownResponse
847
+ n.node.element_name.should == 'foo-bar'
848
+ end
849
+ mocked_server(3) do |val, server|
850
+ case state
851
+ when nil
852
+ state = :started
853
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
854
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
855
+ val.should match(/stream:stream/)
856
+
857
+ when :started
858
+ state = :complete
859
+ doc = parse_stanza val
860
+ doc.xpath('/iq/bind_ns:bind/bind_ns:resource[.="r"]', :bind_ns => Blather::Stream::Resource::BIND_NS).should_not be_empty
861
+ server.send_data "<foo-bar />"
862
+
863
+ when :complete
864
+ EM.stop
865
+ val.should match(/\/stream:stream/)
866
+
867
+ else
868
+ EM.stop
869
+ false
870
+
871
+ end
872
+ end
873
+ end
874
+
875
+ it 'will establish a session if requested' do
876
+ state = nil
877
+ @client = mock()
878
+ @client.expects(:post_init)
879
+
880
+ mocked_server(3) do |val, server|
881
+ case state
882
+ when nil
883
+ state = :started
884
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
885
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
886
+ val.should match(/stream:stream/)
887
+
888
+ when :started
889
+ state = :completed
890
+ doc = parse_stanza val
891
+ doc.find('/iq[@type="set" and @to="d"]/sess_ns:session', :sess_ns => Blather::Stream::Session::SESSION_NS).should_not be_empty
892
+ server.send_data "<iq from='d' type='result' id='#{doc.find_first('iq')['id']}' />"
893
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
894
+
895
+ when :completed
896
+ EM.stop
897
+ true
898
+
899
+ else
900
+ EM.stop
901
+ false
902
+
903
+ end
904
+ end
905
+ end
906
+
907
+ it 'will return an error if session establishment errors out' do
908
+ state = nil
909
+ @client = mock()
910
+ @client.expects(:receive_data).with do |n|
911
+ n.name.should == :internal_server_error
912
+ end
913
+ mocked_server(3) do |val, server|
914
+ case state
915
+ when nil
916
+ state = :started
917
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
918
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
919
+ val.should match(/stream:stream/)
920
+
921
+ when :started
922
+ state = :completed
923
+ doc = parse_stanza val
924
+ doc.find('/iq[@type="set" and @to="d"]/sess_ns:session', :sess_ns => Blather::Stream::Session::SESSION_NS).should_not be_empty
925
+ server.send_data "<iq from='d' type='error' id='#{doc.find_first('iq')['id']}'><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>"
926
+
927
+ when :completed
928
+ EM.stop
929
+ val.should match(/\/stream:stream/)
930
+
931
+ else
932
+ EM.stop
933
+ false
934
+
935
+ end
936
+ end
937
+ end
938
+
939
+ it 'will return an error if an unknown node come through during session establishment' do
940
+ state = nil
941
+ @client = mock()
942
+ @client.expects(:receive_data).with do |n|
943
+ n.should be_instance_of Blather::UnknownResponse
944
+ n.node.element_name.should == 'foo-bar'
945
+ end
946
+ mocked_server(3) do |val, server|
947
+ case state
948
+ when nil
949
+ state = :started
950
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
951
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
952
+ val.should match(/stream:stream/)
953
+
954
+ when :started
955
+ state = :completed
956
+ doc = parse_stanza val
957
+ doc.find('/iq[@type="set" and @to="d"]/sess_ns:session', :sess_ns => Blather::Stream::Session::SESSION_NS).should_not be_empty
958
+ server.send_data '<foo-bar />'
959
+
960
+ when :completed
961
+ EM.stop
962
+ val.should match(/\/stream:stream/)
963
+
964
+ else
965
+ EM.stop
966
+ false
967
+
968
+ end
969
+ end
970
+ end
971
+
972
+ it 'sends client an error and reply to the server on parse error' do
973
+ @client = mock()
974
+ @client.expects(:receive_data).with do |v|
975
+ v.should be_kind_of Blather::ParseError
976
+ v.message.should match(/generate\-parse\-error/)
977
+ end
978
+ state = nil
979
+ mocked_server(3) do |val, server|
980
+ case state
981
+ when nil
982
+ state = :started
983
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
984
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
985
+ val.should match(/stream:stream/)
986
+
987
+ when :started
988
+ state = :parse_error
989
+ server.send_data "</generate-parse-error>"
990
+
991
+ when :parse_error
992
+ EM.stop
993
+ val.should == "<stream:error><xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error></stream:stream>"
994
+
995
+ else
996
+ EM.stop
997
+ false
998
+
999
+ end
1000
+ end
1001
+ end
1002
+
1003
+ it 'sends stanzas to the wire ensuring "from" is the full JID if set' do
1004
+ client = mock()
1005
+ client.stubs(:jid)
1006
+ client.stubs(:jid=)
1007
+ msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
1008
+ msg.from = 'node@jid.com'
1009
+ comp = Blather::Stream::Client.new nil, client, 'node@jid.com/resource', 'pass'
1010
+ comp.expects(:send_data).with { |s| s.should match(/^<message[^>]*from="node@jid\.com\/resource"/) }
1011
+ comp.send msg
1012
+ end
1013
+
1014
+ it 'sends stanzas to the wire leaving "from" nil if not set' do
1015
+ client = mock()
1016
+ client.stubs(:jid)
1017
+ client.stubs(:jid=)
1018
+ msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
1019
+ comp = Blather::Stream::Client.new nil, client, 'node@jid.com/resource', 'pass'
1020
+ comp.expects(:send_data).with { |s| s.should_not match(/^<message[^>]*from=/); true }
1021
+ comp.send msg
1022
+ end
1023
+
1024
+ it 'sends xml without formatting' do
1025
+ client = mock()
1026
+ client.stubs(:jid)
1027
+ client.stubs(:jid=)
1028
+
1029
+ msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
1030
+ msg.xhtml = '<i>xhtml</i> body'
1031
+
1032
+ comp = Blather::Stream::Client.new nil, client, 'node@jid.com/resource', 'pass'
1033
+ comp.expects(:send_data).with { |s| s.should_not match(/\n/); true }
1034
+ comp.send msg
1035
+ end
1036
+
1037
+ it 'tries to register if initial authentication failed but in-band registration enabled' do
1038
+ state = nil
1039
+ mocked_server(4) do |val, server|
1040
+ case state
1041
+ when nil
1042
+ state = :sasl_failed
1043
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
1044
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms><register xmlns='http://jabber.org/features/iq-register'/></stream:features>"
1045
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
1046
+ val.should match(/stream:stream/)
1047
+ when :sasl_failed
1048
+ state = :registered
1049
+ server.send_data "<iq type='result'/>"
1050
+ val.should match(/jabber:iq:register/)
1051
+ when :registered
1052
+ state = :authenticated
1053
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
1054
+ val.should match(/mechanism="PLAIN"/)
1055
+ when :authenticated
1056
+ EM.stop
1057
+ val.should match(/stream:stream/)
1058
+ else
1059
+ EM.stop
1060
+ false
1061
+ end
1062
+ end
1063
+ end
1064
+
1065
+ it 'fails when in-band registration failed' do
1066
+ state = nil
1067
+ @client = mock()
1068
+ @client.expects(:receive_data).with { |n| n.should be_instance_of Blather::BlatherError }
1069
+ mocked_server(3) do |val, server|
1070
+ case state
1071
+ when nil
1072
+ state = :sasl_failed
1073
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
1074
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms><register xmlns='http://jabber.org/features/iq-register'/></stream:features>"
1075
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
1076
+ val.should match(/stream:stream/)
1077
+ when :sasl_failed
1078
+ state = :registration_failed
1079
+ server.send_data "<iq type='error'><query /><error code='409' type='cancel'><conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"
1080
+ val.should match(/jabber:iq:register/)
1081
+ when :registration_failed
1082
+ EM.stop
1083
+ val.should match(/\/stream:stream/)
1084
+ else
1085
+ EM.stop
1086
+ false
1087
+ end
1088
+ end
1089
+ end
1090
+ end