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.
Files changed (73) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +110 -0
  4. data/examples/drb_client.rb +5 -0
  5. data/examples/echo.rb +18 -0
  6. data/ext/extconf.rb +65 -0
  7. data/ext/push_parser.c +231 -0
  8. data/lib/blather/client.rb +219 -44
  9. data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
  10. data/lib/blather/core_ext/libxml.rb +28 -0
  11. data/lib/blather/errors/sasl_error.rb +87 -0
  12. data/lib/blather/errors/stanza_error.rb +262 -0
  13. data/lib/blather/errors/stream_error.rb +253 -0
  14. data/lib/blather/errors.rb +48 -0
  15. data/lib/blather/{core/jid.rb → jid.rb} +15 -26
  16. data/lib/blather/{core/roster.rb → roster.rb} +22 -0
  17. data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
  18. data/lib/blather/stanza/iq/disco.rb +11 -0
  19. data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
  20. data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
  21. data/lib/blather/stanza/iq/query.rb +51 -0
  22. data/lib/blather/stanza/iq/roster.rb +90 -0
  23. data/lib/blather/stanza/iq.rb +38 -0
  24. data/lib/blather/stanza/message.rb +58 -0
  25. data/lib/blather/stanza/presence/status.rb +78 -0
  26. data/lib/blather/stanza/presence/subscription.rb +72 -0
  27. data/lib/blather/stanza/presence.rb +45 -0
  28. data/lib/blather/stanza.rb +101 -0
  29. data/lib/blather/stream/client.rb +26 -0
  30. data/lib/blather/stream/component.rb +34 -0
  31. data/lib/blather/stream/parser.rb +70 -0
  32. data/lib/blather/stream/resource.rb +48 -0
  33. data/lib/blather/stream/sasl.rb +173 -0
  34. data/lib/blather/stream/session.rb +36 -0
  35. data/lib/blather/stream/stream_handler.rb +39 -0
  36. data/lib/blather/stream/tls.rb +33 -0
  37. data/lib/blather/stream.rb +249 -0
  38. data/lib/blather/xmpp_node.rb +199 -0
  39. data/lib/blather.rb +40 -41
  40. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  41. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  42. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  43. data/spec/blather/errors/stream_error_spec.rb +114 -0
  44. data/spec/blather/errors_spec.rb +40 -0
  45. data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
  46. data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
  47. data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
  48. data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
  49. data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
  50. data/spec/blather/stanza/iq/query_spec.rb +34 -0
  51. data/spec/blather/stanza/iq/roster_spec.rb +123 -0
  52. data/spec/blather/stanza/iq_spec.rb +40 -0
  53. data/spec/blather/stanza/message_spec.rb +52 -0
  54. data/spec/blather/stanza/presence/status_spec.rb +102 -0
  55. data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
  56. data/spec/blather/stanza/presence_spec.rb +53 -0
  57. data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
  58. data/spec/blather/stream/client_spec.rb +787 -0
  59. data/spec/blather/stream/component_spec.rb +86 -0
  60. data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
  61. data/spec/build_safe.rb +20 -0
  62. data/spec/spec_helper.rb +7 -17
  63. metadata +79 -59
  64. data/CHANGELOG +0 -1
  65. data/blather.gemspec +0 -73
  66. data/lib/blather/callback.rb +0 -24
  67. data/lib/blather/core/errors.rb +0 -24
  68. data/lib/blather/core/stanza.rb +0 -90
  69. data/lib/blather/core/stream.rb +0 -179
  70. data/lib/blather/core/xmpp_node.rb +0 -95
  71. data/lib/blather/extensions/last_activity.rb +0 -57
  72. data/lib/blather/extensions/version.rb +0 -85
  73. data/spec/blather/core/stream_spec.rb +0 -263
@@ -1,81 +1,256 @@
1
- require File.join(File.dirname(__FILE__), %w[.. blather])
1
+ require File.join(File.dirname(__FILE__), *%w[.. blather])
2
2
 
3
- module Blather
4
-
5
- class Client
6
- @@callbacks = {}
7
- @@status = nil
3
+ module Blather #:nodoc:
8
4
 
5
+ class Client #:nodoc:
9
6
  attr_accessor :jid,
10
7
  :roster
11
8
 
12
- def send_data(data)
13
- @stream.send data
9
+ def initialize
10
+ @state = :initializing
11
+
12
+ @status = Stanza::Presence::Status.new
13
+ @handlers = {}
14
+ @tmp_handlers = {}
15
+ @roster = Roster.new self
16
+
17
+ setup_initial_handlers
18
+ end
19
+
20
+ def setup(jid, password, host = nil, port = 5222)
21
+ @setup = [JID.new(jid), password, host, port]
22
+ self
23
+ end
24
+
25
+ def setup?
26
+ @setup.is_a?(Array) && !@setup.empty?
27
+ end
28
+
29
+ def run
30
+ raise 'Not setup!' unless @setup.is_a?(Array)
31
+ trap(:INT) { EM.stop }
32
+ EM.run {
33
+ klass = @setup[2].node ? Blather::Stream::Client : Blather::Stream::Component
34
+ klass.start Blather.client, *@setup
35
+ }
36
+
37
+ def temporary_handler(id, &handler)
38
+ @tmp_handlers[id] = handler
39
+ end
40
+
41
+ def register_handler(type, *guards, &handler)
42
+ @handlers[type] ||= []
43
+ @handlers[type] << [guards, handler]
14
44
  end
15
45
 
16
46
  def status
17
- @@status
47
+ @status.state
18
48
  end
19
49
 
20
- def set_status(state = nil, msg = nil, to = nil)
21
- status = Presence::Status.new state, msg
50
+ def status=(state)
51
+ state, msg, to = state
52
+
53
+ status = Stanza::Presence::Status.new state, msg
22
54
  status.to = to
23
- @@status = status unless to
55
+ @statustatus unless to
56
+
57
+ write status
58
+ end
24
59
 
25
- send_data status
60
+ def write(stanza)
61
+ stanza.from ||= jid if stanza.respond_to?(:from)
62
+ @stream.send(stanza) if @stream
26
63
  end
27
64
 
28
65
  def stream_started(stream)
29
66
  @stream = stream
30
- retreive_roster
67
+
68
+ #retreive roster
69
+ if @stream.is_a?(Stream::Component)
70
+ @state = :ready
71
+ call_handler_for :ready, nil
72
+ else
73
+ write Stanza::Iq::Roster.new
74
+ end
31
75
  end
32
76
 
33
- def call(stanza)
34
- stanza.callback_heirarchy.each { |type| break if callback(type, stanza) }
77
+ def stop
78
+ @stream.close_connection_after_writing
35
79
  end
36
80
 
37
- # Default response to an Iq 'get' or 'set' is 'service-unavailable'/'cancel'
38
- def receive_iq(iq)
39
- send_data(ErrorStanza.new_from(iq, 'service-unavailable', 'cancel').reply!) if [:set, :get].include?(iq.type)
81
+ def stopped
82
+ EM.stop
40
83
  end
41
84
 
42
- def receive_roster(node)
43
- if !@roster && node.type == :result
44
- self.roster = Roster.new(@stream, node)
45
- register_callback(:status, -128) { |_, status| roster[status.from].status = status if roster[status.from]; false }
46
- set_status
47
- elsif node.type == :set
48
- roster.process node
85
+ def call(stanza)
86
+ if handler = @tmp_handlers.delete(stanza.id)
87
+ handler.call stanza
88
+ else
89
+ stanza.handler_heirarchy.each do |type|
90
+ break if call_handler_for(type, stanza) && (stanza.is_a?(BlatherError) || stanza.type == :iq)
91
+ end
49
92
  end
50
93
  end
51
94
 
52
- def self.register_callback(type, priority = 0, &callback)
53
- @@callbacks[type] ||= []
54
- @@callbacks[type] << Callback.new(priority, &callback)
55
- @@callbacks[type].sort!
95
+ def call_handler_for(type, stanza)
96
+ if @handlers[type]
97
+ @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
98
+ true
99
+ end
56
100
  end
57
101
 
58
- def register_callback(type, priority = 0, &callback)
59
- self.class.register_callback(type, priority = 0, &callback)
60
- end
102
+ protected
103
+ def setup_initial_handlers
104
+ register_handler :error do |err|
105
+ raise err
106
+ end
61
107
 
62
- def self.status(state = nil, msg = nil)
63
- @@status = Presence::Status.new state, msg
64
- end
108
+ register_handler :iq do |iq|
109
+ write(StanzaError::ServiceUnavailable.new(iq, :cancel).to_node) if [:set, :get].include?(iq.type)
110
+ end
65
111
 
66
- def callback(type, stanza)
67
- complete = false
68
- (@@callbacks[type] || []).each { |callback| break if complete = callback.call(self, stanza) }
112
+ register_handler :status do |status|
113
+ roster[status.from].status = status if roster[status.from]
114
+ end
69
115
 
70
- method = "receive_#{type}"
71
- complete = __send__(method, stanza) if !complete && respond_to?(method)
72
- complete
116
+ register_handler :roster do |node|
117
+ roster.process node
118
+ if @state == :initializing
119
+ @state = :ready
120
+ write @status
121
+ call_handler_for :ready, nil
122
+ end
123
+ end
73
124
  end
74
125
 
75
- def retreive_roster
76
- send_data Iq::Roster.new
126
+ ##
127
+ # If any of the guards returns FALSE this returns true
128
+ def guarded?(guards, stanza)
129
+ guards.find do |guard|
130
+ case guard
131
+ when Symbol
132
+ !stanza.__send__(guard)
133
+ when Array
134
+ # return FALSE if any item is TRUE
135
+ !guard.detect { |condition| !guarded?([condition], stanza) }
136
+ when Hash
137
+ # return FALSE unless any inequality is found
138
+ guard.find do |method, value|
139
+ if value.is_a?(Regexp)
140
+ !stanza.__send__(method).to_s.match(value)
141
+ else
142
+ stanza.__send__(method) != value
143
+ end
144
+ end
145
+ when Proc
146
+ !guard.call(stanza)
147
+ else
148
+ raise "Bad guard: #{guard.inspect}"
149
+ end
150
+ end
77
151
  end
78
152
 
79
153
  end #Client
80
154
 
155
+ def client
156
+ @client ||= Client.new
157
+ end
158
+ module_function :client
159
+ end #Blather
160
+
161
+ ##
162
+ # Prepare server settings
163
+ # setup_client [node@domain/resource], [password], [host], [port]
164
+ # host and port are optional defaulting to the domain in the JID and 5222 respectively
165
+ def setup_client(jid, password, host = nil, port = 5222)
166
+ at_exit { Blather.client.setup_client(jid, password, host, port).run }
167
+ end
168
+
169
+ def setup_component(jid, secret, host, port)
170
+ at_exit { Blather.client.setup_component(jid, secret, host, port).run }
171
+ end
172
+
173
+ ##
174
+ # Shutdown the connection.
175
+ # Flushes the write buffer then stops EventMachine
176
+ def shutdown
177
+ Blather.client.stop
178
+ end
179
+
180
+ ##
181
+ # Set handler for a stanza type
182
+ def handle(stanza_type, *guards, &block)
183
+ Blather.client.register_handler stanza_type, *guards, &block
184
+ end
185
+
186
+ ##
187
+ # Wrapper for "handle :ready" (just a bit of syntactic sugar)
188
+ def when_ready(&block)
189
+ handle :ready, &block
190
+ end
191
+
192
+ ##
193
+ # Set current status
194
+ def status(state = nil, msg = nil)
195
+ Blather.client.status = state, msg
196
+ end
197
+
198
+ ##
199
+ # Direct access to the roster
200
+ def roster
201
+ Blather.client.roster
202
+ end
203
+
204
+ ##
205
+ # Write data to the stream
206
+ # Anything that resonds to #to_s can be paseed to the stream
207
+ def write(stanza)
208
+ Blather.client.write(stanza)
209
+ end
210
+
211
+ ##
212
+ # Helper method to make sending basic messages easier
213
+ # say [jid], [msg]
214
+ def say(to, msg)
215
+ Blather.client.write Blather::Stanza::Message.new(to, msg)
216
+ end
217
+
218
+ ##
219
+ # Wrapper to grab the current JID
220
+ def jid
221
+ Blather.client.jid
222
+ end
223
+
224
+ ##
225
+ #
226
+ def discover(what, who, where, &callback)
227
+ stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
228
+ stanza.to = who
229
+ stanza.node = where
230
+
231
+ Blather.client.temporary_handler stanza.id, &callback
232
+ write stanza
233
+ end
234
+
235
+ ##
236
+ # Checks to see if the method is part of the handlers list.
237
+ # If so it creates a handler, otherwise it'll pass it back
238
+ # to Ruby's method_missing handler
239
+ def method_missing(method, *args, &block)
240
+ if Blather::Stanza.handler_list.include?(method)
241
+ handle method, *args, &block
242
+ else
243
+ super
244
+ end
245
+ end
246
+
247
+
248
+ at_exit do
249
+ unless Blather.client.setup?
250
+ if ARGV.length < 2
251
+ puts "Run with #{$0} user@server/resource password [host] [port]"
252
+ else
253
+ Blather.client.setup(*ARGV).run
254
+ end
255
+ end
81
256
  end
@@ -1,16 +1,9 @@
1
- module LibXML
2
- module XML
3
-
4
- class Attributes
5
- def remove(name)
6
- name = name.to_s
7
- self.each { |a| a.remove! or break if a.name == name }
8
- end
9
- end #Attributes
10
-
11
- end #XML
12
- end #LibXML
1
+ # Thanks to Rails ActiveSupport for everything in this file
13
2
 
3
+ # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
4
+ # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
5
+ # to, for example, an array without those additions being shared with either their parent, siblings, or
6
+ # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
14
7
  class Class # :nodoc:
15
8
  def class_inheritable_reader(*syms)
16
9
  syms.each do |sym|
@@ -123,20 +116,38 @@ class Class # :nodoc:
123
116
  alias inherited inherited_with_inheritable_attributes
124
117
  end #Class
125
118
 
126
- class Object
119
+ class Object # :nodoc:
127
120
  def duplicable?; true; end
121
+ def blank?; respond_to?(:empty?) ? empty? : !self; end
122
+ def present?; !blank?; end
123
+ end
124
+
125
+ class Array #:nodoc:
126
+ alias_method :blank?, :empty?
127
+ def extract_options!; last.is_a?(::Hash) ? pop : {}; end
128
+ end
129
+
130
+ class Hash #:nodoc:
131
+ alias_method :blank?, :empty?
132
+ end
133
+
134
+ class String #:nodoc:
135
+ def blank?; self !~ /\S/; end
128
136
  end
129
137
 
130
138
  class NilClass #:nodoc:
131
139
  def duplicable?; false; end
140
+ def blank?; true; end
132
141
  end
133
142
 
134
143
  class FalseClass #:nodoc:
135
144
  def duplicable?; false; end
145
+ def blank?; true; end
136
146
  end
137
147
 
138
148
  class TrueClass #:nodoc:
139
149
  def duplicable?; false; end
150
+ def blank?; false; end
140
151
  end
141
152
 
142
153
  class Symbol #:nodoc:
@@ -145,4 +156,5 @@ end
145
156
 
146
157
  class Numeric #:nodoc:
147
158
  def duplicable?; false; end
159
+ def blank?; false; end
148
160
  end
@@ -0,0 +1,28 @@
1
+ module LibXML # :nodoc:
2
+ module XML # :nodoc:
3
+
4
+ class Node
5
+ alias_method :element_name, :name
6
+ alias_method :element_name=, :name=
7
+ end
8
+
9
+ class Attributes
10
+ # Helper method for removing attributes
11
+ def remove(name)
12
+ attribute = get_attribute(name.to_s)
13
+ attribute.remove! if attribute
14
+ end
15
+
16
+ alias_method :old_hash_set, :[]= # :nodoc:
17
+ def []=(name, val)
18
+ val.nil? ? remove(name.to_s) : old_hash_set(name.to_s, val.to_s)
19
+ end
20
+
21
+ alias_method :old_hash_get, :[] # :nodoc:
22
+ def [](name)
23
+ old_hash_get name.to_s
24
+ end
25
+ end #Attributes
26
+
27
+ end #XML
28
+ end #LibXML
@@ -0,0 +1,87 @@
1
+ module Blather
2
+
3
+ class SASLError < BlatherError
4
+ class_inheritable_accessor :err_name
5
+ @@registrations = {}
6
+
7
+ register :sasl_error
8
+
9
+ ##
10
+ # Register the handler and type to simplify importing
11
+ def self.register(handler, err_name)
12
+ super handler
13
+ self.err_name = err_name
14
+ @@registrations[err_name] = self
15
+ end
16
+
17
+ ##
18
+ # Retreive an error class from a given name
19
+ def self.class_from_registration(err_name)
20
+ @@registrations[err_name.to_s] || self
21
+ end
22
+
23
+ ##
24
+ # Factory to create the proper error object from an error node
25
+ def self.import(node)
26
+ err_name = node.children.first.element_name
27
+ class_from_registration(err_name).new
28
+ end
29
+
30
+ ##
31
+ # XMPP defined error name
32
+ def err_name
33
+ self.class.err_name
34
+ end
35
+
36
+ ##
37
+ # The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.
38
+ class Aborted < SASLError
39
+ register :sasl_aborted_error, 'aborted'
40
+ end
41
+
42
+ ##
43
+ # The data provided by the initiating entity could not be processed because the [BASE64] encoding is incorrect
44
+ # (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]); sent in reply to a <response/>
45
+ # element or an <auth/> element with initial response data.
46
+ class IncorrectEncoding < SASLError
47
+ register :sasl_incorrect_encoding_error, 'incorrect-encoding'
48
+ end
49
+
50
+ ##
51
+ # The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the
52
+ # initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/>
53
+ # element with initial response data.
54
+ class InvalidAuthzid < SASLError
55
+ register :sasl_invalid_authzid_error, 'invalid-authzid'
56
+ end
57
+
58
+ ##
59
+ # The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity;
60
+ # sent in reply to an <auth/> element.
61
+ class InvalidMechanism < SASLError
62
+ register :sasl_invalid_mechanism_error, 'invalid-mechanism'
63
+ end
64
+
65
+ ##
66
+ # The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in
67
+ # reply to a <response/> element or an <auth/> element with initial response data.
68
+ class MechanismTooWeak < SASLError
69
+ register :sasl_mechanism_too_weak_error, 'mechanism-too-weak'
70
+ end
71
+
72
+ ##
73
+ # The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited
74
+ # to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data.
75
+ class NotAuthorized < SASLError
76
+ register :sasl_not_authorized_error, 'not-authorized'
77
+ end
78
+
79
+ ##
80
+ # The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/>
81
+ # element or <response/> element.
82
+ class TemporaryAuthFailure < SASLError
83
+ register :sasl_temporary_auth_failure_error, 'temporary-auth-failure'
84
+ end
85
+ end #SASLError
86
+
87
+ end #Blather