xmpp4r 0.5 → 0.5.5
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.
- 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
|