cancer 0.1.0.a1

Sign up to get free protection for your applications and to get access to all the features.
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