blather 0.4.15 → 0.4.16

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 (75) hide show
  1. data/.autotest +13 -0
  2. data/.gitignore +18 -0
  3. data/CHANGELOG +150 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +32 -0
  6. data/TODO.md +2 -0
  7. data/blather.gemspec +49 -0
  8. data/lib/blather.rb +3 -0
  9. data/lib/blather/client/client.rb +139 -1
  10. data/lib/blather/client/dsl.rb +16 -0
  11. data/lib/blather/core_ext/active_support.rb +1 -1
  12. data/lib/blather/core_ext/ipaddr.rb +19 -0
  13. data/lib/blather/file_transfer/s5b.rb +8 -0
  14. data/lib/blather/roster_item.rb +5 -2
  15. data/lib/blather/stanza/disco.rb +7 -0
  16. data/lib/blather/stanza/disco/disco_info.rb +13 -19
  17. data/lib/blather/stanza/disco/disco_items.rb +5 -11
  18. data/lib/blather/stanza/presence.rb +15 -5
  19. data/lib/blather/stanza/presence/c.rb +103 -0
  20. data/lib/blather/stanza/x.rb +2 -4
  21. data/lib/blather/version.rb +3 -0
  22. data/lib/blather/xmpp_node.rb +8 -0
  23. data/spec/blather/client/client_spec.rb +87 -1
  24. data/spec/blather/client/dsl/pubsub_spec.rb +2 -2
  25. data/spec/blather/client/dsl_spec.rb +85 -5
  26. data/spec/blather/core_ext/nokogiri_spec.rb +1 -1
  27. data/spec/blather/errors/sasl_error_spec.rb +1 -1
  28. data/spec/blather/errors/stanza_error_spec.rb +2 -2
  29. data/spec/blather/errors/stream_error_spec.rb +2 -2
  30. data/spec/blather/errors_spec.rb +1 -1
  31. data/spec/blather/file_transfer_spec.rb +2 -2
  32. data/spec/blather/jid_spec.rb +1 -1
  33. data/spec/blather/roster_item_spec.rb +1 -1
  34. data/spec/blather/roster_spec.rb +1 -1
  35. data/spec/blather/stanza/discos/disco_info_spec.rb +1 -11
  36. data/spec/blather/stanza/discos/disco_items_spec.rb +1 -6
  37. data/spec/blather/stanza/iq/command_spec.rb +3 -3
  38. data/spec/blather/stanza/iq/ibb_spec.rb +2 -2
  39. data/spec/blather/stanza/iq/query_spec.rb +1 -1
  40. data/spec/blather/stanza/iq/roster_spec.rb +1 -1
  41. data/spec/blather/stanza/iq/s5b_spec.rb +2 -2
  42. data/spec/blather/stanza/iq/si_spec.rb +2 -2
  43. data/spec/blather/stanza/iq/vcard_spec.rb +3 -3
  44. data/spec/blather/stanza/iq_spec.rb +1 -1
  45. data/spec/blather/stanza/message_spec.rb +2 -2
  46. data/spec/blather/stanza/presence/c_spec.rb +46 -0
  47. data/spec/blather/stanza/presence/status_spec.rb +1 -1
  48. data/spec/blather/stanza/presence/subscription_spec.rb +1 -1
  49. data/spec/blather/stanza/presence_spec.rb +1 -1
  50. data/spec/blather/stanza/pubsub/affiliations_spec.rb +2 -2
  51. data/spec/blather/stanza/pubsub/create_spec.rb +3 -3
  52. data/spec/blather/stanza/pubsub/event_spec.rb +2 -2
  53. data/spec/blather/stanza/pubsub/items_spec.rb +2 -2
  54. data/spec/blather/stanza/pubsub/publish_spec.rb +2 -2
  55. data/spec/blather/stanza/pubsub/retract_spec.rb +2 -2
  56. data/spec/blather/stanza/pubsub/subscribe_spec.rb +2 -2
  57. data/spec/blather/stanza/pubsub/subscription_spec.rb +3 -3
  58. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +2 -2
  59. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +2 -2
  60. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +2 -2
  61. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +3 -3
  62. data/spec/blather/stanza/pubsub_owner_spec.rb +2 -2
  63. data/spec/blather/stanza/pubsub_spec.rb +2 -8
  64. data/spec/blather/stanza/x_spec.rb +1 -6
  65. data/spec/blather/stanza_spec.rb +1 -1
  66. data/spec/blather/stream/client_spec.rb +3 -3
  67. data/spec/blather/stream/component_spec.rb +1 -1
  68. data/spec/blather/stream/parser_spec.rb +1 -1
  69. data/spec/blather/xmpp_node_spec.rb +1 -1
  70. data/spec/spec_helper.rb +5 -12
  71. data/yard/templates/default/class/html/handlers.erb +18 -0
  72. data/yard/templates/default/class/setup.rb +10 -0
  73. data/yard/templates/default/class/text/handlers.erb +1 -0
  74. metadata +123 -13
  75. data/lib/test.rb +0 -55
@@ -252,6 +252,22 @@ module Blather
252
252
  client.write stanza
253
253
  end
254
254
 
255
+ def set_caps(node, identities, features)
256
+ client.caps.node = node
257
+ client.caps.identities = identities
258
+ client.caps.features = features
259
+ end
260
+
261
+ def send_caps
262
+ client.register_handler :disco_info, :type => :get, :node => client.caps.node do |s|
263
+ r = client.caps.dup
264
+ r.to = s.from
265
+ r.id = s.id
266
+ client.write r
267
+ end
268
+ client.write client.caps.c
269
+ end
270
+
255
271
  # Generate a method for every stanza handler that exists.
256
272
  Blather::Stanza.handler_list.each do |handler_name|
257
273
  module_eval <<-METHOD, __FILE__, __LINE__
@@ -36,7 +36,7 @@ end
36
36
 
37
37
  class Symbol # @private
38
38
  def duplicable?; false; end
39
- def to_proc; proc { |obj, *args| obj.send(self, *args) }; end
39
+ def to_proc; Proc.new { |*args| args.shift.__send__(self, *args) }; end
40
40
  end
41
41
 
42
42
  class Numeric # @private
@@ -0,0 +1,19 @@
1
+ class IPAddr
2
+ PrivateRanges = [
3
+ IPAddr.new("10.0.0.0/8"),
4
+ IPAddr.new("172.16.0.0/12"),
5
+ IPAddr.new("192.168.0.0/16")
6
+ ]
7
+
8
+ def private?
9
+ return false unless self.ipv4?
10
+ PrivateRanges.each do |ipr|
11
+ return true if ipr.include?(self)
12
+ end
13
+ return false
14
+ end
15
+
16
+ def public?
17
+ !private?
18
+ end
19
+ end
@@ -7,10 +7,17 @@ module Blather
7
7
  # Set this to false if you don't want to fallback to In-Band Bytestreams
8
8
  attr_accessor :allow_ibb_fallback
9
9
 
10
+ # Set this to true if the buddies of your bot will be in the same local network
11
+ #
12
+ # Usually IM clients advertise all network addresses which they can determine.
13
+ # Skipping the local ones can save time if your bot is not in the same local network as it's buddies
14
+ attr_accessor :allow_private_ips
15
+
10
16
  def initialize(stream, iq)
11
17
  @stream = stream
12
18
  @iq = iq
13
19
  @allow_ibb_fallback = true
20
+ @allow_private_ips = false
14
21
  end
15
22
 
16
23
  # Accept an incoming file-transfer
@@ -19,6 +26,7 @@ module Blather
19
26
  # @param [Array] params the params to be passed into the handler
20
27
  def accept(handler, *params)
21
28
  @streamhosts = @iq.streamhosts
29
+ @streamhosts.delete_if {|s| begin IPAddr.new(s.host).private? rescue false end } unless @allow_private_ips
22
30
  @socket_address = Digest::SHA1.hexdigest("#{@iq.sid}#{@iq.from}#{@iq.to}")
23
31
 
24
32
  @handler = handler
@@ -122,8 +122,11 @@ module Blather
122
122
  self.jid.to_s <=> o.jid.to_s
123
123
  end
124
124
 
125
- def eql?(o)
126
- o.is_a?(RosterItem) &&
125
+ # Compare two RosterItem objects by name, type and category
126
+ # @param [RosterItem] o the Identity object to compare against
127
+ # @return [true, false]
128
+ def eql?(o, *fields)
129
+ o.is_a?(self.class) &&
127
130
  o.jid == self.jid &&
128
131
  o.groups == self.groups
129
132
  end
@@ -19,6 +19,13 @@ class Stanza
19
19
  def node=(node)
20
20
  query[:node] = node
21
21
  end
22
+
23
+ # Compare two Disco objects by name, type and category
24
+ # @param [Disco] o the Identity object to compare against
25
+ # @return [true, false]
26
+ def eql?(o, *fields)
27
+ super o, *(fields + [:node])
28
+ end
22
29
  end
23
30
 
24
31
  end # Stanza
@@ -59,6 +59,13 @@ class Stanza
59
59
  end
60
60
  end
61
61
 
62
+ # Compare two DiscoInfo objects by name, type and category
63
+ # @param [DiscoInfo] o the Identity object to compare against
64
+ # @return [true, false]
65
+ def eql?(o, *fields)
66
+ super o, *(fields + [:identities, :features])
67
+ end
68
+
62
69
  class Identity < XMPPNode
63
70
  # Create a new DiscoInfo Identity
64
71
  # @overload new(node)
@@ -146,17 +153,9 @@ class Stanza
146
153
  # Compare two Identity objects by name, type and category
147
154
  # @param [DiscoInfo::Identity] o the Identity object to compare against
148
155
  # @return [true, false]
149
- def eql?(o)
150
- unless o.is_a?(self.class)
151
- raise "Cannot compare #{self.class} with #{o.class}"
152
- end
153
-
154
- o.name == self.name &&
155
- o.type == self.type &&
156
- o.category == self.category &&
157
- o.xml_lang == self.xml_lang
156
+ def eql?(o, *fields)
157
+ super o, *(fields + [:name, :type, :category, :xml_lang])
158
158
  end
159
- alias_method :==, :eql?
160
159
  end # Identity
161
160
 
162
161
  class Feature < XMPPNode
@@ -191,17 +190,12 @@ class Stanza
191
190
  write_attr :var, var
192
191
  end
193
192
 
194
- # Compare two Feature objects by var
195
- # @param [DiscoInfo::Feature] o the Feature object to compare against
193
+ # Compare two DiscoInfo::Feature objects by name, type and category
194
+ # @param [DiscoInfo::Feature] o the Identity object to compare against
196
195
  # @return [true, false]
197
- def eql?(o)
198
- unless o.is_a?(self.class)
199
- raise "Cannot compare #{self.class} with #{o.class}"
200
- end
201
-
202
- o.var == self.var
196
+ def eql?(o, *fields)
197
+ super o, *(fields + [:var])
203
198
  end
204
- alias_method :==, :eql?
205
199
  end
206
200
  end # Feature
207
201
 
@@ -121,19 +121,13 @@ class Stanza
121
121
  write_attr :name, name
122
122
  end
123
123
 
124
- # Check for equality based on jid, node, and name
125
- #
126
- # @param [Blather::Stanza::DiscoItems::Item] o the other Item
127
- def eql?(o)
128
- unless o.is_a?(self.class)
129
- raise "Cannot compare #{self.class} with #{o.class}"
130
- end
131
124
 
132
- o.jid == self.jid &&
133
- o.node == self.node &&
134
- o.name == self.name
125
+ # Compare two DiscoItems::Item objects by name, type and category
126
+ # @param [DiscoItems::Item] o the Identity object to compare against
127
+ # @return [true, false]
128
+ def eql?(o, *fields)
129
+ super o, *(fields + [:jid, :node, :name])
135
130
  end
136
- alias_method :==, :eql?
137
131
  end
138
132
  end
139
133
 
@@ -85,12 +85,22 @@ class Stanza
85
85
  # on the type attribute.
86
86
  # If neither is found it instantiates a Presence object
87
87
  def self.import(node) # :nodoc:
88
- klass = case node['type']
89
- when nil, 'unavailable' then Status
90
- when /subscribe/ then Subscription
91
- else self
88
+ klass = nil
89
+ node.children.detect do |e|
90
+ ns = e.namespace ? e.namespace.href : nil
91
+ klass = class_from_registration(e.element_name, ns)
92
+ end
93
+
94
+ if klass && klass != self
95
+ klass.import(node)
96
+ else
97
+ klass = case node['type']
98
+ when nil, 'unavailable' then Status
99
+ when /subscribe/ then Subscription
100
+ else self
101
+ end
102
+ klass.new.inherit(node)
92
103
  end
93
- klass.new.inherit(node)
94
104
  end
95
105
 
96
106
  # Ensure element_name is "presence" for all subclasses
@@ -0,0 +1,103 @@
1
+ module Blather
2
+ class Stanza
3
+ class Presence
4
+
5
+ # # Entity Capabilities Stanza
6
+ #
7
+ # [XEP-0115 - Entity Capabilities](http://http://xmpp.org/extensions/xep-0115.html)
8
+ #
9
+ # Blather handles c nodes through this class. It provides a set of helper methods
10
+ # to quickly deal with capabilites presence stanzas.
11
+ #
12
+ # @handler :c
13
+ class C < Presence
14
+ register :c, :c, 'http://jabber.org/protocol/caps'
15
+
16
+ VALID_HASH_TYPES = %w[md2 md5 sha-1 sha-224 sha-256 sha-384 sha-512].freeze
17
+
18
+ def self.new(node = nil, ver = nil, hash = 'sha-1')
19
+ new_node = super()
20
+ new_node.c
21
+ new_node.hash = hash
22
+ new_node.node = node
23
+ new_node.ver = ver
24
+ new_node
25
+ end
26
+
27
+ def self.import(node)
28
+ self.new.inherit(node)
29
+ end
30
+
31
+ # @private
32
+ def inherit(node)
33
+ inherit_attrs node.attributes
34
+ self
35
+ end
36
+
37
+ # Get the name of the node
38
+ #
39
+ # @return [String, nil]
40
+ def node
41
+ c[:node]
42
+ end
43
+
44
+ # Set the name of the node
45
+ #
46
+ # @param [String, nil] node the new node name
47
+ def node=(node)
48
+ c[:node] = node
49
+ end
50
+
51
+ # Get the name of the hash
52
+ #
53
+ # @return [Symbol, nil]
54
+ def hash
55
+ c[:hash].to_sym
56
+ end
57
+
58
+ # Set the name of the hash
59
+ #
60
+ # @param [String, nil] hash the new hash name
61
+ def hash=(hash)
62
+ if hash && !VALID_HASH_TYPES.include?(hash.to_s)
63
+ raise ArgumentError, "Invalid Hash Type (#{hash}), use: #{VALID_HASH_TYPES*' '}"
64
+ end
65
+ c[:hash] = hash
66
+ end
67
+
68
+ # Get the ver
69
+ #
70
+ # @return [String, nil]
71
+ def ver
72
+ c[:ver]
73
+ end
74
+
75
+ # Set the ver
76
+ #
77
+ # @param [String, nil] ver the new ver
78
+ def ver=(ver)
79
+ c[:ver] = ver
80
+ end
81
+
82
+ # C node accessor
83
+ # If a c node exists it will be returned.
84
+ # Otherwise a new node will be created and returned
85
+ #
86
+ # @return [Blather::XMPPNode]
87
+ def c
88
+ c = if self.class.registered_ns
89
+ find_first('ns:c', :ns => self.class.registered_ns)
90
+ else
91
+ find_first('c')
92
+ end
93
+
94
+ unless c
95
+ (self << (c = XMPPNode.new('c', self.document)))
96
+ c.namespace = self.class.registered_ns
97
+ end
98
+ c
99
+ end
100
+ end # C
101
+ end #Presence
102
+ end #Stanza
103
+ end
@@ -331,11 +331,9 @@ class Stanza
331
331
  # Compare two Field objects by type, var and label
332
332
  # @param [X::Field] o the Field object to compare against
333
333
  # @return [true, false]
334
- def eql?(o)
335
- raise "Cannot compare #{self.class} with #{o.class}" unless o.is_a?(self.class)
336
- ![:type, :var, :label, :desc, :required?, :value].detect { |m| o.send(m) != self.send(m) }
334
+ def eql?(o, *fields)
335
+ super o, *(fields + [:type, :var, :label, :desc, :required?, :value])
337
336
  end
338
- alias_method :==, :eql?
339
337
 
340
338
  class Option < XMPPNode
341
339
  register :option, 'jabber:x:data'
@@ -0,0 +1,3 @@
1
+ module Blather
2
+ VERSION = '0.4.16'
3
+ end
@@ -213,6 +213,14 @@ module Blather
213
213
  def inspect
214
214
  self.to_xml
215
215
  end
216
+
217
+ def eql?(o, *fields)
218
+ o.is_a?(self.class) && fields.all? { |f| self.__send__(f) == o.__send__(f) }
219
+ end
220
+
221
+ def ==(o)
222
+ eql?(o)
223
+ end
216
224
  end # XMPPNode
217
225
 
218
226
  end # Blather
@@ -1,4 +1,4 @@
1
- require File.expand_path "../../../spec_helper", __FILE__
1
+ require 'spec_helper'
2
2
  require 'blather/client/client'
3
3
 
4
4
  describe Blather::Client do
@@ -26,6 +26,11 @@ describe Blather::Client do
26
26
  @client.status.must_equal :away
27
27
  end
28
28
 
29
+ it 'should have a caps handler' do
30
+ @client.must_respond_to :caps
31
+ @client.caps.must_be_kind_of Blather::Client::Caps
32
+ end
33
+
29
34
  it 'can be setup' do
30
35
  @client.must_respond_to :setup
31
36
  @client.setup('me@me.com', 'pass').must_equal @client
@@ -571,3 +576,84 @@ describe 'Blather::Client guards' do
571
576
  lambda { @client.register_handler(:iq, 0) {} }.must_raise RuntimeError
572
577
  end
573
578
  end
579
+
580
+ describe 'Blather::Client::Caps' do
581
+ before do
582
+ @client = Blather::Client.new
583
+ @stream = mock()
584
+ @stream.stubs(:send)
585
+ @client.post_init @stream, Blather::JID.new('n@d/r')
586
+ @caps = @client.caps
587
+ end
588
+
589
+ it 'must be of type result' do
590
+ @caps.must_respond_to :type
591
+ @caps.type.must_equal :result
592
+ end
593
+
594
+ it 'can have a client node set' do
595
+ @caps.must_respond_to :node=
596
+ @caps.node = "somenode"
597
+ end
598
+
599
+ it 'provides a client node reader' do
600
+ @caps.must_respond_to :node
601
+ @caps.node = "somenode"
602
+ @caps.node.must_equal "somenode##{@caps.ver}"
603
+ end
604
+
605
+ it 'can have identities set' do
606
+ @caps.must_respond_to :identities=
607
+ @caps.identities = [{:name => "name", :type => "type", :category => "cat"}]
608
+ end
609
+
610
+ it 'provides an identities reader' do
611
+ @caps.must_respond_to :identities
612
+ @caps.identities = [{:name => "name", :type => "type", :category => "cat"}]
613
+ @caps.identities.must_equal [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => "name", :type => "type", :category => "cat"})]
614
+ end
615
+
616
+ it 'can have features set' do
617
+ @caps.must_respond_to :features=
618
+ @caps.features.size.must_equal 0
619
+ @caps.features = ["feature1"]
620
+ @caps.features.size.must_equal 1
621
+ @caps.features += [Blather::Stanza::Iq::DiscoInfo::Feature.new("feature2")]
622
+ @caps.features.size.must_equal 2
623
+ @caps.features = nil
624
+ @caps.features.size.must_equal 0
625
+ end
626
+
627
+ it 'provides a features reader' do
628
+ @caps.must_respond_to :features
629
+ @caps.features = %w{feature1 feature2}
630
+ @caps.features.must_equal [Blather::Stanza::Iq::DiscoInfo::Feature.new("feature1"), Blather::Stanza::Iq::DiscoInfo::Feature.new("feature2")]
631
+ end
632
+
633
+ it 'provides a client ver reader' do
634
+ @caps.must_respond_to :ver
635
+ @caps.node = 'http://code.google.com/p/exodus'
636
+ @caps.identities = [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => 'Exodus 0.9.1', :type => 'pc', :category => 'client'})]
637
+ @caps.features = %w{
638
+ http://jabber.org/protocol/caps
639
+ http://jabber.org/protocol/disco#info
640
+ http://jabber.org/protocol/disco#items
641
+ http://jabber.org/protocol/muc
642
+ }
643
+ @caps.ver.must_equal 'QgayPKawpkPSDYmwT/WM94uAlu0='
644
+ @caps.node.must_equal "http://code.google.com/p/exodus#QgayPKawpkPSDYmwT/WM94uAlu0="
645
+ end
646
+
647
+ it 'can construct caps presence correctly' do
648
+ @caps.must_respond_to :c
649
+ @caps.node = 'http://code.google.com/p/exodus'
650
+ @caps.identities = [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => 'Exodus 0.9.1', :type => 'pc', :category => 'client'})]
651
+ @caps.features = %w{
652
+ http://jabber.org/protocol/caps
653
+ http://jabber.org/protocol/disco#info
654
+ http://jabber.org/protocol/disco#items
655
+ http://jabber.org/protocol/muc
656
+ }
657
+ @caps.c.inspect.must_equal "<presence>\n <c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://code.google.com/p/exodus\" ver=\"QgayPKawpkPSDYmwT/WM94uAlu0=\"/>\n</presence>"
658
+ end
659
+ end