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,69 @@
1
+
2
+ module Cancer
3
+ # Out of Band Data
4
+ # http://xmpp.org/extensions/xep-0066.html
5
+ module XEP_0066
6
+ include Cancer::XEP
7
+
8
+ dependency 'core'
9
+ dependency 'xep-0095', :weak => true
10
+
11
+ IQ_NS = 'jabber:iq:oob'
12
+ X_NS = 'jabber:x:oob'
13
+
14
+ def self.enhance_stream(stream)
15
+ stream.extend_stream do
16
+ include Cancer::XEP_0066::StreamHelpers
17
+ end
18
+ stream.extend_builder do
19
+ include Cancer::XEP_0066::BuilderHelper
20
+ end
21
+ stream.install_controller Cancer::XEP_0066::Controller
22
+ stream.install_event_helper Cancer::XEP_0066::EventDSL
23
+ if stream.respond_to?(:register_stream_method)
24
+ stream.register_stream_method(Cancer::XEP_0066::StreamMethod, Cancer::XEP_0066::IQ_NS)
25
+ end
26
+ end
27
+
28
+ module StreamHelpers
29
+
30
+ def oob_offer(to, uri, desc=nil)
31
+ send_iq(to, :set) do |x|
32
+ x.query(:xmlns => Cancer::XEP_0066::IQ_NS) do
33
+ x.url { x.text(uri.to_s) }
34
+ x.desc { x.text(desc) } if desc
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ module BuilderHelper
42
+
43
+ def oob_offer(uri, desc=nil)
44
+ self.x(:xmlns => Cancer::XEP_0066::X_NS) do
45
+ self.url { x.text(uri.to_s) }
46
+ self.desc { x.text(desc) } if desc
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ module EventDSL
53
+
54
+ def oob_offer
55
+ ( xpath('/c:iq[@type="set"]/oob:query', 'c' => CLIENT_NS, 'oob' => IQ_NS)
56
+ | xpath('/c:message/oob:x', 'c' => CLIENT_NS, 'oob' => X_NS)
57
+ )
58
+ end
59
+
60
+ end
61
+
62
+ class StreamMethod < Cancer::XEP_0095::StreamMethod
63
+ def handle(to, stream_id, &proc)
64
+ @stream.oob_offer(to, uri, :stream_id => stream_id)
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,211 @@
1
+
2
+ module Cancer
3
+ # Stream Initiation
4
+ # http://xmpp.org/extensions/xep-0095.html
5
+ module XEP_0095
6
+ include Cancer::XEP
7
+
8
+ dependency 'core'
9
+ dependency 'xep-0020'
10
+ dependency 'xep-0030'
11
+
12
+ NS = 'http://jabber.org/protocol/si'
13
+
14
+ def self.enhance_stream(stream)
15
+ stream.extend_stream do
16
+ include Cancer::XEP_0095::StreamHelper
17
+ end
18
+ stream.install_controller Cancer::XEP_0095::Controller
19
+ stream.install_event_helper Cancer::XEP_0095::EventDSL
20
+ stream.disco.feature NS
21
+ end
22
+
23
+ class Profile
24
+ def initialize(stream)
25
+ @stream = stream
26
+ end
27
+ def enhance_si(si, options={})
28
+
29
+ end
30
+ end
31
+
32
+ class StreamMethod
33
+ def initialize(stream)
34
+ @stream = stream
35
+ end
36
+ def handle(to, stream_id, &proc)
37
+
38
+ end
39
+ end
40
+
41
+ module StreamHelper
42
+
43
+ def profiles
44
+ @profiles ||= {}
45
+ end
46
+
47
+ def register_profile(handler, uri)
48
+ self.profiles[uri.to_s] = handler.new(self)
49
+ end
50
+
51
+ def stream_methods
52
+ @stream_methods ||= {}
53
+ end
54
+
55
+ def register_stream_method(handler, uri)
56
+ self.stream_methods[uri.to_s] = handler.new(self)
57
+ end
58
+
59
+ def si_offer(to, options={}, &proc)
60
+ si_options = {}
61
+ si_options[:'id'] = options.delete(:id) || rand(1<<20)
62
+ si_options[:'mime-type'] = options.delete(:mime_type) || options.delete(:'mime-type') || options.delete(:type)
63
+ si_options[:profile] = options.delete(:profile)
64
+ si_options[:xmlns] = NS
65
+
66
+ profile = self.profiles[si_options[:profile]]
67
+
68
+ result = send_iq(to, :set) do |x|
69
+ x.si(si_options) do
70
+ profile.enhance_si(x, options)
71
+ x.negotiate_features do |form|
72
+ form.define('stream-method') do |field|
73
+ field.type = :'list-single'
74
+
75
+ self.stream_methods.keys.each do |uri|
76
+ field.add_option uri
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ begin
85
+ form = Cancer::XEP_0004::Form.load(result.first(
86
+ "/c:iq[@type='result']/si:si/neg:feature/x:x",
87
+ 'c' => CLIENT_NS,
88
+ 'si' => Cancer::XEP_0095::NS,
89
+ 'neg' => Cancer::XEP_0020::NS,
90
+ 'x' => Cancer::XEP_0004::NS
91
+ ))
92
+ stream_method = form['stream-method']
93
+
94
+ self.stream_methods[stream_method].handle(to, si_options[:id], &proc)
95
+ rescue
96
+ nil
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ class Controller < Cancer::Controller
103
+
104
+ NAMESPACES = { 'c' => CLIENT_NS, 'si' => NS }
105
+
106
+ on { |e| e.xpath('/c:iq[@type="set"]/si:si', NAMESPACES) }
107
+ def received_si(e)
108
+ si = e.xml.first('/c:iq/si:si', NAMESPACES)
109
+ fire! Cancer::XEP_0095::Event, self, e.xml, si
110
+ end
111
+
112
+ def pending_streams
113
+ @pending_streams ||= {}
114
+ end
115
+
116
+ def catch_stream(id, event)
117
+ self.pending_streams[id.to_s] = event
118
+ end
119
+
120
+ end
121
+
122
+ class Event < Cancer::Events::AbstractEvent
123
+ attr_reader :si, :iq, :accept_proc
124
+ def initialize(controller, iq, si)
125
+ @controller, @iq, @si = controller, iq, si
126
+ end
127
+
128
+ def accept!(&proc)
129
+ form = Cancer::XEP_0004::Form.load(si.all("neg:feature/x:x",
130
+ 'neg' => Cancer::XEP_0020::NS,
131
+ 'x' => Cancer::XEP_0004::NS))
132
+
133
+ stream_method = (form.field('stream-method').option_values & @controller.stream_methods.keys).first
134
+ form['stream-method'] = stream_method
135
+
136
+ unless stream_method
137
+ # error
138
+ return false
139
+ end
140
+
141
+ @controller.send_iq(@iq[:from], :result, @iq[:id]) do |x|
142
+ x.si(:xmlns => Cancer::XEP_0095::NS, :id => @si[:id]) do
143
+ x.negotiate_features(:set, form)
144
+ end
145
+ end
146
+
147
+ # catch stream
148
+
149
+ @accept_proc = proc
150
+ controller = @controller.controllers_by_name['Cancer::XEP_0095::Controller']
151
+ controller.catch_stream(@si[:id], self)
152
+ end
153
+
154
+ def reject!
155
+ @controller.send_iq(@iq[:from], :error, @iq[:id]) do |x|
156
+ x.error(:code => 403, :type => :cancel) do
157
+ x.forbidden(:xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas')
158
+ x.text_(:xmlns => 'urn:ietf:params:xml:ns:xmpp-stanzas') do
159
+ x.text('Offer Declined')
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ def caught_stream!(stream)
166
+ @stream = stream
167
+ @stream.accept!(&@accept_proc)
168
+ end
169
+ end
170
+
171
+ class EventMatcher < Cancer::Events::Matcher
172
+
173
+ def initialize(options={})
174
+ @mime_type = options[:mime_type] || options[:type]
175
+ @profile = options[:profile]
176
+ end
177
+
178
+ def match?(event)
179
+ Cancer::XEP_0095::Event === event and match_mime_type?(event) and match_profile?(event)
180
+ end
181
+
182
+ def match_mime_type?(event, pattern=@mime_type)
183
+ case pattern
184
+ when String then event.si[:'mime-type'] == pattern
185
+ when Regexp then event.si[:'mime-type'] =~ pattern
186
+ when Array then pattern.any? { |p| match_mime_type?(event, p) }
187
+ when NilClass then true
188
+ end
189
+ end
190
+
191
+ def match_profile?(event, pattern=@profile)
192
+ case pattern
193
+ when String then event.si[:profile] == pattern
194
+ when Regexp then event.si[:profile] =~ pattern
195
+ when Array then pattern.any? { |p| match_profile?(event, p) }
196
+ when NilClass then true
197
+ end
198
+ end
199
+
200
+ end
201
+
202
+ module EventDSL
203
+
204
+ def si(options={})
205
+ Cancer::XEP_0095::EventMatcher.new(options)
206
+ end
207
+
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,173 @@
1
+
2
+ module Cancer
3
+ # SI File Transfer
4
+ # http://xmpp.org/extensions/xep-0096.html
5
+ module XEP_0096
6
+ include Cancer::XEP
7
+
8
+ dependency 'core'
9
+ dependency 'xep-0095'
10
+
11
+ NS = 'http://jabber.org/protocol/si/profile/file-transfer'
12
+
13
+ def self.enhance_stream(stream)
14
+ stream.extend_stream do
15
+ include Cancer::XEP_0096::StreamHelper
16
+ end
17
+ stream.install_controller Cancer::XEP_0096::Controller
18
+ stream.install_event_helper Cancer::XEP_0096::EventDSL
19
+ if stream.respond_to?(:register_profile)
20
+ stream.register_profile(Cancer::XEP_0096::Profile, Cancer::XEP_0096::NS)
21
+ stream.disco.feature NS
22
+ end
23
+ end
24
+
25
+ module StreamHelper
26
+ def si_file_offer(to, path)
27
+ si_offer(to, :path => path, :profile => Cancer::XEP_0096::NS) do |f|
28
+
29
+ File.open(path, 'r') do |file|
30
+ f.write file.read(4096) until file.eof?
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+
37
+ class Profile < Cancer::XEP_0095::Profile
38
+ def enhance_si(si, options={})
39
+ si.file(:xmlns => Cancer::XEP_0096::NS,
40
+ :name => File.basename(options[:path]),
41
+ :size => File.size(options[:path]),
42
+ :date => fomat_time(File.mtime(options[:path])),
43
+ :hash => Digest::MD5.file(options[:path]).hexdigest
44
+ )
45
+ end
46
+
47
+ def fomat_time(time)
48
+ def time.xmlschema(fraction_digits=0)
49
+ sprintf('%d-%02d-%02dT%02d:%02d:%02d',
50
+ year, mon, day, hour, min, sec) +
51
+ if fraction_digits == 0
52
+ ''
53
+ elsif fraction_digits <= 6
54
+ '.' + sprintf('%06d', usec)[0, fraction_digits]
55
+ else
56
+ '.' + sprintf('%06d', usec) + '0' * (fraction_digits - 6)
57
+ end +
58
+ if utc?
59
+ 'Z'
60
+ else
61
+ off = utc_offset
62
+ sign = off < 0 ? '-' : '+'
63
+ sprintf('%s%02d:%02d', sign, *(off.abs / 60).divmod(60))
64
+ end
65
+ end
66
+ time.xmlschema
67
+ end
68
+ end
69
+
70
+ class Controller < Cancer::Controller
71
+
72
+ NAMESPACES = { 'c' => CLIENT_NS, 'si' => Cancer::XEP_0095::NS, 'ft' => NS }
73
+
74
+ on { |e| e.xpath('/c:iq[@type="set"]/si:si/ft:file', NAMESPACES) }
75
+ def received_si(e)
76
+ ft = e.xml.first('/c:iq/si:si/ft:file', NAMESPACES)
77
+ si = ft.parent
78
+ fire! Cancer::XEP_0096::Event, self, e.xml, si, ft
79
+ end
80
+
81
+ end
82
+
83
+ class Event < Cancer::XEP_0095::Event
84
+ attr_reader :si, :iq, :ft
85
+ attr_reader :status
86
+
87
+ def initialize(controller, iq, si, ft)
88
+ @controller, @iq, @si, @ft = controller, iq, si, ft
89
+ @status = :pending
90
+ end
91
+
92
+ def accept!(options={}, &proc)
93
+ if options[:path]
94
+ super() do |f|
95
+ @status = :active
96
+ @controller.fire! self
97
+
98
+ File.open(options[:path], 'w+', options[:mode] || 0644) do |file|
99
+ file.write f.readpartial(4096) until f.eof?
100
+ end
101
+
102
+ correct_size = lambda { (@ft[:size].nil? or (File.size(options[:path]) == @ft[:size].to_i)) }
103
+ correct_hash = lambda { (@ft[:hash].nil? or (Digest::MD5.file(options[:path]).hexdigest == @ft[:hash].to_s)) }
104
+
105
+ if correct_size.call and correct_hash.call
106
+ @status = :completed
107
+ @controller.fire! self
108
+ else
109
+ File.unlink(options[:path])
110
+ @status = :failed
111
+ @controller.fire! self
112
+ end
113
+ end
114
+ else
115
+ super() do |f|
116
+ @status = :active
117
+ @controller.fire! self
118
+
119
+ proc.call(f)
120
+
121
+ @status = :completed
122
+ @controller.fire! self
123
+ end
124
+ end
125
+ end
126
+
127
+ def filename
128
+ @ft[:name].to_s
129
+ end
130
+ end
131
+
132
+ class EventMatcher < Cancer::XEP_0095::EventMatcher
133
+
134
+ def initialize(options={})
135
+ options[:profile] = NS
136
+ super(options)
137
+ @filename = options[:filename]
138
+ @status = options[:status] || :pending
139
+ end
140
+
141
+ def match?(event)
142
+ Cancer::XEP_0096::Event === event and super(event) and match_filename?(event) and match_status?(event)
143
+ end
144
+
145
+ def match_filename?(event, pattern=@filename)
146
+ case pattern
147
+ when String then event.filename == pattern
148
+ when Regexp then event.filename =~ pattern
149
+ when Array then pattern.any? { |p| match_filename?(event, p) }
150
+ when NilClass then true
151
+ end
152
+ end
153
+
154
+ def match_status?(event, pattern=@status)
155
+ case pattern
156
+ when Symbol then event.status == pattern
157
+ when Array then pattern.any? { |p| match_status?(event, p) }
158
+ when NilClass then true
159
+ end
160
+ end
161
+
162
+ end
163
+
164
+ module EventDSL
165
+
166
+ def si_file(options={})
167
+ Cancer::XEP_0096::EventMatcher.new(options)
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+ end