em-xmpp 0.0.11 → 0.0.12

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