blather 0.1

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 (45) hide show
  1. data/CHANGELOG +1 -0
  2. data/LICENSE +20 -0
  3. data/Manifest +43 -0
  4. data/README.rdoc +78 -0
  5. data/Rakefile +16 -0
  6. data/blather.gemspec +41 -0
  7. data/examples/echo.rb +22 -0
  8. data/examples/shell_client.rb +28 -0
  9. data/lib/autotest/discover.rb +1 -0
  10. data/lib/autotest/spec.rb +60 -0
  11. data/lib/blather.rb +46 -0
  12. data/lib/blather/callback.rb +24 -0
  13. data/lib/blather/client.rb +81 -0
  14. data/lib/blather/core/errors.rb +24 -0
  15. data/lib/blather/core/jid.rb +101 -0
  16. data/lib/blather/core/roster.rb +84 -0
  17. data/lib/blather/core/roster_item.rb +92 -0
  18. data/lib/blather/core/stanza.rb +116 -0
  19. data/lib/blather/core/stanza/iq.rb +27 -0
  20. data/lib/blather/core/stanza/iq/query.rb +42 -0
  21. data/lib/blather/core/stanza/iq/roster.rb +96 -0
  22. data/lib/blather/core/stanza/message.rb +55 -0
  23. data/lib/blather/core/stanza/presence.rb +35 -0
  24. data/lib/blather/core/stanza/presence/status.rb +77 -0
  25. data/lib/blather/core/stanza/presence/subscription.rb +73 -0
  26. data/lib/blather/core/stream.rb +181 -0
  27. data/lib/blather/core/stream/parser.rb +74 -0
  28. data/lib/blather/core/stream/resource.rb +51 -0
  29. data/lib/blather/core/stream/sasl.rb +135 -0
  30. data/lib/blather/core/stream/session.rb +43 -0
  31. data/lib/blather/core/stream/tls.rb +29 -0
  32. data/lib/blather/core/sugar.rb +150 -0
  33. data/lib/blather/core/xmpp_node.rb +132 -0
  34. data/lib/blather/extensions.rb +4 -0
  35. data/lib/blather/extensions/last_activity.rb +55 -0
  36. data/lib/blather/extensions/version.rb +85 -0
  37. data/spec/blather/core/jid_spec.rb +78 -0
  38. data/spec/blather/core/roster_item_spec.rb +80 -0
  39. data/spec/blather/core/roster_spec.rb +79 -0
  40. data/spec/blather/core/stanza_spec.rb +95 -0
  41. data/spec/blather/core/stream_spec.rb +263 -0
  42. data/spec/blather/core/xmpp_node_spec.rb +130 -0
  43. data/spec/build_safe.rb +20 -0
  44. data/spec/spec_helper.rb +49 -0
  45. metadata +172 -0
@@ -0,0 +1,74 @@
1
+ module Blather # :nodoc:
2
+ module Stream # :nodoc:
3
+
4
+ class Parser # :nodoc:
5
+ STREAM_REGEX = %r{(/)?stream:stream}.freeze
6
+
7
+ @@debug = false
8
+ def self.debug; @@debug; end
9
+ def self.debug=(debug); @@debug = debug; end
10
+
11
+ include XML::SaxParser::Callbacks
12
+
13
+ def initialize(receiver)
14
+ @receiver = receiver
15
+ @current = nil
16
+
17
+ @parser = XML::SaxParser.new
18
+ @parser.callbacks = self
19
+ end
20
+
21
+ def parse(string)
22
+ LOG.debug "PARSING: #{string}" if @@debug
23
+ if string =~ STREAM_REGEX && $1
24
+ @receiver.receive XMPPNode.new('stream:end')
25
+ else
26
+ string << "</stream:stream>" if string =~ STREAM_REGEX && !$1
27
+
28
+ @parser.string = string
29
+ @parser.parse
30
+ end
31
+ end
32
+
33
+ def on_start_element(elem, attrs)
34
+ LOG.debug "START ELEM: (#{[elem, attrs].inspect})" if @@debug
35
+ e = XMPPNode.new elem
36
+ attrs.each { |n,v| e[n] = v }
37
+
38
+ if elem == 'stream:stream'
39
+ @receiver.receive e
40
+
41
+ elsif !@receiver.stopped?
42
+ @current << e if @current
43
+ @current = e
44
+
45
+ end
46
+ end
47
+
48
+ def on_characters(chars = '')
49
+ LOG.debug "CHARS: #{chars}" if @@debug
50
+ @current << XML::Node.new_text(chars) if @current
51
+ end
52
+
53
+ def on_cdata_block(block)
54
+ LOG.debug "CDATA: #{block}" if @@debug
55
+ @current << XML::Node.new_cdata(block) if @current
56
+ end
57
+
58
+ def on_end_element(elem)
59
+ return if elem =~ STREAM_REGEX
60
+
61
+ LOG.debug "END ELEM: (#{@current}) #{elem}" if @@debug
62
+ if @current.parent?
63
+ @current = @current.parent
64
+
65
+ else
66
+ c, @current = @current, nil
67
+ @receiver.receive c
68
+
69
+ end
70
+ end
71
+ end #Parser
72
+
73
+ end #Stream
74
+ end #Blather
@@ -0,0 +1,51 @@
1
+ module Blather # :nodoc:
2
+ module Stream # :nodoc:
3
+
4
+ class Resource # :nodoc:
5
+ def initialize(stream, jid)
6
+ @stream = stream
7
+ @jid = jid
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
+ end
23
+
24
+ def bind
25
+ binder = XMPPNode.new('bind')
26
+ binder.xmlns = 'urn:ietf:params:xml:ns:xmpp-bind'
27
+
28
+ binder << XMPPNode.new('resource', @jid.resource) if @jid.resource
29
+
30
+ response = Stanza::Iq.new :set
31
+ @id = response.id
32
+ response << binder
33
+
34
+ @stream.send response
35
+ end
36
+
37
+ def result
38
+ LOG.debug "RESOURE NODE #{@node}"
39
+ if @id == @node['id']
40
+ @jid = JID.new @node.find_first('bind').content_from(:jid)
41
+ @callbacks[:success].call(@jid) if @callbacks[:success]
42
+ end
43
+ end
44
+
45
+ def error
46
+ @callbacks[:failure].call if @callbacks[:failure]
47
+ end
48
+ end #Resource
49
+
50
+ end #Stream
51
+ end #Blather
@@ -0,0 +1,135 @@
1
+ module Blather # :nodoc:
2
+ module Stream # :nodoc:
3
+
4
+ class SASL # :nodoc:
5
+ SASL_NS = 'urn:ietf:params:xml:ns:xmpp-sasl'
6
+
7
+ def initialize(stream, jid, pass = nil)
8
+ @stream = stream
9
+ @jid = jid
10
+ @pass = pass
11
+ @callbacks = {}
12
+
13
+ init_callbacks
14
+ end
15
+
16
+ def init_callbacks
17
+ @callbacks['mechanisms'] = proc { set_mechanism; authenticate }
18
+ end
19
+
20
+ def set_mechanism
21
+ mod = case (mechanism = @node.first.content)
22
+ when 'DIGEST-MD5' then DigestMD5
23
+ when 'PLAIN' then Plain
24
+ when 'ANONYMOUS' then Anonymous
25
+ else raise "Unknown SASL mechanism (#{mechanism})"
26
+ end
27
+
28
+ extend mod
29
+ end
30
+
31
+ def receive(node)
32
+ @node = node
33
+ @callbacks[@node.element_name].call if @callbacks[@node.element_name]
34
+ end
35
+
36
+ def success(&callback)
37
+ @callbacks['success'] = callback
38
+ end
39
+
40
+ def failure(&callback)
41
+ @callbacks['failure'] = callback
42
+ end
43
+
44
+ protected
45
+ def b64(str)
46
+ [str].pack('m').gsub(/\s/,'')
47
+ end
48
+
49
+ def auth_node(mechanism, content = nil)
50
+ node = XMPPNode.new 'auth', content
51
+ node['xmlns'] = SASL_NS
52
+ node['mechanism'] = mechanism
53
+ node
54
+ end
55
+
56
+ module DigestMD5 # :nodoc:
57
+ def self.extended(obj)
58
+ obj.instance_eval { @callbacks['challenge'] = proc { decode_challenge; respond } }
59
+ end
60
+
61
+ def authenticate
62
+ @stream.send auth_node('DIGEST-MD5')
63
+ end
64
+
65
+ private
66
+ def decode_challenge
67
+ text = @node.content.unpack('m').first
68
+ res = {}
69
+
70
+ text.split(',').each do |statement|
71
+ key, value = statement.split('=')
72
+ res[key] = value.delete('"') unless key.empty?
73
+ end
74
+ LOG.debug "CHALLENGE DECODE: #{res.inspect}"
75
+
76
+ @nonce ||= res['nonce']
77
+ @realm ||= res['realm']
78
+ end
79
+
80
+ def generate_response
81
+ a1 = "#{d("#{@response[:username]}:#{@response[:realm]}:#{@pass}")}:#{@response[:nonce]}:#{@response[:cnonce]}"
82
+ a2 = "AUTHENTICATE:#{@response[:'digest-uri']}"
83
+ h("#{h(a1)}:#{@response[:nonce]}:#{@response[:nc]}:#{@response[:cnonce]}:#{@response[:qop]}:#{h(a2)}")
84
+ end
85
+
86
+ def respond
87
+ node = XMPPNode.new 'response'
88
+ node['xmlns'] = SASL_NS
89
+
90
+ unless @initial_response_sent
91
+ @initial_response_sent = true
92
+ @response = {
93
+ :nonce => @nonce,
94
+ :charset => 'utf-8',
95
+ :username => @jid.node,
96
+ :realm => @realm || @jid.domain,
97
+ :cnonce => h(Time.new.to_f.to_s),
98
+ :nc => '00000001',
99
+ :qop => 'auth',
100
+ :'digest-uri' => "xmpp/#{@jid.domain}",
101
+ }
102
+ @response[:response] = generate_response
103
+ @response.each { |k,v| @response[k] = "\"#{v}\"" unless [:nc, :qop, :response, :charset].include?(k) }
104
+
105
+ LOG.debug "CHALLENGE RESPOSNE: #{@response.inspect}"
106
+ LOG.debug "CH RESP TXT: #{@response.map { |k,v| "#{k}=#{v}" } * ','}"
107
+
108
+ # order is to simplify testing
109
+ order = [:nonce, :charset, :username, :realm, :cnonce, :nc, :qop, :'digest-uri']
110
+ node.content = b64(order.map { |k| v = @response[k]; "#{k}=#{v}" } * ',')
111
+ end
112
+
113
+ @stream.send node
114
+ end
115
+
116
+ def d(s); Digest::MD5.digest(s); end
117
+ def h(s); Digest::MD5.hexdigest(s); end
118
+ end #DigestMD5
119
+
120
+ module Plain # :nodoc:
121
+ def authenticate
122
+ @stream.send auth_node('PLAIN', b64("#{@jid.stripped}\x00#{@jid.node}\x00#{@pass}"))
123
+ end
124
+ end #Plain
125
+
126
+ module Anonymous # :nodoc:
127
+ def authenticate
128
+ @stream.send auth_node('ANONYMOUS', b64(@jid.node))
129
+ end
130
+ end #Anonymous
131
+
132
+ end #SASL
133
+
134
+ end #Stream
135
+ end
@@ -0,0 +1,43 @@
1
+ module Blather # :nodoc:
2
+ module Stream # :nodoc:
3
+
4
+ class Session # :nodoc:
5
+ def initialize(stream, to)
6
+ @stream = stream
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
+ end
23
+
24
+ def session
25
+ response = Stanza::Iq.new :set
26
+ response.to = @to
27
+ sess = XMPPNode.new 'session'
28
+ sess['xmlns'] = 'urn:ietf:params:xml:ns:xmpp-session'
29
+ response << sess
30
+ @stream.send response
31
+ end
32
+
33
+ def result
34
+ @callbacks[:success].call(@jid) if @callbacks[:success]
35
+ end
36
+
37
+ def error
38
+ @callbacks[:failure].call if @callbacks[:failure]
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ module Blather # :nodoc:
2
+ module Stream # :nodoc:
3
+
4
+ class TLS # :nodoc:
5
+ def initialize(stream)
6
+ @stream = stream
7
+ @callbacks = {
8
+ 'starttls' => proc { @stream.send "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" },
9
+ 'proceed' => proc { @stream.start_tls; @callbacks['success'].call },
10
+ 'success' => proc { },
11
+ 'failure' => proc { }
12
+ }
13
+ end
14
+
15
+ def success(&callback)
16
+ @callbacks['success'] = callback
17
+ end
18
+
19
+ def failure(&callback)
20
+ @callbacks['failure'] = callback
21
+ end
22
+
23
+ def receive(node)
24
+ @callbacks[node.element_name].call if @callbacks[node.element_name]
25
+ end
26
+ end #TLS
27
+
28
+ end #Stream
29
+ end #Blather
@@ -0,0 +1,150 @@
1
+ module LibXML # :nodoc:
2
+ module XML # :nodoc:
3
+
4
+ class Attributes
5
+ # Helper method for removing attributes
6
+ def remove(name)
7
+ name = name.to_s
8
+ self.each { |a| a.remove! or break if a.name == name }
9
+ end
10
+ end #Attributes
11
+
12
+ end #XML
13
+ end #LibXML
14
+
15
+ ## Thanks to ActiveSupport for everything below this line
16
+ class Class # :nodoc:
17
+ def class_inheritable_reader(*syms)
18
+ syms.each do |sym|
19
+ next if sym.is_a?(Hash)
20
+ class_eval <<-EOS
21
+ def self.#{sym}
22
+ read_inheritable_attribute(:#{sym})
23
+ end
24
+
25
+ def #{sym}
26
+ self.class.#{sym}
27
+ end
28
+ EOS
29
+ end
30
+ end
31
+
32
+ def class_inheritable_writer(*syms)
33
+ syms.each do |sym|
34
+ class_eval <<-EOS
35
+ def self.#{sym}=(obj)
36
+ write_inheritable_attribute(:#{sym}, obj)
37
+ end
38
+ EOS
39
+ end
40
+ end
41
+
42
+ def class_inheritable_array_writer(*syms)
43
+ syms.each do |sym|
44
+ class_eval <<-EOS
45
+ def self.#{sym}=(obj)
46
+ write_inheritable_array(:#{sym}, obj)
47
+ end
48
+ EOS
49
+ end
50
+ end
51
+
52
+ def class_inheritable_hash_writer(*syms)
53
+ syms.each do |sym|
54
+ class_eval <<-EOS
55
+ def self.#{sym}=(obj)
56
+ write_inheritable_hash(:#{sym}, obj)
57
+ end
58
+ EOS
59
+ end
60
+ end
61
+
62
+ def class_inheritable_accessor(*syms)
63
+ class_inheritable_reader(*syms)
64
+ class_inheritable_writer(*syms)
65
+ end
66
+
67
+ def class_inheritable_array(*syms)
68
+ class_inheritable_reader(*syms)
69
+ class_inheritable_array_writer(*syms)
70
+ end
71
+
72
+ def class_inheritable_hash(*syms)
73
+ class_inheritable_reader(*syms)
74
+ class_inheritable_hash_writer(*syms)
75
+ end
76
+
77
+ def inheritable_attributes
78
+ @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
79
+ end
80
+
81
+ def write_inheritable_attribute(key, value)
82
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
83
+ @inheritable_attributes = {}
84
+ end
85
+ inheritable_attributes[key] = value
86
+ end
87
+
88
+ def write_inheritable_array(key, elements)
89
+ write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
90
+ write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
91
+ end
92
+
93
+ def write_inheritable_hash(key, hash)
94
+ write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
95
+ write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
96
+ end
97
+
98
+ def read_inheritable_attribute(key)
99
+ inheritable_attributes[key]
100
+ end
101
+
102
+ def reset_inheritable_attributes
103
+ @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
104
+ end
105
+
106
+ private
107
+ # Prevent this constant from being created multiple times
108
+ EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
109
+
110
+ def inherited_with_inheritable_attributes(child)
111
+ inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
112
+
113
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
114
+ new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
115
+ else
116
+ new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
117
+ memo.update(key => value.duplicable? ? value.dup : value)
118
+ end
119
+ end
120
+
121
+ child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
122
+ end
123
+
124
+ alias inherited_without_inheritable_attributes inherited
125
+ alias inherited inherited_with_inheritable_attributes
126
+ end #Class
127
+
128
+ class Object # :nodoc:
129
+ def duplicable?; true; end
130
+ end
131
+
132
+ class NilClass #:nodoc:
133
+ def duplicable?; false; end
134
+ end
135
+
136
+ class FalseClass #:nodoc:
137
+ def duplicable?; false; end
138
+ end
139
+
140
+ class TrueClass #:nodoc:
141
+ def duplicable?; false; end
142
+ end
143
+
144
+ class Symbol #:nodoc:
145
+ def duplicable?; false; end
146
+ end
147
+
148
+ class Numeric #:nodoc:
149
+ def duplicable?; false; end
150
+ end