cancer 0.1.0.a1

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 (89) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/cancer.gemspec +156 -0
  7. data/documentation/STREAM_INITIATION.markdown +18 -0
  8. data/examples/example.rb +80 -0
  9. data/examples/example_2.rb +20 -0
  10. data/examples/example_em.rb +11 -0
  11. data/examples/example_em_helper.rb +23 -0
  12. data/examples/example_im_roster.rb +26 -0
  13. data/examples/example_xep_0004.rb +124 -0
  14. data/examples/example_xep_0047.rb +35 -0
  15. data/examples/example_xep_0050.rb +78 -0
  16. data/examples/example_xep_0065.rb +66 -0
  17. data/examples/example_xep_0096.dup.rb +40 -0
  18. data/examples/example_xep_0096_xep_0047.rb +42 -0
  19. data/examples/example_xep_0096_xep_0065.rb +40 -0
  20. data/lib/cancer.rb +122 -0
  21. data/lib/cancer/adapter.rb +33 -0
  22. data/lib/cancer/adapters/em.rb +88 -0
  23. data/lib/cancer/adapters/socket.rb +122 -0
  24. data/lib/cancer/builder.rb +28 -0
  25. data/lib/cancer/controller.rb +32 -0
  26. data/lib/cancer/dependencies.rb +187 -0
  27. data/lib/cancer/events/abstract_event.rb +10 -0
  28. data/lib/cancer/events/base_matchers.rb +63 -0
  29. data/lib/cancer/events/binary_matchers.rb +30 -0
  30. data/lib/cancer/events/eventable.rb +92 -0
  31. data/lib/cancer/events/exception_events.rb +28 -0
  32. data/lib/cancer/events/handler.rb +33 -0
  33. data/lib/cancer/events/manager.rb +130 -0
  34. data/lib/cancer/events/named_events.rb +25 -0
  35. data/lib/cancer/events/proc_matcher.rb +16 -0
  36. data/lib/cancer/events/xml_events.rb +44 -0
  37. data/lib/cancer/exceptions.rb +53 -0
  38. data/lib/cancer/jid.rb +215 -0
  39. data/lib/cancer/lock.rb +32 -0
  40. data/lib/cancer/spec.rb +35 -0
  41. data/lib/cancer/spec/error.rb +18 -0
  42. data/lib/cancer/spec/extentions.rb +79 -0
  43. data/lib/cancer/spec/matcher.rb +30 -0
  44. data/lib/cancer/spec/mock_adapter.rb +139 -0
  45. data/lib/cancer/spec/mock_stream.rb +15 -0
  46. data/lib/cancer/spec/transcript.rb +107 -0
  47. data/lib/cancer/stream.rb +182 -0
  48. data/lib/cancer/stream/builder.rb +20 -0
  49. data/lib/cancer/stream/controller.rb +36 -0
  50. data/lib/cancer/stream/event_helper.rb +12 -0
  51. data/lib/cancer/stream/xep.rb +52 -0
  52. data/lib/cancer/stream_parser.rb +144 -0
  53. data/lib/cancer/support.rb +27 -0
  54. data/lib/cancer/support/hash.rb +32 -0
  55. data/lib/cancer/support/string.rb +22 -0
  56. data/lib/cancer/synchronized_stanza.rb +79 -0
  57. data/lib/cancer/thread_pool.rb +118 -0
  58. data/lib/cancer/xep.rb +43 -0
  59. data/lib/cancer/xeps/core.rb +109 -0
  60. data/lib/cancer/xeps/core/bind.rb +33 -0
  61. data/lib/cancer/xeps/core/sasl.rb +113 -0
  62. data/lib/cancer/xeps/core/session.rb +22 -0
  63. data/lib/cancer/xeps/core/stream.rb +18 -0
  64. data/lib/cancer/xeps/core/terminator.rb +21 -0
  65. data/lib/cancer/xeps/core/tls.rb +34 -0
  66. data/lib/cancer/xeps/im.rb +323 -0
  67. data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
  68. data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
  69. data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
  70. data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
  71. data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
  72. data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
  73. data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
  74. data/lib/cancer/xeps/xep_0095_si.rb +211 -0
  75. data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
  76. data/lib/cancer/xeps/xep_0114_component.rb +73 -0
  77. data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
  78. data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
  79. data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
  80. data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
  81. data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
  82. data/spec/spec.opts +2 -0
  83. data/spec/spec_helper.rb +14 -0
  84. data/spec/stream/stanza_errors_spec.rb +47 -0
  85. data/spec/stream/stream_errors_spec.rb +38 -0
  86. data/spec/stream/stream_initialization_spec.rb +160 -0
  87. data/spec/xep_0050/local_spec.rb +165 -0
  88. data/spec/xep_0050/remote_spec.rb +44 -0
  89. metadata +200 -0
@@ -0,0 +1,35 @@
1
+
2
+ module Cancer
3
+ # Feature Negotiation
4
+ # http://xmpp.org/extensions/xep-0020.html
5
+ module XEP_0020
6
+ include Cancer::XEP
7
+
8
+ dependency 'core'
9
+ dependency 'xep-0004'
10
+
11
+ NS = 'http://jabber.org/protocol/feature-neg'
12
+
13
+ def self.enhance_stream(stream)
14
+ stream.extend_builder do
15
+ include Cancer::XEP_0020::BuilderHelper
16
+ end
17
+ end
18
+
19
+ module BuilderHelper
20
+
21
+ # type can bet :get and :set
22
+ def negotiate_features(type=:get, form=nil, &proc)
23
+ self.feature(:xmlns => Cancer::XEP_0020::NS) do
24
+ if form
25
+ self.x(form, :type => (type == :get ? :form : :submit), :verbose => (type == :get))
26
+ else
27
+ self.x(:type => (type == :get ? :form : :submit), :verbose => (type == :get), &proc)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,167 @@
1
+
2
+ module Cancer
3
+ # Service Discovery
4
+ # http://xmpp.org/extensions/xep-0030.html
5
+ module XEP_0030
6
+ include Cancer::XEP
7
+
8
+ ITEMS_NS = "http://jabber.org/protocol/disco#items"
9
+ INFO_NS = "http://jabber.org/protocol/disco#info"
10
+
11
+ dependency 'core'
12
+
13
+ def self.enhance_stream(stream)
14
+ stream.extend_stream do
15
+ include Cancer::XEP_0030::StreamHelpers
16
+ end
17
+ stream.install_controller(Cancer::XEP_0030::Controller)
18
+ stream.disco.identity(:category => 'client', :type => 'pc', :name => 'Cancer')
19
+ stream.disco.feature INFO_NS
20
+ stream.disco.feature ITEMS_NS
21
+ end
22
+
23
+ class Item
24
+
25
+ attr_reader :jid, :node, :name
26
+ attr_reader :features, :identities, :items
27
+
28
+ def initialize(stream, options={})
29
+ @stream = stream
30
+
31
+ @jid = options[:jid].to_jid if options[:jid]
32
+ @node = options[:node]
33
+ @name = options[:name]
34
+
35
+ @items = {}
36
+ @features = []
37
+ @identities = []
38
+ end
39
+
40
+ def items
41
+ @items.values
42
+ end
43
+
44
+ def [](node)
45
+ @items[node.to_s]
46
+ end
47
+
48
+ def identity(options={})
49
+ self.identities.push({
50
+ :name => (options[:name] ? options[:name].to_s : nil),
51
+ :type => (options[:type] ? options[:type].to_s : nil),
52
+ :category => (options[:category] ? options[:category].to_s : nil)
53
+ })
54
+ end
55
+
56
+ def feature(var)
57
+ self.features.push(var.to_s)
58
+ end
59
+
60
+ def item(options={})
61
+ item = self[options[:node].to_s] || Item.new(@stream, options)
62
+ yield(item) if block_given?
63
+ @stream.disco_items[options[:node].to_s] = item
64
+ @items[options[:node].to_s] = item
65
+ item
66
+ end
67
+
68
+ end
69
+
70
+ class Controller < Cancer::Controller
71
+
72
+ on { |e| e.xpath('/c:iq[@type="get"]/d:query', 'c' => CLIENT_NS, 'd' => INFO_NS) }
73
+ def respond_with_info(e)
74
+ query_node = e.xml.first('/c:iq/d:query', 'c' => CLIENT_NS, 'd' => INFO_NS)
75
+ node = (query_node[:node] ? query_node[:node].to_s : nil)
76
+ item = (node ? disco_items[node] : disco)
77
+
78
+ return unless item
79
+
80
+ send_iq(e.sender, :result, e.id, e.receiver) do |x|
81
+ x.query((node ? {:node => node} : {}).merge(:xmlns => INFO_NS)) do
82
+ item.identities.each do |identity|
83
+ x.identity(identity)
84
+ end
85
+ item.features.each do |var|
86
+ x.feature(:var => var)
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ on { |e| e.xpath("/c:iq[@type='get']/d:query", 'c' => CLIENT_NS, 'd' => ITEMS_NS) }
94
+ def respond_with_items(e)
95
+ query_node = e.xml.first('/c:iq/d:query', 'c' => CLIENT_NS, 'd' => ITEMS_NS)
96
+ node = (query_node[:node] ? query_node[:node].to_s : nil)
97
+ item = (node ? disco_items[node] : disco)
98
+
99
+ return unless item
100
+
101
+ send_iq(e.sender, :result, e.id, e.receiver) do |x|
102
+ x.query((node ? {:node => node} : {}).merge(:xmlns => ITEMS_NS)) do
103
+ item.items.each do |i|
104
+ attributes = {}
105
+ attributes[:node] = i.node if i.node
106
+ attributes[:name] = i.name if i.name
107
+ attributes[:jid] = i.jid if i.jid
108
+ x.item(attributes)
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ module StreamHelpers
118
+
119
+ def disco
120
+ @disco_root ||= Cancer::XEP_0030::Item.new(self)
121
+ end
122
+
123
+ def disco_items
124
+ @disco_items ||= {}
125
+ end
126
+
127
+ def disco_items_for(jid, node=nil)
128
+ result = send_iq(jid) do |x|
129
+ attibutes = { :xmlns => Cancer::XEP_0030::ITEMS_NS }
130
+ attibutes[:node] = node if node
131
+ x.query attibutes
132
+ end
133
+
134
+ return nil unless result[:type] == 'result'
135
+
136
+ result = result.all('n:query/n:item', 'n' => Cancer::XEP_0030::ITEMS_NS)
137
+ result.collect do |item|
138
+ item.attributes
139
+ end
140
+ end
141
+
142
+ def disco_info_for(jid, node=nil)
143
+ result = send_iq(jid) do |x|
144
+ attibutes = { :xmlns => Cancer::XEP_0030::INFO_NS }
145
+ attibutes[:node] = node if node
146
+ x.query attibutes
147
+ end
148
+
149
+ return nil unless result[:type] == 'result'
150
+
151
+ return_value = Item.new(:jid => jid, :node => node)
152
+
153
+ result.all('n:query/n:identity', 'n' => Cancer::XEP_0030::INFO_NS) do |e|
154
+ return_value.identity e
155
+ end
156
+
157
+ result.all('n:query/n:feature', 'n' => Cancer::XEP_0030::INFO_NS) do |e|
158
+ return_value.feature e[:var]
159
+ end
160
+
161
+ return_value
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+ end
@@ -0,0 +1,322 @@
1
+
2
+ module Cancer
3
+ # In-Band Bytestreams
4
+ # http://xmpp.org/extensions/xep-0047.html
5
+ module XEP_0047
6
+ include Cancer::XEP
7
+
8
+ dependency 'core'
9
+ dependency 'xep-0095', :weak => true
10
+
11
+ NS = 'http://jabber.org/protocol/ibb'
12
+
13
+ def self.enhance_stream(stream)
14
+ stream.extend_stream do
15
+ include Cancer::XEP_0047::StreamHelpers
16
+ end
17
+ stream.install_controller Cancer::XEP_0047::Controller
18
+ stream.install_event_helper Cancer::XEP_0047::EventDSL
19
+ if stream.respond_to?(:register_stream_method)
20
+ stream.register_stream_method(Cancer::XEP_0047::StreamMethod, Cancer::XEP_0047::NS)
21
+ stream.install_controller Cancer::XEP_0047::IBBController
22
+ end
23
+ stream.disco.feature NS
24
+ end
25
+
26
+ class Controller < Cancer::Controller
27
+
28
+ NAMESPACES = { 'c' => CLIENT_NS, 'ibb' => NS }
29
+
30
+ on { |e| e.xpath('/c:iq/ibb:open', NAMESPACES) }
31
+ def open_bytestream(e)
32
+ open_element = e.xml.first('/c:iq/ibb:open', NAMESPACES)
33
+ bytestream = Bytestream.new(
34
+ self,
35
+ e.sender,
36
+ open_element['block-size'].to_i,
37
+ open_element['sid'],
38
+ open_element['stanza'] || 'iq')
39
+ fire! Event, self, bytestream, e.id
40
+ end
41
+
42
+ on { |e| e.xpath('/c:iq[@type="set"]/ibb:data', NAMESPACES) }
43
+ def receive_data(e)
44
+ data_element = e.xml.first('ibb:data', NAMESPACES)
45
+ bytestream = nil
46
+
47
+ if data_element[:sid]
48
+ bytestream = active_bytestreams[data_element[:sid]]
49
+ else
50
+ respond_with_error(:missing_session, e.sender, e.id)
51
+ end
52
+
53
+ if bytestream
54
+ bytestream.received_packet(
55
+ data_element[:seq].to_i,
56
+ Base64.decode64(data_element.text))
57
+ acknowledge_data(e.xml)
58
+ else
59
+ respond_with_error(:missing_session, e.sender, e.id)
60
+ end
61
+ end
62
+
63
+ on { |e| e.xpath('/c:iq[@type="set"]/ibb:close', NAMESPACES) }
64
+ def close_bytestream(e)
65
+ close_element = e.xml.first('ibb:close', NAMESPACES)
66
+ bytestream = nil
67
+
68
+ if close_element[:sid]
69
+ bytestream = active_bytestreams[close_element[:sid]]
70
+ else
71
+ respond_with_error(:missing_session, e.sender, e.id)
72
+ end
73
+
74
+ if bytestream
75
+ bytestream.internal.close unless bytestream.internal.closed?
76
+ acknowledge_data(e.xml)
77
+ else
78
+ respond_with_error(:missing_session, e.sender, e.id)
79
+ end
80
+ end
81
+
82
+ def active_bytestreams
83
+ @active_bytestreams ||= {}
84
+ end
85
+
86
+ def accept_bytestream(event)
87
+ active_bytestreams[event.bytestream.stream_id] = event.bytestream
88
+ send_iq(event.bytestream.peer, :result, event.iq_id)
89
+ end
90
+
91
+ def reject_bytestream(event)
92
+ send_iq(event.bytestream.peer, :error, event.iq_id) do |x|
93
+ x.error(:type => 'cancel') do
94
+ x.send('not-acceptable', :xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas')
95
+ end
96
+ end
97
+ end
98
+
99
+ def acknowledge_data(iq)
100
+ send_iq(iq[:from], :result, iq[:id])
101
+ end
102
+
103
+ def respond_with_error(error_type, to, id)
104
+ case error_type
105
+ when :missing_session
106
+ send_iq(to, :error, id) do |x|
107
+ x.error(:type => 'cancel') do
108
+ x.send('item-not-found', :xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas')
109
+ end
110
+ end
111
+ throw :halt
112
+
113
+ end
114
+ end
115
+
116
+ end
117
+
118
+ module StreamHelpers
119
+
120
+ def open_ibb(to, options={}, &proc)
121
+ controller = self.controllers_by_name['Cancer::XEP_0047::Controller']
122
+
123
+ bytestream = Bytestream.new(
124
+ controller,
125
+ to, options[:block_size], options[:stream_id])
126
+
127
+ controller.active_bytestreams[bytestream.stream_id] = bytestream
128
+ result = send_iq(to, :set) do |x|
129
+ x.open(
130
+ :xmlns => Cancer::XEP_0047::NS,
131
+ :'block-size' => bytestream.block_size,
132
+ :sid => bytestream.stream_id,
133
+ :stanza => bytestream.stanza
134
+ )
135
+ end
136
+
137
+ if result[:type] == 'result'
138
+ yield(bytestream)
139
+ bytestream.close
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ module Bytestream
148
+
149
+ def self.new(controller, peer, block_size=nil, stream_id=nil, stanza=nil)
150
+ internal, external = * Socket.pair(Socket::AF_LOCAL, Socket::SOCK_STREAM, 0)
151
+
152
+ external.extend self
153
+ external.setup(controller, internal, peer, block_size, stream_id.to_s, stanza)
154
+
155
+ internal.extend Internal
156
+ internal.setup(controller, external)
157
+ internal.read_packets!
158
+
159
+ def external.close
160
+ sleep 0.1
161
+ @controller.send_iq(@peer, :set) do |x|
162
+ x.close(:xmlns => NS, :sid => @stream_id)
163
+ end
164
+ self.remove_from_controller
165
+ super
166
+ end
167
+
168
+ external
169
+ end
170
+
171
+ module Internal
172
+
173
+ attr_reader :packets, :controller, :external
174
+ attr_accessor :read_sec, :write_sec
175
+
176
+ def peer
177
+ @external.peer
178
+ end
179
+
180
+ def block_size
181
+ @external.block_size
182
+ end
183
+
184
+ def stream_id
185
+ @external.stream_id
186
+ end
187
+
188
+ def stanza
189
+ @external.stanza
190
+ end
191
+
192
+ def setup(controller, external)
193
+ @controller, @external = controller, external
194
+ @read_sec = @write_sec = 0
195
+ @packets = {}
196
+ end
197
+
198
+ def received_packet(sec, packet)
199
+ self.packets[sec.to_i] = packet
200
+ while bufferd_packet = self.packets.delete(self.read_sec)
201
+ begin
202
+ self.write(bufferd_packet)
203
+ self.flush
204
+ self.read_sec += 1
205
+ rescue IOError
206
+ end
207
+ end
208
+ end
209
+
210
+ def read_packets!
211
+ @controller.deferred(self) do |ibb|
212
+ loop do
213
+
214
+ begin
215
+ packet = ibb.recv_nonblock(4096)
216
+ if packet and !packet.empty?
217
+ ibb.send_packet(ibb.write_sec, packet)
218
+ ibb.write_sec += 1
219
+ end
220
+ rescue Errno::EAGAIN, OpenSSL::SSL::ReadAgain
221
+ IO.select([ibb], nil, nil, 0.5) rescue nil
222
+ retry
223
+ rescue IOError
224
+ break
225
+ rescue OpenSSL::SSL::SSLError
226
+ break
227
+ end
228
+
229
+ end
230
+ end
231
+ end
232
+
233
+ def send_packet(sec, packet)
234
+ @controller.send_iq(self.peer, :set) do |x|
235
+ x.data(:xmlns => NS, :seq => sec, :sid => self.stream_id) do
236
+ x.text Base64.encode64(packet)
237
+ end
238
+ end
239
+ end
240
+
241
+ end
242
+
243
+ attr_reader :controller, :internal, :peer, :block_size, :stream_id, :stanza
244
+
245
+ def setup(controller, internal, peer, block_size=nil, stream_id=nil, stanza=nil)
246
+ @controller, @internal = controller, internal
247
+ @peer, @block_size, @stream_id, @stanza =
248
+ peer, block_size, stream_id, stanza
249
+
250
+ @stanza ||= 'iq'
251
+ @stream_id ||= rand(1<<100).to_s
252
+ @block_size ||= 4096
253
+ end
254
+
255
+ def received_packet(sec, packet)
256
+ self.internal.received_packet(sec, packet)
257
+ end
258
+
259
+ def remove_from_controller
260
+ self.controller.active_bytestreams.delete(self.stream_id)
261
+ end
262
+
263
+ end
264
+
265
+ class Event < Cancer::Events::AbstractEvent
266
+ attr_reader :bytestream, :iq_id
267
+ def initialize(controller, bytestream, iq_id)
268
+ @controller, @bytestream, @iq_id = controller, bytestream, iq_id
269
+ end
270
+
271
+ def accept!(&proc)
272
+ @controller.accept_bytestream(self)
273
+ if block_given?
274
+ @controller.enqueue(@bytestream, proc) do |bs, proc|
275
+ proc.call(bs)
276
+ end
277
+ end
278
+ end
279
+
280
+ def reject!
281
+ @controller.reject_bytestream(self)
282
+ end
283
+ end
284
+
285
+ class EventMatcher < Cancer::Events::Matcher
286
+
287
+ def initialize
288
+ end
289
+
290
+ def match?(event)
291
+ Event === event
292
+ end
293
+
294
+ end
295
+
296
+ module EventDSL
297
+
298
+ def ibb
299
+ Cancer::XEP_0047::EventMatcher.new
300
+ end
301
+
302
+ end
303
+
304
+ class StreamMethod < Cancer::XEP_0095::StreamMethod
305
+ def handle(to, id, &proc)
306
+ @stream.open_ibb(to, :stream_id => id, &proc)
307
+ end
308
+ end
309
+
310
+ class IBBController < Cancer::Controller
311
+ on { |e| e.ibb }
312
+ def catch_ibb(e)
313
+ controller = self.controllers_by_name['Cancer::XEP_0095::Controller']
314
+ si_event = controller.pending_streams[e.bytestream.stream_id.to_s]
315
+ if si_event
316
+ si_event.caught_stream!(e)
317
+ end
318
+ end
319
+ end
320
+
321
+ end
322
+ end