xmpp4r 0.5 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/README.rdoc +29 -38
- data/Rakefile +11 -18
- data/data/doc/xmpp4r/examples/advanced/recvfile.rb +5 -4
- data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +2 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +1 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +1 -1
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +4 -1
- data/lib/xmpp4r/caps/helper/generator.rb +2 -2
- data/lib/xmpp4r/client.rb +18 -4
- data/lib/xmpp4r/connection.rb +8 -5
- data/lib/xmpp4r/delay/x/delay.rb +1 -1
- data/lib/xmpp4r/entity_time.rb +6 -0
- data/lib/xmpp4r/entity_time/iq.rb +45 -0
- data/lib/xmpp4r/entity_time/responder.rb +57 -0
- data/lib/xmpp4r/httpbinding/client.rb +178 -44
- data/lib/xmpp4r/message.rb +46 -0
- data/lib/xmpp4r/muc/helper/mucclient.rb +8 -1
- data/lib/xmpp4r/observable.rb +9 -0
- data/lib/xmpp4r/observable/contact.rb +61 -0
- data/lib/xmpp4r/observable/helper.rb +389 -0
- data/lib/xmpp4r/observable/observable_thing.rb +191 -0
- data/lib/xmpp4r/observable/pubsub.rb +211 -0
- data/lib/xmpp4r/observable/subscription.rb +57 -0
- data/lib/xmpp4r/observable/thread_store.rb +65 -0
- data/lib/xmpp4r/pubsub/children/unsubscribe.rb +16 -1
- data/lib/xmpp4r/pubsub/helper/servicehelper.rb +48 -14
- data/lib/xmpp4r/rexmladdons.rb +46 -6
- data/lib/xmpp4r/roster/helper/roster.rb +19 -9
- data/lib/xmpp4r/sasl.rb +1 -1
- data/lib/xmpp4r/stream.rb +2 -2
- data/lib/xmpp4r/xmpp4r.rb +1 -1
- data/test/bytestreams/tc_socks5bytestreams.rb +2 -2
- data/test/entity_time/tc_responder.rb +65 -0
- data/test/roster/tc_helper.rb +2 -3
- data/test/tc_message.rb +52 -1
- data/test/tc_stream.rb +2 -2
- data/test/tc_streamComponent.rb +11 -1
- data/test/ts_xmpp4r.rb +11 -2
- data/xmpp4r.gemspec +20 -9
- metadata +41 -35
data/lib/xmpp4r/delay/x/delay.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
4
|
+
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Jabber::EntityTime
|
8
|
+
NS_TIME = 'urn:xmpp:time'
|
9
|
+
|
10
|
+
class IqTime < Jabber::IqQuery
|
11
|
+
name_xmlns 'time', NS_TIME
|
12
|
+
|
13
|
+
def initialize(time=nil)
|
14
|
+
super()
|
15
|
+
set_time(time)
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_time(time=nil)
|
19
|
+
time ||= Time.now
|
20
|
+
|
21
|
+
tzo = self.add_element('tzo')
|
22
|
+
tzo.add_text(time_zone_offset(time))
|
23
|
+
|
24
|
+
utc = self.add_element('utc')
|
25
|
+
utc.add_text(utc_time(time))
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def utc_time(time)
|
30
|
+
raise ArgumentError, 'invalid time object' unless time.respond_to?(:utc)
|
31
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
32
|
+
end
|
33
|
+
|
34
|
+
def time_zone_offset(time)
|
35
|
+
raise ArgumentError, 'invalid time object' unless time.respond_to?(:utc_offset)
|
36
|
+
|
37
|
+
sec_offset = time.utc_offset
|
38
|
+
h_offset = sec_offset.to_i / 3600
|
39
|
+
m_offset = sec_offset.abs % 60
|
40
|
+
|
41
|
+
"%+03d:%02d"%[h_offset, m_offset]
|
42
|
+
end
|
43
|
+
|
44
|
+
end # /IqTime
|
45
|
+
end # /Jabber::EntityTime
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# =XMPP4R - XMPP Library for Ruby
|
2
|
+
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
3
|
+
# Website::http://home.gna.org/xmpp4r/
|
4
|
+
|
5
|
+
require 'xmpp4r/entity_time/iq'
|
6
|
+
|
7
|
+
module Jabber::EntityTime
|
8
|
+
##
|
9
|
+
# XEP 202 Entity Time implementation
|
10
|
+
#
|
11
|
+
# @see http://xmpp.org/extensions/xep-0202.html
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# <iq type='get'
|
15
|
+
# from='romeo@montague.net/orchard'
|
16
|
+
# to='juliet@capulet.com/balcony'
|
17
|
+
# id='time_1'>
|
18
|
+
# <time xmlns='urn:xmpp:time'/>
|
19
|
+
# </iq>
|
20
|
+
#
|
21
|
+
class Responder
|
22
|
+
|
23
|
+
CALLBACK_PRIORITY = 180
|
24
|
+
|
25
|
+
##
|
26
|
+
# +to_s+ allows the responder to be added to another responder as a feature
|
27
|
+
def to_s
|
28
|
+
NS_TIME
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(stream)
|
32
|
+
@stream = stream
|
33
|
+
|
34
|
+
stream.add_iq_callback(CALLBACK_PRIORITY, self) do |iq|
|
35
|
+
iq_callback(iq)
|
36
|
+
end
|
37
|
+
end # /initialize
|
38
|
+
|
39
|
+
def iq_callback(iq)
|
40
|
+
if iq.type == :get &&
|
41
|
+
iq.first_element('time') &&
|
42
|
+
iq.first_element('time').namespace === NS_TIME
|
43
|
+
|
44
|
+
answer = iq.answer(false)
|
45
|
+
answer.type = :result
|
46
|
+
answer.add(IqTime.new(Time.now))
|
47
|
+
|
48
|
+
@stream.send(answer)
|
49
|
+
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
end # /Responder
|
57
|
+
end # /Jabber::EntityTime
|
@@ -7,6 +7,8 @@
|
|
7
7
|
require 'xmpp4r/client'
|
8
8
|
require 'xmpp4r/semaphore'
|
9
9
|
require 'net/http'
|
10
|
+
require 'uri'
|
11
|
+
require 'cgi' # for escaping
|
10
12
|
|
11
13
|
module Jabber
|
12
14
|
module HTTPBinding
|
@@ -40,6 +42,8 @@ module Jabber
|
|
40
42
|
# The server may hold this amount of stanzas
|
41
43
|
# to reduce number of HTTP requests
|
42
44
|
attr_accessor :http_hold
|
45
|
+
# Hook to initialize SSL parameters on Net::HTTP
|
46
|
+
attr_accessor :http_ssl_setup
|
43
47
|
|
44
48
|
##
|
45
49
|
# Initialize
|
@@ -56,6 +60,50 @@ module Jabber
|
|
56
60
|
@http_wait = 20
|
57
61
|
@http_hold = 1
|
58
62
|
@http_content_type = 'text/xml; charset=utf-8'
|
63
|
+
|
64
|
+
@no_proxy = []
|
65
|
+
@proxy_args = []
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Set up proxy from the given url
|
70
|
+
|
71
|
+
# url:: [URI::Generic or String] of the form:
|
72
|
+
# when without proxy authentication
|
73
|
+
# http://proxy_host:proxy_port/
|
74
|
+
# when with proxy authentication
|
75
|
+
# http://proxy_user:proxy_password@proxy_host:proxy_port/
|
76
|
+
def http_proxy_uri=(uri)
|
77
|
+
uri = URI.parse(uri) unless uri.respond_to?(:host)
|
78
|
+
@proxy_args = [
|
79
|
+
uri.host,
|
80
|
+
uri.port,
|
81
|
+
uri.user,
|
82
|
+
uri.password,
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Set up proxy from the environment variables
|
88
|
+
#
|
89
|
+
# Following environment variables are considered
|
90
|
+
# HTTP_PROXY, http_proxy
|
91
|
+
# NO_PROXY, no_proxy
|
92
|
+
# HTTP_PROXY_USER, HTTP_PROXY_PASSWORD
|
93
|
+
def http_proxy_env
|
94
|
+
@proxy_args = []
|
95
|
+
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
96
|
+
return if env_proxy.nil? or env_proxy.empty?
|
97
|
+
|
98
|
+
uri = URI.parse env_proxy
|
99
|
+
unless uri.user or uri.password then
|
100
|
+
uri.user = CGI.escape (ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']) rescue nil
|
101
|
+
uri.password = CGI.escape (ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']) rescue nil
|
102
|
+
end
|
103
|
+
|
104
|
+
self.http_proxy_uri = uri
|
105
|
+
|
106
|
+
@no_proxy = (ENV['NO_PROXY'] || ENV['no_proxy'] || 'localhost, 127.0.0.1').split(/\s*,\s*/)
|
59
107
|
end
|
60
108
|
|
61
109
|
##
|
@@ -76,7 +124,8 @@ module Jabber
|
|
76
124
|
@stream_features = {}
|
77
125
|
@http_rid = IdGenerator.generate_id.to_i
|
78
126
|
@pending_rid = @http_rid
|
79
|
-
@pending_rid_lock =
|
127
|
+
@pending_rid_lock = Mutex.new
|
128
|
+
@pending_rid_cv = [] # [ [rid, cv], [rid, cv], ... ]
|
80
129
|
|
81
130
|
req_body = REXML::Element.new('body')
|
82
131
|
req_body.attributes['rid'] = @http_rid
|
@@ -89,7 +138,7 @@ module Jabber
|
|
89
138
|
end
|
90
139
|
req_body.attributes['secure'] = 'true'
|
91
140
|
req_body.attributes['xmlns'] = 'http://jabber.org/protocol/httpbind'
|
92
|
-
res_body = post(req_body)
|
141
|
+
res_body = post(req_body, "sid=new rid=#{@http_rid}")
|
93
142
|
unless res_body.name == 'body'
|
94
143
|
raise 'Response body is no <body/> element'
|
95
144
|
end
|
@@ -125,10 +174,26 @@ module Jabber
|
|
125
174
|
|
126
175
|
##
|
127
176
|
# Close the session by sending
|
128
|
-
# <
|
177
|
+
# <body type='terminate'/>
|
129
178
|
def close
|
130
179
|
@status = DISCONNECTED
|
131
|
-
|
180
|
+
req_body = nil
|
181
|
+
@lock.synchronize {
|
182
|
+
req_body = "<body"
|
183
|
+
req_body += " rid='#{@http_rid += 1}'"
|
184
|
+
req_body += " sid='#{@http_sid}'"
|
185
|
+
req_body += " type='terminate'"
|
186
|
+
req_body += " xmlns='http://jabber.org/protocol/httpbind'"
|
187
|
+
req_body += ">"
|
188
|
+
req_body += "<presence type='unavailable' xmlns='jabber:client'/>"
|
189
|
+
req_body += "</body>"
|
190
|
+
current_rid = @http_rid
|
191
|
+
@pending_requests += 1
|
192
|
+
@last_send = Time.now
|
193
|
+
}
|
194
|
+
res_body = post(req_body, "terminate sid=#{@http_sid} rid=#{@http_rid}")
|
195
|
+
sleep(3)
|
196
|
+
Jabber::debuglog("Connection closed")
|
132
197
|
end
|
133
198
|
|
134
199
|
private
|
@@ -137,32 +202,60 @@ module Jabber
|
|
137
202
|
# Receive stanzas ensuring that the 'rid' order is kept
|
138
203
|
# result:: [REXML::Element]
|
139
204
|
def receive_elements_with_rid(rid, elements)
|
140
|
-
|
141
|
-
@
|
205
|
+
@pending_rid_lock.synchronize do
|
206
|
+
# Wait until rid == @pending_rid
|
207
|
+
if rid > @pending_rid
|
208
|
+
cv = ConditionVariable.new
|
209
|
+
@pending_rid_cv << [rid, cv]
|
210
|
+
@pending_rid_cv.sort!
|
211
|
+
while rid > @pending_rid
|
212
|
+
cv.wait(@pending_rid_lock)
|
213
|
+
end
|
214
|
+
end
|
142
215
|
end
|
143
|
-
@pending_rid = rid + 1
|
144
216
|
|
145
217
|
elements.each { |e|
|
146
218
|
receive(e)
|
147
219
|
}
|
148
220
|
|
149
|
-
|
221
|
+
# Signal waiting elements
|
222
|
+
@pending_rid_lock.synchronize do
|
223
|
+
@pending_rid = rid + 1 # Ensure @pending_rid is modified atomically
|
224
|
+
if @pending_rid_cv.size > 0 && @pending_rid_cv.first.first == @pending_rid
|
225
|
+
next_rid, cv = @pending_rid_cv.shift
|
226
|
+
cv.signal
|
227
|
+
end
|
228
|
+
end
|
150
229
|
end
|
151
230
|
|
152
231
|
##
|
153
232
|
# Do a POST request
|
154
|
-
def post(body)
|
233
|
+
def post(body, debug_info)
|
155
234
|
body = body.to_s
|
156
235
|
request = Net::HTTP::Post.new(@uri.path)
|
157
236
|
request.content_length = body.size
|
158
237
|
request.body = body
|
159
238
|
request['Content-Type'] = @http_content_type
|
160
|
-
Jabber::debuglog("HTTP REQUEST (#{@pending_requests
|
161
|
-
|
162
|
-
|
239
|
+
Jabber::debuglog("HTTP REQUEST (#{@pending_requests}/#{@http_requests}) #{debug_info}:\n#{request.body}")
|
240
|
+
|
241
|
+
net_http_args = [@uri.host, @uri.port]
|
242
|
+
unless @proxy_args.empty?
|
243
|
+
unless no_proxy?(@uri)
|
244
|
+
net_http_args.concat @proxy_args
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
http = Net::HTTP.new(*net_http_args)
|
249
|
+
if @uri.kind_of? URI::HTTPS
|
250
|
+
http.use_ssl = true
|
251
|
+
@http_ssl_setup and @http_ssl_setup.call(http)
|
252
|
+
end
|
253
|
+
http.read_timeout = @http_wait * 1.1
|
254
|
+
|
255
|
+
response = http.start { |http|
|
163
256
|
http.request(request)
|
164
257
|
}
|
165
|
-
Jabber::debuglog("HTTP RESPONSE (#{@pending_requests
|
258
|
+
Jabber::debuglog("HTTP RESPONSE (#{@pending_requests}/#{@http_requests}) #{debug_info}: #{response.class}\n#{response.body}")
|
166
259
|
|
167
260
|
unless response.kind_of? Net::HTTPSuccess
|
168
261
|
# Unfortunately, HTTPResponses aren't exceptions
|
@@ -177,18 +270,30 @@ module Jabber
|
|
177
270
|
body
|
178
271
|
end
|
179
272
|
|
273
|
+
##
|
274
|
+
# Check whether uri should be accessed without proxy
|
275
|
+
def no_proxy?(uri)
|
276
|
+
@no_proxy.each do |host_addr|
|
277
|
+
return true if uri.host.match(Regexp.quote(host_addr) + '$')
|
278
|
+
end
|
279
|
+
return false
|
280
|
+
end
|
281
|
+
|
180
282
|
##
|
181
283
|
# Prepare data to POST and
|
182
284
|
# handle the result
|
183
|
-
def post_data(data)
|
285
|
+
def post_data(data, restart = false)
|
184
286
|
req_body = nil
|
185
287
|
current_rid = nil
|
288
|
+
debug_info = ''
|
186
289
|
|
187
290
|
begin
|
188
291
|
begin
|
189
292
|
@lock.synchronize {
|
190
293
|
# Do not send unneeded requests
|
191
|
-
if data.size < 1 and @pending_requests > 0
|
294
|
+
if data.size < 1 and @pending_requests > 0 and !restart
|
295
|
+
@pending_requests += 1 # compensate for decrement in ensure clause
|
296
|
+
Jabber::debuglog "post_data: not sending excessive poll"
|
192
297
|
return
|
193
298
|
end
|
194
299
|
|
@@ -196,23 +301,27 @@ module Jabber
|
|
196
301
|
req_body += " rid='#{@http_rid += 1}'"
|
197
302
|
req_body += " sid='#{@http_sid}'"
|
198
303
|
req_body += " xmlns='http://jabber.org/protocol/httpbind'"
|
304
|
+
req_body += " xml:lang='en' xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'" if restart
|
199
305
|
req_body += ">"
|
200
|
-
req_body += data
|
306
|
+
req_body += data unless restart
|
201
307
|
req_body += "</body>"
|
202
308
|
current_rid = @http_rid
|
309
|
+
debug_info = "sid=#{@http_sid} rid=#{current_rid}"
|
203
310
|
|
204
311
|
@pending_requests += 1
|
205
312
|
@last_send = Time.now
|
206
313
|
}
|
207
314
|
|
208
|
-
res_body = post(req_body)
|
315
|
+
res_body = post(req_body, debug_info)
|
209
316
|
|
210
317
|
ensure
|
211
|
-
@lock.synchronize {
|
318
|
+
@lock.synchronize {
|
319
|
+
@pending_requests -= 1
|
320
|
+
}
|
212
321
|
end
|
213
322
|
|
214
323
|
receive_elements_with_rid(current_rid, res_body.children)
|
215
|
-
ensure_one_pending_request
|
324
|
+
ensure_one_pending_request if @authenticated
|
216
325
|
|
217
326
|
rescue REXML::ParseException
|
218
327
|
if @exception_block
|
@@ -221,53 +330,78 @@ module Jabber
|
|
221
330
|
close; @exception_block.call(e, self, :parser)
|
222
331
|
end
|
223
332
|
else
|
224
|
-
Jabber::debuglog "Exception caught when parsing HTTP response!"
|
333
|
+
Jabber::debuglog "Exception caught when parsing HTTP response! (#{debug_info})"
|
225
334
|
close
|
226
335
|
raise
|
227
336
|
end
|
228
337
|
|
229
338
|
rescue StandardError => e
|
230
|
-
Jabber::debuglog("POST error (will retry): #{e.class}: #{e}")
|
339
|
+
Jabber::debuglog("POST error (will retry) #{debug_info}: #{e.class}: #{e}, #{e.backtrace}")
|
231
340
|
receive_elements_with_rid(current_rid, [])
|
232
341
|
# It's not good to resend on *any* exception,
|
233
342
|
# but there are too many cases (Timeout, 404, 502)
|
234
343
|
# where resending is appropriate
|
235
344
|
# TODO: recognize these conditions and act appropriate
|
345
|
+
# FIXME: resending the same data with a new rid is wrong. should resend with the same rid
|
236
346
|
send_data(data)
|
237
347
|
end
|
238
348
|
end
|
239
349
|
|
350
|
+
##
|
351
|
+
# Restart stream after SASL authentication
|
352
|
+
def restart
|
353
|
+
Jabber::debuglog("Restarting after SASL")
|
354
|
+
@stream_mechanisms = []
|
355
|
+
@stream_features = {}
|
356
|
+
@features_sem = Semaphore.new
|
357
|
+
send_data('', true) # restart
|
358
|
+
end
|
359
|
+
|
240
360
|
##
|
241
361
|
# Send data,
|
242
362
|
# buffered and obeying 'polling' and 'requests' limits
|
243
|
-
def send_data(data)
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
data = @send_buffer
|
253
|
-
@send_buffer = ''
|
363
|
+
def send_data(data, restart = false)
|
364
|
+
Jabber::debuglog("send_data")
|
365
|
+
|
366
|
+
while true do # exit by return
|
367
|
+
@lock.synchronize do
|
368
|
+
if @last_send + 0.05 >= Time.now
|
369
|
+
Jabber::debuglog("send_data too fast: waiting 0.05sec")
|
370
|
+
next
|
371
|
+
end
|
254
372
|
|
255
|
-
|
256
|
-
|
257
|
-
|
373
|
+
@send_buffer += data
|
374
|
+
limited_by_polling = false
|
375
|
+
if @pending_requests + 1 == @http_requests && @send_buffer.size == 0
|
376
|
+
limited_by_polling = (@last_send + @http_polling >= Time.now)
|
258
377
|
end
|
378
|
+
limited_by_requests = (@pending_requests + 1 > @http_requests)
|
259
379
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
380
|
+
# Can we send?
|
381
|
+
if !limited_by_polling and !limited_by_requests or !@authenticated
|
382
|
+
Jabber::debuglog("send_data non_limited")
|
383
|
+
data = @send_buffer
|
384
|
+
@send_buffer = ''
|
385
|
+
|
386
|
+
Thread.new do
|
387
|
+
Thread.current.abort_on_exception = true
|
388
|
+
post_data(data, restart)
|
389
|
+
end
|
390
|
+
return
|
391
|
+
elsif limited_by_requests
|
392
|
+
Jabber::debuglog("send_data limited by requests")
|
393
|
+
# Do nothing.
|
394
|
+
# When a pending request gets an response, it calls send_data('')
|
395
|
+
return
|
396
|
+
elsif # limited_by_polling && @authenticated
|
397
|
+
# Wait until we get some data to send, or @http_polling expires
|
398
|
+
Jabber::debuglog("send_data limited by polling: #{@http_polling}")
|
399
|
+
else
|
400
|
+
Jabber::errorlog("send_data: can't happen: pending_requests=#{@pending_requests} http_requests=#{@http_requests} send_buffer.size=#{@send_buffer.size} limited_by_polling=#{limited_by_polling} limited_by_requests=#{limited_by_requests}")
|
401
|
+
return
|
268
402
|
end
|
269
403
|
end
|
270
|
-
|
404
|
+
sleep(0.1)
|
271
405
|
end
|
272
406
|
end
|
273
407
|
end
|