shingara-blather 0.4.8
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 +22 -0
- data/README.md +162 -0
- data/examples/echo.rb +18 -0
- data/examples/execute.rb +16 -0
- data/examples/ping_pong.rb +37 -0
- data/examples/print_hierarchy.rb +76 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/stream_only.rb +27 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/lib/blather/client/client.rb +310 -0
- data/lib/blather/client/dsl/pubsub.rb +170 -0
- data/lib/blather/client/dsl.rb +264 -0
- data/lib/blather/client.rb +87 -0
- data/lib/blather/core_ext/nokogiri.rb +40 -0
- data/lib/blather/errors/sasl_error.rb +43 -0
- data/lib/blather/errors/stanza_error.rb +107 -0
- data/lib/blather/errors/stream_error.rb +82 -0
- data/lib/blather/errors.rb +69 -0
- data/lib/blather/jid.rb +142 -0
- data/lib/blather/roster.rb +111 -0
- data/lib/blather/roster_item.rb +122 -0
- data/lib/blather/stanza/disco/disco_info.rb +176 -0
- data/lib/blather/stanza/disco/disco_items.rb +132 -0
- data/lib/blather/stanza/disco.rb +25 -0
- data/lib/blather/stanza/iq/query.rb +53 -0
- data/lib/blather/stanza/iq/roster.rb +179 -0
- data/lib/blather/stanza/iq.rb +138 -0
- data/lib/blather/stanza/message.rb +332 -0
- data/lib/blather/stanza/presence/status.rb +212 -0
- data/lib/blather/stanza/presence/subscription.rb +101 -0
- data/lib/blather/stanza/presence.rb +163 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
- data/lib/blather/stanza/pubsub/create.rb +65 -0
- data/lib/blather/stanza/pubsub/errors.rb +18 -0
- data/lib/blather/stanza/pubsub/event.rb +123 -0
- data/lib/blather/stanza/pubsub/items.rb +103 -0
- data/lib/blather/stanza/pubsub/publish.rb +103 -0
- data/lib/blather/stanza/pubsub/retract.rb +92 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
- data/lib/blather/stanza/pubsub/subscription.rb +134 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
- data/lib/blather/stanza/pubsub.rb +129 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
- data/lib/blather/stanza/pubsub_owner.rb +51 -0
- data/lib/blather/stanza.rb +149 -0
- data/lib/blather/stream/client.rb +31 -0
- data/lib/blather/stream/component.rb +38 -0
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/features/sasl.rb +187 -0
- data/lib/blather/stream/features/session.rb +44 -0
- data/lib/blather/stream/features/tls.rb +28 -0
- data/lib/blather/stream/features.rb +53 -0
- data/lib/blather/stream/parser.rb +102 -0
- data/lib/blather/stream.rb +231 -0
- data/lib/blather/xmpp_node.rb +218 -0
- data/lib/blather.rb +78 -0
- data/spec/blather/client/client_spec.rb +559 -0
- data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
- data/spec/blather/client/dsl_spec.rb +143 -0
- data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
- data/spec/blather/errors/sasl_error_spec.rb +33 -0
- data/spec/blather/errors/stanza_error_spec.rb +129 -0
- data/spec/blather/errors/stream_error_spec.rb +108 -0
- data/spec/blather/errors_spec.rb +33 -0
- data/spec/blather/jid_spec.rb +87 -0
- data/spec/blather/roster_item_spec.rb +96 -0
- data/spec/blather/roster_spec.rb +103 -0
- data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
- data/spec/blather/stanza/iq/query_spec.rb +64 -0
- data/spec/blather/stanza/iq/roster_spec.rb +140 -0
- data/spec/blather/stanza/iq_spec.rb +45 -0
- data/spec/blather/stanza/message_spec.rb +132 -0
- data/spec/blather/stanza/presence/status_spec.rb +132 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
- data/spec/blather/stanza/presence_spec.rb +66 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +67 -0
- data/spec/blather/stanza_spec.rb +116 -0
- data/spec/blather/stream/client_spec.rb +1011 -0
- data/spec/blather/stream/component_spec.rb +95 -0
- data/spec/blather/stream/parser_spec.rb +145 -0
- data/spec/blather/xmpp_node_spec.rb +231 -0
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +43 -0
- metadata +249 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'client')
|
|
2
|
+
|
|
3
|
+
module Blather
|
|
4
|
+
|
|
5
|
+
# # Blather DSL
|
|
6
|
+
#
|
|
7
|
+
# The DSL is a set of methods that enables you to write cleaner code. Being a
|
|
8
|
+
# module means it can be included in or extend any class you may want to
|
|
9
|
+
# create.
|
|
10
|
+
#
|
|
11
|
+
# Every stanza handler is registered as a method on the DSL.
|
|
12
|
+
#
|
|
13
|
+
# @example Include the DSL in the top level namespace.
|
|
14
|
+
#
|
|
15
|
+
# require 'blather/client'
|
|
16
|
+
# when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
|
17
|
+
#
|
|
18
|
+
# subscription :request? do |s|
|
|
19
|
+
# write_to_stream s.approve!
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# message :chat?, :body => 'exit' do |m|
|
|
23
|
+
# say m.from, 'Exiting ...'
|
|
24
|
+
# shutdown
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# message :chat?, :body do |m|
|
|
28
|
+
# say m.from, "You sent: #{m.body}"
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Set the DSL to its own namespace.
|
|
32
|
+
#
|
|
33
|
+
# require 'blather/client/dsl'
|
|
34
|
+
# module Echo
|
|
35
|
+
# extend Blather::DSL
|
|
36
|
+
# def self.run
|
|
37
|
+
# client.run
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
|
41
|
+
#
|
|
42
|
+
# subscription :request? do |s|
|
|
43
|
+
# write_to_stream s.approve!
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# message :chat?, :body => 'exit' do |m|
|
|
47
|
+
# say m.from, 'Exiting ...'
|
|
48
|
+
# shutdown
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# message :chat?, :body do |m|
|
|
52
|
+
# say m.from, "You sent: #{m.body}"
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# EM.run { Echo.run }
|
|
57
|
+
#
|
|
58
|
+
# @example Create a class out of it
|
|
59
|
+
#
|
|
60
|
+
# require 'blather/client/dsl'
|
|
61
|
+
# class Echo
|
|
62
|
+
# include Blather::DSL
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# echo = Echo.new
|
|
66
|
+
# echo.when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
|
67
|
+
#
|
|
68
|
+
# echo.subscription :request? do |s|
|
|
69
|
+
# write_to_stream s.approve!
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# echo.message :chat?, :body => 'exit' do |m|
|
|
73
|
+
# say m.from, 'Exiting ...'
|
|
74
|
+
# shutdown
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# echo.message :chat?, :body do |m|
|
|
78
|
+
# say m.from, "You sent: #{m.body}"
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# EM.run { echo.client.run }
|
|
82
|
+
module DSL
|
|
83
|
+
|
|
84
|
+
autoload :PubSub, File.expand_path(File.join(File.dirname(__FILE__), *%w[dsl pubsub]))
|
|
85
|
+
|
|
86
|
+
# The actual client connection
|
|
87
|
+
#
|
|
88
|
+
# @return [Blather::Client]
|
|
89
|
+
def client
|
|
90
|
+
@client ||= Client.new
|
|
91
|
+
end
|
|
92
|
+
module_function :client
|
|
93
|
+
|
|
94
|
+
# A pubsub helper
|
|
95
|
+
#
|
|
96
|
+
# @return [Blather::PubSub]
|
|
97
|
+
def pubsub
|
|
98
|
+
@pubsub ||= PubSub.new client, jid.domain
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Push data to the stream
|
|
102
|
+
# This works such that it can be chained:
|
|
103
|
+
# self << stanza1 << stanza2 << "raw data"
|
|
104
|
+
#
|
|
105
|
+
# @param [#to_xml, #to_s] stanza data to send down the wire
|
|
106
|
+
# @return [self]
|
|
107
|
+
def <<(stanza)
|
|
108
|
+
client.write stanza
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Prepare server settings
|
|
113
|
+
#
|
|
114
|
+
# @param [#to_s] jid the JID to authenticate with
|
|
115
|
+
# @param [#to_s] password the password to authenticate with
|
|
116
|
+
# @param [String] host (optional) the host to connect to (can be an IP). If
|
|
117
|
+
# this is `nil` the domain on the JID will be used
|
|
118
|
+
# @param [Fixnum, String] (optional) port the port to connect on
|
|
119
|
+
def setup(jid, password, host = nil, port = nil)
|
|
120
|
+
client.setup(jid, password, host, port)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Shutdown the connection.
|
|
124
|
+
# Flushes the write buffer then stops EventMachine
|
|
125
|
+
def shutdown
|
|
126
|
+
client.close
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Setup a before filter
|
|
130
|
+
#
|
|
131
|
+
# @param [Symbol] handler (optional) the stanza handler the filter should
|
|
132
|
+
# run before
|
|
133
|
+
# @param [guards] guards (optional) a set of guards to check the stanza
|
|
134
|
+
# against
|
|
135
|
+
# @yield [Blather::Stanza] stanza
|
|
136
|
+
def before(handler = nil, *guards, &block)
|
|
137
|
+
client.register_filter :before, handler, *guards, &block
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Setup an after filter
|
|
141
|
+
#
|
|
142
|
+
# @param [Symbol] handler (optional) the stanza handler the filter should
|
|
143
|
+
# run after
|
|
144
|
+
# @param [guards] guards (optional) a set of guards to check the stanza
|
|
145
|
+
# against
|
|
146
|
+
# @yield [Blather::Stanza] stanza
|
|
147
|
+
def after(handler = nil, *guards, &block)
|
|
148
|
+
client.register_filter :after, handler, *guards, &block
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Set handler for a stanza type
|
|
152
|
+
#
|
|
153
|
+
# @param [Symbol] handler the stanza type it should handle
|
|
154
|
+
# @param [guards] guards (optional) a set of guards to check the stanza
|
|
155
|
+
# against
|
|
156
|
+
# @yield [Blather::Stanza] stanza
|
|
157
|
+
def handle(handler, *guards, &block)
|
|
158
|
+
client.register_handler handler, *guards, &block
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Wrapper for "handle :ready" (just a bit of syntactic sugar)
|
|
162
|
+
#
|
|
163
|
+
# This is run after the connection has been completely setup
|
|
164
|
+
def when_ready(&block)
|
|
165
|
+
handle :ready, &block
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Wrapper for "handle :disconnected"
|
|
169
|
+
#
|
|
170
|
+
# This is run after the connection has been shut down.
|
|
171
|
+
#
|
|
172
|
+
# @example Reconnect after a disconnection
|
|
173
|
+
# disconnected { client.run }
|
|
174
|
+
def disconnected(&block)
|
|
175
|
+
handle :disconnected, &block
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Set current status
|
|
179
|
+
#
|
|
180
|
+
# @param [Blather::Stanza::Presence::State::VALID_STATES] state the current
|
|
181
|
+
# state
|
|
182
|
+
# @param [#to_s] msg the status message to use
|
|
183
|
+
def set_status(state = nil, msg = nil)
|
|
184
|
+
client.status = state, msg
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Direct access to the roster
|
|
188
|
+
#
|
|
189
|
+
# @return [Blather::Roster]
|
|
190
|
+
def my_roster
|
|
191
|
+
client.roster
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Write data to the stream
|
|
195
|
+
#
|
|
196
|
+
# @param [#to_xml, #to_s] stanza the data to send down the wire.
|
|
197
|
+
def write_to_stream(stanza)
|
|
198
|
+
client.write stanza
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Helper method to make sending basic messages easier
|
|
202
|
+
#
|
|
203
|
+
# @param [Blather::JID, #to_s] to the JID of the message recipient
|
|
204
|
+
# @param [#to_s] msg the message to send
|
|
205
|
+
def say(to, msg)
|
|
206
|
+
client.write Blather::Stanza::Message.new(to, msg)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# The JID according to the server
|
|
210
|
+
#
|
|
211
|
+
# @return [Blather::JID]
|
|
212
|
+
def jid
|
|
213
|
+
client.jid
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Halt the handler chain
|
|
217
|
+
#
|
|
218
|
+
# Use this to stop the propogation of the stanza though the handler chain.
|
|
219
|
+
#
|
|
220
|
+
# @example
|
|
221
|
+
# Ignore all IQ stanzas
|
|
222
|
+
#
|
|
223
|
+
# before(:iq) { halt }
|
|
224
|
+
def halt
|
|
225
|
+
throw :halt
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Pass responsibility to the next handler
|
|
229
|
+
#
|
|
230
|
+
# Use this to jump out of the current handler and let the next registered
|
|
231
|
+
# handler take care of the stanza
|
|
232
|
+
#
|
|
233
|
+
# @example
|
|
234
|
+
# This is contrive and should be handled with guards, but pass a message
|
|
235
|
+
# to the next handler based on the content
|
|
236
|
+
#
|
|
237
|
+
# message { |s| puts "message caught" }
|
|
238
|
+
# message { |s| pass if s.body =~ /pass along/ }
|
|
239
|
+
def pass
|
|
240
|
+
throw :pass
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Request items or info from an entity
|
|
244
|
+
# discover (items|info), [jid], [node] do |response|
|
|
245
|
+
# end
|
|
246
|
+
def discover(what, who, where, &callback)
|
|
247
|
+
stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
|
|
248
|
+
stanza.to = who
|
|
249
|
+
stanza.node = where
|
|
250
|
+
|
|
251
|
+
client.register_tmp_handler stanza.id, &callback
|
|
252
|
+
client.write stanza
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Generate a method for every stanza handler that exists.
|
|
256
|
+
Blather::Stanza.handler_list.each do |handler_name|
|
|
257
|
+
module_eval <<-METHOD, __FILE__, __LINE__
|
|
258
|
+
def #{handler_name}(*args, &callback)
|
|
259
|
+
handle :#{handler_name}, *args, &callback
|
|
260
|
+
end
|
|
261
|
+
METHOD
|
|
262
|
+
end
|
|
263
|
+
end # DSL
|
|
264
|
+
end # Blather
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require File.join(File.dirname(__FILE__), *%w[client dsl])
|
|
3
|
+
|
|
4
|
+
include Blather::DSL
|
|
5
|
+
|
|
6
|
+
options = {}
|
|
7
|
+
optparse = OptionParser.new do |opts|
|
|
8
|
+
opts.banner = "Run with #{$0} [options] user@server/resource password [host] [port]"
|
|
9
|
+
|
|
10
|
+
opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do
|
|
11
|
+
options[:debug] = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
opts.on('-d', '--daemonize', 'Daemonize the process') do |daemonize|
|
|
15
|
+
options[:daemonize] = daemonize
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
opts.on('--pid=[PID]', 'Write the PID to this file') do |pid|
|
|
19
|
+
if !File.writable?(File.dirname(pid))
|
|
20
|
+
$stderr.puts "Unable to write log file to #{pid}"
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
options[:pid] = pid
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
opts.on('--log=[LOG]', 'Write to the [LOG] file instead of stdout/stderr') do |log|
|
|
27
|
+
if !File.writable?(File.dirname(log))
|
|
28
|
+
$stderr.puts "Unable to write log file to #{log}"
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
options[:log] = log
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
|
35
|
+
puts opts
|
|
36
|
+
exit
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on_tail('-v', '--version', 'Show version') do
|
|
40
|
+
require 'yaml'
|
|
41
|
+
version = YAML.load_file File.join(File.dirname(__FILE__), %w[.. .. VERSION.yml])
|
|
42
|
+
puts "Blather v#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
|
|
43
|
+
exit
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
optparse.parse!
|
|
47
|
+
|
|
48
|
+
at_exit do
|
|
49
|
+
unless client.setup?
|
|
50
|
+
if ARGV.length < 2
|
|
51
|
+
puts optparse
|
|
52
|
+
exit 1
|
|
53
|
+
end
|
|
54
|
+
client.setup(*ARGV)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run(options)
|
|
58
|
+
$stdin.reopen "/dev/null"
|
|
59
|
+
|
|
60
|
+
if options[:log]
|
|
61
|
+
log = File.new(options[:log], 'a')
|
|
62
|
+
log.sync = options[:debug]
|
|
63
|
+
$stdout.reopen log
|
|
64
|
+
$stderr.reopen $stdout
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Blather.logger.level = Logger::DEBUG if options[:debug]
|
|
68
|
+
|
|
69
|
+
trap(:INT) { EM.stop }
|
|
70
|
+
trap(:TERM) { EM.stop }
|
|
71
|
+
EM.run { client.run }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if options[:daemonize]
|
|
75
|
+
pid = fork do
|
|
76
|
+
Process.setsid
|
|
77
|
+
exit if fork
|
|
78
|
+
File.open(options[:pid], 'w') { |f| f << Process.pid } if options[:pid]
|
|
79
|
+
run options
|
|
80
|
+
FileUtils.rm(options[:pid]) if options[:pid]
|
|
81
|
+
end
|
|
82
|
+
::Process.detach pid
|
|
83
|
+
exit
|
|
84
|
+
else
|
|
85
|
+
run options
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Nokogiri
|
|
2
|
+
module XML
|
|
3
|
+
|
|
4
|
+
class Node
|
|
5
|
+
# Alias #name to #element_name so we can use #name in an XMPP Stanza context
|
|
6
|
+
alias_method :element_name, :name
|
|
7
|
+
alias_method :element_name=, :name=
|
|
8
|
+
|
|
9
|
+
alias_method :attr_set, :[]= # :nodoc:
|
|
10
|
+
# Override Nokogiri's attribute setter to add the ability to kill an attribute
|
|
11
|
+
# by setting it to nil and to be able to lookup an attribute by symbol
|
|
12
|
+
#
|
|
13
|
+
# @param [#to_s] name the name of the attribute
|
|
14
|
+
# @param [#to_s, nil] value the new value or nil to remove it
|
|
15
|
+
def []=(name, value)
|
|
16
|
+
name = name.to_s
|
|
17
|
+
value.nil? ? remove_attribute(name) : attr_set(name, value.to_s)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
alias_method :nokogiri_xpath, :xpath
|
|
21
|
+
# Override Nokogiri's #xpath method to add the ability to use symbols for lookup
|
|
22
|
+
# and namespace designation
|
|
23
|
+
def xpath(*paths)
|
|
24
|
+
paths[0] = paths[0].to_s
|
|
25
|
+
if paths.size > 1 && (namespaces = paths.pop).is_a?(Hash)
|
|
26
|
+
paths << namespaces.inject({}) { |h,v| h[v[0].to_s] = v[1]; h }
|
|
27
|
+
end
|
|
28
|
+
nokogiri_xpath *paths
|
|
29
|
+
end
|
|
30
|
+
alias_method :find, :xpath
|
|
31
|
+
|
|
32
|
+
# Return the first element at a specified xpath
|
|
33
|
+
# @see #xpath
|
|
34
|
+
def find_first(*paths)
|
|
35
|
+
xpath(*paths).first
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end #XML
|
|
40
|
+
end #Blather
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
|
|
3
|
+
# General SASL Errors
|
|
4
|
+
# Check #name for the error name
|
|
5
|
+
#
|
|
6
|
+
# @handler :sasl_error
|
|
7
|
+
class SASLError < BlatherError
|
|
8
|
+
SASL_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
9
|
+
|
|
10
|
+
class_inheritable_accessor :err_name
|
|
11
|
+
# @private
|
|
12
|
+
@@registrations = {}
|
|
13
|
+
|
|
14
|
+
register :sasl_error
|
|
15
|
+
|
|
16
|
+
# Import the stanza
|
|
17
|
+
#
|
|
18
|
+
# @param [Blather::XMPPNode] node the error node
|
|
19
|
+
# @return [Blather::SASLError]
|
|
20
|
+
def self.import(node)
|
|
21
|
+
self.new node
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Create a new SASLError
|
|
25
|
+
#
|
|
26
|
+
# @param [Blather::XMPPNode] node the error node
|
|
27
|
+
def initialize(node)
|
|
28
|
+
super()
|
|
29
|
+
@node = node
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The actual error name
|
|
33
|
+
#
|
|
34
|
+
# @return [Symbol] a symbol representing the error name
|
|
35
|
+
def name
|
|
36
|
+
if @node
|
|
37
|
+
name = @node.find_first('ns:*', :ns => SASL_ERR_NS).element_name
|
|
38
|
+
name.gsub('-', '_').to_sym
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end # SASLError
|
|
42
|
+
|
|
43
|
+
end # Blather
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
|
|
3
|
+
# Stanza errors
|
|
4
|
+
# RFC3920 Section 9.3 (http://xmpp.org/rfcs/rfc3920.html#stanzas-error)
|
|
5
|
+
#
|
|
6
|
+
# @handler :stanza_error
|
|
7
|
+
class StanzaError < BlatherError
|
|
8
|
+
STANZA_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
|
9
|
+
VALID_TYPES = [:cancel, :continue, :modify, :auth, :wait].freeze
|
|
10
|
+
|
|
11
|
+
register :stanza_error
|
|
12
|
+
|
|
13
|
+
attr_reader :original, :name, :type, :text, :extras
|
|
14
|
+
|
|
15
|
+
# Factory method for instantiating the proper class for the error
|
|
16
|
+
#
|
|
17
|
+
# @param [Blather::XMPPNode] node the error node to import
|
|
18
|
+
# @return [Blather::StanzaError]
|
|
19
|
+
def self.import(node)
|
|
20
|
+
original = node.copy
|
|
21
|
+
original.remove_child 'error'
|
|
22
|
+
|
|
23
|
+
error_node = node.find_first '//*[local-name()="error"]'
|
|
24
|
+
|
|
25
|
+
name = error_node.find_first('child::*[name()!="text"]', STANZA_ERR_NS).element_name
|
|
26
|
+
type = error_node['type']
|
|
27
|
+
text = node.find_first 'descendant::*[name()="text"]', STANZA_ERR_NS
|
|
28
|
+
text = text.content if text
|
|
29
|
+
|
|
30
|
+
extras = error_node.find("descendant::*[name()!='text' and name()!='#{name}']").map { |n| n }
|
|
31
|
+
|
|
32
|
+
self.new original, name, type, text, extras
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Create a new StanzaError
|
|
36
|
+
#
|
|
37
|
+
# @param [Blather::XMPPNode] original the original stanza
|
|
38
|
+
# @param [String] name the error name
|
|
39
|
+
# @param [#to_s] type the error type as specified in
|
|
40
|
+
# [RFC3920](http://xmpp.org/rfcs/rfc3920.html#rfc.section.9.3.2)
|
|
41
|
+
# @param [String, nil] text additional text for the error
|
|
42
|
+
# @param [Array<Blather::XMPPNode>] extras an array of extra nodes to add
|
|
43
|
+
def initialize(original, name, type, text = nil, extras = [])
|
|
44
|
+
@original = original
|
|
45
|
+
@name = name
|
|
46
|
+
self.type = type
|
|
47
|
+
@text = text
|
|
48
|
+
@extras = extras
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Set the error type
|
|
52
|
+
#
|
|
53
|
+
# @param [#to_sym] type the new error type. Must be on of
|
|
54
|
+
# Blather::StanzaError::VALID_TYPES
|
|
55
|
+
# @see [RFC3920 Section 9.3.2](http://xmpp.org/rfcs/rfc3920.html#rfc.section.9.3.2)
|
|
56
|
+
def type=(type)
|
|
57
|
+
type = type.to_sym
|
|
58
|
+
if !VALID_TYPES.include?(type)
|
|
59
|
+
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
|
|
60
|
+
end
|
|
61
|
+
@type = type
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The error name
|
|
65
|
+
#
|
|
66
|
+
# @return [Symbol]
|
|
67
|
+
def name
|
|
68
|
+
@name.gsub('-','_').to_sym
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Creates an XML node from the error
|
|
72
|
+
#
|
|
73
|
+
# @return [Blather::XMPPNode]
|
|
74
|
+
def to_node
|
|
75
|
+
node = self.original.reply
|
|
76
|
+
node.type = 'error'
|
|
77
|
+
node << (error_node = XMPPNode.new('error'))
|
|
78
|
+
|
|
79
|
+
error_node << (err = XMPPNode.new(@name, error_node.document))
|
|
80
|
+
err.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
|
81
|
+
|
|
82
|
+
if self.text
|
|
83
|
+
error_node << (text = XMPPNode.new('text', error_node.document))
|
|
84
|
+
text.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
|
85
|
+
text.content = self.text
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
self.extras.each { |extra| error_node << extra.dup }
|
|
89
|
+
node
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Convert the object to a proper node then convert it to a string
|
|
93
|
+
#
|
|
94
|
+
# @return [String]
|
|
95
|
+
def to_xml
|
|
96
|
+
to_node.to_s
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @private
|
|
100
|
+
def inspect
|
|
101
|
+
"Stanza Error (#{@name}): #{self.text} [#{self.extras}]"
|
|
102
|
+
end
|
|
103
|
+
# @private
|
|
104
|
+
alias_method :to_s, :inspect
|
|
105
|
+
end # StanzaError
|
|
106
|
+
|
|
107
|
+
end # Blather
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
|
|
3
|
+
# Stream Errors
|
|
4
|
+
# [RFC3920 Section 9.3](http://xmpp.org/rfcs/rfc3920.html#streams-error-rules)
|
|
5
|
+
#
|
|
6
|
+
# @handler :stream_error
|
|
7
|
+
class StreamError < BlatherError
|
|
8
|
+
STREAM_ERR_NS = 'urn:ietf:params:xml:ns:xmpp-streams'
|
|
9
|
+
|
|
10
|
+
register :stream_error
|
|
11
|
+
|
|
12
|
+
attr_reader :text, :extras
|
|
13
|
+
|
|
14
|
+
# Factory method for instantiating the proper class for the error
|
|
15
|
+
#
|
|
16
|
+
# @param [Blather::XMPPNode] node the importable node
|
|
17
|
+
def self.import(node)
|
|
18
|
+
name = node.find_first('descendant::*[name()!="text"]', STREAM_ERR_NS).element_name
|
|
19
|
+
|
|
20
|
+
text = node.find_first 'descendant::*[name()="text"]', STREAM_ERR_NS
|
|
21
|
+
text = text.content if text
|
|
22
|
+
|
|
23
|
+
extras = node.find("descendant::*[namespace-uri()!='#{STREAM_ERR_NS}']").map { |n| n }
|
|
24
|
+
|
|
25
|
+
self.new name, text, extras
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Create a new Stream Error
|
|
29
|
+
# [RFC3920 Section 4.7.2](http://xmpp.org/rfcs/rfc3920.html#rfc.section.4.7.2)
|
|
30
|
+
#
|
|
31
|
+
# @param [String] name the error name
|
|
32
|
+
# @param [String, nil] text optional error text
|
|
33
|
+
# @param [Array<Blather::XMPPNode>] extras an array of extras to attach to the
|
|
34
|
+
# error
|
|
35
|
+
def initialize(name, text = nil, extras = [])
|
|
36
|
+
@name = name
|
|
37
|
+
@text = text
|
|
38
|
+
@extras = extras
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The error name
|
|
42
|
+
#
|
|
43
|
+
# @return [Symbol]
|
|
44
|
+
def name
|
|
45
|
+
@name.gsub('-','_').to_sym
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Creates an XML node from the error
|
|
49
|
+
#
|
|
50
|
+
# @return [Blather::XMPPNode]
|
|
51
|
+
def to_node
|
|
52
|
+
node = XMPPNode.new('stream:error')
|
|
53
|
+
|
|
54
|
+
node << (err = XMPPNode.new(@name, node.document))
|
|
55
|
+
err.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
|
|
56
|
+
|
|
57
|
+
if self.text
|
|
58
|
+
node << (text = XMPPNode.new('text', node.document))
|
|
59
|
+
text.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
|
|
60
|
+
text.content = self.text
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
self.extras.each { |extra| node << extra.dup }
|
|
64
|
+
node
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Convert the object to a proper node then convert it to a string
|
|
68
|
+
#
|
|
69
|
+
# @return [String]
|
|
70
|
+
def to_xml
|
|
71
|
+
to_node.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @private
|
|
75
|
+
def inspect
|
|
76
|
+
"Stream Error (#{@name}): #{self.text}" + (self.extras.empty? ? '' : " [#{self.extras}]")
|
|
77
|
+
end
|
|
78
|
+
# @private
|
|
79
|
+
alias_method :to_s, :inspect
|
|
80
|
+
end # StreamError
|
|
81
|
+
|
|
82
|
+
end # Blather
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
# Main error class
|
|
3
|
+
# This starts the error hierarchy
|
|
4
|
+
#
|
|
5
|
+
# @handler :error
|
|
6
|
+
class BlatherError < StandardError
|
|
7
|
+
class_inheritable_array :handler_hierarchy
|
|
8
|
+
self.handler_hierarchy ||= []
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
@@handler_list = []
|
|
12
|
+
|
|
13
|
+
# Register the class's handler
|
|
14
|
+
#
|
|
15
|
+
# @param [Symbol] handler the handler name
|
|
16
|
+
def self.register(handler)
|
|
17
|
+
@@handler_list << handler
|
|
18
|
+
self.handler_hierarchy.unshift handler
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The list of registered handlers
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<Symbol>] a list of currently registered handlers
|
|
24
|
+
def self.handler_list
|
|
25
|
+
@@handler_list
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
register :error
|
|
29
|
+
|
|
30
|
+
# @private
|
|
31
|
+
# HACK!! until I can refactor the entire Error object model
|
|
32
|
+
def id
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end # BlatherError
|
|
36
|
+
|
|
37
|
+
# Used in cases where a stanza only allows specific values for its attributes
|
|
38
|
+
# and an invalid value is attempted.
|
|
39
|
+
#
|
|
40
|
+
# @handler :argument_error
|
|
41
|
+
class ArgumentError < BlatherError
|
|
42
|
+
register :argument_error
|
|
43
|
+
end # ArgumentError
|
|
44
|
+
|
|
45
|
+
# The stream handler received a response it didn't know how to handle
|
|
46
|
+
#
|
|
47
|
+
# @handler :unknown_response_error
|
|
48
|
+
class UnknownResponse < BlatherError
|
|
49
|
+
register :unknown_response_error
|
|
50
|
+
attr_reader :node
|
|
51
|
+
|
|
52
|
+
def initialize(node)
|
|
53
|
+
@node = node
|
|
54
|
+
end
|
|
55
|
+
end # UnknownResponse
|
|
56
|
+
|
|
57
|
+
# Something bad happened while parsing the incoming stream
|
|
58
|
+
#
|
|
59
|
+
# @handler :parse_error
|
|
60
|
+
class ParseError < BlatherError
|
|
61
|
+
register :parse_error
|
|
62
|
+
attr_reader :message
|
|
63
|
+
|
|
64
|
+
def initialize(msg)
|
|
65
|
+
@message = msg.to_s
|
|
66
|
+
end
|
|
67
|
+
end # ParseError
|
|
68
|
+
|
|
69
|
+
end # Blather
|