cancer 0.1.0.a1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +3 -0
- data/.gitignore +5 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/cancer.gemspec +156 -0
- data/documentation/STREAM_INITIATION.markdown +18 -0
- data/examples/example.rb +80 -0
- data/examples/example_2.rb +20 -0
- data/examples/example_em.rb +11 -0
- data/examples/example_em_helper.rb +23 -0
- data/examples/example_im_roster.rb +26 -0
- data/examples/example_xep_0004.rb +124 -0
- data/examples/example_xep_0047.rb +35 -0
- data/examples/example_xep_0050.rb +78 -0
- data/examples/example_xep_0065.rb +66 -0
- data/examples/example_xep_0096.dup.rb +40 -0
- data/examples/example_xep_0096_xep_0047.rb +42 -0
- data/examples/example_xep_0096_xep_0065.rb +40 -0
- data/lib/cancer.rb +122 -0
- data/lib/cancer/adapter.rb +33 -0
- data/lib/cancer/adapters/em.rb +88 -0
- data/lib/cancer/adapters/socket.rb +122 -0
- data/lib/cancer/builder.rb +28 -0
- data/lib/cancer/controller.rb +32 -0
- data/lib/cancer/dependencies.rb +187 -0
- data/lib/cancer/events/abstract_event.rb +10 -0
- data/lib/cancer/events/base_matchers.rb +63 -0
- data/lib/cancer/events/binary_matchers.rb +30 -0
- data/lib/cancer/events/eventable.rb +92 -0
- data/lib/cancer/events/exception_events.rb +28 -0
- data/lib/cancer/events/handler.rb +33 -0
- data/lib/cancer/events/manager.rb +130 -0
- data/lib/cancer/events/named_events.rb +25 -0
- data/lib/cancer/events/proc_matcher.rb +16 -0
- data/lib/cancer/events/xml_events.rb +44 -0
- data/lib/cancer/exceptions.rb +53 -0
- data/lib/cancer/jid.rb +215 -0
- data/lib/cancer/lock.rb +32 -0
- data/lib/cancer/spec.rb +35 -0
- data/lib/cancer/spec/error.rb +18 -0
- data/lib/cancer/spec/extentions.rb +79 -0
- data/lib/cancer/spec/matcher.rb +30 -0
- data/lib/cancer/spec/mock_adapter.rb +139 -0
- data/lib/cancer/spec/mock_stream.rb +15 -0
- data/lib/cancer/spec/transcript.rb +107 -0
- data/lib/cancer/stream.rb +182 -0
- data/lib/cancer/stream/builder.rb +20 -0
- data/lib/cancer/stream/controller.rb +36 -0
- data/lib/cancer/stream/event_helper.rb +12 -0
- data/lib/cancer/stream/xep.rb +52 -0
- data/lib/cancer/stream_parser.rb +144 -0
- data/lib/cancer/support.rb +27 -0
- data/lib/cancer/support/hash.rb +32 -0
- data/lib/cancer/support/string.rb +22 -0
- data/lib/cancer/synchronized_stanza.rb +79 -0
- data/lib/cancer/thread_pool.rb +118 -0
- data/lib/cancer/xep.rb +43 -0
- data/lib/cancer/xeps/core.rb +109 -0
- data/lib/cancer/xeps/core/bind.rb +33 -0
- data/lib/cancer/xeps/core/sasl.rb +113 -0
- data/lib/cancer/xeps/core/session.rb +22 -0
- data/lib/cancer/xeps/core/stream.rb +18 -0
- data/lib/cancer/xeps/core/terminator.rb +21 -0
- data/lib/cancer/xeps/core/tls.rb +34 -0
- data/lib/cancer/xeps/im.rb +323 -0
- data/lib/cancer/xeps/xep_0004_x_data.rb +692 -0
- data/lib/cancer/xeps/xep_0020_feature_neg.rb +35 -0
- data/lib/cancer/xeps/xep_0030_disco.rb +167 -0
- data/lib/cancer/xeps/xep_0047_ibb.rb +322 -0
- data/lib/cancer/xeps/xep_0050_commands.rb +256 -0
- data/lib/cancer/xeps/xep_0065_bytestreams.rb +306 -0
- data/lib/cancer/xeps/xep_0066_oob.rb +69 -0
- data/lib/cancer/xeps/xep_0095_si.rb +211 -0
- data/lib/cancer/xeps/xep_0096_si_filetransfer.rb +173 -0
- data/lib/cancer/xeps/xep_0114_component.rb +73 -0
- data/lib/cancer/xeps/xep_0115_caps.rb +180 -0
- data/lib/cancer/xeps/xep_0138_compress.rb +134 -0
- data/lib/cancer/xeps/xep_0144_rosterx.rb +250 -0
- data/lib/cancer/xeps/xep_0184_receipts.rb +40 -0
- data/lib/cancer/xeps/xep_0199_ping.rb +41 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/stream/stanza_errors_spec.rb +47 -0
- data/spec/stream/stream_errors_spec.rb +38 -0
- data/spec/stream/stream_initialization_spec.rb +160 -0
- data/spec/xep_0050/local_spec.rb +165 -0
- data/spec/xep_0050/remote_spec.rb +44 -0
- metadata +200 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
|
4
|
+
class CancerError < RuntimeError
|
5
|
+
end
|
6
|
+
|
7
|
+
class XMPPError < CancerError
|
8
|
+
attr_reader :conditions
|
9
|
+
def initialize(xml)
|
10
|
+
text = xml.first('n:text', "n" => 'urn:ietf:params:xml:ns:xmpp-streams')
|
11
|
+
text = text ? text.text.to_s : "Stream error: #{xml}"
|
12
|
+
super(text)
|
13
|
+
@conditions = xml.all('*[name() != "text"]')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class StreamError < XMPPError
|
18
|
+
end
|
19
|
+
|
20
|
+
class StanzaError < XMPPError
|
21
|
+
attr_reader :stanza
|
22
|
+
def initialize(xml)
|
23
|
+
super(xml.first('c:error'))
|
24
|
+
@stanza = xml
|
25
|
+
end
|
26
|
+
def self.build(xml)
|
27
|
+
type = xml.first('c:error')[:type]
|
28
|
+
case type
|
29
|
+
when 'cancel' then StanzaCancelError.new(xml)
|
30
|
+
when 'continue' then StanzaContinueError.new(xml)
|
31
|
+
when 'modify' then StanzaModifyError.new(xml)
|
32
|
+
when 'auth' then StanzaAuthError.new(xml)
|
33
|
+
when 'wait' then StanzaWaitError.new(xml)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class StanzaCancelError < StanzaError
|
39
|
+
end
|
40
|
+
|
41
|
+
class StanzaContinueError < StanzaError
|
42
|
+
end
|
43
|
+
|
44
|
+
class StanzaModifyError < StanzaError
|
45
|
+
end
|
46
|
+
|
47
|
+
class StanzaAuthError < StanzaError
|
48
|
+
end
|
49
|
+
|
50
|
+
class StanzaWaitError < StanzaError
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/cancer/jid.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Cancer
|
5
|
+
|
6
|
+
#
|
7
|
+
# RFC2368, The mailto URL scheme
|
8
|
+
#
|
9
|
+
class JID < URI::Generic
|
10
|
+
include REGEXP
|
11
|
+
|
12
|
+
COMPONENT = [ :scheme, :user, :domain, :resource ].freeze
|
13
|
+
|
14
|
+
RESOURCE_PATTERN = "(?:[^\\/]*)".freeze
|
15
|
+
RESOURCE_REGEXP = Regexp.new(RESOURCE_PATTERN, 'N').freeze
|
16
|
+
USER_PATTERN = "(?:#{PATTERN::ESCAPED}|[^@])*".freeze
|
17
|
+
DOMAIN_PATTERN = "(?:#{PATTERN::ESCAPED}|[^\\/:])".freeze
|
18
|
+
FULL_JID_REGEXP = Regexp.new(" # :nodoc:
|
19
|
+
\\A
|
20
|
+
(?:
|
21
|
+
(#{USER_PATTERN})@ (?# 1: user)
|
22
|
+
)?
|
23
|
+
(#{DOMAIN_PATTERN}*?) (?# 2: domain)
|
24
|
+
(?:
|
25
|
+
\\/(#{RESOURCE_PATTERN}(?:\\/#{RESOURCE_PATTERN})*) (?# 3: resource)
|
26
|
+
)?
|
27
|
+
\\z
|
28
|
+
", Regexp::EXTENDED, 'N').freeze
|
29
|
+
|
30
|
+
def self.parse(string)
|
31
|
+
URI.parse(string[0,5] == 'xmpp:' ? string : 'xmpp:' + string)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.build(args)
|
35
|
+
tmp = Util::make_components_hash(self, args)
|
36
|
+
|
37
|
+
if tmp[:user]
|
38
|
+
tmp[:opaque] = tmp.delete(:user) + '@'
|
39
|
+
else
|
40
|
+
tmp[:opaque] = ''
|
41
|
+
end
|
42
|
+
|
43
|
+
if tmp[:domain]
|
44
|
+
tmp[:opaque] << tmp.delete(:domain)
|
45
|
+
else
|
46
|
+
tmp[:opaque] << ''
|
47
|
+
end
|
48
|
+
|
49
|
+
if tmp[:resource]
|
50
|
+
if tmp[:resource][0,1] == '/'
|
51
|
+
tmp[:opaque] << tmp.delete(:resource).to_s
|
52
|
+
else
|
53
|
+
tmp[:opaque] << '/' + tmp.delete(:resource).to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return super(tmp)
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(*arg)
|
61
|
+
super(*arg)
|
62
|
+
|
63
|
+
if FULL_JID_REGEXP =~ @opaque
|
64
|
+
if arg[-1]
|
65
|
+
self.user = $1
|
66
|
+
self.domain = $2
|
67
|
+
self.resource = $3
|
68
|
+
else
|
69
|
+
set_user($1)
|
70
|
+
set_domain($2)
|
71
|
+
set_resource($3)
|
72
|
+
end
|
73
|
+
|
74
|
+
else
|
75
|
+
raise InvalidComponentError,
|
76
|
+
"unrecognised opaque part for xmpp url: #{@opaque}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :domain, :user
|
81
|
+
attr_reader :resource
|
82
|
+
|
83
|
+
def check_user(v)
|
84
|
+
return true unless v
|
85
|
+
return true if v.size == 0
|
86
|
+
|
87
|
+
if OPAQUE !~ v || /\A#{USER_PATTERN}\z/o !~ v
|
88
|
+
raise InvalidComponentError,
|
89
|
+
"bad component(expected opaque component): #{v}"
|
90
|
+
end
|
91
|
+
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
private :check_user
|
95
|
+
|
96
|
+
def set_user(v)
|
97
|
+
@user = v
|
98
|
+
end
|
99
|
+
protected :set_user
|
100
|
+
|
101
|
+
def user=(v)
|
102
|
+
check_user(v)
|
103
|
+
set_user(v)
|
104
|
+
v
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_domain(v)
|
108
|
+
return true unless v
|
109
|
+
return true if v.size == 0
|
110
|
+
|
111
|
+
if OPAQUE !~ v || /\A#{DOMAIN_PATTERN}*\z/o !~ v
|
112
|
+
raise InvalidComponentError,
|
113
|
+
"bad component(expected opaque component): #{v}"
|
114
|
+
end
|
115
|
+
|
116
|
+
return true
|
117
|
+
end
|
118
|
+
private :check_domain
|
119
|
+
|
120
|
+
def set_domain(v)
|
121
|
+
@domain = v
|
122
|
+
end
|
123
|
+
protected :set_domain
|
124
|
+
|
125
|
+
def domain=(v)
|
126
|
+
check_domain(v)
|
127
|
+
set_domain(v)
|
128
|
+
v
|
129
|
+
end
|
130
|
+
|
131
|
+
def check_resource(v)
|
132
|
+
return true unless v
|
133
|
+
return true if v.size == 0
|
134
|
+
|
135
|
+
if OPAQUE !~ v ||
|
136
|
+
/\A\/?(#{RESOURCE_PATTERN}(?:\/#{RESOURCE_PATTERN})*)\z/o !~ v
|
137
|
+
raise InvalidComponentError,
|
138
|
+
"bad component(expected opaque component): #{v}"
|
139
|
+
end
|
140
|
+
|
141
|
+
return true
|
142
|
+
end
|
143
|
+
private :check_resource
|
144
|
+
|
145
|
+
def set_resource(v)
|
146
|
+
@resource = v
|
147
|
+
end
|
148
|
+
protected :set_resource
|
149
|
+
|
150
|
+
def resource=(v)
|
151
|
+
check_resource(v)
|
152
|
+
set_resource(v)
|
153
|
+
v
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s(include_scheme=false)
|
157
|
+
(include_scheme ? @scheme + ':' : '') + (@user ? @user + '@' : '') + (@domain ? @domain : '') + (@resource ? '/' + @resource : '')
|
158
|
+
end
|
159
|
+
|
160
|
+
def server_jid!
|
161
|
+
self.resource = nil
|
162
|
+
self.user = nil
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
def server_jid
|
167
|
+
Cancer::JID.build([nil, @domain, nil])
|
168
|
+
end
|
169
|
+
|
170
|
+
def component_jid(name)
|
171
|
+
Cancer::JID.build([nil, (name + '.' + @domain), nil])
|
172
|
+
end
|
173
|
+
|
174
|
+
def bare_jid!
|
175
|
+
self.resource = nil
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
def bare_jid
|
180
|
+
Cancer::JID.build([@user, @domain, nil])
|
181
|
+
end
|
182
|
+
|
183
|
+
def merge!(options={})
|
184
|
+
self.user = options[:user] if options.key? :user
|
185
|
+
self.domain = options[:domain] if options.key? :domain
|
186
|
+
self.resource = options[:resource] if options.key? :resource
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
def merge(options={})
|
191
|
+
Cancer::JID.build([options[:user] || @user, options[:domain] || @domain, options[:resource] || @resource])
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_jid
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
def to_hash
|
199
|
+
hash = { :user => @user, :domain => @domain, :resource => @resource }
|
200
|
+
hash.keys.each { |k| hash.delete(k) if hash[k].nil? }
|
201
|
+
hash
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
module URI
|
208
|
+
@@schemes['XMPP'] = Cancer::JID
|
209
|
+
end
|
210
|
+
|
211
|
+
class String
|
212
|
+
def to_jid
|
213
|
+
Cancer::JID.parse(self) rescue nil
|
214
|
+
end
|
215
|
+
end
|
data/lib/cancer/lock.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module Cancer
|
3
|
+
class Lock
|
4
|
+
|
5
|
+
def initialize(&proc)
|
6
|
+
@mutex = Mutex.new
|
7
|
+
@condition = ConditionVariable.new
|
8
|
+
@proc = proc
|
9
|
+
@values = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def wait!
|
13
|
+
@mutex.synchronize do
|
14
|
+
@condition.wait(@mutex)
|
15
|
+
if @values.size == 1 and Exception === @values.first
|
16
|
+
raise @values.first
|
17
|
+
else
|
18
|
+
@proc.call(*@values) if @proc
|
19
|
+
return *@values
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def continue!(*values)
|
25
|
+
@mutex.synchronize do
|
26
|
+
@values = values
|
27
|
+
@condition.signal
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/cancer/spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require 'cancer/spec/extentions'
|
3
|
+
|
4
|
+
module Cancer
|
5
|
+
# A set of tools to test Cancer using rspec.
|
6
|
+
#
|
7
|
+
# Spec::Runner.configure do |config|
|
8
|
+
# config.include(Cancer::Spec)
|
9
|
+
# end
|
10
|
+
module Spec
|
11
|
+
|
12
|
+
autoload :Error, 'cancer/spec/error'
|
13
|
+
autoload :Matcher, 'cancer/spec/matcher'
|
14
|
+
autoload :MockStream, 'cancer/spec/mock_stream'
|
15
|
+
autoload :Transcript, 'cancer/spec/transcript'
|
16
|
+
autoload :MockAdapter, 'cancer/spec/mock_adapter'
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :debug
|
20
|
+
end
|
21
|
+
|
22
|
+
def dialog(&proc)
|
23
|
+
Cancer::Spec::MockAdapter.mock(&proc)
|
24
|
+
end
|
25
|
+
|
26
|
+
def dialog_with(options={}, &proc)
|
27
|
+
Cancer::Spec::MockStream.mock do |adapter|
|
28
|
+
options = {:jid => 'alice@wonderland.lit', :password => 'ihatethequeen'}.merge(options)
|
29
|
+
options = options.merge(:adapter => adapter)
|
30
|
+
Cancer::Stream.open(options, &proc)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cancer
|
2
|
+
module Spec
|
3
|
+
|
4
|
+
class Error < ::Spec::Expectations::ExpectationNotMetError
|
5
|
+
attr_reader :error_message, :expectation_backtrace
|
6
|
+
def initialize(error_message, expectation_backtrace=nil)
|
7
|
+
@error_message, @expectation_backtrace = error_message, expectation_backtrace
|
8
|
+
end
|
9
|
+
def message
|
10
|
+
@error_message
|
11
|
+
end
|
12
|
+
def backtrace
|
13
|
+
@expectation_backtrace || super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
module Spec
|
3
|
+
module Matchers
|
4
|
+
class RaiseError #:nodoc:
|
5
|
+
def matches?(given_proc)
|
6
|
+
@raised_expected_error = false
|
7
|
+
@with_expected_message = false
|
8
|
+
@eval_block = false
|
9
|
+
@eval_block_passed = false
|
10
|
+
begin
|
11
|
+
given_proc.call
|
12
|
+
rescue Spec::Expectations::ExpectationNotMetError => e
|
13
|
+
@eval_block_passed = true
|
14
|
+
@raised_expected_error = true
|
15
|
+
@with_expected_message = true
|
16
|
+
raise e
|
17
|
+
|
18
|
+
rescue @expected_error => @actual_error
|
19
|
+
@raised_expected_error = true
|
20
|
+
@with_expected_message = verify_message
|
21
|
+
rescue Exception => @actual_error
|
22
|
+
# This clause should be empty, but rcov will not report it as covered
|
23
|
+
# unless something (anything) is executed within the clause
|
24
|
+
rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0"
|
25
|
+
end
|
26
|
+
|
27
|
+
unless negative_expectation?
|
28
|
+
eval_block if @raised_expected_error && @with_expected_message && @block
|
29
|
+
end
|
30
|
+
ensure
|
31
|
+
return (@raised_expected_error & @with_expected_message) ? (@eval_block ? @eval_block_passed : true) : false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Nokogiri
|
38
|
+
module XML
|
39
|
+
class Node
|
40
|
+
def strip_empty_text!
|
41
|
+
if node_type == ELEMENT_NODE
|
42
|
+
self.children.each do |child|
|
43
|
+
child.strip_empty_text!
|
44
|
+
end
|
45
|
+
elsif node_type == TEXT_NODE
|
46
|
+
if to_s =~ /^[\s\n]+$/m
|
47
|
+
unlink
|
48
|
+
else
|
49
|
+
self.replace(Text.new(self.to_s.strip, self.document))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
def matching?(other)
|
55
|
+
(self.node_type == other.node_type) and
|
56
|
+
(
|
57
|
+
(
|
58
|
+
(self.node_type == ELEMENT_NODE) and
|
59
|
+
(self.name == other.name) and
|
60
|
+
((!!self.namespace) == (!!other.namespace)) and
|
61
|
+
(self.namespace ? (self.namespace.href == other.namespace.href) : true) and
|
62
|
+
(self.attributes.size == other.attributes.size) and
|
63
|
+
(self.attributes.inject(other.attributes) { |m, (x, n)|
|
64
|
+
m.delete(n.name) if m[n.name] and m[n.name].to_s == n.to_s; m }.empty?) and
|
65
|
+
(self.children.size == other.children.size) and
|
66
|
+
(self.children.inject(other.children.to_a) { |m, n|
|
67
|
+
m.shift if n.matching? m.first ; m }.empty?)
|
68
|
+
) or (
|
69
|
+
(self.node_type == TEXT_NODE) and
|
70
|
+
(self.to_s == other.to_s)
|
71
|
+
) or (
|
72
|
+
(self.node_type == CDATA_SECTION_NODE) and
|
73
|
+
(self.to_s == other.to_s)
|
74
|
+
)
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Cancer
|
2
|
+
module Spec
|
3
|
+
|
4
|
+
class Matcher
|
5
|
+
def initialize(transcript)
|
6
|
+
@transcript = transcript
|
7
|
+
end
|
8
|
+
def matches?(mock)
|
9
|
+
mock.transcript = @transcript.dup
|
10
|
+
mock.dialog.call(mock)
|
11
|
+
|
12
|
+
if $cancer_error
|
13
|
+
raise $cancer_error
|
14
|
+
$cancer_error = nil
|
15
|
+
end
|
16
|
+
return true
|
17
|
+
rescue ::Spec::Expectations::ExpectationNotMetError => e
|
18
|
+
@cancer_error ||= e
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
def failure_message_for_should
|
22
|
+
@cancer_error.message
|
23
|
+
end
|
24
|
+
def failure_message_for_should_not
|
25
|
+
@cancer_error.message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|