blather 0.1

Sign up to get free protection for your applications and to get access to all the features.
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