blather 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|