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.
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