em-xmpp 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,19 @@
1
1
 
2
+ require 'em-xmpp/namespaces'
2
3
  require 'em-xmpp/nodes'
3
4
  require 'em-xmpp/conversation'
5
+ require 'fiber'
6
+
4
7
  module EM::Xmpp
5
8
  module Helpers
6
9
  include EM::Xmpp::Namespaces
7
10
  def get_roster
8
11
  f = Fiber.current
9
12
 
10
- roster = iq_stanza do |iq|
11
- iq.query(:xmlns => Roster)
12
- end
13
+ roster = iq_stanza(x('query',:xmlns => Roster))
13
14
 
14
15
  send_stanza(roster) do |response|
15
- f.resume response.bit!(:roster).items
16
+ f.resume response.bit(:roster).items
16
17
  end
17
18
 
18
19
  Fiber.yield
@@ -158,17 +159,17 @@ module EM::Xmpp
158
159
  until state.finished?
159
160
  state.status = 'executing'
160
161
 
161
- reply = query.reply do |iq|
162
- iq.command(:xmlns => EM::Xmpp::Namespaces::Commands, :sessionid => sess_id, :node => query.node, :status => state.status) do |cmd|
163
- cmd.actions do |n|
164
- n.prev if state.can_prev?
165
- n.complete if state.can_complete?
166
- n.next if state.can_next?
167
- end
168
- build_form(cmd, state.form,'form')
169
- cmd.note({:type => state.flash.level}, state.flash.msg) if state.flash
170
- end
171
- end
162
+ reply = query.reply(
163
+ x('command',{:xmlns => EM::Xmpp::Namespaces::Commands, :sessionid => sess_id, :node => query.node, :status => state.status},
164
+ x('actions',
165
+ x_if(state.can_prev?,'prev'),
166
+ x_if(state.can_complete?,'complete'),
167
+ x_if(state.can_next?,'next')
168
+ ),
169
+ build_form(state.form,'form'),
170
+ x_if(state.flash,'note',{:type => state.flash.level}, state.flash.msg)
171
+ )
172
+ )
172
173
 
173
174
  user_answer = conv.send_stanza reply
174
175
  state.last_answer = user_answer
@@ -187,40 +188,40 @@ module EM::Xmpp
187
188
  state.status = 'completed'
188
189
  end
189
190
 
190
- finalizer = state.last_answer.ctx.bit(:command).reply do |iq|
191
- iq.command(:xmlns => EM::Xmpp::Namespaces::Commands, :sessionid => sess_id, :node => query.node, :status => state.status) do |cmd|
192
- cmd.note({:type => state.flash.level}, state.flash.msg) if state.flash
193
- build_form(cmd, state.result,'result') if state.result
194
- end
195
- end
191
+ finalizer = state.last_answer.ctx.bit(:command).reply(
192
+ x('command',{:xmlns => EM::Xmpp::Namespaces::Commands, :sessionid => sess_id, :node => query.node, :status => state.status},
193
+ x_if(state.flash,'note',{:type => state.flash.level}, state.flash.msg),
194
+ state.result ? build_form(state.result,'result') : nil
195
+ )
196
+ )
196
197
  send_stanza finalizer
197
198
  end
198
199
  end
199
200
 
200
- def build_form(xml,form,type='submit')
201
- xml.x(:xmlns => DataForms, :type => type) do |x|
202
- x.title form.title if form.title
203
- x.instructions form.instructions if form.instructions
204
- form.fields.each do |field|
201
+ def build_form(form,type='submit')
202
+ x('x',{:xmlns => DataForms, :type => type},
203
+ x_if(form.title,'title',form.title),
204
+ x_if(form.instructions,'instructions',form.instructions),
205
+ form.fields.map do |field|
205
206
  args = {'var' => field.var}
206
207
  args = args.merge('type' => field.type) unless field.type.nil? or field.type.empty?
207
208
  args = args.merge('label' => field.label) unless field.label.nil? or field.label.empty?
208
- x.field(args) do |f|
209
- (field.options||[]).each do |opt_value|
210
- f.option do |o|
211
- o.value opt_value
212
- end
213
- end
214
- (field.values||[]).each do |value|
215
- f.value value
209
+ x('field',args,
210
+ (field.options||[]).map do |opt_value|
211
+ x('option',
212
+ x('value',opt_value)
213
+ )
214
+ end,
215
+ (field.values||[]).map do |value|
216
+ x('value',value)
216
217
  end
217
- end
218
+ )
218
219
  end
219
- end
220
+ )
220
221
  end
221
222
 
222
- def build_submit_form(xml,form)
223
- build_form(xml,form,'submit')
223
+ def build_submit_form(form)
224
+ build_form(form,'submit')
224
225
  end
225
226
 
226
227
  end
@@ -3,6 +3,7 @@ module EM::Xmpp
3
3
  # A handy module with the XMPP namespaces name.
4
4
  module Namespaces
5
5
  Client = 'jabber:client'
6
+ Component = 'jabber:component'
6
7
  #In-band registration
7
8
  Registration = 'http://jabber.org/features/iq-register'
8
9
  #XMPP stream-level stanza
@@ -0,0 +1,95 @@
1
+
2
+ require 'em-xmpp'
3
+ require 'em-xmpp/namespaces'
4
+ require 'em-xmpp/evented'
5
+ require 'em-xmpp/jid'
6
+ require 'em-xmpp/component'
7
+ require 'em-xmpp/resolver'
8
+
9
+ require 'socket'
10
+ require 'openssl'
11
+
12
+ module EM::Xmpp
13
+ module NonEM
14
+ class Connection
15
+ include Namespaces
16
+ include Evented
17
+
18
+ attr_reader :jid, :pass, :user_data
19
+
20
+ def initialize(jid, pass, mod=nil, cfg={})
21
+ @jid = jid
22
+ @component = jid.node.nil?
23
+ self.extend Component if component?
24
+ @pass = pass.dup.freeze
25
+ self.extend mod if mod
26
+ certdir = cfg[:certificates]
27
+ @user_data = cfg[:data]
28
+ @certstore = if certdir
29
+ CertStore.new(certdir)
30
+ else
31
+ nil
32
+ end
33
+ @ssl = nil
34
+ end
35
+
36
+ def self.start(jid, pass=nil, mod=nil, cfg={}, server=nil, port=5222, &blk)
37
+ jid = JID.parse jid
38
+ if server.nil?
39
+ record = Resolver.resolve jid.domain
40
+ if record
41
+ server = record.target.to_s
42
+ port = record.port
43
+ else
44
+ server = jid.domain
45
+ end
46
+ end
47
+ obj = self.new(jid,pass,mod,cfg)
48
+ obj.start(server,port)
49
+ obj
50
+ end
51
+
52
+ def start(server,port)
53
+ @skt = TCPSocket.open(server,port)
54
+ end
55
+
56
+ ChunkSize = 1450
57
+
58
+ def event_loop
59
+ prepare_parser!
60
+ set_negotiation_handler!
61
+ catch :stop do
62
+ loop do
63
+ tick=5
64
+ ok = IO.select([@skt], nil, nil, tick)
65
+ if ok
66
+ if @ssl
67
+ dat = @ssl.sysread(ChunkSize)
68
+ puts "<< in\n#{dat}\n" if $DEBUG
69
+ receive_raw(dat)
70
+ else
71
+ dat = @skt.recv(ChunkSize)
72
+ puts "<< in\n#{dat}\n" if $DEBUG
73
+ receive_raw(dat)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def send_raw(dat)
81
+ puts ">> out\n#{dat}\n" if $DEBUG
82
+ if @ssl
83
+ @ssl.syswrite dat
84
+ else
85
+ @skt << dat if @skt
86
+ end
87
+ end
88
+
89
+ def initiate_tls
90
+ @ssl = OpenSSL::SSL::SSLSocket.new(@skt).connect
91
+ end
92
+ end
93
+ end
94
+
95
+ end
@@ -1,5 +1,5 @@
1
1
  module Em
2
2
  module Xmpp
3
- VERSION = "0.0.11"
3
+ VERSION = "0.0.12"
4
4
  end
5
5
  end
@@ -0,0 +1,160 @@
1
+ require 'ox'
2
+ require 'nokogiri'
3
+
4
+ module Ox
5
+ module Builder
6
+ # args = attributes and/or children in any order, multiple appearance is possible
7
+ # @overload build(name,attributes,children)
8
+ # @param [String] name name of the Element
9
+ # @param [Hash] attributes
10
+ # @param [String|Element|Array] children text, child element or array of elements
11
+ def x(name, *args)
12
+ n = Element.new(name)
13
+ yielded = if block_given?
14
+ yield
15
+ else
16
+ []
17
+ end
18
+ unless yielded.is_a?(Array)
19
+ yielded = [yielded]
20
+ end
21
+ values = args + yielded
22
+
23
+ values.each do |val|
24
+ case val
25
+ when Hash
26
+ val.each { |k,v| n[k.to_s] = v }
27
+ when Array
28
+ val.each { |c| n << c if c}
29
+ else
30
+ n << val if val
31
+ end
32
+ end
33
+
34
+ n
35
+ end
36
+ def x_if(condition, *args)
37
+ x(*args) if condition
38
+ end
39
+ end
40
+ end
41
+
42
+ module Nokogiri::AlternativeBuilder
43
+ class Element
44
+ attr_reader :name, :children, :attributes
45
+ def initialize(name)
46
+ @name = name
47
+ @children = []
48
+ @attributes = {}
49
+ end
50
+ def << n
51
+ @children << n
52
+ end
53
+ def []= k,v
54
+ @attributes[k] = v
55
+ end
56
+ end
57
+
58
+ def x(name, *args)
59
+ n = Element.new(name)
60
+ yielded = if block_given?
61
+ yield
62
+ else
63
+ []
64
+ end
65
+ unless yielded.is_a?(Array)
66
+ yielded = [yielded]
67
+ end
68
+ values = args + yielded
69
+
70
+ values.each do |val|
71
+ case val
72
+ when Hash
73
+ val.each { |k,v| n[k.to_s] = v }
74
+ when Array
75
+ val.each { |c| n << c if c}
76
+ else
77
+ n << val if val
78
+ end
79
+ end
80
+ n
81
+ end
82
+ def x_if(condition, *args)
83
+ x(*args) if condition
84
+ end
85
+ end
86
+
87
+ module EM::Xmpp
88
+ module NokogiriXmlBuilder
89
+ include Nokogiri::AlternativeBuilder
90
+ class OutgoingStanza
91
+ include NokogiriXmlBuilder
92
+
93
+ attr_reader :xml,:params
94
+
95
+ def initialize(*args,&blk)
96
+ @root = x(*args,&blk)
97
+ @doc = build_doc_from_element_root @root
98
+ @xml = @doc.root.to_xml
99
+ @params = @root.attributes
100
+ end
101
+
102
+ private
103
+
104
+ def build_doc_from_element_root(root)
105
+ doc = Nokogiri::XML::Document.new
106
+ root_node = build_tree doc, root
107
+ doc.root = root_node
108
+ doc
109
+ end
110
+ end
111
+
112
+ def build_xml(*args)
113
+ root = x(*args)
114
+ doc = Nokogiri::XML::Document.new
115
+ doc.root = build_tree(doc, root)
116
+ ret = doc.root.to_xml
117
+ ret
118
+ end
119
+
120
+ private
121
+
122
+ def build_tree(doc, node_info)
123
+ node = node_info
124
+ if node_info.respond_to?(:name)
125
+ node = node_for_info doc, node_info
126
+ list = node_info.children.map{|child| build_tree doc, child}
127
+ list.each{|l| node << l }
128
+ end
129
+ node
130
+ end
131
+
132
+ def node_for_info(doc, node_info)
133
+ node = Nokogiri::XML::Node.new(node_info.name, doc)
134
+ node_info.attributes.each_pair {|k,v| node[k] = v}
135
+ node
136
+ end
137
+ end
138
+
139
+ module OxXmlBuilder
140
+ include Ox::Builder
141
+
142
+ class OutgoingStanza
143
+ include OxXmlBuilder
144
+ attr_reader :xml,:params
145
+
146
+ def initialize(*args,&blk)
147
+ node = x(*args,&blk)
148
+ @xml = Ox.dump(node)
149
+ @params = node.attributes
150
+ end
151
+ end
152
+
153
+ def build_xml(*args)
154
+ Ox.dump(x(*args))
155
+ end
156
+ end
157
+
158
+ #XmlBuilder = OxXmlBuilder
159
+ XmlBuilder = NokogiriXmlBuilder
160
+ end
@@ -0,0 +1,344 @@
1
+ require 'nokogiri'
2
+ require 'ox'
3
+
4
+ #workarounds
5
+ module Ox
6
+ module XPathSubset
7
+ class Query
8
+ attr_reader :type, :name, :ns
9
+ def initialize(t,e,n)
10
+ @type, @name, @ns = t, e, n
11
+ end
12
+
13
+ def match(elem, ns_mapping)
14
+ wanted_ns = ns_mapping[ns]
15
+
16
+ same_value = elem.value == name
17
+ same_ns = elem.xmlns == wanted_ns
18
+
19
+ matching = same_value & same_ns
20
+ end
21
+
22
+ def find(elem, ns_mapping)
23
+ ret = []
24
+ ret << elem if match(elem, ns_mapping)
25
+ case type
26
+ when 'normal'
27
+ elem.nodes.reject{|n| n.is_a?(String)}.each do |n|
28
+ ret << n if match(n, ns_mapping)
29
+ end
30
+ when 'relative', 'anywhere' #TODO: for anywhere, should go to the root first, is that even possible?
31
+ elem.nodes.reject{|n| n.is_a?(String)}.each do |n|
32
+ find(n, ns_mapping).each {|m| ret << m}
33
+ end
34
+ else
35
+ raise NotImplementedError
36
+ end
37
+
38
+ ret
39
+ end
40
+ end
41
+ end
42
+
43
+ class Element
44
+ attr_accessor :xmlns
45
+ def parse_xpath(path)
46
+ queries = path.split('|').map(&:strip).map do |str|
47
+ kind = if str.start_with?("//")
48
+ 'anywhere'
49
+ elsif str.start_with?(".//")
50
+ 'relative'
51
+ else
52
+ 'normal'
53
+ end
54
+ ns,name = str.split(':',2)
55
+ if name
56
+ ns = ns.tr('/.','')
57
+ else
58
+ name,ns = ns,nil unless name
59
+ end
60
+ XPathSubset::Query.new(kind, name, ns)
61
+ end
62
+ end
63
+
64
+ def xpath(path,ns_mapping)
65
+ queries = parse_xpath(path)
66
+ elems = []
67
+ queries.each do |q|
68
+ q.find(self,ns_mapping).each do |n|
69
+ elems << n
70
+ end
71
+ end
72
+ elems.uniq
73
+ end
74
+
75
+ def children
76
+ text ? [] : nodes
77
+ end
78
+
79
+ def content
80
+ text
81
+ end
82
+
83
+ def any?
84
+ text
85
+ end
86
+
87
+ def child
88
+ nodes.first
89
+ end
90
+ end
91
+ end
92
+
93
+ module EM::Xmpp
94
+ module XmlParser
95
+
96
+ class ForwardingParser < ::Nokogiri::XML::SAX::PushParser
97
+ def initialize(receiver)
98
+ doc = ForwardingDocument.new
99
+ doc.recipient = receiver
100
+ super doc
101
+ end
102
+ end
103
+
104
+ #XML SAX document which delegates its method to a recipient object
105
+ class ForwardingDocument < ::Nokogiri::XML::SAX::Document
106
+ attr_accessor :recipient
107
+ %w{xmldecl start_document end_document start_element_namespace end_element characters
108
+ comment warning error cdata_block}.each do |meth|
109
+ meth2 = "xml_#{meth}"
110
+ define_method(meth) do |*args|
111
+ recipient.send(meth2, *args) if recipient
112
+ end
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ ### XML world
119
+ module XmlInterface
120
+ def xml_xmldecl(version,encoding,standalone)
121
+ raise NotImplementedError
122
+ end
123
+ def xml_start_document
124
+ raise NotImplementedError
125
+ end
126
+ def xml_end_document
127
+ raise NotImplementedError
128
+ end
129
+ def xml_start_element_namespace(name, attrs=[],prefix=nil,uri=nil,ns=[])
130
+ raise NotImplementedError
131
+ end
132
+ def xml_end_element(name)
133
+ raise NotImplementedError
134
+ end
135
+ def xml_characters(txt)
136
+ raise NotImplementedError
137
+ end
138
+ def xml_error(err)
139
+ raise RuntimeError, err
140
+ end
141
+ def xml_stream_closing
142
+ raise NotImplementedError
143
+ end
144
+ def xml_comment(comm)
145
+ raise NotImplementedError
146
+ end
147
+
148
+ def xml_warning(warn)
149
+ raise NotImplementedError
150
+ end
151
+
152
+ def xml_cdata_block(data)
153
+ raise NotImplementedError
154
+ end
155
+ end
156
+
157
+ module Nokogiri
158
+ include XmlInterface
159
+ def xml_xmldecl(version,encoding,standalone)
160
+ end
161
+
162
+ def xml_start_document
163
+ #XXX set namespaces and stream prefix
164
+ # namespace may depend on the type of connection ('jabber:client' or
165
+ # 'jabber:server')
166
+ # currently we do not set any stream's namespace, hence when builidng stanza,
167
+ # we must explicitely avoid writing the namespace of iq/presence/message XML nodes
168
+ @streamdoc = ::Nokogiri::XML::Document.new
169
+ end
170
+
171
+ def xml_end_document
172
+ @streamdoc = @stanza = @stack = @xml_parser = nil
173
+ end
174
+
175
+ def xml_start_element_namespace(name, attrs=[],prefix=nil,uri=nil,ns=[])
176
+ node = ::Nokogiri::XML::Node.new(name, @streamdoc)
177
+ attrs.each do |attr|
178
+ #attr is a Struct with members localname/prefix/uri/value
179
+ node[attr.localname] = attr.value
180
+ end
181
+ #XXX - if prefix is there maybe we do not want to set uri as default
182
+ node.default_namespace = uri if uri
183
+
184
+ ns.each do |pfx,href|
185
+ node.add_namespace_definition pfx, href
186
+ end
187
+
188
+ # puts "starting: #{name}, stack:#{@stack.size}" if $DEBUG
189
+ case @stack.size
190
+ when 0 #the streaming tag starts
191
+ stream_support(node)
192
+ when 1 #a stanza starts
193
+ set_current_stanza!(node)
194
+ stanza_start node
195
+ else
196
+ @stack.last.add_child node
197
+ end
198
+
199
+ @stack << node
200
+ end
201
+ def xml_end_element(name)
202
+ node = @stack.pop
203
+ #puts "ending: #{name}, stack:#{@stack.size}" if $DEBUG
204
+
205
+ case @stack.size
206
+ when 0 #i.e., the stream support ends
207
+ xml_stream_closing
208
+ when 1 #i.e., we've finished a stanza
209
+ raise RuntimeError, "should end on a stanza" unless node == @stanza
210
+ stanza_end node
211
+ else
212
+ #the stanza keeps growing
213
+ end
214
+ end
215
+
216
+ def xml_characters(txt)
217
+ @stack.last << ::Nokogiri::XML::Text.new(txt, @streamdoc)
218
+ end
219
+
220
+ def xml_stream_closing
221
+ close_xml_stream
222
+ close_connection
223
+ end
224
+
225
+ def stream_support(node)
226
+ @stanza = ::Nokogiri::XML::Node.new('dummy', @streamdoc)
227
+ node << @stanza
228
+
229
+ @streamdoc.root = node
230
+ end
231
+
232
+ def set_current_stanza!(node)
233
+ @stanza.remove
234
+
235
+ @stanza = node
236
+ @streamdoc.root << @stanza
237
+ end
238
+ end
239
+
240
+ module Ox
241
+ include XmlInterface
242
+ def xml_xmldecl(version,encoding,standalone)
243
+ end
244
+
245
+ def xml_start_document
246
+ #XXX set namespaces and stream prefix
247
+ # namespace may depend on the type of connection ('jabber:client' or
248
+ # 'jabber:server')
249
+ # currently we do not set any stream's namespace, hence when builidng stanza,
250
+ # we must explicitely avoid writing the namespace of iq/presence/message XML nodes
251
+ end
252
+
253
+ def xml_end_document
254
+ @stanza = @stack = @xml_parser = nil
255
+ end
256
+
257
+ def xml_start_element_namespace(name, attrs=[],prefix=nil,uri=nil,ns=[])
258
+ node = ::Ox::Element.new(name)
259
+ node.xmlns = uri
260
+ attrs.each do |attr|
261
+ #attr is a Struct with members localname/prefix/uri/value
262
+ node[attr.localname] = attr.value
263
+ end
264
+
265
+ case @stack.size
266
+ when 0 #the streaming tag starts
267
+ stream_support(node)
268
+ stream_start node
269
+ when 1 #a stanza starts
270
+ set_current_stanza!(node)
271
+ stanza_start node
272
+ else
273
+ @stack.last << node
274
+ end
275
+
276
+ @stack << node
277
+ @text = nil
278
+ end
279
+
280
+ def xml_end_element(name)
281
+ node = @stack.pop
282
+ if @text
283
+ node << @text
284
+ @text = nil
285
+ end
286
+ #puts "ending: #{name}, stack:#{@stack.size}" if $DEBUG
287
+
288
+ case @stack.size
289
+ when 0 #i.e., the stream support ends
290
+ xml_stream_closing
291
+ when 1 #i.e., we've finished a stanza
292
+ raise RuntimeError, "should end on a stanza" unless node == @stanza
293
+ stanza_end node
294
+ else
295
+ #the stanza keeps growing
296
+ end
297
+ end
298
+
299
+ def xml_characters(txt)
300
+ if @text
301
+ @text<<txt
302
+ else
303
+ @text=txt
304
+ end
305
+ end
306
+
307
+ def xml_error(err)
308
+ #raise RuntimeError, err
309
+ end
310
+
311
+ def xml_stream_closing
312
+ close_xml_stream
313
+ close_connection
314
+ end
315
+
316
+ def xml_comment(comm)
317
+ raise NotImplementedError
318
+ end
319
+
320
+ def xml_warning(warn)
321
+ raise NotImplementedError
322
+ end
323
+
324
+ def xml_cdata_block(data)
325
+ raise NotImplementedError
326
+ end
327
+
328
+ ### XMPP World
329
+
330
+ def stream_support(node)
331
+ @stanza = ::Ox::Element.new('dummy')
332
+ node << @stanza
333
+
334
+ @streamdoc_root = node
335
+ end
336
+
337
+ def set_current_stanza!(node)
338
+ @stanza = node
339
+ @streamdoc_root.nodes[0] = @stanza
340
+ end
341
+ end
342
+
343
+ end
344
+ end