sprsquish-blather 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.rdoc +41 -12
- data/examples/echo.rb +1 -1
- data/examples/execute.rb +0 -5
- data/examples/pubsub/cli.rb +64 -0
- data/examples/pubsub/ping_pong.rb +18 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/lib/blather/client/client.rb +19 -13
- data/lib/blather/client/dsl/pubsub.rb +133 -0
- data/lib/blather/client/dsl.rb +16 -0
- data/lib/blather/client.rb +1 -1
- data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
- data/lib/blather/core_ext/active_support.rb +1 -117
- data/lib/blather/core_ext/nokogiri.rb +35 -0
- data/lib/blather/errors/sasl_error.rb +3 -1
- data/lib/blather/errors/stanza_error.rb +10 -17
- data/lib/blather/errors/stream_error.rb +11 -14
- data/lib/blather/errors.rb +3 -20
- data/lib/blather/jid.rb +1 -0
- data/lib/blather/roster.rb +9 -0
- data/lib/blather/roster_item.rb +6 -1
- data/lib/blather/stanza/disco/disco_info.rb +45 -33
- data/lib/blather/stanza/disco/disco_items.rb +32 -21
- data/lib/blather/stanza/disco.rb +7 -1
- data/lib/blather/stanza/iq/query.rb +16 -8
- data/lib/blather/stanza/iq/roster.rb +33 -22
- data/lib/blather/stanza/iq.rb +13 -8
- data/lib/blather/stanza/message.rb +20 -31
- data/lib/blather/stanza/presence/status.rb +13 -21
- data/lib/blather/stanza/presence/subscription.rb +11 -16
- data/lib/blather/stanza/presence.rb +3 -5
- data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
- data/lib/blather/stanza/pubsub/create.rb +43 -0
- data/lib/blather/stanza/pubsub/errors.rb +9 -0
- data/lib/blather/stanza/pubsub/event.rb +77 -0
- data/lib/blather/stanza/pubsub/items.rb +63 -0
- data/lib/blather/stanza/pubsub/publish.rb +58 -0
- data/lib/blather/stanza/pubsub/retract.rb +53 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
- data/lib/blather/stanza/pubsub/subscription.rb +66 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
- data/lib/blather/stanza/pubsub.rb +63 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
- data/lib/blather/stanza/pubsub_owner.rb +41 -0
- data/lib/blather/stanza.rb +35 -18
- data/lib/blather/stream/client.rb +1 -2
- data/lib/blather/stream/component.rb +9 -5
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
- data/lib/blather/stream/features/session.rb +44 -0
- data/lib/blather/stream/features/tls.rb +28 -0
- data/lib/blather/stream/features.rb +53 -0
- data/lib/blather/stream/parser.rb +70 -46
- data/lib/blather/stream.rb +76 -168
- data/lib/blather/xmpp_node.rb +113 -52
- data/lib/blather.rb +35 -12
- data/spec/blather/client/client_spec.rb +44 -58
- data/spec/blather/client/dsl/pubsub_spec.rb +465 -0
- data/spec/blather/client/dsl_spec.rb +19 -6
- data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
- data/spec/blather/errors/sasl_error_spec.rb +8 -8
- data/spec/blather/errors/stanza_error_spec.rb +25 -33
- data/spec/blather/errors/stream_error_spec.rb +21 -16
- data/spec/blather/errors_spec.rb +4 -11
- data/spec/blather/jid_spec.rb +31 -30
- data/spec/blather/roster_item_spec.rb +34 -23
- data/spec/blather/roster_spec.rb +27 -12
- data/spec/blather/stanza/discos/disco_info_spec.rb +61 -42
- data/spec/blather/stanza/discos/disco_items_spec.rb +47 -35
- data/spec/blather/stanza/iq/query_spec.rb +34 -11
- data/spec/blather/stanza/iq/roster_spec.rb +47 -30
- data/spec/blather/stanza/iq_spec.rb +19 -14
- data/spec/blather/stanza/message_spec.rb +30 -17
- data/spec/blather/stanza/presence/status_spec.rb +43 -20
- data/spec/blather/stanza/presence/subscription_spec.rb +41 -21
- data/spec/blather/stanza/presence_spec.rb +34 -21
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +62 -0
- data/spec/blather/stanza_spec.rb +53 -38
- data/spec/blather/stream/client_spec.rb +231 -88
- data/spec/blather/stream/component_spec.rb +14 -5
- data/spec/blather/stream/parser_spec.rb +145 -0
- data/spec/blather/xmpp_node_spec.rb +192 -96
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +5 -4
- metadata +53 -17
- data/Rakefile +0 -139
- data/ext/extconf.rb +0 -65
- data/ext/push_parser.c +0 -209
- data/lib/blather/core_ext/libxml.rb +0 -28
- data/lib/blather/stream/resource.rb +0 -48
- data/lib/blather/stream/session.rb +0 -36
- data/lib/blather/stream/stream_handler.rb +0 -39
- data/lib/blather/stream/tls.rb +0 -33
- data/spec/blather/core_ext/libxml_spec.rb +0 -58
@@ -1,64 +1,71 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
2
|
class Stream # :nodoc:
|
3
3
|
|
4
|
-
class SASL <
|
4
|
+
class SASL < Features # :nodoc:
|
5
5
|
class UnknownMechanism < BlatherError
|
6
|
-
|
7
|
-
handler_heirarchy << :unknown_mechanism
|
6
|
+
register :sasl_unknown_mechanism
|
8
7
|
end
|
9
8
|
|
10
9
|
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
|
10
|
+
register SASL_NS
|
11
11
|
|
12
|
-
def initialize(stream,
|
13
|
-
super
|
14
|
-
@jid = jid
|
15
|
-
@pass =
|
16
|
-
@mechanism_idx = 0
|
12
|
+
def initialize(stream, succeed, fail)
|
13
|
+
super
|
14
|
+
@jid = @stream.jid
|
15
|
+
@pass = @stream.password
|
17
16
|
@mechanisms = []
|
18
17
|
end
|
19
18
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
19
|
+
def receive_data(stanza)
|
20
|
+
@node = stanza
|
21
|
+
case stanza.element_name
|
22
|
+
when 'mechanisms'
|
23
|
+
@mechanisms = stanza.children.map { |m| m.content.downcase }
|
24
|
+
next!
|
25
|
+
when 'failure'
|
26
|
+
next!
|
27
|
+
when 'success'
|
28
|
+
@stream.start
|
23
29
|
else
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
30
|
+
if self.respond_to?(stanza.element_name)
|
31
|
+
self.__send__(stanza.element_name)
|
32
|
+
else
|
33
|
+
fail! UnknownResponse.new(stanza)
|
28
34
|
end
|
29
35
|
end
|
36
|
+
end
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
protected
|
39
|
+
def next!
|
40
|
+
if @jid.node == ''
|
41
|
+
process_anonymous
|
34
42
|
else
|
35
|
-
|
36
|
-
@
|
37
|
-
@failure.call UnknownMechanism.new("Unknown SASL mechanism (#{mechanism})")
|
38
|
-
false
|
43
|
+
@idx = @idx ? @idx+1 : 0
|
44
|
+
authenticate_with @mechanisms[@idx]
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# run out of them or none work
|
46
|
-
def handle(node)
|
47
|
-
if node.element_name == 'failure'
|
48
|
-
if @mechanisms[@mechanism_idx += 1]
|
49
|
-
set_mechanism
|
50
|
-
authenticate
|
51
|
-
else
|
52
|
-
failure node
|
53
|
-
end
|
48
|
+
def process_anonymous
|
49
|
+
if @mechanisms.include?('anonymous')
|
50
|
+
authenticate_with 'anonymous'
|
54
51
|
else
|
55
|
-
|
52
|
+
fail! BlatherError.new('The server does not support ANONYMOUS login. You must provide a node in the JID')
|
56
53
|
end
|
57
54
|
end
|
58
55
|
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
def authenticate_with(method)
|
57
|
+
method = case method
|
58
|
+
when 'digest-md5' then DigestMD5
|
59
|
+
when 'plain' then Plain
|
60
|
+
when 'anonymous' then Anonymous
|
61
|
+
when nil then fail!(SASLError.import(@node))
|
62
|
+
else next!
|
63
|
+
end
|
64
|
+
|
65
|
+
if method.is_a?(Module)
|
66
|
+
extend method
|
67
|
+
authenticate
|
68
|
+
end
|
62
69
|
end
|
63
70
|
|
64
71
|
##
|
@@ -70,19 +77,13 @@ class Stream # :nodoc:
|
|
70
77
|
##
|
71
78
|
# Builds a standard auth node
|
72
79
|
def auth_node(mechanism, content = nil)
|
73
|
-
node = XMPPNode.new 'auth'
|
74
|
-
node
|
75
|
-
node
|
80
|
+
node = XMPPNode.new 'auth'
|
81
|
+
node.content = content if content
|
82
|
+
node.namespace = SASL_NS
|
83
|
+
node[:mechanism] = mechanism
|
76
84
|
node
|
77
85
|
end
|
78
86
|
|
79
|
-
##
|
80
|
-
# Respond to the <mechanisms> node sent by the server
|
81
|
-
def mechanisms
|
82
|
-
@mechanisms = @node.children
|
83
|
-
authenticate if set_mechanism
|
84
|
-
end
|
85
|
-
|
86
87
|
##
|
87
88
|
# Digest MD5 authentication
|
88
89
|
module DigestMD5 # :nodoc:
|
@@ -111,7 +112,7 @@ class Stream # :nodoc:
|
|
111
112
|
key, value = statement.split('=')
|
112
113
|
res[key] = value.delete('"') unless key.empty?
|
113
114
|
end
|
114
|
-
|
115
|
+
Blather.logger.debug "CHALLENGE DECODE: #{res.inspect}"
|
115
116
|
|
116
117
|
@nonce ||= res['nonce']
|
117
118
|
@realm ||= res['realm']
|
@@ -129,7 +130,7 @@ class Stream # :nodoc:
|
|
129
130
|
# Send challenge response
|
130
131
|
def respond
|
131
132
|
node = XMPPNode.new 'response'
|
132
|
-
node
|
133
|
+
node.namespace = SASL_NS
|
133
134
|
|
134
135
|
unless @initial_response_sent
|
135
136
|
@initial_response_sent = true
|
@@ -146,8 +147,8 @@ class Stream # :nodoc:
|
|
146
147
|
@response[:response] = generate_response
|
147
148
|
@response.each { |k,v| @response[k] = "\"#{v}\"" unless [:nc, :qop, :response, :charset].include?(k) }
|
148
149
|
|
149
|
-
|
150
|
-
|
150
|
+
Blather.logger.debug "CHALLENGE RESPONSE: #{@response.inspect}"
|
151
|
+
Blather.logger.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
|
151
152
|
|
152
153
|
# order is to simplify testing
|
153
154
|
# Ruby 1.9 eliminates the need for this with ordered hashes
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class Session < Features # :nodoc:
|
5
|
+
SESSION_NS = 'urn:ietf:params:xml:ns:xmpp-session'.freeze
|
6
|
+
register SESSION_NS
|
7
|
+
|
8
|
+
def initialize(stream, succeed, fail)
|
9
|
+
super
|
10
|
+
@to = @stream.jid.domain
|
11
|
+
end
|
12
|
+
|
13
|
+
def receive_data(stanza)
|
14
|
+
@node = stanza
|
15
|
+
case stanza.element_name
|
16
|
+
when 'session' then session
|
17
|
+
when 'iq' then check_response
|
18
|
+
else fail!(UnknownResponse.new(stanza))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def check_response
|
24
|
+
if @node[:type] == 'result'
|
25
|
+
succeed!
|
26
|
+
else
|
27
|
+
fail!(StanzaError.import(@node))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Send a start session command
|
33
|
+
def session
|
34
|
+
response = Stanza::Iq.new :set
|
35
|
+
response.to = @to
|
36
|
+
response << (sess = XMPPNode.new('session', response.document))
|
37
|
+
sess.namespace = SESSION_NS
|
38
|
+
|
39
|
+
@stream.send response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class TLS < Features # :nodoc:
|
5
|
+
class TLSFailure < BlatherError
|
6
|
+
register :tls_failure
|
7
|
+
end
|
8
|
+
|
9
|
+
TLS_NS = 'urn:ietf:params:xml:ns:xmpp-tls'.freeze
|
10
|
+
register TLS_NS
|
11
|
+
|
12
|
+
def receive_data(stanza)
|
13
|
+
case stanza.element_name
|
14
|
+
when 'starttls'
|
15
|
+
@stream.send "<starttls xmlns='#{TLS_NS}'/>"
|
16
|
+
when 'proceed'
|
17
|
+
@stream.start_tls
|
18
|
+
@stream.start
|
19
|
+
# succeed!
|
20
|
+
else
|
21
|
+
fail! TLSFailure.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end #TLS
|
26
|
+
|
27
|
+
end #Stream
|
28
|
+
end #Blather
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class Features
|
5
|
+
@@features = {}
|
6
|
+
def self.register(ns)
|
7
|
+
@@features[ns] = self
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_namespace(ns)
|
11
|
+
@@features[ns]
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(stream, succeed, fail)
|
15
|
+
@stream = stream
|
16
|
+
@succeed = succeed
|
17
|
+
@fail = fail
|
18
|
+
end
|
19
|
+
|
20
|
+
def receive_data(stanza)
|
21
|
+
if @feature
|
22
|
+
@feature.receive_data stanza
|
23
|
+
else
|
24
|
+
@features ||= stanza
|
25
|
+
next!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def next!
|
30
|
+
@idx = @idx ? @idx+1 : 0
|
31
|
+
if stanza = @features.children[@idx]
|
32
|
+
if stanza.namespaces['xmlns'] && (klass = self.class.from_namespace(stanza.namespaces['xmlns']))
|
33
|
+
@feature = klass.new @stream, proc { next! }, @fail
|
34
|
+
@feature.receive_data stanza
|
35
|
+
else
|
36
|
+
next!
|
37
|
+
end
|
38
|
+
else
|
39
|
+
succeed!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def succeed!
|
44
|
+
@succeed.call
|
45
|
+
end
|
46
|
+
|
47
|
+
def fail!(msg)
|
48
|
+
@fail.call msg
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end #Stream
|
53
|
+
end #Blather
|
@@ -1,9 +1,10 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
1
3
|
module Blather # :nodoc:
|
2
4
|
class Stream # :nodoc:
|
3
5
|
|
4
6
|
class Parser # :nodoc:
|
5
|
-
|
6
|
-
ERROR_REGEX = /^<(stream:[a-z]+)/.freeze
|
7
|
+
NS_TO_IGNORE = %w[jabber:client jabber:component:accept]
|
7
8
|
|
8
9
|
@@debug = !false
|
9
10
|
def self.debug; @@debug; end
|
@@ -12,70 +13,93 @@ class Stream # :nodoc:
|
|
12
13
|
def initialize(receiver)
|
13
14
|
@receiver = receiver
|
14
15
|
@current = nil
|
15
|
-
@
|
16
|
+
@namespaces = {}
|
17
|
+
@namespace_definitions = []
|
18
|
+
@parser = Nokogiri::XML::SAX::PushParser.new self
|
16
19
|
end
|
17
20
|
|
18
21
|
def receive_data(string)
|
19
|
-
|
20
|
-
@
|
21
|
-
|
22
|
+
Blather.logger.debug "PARSING: (#{string})" if @@debug
|
23
|
+
@parser << string
|
24
|
+
self
|
22
25
|
end
|
26
|
+
alias_method :<<, :receive_data
|
23
27
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
e = XMPPNode.new elem
|
28
|
-
attrs.each { |k,v| e.attributes[k] = v if k }
|
28
|
+
def start_document; end
|
29
|
+
def end_document; end
|
30
|
+
def warning(*args); end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
else
|
33
|
-
e.namespace = {prefix => uri}
|
34
|
-
end
|
32
|
+
def start_element_ns(elem, attrs, prefix, uri, namespaces)
|
33
|
+
Blather.logger.debug "START ELEM: (#{{:elem => elem, :attrs => attrs, :prefix => prefix, :uri => uri, :ns => namespaces}.inspect})" if @@debug
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
args = [elem]
|
36
|
+
args << @current.document if @current
|
37
|
+
node = XMPPNode.new *args
|
38
|
+
node.document.root = node unless @current
|
39
39
|
|
40
|
-
|
41
|
-
@current << e if @current
|
42
|
-
@current = e
|
40
|
+
attrs.each { |k,v| node[k] = v if k }
|
43
41
|
|
42
|
+
if !@receiver.stopped?
|
43
|
+
@current << node if @current
|
44
|
+
@current = node
|
44
45
|
end
|
45
|
-
end
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
namespaces.delete_if { |pre, href| NS_TO_IGNORE.include? href }
|
48
|
+
@namespace_definitions.push []
|
49
|
+
namespaces.each do |pre, href|
|
50
|
+
next if @namespace_definitions.flatten.include?(@namespaces[[pre, href]])
|
51
|
+
ns = node.add_namespace(pre, href)
|
52
|
+
@namespaces[[pre, href]] ||= ns
|
53
|
+
end
|
54
|
+
@namespaces[[prefix, uri]] ||= node.add_namespace(prefix, uri) if prefix && !namespaces[prefix]
|
55
|
+
node.namespace = @namespaces[[prefix, uri]]
|
56
|
+
|
57
|
+
deliver(node) if elem == 'stream'
|
58
|
+
|
59
|
+
=begin
|
60
|
+
$stderr.puts "\n\n"
|
61
|
+
$stderr.puts [elem, attrs, prefix, uri, namespaces].inspect
|
62
|
+
$stderr.puts @namespaces.inspect
|
63
|
+
$stderr.puts [@namespaces[[prefix, uri]].prefix, @namespaces[[prefix, uri]].href].inspect if @namespaces[[prefix, uri]]
|
64
|
+
$stderr.puts node.inspect
|
65
|
+
$stderr.puts node.document.to_s.gsub(/\n\s*/,'')
|
66
|
+
=end
|
50
67
|
end
|
51
68
|
|
52
|
-
def
|
53
|
-
|
69
|
+
def end_element_ns(elem, prefix, uri)
|
70
|
+
Blather.logger.debug "END ELEM: #{{:elem => elem, :prefix => prefix, :uri => uri}.inspect}" if @@debug
|
54
71
|
|
55
|
-
if
|
56
|
-
|
57
|
-
|
58
|
-
|
72
|
+
if elem == 'stream'
|
73
|
+
node = XMPPNode.new('end')
|
74
|
+
node.namespace = {prefix => uri}
|
75
|
+
deliver node
|
76
|
+
elsif @current.parent != @current.document
|
77
|
+
@namespace_definitions.pop
|
59
78
|
@current = @current.parent
|
60
|
-
|
61
79
|
else
|
62
|
-
|
63
|
-
XML::Document.new.root = c
|
64
|
-
@receiver.receive c
|
65
|
-
|
80
|
+
deliver @current
|
66
81
|
end
|
67
82
|
end
|
68
83
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
84
|
+
def characters(chars = '')
|
85
|
+
Blather.logger.debug "CHARS: #{chars}" if @@debug
|
86
|
+
@current << Nokogiri::XML::Text.new(chars, @current.document) if @current
|
87
|
+
end
|
88
|
+
|
89
|
+
def warning(msg)
|
90
|
+
Blather.logger.debug "PARSE WARNING: #{msg}" if @@debug
|
91
|
+
end
|
92
|
+
|
93
|
+
def error(msg)
|
94
|
+
raise ParseError.new(msg)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def deliver(node)
|
99
|
+
@current, @namespaces, @namespace_definitions = nil, {}, []
|
100
|
+
@receiver.receive node
|
77
101
|
end
|
78
102
|
end #Parser
|
79
103
|
|
80
104
|
end #Stream
|
81
|
-
end #Blather
|
105
|
+
end #Blather
|