sprsquish-blather 0.3.4 → 0.4.0

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.
Files changed (110) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +41 -12
  3. data/examples/echo.rb +1 -1
  4. data/examples/execute.rb +0 -5
  5. data/examples/pubsub/cli.rb +64 -0
  6. data/examples/pubsub/ping_pong.rb +18 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/xmpp4r/echo.rb +35 -0
  9. data/lib/blather/client/client.rb +19 -13
  10. data/lib/blather/client/dsl/pubsub.rb +133 -0
  11. data/lib/blather/client/dsl.rb +16 -0
  12. data/lib/blather/client.rb +1 -1
  13. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +117 -0
  14. data/lib/blather/core_ext/active_support.rb +1 -117
  15. data/lib/blather/core_ext/nokogiri.rb +35 -0
  16. data/lib/blather/errors/sasl_error.rb +3 -1
  17. data/lib/blather/errors/stanza_error.rb +10 -17
  18. data/lib/blather/errors/stream_error.rb +11 -14
  19. data/lib/blather/errors.rb +3 -20
  20. data/lib/blather/jid.rb +1 -0
  21. data/lib/blather/roster.rb +9 -0
  22. data/lib/blather/roster_item.rb +6 -1
  23. data/lib/blather/stanza/disco/disco_info.rb +45 -33
  24. data/lib/blather/stanza/disco/disco_items.rb +32 -21
  25. data/lib/blather/stanza/disco.rb +7 -1
  26. data/lib/blather/stanza/iq/query.rb +16 -8
  27. data/lib/blather/stanza/iq/roster.rb +33 -22
  28. data/lib/blather/stanza/iq.rb +13 -8
  29. data/lib/blather/stanza/message.rb +20 -31
  30. data/lib/blather/stanza/presence/status.rb +13 -21
  31. data/lib/blather/stanza/presence/subscription.rb +11 -16
  32. data/lib/blather/stanza/presence.rb +3 -5
  33. data/lib/blather/stanza/pubsub/affiliations.rb +50 -0
  34. data/lib/blather/stanza/pubsub/create.rb +43 -0
  35. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  36. data/lib/blather/stanza/pubsub/event.rb +77 -0
  37. data/lib/blather/stanza/pubsub/items.rb +63 -0
  38. data/lib/blather/stanza/pubsub/publish.rb +58 -0
  39. data/lib/blather/stanza/pubsub/retract.rb +53 -0
  40. data/lib/blather/stanza/pubsub/subscribe.rb +42 -0
  41. data/lib/blather/stanza/pubsub/subscription.rb +66 -0
  42. data/lib/blather/stanza/pubsub/subscriptions.rb +55 -0
  43. data/lib/blather/stanza/pubsub/unsubscribe.rb +42 -0
  44. data/lib/blather/stanza/pubsub.rb +63 -0
  45. data/lib/blather/stanza/pubsub_owner/delete.rb +34 -0
  46. data/lib/blather/stanza/pubsub_owner/purge.rb +34 -0
  47. data/lib/blather/stanza/pubsub_owner.rb +41 -0
  48. data/lib/blather/stanza.rb +35 -18
  49. data/lib/blather/stream/client.rb +1 -2
  50. data/lib/blather/stream/component.rb +9 -5
  51. data/lib/blather/stream/features/resource.rb +63 -0
  52. data/lib/blather/stream/{sasl.rb → features/sasl.rb} +53 -52
  53. data/lib/blather/stream/features/session.rb +44 -0
  54. data/lib/blather/stream/features/tls.rb +28 -0
  55. data/lib/blather/stream/features.rb +53 -0
  56. data/lib/blather/stream/parser.rb +70 -46
  57. data/lib/blather/stream.rb +76 -168
  58. data/lib/blather/xmpp_node.rb +113 -52
  59. data/lib/blather.rb +35 -12
  60. data/spec/blather/client/client_spec.rb +44 -58
  61. data/spec/blather/client/dsl/pubsub_spec.rb +465 -0
  62. data/spec/blather/client/dsl_spec.rb +19 -6
  63. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  64. data/spec/blather/errors/sasl_error_spec.rb +8 -8
  65. data/spec/blather/errors/stanza_error_spec.rb +25 -33
  66. data/spec/blather/errors/stream_error_spec.rb +21 -16
  67. data/spec/blather/errors_spec.rb +4 -11
  68. data/spec/blather/jid_spec.rb +31 -30
  69. data/spec/blather/roster_item_spec.rb +34 -23
  70. data/spec/blather/roster_spec.rb +27 -12
  71. data/spec/blather/stanza/discos/disco_info_spec.rb +61 -42
  72. data/spec/blather/stanza/discos/disco_items_spec.rb +47 -35
  73. data/spec/blather/stanza/iq/query_spec.rb +34 -11
  74. data/spec/blather/stanza/iq/roster_spec.rb +47 -30
  75. data/spec/blather/stanza/iq_spec.rb +19 -14
  76. data/spec/blather/stanza/message_spec.rb +30 -17
  77. data/spec/blather/stanza/presence/status_spec.rb +43 -20
  78. data/spec/blather/stanza/presence/subscription_spec.rb +41 -21
  79. data/spec/blather/stanza/presence_spec.rb +34 -21
  80. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  81. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  82. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  83. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  84. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  85. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  86. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  87. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  88. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  89. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  90. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  92. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  93. data/spec/blather/stanza/pubsub_spec.rb +62 -0
  94. data/spec/blather/stanza_spec.rb +53 -38
  95. data/spec/blather/stream/client_spec.rb +231 -88
  96. data/spec/blather/stream/component_spec.rb +14 -5
  97. data/spec/blather/stream/parser_spec.rb +145 -0
  98. data/spec/blather/xmpp_node_spec.rb +192 -96
  99. data/spec/fixtures/pubsub.rb +311 -0
  100. data/spec/spec_helper.rb +5 -4
  101. metadata +53 -17
  102. data/Rakefile +0 -139
  103. data/ext/extconf.rb +0 -65
  104. data/ext/push_parser.c +0 -209
  105. data/lib/blather/core_ext/libxml.rb +0 -28
  106. data/lib/blather/stream/resource.rb +0 -48
  107. data/lib/blather/stream/session.rb +0 -36
  108. data/lib/blather/stream/stream_handler.rb +0 -39
  109. data/lib/blather/stream/tls.rb +0 -33
  110. 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 < StreamHandler # :nodoc:
4
+ class SASL < Features # :nodoc:
5
5
  class UnknownMechanism < BlatherError
6
- handler_heirarchy ||= []
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, jid, pass = nil)
13
- super stream
14
- @jid = jid
15
- @pass = 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 set_mechanism
21
- mod = if @jid.node == '' && @mechanisms.find { |m| m.content == 'ANONYMOUS' }
22
- Anonymous
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
- case (mechanism = @mechanisms[@mechanism_idx].content)
25
- when 'DIGEST-MD5' then DigestMD5
26
- when 'PLAIN' then Plain
27
- when 'ANONYMOUS' then Anonymous
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
- if mod
32
- extend mod
33
- true
38
+ protected
39
+ def next!
40
+ if @jid.node == ''
41
+ process_anonymous
34
42
  else
35
- # Send a failure node and kill the stream
36
- @stream.send "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><invalid-mechanism/></failure>"
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
- # Handle incoming nodes
44
- # Cycle through possible mechanisms until we either
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
- super
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
- protected
60
- def failure(node = nil)
61
- @failure.call SASLError.import(node)
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', content
74
- node['xmlns'] = SASL_NS
75
- node['mechanism'] = mechanism
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
- LOG.debug "CHALLENGE DECODE: #{res.inspect}"
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['xmlns'] = SASL_NS
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
- LOG.debug "CHALLENGE RESPOSNE: #{@response.inspect}"
150
- LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
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
- STREAM_REGEX = %r{(/)?stream:stream}.freeze
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
- @parser = XML::SaxPushParser.new self
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
- LOG.debug "PARSING: (#{string})" if @@debug
20
- @stream_error = string =~ /stream:error/
21
- @parser.receive string
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 on_start_element_ns(elem, attrs, prefix, uri, namespaces)
25
- LOG.debug "START ELEM: (#{{:elem => elem, :attrs => attrs, :prefix => prefix, :uri => uri, :ns => namespaces}.inspect})" if @@debug
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
- if @current && (ns = @current.namespaces.find_by_href(uri))
31
- e.namespace = ns
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
- if elem == 'stream' && !@stream_error
37
- XML::Document.new.root = e
38
- @receiver.receive e
35
+ args = [elem]
36
+ args << @current.document if @current
37
+ node = XMPPNode.new *args
38
+ node.document.root = node unless @current
39
39
 
40
- elsif !@receiver.stopped?
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
- def on_characters(chars = '')
48
- LOG.debug "CHARS: #{chars}" if @@debug
49
- @current << XML::Node.new_text(chars) if @current
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 on_end_element_ns(elem, prefix, uri)
53
- LOG.debug "END ELEM: #{{:elem => elem, :prefix => prefix, :uri => uri}.inspect}" if @@debug
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 !@current && "#{prefix}:#{elem}" =~ STREAM_REGEX
56
- @receiver.receive XMPPNode.new('stream:end')
57
-
58
- elsif @current.parent?
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
- c, @current = @current, nil
63
- XML::Document.new.root = c
64
- @receiver.receive c
65
-
80
+ deliver @current
66
81
  end
67
82
  end
68
83
 
69
- def on_error(err)
70
- err_klass = case err.level
71
- when XML::Error::WARNING
72
- ParseWarning
73
- when XML::Error::ERROR, XML::Error::FATAL
74
- ParseError
75
- end
76
- raise err_klass.new(err)
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