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.
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