blather 0.1 → 0.2
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.
- data/CHANGELOG +2 -0
- data/Manifest +36 -33
- data/README.rdoc +11 -17
- data/Rakefile +4 -3
- data/blather.gemspec +9 -9
- data/examples/drb_client.rb +7 -0
- data/examples/echo.rb +9 -18
- data/lib/blather.rb +140 -31
- data/lib/blather/{core/errors.rb → errors.rb} +0 -0
- data/lib/blather/{core/jid.rb → jid.rb} +0 -0
- data/lib/blather/{core/roster.rb → roster.rb} +0 -0
- data/lib/blather/{core/roster_item.rb → roster_item.rb} +0 -0
- data/lib/blather/{core/stanza.rb → stanza.rb} +6 -14
- data/lib/blather/stanza/error.rb +31 -0
- data/lib/blather/{core/stanza → stanza}/iq.rb +2 -2
- data/lib/blather/stanza/iq/query.rb +50 -0
- data/lib/blather/{core/stanza → stanza}/iq/roster.rb +3 -3
- data/lib/blather/{core/stanza → stanza}/message.rb +6 -2
- data/lib/blather/{core/stanza → stanza}/presence.rb +10 -0
- data/lib/blather/{core/stanza → stanza}/presence/status.rb +8 -6
- data/lib/blather/{core/stanza → stanza}/presence/subscription.rb +1 -1
- data/lib/blather/{core/stream.rb → stream.rb} +1 -1
- data/lib/blather/{core/stream → stream}/parser.rb +0 -5
- data/lib/blather/{core/stream → stream}/resource.rb +0 -0
- data/lib/blather/{core/stream → stream}/sasl.rb +16 -5
- data/lib/blather/{core/stream → stream}/session.rb +0 -0
- data/lib/blather/{core/stream → stream}/tls.rb +0 -0
- data/lib/blather/{core/sugar.rb → sugar.rb} +4 -0
- data/lib/blather/{core/xmpp_node.rb → xmpp_node.rb} +1 -3
- data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +16 -1
- data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +1 -1
- data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +11 -1
- data/spec/blather/stanza/iq/query_spec.rb +34 -0
- data/spec/blather/stanza/iq/roster_spec.rb +7 -0
- data/spec/blather/stanza/iq_spec.rb +11 -0
- data/spec/blather/stanza/message_spec.rb +52 -0
- data/spec/blather/stanza/presence/status_spec.rb +102 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +74 -0
- data/spec/blather/stanza/presence_spec.rb +24 -0
- data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +1 -1
- data/spec/blather/{core/stream_spec.rb → stream_spec.rb} +208 -9
- data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +1 -1
- metadata +75 -69
- data/examples/shell_client.rb +0 -28
- data/lib/blather/callback.rb +0 -24
- data/lib/blather/client.rb +0 -81
- data/lib/blather/core/stanza/iq/query.rb +0 -42
- data/lib/blather/extensions.rb +0 -4
- data/lib/blather/extensions/last_activity.rb +0 -55
- data/lib/blather/extensions/version.rb +0 -85
@@ -0,0 +1,31 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
|
4
|
+
##
|
5
|
+
# Base Error stanza
|
6
|
+
class Error < Stanza
|
7
|
+
def self.new_from(stanza, defined_condition, type, text = nil)
|
8
|
+
err = XMPPNode.new(defined_condition)
|
9
|
+
err['type'] = type
|
10
|
+
err.xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
11
|
+
|
12
|
+
if text
|
13
|
+
text = XMPPNode.new('text', text)
|
14
|
+
text.xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
15
|
+
err << text
|
16
|
+
end
|
17
|
+
|
18
|
+
elem = stanza.copy(true)
|
19
|
+
elem.type = 'error'
|
20
|
+
elem << err
|
21
|
+
|
22
|
+
elem
|
23
|
+
end
|
24
|
+
|
25
|
+
def error?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end #ErrorStanza
|
29
|
+
|
30
|
+
end #Stanza
|
31
|
+
end
|
@@ -13,10 +13,10 @@ class Stanza
|
|
13
13
|
(klass || self).new(node['type']).inherit(node)
|
14
14
|
end
|
15
15
|
|
16
|
-
def self.new(type, to = nil, id = nil)
|
16
|
+
def self.new(type = nil, to = nil, id = nil)
|
17
17
|
elem = super :iq
|
18
18
|
elem.xmlns = nil
|
19
|
-
elem.type = type
|
19
|
+
elem.type = type || :get
|
20
20
|
elem.to = to
|
21
21
|
elem.id = id if id
|
22
22
|
elem
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
class Iq
|
4
|
+
|
5
|
+
class Query < Iq
|
6
|
+
register :query, :query
|
7
|
+
|
8
|
+
##
|
9
|
+
# Ensure the namespace is set to the query node
|
10
|
+
def self.new(type = nil)
|
11
|
+
elem = super
|
12
|
+
elem.query.xmlns = self.xmlns
|
13
|
+
elem
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Kill the query node before running inherit
|
18
|
+
def inherit(node)
|
19
|
+
query.remove!
|
20
|
+
@query = nil
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Query node accessor
|
26
|
+
# This will ensure there actually is a query node
|
27
|
+
def query
|
28
|
+
(self << (q = XMPPNode.new('query'))) unless q = find_first('query')
|
29
|
+
q
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# A query reply should have type set to "result"
|
34
|
+
def reply
|
35
|
+
elem = super
|
36
|
+
elem.type = :result
|
37
|
+
elem
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# A query reply should have type set to "result"
|
42
|
+
def reply!
|
43
|
+
self.type = :result
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end #Query
|
47
|
+
|
48
|
+
end #Iq
|
49
|
+
end #Stanza
|
50
|
+
end
|
@@ -5,9 +5,9 @@ class Iq
|
|
5
5
|
class Roster < Query
|
6
6
|
register :roster, nil, 'jabber:iq:roster'
|
7
7
|
|
8
|
-
def self.new(type, item = nil)
|
8
|
+
def self.new(type = nil, item = nil)
|
9
9
|
elem = super(type)
|
10
|
-
elem.query << item
|
10
|
+
elem.query << item if item
|
11
11
|
elem
|
12
12
|
end
|
13
13
|
|
@@ -21,7 +21,7 @@ class Iq
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def items
|
24
|
-
|
24
|
+
query.find('item')#.map { |g| RosterItem.new g }
|
25
25
|
end
|
26
26
|
|
27
27
|
class RosterItem < XMPPNode
|
@@ -8,7 +8,7 @@ class Stanza
|
|
8
8
|
|
9
9
|
register :message
|
10
10
|
|
11
|
-
def self.new(to = nil,
|
11
|
+
def self.new(to = nil, body = nil, type = :chat)
|
12
12
|
elem = super()
|
13
13
|
elem.to = to
|
14
14
|
elem.type = type
|
@@ -16,6 +16,10 @@ class Stanza
|
|
16
16
|
elem
|
17
17
|
end
|
18
18
|
|
19
|
+
VALID_TYPES.each do |valid_type|
|
20
|
+
define_method("#{valid_type}?") { self.type == valid_type }
|
21
|
+
end
|
22
|
+
|
19
23
|
##
|
20
24
|
# Ensures type is :chat, :error, :groupchat, :headline or :normal
|
21
25
|
def type=(type)
|
@@ -43,7 +47,7 @@ class Stanza
|
|
43
47
|
|
44
48
|
def thread=(thread)
|
45
49
|
remove_child :thread
|
46
|
-
self << XMPPNode.new('
|
50
|
+
self << XMPPNode.new('thread', thread) if thread
|
47
51
|
end
|
48
52
|
|
49
53
|
def thread
|
@@ -8,6 +8,12 @@ class Stanza
|
|
8
8
|
|
9
9
|
register :presence
|
10
10
|
|
11
|
+
##
|
12
|
+
# Ensure element_name is "presence" for all subclasses
|
13
|
+
def self.new
|
14
|
+
super :presence
|
15
|
+
end
|
16
|
+
|
11
17
|
##
|
12
18
|
# Creates a class based on the presence type
|
13
19
|
# either a Status or Subscription object is created based
|
@@ -22,6 +28,10 @@ class Stanza
|
|
22
28
|
klass.new.inherit(node)
|
23
29
|
end
|
24
30
|
|
31
|
+
VALID_TYPES.each do |valid_type|
|
32
|
+
define_method("#{valid_type}?") { self.type == valid_type }
|
33
|
+
end
|
34
|
+
|
25
35
|
##
|
26
36
|
# Ensures type is one of :unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe or :error
|
27
37
|
def type=(type)
|
@@ -7,7 +7,7 @@ class Presence
|
|
7
7
|
|
8
8
|
include Comparable
|
9
9
|
|
10
|
-
register :status
|
10
|
+
register :status, :status
|
11
11
|
|
12
12
|
def self.new(state = nil, message = nil)
|
13
13
|
elem = super()
|
@@ -42,15 +42,15 @@ class Presence
|
|
42
42
|
|
43
43
|
##
|
44
44
|
# Ensure priority is between -128 and 127
|
45
|
-
def priority=(
|
46
|
-
raise ArgumentError, 'Priority must be between -128 and +127' if
|
45
|
+
def priority=(new_priority)
|
46
|
+
raise ArgumentError, 'Priority must be between -128 and +127' if new_priority && !(-128..127).include?(new_priority.to_i)
|
47
47
|
|
48
48
|
remove_child :priority
|
49
|
-
self << XMPPNode.new('priority',
|
49
|
+
self << XMPPNode.new('priority', new_priority) if new_priority
|
50
50
|
end
|
51
51
|
|
52
52
|
def priority
|
53
|
-
|
53
|
+
content_from(:priority).to_i
|
54
54
|
end
|
55
55
|
|
56
56
|
def message=(msg)
|
@@ -66,7 +66,9 @@ class Presence
|
|
66
66
|
# Compare status based on priority
|
67
67
|
# raises an error if the JIDs aren't the same
|
68
68
|
def <=>(o)
|
69
|
-
|
69
|
+
unless self.from && o.from && self.from.stripped == o.from.stripped
|
70
|
+
raise ArgumentError, "Cannot compare status from different JIDs: #{[self.from, o.from].inspect}"
|
71
|
+
end
|
70
72
|
self.priority <=> o.priority
|
71
73
|
end
|
72
74
|
|
@@ -50,11 +50,6 @@ module Stream # :nodoc:
|
|
50
50
|
@current << XML::Node.new_text(chars) if @current
|
51
51
|
end
|
52
52
|
|
53
|
-
def on_cdata_block(block)
|
54
|
-
LOG.debug "CDATA: #{block}" if @@debug
|
55
|
-
@current << XML::Node.new_cdata(block) if @current
|
56
|
-
end
|
57
|
-
|
58
53
|
def on_end_element(elem)
|
59
54
|
return if elem =~ STREAM_REGEX
|
60
55
|
|
File without changes
|
@@ -2,6 +2,8 @@ module Blather # :nodoc:
|
|
2
2
|
module Stream # :nodoc:
|
3
3
|
|
4
4
|
class SASL # :nodoc:
|
5
|
+
class UnknownMechanism < BlatherError; end
|
6
|
+
|
5
7
|
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
6
8
|
|
7
9
|
def initialize(stream, jid, pass = nil)
|
@@ -9,20 +11,24 @@ module Stream # :nodoc:
|
|
9
11
|
@jid = jid
|
10
12
|
@pass = pass
|
11
13
|
@callbacks = {}
|
14
|
+
@mechanism = 0
|
15
|
+
@mechanisms = []
|
12
16
|
|
13
17
|
init_callbacks
|
14
18
|
end
|
15
19
|
|
16
20
|
def init_callbacks
|
17
|
-
@callbacks['mechanisms'] = proc { set_mechanism; authenticate }
|
21
|
+
@callbacks['mechanisms'] = proc { @mechanisms = @node.children; set_mechanism; authenticate }
|
18
22
|
end
|
19
23
|
|
20
24
|
def set_mechanism
|
21
|
-
mod = case (mechanism = @
|
25
|
+
mod = case (mechanism = @mechanisms[@mechanism].content)
|
22
26
|
when 'DIGEST-MD5' then DigestMD5
|
23
27
|
when 'PLAIN' then Plain
|
24
28
|
when 'ANONYMOUS' then Anonymous
|
25
|
-
else
|
29
|
+
else
|
30
|
+
@stream.send "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><invalid-mechanism/></failure>"
|
31
|
+
raise UnknownMechanism, "Unknown SASL mechanism (#{mechanism})"
|
26
32
|
end
|
27
33
|
|
28
34
|
extend mod
|
@@ -30,7 +36,12 @@ module Stream # :nodoc:
|
|
30
36
|
|
31
37
|
def receive(node)
|
32
38
|
@node = node
|
33
|
-
@
|
39
|
+
if @node.element_name == 'failure' && @mechanisms[@mechanism += 1]
|
40
|
+
set_mechanism
|
41
|
+
authenticate
|
42
|
+
else
|
43
|
+
@callbacks[@node.element_name].call if @callbacks[@node.element_name]
|
44
|
+
end
|
34
45
|
end
|
35
46
|
|
36
47
|
def success(&callback)
|
@@ -106,7 +117,7 @@ module Stream # :nodoc:
|
|
106
117
|
LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
|
107
118
|
|
108
119
|
# order is to simplify testing
|
109
|
-
order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri']
|
120
|
+
order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri', :response]
|
110
121
|
node.content = b64(order.map { |k| v = @response[k]; "#{k}=#{v}" } * ',')
|
111
122
|
end
|
112
123
|
|
File without changes
|
File without changes
|
@@ -7,8 +7,6 @@ module Blather
|
|
7
7
|
class XMPPNode < XML::Node
|
8
8
|
@@registrations = {}
|
9
9
|
|
10
|
-
alias_method :element_name, :name
|
11
|
-
|
12
10
|
class_inheritable_accessor :xmlns,
|
13
11
|
:name
|
14
12
|
|
@@ -35,7 +33,7 @@ module Blather
|
|
35
33
|
def self.register(name, xmlns = nil)
|
36
34
|
self.name = name.to_s
|
37
35
|
self.xmlns = xmlns
|
38
|
-
@@registrations[[name, xmlns]] = self
|
36
|
+
@@registrations[[self.name, self.xmlns]] = self
|
39
37
|
end
|
40
38
|
|
41
39
|
##
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), *%w[..
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
|
2
2
|
|
3
3
|
describe 'Blather::JID' do
|
4
4
|
it 'does nothing if creaded from JID' do
|
@@ -75,4 +75,19 @@ describe 'Blather::JID' do
|
|
75
75
|
JID.new('n', 'd', 'r').to_s.must_equal 'n@d/r'
|
76
76
|
JID.new('n', 'd').to_s.must_equal 'n@d'
|
77
77
|
end
|
78
|
+
|
79
|
+
it 'falls back to basic ruby if idn gem is not present' do
|
80
|
+
JID.const_set 'USE_STRINGPREP', false
|
81
|
+
jid = JID.new 'AB#$cdE@&^FE'
|
82
|
+
jid.node.must_equal 'ab#$cde'
|
83
|
+
jid.domain.must_equal '&^fe'
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'provides a #stripped? helper' do
|
87
|
+
jid = JID.new 'a@b/c'
|
88
|
+
jid.must_respond_to :stripped?
|
89
|
+
jid.stripped?.wont_equal true
|
90
|
+
jid.strip!
|
91
|
+
jid.stripped?.must_equal true
|
92
|
+
end
|
78
93
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), *%w[..
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
|
2
2
|
|
3
3
|
describe 'Blather::Roster' do
|
4
4
|
before do
|
@@ -40,6 +40,16 @@ describe 'Blather::Roster' do
|
|
40
40
|
@roster[jid].wont_be_nil
|
41
41
|
end
|
42
42
|
|
43
|
+
it 'aliases #<< to #push and returns self to allow for chaining' do
|
44
|
+
jid = 'a@b/c'
|
45
|
+
item = RosterItem.new(JID.new(jid))
|
46
|
+
jid2 = 'd@e/f'
|
47
|
+
item2 = RosterItem.new(JID.new(jid2))
|
48
|
+
proc { @roster << item << item2 }.must_change('@roster.items', :length, :by => 2)
|
49
|
+
@roster[jid].wont_be_nil
|
50
|
+
@roster[jid2].wont_be_nil
|
51
|
+
end
|
52
|
+
|
43
53
|
it 'sends a @roster addition over the wire' do
|
44
54
|
stream = mock()
|
45
55
|
stream.expects(:send_data)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'Blather::Stanza::Iq::Query' do
|
4
|
+
it 'registers itself' do
|
5
|
+
XMPPNode.class_from_registration(:query, nil).must_equal Stanza::Iq::Query
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'ensures a query node is present on create' do
|
9
|
+
query = Stanza::Iq::Query.new
|
10
|
+
query.children.detect { |n| n.element_name == 'query' }.wont_be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'ensures a query node exists when calling #query' do
|
14
|
+
query = Stanza::Iq::Query.new
|
15
|
+
query.remove_child :query
|
16
|
+
query.children.detect { |n| n.element_name == 'query' }.must_be_nil
|
17
|
+
|
18
|
+
query.query.wont_be_nil
|
19
|
+
query.children.detect { |n| n.element_name == 'query' }.wont_be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets type to "reslut" on reply' do
|
23
|
+
query = Stanza::Iq::Query.new
|
24
|
+
query.type.must_equal :get
|
25
|
+
reply = query.reply.type.must_equal :result
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets type to "reslut" on reply!' do
|
29
|
+
query = Stanza::Iq::Query.new
|
30
|
+
query.type.must_equal :get
|
31
|
+
query.reply!
|
32
|
+
query.type.must_equal :result
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
2
|
+
|
3
|
+
describe 'Blather::Stanza::Iq' do
|
4
|
+
it 'registers itself' do
|
5
|
+
XMPPNode.class_from_registration(:iq, nil).must_equal Stanza::Iq
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'creates a new Iq stanza defaulted as a get' do
|
9
|
+
Stanza::Iq.new.type.must_equal :get
|
10
|
+
end
|
11
|
+
end
|