blather 0.4.14 → 0.4.15

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/lib/blather.rb +8 -0
  2. data/lib/blather/client/client.rb +9 -1
  3. data/lib/blather/client/dsl/pubsub.rb +2 -2
  4. data/lib/blather/core_ext/active_support.rb +1 -0
  5. data/lib/blather/core_ext/eventmachine.rb +122 -0
  6. data/lib/blather/errors/stanza_error.rb +1 -0
  7. data/lib/blather/file_transfer.rb +100 -0
  8. data/lib/blather/file_transfer/ibb.rb +68 -0
  9. data/lib/blather/file_transfer/s5b.rb +104 -0
  10. data/lib/blather/roster_item.rb +2 -2
  11. data/lib/blather/stanza/disco/disco_info.rb +17 -2
  12. data/lib/blather/stanza/iq/ibb.rb +83 -0
  13. data/lib/blather/stanza/iq/s5b.rb +205 -0
  14. data/lib/blather/stanza/iq/si.rb +410 -0
  15. data/lib/blather/stanza/iq/vcard.rb +147 -0
  16. data/lib/blather/stanza/message.rb +10 -2
  17. data/lib/blather/stanza/presence/status.rb +11 -3
  18. data/lib/blather/stanza/pubsub.rb +3 -1
  19. data/lib/blather/stanza/pubsub/event.rb +18 -0
  20. data/lib/blather/stanza/pubsub/subscriptions.rb +4 -2
  21. data/lib/blather/stanza/pubsub/unsubscribe.rb +17 -1
  22. data/lib/blather/stanza/x.rb +12 -2
  23. data/lib/blather/stream/parser.rb +1 -0
  24. data/lib/test.rb +55 -0
  25. data/spec/blather/client/client_spec.rb +18 -4
  26. data/spec/blather/client/dsl/pubsub_spec.rb +12 -2
  27. data/spec/blather/client/dsl_spec.rb +1 -1
  28. data/spec/blather/core_ext/nokogiri_spec.rb +1 -1
  29. data/spec/blather/errors/sasl_error_spec.rb +1 -1
  30. data/spec/blather/errors/stanza_error_spec.rb +1 -1
  31. data/spec/blather/errors/stream_error_spec.rb +1 -1
  32. data/spec/blather/errors_spec.rb +1 -1
  33. data/spec/blather/file_transfer_spec.rb +100 -0
  34. data/spec/blather/jid_spec.rb +1 -1
  35. data/spec/blather/roster_item_spec.rb +32 -2
  36. data/spec/blather/roster_spec.rb +1 -1
  37. data/spec/blather/stanza/discos/disco_info_spec.rb +12 -3
  38. data/spec/blather/stanza/discos/disco_items_spec.rb +1 -1
  39. data/spec/blather/stanza/iq/command_spec.rb +1 -1
  40. data/spec/blather/stanza/iq/ibb_spec.rb +136 -0
  41. data/spec/blather/stanza/iq/query_spec.rb +1 -1
  42. data/spec/blather/stanza/iq/roster_spec.rb +1 -1
  43. data/spec/blather/stanza/iq/s5b_spec.rb +60 -0
  44. data/spec/blather/stanza/iq/si_spec.rb +101 -0
  45. data/spec/blather/stanza/iq/vcard_spec.rb +96 -0
  46. data/spec/blather/stanza/iq_spec.rb +1 -1
  47. data/spec/blather/stanza/message_spec.rb +48 -2
  48. data/spec/blather/stanza/presence/status_spec.rb +1 -1
  49. data/spec/blather/stanza/presence/subscription_spec.rb +1 -1
  50. data/spec/blather/stanza/presence_spec.rb +1 -1
  51. data/spec/blather/stanza/pubsub/affiliations_spec.rb +2 -2
  52. data/spec/blather/stanza/pubsub/create_spec.rb +2 -2
  53. data/spec/blather/stanza/pubsub/event_spec.rb +16 -2
  54. data/spec/blather/stanza/pubsub/items_spec.rb +2 -2
  55. data/spec/blather/stanza/pubsub/publish_spec.rb +2 -2
  56. data/spec/blather/stanza/pubsub/retract_spec.rb +2 -2
  57. data/spec/blather/stanza/pubsub/subscribe_spec.rb +2 -2
  58. data/spec/blather/stanza/pubsub/subscription_spec.rb +2 -2
  59. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +3 -3
  60. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +15 -2
  61. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +2 -2
  62. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +2 -2
  63. data/spec/blather/stanza/pubsub_owner_spec.rb +2 -2
  64. data/spec/blather/stanza/pubsub_spec.rb +14 -2
  65. data/spec/blather/stanza/x_spec.rb +1 -1
  66. data/spec/blather/stanza_spec.rb +1 -1
  67. data/spec/blather/stream/client_spec.rb +1 -1
  68. data/spec/blather/stream/component_spec.rb +1 -1
  69. data/spec/blather/stream/parser_spec.rb +11 -1
  70. data/spec/blather/xmpp_node_spec.rb +1 -1
  71. data/spec/fixtures/pubsub.rb +2 -2
  72. data/spec/spec_helper.rb +27 -0
  73. metadata +85 -23
@@ -0,0 +1,147 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ # # Vcard Stanza
6
+ #
7
+ # [XEP-0054 vcard-temp](http://xmpp.org/extensions/xep-0054.html)
8
+ #
9
+ # This is a base class for any vcard based Iq stanzas. It provides a base set
10
+ # of methods for working with vcard stanzas
11
+ #
12
+ # @example Retrieving One's vCard
13
+ # iq = Blather::Stanza::Iq::Vcard.new :get
14
+ # client.write_with_handler iq do |response|
15
+ # puts response.vcard
16
+ # end
17
+ #
18
+ # @example Updating One's vCard
19
+ # iq = Blather::Stanza::Iq::Vcard.new :set
20
+ # iq.vcard['NICKNAME'] = 'Romeo'
21
+ # client.write_with_handler iq do |response|
22
+ # puts response
23
+ # end
24
+ #
25
+ # @example Viewing Another User's vCard
26
+ # iq = Blather::Stanza::Iq::Vcard.new :get, 'mercutio@example.org'
27
+ # client.write_with_handler iq do |response|
28
+ # puts response.vcard
29
+ # end
30
+ #
31
+ # @handler :vcard
32
+ class Vcard < Iq
33
+
34
+ register :vcard, :vCard, 'vcard-temp'
35
+
36
+ # Overrides the parent method to ensure a vcard node is created
37
+ #
38
+ # @see Blather::Stanza::Iq.new
39
+ def self.new(type = nil, to = nil, id = nil)
40
+ node = super
41
+ node.vcard
42
+ node
43
+ end
44
+
45
+ # Overrides the parent method to ensure the current vcard node is destroyed
46
+ #
47
+ # @see Blather::Stanza::Iq#inherit
48
+ def inherit(node)
49
+ vcard.remove
50
+ super
51
+ self
52
+ end
53
+
54
+ # Find or create vcard node
55
+ #
56
+ # @return [Vcard::Vcard]
57
+ def vcard
58
+ Vcard.find_or_create self
59
+ end
60
+
61
+ # Replaces vcard node
62
+ #
63
+ # @param [Vcard::Vcard, XML::Node] info the stanza's new vcard node
64
+ #
65
+ # @return [Vcard::Vcard]
66
+ def vcard=(info)
67
+ vcard.remove
68
+ self << info
69
+ Vcard.find_or_create self
70
+ end
71
+
72
+ class Vcard < XMPPNode
73
+
74
+ VCARD_NS = 'vcard-temp'
75
+
76
+ # Create a new Vcard::Vcard object
77
+ #
78
+ # @param [XML::Node, nil] node a node to inherit from
79
+ #
80
+ # @return [Vcard::Vcard]
81
+ def self.new(node = nil)
82
+ new_node = super :vCard
83
+ new_node.namespace = VCARD_NS
84
+ new_node.inherit node if node
85
+ new_node
86
+ end
87
+
88
+ # Find or create vCard node in Vcard Iq and converts it to Vcard::Vcard
89
+ #
90
+ # @param [Vcard] parent a Vcard Iq where to find or create vCard
91
+ #
92
+ # @return [Vcard::Vcard]
93
+ def self.find_or_create(parent)
94
+ if found_vcard = parent.find_first('//ns:vCard', :ns => VCARD_NS)
95
+ vcard = self.new found_vcard
96
+ found_vcard.remove
97
+ else
98
+ vcard = self.new
99
+ end
100
+ parent << vcard
101
+
102
+ vcard
103
+ end
104
+
105
+ # Find the element's value by name
106
+ #
107
+ # @param [String] name the name of the element
108
+ #
109
+ # @return [String, nil]
110
+ def [](name)
111
+ name = name.split("/").map{|child| "ns:#{child}"}.join("/")
112
+
113
+ if elem = find_first(name, :ns => VCARD_NS)
114
+ elem.content
115
+ else
116
+ nil
117
+ end
118
+ end
119
+
120
+ # Set the element's value
121
+ #
122
+ # @param [String] name the name of the element
123
+ # @param [String, nil] value the new value of element
124
+ #
125
+ # @return [String, nil]
126
+ def []=(name, value)
127
+ elem = nil
128
+ parent = self
129
+
130
+ name.split("/").each do |child|
131
+ elem = parent.find_first("ns:#{child}", :ns => VCARD_NS)
132
+ unless elem
133
+ elem = XMPPNode.new(child, parent.document)
134
+ parent << elem
135
+ parent = elem
136
+ else
137
+ parent = elem
138
+ end
139
+ end
140
+
141
+ elem.content = value
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -175,7 +175,7 @@ class Stanza
175
175
  klass = class_from_registration(e.element_name, ns)
176
176
  end
177
177
 
178
- if klass && klass != self && klass != Blather::Stanza::X
178
+ if klass && klass != self && ![Blather::Stanza::X, Blather::Stanza::Iq].include?(klass)
179
179
  klass.import(node)
180
180
  else
181
181
  new(node[:type]).inherit(node)
@@ -196,6 +196,14 @@ class Stanza
196
196
  node
197
197
  end
198
198
 
199
+ # Overrides the parent method to ensure the current chat state is removed
200
+ #
201
+ # @see Blather::Stanza::Iq#inherit
202
+ def inherit(node)
203
+ xpath('ns:*', :ns => CHAT_STATE_NS).remove
204
+ super
205
+ end
206
+
199
207
  # Check if the Message is of type :chat
200
208
  #
201
209
  # @return [true, false]
@@ -260,7 +268,7 @@ class Stanza
260
268
  #
261
269
  # @return [XML::Node]
262
270
  def xhtml_node
263
- unless h = find_first('ns:html', :ns => HTML_NS)
271
+ unless h = find_first('ns:html', :ns => HTML_NS) || find_first('ns:html', :ns => HTML_BODY_NS)
264
272
  self << (h = XMPPNode.new('html', self.document))
265
273
  h.namespace = HTML_NS
266
274
  end
@@ -193,8 +193,9 @@ class Presence
193
193
  set_content_for :status, message
194
194
  end
195
195
 
196
- # Compare status based on priority
197
- # raises an error if the JIDs aren't the same
196
+ # Compare status based on priority and state:
197
+ # unavailable status is always less valuable than others
198
+ # Raises an error if the JIDs aren't the same
198
199
  #
199
200
  # @param [Blather::Stanza::Presence::Status] o
200
201
  # @return [true,false]
@@ -202,7 +203,14 @@ class Presence
202
203
  unless self.from && o.from && self.from.stripped == o.from.stripped
203
204
  raise ArgumentError, "Cannot compare status from different JIDs: #{[self.from, o.from].inspect}"
204
205
  end
205
- self.priority <=> o.priority
206
+
207
+ if (self.type.nil? && o.type.nil?) || (!self.type.nil? && !o.type.nil?)
208
+ self.priority <=> o.priority
209
+ elsif self.type.nil? && !o.type.nil?
210
+ 1
211
+ elsif !self.type.nil? && o.type.nil?
212
+ -1
213
+ end
206
214
  end
207
215
 
208
216
  end #Status
@@ -92,9 +92,11 @@ class Stanza
92
92
  write_attr :id, id
93
93
  end
94
94
 
95
+ alias_method :payload_node, :child
96
+
95
97
  # Get the item's payload
96
98
  #
97
- # @return [String, XMPPNode, nil]
99
+ # @return [String, nil]
98
100
  def payload
99
101
  children.empty? ? nil : children.to_s
100
102
  end
@@ -116,6 +116,24 @@ class PubSub
116
116
  n.content
117
117
  end
118
118
  end
119
+
120
+ # Check if this is a subscription stanza
121
+ #
122
+ # @return [XML::Node, nil]
123
+ def subscription?
124
+ subscription_node
125
+ end
126
+
127
+ # Get the actual subscription node
128
+ #
129
+ # @return [Blather::XMPPNode]
130
+ def subscription_node
131
+ event_node.find_first('//ns:subscription', :ns => self.class.registered_ns)
132
+ end
133
+
134
+ def subscription
135
+ subscription_node
136
+ end
119
137
  end # Event
120
138
 
121
139
  end # PubSub
@@ -58,7 +58,7 @@ class PubSub
58
58
  # Get a hash of subscriptions
59
59
  #
60
60
  # @example
61
- # { :subscribed => [{:node => 'node1', :jid => 'francisco@denmark.lit'}, {:node => 'node2', :jid => 'francisco@denmark.lit'}],
61
+ # { :subscribed => [{:node => 'node1', :jid => 'francisco@denmark.lit', :subid => 'fd8237yr872h3f289j2'}, {:node => 'node2', :jid => 'francisco@denmark.lit', :subid => 'h8394hf8923ju'}],
62
62
  # :unconfigured => [{:node => 'node3', :jid => 'francisco@denmark.lit'}],
63
63
  # :pending => [{:node => 'node4', :jid => 'francisco@denmark.lit'}],
64
64
  # :none => [{:node => 'node5', :jid => 'francisco@denmark.lit'}] }
@@ -67,10 +67,12 @@ class PubSub
67
67
  def list
68
68
  subscriptions.find('//ns:subscription', :ns => self.class.registered_ns).inject({}) do |hash, item|
69
69
  hash[item[:subscription].to_sym] ||= []
70
- hash[item[:subscription].to_sym] << {
70
+ sub = {
71
71
  :node => item[:node],
72
72
  :jid => item[:jid]
73
73
  }
74
+ sub[:subid] = item[:subid] if item[:subid]
75
+ hash[item[:subscription].to_sym] << sub
74
76
  hash
75
77
  end
76
78
  end
@@ -16,10 +16,12 @@ class PubSub
16
16
  # @param [String] host the host to send the request to
17
17
  # @param [String] node the node to unsubscribe from
18
18
  # @param [Blather::JID, #to_s] jid the JID of the unsubscription
19
- def self.new(type = :set, host = nil, node = nil, jid = nil)
19
+ # @param [String] subid the subscription ID of the unsubscription
20
+ def self.new(type = :set, host = nil, node = nil, jid = nil, subid = nil)
20
21
  new_node = super(type, host)
21
22
  new_node.node = node
22
23
  new_node.jid = jid
24
+ new_node.subid = subid
23
25
  new_node
24
26
  end
25
27
 
@@ -51,6 +53,20 @@ class PubSub
51
53
  unsubscribe[:node] = node
52
54
  end
53
55
 
56
+ # Get the subscription ID to unsubscribe from
57
+ #
58
+ # @return [String]
59
+ def subid
60
+ unsubscribe[:subid]
61
+ end
62
+
63
+ # Set the subscription ID to unsubscribe from
64
+ #
65
+ # @param [String] node
66
+ def subid=(subid)
67
+ unsubscribe[:subid] = subid
68
+ end
69
+
54
70
  # Get or create the actual unsubscribe node
55
71
  #
56
72
  # @return [Blather::XMPPNode]
@@ -150,6 +150,7 @@ class Stanza
150
150
  end
151
151
 
152
152
  class Field < XMPPNode
153
+ register :field, 'jabber:x:data'
153
154
  VALID_TYPES = [:boolean, :fixed, :hidden, :"jid-multi", :"jid-single", :"list-multi", :"list-single", :"text-multi", :"text-private", :"text-single"].freeze
154
155
 
155
156
  # Create a new X Field
@@ -292,7 +293,11 @@ class Stanza
292
293
  #
293
294
  # @param [true, false]
294
295
  def required?
295
- !self.find_first('required').nil?
296
+ if self.namespace
297
+ !self.find_first('ns:required', :ns => self.namespace.href).nil?
298
+ else
299
+ !self.find_first('required').nil?
300
+ end
296
301
  end
297
302
 
298
303
  # Set the field's required flag
@@ -307,7 +312,11 @@ class Stanza
307
312
  #
308
313
  # @return [Blather::Stanza::X::Field::Option]
309
314
  def options
310
- self.find(:option).map { |f| Option.new(f) }
315
+ if self.namespace
316
+ self.find('ns:option', :ns => self.namespace.href)
317
+ else
318
+ self.find('option')
319
+ end.map { |f| Option.new(f) }
311
320
  end
312
321
 
313
322
  # Add an array of options to field
@@ -329,6 +338,7 @@ class Stanza
329
338
  alias_method :==, :eql?
330
339
 
331
340
  class Option < XMPPNode
341
+ register :option, 'jabber:x:data'
332
342
  # Create a new X Field Option
333
343
  # @overload new(node)
334
344
  # Imports the XML::Node to create a Field option object
@@ -16,6 +16,7 @@ class Stream # :nodoc:
16
16
  @namespaces = {}
17
17
  @namespace_definitions = []
18
18
  @parser = Nokogiri::XML::SAX::PushParser.new self
19
+ @parser.options = Nokogiri::XML::ParseOptions::DEFAULT_XML | Nokogiri::XML::ParseOptions::NOENT
19
20
  end
20
21
 
21
22
  def receive_data(string)
data/lib/test.rb ADDED
@@ -0,0 +1,55 @@
1
+ require "blather/client/client"
2
+ require "openssl"
3
+ require "em-http"
4
+ require "cgi"
5
+
6
+ module S3FileReceiver
7
+ def initialize(iq)
8
+ p "initialize"
9
+ @filename = iq.si.file["name"]
10
+ @size = iq.si.file["size"].to_i
11
+ end
12
+
13
+ def post_init
14
+ p "post_init"
15
+ time = Time.now.httpdate
16
+ sign = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), "qHG5ywFiZR2EK7oUvLVLhGHr2Rp05ggriTdExw0I", "PUT\n\n\n#{time}\n/jaconda-dev/#{CGI::escape(@filename)}")).strip
17
+ @s3 = EM::HttpRequest.new("https://jaconda-dev.s3.amazonaws.com:443/#{CGI::escape(@filename)}").put({:timeout => 10, :head => {'date' => time, 'url' => "/jaconda-dev/#{CGI::escape(@filename)}", 'Content-Length' => @size, "Authorization" => "AWS AKIAIVULGEHUOR7PIR4Q:#{sign}"}})
18
+ @s3.errback do |e|
19
+ p "error: #{e.response_header.inspect}"
20
+ end
21
+ @s3.callback do |s|
22
+ p "success: #{s.response.inspect}"
23
+ end
24
+ EM::enable_proxy(self, @s3)
25
+ end
26
+
27
+ def proxy_target_unbound
28
+ close_connection_after_writing
29
+ end
30
+ end
31
+
32
+ EM.run do
33
+ Blather.logger = Logger.new("/Users/antonmironov/Desktop/blather.log")
34
+ Blather.logger.level = Logger::DEBUG
35
+
36
+ im = Blather::Client.setup "ant.mironov@jabber.ru/test", "zzzxxx"
37
+
38
+ im.register_handler :disco_info, :type => :get do |iq|
39
+ answer = iq.reply
40
+ answer.identities = [{:type => 'pc', :category => 'client'}]
41
+ answer.features = ["http://jabber.org/protocol/si/profile/file-transfer", "http://jabber.org/protocol/si", "http://jabber.org/protocol/bytestreams", "http://jabber.org/protocol/ibb"]
42
+
43
+ im.write answer
44
+ end
45
+
46
+ im.register_handler :file_transfer do |iq|
47
+ transfer = Blather::FileTransfer.new(im, iq)
48
+ # transfer.allow_bytestreams = false
49
+
50
+ transfer.accept(S3FileReceiver, iq)
51
+
52
+ true
53
+ end
54
+ im.connect
55
+ end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
1
+ require File.expand_path "../../../spec_helper", __FILE__
2
2
  require 'blather/client/client'
3
3
 
4
4
  describe Blather::Client do
@@ -81,7 +81,7 @@ describe Blather::Client do
81
81
  disconnected = mock()
82
82
  disconnected.expects(:call)
83
83
  @client.register_handler(:disconnected) { disconnected.call }
84
- @client.unbind
84
+ @client.unbind
85
85
  end
86
86
 
87
87
  it 'does not call EM.stop on #unbind if a handler returns positive' do
@@ -175,6 +175,20 @@ describe Blather::Client do
175
175
  @client.register_handler(:iq) { |_| response.iq }
176
176
  @client.receive_data stanza
177
177
  end
178
+
179
+ it 'can clear handlers' do
180
+ stanza = Blather::Stanza::Message.new
181
+ stanza.expects(:chat?).returns true
182
+
183
+ response = mock
184
+ response.expects(:call).once
185
+
186
+ @client.register_handler(:message, :chat?) { |_| response.call }
187
+ @client.receive_data stanza
188
+
189
+ @client.clear_handlers(:message, :chat?)
190
+ @client.receive_data stanza
191
+ end
178
192
  end
179
193
 
180
194
  describe 'Blather::Client#write' do
@@ -309,7 +323,7 @@ describe 'Blather::Client with a Component stream' do
309
323
  class MockComponent < Blather::Stream::Component; def initialize(); end; end
310
324
  @stream = MockComponent.new('')
311
325
  @stream.stubs(:send_data)
312
- @client = Blather::Client.new
326
+ @client = Blather::Client.new
313
327
  @client.setup('me.com', 'secret')
314
328
  end
315
329
 
@@ -325,7 +339,7 @@ describe 'Blather::Client with a Client stream' do
325
339
  before do
326
340
  class MockClientStream < Blather::Stream::Client; def initialize(); end; end
327
341
  @stream = MockClientStream.new('')
328
- @client = Blather::Client.new
342
+ @client = Blather::Client.new
329
343
  Blather::Stream::Client.stubs(:start).returns @stream
330
344
  @client.setup('me@me.com', 'secret').run
331
345
  end