shingara-blather 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +162 -0
  3. data/examples/echo.rb +18 -0
  4. data/examples/execute.rb +16 -0
  5. data/examples/ping_pong.rb +37 -0
  6. data/examples/print_hierarchy.rb +76 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/stream_only.rb +27 -0
  9. data/examples/xmpp4r/echo.rb +35 -0
  10. data/lib/blather/client/client.rb +310 -0
  11. data/lib/blather/client/dsl/pubsub.rb +170 -0
  12. data/lib/blather/client/dsl.rb +264 -0
  13. data/lib/blather/client.rb +87 -0
  14. data/lib/blather/core_ext/nokogiri.rb +40 -0
  15. data/lib/blather/errors/sasl_error.rb +43 -0
  16. data/lib/blather/errors/stanza_error.rb +107 -0
  17. data/lib/blather/errors/stream_error.rb +82 -0
  18. data/lib/blather/errors.rb +69 -0
  19. data/lib/blather/jid.rb +142 -0
  20. data/lib/blather/roster.rb +111 -0
  21. data/lib/blather/roster_item.rb +122 -0
  22. data/lib/blather/stanza/disco/disco_info.rb +176 -0
  23. data/lib/blather/stanza/disco/disco_items.rb +132 -0
  24. data/lib/blather/stanza/disco.rb +25 -0
  25. data/lib/blather/stanza/iq/query.rb +53 -0
  26. data/lib/blather/stanza/iq/roster.rb +179 -0
  27. data/lib/blather/stanza/iq.rb +138 -0
  28. data/lib/blather/stanza/message.rb +332 -0
  29. data/lib/blather/stanza/presence/status.rb +212 -0
  30. data/lib/blather/stanza/presence/subscription.rb +101 -0
  31. data/lib/blather/stanza/presence.rb +163 -0
  32. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  33. data/lib/blather/stanza/pubsub/create.rb +65 -0
  34. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  35. data/lib/blather/stanza/pubsub/event.rb +123 -0
  36. data/lib/blather/stanza/pubsub/items.rb +103 -0
  37. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  38. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  39. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  40. data/lib/blather/stanza/pubsub/subscription.rb +134 -0
  41. data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
  42. data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
  43. data/lib/blather/stanza/pubsub.rb +129 -0
  44. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  45. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  46. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  47. data/lib/blather/stanza.rb +149 -0
  48. data/lib/blather/stream/client.rb +31 -0
  49. data/lib/blather/stream/component.rb +38 -0
  50. data/lib/blather/stream/features/resource.rb +63 -0
  51. data/lib/blather/stream/features/sasl.rb +187 -0
  52. data/lib/blather/stream/features/session.rb +44 -0
  53. data/lib/blather/stream/features/tls.rb +28 -0
  54. data/lib/blather/stream/features.rb +53 -0
  55. data/lib/blather/stream/parser.rb +102 -0
  56. data/lib/blather/stream.rb +231 -0
  57. data/lib/blather/xmpp_node.rb +218 -0
  58. data/lib/blather.rb +78 -0
  59. data/spec/blather/client/client_spec.rb +559 -0
  60. data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
  61. data/spec/blather/client/dsl_spec.rb +143 -0
  62. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  63. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  64. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  65. data/spec/blather/errors/stream_error_spec.rb +108 -0
  66. data/spec/blather/errors_spec.rb +33 -0
  67. data/spec/blather/jid_spec.rb +87 -0
  68. data/spec/blather/roster_item_spec.rb +96 -0
  69. data/spec/blather/roster_spec.rb +103 -0
  70. data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
  71. data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
  72. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  73. data/spec/blather/stanza/iq/roster_spec.rb +140 -0
  74. data/spec/blather/stanza/iq_spec.rb +45 -0
  75. data/spec/blather/stanza/message_spec.rb +132 -0
  76. data/spec/blather/stanza/presence/status_spec.rb +132 -0
  77. data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
  78. data/spec/blather/stanza/presence_spec.rb +66 -0
  79. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  80. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  81. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  82. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  83. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  84. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  85. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  86. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  87. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  88. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  89. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  90. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  92. data/spec/blather/stanza/pubsub_spec.rb +67 -0
  93. data/spec/blather/stanza_spec.rb +116 -0
  94. data/spec/blather/stream/client_spec.rb +1011 -0
  95. data/spec/blather/stream/component_spec.rb +95 -0
  96. data/spec/blather/stream/parser_spec.rb +145 -0
  97. data/spec/blather/xmpp_node_spec.rb +231 -0
  98. data/spec/fixtures/pubsub.rb +311 -0
  99. data/spec/spec_helper.rb +43 -0
  100. 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