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,139 @@
1
+ module Cancer
2
+ module Spec
3
+
4
+ class MockAdapter < Cancer::Adapter
5
+ class << self
6
+ attr_accessor :transcript, :instance, :dialog
7
+ end
8
+
9
+ def self.mock(&dialog)
10
+ mock = Class.new(self)
11
+ mock.dialog = dialog
12
+ mock
13
+ end
14
+
15
+ def self.should_produce_transcript(&proc)
16
+ should Cancer::Spec::Matcher.new(Cancer::Spec::Transcript.new(&proc))
17
+ rescue ::Spec::Expectations::ExpectationNotMetError => e
18
+ if $cancer_error
19
+ if $cancer_error.backtrace and $cancer_error != e
20
+ e.backtrace.clear
21
+ e.backtrace.concat($cancer_error.backtrace)
22
+ end
23
+ $cancer_error = nil
24
+ end
25
+ if !Cancer::Spec.debug
26
+ backtrace = e.backtrace.dup
27
+ e.backtrace.clear
28
+ e.backtrace.concat backtrace.select { |f| !f.include?('spec_helper.rb') }
29
+ end
30
+ raise e
31
+ end
32
+
33
+ def initialize(stream, options={})
34
+ @stream, @options = stream, options
35
+ self.class.instance = self
36
+ @transcript = self.class.transcript.dup
37
+ @queue = Queue.new
38
+ @responder_tread = Thread.new do
39
+ Thread.current.abort_on_exception = true
40
+ begin
41
+ run_server_emulation_and_state_verification
42
+ loop do
43
+ data = @queue.pop
44
+ break unless data
45
+ verify_client_data(data)
46
+ run_server_emulation_and_state_verification
47
+ end
48
+ rescue ::Spec::Expectations::ExpectationNotMetError => e
49
+ if lock = @stream.instance_variable_get('@lock')
50
+ $cancer_error ||= e
51
+ lock.continue!(e)
52
+ elsif locks = @stream.instance_variable_get('@receive_locks') and lock = locks.first
53
+ $cancer_error ||= e
54
+ lock.continue!(e)
55
+ else
56
+ $cancer_error ||= e
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def connect
63
+ end
64
+
65
+ def start_tls?
66
+ end
67
+
68
+ def start_tls
69
+ end
70
+
71
+ def disconnect
72
+ @queue.push(nil)
73
+ @responder_tread.join
74
+
75
+ if $cancer_error
76
+ raise $cancer_error
77
+ elsif @transcript.first
78
+ messages = ""
79
+ backtrace = nil
80
+ while message = @transcript.pop
81
+ case message.first
82
+ when :in then messages += " in: #{message[1]}\n"
83
+ when :out then messages += " out: #{message[1]}\n"
84
+ when :verify then messages += " verify: #{(message[2] || message[3]).first}\n"
85
+ end
86
+ backtrace ||= message.last
87
+ end
88
+ raise Cancer::Spec::Error.new("Expected a longer dialog:\n#{messages}", backtrace)
89
+ end
90
+ end
91
+
92
+ def send(data)
93
+ @queue.push(data)
94
+ end
95
+
96
+ def verify_client_data(xml)
97
+ type, data, backtrace = @transcript.pop
98
+
99
+ if type == nil
100
+ raise Cancer::Spec::Error.new("Unexpected data send by client:\n #{xml}")
101
+ end
102
+
103
+ if type != :out
104
+ raise Cancer::Spec::Error.new("should be write instead of #{type}", backtrace)
105
+ end
106
+
107
+ a = (Nokogiri::XML.parse(data).root rescue data) || data
108
+ b = (Nokogiri::XML.parse(xml).root rescue xml) || xml
109
+
110
+ a.strip_empty_text! if Nokogiri::XML::Node === a
111
+
112
+ if Nokogiri::XML::Node === b and Nokogiri::XML::Node === a
113
+ if !b.matching?(a)
114
+ raise Cancer::Spec::Error.new("Expected xml-data doesn't match the actual xml-data!\n expected: #{data}\n actual: #{xml}", backtrace)
115
+ end
116
+ elsif a != b
117
+ raise Cancer::Spec::Error.new("Expected xml-data doesn't match the actual xml-data!\n expected: #{data}\n actual: #{xml}", backtrace)
118
+ end
119
+ end
120
+
121
+ def run_server_emulation_and_state_verification
122
+ while @transcript.first and @transcript.first[0] != :out
123
+ type, data, backtrace = @transcript.pop
124
+ return if type == nil
125
+ case type
126
+ when :in
127
+ if Proc === data
128
+ @stream.received_data(stream.build(&data))
129
+ else
130
+ @stream.received_data(data)
131
+ end
132
+ when :verify then data.call(@stream)
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,15 @@
1
+ module Cancer
2
+ module Spec
3
+
4
+ class MockStream < Cancer::Spec::MockAdapter
5
+ def self.should_produce_transcript
6
+ super() do |t|
7
+ t.initiate_stream('alice@wonderland.lit')
8
+ yield(t)
9
+ t.close_stream
10
+ end
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,107 @@
1
+ module Cancer
2
+ module Spec
3
+
4
+ class Transcript
5
+
6
+ def initialize(entries=nil)
7
+ @entries = entries || []
8
+ yield(self) if block_given?
9
+ end
10
+
11
+ def dup
12
+ self.class.new(@entries.dup)
13
+ end
14
+
15
+ def in(xml, backtrace=nil)
16
+ @entries.push [:in, xml, backtrace||caller]
17
+ end
18
+
19
+ def out(xml, backtrace=nil)
20
+ @entries.push [:out, xml, backtrace||caller]
21
+ end
22
+
23
+ def verify(&proc)
24
+ @entries.push [:verify, proc, nil, caller]
25
+ end
26
+
27
+ def should(matcher)
28
+ @entries.push [:verify, lambda { |stream| stream.should matcher }, caller]
29
+ end
30
+
31
+ def should_not(matcher)
32
+ @entries.push [:verify, lambda { |stream| stream.should_not matcher }, caller]
33
+ end
34
+
35
+ def start_stream(from, to=nil, id=nil, backtrace=nil)
36
+ backtrace = backtrace || caller
37
+ from = from.to_jid
38
+ to ||= from.server_jid
39
+ id ||= rand(6000).to_s
40
+ self.out %{<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" to="#{to}" from="#{from.bare_jid}" version="1.0">}, backtrace
41
+ self.in %{<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" from="to" to="#{from.bare_jid}" id="#{id}" version="1.0">}, backtrace
42
+ end
43
+
44
+ def close_stream(backtrace=nil)
45
+ backtrace = backtrace || caller
46
+ self.out %{</stream:stream>}, backtrace
47
+ self.in %{</stream:stream>}, backtrace
48
+ end
49
+
50
+ def initiate_stream(from, to=nil, id=nil, backtrace=nil)
51
+ backtrace = backtrace || caller
52
+ from = from.to_jid
53
+
54
+ self.start_stream(from, to, id, backtrace)
55
+ self.in %{<stream:features>
56
+ <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls">
57
+ <optional/>
58
+ </starttls>
59
+ <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
60
+ <mechanism>PLAIN</mechanism>
61
+ <required/>
62
+ </mechanisms>
63
+ </stream:features>}, backtrace
64
+
65
+ self.out %{<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>}, backtrace
66
+ self.in %{<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>}, backtrace
67
+
68
+ self.start_stream(from, to, id, backtrace)
69
+ self.in %{<stream:features>
70
+ <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
71
+ <mechanism>PLAIN</mechanism>
72
+ <required/>
73
+ </mechanisms>
74
+ </stream:features>}, backtrace
75
+
76
+ self.out %{<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGFsaWNlQHdvbmRlcmxhbmQubGl0AGloYXRldGhlcXVlZW4=</auth>}, backtrace
77
+ self.in %{<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />}, backtrace
78
+
79
+ self.start_stream(from, to, id, backtrace)
80
+ self.in %{<stream:features>
81
+ <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
82
+ <required/>
83
+ </bind>
84
+ <session xmlns="urn:ietf:params:xml:ns:xmpp-session">
85
+ <optional/>
86
+ </session>
87
+ </stream:features>}, backtrace
88
+
89
+ self.out %{<iq type="set" id="cancer-1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>}, backtrace
90
+ self.in %{<iq type="result" id="cancer-1"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>#{from.bare_jid}/#{from.resource || 'rabbithole'}</jid></bind></iq>}, backtrace
91
+
92
+ self.out %{<iq type="set" id="cancer-2"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>}, backtrace
93
+ self.in %{<iq type="result" id="cancer-2" />}, backtrace
94
+
95
+ end
96
+
97
+ def first
98
+ @entries.first
99
+ end
100
+
101
+ def pop
102
+ @entries.shift
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,182 @@
1
+
2
+ module Cancer
3
+ class Stream
4
+
5
+ autoload :XEP, 'cancer/stream/xep'
6
+ autoload :Builder, 'cancer/stream/builder'
7
+ autoload :Controller, 'cancer/stream/controller'
8
+ autoload :EventHelper, 'cancer/stream/event_helper'
9
+
10
+ include Cancer::SynchronizedStanza
11
+ include Cancer::Stream::Controller
12
+ include Cancer::Stream::Builder
13
+ include Cancer::Stream::XEP
14
+ include Cancer::Stream::EventHelper
15
+ extend Cancer::Support
16
+ include Cancer::Events::Eventable
17
+
18
+ attr_accessor :options
19
+
20
+ def self.open(options={},&proc)
21
+ new(options).open(&proc)
22
+ end
23
+
24
+ def initialize(options={})
25
+
26
+ options[:jid] = options[:jid].to_jid if options[:jid]
27
+ options[:adapter] ||= Cancer::Adapters::Socket
28
+
29
+ @options = options
30
+ @lock = Cancer::Lock.new
31
+ @receive_locks = []
32
+ @mutex = Mutex.new
33
+
34
+ register_with Cancer::Events::Manager.new
35
+ @low_priority_queue = Cancer::ThreadPool.start!(4) do |job|
36
+ args, proc = *job
37
+ proc.call(*args)
38
+ end
39
+
40
+ @stream_parser = Cancer::StreamParser.new(self)
41
+ @xml_parser = Nokogiri::XML::SAX::PushParser.new(@stream_parser)
42
+
43
+ ['core', options[:xeps]].flatten.compact.each do |xep_name|
44
+ install_xep xep_name
45
+ end
46
+
47
+ enhance_stream_with_xeps!
48
+ @manager.resolve_martchers!
49
+
50
+ @initialized = true
51
+
52
+ controller do
53
+ on { |e| e.exception(Exception) }
54
+ def err(e)
55
+ Cancer.logger.error e.exception
56
+ end
57
+ end
58
+
59
+ @exception = nil
60
+ end
61
+
62
+ def deferred(*args, &proc)
63
+ @manager.deferred(*args, &proc)
64
+ end
65
+
66
+ def enqueue(*args, &proc)
67
+ @low_priority_queue.push([args, proc])
68
+ end
69
+
70
+ def fire!(*args)
71
+ @manager.fire!(*args)
72
+ end
73
+
74
+ def handle_interthread_exceptions
75
+ raise @exception if @exception
76
+ end
77
+
78
+ def extend_stream(&proc)
79
+ metaklass = (class << self ; self ; end)
80
+ metaklass.instance_eval(&proc)
81
+ end
82
+
83
+ def send(stanza=nil, &proc)
84
+ handle_interthread_exceptions
85
+ stanza ||= proc
86
+ if Proc === stanza
87
+ send build(&stanza)
88
+ else
89
+ send_data stanza.to_s.gsub(/>[\n\s]+/m, '>').gsub(/[\n\s]+</m, '<')
90
+ end
91
+ end
92
+
93
+ def received_data(data)
94
+ data = process_received_data(data)
95
+ @xml_parser << data
96
+
97
+ rescue RuntimeError => e
98
+ if data[0, 5] == "<?xml" or data[0, 14] == "<stream:stream"
99
+ @xml_parser = Nokogiri::XML::SAX::PushParser.new(@stream_parser)
100
+ @xml_parser << data
101
+ else
102
+ raise e
103
+ end
104
+ end
105
+
106
+ def received_xml(xml)
107
+ Cancer.logger.debug "RECEIVED: #{xml}"
108
+ handle_stream_errors(xml) or
109
+ handle_synchronized_stanza(xml) or
110
+ @manager.fire!(Cancer::Events::XMLEvent, xml)
111
+ end
112
+
113
+ def handle_stream_errors(xml)
114
+ return unless xml.named?('error', STREAM_NS)
115
+
116
+ @exception = Cancer::StreamError.new(xml)
117
+ handle_synchronized_stanza(@exception)
118
+ if @lock
119
+ @lock.continue!(@exception)
120
+ end
121
+
122
+ true
123
+ end
124
+
125
+ def open
126
+ handle_interthread_exceptions
127
+
128
+ resolve_options(options)
129
+
130
+ @manager.start!
131
+ @adapter = options[:adapter].new(self, @options)
132
+ @adapter.connect
133
+ @manager.fire!(Cancer::Events::NamedEvent, :connected)
134
+
135
+ send_stream_header
136
+ @lock.wait!
137
+ @lock = nil
138
+ if block_given?
139
+ yield(self)
140
+ close
141
+ end
142
+ self
143
+ end
144
+
145
+ def close
146
+ handle_interthread_exceptions
147
+
148
+ @low_priority_queue.stop!
149
+
150
+ @adapter.send('</stream:stream>')
151
+ @adapter.disconnect
152
+
153
+ @manager.stop!
154
+
155
+ self
156
+ end
157
+
158
+ def initialization_completed!
159
+ @lock.continue!
160
+ end
161
+
162
+ private
163
+
164
+ def resolve_options(options)
165
+ options[:username] ||= options[:jid].user
166
+ end
167
+
168
+ def process_received_data(data)
169
+ data
170
+ end
171
+
172
+ def process_send_data(data)
173
+ data
174
+ end
175
+
176
+ def send_data(data)
177
+ Cancer.logger.debug "SEND: #{data.to_s}"
178
+ @adapter.send(process_send_data(data))
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,20 @@
1
+
2
+ module Cancer
3
+ class Stream
4
+ module Builder
5
+
6
+ def builder_helpers
7
+ @builder_helpers ||= Module.new
8
+ end
9
+
10
+ def extend_builder(&proc)
11
+ self.builder_helpers.module_eval(&proc)
12
+ end
13
+
14
+ def build(builder=nil, &proc)
15
+ Cancer::Builder.new(self, builder, self.builder_helpers, &proc).doc.root
16
+ end
17
+
18
+ end
19
+ end
20
+ end