sprsquish-blather 0.1 → 0.2.3
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/LICENSE +2 -0
- data/README.rdoc +100 -0
- data/Rakefile +110 -0
- data/examples/drb_client.rb +5 -0
- data/examples/echo.rb +18 -0
- data/ext/extconf.rb +65 -0
- data/ext/push_parser.c +231 -0
- data/lib/blather/client.rb +219 -44
- data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
- data/lib/blather/core_ext/libxml.rb +28 -0
- data/lib/blather/errors/sasl_error.rb +87 -0
- data/lib/blather/errors/stanza_error.rb +262 -0
- data/lib/blather/errors/stream_error.rb +253 -0
- data/lib/blather/errors.rb +48 -0
- data/lib/blather/{core/jid.rb → jid.rb} +15 -26
- data/lib/blather/{core/roster.rb → roster.rb} +22 -0
- data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
- data/lib/blather/stanza/iq/disco.rb +11 -0
- data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
- data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
- data/lib/blather/stanza/iq/query.rb +51 -0
- data/lib/blather/stanza/iq/roster.rb +90 -0
- data/lib/blather/stanza/iq.rb +38 -0
- data/lib/blather/stanza/message.rb +58 -0
- data/lib/blather/stanza/presence/status.rb +78 -0
- data/lib/blather/stanza/presence/subscription.rb +72 -0
- data/lib/blather/stanza/presence.rb +45 -0
- data/lib/blather/stanza.rb +101 -0
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +70 -0
- data/lib/blather/stream/resource.rb +48 -0
- data/lib/blather/stream/sasl.rb +173 -0
- data/lib/blather/stream/session.rb +36 -0
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +33 -0
- data/lib/blather/stream.rb +249 -0
- data/lib/blather/xmpp_node.rb +199 -0
- data/lib/blather.rb +40 -41
- data/spec/blather/core_ext/libxml_spec.rb +58 -0
- data/spec/blather/errors/sasl_error_spec.rb +56 -0
- data/spec/blather/errors/stanza_error_spec.rb +148 -0
- data/spec/blather/errors/stream_error_spec.rb +114 -0
- data/spec/blather/errors_spec.rb +40 -0
- data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
- data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
- data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
- data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +34 -0
- data/spec/blather/stanza/iq/roster_spec.rb +123 -0
- data/spec/blather/stanza/iq_spec.rb +40 -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 +85 -0
- data/spec/blather/stanza/presence_spec.rb +53 -0
- data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
- data/spec/build_safe.rb +20 -0
- data/spec/spec_helper.rb +7 -17
- metadata +79 -59
- data/CHANGELOG +0 -1
- data/blather.gemspec +0 -73
- data/lib/blather/callback.rb +0 -24
- data/lib/blather/core/errors.rb +0 -24
- data/lib/blather/core/stanza.rb +0 -90
- data/lib/blather/core/stream.rb +0 -179
- data/lib/blather/core/xmpp_node.rb +0 -95
- data/lib/blather/extensions/last_activity.rb +0 -57
- data/lib/blather/extensions/version.rb +0 -85
- data/spec/blather/core/stream_spec.rb +0 -263
@@ -0,0 +1,45 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stanza
|
3
|
+
|
4
|
+
##
|
5
|
+
# Base Presence stanza
|
6
|
+
class Presence < Stanza
|
7
|
+
VALID_TYPES = [:unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe, :error]
|
8
|
+
|
9
|
+
register :presence
|
10
|
+
|
11
|
+
##
|
12
|
+
# Creates a class based on the presence type
|
13
|
+
# either a Status or Subscription object is created based
|
14
|
+
# on the type attribute.
|
15
|
+
# If neither is found it instantiates a Presence object
|
16
|
+
def self.import(node)
|
17
|
+
klass = case node['type']
|
18
|
+
when nil, 'unavailable' then Status
|
19
|
+
when /subscribe/ then Subscription
|
20
|
+
else self
|
21
|
+
end
|
22
|
+
klass.new.inherit(node)
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Ensure element_name is "presence" for all subclasses
|
27
|
+
def initialize
|
28
|
+
super :presence
|
29
|
+
end
|
30
|
+
|
31
|
+
VALID_TYPES.each do |valid_type|
|
32
|
+
define_method("#{valid_type}?") { self.type == valid_type }
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Ensures type is one of :unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe or :error
|
37
|
+
def type=(type)
|
38
|
+
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end #Stanza
|
45
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Blather
|
2
|
+
##
|
3
|
+
# Base XMPP Stanza
|
4
|
+
class Stanza < XMPPNode
|
5
|
+
@@last_id = 0
|
6
|
+
@@handler_list = []
|
7
|
+
|
8
|
+
class_inheritable_array :handler_heirarchy
|
9
|
+
|
10
|
+
##
|
11
|
+
# Registers a callback onto the callback heirarchy stack
|
12
|
+
#
|
13
|
+
# Thanks to help from ActiveSupport every class
|
14
|
+
# that inherits Stanza can register a callback for itself
|
15
|
+
# which is added to a list and iterated over when looking for
|
16
|
+
# a callback to use
|
17
|
+
def self.register(type, name = nil, ns = nil)
|
18
|
+
@@handler_list << type
|
19
|
+
self.handler_heirarchy ||= []
|
20
|
+
self.handler_heirarchy.unshift type
|
21
|
+
|
22
|
+
name = name || self.name || type
|
23
|
+
super name, ns
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.handler_list
|
27
|
+
@@handler_list
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Helper method that creates a unique ID for stanzas
|
32
|
+
def self.next_id
|
33
|
+
@@last_id += 1
|
34
|
+
'blather%04x' % @@last_id
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Creates a new stanza with the same name as the node
|
39
|
+
# then inherits all the node's attributes and properties
|
40
|
+
def self.import(node)
|
41
|
+
self.new(node.element_name).inherit(node)
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Automatically set the stanza's ID
|
46
|
+
# and attach it to a document so XPath searching works
|
47
|
+
def initialize(name = nil)
|
48
|
+
super
|
49
|
+
XML::Document.new.root = self
|
50
|
+
self.name = name.to_s if name
|
51
|
+
self.id = self.class.next_id
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Helper method to ask the object if it's an error
|
56
|
+
def error?
|
57
|
+
self.type == :error
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Copies itself then swaps from and to
|
62
|
+
# then returns the new stanza
|
63
|
+
def reply
|
64
|
+
self.copy(true).reply!
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Swaps from and to
|
69
|
+
def reply!
|
70
|
+
self.to, self.from = self.from, self.to
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
attribute_accessor :id, :to_sym => false
|
75
|
+
|
76
|
+
attribute_writer :to, :from
|
77
|
+
|
78
|
+
##
|
79
|
+
# returns:: JID created from the "to" value of the stanza
|
80
|
+
def to
|
81
|
+
JID.new(attributes[:to]) if attributes[:to]
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# returns:: JID created from the "from" value of the stanza
|
86
|
+
def from
|
87
|
+
JID.new(attributes[:from]) if attributes[:from]
|
88
|
+
end
|
89
|
+
|
90
|
+
attribute_accessor :type
|
91
|
+
|
92
|
+
##
|
93
|
+
# Transform the stanza into a stanza error
|
94
|
+
# <tt>err_name_or_class</tt> can be the name of the error or the error class to use
|
95
|
+
# <tt>type</tt>, <tt>text</tt>, <tt>extras</tt> are the same as for StanzaError#new
|
96
|
+
def as_error(err_name_or_class, type, text = nil, extras = [])
|
97
|
+
klass = (err_name_or_class.is_a?(Class) ? err_name_or_class : StanzaError.class_from_registration(err_name_or_class))
|
98
|
+
klass.new self, type, text, extras
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stream
|
3
|
+
|
4
|
+
class Client < Stream
|
5
|
+
LANG = 'en'
|
6
|
+
VERSION = '1.0'
|
7
|
+
NAMESPACE = 'jabber:client'
|
8
|
+
|
9
|
+
protected
|
10
|
+
def start
|
11
|
+
@parser = Parser.new self
|
12
|
+
start_stream = <<-STREAM
|
13
|
+
<stream:stream
|
14
|
+
to='#{@to}'
|
15
|
+
xmlns='#{NAMESPACE}'
|
16
|
+
xmlns:stream='http://etherx.jabber.org/streams'
|
17
|
+
version='#{VERSION}'
|
18
|
+
xml:lang='#{LANG}'
|
19
|
+
>
|
20
|
+
STREAM
|
21
|
+
send start_stream.gsub(/\s+/, ' ')
|
22
|
+
end
|
23
|
+
end #Client
|
24
|
+
|
25
|
+
end #Stream
|
26
|
+
end #Blather
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Blather
|
2
|
+
class Stream
|
3
|
+
|
4
|
+
class Component < Stream
|
5
|
+
NAMESPACE = 'jabber:component:accept'
|
6
|
+
|
7
|
+
def receive(node) # :nodoc:
|
8
|
+
if node.element_name == 'handshake'
|
9
|
+
@client.stream_started(self)
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
if node.element_name == 'stream:stream'
|
15
|
+
send("<handshake>#{Digest::SHA1.hexdigest(@node['id']+@pass)}</handshake>")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def start
|
21
|
+
@parser = Parser.new self
|
22
|
+
start_stream = <<-STREAM
|
23
|
+
<stream:stream
|
24
|
+
to='#{@jid}'
|
25
|
+
xmlns='#{NAMESPACE}'
|
26
|
+
xmlns:stream='http://etherx.jabber.org/streams'
|
27
|
+
>
|
28
|
+
STREAM
|
29
|
+
send start_stream.gsub(/\s+/, ' ')
|
30
|
+
end
|
31
|
+
end #Client
|
32
|
+
|
33
|
+
end #Stream
|
34
|
+
end #Blather
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class Parser # :nodoc:
|
5
|
+
STREAM_REGEX = %r{(/)?stream:stream}.freeze
|
6
|
+
ERROR_REGEX = /^<(stream:[a-z]+)/.freeze
|
7
|
+
|
8
|
+
@@debug = !false
|
9
|
+
def self.debug; @@debug; end
|
10
|
+
def self.debug=(debug); @@debug = debug; end
|
11
|
+
|
12
|
+
include XML::SaxParser::Callbacks
|
13
|
+
|
14
|
+
def initialize(receiver)
|
15
|
+
@receiver = receiver
|
16
|
+
@current = nil
|
17
|
+
@parser = XML::SaxPushParser.new self
|
18
|
+
end
|
19
|
+
|
20
|
+
def receive_data(string)
|
21
|
+
LOG.debug "PARSING: (#{string})" if @@debug
|
22
|
+
@parser.receive string
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_start_element_ns(elem, attrs, prefix, uri, namespaces)
|
26
|
+
LOG.debug "START ELEM: (#{{:elem => elem, :attrs => attrs, :prefix => prefix, :uri => uri, :ns => namespaces}.inspect})" if @@debug
|
27
|
+
elem = "#{"#{prefix}:" if prefix}#{elem}"
|
28
|
+
e = XMPPNode.new elem
|
29
|
+
XML::Namespace.new(e, prefix, uri)
|
30
|
+
attrs.each { |k,v| e.attributes[k] = v if k }
|
31
|
+
|
32
|
+
if elem == 'stream:stream'
|
33
|
+
@receiver.receive e
|
34
|
+
|
35
|
+
elsif !@receiver.stopped?
|
36
|
+
@current << e if @current
|
37
|
+
@current = e
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_characters(chars = '')
|
43
|
+
LOG.debug "CHARS: #{chars}" if @@debug
|
44
|
+
@current << XML::Node.new_text(chars) if @current
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_end_element_ns(elem, prefix, uri)
|
48
|
+
LOG.debug "END ELEM: #{{:elem => elem, :prefix => prefix, :uri => uri}.inspect}" if @@debug
|
49
|
+
|
50
|
+
if !@current && "#{prefix}:#{elem}" =~ STREAM_REGEX
|
51
|
+
@receiver.receive XMPPNode.new('stream:end')
|
52
|
+
|
53
|
+
elsif @current.parent?
|
54
|
+
@current = @current.parent
|
55
|
+
|
56
|
+
else
|
57
|
+
c, @current = @current, nil
|
58
|
+
XML::Document.new.root = c
|
59
|
+
@receiver.receive c
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_error(msg)
|
65
|
+
raise ParseError.new(msg.to_s)
|
66
|
+
end
|
67
|
+
end #Parser
|
68
|
+
|
69
|
+
end #Stream
|
70
|
+
end #Blather
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class Resource < StreamHandler # :nodoc:
|
5
|
+
def initialize(stream, jid)
|
6
|
+
super stream
|
7
|
+
@jid = jid
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# Respond to the bind request
|
13
|
+
# If @jid has a resource set already request it from the server
|
14
|
+
def bind
|
15
|
+
response = Stanza::Iq.new :set
|
16
|
+
@id = response.id
|
17
|
+
|
18
|
+
binder = XMPPNode.new('bind')
|
19
|
+
binder.namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
|
20
|
+
|
21
|
+
binder << XMPPNode.new('resource', @jid.resource) if @jid.resource
|
22
|
+
|
23
|
+
response << binder
|
24
|
+
@stream.send response
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Process the result from the server
|
29
|
+
# Sets the sends the JID (now bound to a resource)
|
30
|
+
# back to the stream
|
31
|
+
def result
|
32
|
+
LOG.debug "RESOURE NODE #{@node}"
|
33
|
+
# ensure this is a response to our original request
|
34
|
+
if @id == @node['id']
|
35
|
+
@jid = JID.new @node.find_first('bind/jid').content
|
36
|
+
success @jid
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Server returned an error
|
42
|
+
def error
|
43
|
+
failure StanzaError.import(@node)
|
44
|
+
end
|
45
|
+
end #Resource
|
46
|
+
|
47
|
+
end #Stream
|
48
|
+
end #Blather
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class SASL < StreamHandler # :nodoc:
|
5
|
+
class UnknownMechanism < BlatherError
|
6
|
+
handler_heirarchy ||= []
|
7
|
+
handler_heirarchy << :unknown_mechanism
|
8
|
+
end
|
9
|
+
|
10
|
+
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
|
11
|
+
|
12
|
+
def initialize(stream, jid, pass = nil)
|
13
|
+
super stream
|
14
|
+
@jid = jid
|
15
|
+
@pass = pass
|
16
|
+
@mechanism_idx = 0
|
17
|
+
@mechanisms = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_mechanism
|
21
|
+
mod = case (mechanism = @mechanisms[@mechanism_idx].content)
|
22
|
+
when 'DIGEST-MD5' then DigestMD5
|
23
|
+
when 'PLAIN' then Plain
|
24
|
+
when 'ANONYMOUS' then Anonymous
|
25
|
+
else
|
26
|
+
# Send a failure node and kill the stream
|
27
|
+
@stream.send "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><invalid-mechanism/></failure>"
|
28
|
+
@failure.call UnknownMechanism.new("Unknown SASL mechanism (#{mechanism})")
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
|
32
|
+
extend mod
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Handle incoming nodes
|
38
|
+
# Cycle through possible mechanisms until we either
|
39
|
+
# run out of them or none work
|
40
|
+
def handle(node)
|
41
|
+
if node.element_name == 'failure'
|
42
|
+
if @mechanisms[@mechanism_idx += 1]
|
43
|
+
set_mechanism
|
44
|
+
authenticate
|
45
|
+
else
|
46
|
+
failure node
|
47
|
+
end
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def failure(node = nil)
|
55
|
+
@failure.call SASLError.import(node)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Base64 Encoder
|
60
|
+
def b64(str)
|
61
|
+
[str].pack('m').gsub(/\s/,'')
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Builds a standard auth node
|
66
|
+
def auth_node(mechanism, content = nil)
|
67
|
+
node = XMPPNode.new 'auth', content
|
68
|
+
node['xmlns'] = SASL_NS
|
69
|
+
node['mechanism'] = mechanism
|
70
|
+
node
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Respond to the <mechanisms> node sent by the server
|
75
|
+
def mechanisms
|
76
|
+
@mechanisms = @node.children
|
77
|
+
authenticate if set_mechanism
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Digest MD5 authentication
|
82
|
+
module DigestMD5 # :nodoc:
|
83
|
+
##
|
84
|
+
# Lets the server know we're going to try DigestMD5 authentication
|
85
|
+
def authenticate
|
86
|
+
@stream.send auth_node('DIGEST-MD5')
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Receive the challenge command.
|
91
|
+
def challenge
|
92
|
+
decode_challenge
|
93
|
+
respond
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
##
|
98
|
+
# Decodes digest strings 'foo=bar,baz="faz"'
|
99
|
+
# into {'foo' => 'bar', 'baz' => 'faz'}
|
100
|
+
def decode_challenge
|
101
|
+
text = @node.content.unpack('m').first
|
102
|
+
res = {}
|
103
|
+
|
104
|
+
text.split(',').each do |statement|
|
105
|
+
key, value = statement.split('=')
|
106
|
+
res[key] = value.delete('"') unless key.empty?
|
107
|
+
end
|
108
|
+
LOG.debug "CHALLENGE DECODE: #{res.inspect}"
|
109
|
+
|
110
|
+
@nonce ||= res['nonce']
|
111
|
+
@realm ||= res['realm']
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Builds the properly encoded challenge response
|
116
|
+
def generate_response
|
117
|
+
a1 = "#{d("#{@response[:username]}:#{@response[:realm]}:#{@pass}")}:#{@response[:nonce]}:#{@response[:cnonce]}"
|
118
|
+
a2 = "AUTHENTICATE:#{@response[:'digest-uri']}"
|
119
|
+
h("#{h(a1)}:#{@response[:nonce]}:#{@response[:nc]}:#{@response[:cnonce]}:#{@response[:qop]}:#{h(a2)}")
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Send challenge response
|
124
|
+
def respond
|
125
|
+
node = XMPPNode.new 'response'
|
126
|
+
node['xmlns'] = SASL_NS
|
127
|
+
|
128
|
+
unless @initial_response_sent
|
129
|
+
@initial_response_sent = true
|
130
|
+
@response = {
|
131
|
+
:nonce => @nonce,
|
132
|
+
:charset => 'utf-8',
|
133
|
+
:username => @jid.node,
|
134
|
+
:realm => @realm || @jid.domain,
|
135
|
+
:cnonce => h(Time.new.to_f.to_s),
|
136
|
+
:nc => '00000001',
|
137
|
+
:qop => 'auth',
|
138
|
+
:'digest-uri' => "xmpp/#{@jid.domain}",
|
139
|
+
}
|
140
|
+
@response[:response] = generate_response
|
141
|
+
@response.each { |k,v| @response[k] = "\"#{v}\"" unless [:nc, :qop, :response, :charset].include?(k) }
|
142
|
+
|
143
|
+
LOG.debug "CHALLENGE RESPOSNE: #{@response.inspect}"
|
144
|
+
LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
|
145
|
+
|
146
|
+
# order is to simplify testing
|
147
|
+
# Ruby 1.9 eliminates the need for this with ordered hashes
|
148
|
+
order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri', :response]
|
149
|
+
node.content = b64(order.map { |k| v = @response[k]; "#{k}=#{v}" } * ',')
|
150
|
+
end
|
151
|
+
|
152
|
+
@stream.send node
|
153
|
+
end
|
154
|
+
|
155
|
+
def d(s); Digest::MD5.digest(s); end
|
156
|
+
def h(s); Digest::MD5.hexdigest(s); end
|
157
|
+
end #DigestMD5
|
158
|
+
|
159
|
+
module Plain # :nodoc:
|
160
|
+
def authenticate
|
161
|
+
@stream.send auth_node('PLAIN', b64("#{@jid.stripped}\x00#{@jid.node}\x00#{@pass}"))
|
162
|
+
end
|
163
|
+
end #Plain
|
164
|
+
|
165
|
+
module Anonymous # :nodoc:
|
166
|
+
def authenticate
|
167
|
+
@stream.send auth_node('ANONYMOUS', b64(@jid.node))
|
168
|
+
end
|
169
|
+
end #Anonymous
|
170
|
+
end #SASL
|
171
|
+
|
172
|
+
end #Stream
|
173
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class Session < StreamHandler # :nodoc:
|
5
|
+
def initialize(stream, to)
|
6
|
+
super stream
|
7
|
+
@to = to
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# Send a start session command
|
13
|
+
def session
|
14
|
+
response = Stanza::Iq.new :set
|
15
|
+
response.to = @to
|
16
|
+
sess = XMPPNode.new 'session'
|
17
|
+
sess['xmlns'] = 'urn:ietf:params:xml:ns:xmpp-session'
|
18
|
+
response << sess
|
19
|
+
@stream.send response
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# The server should respond with a <result> node if all is well
|
24
|
+
def result
|
25
|
+
success
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Server returned an error.
|
30
|
+
def error
|
31
|
+
failure StanzaError.import(@node)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
class StreamHandler # :nodoc:
|
5
|
+
def on_success(&block); @success = block; end
|
6
|
+
def on_failure(&block); @failure = block; end
|
7
|
+
|
8
|
+
def initialize(stream)
|
9
|
+
@stream = stream
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle(node)
|
13
|
+
@node = node
|
14
|
+
method = @node.element_name == 'iq' ? @node['type'] : @node.element_name
|
15
|
+
if self.respond_to?(method, true)
|
16
|
+
self.__send__ method
|
17
|
+
else
|
18
|
+
@failure.call UnknownResponse.new(@node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
##
|
24
|
+
# Handle error response from the server
|
25
|
+
def error
|
26
|
+
failure
|
27
|
+
end
|
28
|
+
|
29
|
+
def success(message_back = nil)
|
30
|
+
@success.call message_back
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure(err = nil)
|
34
|
+
@failure.call err
|
35
|
+
end
|
36
|
+
end #StreamHandler
|
37
|
+
|
38
|
+
end #Stream
|
39
|
+
end #Blather
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Blather # :nodoc:
|
2
|
+
class Stream # :nodoc:
|
3
|
+
|
4
|
+
# TLS negotiation invovles 3 node types:
|
5
|
+
# * starttls -- Server asking for TLS to be started
|
6
|
+
# * proceed -- Server saying it's ready for a TLS connection to be started
|
7
|
+
# * failure -- Failed TLS negotiation. Failure results in a closed connection.
|
8
|
+
# so there's no message to pass back to the tream
|
9
|
+
class TLS < StreamHandler # :nodoc:
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# After receiving <starttls> from the server send one
|
13
|
+
# back to let it know we're ready to start TLS
|
14
|
+
def starttls
|
15
|
+
@stream.send "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Server's ready for TLS, so start it up
|
20
|
+
def proceed
|
21
|
+
@stream.start_tls
|
22
|
+
success
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Negotiations failed
|
27
|
+
def failure
|
28
|
+
super StreamError::TLSFailure.new
|
29
|
+
end
|
30
|
+
end #TLS
|
31
|
+
|
32
|
+
end #Stream
|
33
|
+
end #Blather
|