blather 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +2 -0
- data/README.rdoc +54 -29
- data/Rakefile +94 -13
- data/VERSION.yml +4 -0
- data/examples/drb_client.rb +2 -4
- data/examples/echo.rb +13 -8
- data/examples/pubsub/cli.rb +64 -0
- data/examples/pubsub/ping_pong.rb +18 -0
- data/examples/pubsub/pubsub_dsl.rb +52 -0
- data/examples/pubsub_client.rb +39 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/ext/extconf.rb +65 -0
- data/lib/blather.rb +18 -121
- data/lib/blather/client.rb +13 -0
- data/lib/blather/client/client.rb +165 -0
- data/lib/blather/client/dsl.rb +99 -0
- data/lib/blather/client/pubsub.rb +53 -0
- data/lib/blather/client/pubsub/node.rb +27 -0
- data/lib/blather/core_ext/active_support.rb +1 -0
- data/lib/blather/core_ext/libxml.rb +7 -1
- data/lib/blather/errors.rb +39 -18
- 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/jid.rb +9 -16
- data/lib/blather/roster.rb +9 -0
- data/lib/blather/roster_item.rb +7 -4
- data/lib/blather/stanza.rb +19 -25
- data/lib/blather/stanza/disco.rb +9 -0
- data/lib/blather/stanza/disco/disco_info.rb +84 -0
- data/lib/blather/stanza/disco/disco_items.rb +59 -0
- data/lib/blather/stanza/iq.rb +16 -4
- data/lib/blather/stanza/iq/query.rb +6 -4
- data/lib/blather/stanza/iq/roster.rb +38 -38
- data/lib/blather/stanza/pubsub.rb +33 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +52 -0
- data/lib/blather/stanza/pubsub/errors.rb +9 -0
- data/lib/blather/stanza/pubsub/event.rb +21 -0
- data/lib/blather/stanza/pubsub/items.rb +59 -0
- data/lib/blather/stanza/pubsub/owner.rb +9 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +57 -0
- data/lib/blather/stream.rb +125 -57
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +17 -27
- data/lib/blather/stream/resource.rb +21 -24
- data/lib/blather/stream/sasl.rb +60 -37
- data/lib/blather/stream/session.rb +12 -19
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +22 -18
- data/lib/blather/xmpp_node.rb +91 -17
- 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/jid_spec.rb +0 -7
- data/spec/blather/roster_item_spec.rb +5 -0
- data/spec/blather/roster_spec.rb +6 -6
- data/spec/blather/stanza/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +9 -2
- data/spec/blather/stanza/iq/roster_spec.rb +117 -1
- data/spec/blather/stanza/iq_spec.rb +29 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +12 -1
- data/spec/blather/stanza/presence_spec.rb +29 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +46 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +63 -0
- data/spec/blather/stanza/pubsub_spec.rb +26 -0
- data/spec/blather/stanza_spec.rb +13 -1
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/xmpp_node_spec.rb +75 -22
- data/spec/fixtures/pubsub.rb +157 -0
- data/spec/spec_helper.rb +6 -14
- metadata +86 -74
- data/CHANGELOG +0 -5
- data/Manifest +0 -47
- data/blather.gemspec +0 -41
- data/lib/blather/stanza/error.rb +0 -31
- data/spec/blather/stream_spec.rb +0 -462
- data/spec/build_safe.rb +0 -20
data/lib/blather/stream/sasl.rb
CHANGED
@@ -1,66 +1,68 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
|
-
|
2
|
+
class Stream # :nodoc:
|
3
3
|
|
4
|
-
class SASL # :nodoc:
|
5
|
-
class UnknownMechanism < BlatherError
|
4
|
+
class SASL < StreamHandler # :nodoc:
|
5
|
+
class UnknownMechanism < BlatherError
|
6
|
+
handler_heirarchy ||= []
|
7
|
+
handler_heirarchy << :unknown_mechanism
|
8
|
+
end
|
6
9
|
|
7
|
-
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
10
|
+
SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'.freeze
|
8
11
|
|
9
12
|
def initialize(stream, jid, pass = nil)
|
10
|
-
|
13
|
+
super stream
|
11
14
|
@jid = jid
|
12
15
|
@pass = pass
|
13
|
-
@
|
14
|
-
@mechanism = 0
|
16
|
+
@mechanism_idx = 0
|
15
17
|
@mechanisms = []
|
16
|
-
|
17
|
-
init_callbacks
|
18
|
-
end
|
19
|
-
|
20
|
-
def init_callbacks
|
21
|
-
@callbacks['mechanisms'] = proc {
|
22
|
-
@mechanisms = @node.children
|
23
|
-
set_mechanism
|
24
|
-
authenticate
|
25
|
-
}
|
26
18
|
end
|
27
19
|
|
28
20
|
def set_mechanism
|
29
|
-
mod = case (mechanism = @mechanisms[@
|
21
|
+
mod = case (mechanism = @mechanisms[@mechanism_idx].content)
|
30
22
|
when 'DIGEST-MD5' then DigestMD5
|
31
23
|
when 'PLAIN' then Plain
|
32
24
|
when 'ANONYMOUS' then Anonymous
|
33
25
|
else
|
26
|
+
# Send a failure node and kill the stream
|
34
27
|
@stream.send "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><invalid-mechanism/></failure>"
|
35
|
-
|
28
|
+
@failure.call UnknownMechanism.new("Unknown SASL mechanism (#{mechanism})")
|
29
|
+
return false
|
36
30
|
end
|
37
31
|
|
38
32
|
extend mod
|
33
|
+
true
|
39
34
|
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
46
48
|
else
|
47
|
-
|
49
|
+
super
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
def failure(&callback)
|
56
|
-
@callbacks['failure'] = callback
|
53
|
+
protected
|
54
|
+
def failure(node = nil)
|
55
|
+
@failure.call SASLError.import(node)
|
57
56
|
end
|
58
57
|
|
59
|
-
|
58
|
+
##
|
59
|
+
# Base64 Encoder
|
60
60
|
def b64(str)
|
61
61
|
[str].pack('m').gsub(/\s/,'')
|
62
62
|
end
|
63
63
|
|
64
|
+
##
|
65
|
+
# Builds a standard auth node
|
64
66
|
def auth_node(mechanism, content = nil)
|
65
67
|
node = XMPPNode.new 'auth', content
|
66
68
|
node['xmlns'] = SASL_NS
|
@@ -68,16 +70,33 @@ module Stream # :nodoc:
|
|
68
70
|
node
|
69
71
|
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
75
79
|
|
80
|
+
##
|
81
|
+
# Digest MD5 authentication
|
82
|
+
module DigestMD5 # :nodoc:
|
83
|
+
##
|
84
|
+
# Lets the server know we're going to try DigestMD5 authentication
|
76
85
|
def authenticate
|
77
86
|
@stream.send auth_node('DIGEST-MD5')
|
78
87
|
end
|
79
88
|
|
89
|
+
##
|
90
|
+
# Receive the challenge command.
|
91
|
+
def challenge
|
92
|
+
decode_challenge
|
93
|
+
respond
|
94
|
+
end
|
95
|
+
|
80
96
|
private
|
97
|
+
##
|
98
|
+
# Decodes digest strings 'foo=bar,baz="faz"'
|
99
|
+
# into {'foo' => 'bar', 'baz' => 'faz'}
|
81
100
|
def decode_challenge
|
82
101
|
text = @node.content.unpack('m').first
|
83
102
|
res = {}
|
@@ -92,12 +111,16 @@ module Stream # :nodoc:
|
|
92
111
|
@realm ||= res['realm']
|
93
112
|
end
|
94
113
|
|
114
|
+
##
|
115
|
+
# Builds the properly encoded challenge response
|
95
116
|
def generate_response
|
96
117
|
a1 = "#{d("#{@response[:username]}:#{@response[:realm]}:#{@pass}")}:#{@response[:nonce]}:#{@response[:cnonce]}"
|
97
118
|
a2 = "AUTHENTICATE:#{@response[:'digest-uri']}"
|
98
119
|
h("#{h(a1)}:#{@response[:nonce]}:#{@response[:nc]}:#{@response[:cnonce]}:#{@response[:qop]}:#{h(a2)}")
|
99
120
|
end
|
100
121
|
|
122
|
+
##
|
123
|
+
# Send challenge response
|
101
124
|
def respond
|
102
125
|
node = XMPPNode.new 'response'
|
103
126
|
node['xmlns'] = SASL_NS
|
@@ -121,6 +144,7 @@ module Stream # :nodoc:
|
|
121
144
|
LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
|
122
145
|
|
123
146
|
# order is to simplify testing
|
147
|
+
# Ruby 1.9 eliminates the need for this with ordered hashes
|
124
148
|
order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri', :response]
|
125
149
|
node.content = b64(order.map { |k| v = @response[k]; "#{k}=#{v}" } * ',')
|
126
150
|
end
|
@@ -143,7 +167,6 @@ module Stream # :nodoc:
|
|
143
167
|
@stream.send auth_node('ANONYMOUS', b64(@jid.node))
|
144
168
|
end
|
145
169
|
end #Anonymous
|
146
|
-
|
147
170
|
end #SASL
|
148
171
|
|
149
172
|
end #Stream
|
@@ -1,26 +1,15 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
|
-
|
2
|
+
class Stream # :nodoc:
|
3
3
|
|
4
|
-
class Session # :nodoc:
|
4
|
+
class Session < StreamHandler # :nodoc:
|
5
5
|
def initialize(stream, to)
|
6
|
-
|
6
|
+
super stream
|
7
7
|
@to = to
|
8
|
-
@callbacks = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def success(&callback)
|
12
|
-
@callbacks[:success] = callback
|
13
|
-
end
|
14
|
-
|
15
|
-
def failure(&callback)
|
16
|
-
@callbacks[:failure] = callback
|
17
|
-
end
|
18
|
-
|
19
|
-
def receive(node)
|
20
|
-
@node = node
|
21
|
-
__send__(@node.element_name == 'iq' ? @node['type'] : @node.element_name)
|
22
8
|
end
|
23
9
|
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# Send a start session command
|
24
13
|
def session
|
25
14
|
response = Stanza::Iq.new :set
|
26
15
|
response.to = @to
|
@@ -30,12 +19,16 @@ module Stream # :nodoc:
|
|
30
19
|
@stream.send response
|
31
20
|
end
|
32
21
|
|
22
|
+
##
|
23
|
+
# The server should respond with a <result> node if all is well
|
33
24
|
def result
|
34
|
-
|
25
|
+
success
|
35
26
|
end
|
36
27
|
|
28
|
+
##
|
29
|
+
# Server returned an error.
|
37
30
|
def error
|
38
|
-
|
31
|
+
failure StanzaError.import(@node)
|
39
32
|
end
|
40
33
|
end
|
41
34
|
|
@@ -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
|
data/lib/blather/stream/tls.rb
CHANGED
@@ -1,27 +1,31 @@
|
|
1
1
|
module Blather # :nodoc:
|
2
|
-
|
2
|
+
class Stream # :nodoc:
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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'/>"
|
13
16
|
end
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
##
|
19
|
+
# Server's ready for TLS, so start it up
|
20
|
+
def proceed
|
21
|
+
@stream.start_tls
|
22
|
+
success
|
17
23
|
end
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def receive(node)
|
24
|
-
@callbacks[node.element_name].call if @callbacks[node.element_name]
|
25
|
+
##
|
26
|
+
# Negotiations failed
|
27
|
+
def failure
|
28
|
+
super StreamError::TLSFailure.new
|
25
29
|
end
|
26
30
|
end #TLS
|
27
31
|
|
data/lib/blather/xmpp_node.rb
CHANGED
@@ -5,9 +5,11 @@ module Blather
|
|
5
5
|
# All XML classes subclass XMPPNode
|
6
6
|
# it allows the addition of helpers
|
7
7
|
class XMPPNode < XML::Node
|
8
|
+
BASE_NAMES = %w[presence message iq].freeze
|
9
|
+
|
8
10
|
@@registrations = {}
|
9
11
|
|
10
|
-
class_inheritable_accessor :
|
12
|
+
class_inheritable_accessor :ns,
|
11
13
|
:name
|
12
14
|
|
13
15
|
##
|
@@ -16,10 +18,10 @@ module Blather
|
|
16
18
|
# This registers a namespace that is used when looking
|
17
19
|
# up the class name of the object to instantiate when a new
|
18
20
|
# stanza is received
|
19
|
-
def self.register(name,
|
21
|
+
def self.register(name, ns = nil)
|
20
22
|
self.name = name.to_s
|
21
|
-
self.
|
22
|
-
@@registrations[[self.name, self.
|
23
|
+
self.ns = ns
|
24
|
+
@@registrations[[self.name, self.ns]] = self
|
23
25
|
end
|
24
26
|
|
25
27
|
##
|
@@ -34,7 +36,7 @@ module Blather
|
|
34
36
|
# of that class and imports all the <tt>node</tt>'s attributes
|
35
37
|
# and children into it.
|
36
38
|
def self.import(node)
|
37
|
-
klass = class_from_registration(node.element_name, node.
|
39
|
+
klass = class_from_registration(node.element_name, node.namespace)
|
38
40
|
if klass && klass != self
|
39
41
|
klass.import(node)
|
40
42
|
else
|
@@ -42,6 +44,72 @@ module Blather
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
47
|
+
##
|
48
|
+
# Provides an attribute reader helper. Default behavior is to
|
49
|
+
# conver the values of the attribute into a symbol. This can
|
50
|
+
# be turned off by passing <tt>:to_sym => false</tt>
|
51
|
+
#
|
52
|
+
# class Node
|
53
|
+
# attribute_reader :type
|
54
|
+
# attribute_reader :name, :to_sym => false
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# n = Node.new
|
58
|
+
# n.attributes[:type] = 'foo'
|
59
|
+
# n.type == :foo
|
60
|
+
# n.attributes[:name] = 'bar'
|
61
|
+
# n.name == 'bar'
|
62
|
+
def self.attribute_reader(*syms)
|
63
|
+
opts = syms.last.is_a?(Hash) ? syms.pop : {}
|
64
|
+
syms.flatten.each do |sym|
|
65
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
66
|
+
def #{sym}
|
67
|
+
attributes[:#{sym}]#{".to_sym unless attributes[:#{sym}].blank?" unless opts[:to_sym] == false}
|
68
|
+
end
|
69
|
+
END
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Provides an attribute writer helper.
|
75
|
+
#
|
76
|
+
# class Node
|
77
|
+
# attribute_writer :type
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# n = Node.new
|
81
|
+
# n.type = 'foo'
|
82
|
+
# n.attributes[:type] == 'foo'
|
83
|
+
def self.attribute_writer(*syms)
|
84
|
+
syms.flatten.each do |sym|
|
85
|
+
next if sym.is_a?(Hash)
|
86
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
87
|
+
def #{sym}=(value)
|
88
|
+
attributes[:#{sym}] = value
|
89
|
+
end
|
90
|
+
END
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Provides an attribute accessor helper combining
|
96
|
+
# <tt>attribute_reader</tt> and <tt>attribute_writer</tt>
|
97
|
+
#
|
98
|
+
# class Node
|
99
|
+
# attribute_accessor :type
|
100
|
+
# attribute_accessor :name, :to_sym => false
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# n = Node.new
|
104
|
+
# n.type = 'foo'
|
105
|
+
# n.type == :foo
|
106
|
+
# n.name = 'bar'
|
107
|
+
# n.name == 'bar'
|
108
|
+
def self.attribute_accessor(*syms)
|
109
|
+
attribute_reader *syms
|
110
|
+
attribute_writer *syms
|
111
|
+
end
|
112
|
+
|
45
113
|
##
|
46
114
|
# Automatically sets the namespace registered by the subclass
|
47
115
|
def initialize(name = nil, content = nil)
|
@@ -49,7 +117,7 @@ module Blather
|
|
49
117
|
content = content.to_s if content
|
50
118
|
|
51
119
|
super name.to_s, content
|
52
|
-
self.
|
120
|
+
self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s)
|
53
121
|
end
|
54
122
|
|
55
123
|
##
|
@@ -58,19 +126,22 @@ module Blather
|
|
58
126
|
self.class.import self
|
59
127
|
end
|
60
128
|
|
61
|
-
def
|
62
|
-
|
129
|
+
def namespace=(ns)
|
130
|
+
if ns
|
131
|
+
ns = {nil => ns} unless ns.is_a?(Hash)
|
132
|
+
ns.each { |n| XML::Namespace.new self, *n }
|
133
|
+
end
|
63
134
|
end
|
64
135
|
|
65
|
-
def
|
66
|
-
|
136
|
+
def namespace(prefix = nil)
|
137
|
+
(ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil
|
67
138
|
end
|
68
139
|
|
69
140
|
##
|
70
141
|
# Remove a child with the name and (optionally) namespace given
|
71
142
|
def remove_child(name, ns = nil)
|
72
143
|
name = name.to_s
|
73
|
-
self.
|
144
|
+
self.detect { |n| n.remove! if n.element_name == name && (!ns || n.namespace == ns) }
|
74
145
|
end
|
75
146
|
|
76
147
|
##
|
@@ -90,7 +161,9 @@ module Blather
|
|
90
161
|
##
|
91
162
|
# Create a copy
|
92
163
|
def copy(deep = true)
|
93
|
-
self.class.new
|
164
|
+
copy = self.class.new.inherit(self)
|
165
|
+
copy.element_name = self.element_name
|
166
|
+
copy
|
94
167
|
end
|
95
168
|
|
96
169
|
##
|
@@ -104,21 +177,22 @@ module Blather
|
|
104
177
|
##
|
105
178
|
# Inherit only <tt>stanza</tt>'s attributes
|
106
179
|
def inherit_attrs(attrs)
|
107
|
-
attrs.each { |a|
|
180
|
+
attrs.each { |a| attributes[a.name] = a.value }
|
108
181
|
self
|
109
182
|
end
|
110
183
|
|
111
184
|
##
|
112
|
-
# Turn itself into
|
113
|
-
def
|
185
|
+
# Turn itself into an XML string and remove all whitespace between nodes
|
186
|
+
def to_xml
|
114
187
|
# TODO: Fix this for HTML nodes (and any other that might require whitespace)
|
115
|
-
|
188
|
+
to_s.gsub(">\n<", '><')
|
116
189
|
end
|
117
190
|
|
118
191
|
##
|
119
192
|
# Override #find to work when a node isn't attached to a document
|
120
193
|
def find(what, nslist = nil)
|
121
|
-
|
194
|
+
what = what.to_s
|
195
|
+
(self.doc ? super(what, nslist) : select { |i| i.element_name == what })
|
122
196
|
end
|
123
197
|
end #XMPPNode
|
124
198
|
|