sprsquish-blather 0.1 → 0.2.3
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.
- data/LICENSE +2 -0
- data/README.rdoc +100 -0
- data/Rakefile +110 -0
- data/examples/drb_client.rb +5 -0
- data/examples/echo.rb +18 -0
- data/ext/extconf.rb +65 -0
- data/ext/push_parser.c +231 -0
- data/lib/blather/client.rb +219 -44
- data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
- data/lib/blather/core_ext/libxml.rb +28 -0
- data/lib/blather/errors/sasl_error.rb +87 -0
- data/lib/blather/errors/stanza_error.rb +262 -0
- data/lib/blather/errors/stream_error.rb +253 -0
- data/lib/blather/errors.rb +48 -0
- data/lib/blather/{core/jid.rb → jid.rb} +15 -26
- data/lib/blather/{core/roster.rb → roster.rb} +22 -0
- data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
- data/lib/blather/stanza/iq/disco.rb +11 -0
- data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
- data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
- data/lib/blather/stanza/iq/query.rb +51 -0
- data/lib/blather/stanza/iq/roster.rb +90 -0
- data/lib/blather/stanza/iq.rb +38 -0
- data/lib/blather/stanza/message.rb +58 -0
- data/lib/blather/stanza/presence/status.rb +78 -0
- data/lib/blather/stanza/presence/subscription.rb +72 -0
- data/lib/blather/stanza/presence.rb +45 -0
- data/lib/blather/stanza.rb +101 -0
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +70 -0
- data/lib/blather/stream/resource.rb +48 -0
- data/lib/blather/stream/sasl.rb +173 -0
- data/lib/blather/stream/session.rb +36 -0
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +33 -0
- data/lib/blather/stream.rb +249 -0
- data/lib/blather/xmpp_node.rb +199 -0
- data/lib/blather.rb +40 -41
- data/spec/blather/core_ext/libxml_spec.rb +58 -0
- data/spec/blather/errors/sasl_error_spec.rb +56 -0
- data/spec/blather/errors/stanza_error_spec.rb +148 -0
- data/spec/blather/errors/stream_error_spec.rb +114 -0
- data/spec/blather/errors_spec.rb +40 -0
- data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
- data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
- data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
- data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +34 -0
- data/spec/blather/stanza/iq/roster_spec.rb +123 -0
- data/spec/blather/stanza/iq_spec.rb +40 -0
- data/spec/blather/stanza/message_spec.rb +52 -0
- data/spec/blather/stanza/presence/status_spec.rb +102 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
- data/spec/blather/stanza/presence_spec.rb +53 -0
- data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
- data/spec/build_safe.rb +20 -0
- data/spec/spec_helper.rb +7 -17
- metadata +79 -59
- data/CHANGELOG +0 -1
- data/blather.gemspec +0 -73
- data/lib/blather/callback.rb +0 -24
- data/lib/blather/core/errors.rb +0 -24
- data/lib/blather/core/stanza.rb +0 -90
- data/lib/blather/core/stream.rb +0 -179
- data/lib/blather/core/xmpp_node.rb +0 -95
- data/lib/blather/extensions/last_activity.rb +0 -57
- data/lib/blather/extensions/version.rb +0 -85
- data/spec/blather/core/stream_spec.rb +0 -263
@@ -0,0 +1,249 @@
|
|
1
|
+
module Blather
|
2
|
+
|
3
|
+
class Stream < EventMachine::Connection
|
4
|
+
##
|
5
|
+
# Start the stream between client and server
|
6
|
+
# [client] must be an object that will respond to #call and #jid=
|
7
|
+
# [jid] must be a valid argument for JID.new (see JID)
|
8
|
+
# [pass] must be the password
|
9
|
+
# [host] (optional) must be the hostname or IP to connect to. defaults to the domain of [jid]
|
10
|
+
# [port] (optional) must be the port to connect to. defaults to 5222
|
11
|
+
def self.start(client, jid, pass, host = nil, port = 5222)
|
12
|
+
jid = JID.new jid
|
13
|
+
host ||= jid.domain
|
14
|
+
|
15
|
+
EM.connect host, port, self, client, jid, pass
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Send data over the wire
|
20
|
+
# The argument for this can be anything that
|
21
|
+
# responds to #to_s
|
22
|
+
def send(stanza)
|
23
|
+
#TODO Queue if not ready
|
24
|
+
LOG.debug "SENDING: (#{caller[1]}) #{stanza}"
|
25
|
+
send_data stanza.respond_to?(:to_xml) ? stanza.to_xml : stanza.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# True if the stream is in the stopped state
|
30
|
+
def stopped?
|
31
|
+
@state == :stopped
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# True when the stream is in the negotiation phase.
|
36
|
+
def negotiating?
|
37
|
+
![:stopped, :ready].include? @state
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# True when the stream is ready
|
42
|
+
# The stream is ready immediately after receiving <stream:stream>
|
43
|
+
# and before any feature negotion. Once feature negoation starts
|
44
|
+
# the stream will not be ready until all negotations have completed
|
45
|
+
# successfully.
|
46
|
+
def ready?
|
47
|
+
@state == :ready
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Called by EM.connect to initialize stream variables
|
52
|
+
def initialize(client, jid, pass) # :nodoc:
|
53
|
+
super()
|
54
|
+
|
55
|
+
@error = nil
|
56
|
+
@client = client
|
57
|
+
|
58
|
+
self.jid = jid
|
59
|
+
@pass = pass
|
60
|
+
|
61
|
+
@to = @jid.domain
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Called when EM completes the connection to the server
|
66
|
+
# this kicks off the starttls/authorize/bind process
|
67
|
+
def connection_completed # :nodoc:
|
68
|
+
# @keepalive = EM::Timer.new(60) { send_data ' ' }
|
69
|
+
@state = :stopped
|
70
|
+
dispatch
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Called by EM with data from the wire
|
75
|
+
def receive_data(data) # :nodoc:
|
76
|
+
LOG.debug "\n#{'-'*30}\n"
|
77
|
+
LOG.debug "<< #{data}"
|
78
|
+
@parser.receive_data data
|
79
|
+
|
80
|
+
rescue ParseError => e
|
81
|
+
@error = e
|
82
|
+
stop
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Called by EM when the connection is closed
|
87
|
+
def unbind # :nodoc:
|
88
|
+
# @keepalive.cancel
|
89
|
+
@state = :stopped
|
90
|
+
@client.call @error if @error
|
91
|
+
@client.stopped
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Called by the parser with parsed nodes
|
96
|
+
def receive(node) # :nodoc:
|
97
|
+
LOG.debug "RECEIVING (#{node.element_name}) #{node}"
|
98
|
+
@node = node
|
99
|
+
|
100
|
+
case @node.element_name
|
101
|
+
when 'stream:stream'
|
102
|
+
@state = :ready if @state == :stopped
|
103
|
+
|
104
|
+
when 'stream:end'
|
105
|
+
stop
|
106
|
+
|
107
|
+
when 'stream:features'
|
108
|
+
@features = @node.children
|
109
|
+
@state = :features
|
110
|
+
dispatch
|
111
|
+
|
112
|
+
when 'stream:error'
|
113
|
+
@error = StreamError.import @node
|
114
|
+
stop
|
115
|
+
@state = :error
|
116
|
+
|
117
|
+
else
|
118
|
+
dispatch
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Ensure the JID gets attached to the client
|
125
|
+
def jid=(new_jid) # :nodoc:
|
126
|
+
LOG.debug "NEW JID: #{new_jid}"
|
127
|
+
@jid = JID.new new_jid
|
128
|
+
@client.jid = @jid
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
##
|
133
|
+
# Dispatch based on current state
|
134
|
+
def dispatch
|
135
|
+
__send__ @state
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Start the stream
|
140
|
+
# Each time the stream is started or re-started we need to kill off the old
|
141
|
+
# parser so as not to confuse it
|
142
|
+
def start
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Stop the stream
|
147
|
+
def stop
|
148
|
+
unless @state == :stopped
|
149
|
+
@state = :stopped
|
150
|
+
send '</stream:stream>'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Called when @state == :stopped to start the stream
|
156
|
+
# Counter intuitive, I know
|
157
|
+
def stopped
|
158
|
+
start
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Called when @state == :ready
|
163
|
+
# Simply passes the stanza to the client
|
164
|
+
def ready
|
165
|
+
@client.call @node.to_stanza
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Called when @state == :features
|
170
|
+
# Runs through the list of features starting each one in turn
|
171
|
+
def features
|
172
|
+
feature = @features.first
|
173
|
+
LOG.debug "FEATURE: #{feature}"
|
174
|
+
@state = case feature ? feature.namespaces.default.href : nil
|
175
|
+
when 'urn:ietf:params:xml:ns:xmpp-tls' then :establish_tls
|
176
|
+
when 'urn:ietf:params:xml:ns:xmpp-sasl' then :authenticate_sasl
|
177
|
+
when 'urn:ietf:params:xml:ns:xmpp-bind' then :bind_resource
|
178
|
+
when 'urn:ietf:params:xml:ns:xmpp-session' then :establish_session
|
179
|
+
else :ready
|
180
|
+
end
|
181
|
+
|
182
|
+
# Dispatch to the individual feature methods unless
|
183
|
+
# feature negotiation is complete
|
184
|
+
dispatch unless ready?
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Start TLS
|
189
|
+
def establish_tls
|
190
|
+
unless @tls
|
191
|
+
@tls = TLS.new self
|
192
|
+
# on success destroy the TLS object and restart the stream
|
193
|
+
@tls.on_success { LOG.debug "TLS: SUCCESS"; @tls = nil; start }
|
194
|
+
# on failure stop the stream
|
195
|
+
@tls.on_failure { |err| LOG.debug "TLS: FAILURE"; @error = err; stop }
|
196
|
+
|
197
|
+
@node = @features.shift
|
198
|
+
end
|
199
|
+
@tls.handle @node
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Authenticate via SASL
|
204
|
+
def authenticate_sasl
|
205
|
+
unless @sasl
|
206
|
+
@sasl = SASL.new(self, @jid, @pass)
|
207
|
+
# on success destroy the SASL object and restart the stream
|
208
|
+
@sasl.on_success { LOG.debug "SASL SUCCESS"; @sasl = nil; start }
|
209
|
+
# on failure set the error and stop the stream
|
210
|
+
@sasl.on_failure { |err| LOG.debug "SASL FAIL"; @error = err; stop }
|
211
|
+
|
212
|
+
@node = @features.shift
|
213
|
+
end
|
214
|
+
@sasl.handle @node
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Bind to the resource provided by either the client or the server
|
219
|
+
def bind_resource
|
220
|
+
unless @resource
|
221
|
+
@resource = Resource.new self, @jid
|
222
|
+
# on success destroy the Resource object, set the jid, continue along the features dispatch process
|
223
|
+
@resource.on_success { |jid| LOG.debug "RESOURCE: SUCCESS"; @resource = nil; self.jid = jid; @state = :features; dispatch }
|
224
|
+
# on failure end the stream
|
225
|
+
@resource.on_failure { |err| LOG.debug "RESOURCE: FAILURE"; @error = err; stop }
|
226
|
+
|
227
|
+
@node = @features.shift
|
228
|
+
end
|
229
|
+
@resource.handle @node
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Establish the session between client and server
|
234
|
+
def establish_session
|
235
|
+
unless @session
|
236
|
+
@session = Session.new self, @to
|
237
|
+
# on success destroy the session object, let the client know the stream has been started
|
238
|
+
# then continue the features dispatch process
|
239
|
+
@session.on_success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.stream_started(self); @state = :features; dispatch }
|
240
|
+
# on failure end the stream
|
241
|
+
@session.on_failure { |err| LOG.debug "SESSION: FAILURE"; @error = err; stop }
|
242
|
+
|
243
|
+
@node = @features.shift
|
244
|
+
end
|
245
|
+
@session.handle @node
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module Blather
|
2
|
+
|
3
|
+
##
|
4
|
+
# Base XML Node
|
5
|
+
# All XML classes subclass XMPPNode
|
6
|
+
# it allows the addition of helpers
|
7
|
+
class XMPPNode < XML::Node
|
8
|
+
BASE_NAMES = %w[presence message iq].freeze
|
9
|
+
|
10
|
+
@@registrations = {}
|
11
|
+
|
12
|
+
class_inheritable_accessor :ns,
|
13
|
+
:name
|
14
|
+
|
15
|
+
##
|
16
|
+
# Lets a subclass register itself
|
17
|
+
#
|
18
|
+
# This registers a namespace that is used when looking
|
19
|
+
# up the class name of the object to instantiate when a new
|
20
|
+
# stanza is received
|
21
|
+
def self.register(name, ns = nil)
|
22
|
+
self.name = name.to_s
|
23
|
+
self.ns = ns
|
24
|
+
@@registrations[[self.name, self.ns]] = self
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Find the class to use given the name and namespace of a stanza
|
29
|
+
def self.class_from_registration(name, xmlns)
|
30
|
+
name = name.to_s
|
31
|
+
@@registrations[[name, xmlns]] || @@registrations[[name, nil]]
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Looks up the class to use then instantiates an object
|
36
|
+
# of that class and imports all the <tt>node</tt>'s attributes
|
37
|
+
# and children into it.
|
38
|
+
def self.import(node)
|
39
|
+
klass = class_from_registration(node.element_name, node.namespace)
|
40
|
+
if klass && klass != self
|
41
|
+
klass.import(node)
|
42
|
+
else
|
43
|
+
new(node.element_name).inherit(node)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Provides an attribute reader helper. Default behavior is to
|
49
|
+
# conver the values of the attribute into a symbol. This can
|
50
|
+
# be turned off by passing <tt>:to_sym => false</tt>
|
51
|
+
#
|
52
|
+
# class Node
|
53
|
+
# attribute_reader :type
|
54
|
+
# attribute_reader :name, :to_sym => false
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# n = Node.new
|
58
|
+
# n.attributes[:type] = 'foo'
|
59
|
+
# n.type == :foo
|
60
|
+
# n.attributes[:name] = 'bar'
|
61
|
+
# n.name == 'bar'
|
62
|
+
def self.attribute_reader(*syms)
|
63
|
+
opts = syms.last.is_a?(Hash) ? syms.pop : {}
|
64
|
+
syms.flatten.each do |sym|
|
65
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
66
|
+
def #{sym}
|
67
|
+
attributes[:#{sym}]#{".to_sym unless attributes[:#{sym}].blank?" unless opts[:to_sym] == false}
|
68
|
+
end
|
69
|
+
END
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Provides an attribute writer helper.
|
75
|
+
#
|
76
|
+
# class Node
|
77
|
+
# attribute_writer :type
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# n = Node.new
|
81
|
+
# n.type = 'foo'
|
82
|
+
# n.attributes[:type] == 'foo'
|
83
|
+
def self.attribute_writer(*syms)
|
84
|
+
syms.flatten.each do |sym|
|
85
|
+
next if sym.is_a?(Hash)
|
86
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
87
|
+
def #{sym}=(value)
|
88
|
+
attributes[:#{sym}] = value
|
89
|
+
end
|
90
|
+
END
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Provides an attribute accessor helper combining
|
96
|
+
# <tt>attribute_reader</tt> and <tt>attribute_writer</tt>
|
97
|
+
#
|
98
|
+
# class Node
|
99
|
+
# attribute_accessor :type
|
100
|
+
# attribute_accessor :name, :to_sym => false
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# n = Node.new
|
104
|
+
# n.type = 'foo'
|
105
|
+
# n.type == :foo
|
106
|
+
# n.name = 'bar'
|
107
|
+
# n.name == 'bar'
|
108
|
+
def self.attribute_accessor(*syms)
|
109
|
+
attribute_reader *syms
|
110
|
+
attribute_writer *syms
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Automatically sets the namespace registered by the subclass
|
115
|
+
def initialize(name = nil, content = nil)
|
116
|
+
name ||= self.class.name
|
117
|
+
content = content.to_s if content
|
118
|
+
|
119
|
+
super name.to_s, content
|
120
|
+
self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s)
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Quickway of turning itself into a proper object
|
125
|
+
def to_stanza
|
126
|
+
self.class.import self
|
127
|
+
end
|
128
|
+
|
129
|
+
def namespace=(ns)
|
130
|
+
if ns
|
131
|
+
ns = {nil => ns} unless ns.is_a?(Hash)
|
132
|
+
ns.each { |p,n| XML::Namespace.new self, p, n }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def namespace(prefix = nil)
|
137
|
+
(ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Remove a child with the name and (optionally) namespace given
|
142
|
+
def remove_child(name, ns = nil)
|
143
|
+
name = name.to_s
|
144
|
+
self.detect { |n| n.remove! if n.element_name == name && (!ns || n.namespace == ns) }
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Remove all children with a given name
|
149
|
+
def remove_children(name)
|
150
|
+
name = name.to_s
|
151
|
+
self.find(name).each { |n| n.remove! }
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Pull the content from a child
|
156
|
+
def content_from(name)
|
157
|
+
name = name.to_s
|
158
|
+
(child = self.detect { |n| n.element_name == name }) ? child.content : nil
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Create a copy
|
163
|
+
def copy(deep = true)
|
164
|
+
copy = self.class.new.inherit(self)
|
165
|
+
copy.element_name = self.element_name
|
166
|
+
copy
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Inherit all of <tt>stanza</tt>'s attributes and children
|
171
|
+
def inherit(stanza)
|
172
|
+
inherit_attrs stanza.attributes
|
173
|
+
stanza.children.each { |c| self << c.copy(true) }
|
174
|
+
self
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Inherit only <tt>stanza</tt>'s attributes
|
179
|
+
def inherit_attrs(attrs)
|
180
|
+
attrs.each { |a| attributes[a.name] = a.value }
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Turn itself into an XML string and remove all whitespace between nodes
|
186
|
+
def to_xml
|
187
|
+
# TODO: Fix this for HTML nodes (and any other that might require whitespace)
|
188
|
+
to_s.gsub(">\n<", '><')
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Override #find to work when a node isn't attached to a document
|
193
|
+
def find(what, nslist = nil)
|
194
|
+
what = what.to_s
|
195
|
+
(self.doc ? super(what, nslist) : select { |i| i.element_name == what })
|
196
|
+
end
|
197
|
+
end #XMPPNode
|
198
|
+
|
199
|
+
end
|
data/lib/blather.rb
CHANGED
@@ -1,54 +1,53 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '..')
|
2
3
|
|
4
|
+
# Require the necessary files
|
3
5
|
%w[
|
4
6
|
rubygems
|
5
|
-
xml/libxml
|
6
7
|
eventmachine
|
8
|
+
ext/push_parser
|
9
|
+
xml/libxml
|
7
10
|
digest/md5
|
8
11
|
logger
|
9
12
|
|
10
|
-
blather/
|
11
|
-
|
12
|
-
|
13
|
-
blather/
|
14
|
-
blather/
|
15
|
-
blather/
|
16
|
-
blather/
|
17
|
-
blather/
|
18
|
-
|
19
|
-
blather/
|
20
|
-
blather/
|
21
|
-
|
22
|
-
blather/
|
23
|
-
blather/
|
24
|
-
blather/
|
25
|
-
blather/
|
26
|
-
blather/
|
27
|
-
|
28
|
-
blather/
|
29
|
-
blather/
|
30
|
-
blather/
|
31
|
-
blather/
|
32
|
-
blather/
|
33
|
-
|
13
|
+
blather/core_ext/active_support
|
14
|
+
blather/core_ext/libxml
|
15
|
+
|
16
|
+
blather/errors
|
17
|
+
blather/errors/sasl_error
|
18
|
+
blather/errors/stanza_error
|
19
|
+
blather/errors/stream_error
|
20
|
+
blather/jid
|
21
|
+
blather/roster
|
22
|
+
blather/roster_item
|
23
|
+
blather/xmpp_node
|
24
|
+
|
25
|
+
blather/stanza
|
26
|
+
blather/stanza/iq
|
27
|
+
blather/stanza/iq/query
|
28
|
+
blather/stanza/iq/roster
|
29
|
+
blather/stanza/iq/disco
|
30
|
+
blather/stanza/iq/discos/disco_info
|
31
|
+
blather/stanza/iq/discos/disco_items
|
32
|
+
blather/stanza/message
|
33
|
+
blather/stanza/presence
|
34
|
+
blather/stanza/presence/status
|
35
|
+
blather/stanza/presence/subscription
|
36
|
+
|
37
|
+
blather/stream
|
38
|
+
blather/stream/client
|
39
|
+
blather/stream/component
|
40
|
+
blather/stream/stream_handler
|
41
|
+
blather/stream/parser
|
42
|
+
blather/stream/resource
|
43
|
+
blather/stream/sasl
|
44
|
+
blather/stream/session
|
45
|
+
blather/stream/tls
|
34
46
|
].each { |r| require r }
|
35
47
|
|
36
|
-
XML
|
48
|
+
XML.indent_tree_output = false
|
37
49
|
|
38
50
|
module Blather
|
39
|
-
LOG = Logger.new
|
40
|
-
|
41
|
-
def run(jid, password, client, host = nil, port = 5222)
|
42
|
-
EM.run { Stream.start client, JID.new(jid), password, host, port }
|
43
|
-
end
|
44
|
-
module_function :run
|
45
|
-
|
46
|
-
MAJOR = 0
|
47
|
-
MINOR = 1
|
48
|
-
VERSION = [MAJOR, MINOR]*'.'
|
49
|
-
|
50
|
-
def version
|
51
|
-
VERSION
|
52
|
-
end
|
53
|
-
module_function :version
|
51
|
+
LOG = Logger.new(STDOUT) unless const_defined?(:LOG)
|
52
|
+
LOG.level = Logger::INFO
|
54
53
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'LibXML::XML::Node' do
|
4
|
+
it 'aliases #name to #element_name' do
|
5
|
+
node = LibXML::XML::Node.new 'foo'
|
6
|
+
node.must_respond_to :element_name
|
7
|
+
node.element_name.must_equal node.name
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'aliases #name= to #element_name=' do
|
11
|
+
node = LibXML::XML::Node.new 'foo'
|
12
|
+
node.must_respond_to :element_name=
|
13
|
+
node.element_name.must_equal node.name
|
14
|
+
node.element_name = 'bar'
|
15
|
+
node.element_name.must_equal 'bar'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'LibXML::XML::Attributes' do
|
20
|
+
it 'provides a helper to remove a specified attribute' do
|
21
|
+
attrs = LibXML::XML::Node.new('foo').attributes
|
22
|
+
attrs['foo'] = 'bar'
|
23
|
+
attrs['foo'].must_equal 'bar'
|
24
|
+
attrs.remove 'foo'
|
25
|
+
attrs['foo'].must_be_nil
|
26
|
+
|
27
|
+
attrs['foo'] = 'bar'
|
28
|
+
attrs['foo'].must_equal 'bar'
|
29
|
+
attrs.remove :foo
|
30
|
+
attrs['foo'].must_be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'allows symbols as hash keys' do
|
34
|
+
attrs = LibXML::XML::Node.new('foo').attributes
|
35
|
+
attrs['foo'] = 'bar'
|
36
|
+
|
37
|
+
attrs['foo'].must_equal 'bar'
|
38
|
+
attrs[:foo].must_equal 'bar'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'removes an attribute when set to nil' do
|
42
|
+
attrs = LibXML::XML::Node.new('foo').attributes
|
43
|
+
attrs['foo'] = 'bar'
|
44
|
+
|
45
|
+
attrs['foo'].must_equal 'bar'
|
46
|
+
attrs['foo'] = nil
|
47
|
+
attrs['foo'].must_be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'allows attribute values to change' do
|
51
|
+
attrs = LibXML::XML::Node.new('foo').attributes
|
52
|
+
attrs['foo'] = 'bar'
|
53
|
+
|
54
|
+
attrs['foo'].must_equal 'bar'
|
55
|
+
attrs['foo'] = 'baz'
|
56
|
+
attrs['foo'].must_equal 'baz'
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
def sasl_error_node(err_name = 'aborted')
|
4
|
+
node = XMPPNode.new 'failure'
|
5
|
+
node.namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
6
|
+
|
7
|
+
node << XMPPNode.new(err_name)
|
8
|
+
node
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Blather::SASLError' do
|
12
|
+
it 'can import a node' do
|
13
|
+
SASLError.must_respond_to :import
|
14
|
+
e = SASLError.import sasl_error_node
|
15
|
+
e.must_be_kind_of SASLError
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'knows what class to instantiate' do
|
19
|
+
e = SASLError.import sasl_error_node
|
20
|
+
e.must_be_instance_of SASLError::Aborted
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'when instantiated' do
|
24
|
+
before do
|
25
|
+
@err_name = 'mechanism-too-weak'
|
26
|
+
@err = SASLError.import sasl_error_node(@err_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'provides a err_name attribute' do
|
30
|
+
@err.must_respond_to :err_name
|
31
|
+
@err.err_name.must_equal @err_name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'each XMPP SASL error type' do
|
36
|
+
%w[ aborted
|
37
|
+
incorrect-encoding
|
38
|
+
invalid-authzid
|
39
|
+
invalid-mechanism
|
40
|
+
mechanism-too-weak
|
41
|
+
not-authorized
|
42
|
+
temporary-auth-failure
|
43
|
+
].each do |error_type|
|
44
|
+
it "provides a class for #{error_type}" do
|
45
|
+
e = SASLError.import sasl_error_node(error_type)
|
46
|
+
klass = error_type.gsub(/^\w/) { |v| v.upcase }.gsub(/\-(\w)/) { |v| v.delete('-').upcase }
|
47
|
+
e.must_be_instance_of eval("SASLError::#{klass}")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "registers #{error_type} in the handler heirarchy" do
|
51
|
+
e = SASLError.import sasl_error_node(error_type)
|
52
|
+
e.handler_heirarchy.must_equal ["sasl_#{error_type.gsub('-','_').gsub('_error','')}_error".to_sym, :sasl_error, :error]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|