xmpp4r 0.3
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/COPYING +340 -0
- data/ChangeLog +28 -0
- data/LICENSE +59 -0
- data/README +20 -0
- data/Rakefile +103 -0
- data/UPDATING +40 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
- data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
- data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
- data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
- data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
- data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
- data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
- data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
- data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
- data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
- data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
- data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
- data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
- data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
- data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
- data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
- data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
- data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
- data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
- data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
- data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
- data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
- data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
- data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
- data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
- data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
- data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
- data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
- data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
- data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
- data/lib/callbacks.rb +122 -0
- data/lib/xmpp4r/authenticationfailure.rb +13 -0
- data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
- data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
- data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
- data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
- data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
- data/lib/xmpp4r/bytestreams.rb +11 -0
- data/lib/xmpp4r/client.rb +249 -0
- data/lib/xmpp4r/component.rb +103 -0
- data/lib/xmpp4r/connection.rb +166 -0
- data/lib/xmpp4r/dataforms/x/data.rb +248 -0
- data/lib/xmpp4r/dataforms.rb +1 -0
- data/lib/xmpp4r/debuglog.rb +34 -0
- data/lib/xmpp4r/delay/x/delay.rb +100 -0
- data/lib/xmpp4r/delay.rb +1 -0
- data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
- data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
- data/lib/xmpp4r/discovery.rb +2 -0
- data/lib/xmpp4r/error.rb +230 -0
- data/lib/xmpp4r/errorexception.rb +32 -0
- data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
- data/lib/xmpp4r/feature_negotiation.rb +1 -0
- data/lib/xmpp4r/idgenerator.rb +37 -0
- data/lib/xmpp4r/iq.rb +229 -0
- data/lib/xmpp4r/jid.rb +167 -0
- data/lib/xmpp4r/message.rb +171 -0
- data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
- data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
- data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
- data/lib/xmpp4r/muc/x/muc.rb +98 -0
- data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
- data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
- data/lib/xmpp4r/muc.rb +6 -0
- data/lib/xmpp4r/presence.rb +255 -0
- data/lib/xmpp4r/query.rb +43 -0
- data/lib/xmpp4r/rexmladdons.rb +826 -0
- data/lib/xmpp4r/roster/helper/roster.rb +514 -0
- data/lib/xmpp4r/roster/iq/roster.rb +244 -0
- data/lib/xmpp4r/roster/x/roster.rb +155 -0
- data/lib/xmpp4r/roster.rb +4 -0
- data/lib/xmpp4r/sasl.rb +167 -0
- data/lib/xmpp4r/stream.rb +543 -0
- data/lib/xmpp4r/streamparser.rb +77 -0
- data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
- data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
- data/lib/xmpp4r/vcard.rb +3 -0
- data/lib/xmpp4r/version/helper/responder.rb +71 -0
- data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
- data/lib/xmpp4r/version/iq/version.rb +118 -0
- data/lib/xmpp4r/version.rb +3 -0
- data/lib/xmpp4r/x.rb +43 -0
- data/lib/xmpp4r/xmlstanza.rb +174 -0
- data/lib/xmpp4r/xmpp4r.rb +16 -0
- data/lib/xmpp4r.rb +122 -0
- data/setup.rb +1360 -0
- data/test/bytestreams/tc_ibb.rb +186 -0
- data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
- data/test/delay/tc_xdelay.rb +51 -0
- data/test/lib/clienttester.rb +110 -0
- data/test/muc/tc_muc_mucclient.rb +569 -0
- data/test/muc/tc_muc_simplemucclient.rb +72 -0
- data/test/roster/.tc_helper.rb.swp +0 -0
- data/test/roster/tc_helper.rb +389 -0
- data/test/roster/tc_iqqueryroster.rb +140 -0
- data/test/roster/tc_xroster.rb +70 -0
- data/test/tc_callbacks.rb +128 -0
- data/test/tc_class_names.rb +129 -0
- data/test/tc_client.rb +30 -0
- data/test/tc_error.rb +103 -0
- data/test/tc_idgenerator.rb +30 -0
- data/test/tc_iq.rb +109 -0
- data/test/tc_iqquery.rb +31 -0
- data/test/tc_jid.rb +202 -0
- data/test/tc_message.rb +114 -0
- data/test/tc_presence.rb +148 -0
- data/test/tc_stream.rb +182 -0
- data/test/tc_streamError.rb +87 -0
- data/test/tc_streamSend.rb +59 -0
- data/test/tc_streamThreaded.rb +168 -0
- data/test/tc_xmlstanza.rb +76 -0
- data/test/ts_xmpp4r.rb +34 -0
- data/test/vcard/tc_iqvcard.rb +52 -0
- data/test/version/tc_helper.rb +46 -0
- data/test/version/tc_iqqueryversion.rb +96 -0
- data/tools/doctoweb.bash +30 -0
- data/tools/gen_requires.bash +10 -0
- metadata +232 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
require 'callbacks'
|
|
2
|
+
|
|
3
|
+
require 'xmpp4r/bytestreams/iq/si'
|
|
4
|
+
require 'xmpp4r/dataforms/x/data'
|
|
5
|
+
require 'xmpp4r/bytestreams/helper/ibb/base'
|
|
6
|
+
require 'xmpp4r/bytestreams/helper/socks5bytestreams/base'
|
|
7
|
+
|
|
8
|
+
module Jabber
|
|
9
|
+
module FileTransfer
|
|
10
|
+
##
|
|
11
|
+
# The TransferSource is an interface (Mix-in)
|
|
12
|
+
# which sources for FileTransfer#offer should include
|
|
13
|
+
module TransferSource
|
|
14
|
+
##
|
|
15
|
+
# Filename of the offered file
|
|
16
|
+
def filename
|
|
17
|
+
end
|
|
18
|
+
##
|
|
19
|
+
# Mime-type of the offered file, can be nil
|
|
20
|
+
def mime
|
|
21
|
+
end
|
|
22
|
+
##
|
|
23
|
+
# Size of the offered file
|
|
24
|
+
def size
|
|
25
|
+
end
|
|
26
|
+
##
|
|
27
|
+
# MD5-Sum of the offered file, can be nil
|
|
28
|
+
def md5
|
|
29
|
+
end
|
|
30
|
+
##
|
|
31
|
+
# Date of the offered file, can be nil
|
|
32
|
+
def date
|
|
33
|
+
end
|
|
34
|
+
##
|
|
35
|
+
# Read a chunk from the source
|
|
36
|
+
#
|
|
37
|
+
# If this is a ranged transfer, it should
|
|
38
|
+
# implement length checking
|
|
39
|
+
# length:: [Fixnum]
|
|
40
|
+
def read(length=nil)
|
|
41
|
+
end
|
|
42
|
+
##
|
|
43
|
+
# Seek in the source for ranged transfers
|
|
44
|
+
def seek(position)
|
|
45
|
+
end
|
|
46
|
+
##
|
|
47
|
+
# Set the amount of data to send for ranged transfers
|
|
48
|
+
def length=(l)
|
|
49
|
+
end
|
|
50
|
+
##
|
|
51
|
+
# Does implement the methods seek and length= ?
|
|
52
|
+
#
|
|
53
|
+
# FileTransfer will only then offer a ranged transfer.
|
|
54
|
+
# result:: [false] or [true]
|
|
55
|
+
def can_range?
|
|
56
|
+
false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Simple implementation of TransferSource
|
|
62
|
+
# for sending simple files
|
|
63
|
+
# (supports ranged transfers)
|
|
64
|
+
class FileSource
|
|
65
|
+
include TransferSource
|
|
66
|
+
|
|
67
|
+
def initialize(filename)
|
|
68
|
+
@file = File.new(filename)
|
|
69
|
+
@filename = filename
|
|
70
|
+
@bytes_read = 0
|
|
71
|
+
@length = nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def filename
|
|
75
|
+
File::basename @filename
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Everything is 'application/octet-stream'
|
|
80
|
+
def mime
|
|
81
|
+
'application/octet-stream'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def size
|
|
85
|
+
File.size @filename
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def date
|
|
89
|
+
@file.mtime
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Because it can_range?, this method implements length checking
|
|
94
|
+
def read(length=512)
|
|
95
|
+
if @length
|
|
96
|
+
return nil if @bytes_read >= @length # Already read everything requested
|
|
97
|
+
if @bytes_read + length > @length # Will we read more than requested?
|
|
98
|
+
length = @length - @bytes_read # Truncate it!
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
buf = @file.read(length)
|
|
103
|
+
@bytes_read += buf.size if buf
|
|
104
|
+
buf
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def seek(position)
|
|
108
|
+
@file.seek(position)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def length=(l)
|
|
112
|
+
@length = l
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def can_range?
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# The FileTransfer helper provides the ability to respond
|
|
122
|
+
# to incoming and to offer outgoing file-transfers.
|
|
123
|
+
class Helper
|
|
124
|
+
##
|
|
125
|
+
# Set this if you want to use this helper in a Component
|
|
126
|
+
attr_accessor :my_jid
|
|
127
|
+
##
|
|
128
|
+
# Set this to false if you don't want to use SOCKS5Bytestreams
|
|
129
|
+
attr_accessor :allow_bytestreams
|
|
130
|
+
##
|
|
131
|
+
# Set this to false if you don't want to use IBB
|
|
132
|
+
attr_accessor :allow_ibb
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Create a new FileTransfer instance
|
|
136
|
+
def initialize(stream)
|
|
137
|
+
@stream = stream
|
|
138
|
+
@my_jid = nil
|
|
139
|
+
@allow_bytestreams = true
|
|
140
|
+
@allow_ibb = true
|
|
141
|
+
|
|
142
|
+
@incoming_cbs = CallbackList.new
|
|
143
|
+
|
|
144
|
+
@stream.add_iq_callback(150, self) { |iq|
|
|
145
|
+
if iq.type == :set
|
|
146
|
+
file = iq.first_element('si/file')
|
|
147
|
+
field = nil
|
|
148
|
+
iq.each_element('si/feature/x') { |e| field = e.field('stream-method') }
|
|
149
|
+
|
|
150
|
+
if file and field
|
|
151
|
+
@incoming_cbs.process(iq, file)
|
|
152
|
+
true
|
|
153
|
+
else
|
|
154
|
+
false
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
false
|
|
158
|
+
end
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
##
|
|
163
|
+
# Add a callback which will be invoked upon an incoming file-transfer
|
|
164
|
+
#
|
|
165
|
+
# block takes two arguments:
|
|
166
|
+
# * Iq
|
|
167
|
+
# * Bytestreams::IqSiFile in the Iq
|
|
168
|
+
# You may then invoke accept or decline
|
|
169
|
+
def add_incoming_callback(priority = 0, ref = nil, &block)
|
|
170
|
+
@incoming_cbs.add(priority, ref, block)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
##
|
|
174
|
+
# Accept an incoming file-transfer,
|
|
175
|
+
# to be used in a block given to add_incoming_callback
|
|
176
|
+
#
|
|
177
|
+
# offset and length will be ignored if there is no
|
|
178
|
+
# 'si/file/range' in iq.
|
|
179
|
+
# iq:: [Iq] of file-transfer we want to accept
|
|
180
|
+
# offset:: [Fixnum] or [nil]
|
|
181
|
+
# length:: [Fixnum] or [nil]
|
|
182
|
+
# result:: [Bytestreams::SOCKS5BytestreamsTarget] or [Bytestreams::IBBTarget] or [nil] if no valid stream-method
|
|
183
|
+
def accept(iq, offset=nil, length=nil)
|
|
184
|
+
oldsi = iq.first_element('si')
|
|
185
|
+
|
|
186
|
+
answer = iq.answer(false)
|
|
187
|
+
answer.type = :result
|
|
188
|
+
|
|
189
|
+
si = answer.add(Bytestreams::IqSi.new)
|
|
190
|
+
if (offset or length) and oldsi.file.range
|
|
191
|
+
si.add(Bytestreams::IqSiFile.new)
|
|
192
|
+
si.file.add(Bytestreams::IqSiFileRange.new(offset, length))
|
|
193
|
+
end
|
|
194
|
+
si.add(FeatureNegotiation::IqFeature.new.import(oldsi.feature))
|
|
195
|
+
si.feature.x.type = :submit
|
|
196
|
+
stream_method = si.feature.x.field('stream-method')
|
|
197
|
+
|
|
198
|
+
if stream_method.options.keys.include?(Bytestreams::IqQueryBytestreams::NS_BYTESTREAMS) and @allow_bytestreams
|
|
199
|
+
stream_method.values = [Bytestreams::IqQueryBytestreams::NS_BYTESTREAMS]
|
|
200
|
+
stream_method.options = []
|
|
201
|
+
@stream.send(answer)
|
|
202
|
+
|
|
203
|
+
Bytestreams::SOCKS5BytestreamsTarget.new(@stream, oldsi.id, iq.from, iq.to)
|
|
204
|
+
elsif stream_method.options.keys.include?(Bytestreams::IBB::NS_IBB) and @allow_ibb
|
|
205
|
+
stream_method.values = [Bytestreams::IBB::NS_IBB]
|
|
206
|
+
stream_method.options = []
|
|
207
|
+
@stream.send(answer)
|
|
208
|
+
|
|
209
|
+
Bytestreams::IBBTarget.new(@stream, oldsi.id, iq.from, iq.to)
|
|
210
|
+
else
|
|
211
|
+
eanswer = iq.answer(false)
|
|
212
|
+
eanswer.type = :error
|
|
213
|
+
eanswer.add(Error.new('bad-request')).type = :cancel
|
|
214
|
+
eanswer.error.add(REXML::Element.new('no-valid-streams')).add_namespace('http://jabber.org/protocol/si')
|
|
215
|
+
@stream.send(eanswer)
|
|
216
|
+
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
##
|
|
222
|
+
# Decline an incoming file-transfer,
|
|
223
|
+
# to be used in a block given to add_incoming_callback
|
|
224
|
+
# iq:: [Iq] of file-transfer we want to decline
|
|
225
|
+
def decline(iq)
|
|
226
|
+
answer = iq.answer(false)
|
|
227
|
+
answer.type = :error
|
|
228
|
+
error = answer.add(Error.new('forbidden', 'Offer declined'))
|
|
229
|
+
error.type = :cancel
|
|
230
|
+
@stream.send(answer)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
##
|
|
234
|
+
# Offer a file to somebody
|
|
235
|
+
#
|
|
236
|
+
# Will wait for a response from the peer
|
|
237
|
+
#
|
|
238
|
+
# The result is a stream which you can configure, or nil
|
|
239
|
+
# if the peer responded with an invalid stream-method.
|
|
240
|
+
#
|
|
241
|
+
# May raise an ErrorException
|
|
242
|
+
# jid:: [JID] to send the file to
|
|
243
|
+
# source:: File-transfer source, implementing the FileSource interface
|
|
244
|
+
# desc:: [String] or [nil] Optional file description
|
|
245
|
+
# result:: [Bytestreams::SOCKS5BytestreamsInitiator] or [Bytestreams::IBBInitiator] or [nil]
|
|
246
|
+
def offer(jid, source, desc=nil)
|
|
247
|
+
session_id = Jabber::IdGenerator.instance.generate_id
|
|
248
|
+
|
|
249
|
+
offered_methods = {}
|
|
250
|
+
if @allow_bytestreams
|
|
251
|
+
offered_methods[Bytestreams::IqQueryBytestreams::NS_BYTESTREAMS] = nil
|
|
252
|
+
end
|
|
253
|
+
if @allow_ibb
|
|
254
|
+
offered_methods[Bytestreams::IBB::NS_IBB] = nil
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
iq = Iq::new(:set, jid)
|
|
258
|
+
iq.from = @my_jid
|
|
259
|
+
si = iq.add(Bytestreams::IqSi.new(session_id, Bytestreams::IqSi::PROFILE_FILETRANSFER, source.mime))
|
|
260
|
+
|
|
261
|
+
file = si.add(Bytestreams::IqSiFile.new(source.filename, source.size))
|
|
262
|
+
file.hash = source.md5
|
|
263
|
+
file.date = source.date
|
|
264
|
+
file.description = desc if desc
|
|
265
|
+
file.add(Bytestreams::IqSiFileRange.new) if source.can_range?
|
|
266
|
+
|
|
267
|
+
feature = si.add(REXML::Element.new('feature'))
|
|
268
|
+
feature.add_namespace 'http://jabber.org/protocol/feature-neg'
|
|
269
|
+
x = feature.add(Dataforms::XData.new(:form))
|
|
270
|
+
stream_method_field = x.add(Dataforms::XDataField.new('stream-method', :list_single))
|
|
271
|
+
stream_method_field.options = offered_methods
|
|
272
|
+
|
|
273
|
+
begin
|
|
274
|
+
stream_method = nil
|
|
275
|
+
response = nil
|
|
276
|
+
@stream.send_with_id(iq) { |r|
|
|
277
|
+
response = r
|
|
278
|
+
si = response.first_element('si')
|
|
279
|
+
if response.type == :result and si and si.feature and si.feature.x
|
|
280
|
+
stream_method = si.feature.x.field('stream-method').values.first
|
|
281
|
+
|
|
282
|
+
if si.file and si.file.range
|
|
283
|
+
if source.can_range?
|
|
284
|
+
source.seek(si.file.range.offset) if si.file.range.offset
|
|
285
|
+
source.length = si.file.range.length if si.file.range.length
|
|
286
|
+
else
|
|
287
|
+
source.read(si.file.range.offset)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
true
|
|
292
|
+
}
|
|
293
|
+
rescue ErrorException => e
|
|
294
|
+
if e.error.code == 403 # Declined
|
|
295
|
+
return false
|
|
296
|
+
else
|
|
297
|
+
raise e
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
if stream_method == Bytestreams::IqQueryBytestreams::NS_BYTESTREAMS and @allow_bytestreams
|
|
302
|
+
Bytestreams::SOCKS5BytestreamsInitiator.new(@stream, session_id, @my_jid || @stream.jid, jid)
|
|
303
|
+
elsif stream_method == Bytestreams::IBB::NS_IBB and @allow_ibb
|
|
304
|
+
Bytestreams::IBBInitiator.new(@stream, session_id, @my_jid || @stream.jid, jid)
|
|
305
|
+
else # Target responded with a stream_method we didn't offer
|
|
306
|
+
eanswer = response.answer
|
|
307
|
+
eanswer.type = :error
|
|
308
|
+
eanswer.add Error::new('bad-request')
|
|
309
|
+
@stream.send(eanswer)
|
|
310
|
+
nil
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
3
|
+
module Jabber
|
|
4
|
+
module Bytestreams
|
|
5
|
+
##
|
|
6
|
+
# In-Band Bytestreams (JEP-0047) implementation
|
|
7
|
+
#
|
|
8
|
+
# Don't use directly, use IBBInitiator and IBBTarget
|
|
9
|
+
#
|
|
10
|
+
# In-Band Bytestreams should only be used when transferring
|
|
11
|
+
# very small amounts of binary data, because it is slow and
|
|
12
|
+
# increases server load drastically.
|
|
13
|
+
#
|
|
14
|
+
# Note that the constructor takes a lot of arguments. In-Band
|
|
15
|
+
# Bytestreams do not specify a way to initiate the stream,
|
|
16
|
+
# this should be done via Stream Initiation.
|
|
17
|
+
class IBB
|
|
18
|
+
NS_IBB = 'http://jabber.org/protocol/ibb'
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Create a new bytestream
|
|
22
|
+
#
|
|
23
|
+
# Will register a <message/> callback to intercept data
|
|
24
|
+
# of this stream. This data will be buffered, you can retrieve
|
|
25
|
+
# it with receive
|
|
26
|
+
def initialize(stream, session_id, my_jid, peer_jid)
|
|
27
|
+
@stream = stream
|
|
28
|
+
@session_id = session_id
|
|
29
|
+
@my_jid = (my_jid.kind_of?(String) ? JID.new(my_jid) : my_jid)
|
|
30
|
+
@peer_jid = (peer_jid.kind_of?(String) ? JID.new(peer_jid) : peer_jid)
|
|
31
|
+
|
|
32
|
+
@active = false
|
|
33
|
+
@seq_send = 0
|
|
34
|
+
@seq_recv = 0
|
|
35
|
+
@queue = []
|
|
36
|
+
@queue_lock = Mutex.new
|
|
37
|
+
@pending = Mutex.new
|
|
38
|
+
@pending.lock
|
|
39
|
+
@sendbuf = ''
|
|
40
|
+
@sendbuf_lock = Mutex.new
|
|
41
|
+
|
|
42
|
+
@block_size = 4096 # Recommended by JEP0047
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def active?
|
|
46
|
+
@active
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Send data
|
|
51
|
+
#
|
|
52
|
+
# Data is buffered to match block_size in each packet.
|
|
53
|
+
# If you need the data to be sent immediately, use
|
|
54
|
+
# flush afterwards.
|
|
55
|
+
# buf:: [String]
|
|
56
|
+
def write(buf)
|
|
57
|
+
@sendbuf_lock.synchronize {
|
|
58
|
+
@sendbuf += buf
|
|
59
|
+
|
|
60
|
+
while @sendbuf.size >= @block_size
|
|
61
|
+
send_data(@sendbuf[0..@block_size-1])
|
|
62
|
+
@sendbuf = @sendbuf[@block_size..-1].to_s
|
|
63
|
+
end
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Empty the send-buffer by sending remaining data
|
|
69
|
+
def flush
|
|
70
|
+
@sendbuf_lock.synchronize {
|
|
71
|
+
while @sendbuf.size > 0
|
|
72
|
+
send_data(@sendbuf[0..@block_size-1])
|
|
73
|
+
@sendbuf = @sendbuf[@block_size..-1].to_s
|
|
74
|
+
end
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Receive data
|
|
80
|
+
#
|
|
81
|
+
# Will wait until the Message with the next sequence number
|
|
82
|
+
# is in the stanza queue.
|
|
83
|
+
def read
|
|
84
|
+
if active?
|
|
85
|
+
res = nil
|
|
86
|
+
|
|
87
|
+
while res.nil?
|
|
88
|
+
@queue_lock.synchronize {
|
|
89
|
+
@queue.each { |item|
|
|
90
|
+
# Find next data
|
|
91
|
+
if item.type == :data and item.seq == @seq_recv.to_s
|
|
92
|
+
res = item
|
|
93
|
+
break
|
|
94
|
+
# No data? Find close
|
|
95
|
+
elsif item.type == :close and res.nil?
|
|
96
|
+
res = item
|
|
97
|
+
end
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@queue.delete_if { |item| item == res }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# No data? Wait for next to arrive...
|
|
104
|
+
@pending.lock unless res
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if res.type == :data
|
|
108
|
+
@seq_recv += 1
|
|
109
|
+
@seq_recv = 0 if @seq_recv > 65535
|
|
110
|
+
res.data
|
|
111
|
+
elsif res.type == :close
|
|
112
|
+
deactivate
|
|
113
|
+
nil # Closed
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# Close the stream
|
|
122
|
+
#
|
|
123
|
+
# Waits for acknowledge from peer,
|
|
124
|
+
# may throw ErrorException
|
|
125
|
+
def close
|
|
126
|
+
if active?
|
|
127
|
+
flush
|
|
128
|
+
deactivate
|
|
129
|
+
|
|
130
|
+
iq = Iq.new(:set, @peer_jid)
|
|
131
|
+
close = iq.add REXML::Element.new('close')
|
|
132
|
+
close.add_namespace IBB::NS_IBB
|
|
133
|
+
close.attributes['sid'] = @session_id
|
|
134
|
+
|
|
135
|
+
@stream.send_with_id(iq) { |answer|
|
|
136
|
+
answer.type == :result
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Send data directly
|
|
145
|
+
# data:: [String]
|
|
146
|
+
def send_data(databuf)
|
|
147
|
+
if active?
|
|
148
|
+
msg = Message.new
|
|
149
|
+
msg.from = @my_jid
|
|
150
|
+
msg.to = @peer_jid
|
|
151
|
+
|
|
152
|
+
data = msg.add REXML::Element.new('data')
|
|
153
|
+
data.add_namespace NS_IBB
|
|
154
|
+
data.attributes['sid'] = @session_id
|
|
155
|
+
data.attributes['seq'] = @seq_send.to_s
|
|
156
|
+
data.text = Base64::encode64 databuf
|
|
157
|
+
|
|
158
|
+
# TODO: Implement AMP correctly
|
|
159
|
+
amp = msg.add REXML::Element.new('amp')
|
|
160
|
+
amp.add_namespace 'http://jabber.org/protocol/amp'
|
|
161
|
+
deliver_at = amp.add REXML::Element.new('rule')
|
|
162
|
+
deliver_at.attributes['condition'] = 'deliver-at'
|
|
163
|
+
deliver_at.attributes['value'] = 'stored'
|
|
164
|
+
deliver_at.attributes['action'] = 'error'
|
|
165
|
+
match_resource = amp.add REXML::Element.new('rule')
|
|
166
|
+
match_resource.attributes['condition'] = 'match-resource'
|
|
167
|
+
match_resource.attributes['value'] = 'exact'
|
|
168
|
+
match_resource.attributes['action'] = 'error'
|
|
169
|
+
|
|
170
|
+
@stream.send(msg)
|
|
171
|
+
|
|
172
|
+
@seq_send += 1
|
|
173
|
+
@seq_send = 0 if @seq_send > 65535
|
|
174
|
+
else
|
|
175
|
+
raise 'Attempt to send data when not activated'
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def activate
|
|
180
|
+
unless active?
|
|
181
|
+
@stream.add_message_callback(200, self) { |msg|
|
|
182
|
+
data = msg.first_element('data')
|
|
183
|
+
if msg.from == @peer_jid and msg.to == @my_jid and data and data.attributes['sid'] == @session_id
|
|
184
|
+
if msg.type == nil
|
|
185
|
+
@queue_lock.synchronize {
|
|
186
|
+
@queue.push IBBQueueItem.new(:data, data.attributes['seq'], data.text.to_s)
|
|
187
|
+
@pending.unlock
|
|
188
|
+
}
|
|
189
|
+
elsif msg.type == :error
|
|
190
|
+
@queue_lock.synchronize {
|
|
191
|
+
@queue << IBBQueueItem.new(:close)
|
|
192
|
+
@pending.unlock
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
true
|
|
196
|
+
else
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@stream.add_iq_callback(200, self) { |iq|
|
|
202
|
+
close = iq.first_element('close')
|
|
203
|
+
if iq.type == :set and close and close.attributes['sid'] == @session_id
|
|
204
|
+
answer = iq.answer(false)
|
|
205
|
+
answer.type = :result
|
|
206
|
+
@stream.send(answer)
|
|
207
|
+
|
|
208
|
+
@queue_lock.synchronize {
|
|
209
|
+
@queue << IBBQueueItem.new(:close)
|
|
210
|
+
@pending.unlock
|
|
211
|
+
}
|
|
212
|
+
true
|
|
213
|
+
else
|
|
214
|
+
false
|
|
215
|
+
end
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@active = true
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def deactivate
|
|
223
|
+
if active?
|
|
224
|
+
@stream.delete_message_callback(self)
|
|
225
|
+
@stream.delete_iq_callback(self)
|
|
226
|
+
|
|
227
|
+
@active = false
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
##
|
|
233
|
+
# Represents an item in the internal data queue
|
|
234
|
+
class IBBQueueItem
|
|
235
|
+
attr_reader :type, :seq
|
|
236
|
+
def initialize(type, seq=nil, data_text='')
|
|
237
|
+
unless [:data, :close].include? type
|
|
238
|
+
raise "Unknown IBBQueueItem type: #{type}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
@type = type
|
|
242
|
+
@seq = seq
|
|
243
|
+
@data = data_text
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
##
|
|
247
|
+
# Return the Base64-*decoded* data
|
|
248
|
+
#
|
|
249
|
+
# There's no need to catch Exceptions here,
|
|
250
|
+
# as none are thrown.
|
|
251
|
+
def data
|
|
252
|
+
Base64::decode64(@data)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Jabber
|
|
2
|
+
module Bytestreams
|
|
3
|
+
##
|
|
4
|
+
# Implementation of IBB at the initiator side
|
|
5
|
+
class IBBInitiator < IBB
|
|
6
|
+
# You may set the block-size before open
|
|
7
|
+
attr_accessor :block_size
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Open the stream to the peer,
|
|
11
|
+
# waits for successful result
|
|
12
|
+
#
|
|
13
|
+
# May throw ErrorException
|
|
14
|
+
def open
|
|
15
|
+
iq = Iq.new(:set, @peer_jid)
|
|
16
|
+
open = iq.add REXML::Element.new('open')
|
|
17
|
+
open.add_namespace IBB::NS_IBB
|
|
18
|
+
open.attributes['sid'] = @session_id
|
|
19
|
+
open.attributes['block-size'] = @block_size
|
|
20
|
+
|
|
21
|
+
@stream.send_with_id(iq) { |answer|
|
|
22
|
+
answer.type == :result
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
activate
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Jabber
|
|
2
|
+
module Bytestreams
|
|
3
|
+
##
|
|
4
|
+
# Implementation of IBB at the target side
|
|
5
|
+
class IBBTarget < IBB
|
|
6
|
+
# You may read the block-size after accept
|
|
7
|
+
attr_reader :block_size
|
|
8
|
+
|
|
9
|
+
def initialize(stream, session_id, initiator_jid, target_jid)
|
|
10
|
+
# Target and Initiator are swapped here, because we're the target
|
|
11
|
+
super(stream, session_id, target_jid, initiator_jid)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Wait for the initiator side to start
|
|
16
|
+
# the stream.
|
|
17
|
+
def accept
|
|
18
|
+
connect_lock = Mutex.new
|
|
19
|
+
connect_lock.lock
|
|
20
|
+
|
|
21
|
+
@stream.add_iq_callback(200, self) { |iq|
|
|
22
|
+
open = iq.first_element('open')
|
|
23
|
+
if iq.type == :set and iq.from == @peer_jid and iq.to == @my_jid and open and open.attributes['sid'] == @session_id
|
|
24
|
+
@stream.delete_iq_callback(self)
|
|
25
|
+
activate
|
|
26
|
+
@block_size = (open.attributes['block-size'] || 4096).to_i
|
|
27
|
+
|
|
28
|
+
reply = iq.answer(false)
|
|
29
|
+
reply.type = :result
|
|
30
|
+
@stream.send(reply)
|
|
31
|
+
|
|
32
|
+
connect_lock.unlock
|
|
33
|
+
true
|
|
34
|
+
else
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
connect_lock.lock
|
|
40
|
+
connect_lock.unlock
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|