xmpp4r 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/COPYING +340 -0
  2. data/ChangeLog +28 -0
  3. data/LICENSE +59 -0
  4. data/README +20 -0
  5. data/Rakefile +103 -0
  6. data/UPDATING +40 -0
  7. data/data/doc/xmpp4r/examples/advanced/adventure/README +57 -0
  8. data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
  9. data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
  10. data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
  11. data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
  12. data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +425 -0
  13. data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
  14. data/data/doc/xmpp4r/examples/advanced/fileserve.rb +344 -0
  15. data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
  16. data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
  17. data/data/doc/xmpp4r/examples/advanced/migrate.rb +89 -0
  18. data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
  19. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +83 -0
  20. data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +130 -0
  21. data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
  22. data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
  23. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
  24. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
  25. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
  26. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +90 -0
  27. data/data/doc/xmpp4r/examples/advanced/xmpping.rb +134 -0
  28. data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +9 -0
  29. data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
  30. data/data/doc/xmpp4r/examples/basic/client.rb +68 -0
  31. data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
  32. data/data/doc/xmpp4r/examples/basic/echo_nonthreaded.rb +32 -0
  33. data/data/doc/xmpp4r/examples/basic/echo_threaded.rb +32 -0
  34. data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
  35. data/data/doc/xmpp4r/examples/basic/mass_sender.rb +67 -0
  36. data/data/doc/xmpp4r/examples/basic/mucinfo.rb +39 -0
  37. data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +83 -0
  38. data/data/doc/xmpp4r/examples/basic/register.rb +25 -0
  39. data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
  40. data/data/doc/xmpp4r/examples/basic/roster.rb +42 -0
  41. data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
  42. data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
  43. data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +172 -0
  44. data/data/doc/xmpp4r/examples/basic/send_vcard.rb +68 -0
  45. data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
  46. data/data/doc/xmpp4r/examples/buggy/jabber2jabber/jabber2jabber.rb +18 -0
  47. data/data/doc/xmpp4r/examples/buggy/miniedgarr_cgi.rb +192 -0
  48. data/data/doc/xmpp4r/examples/buggy/miniedgarr_watch.rb +82 -0
  49. data/lib/callbacks.rb +122 -0
  50. data/lib/xmpp4r/authenticationfailure.rb +13 -0
  51. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +315 -0
  52. data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +256 -0
  53. data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +30 -0
  54. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +46 -0
  55. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +151 -0
  56. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +85 -0
  57. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +178 -0
  58. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +56 -0
  59. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +61 -0
  60. data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +177 -0
  61. data/lib/xmpp4r/bytestreams/iq/si.rb +221 -0
  62. data/lib/xmpp4r/bytestreams.rb +11 -0
  63. data/lib/xmpp4r/client.rb +249 -0
  64. data/lib/xmpp4r/component.rb +103 -0
  65. data/lib/xmpp4r/connection.rb +166 -0
  66. data/lib/xmpp4r/dataforms/x/data.rb +248 -0
  67. data/lib/xmpp4r/dataforms.rb +1 -0
  68. data/lib/xmpp4r/debuglog.rb +34 -0
  69. data/lib/xmpp4r/delay/x/delay.rb +100 -0
  70. data/lib/xmpp4r/delay.rb +1 -0
  71. data/lib/xmpp4r/discovery/iq/discoinfo.rb +225 -0
  72. data/lib/xmpp4r/discovery/iq/discoitems.rb +162 -0
  73. data/lib/xmpp4r/discovery.rb +2 -0
  74. data/lib/xmpp4r/error.rb +230 -0
  75. data/lib/xmpp4r/errorexception.rb +32 -0
  76. data/lib/xmpp4r/feature_negotiation/iq/feature.rb +42 -0
  77. data/lib/xmpp4r/feature_negotiation.rb +1 -0
  78. data/lib/xmpp4r/idgenerator.rb +37 -0
  79. data/lib/xmpp4r/iq.rb +229 -0
  80. data/lib/xmpp4r/jid.rb +167 -0
  81. data/lib/xmpp4r/message.rb +171 -0
  82. data/lib/xmpp4r/muc/helper/mucbrowser.rb +107 -0
  83. data/lib/xmpp4r/muc/helper/mucclient.rb +382 -0
  84. data/lib/xmpp4r/muc/helper/simplemucclient.rb +222 -0
  85. data/lib/xmpp4r/muc/x/muc.rb +98 -0
  86. data/lib/xmpp4r/muc/x/mucuserinvite.rb +58 -0
  87. data/lib/xmpp4r/muc/x/mucuseritem.rb +148 -0
  88. data/lib/xmpp4r/muc.rb +6 -0
  89. data/lib/xmpp4r/presence.rb +255 -0
  90. data/lib/xmpp4r/query.rb +43 -0
  91. data/lib/xmpp4r/rexmladdons.rb +826 -0
  92. data/lib/xmpp4r/roster/helper/roster.rb +514 -0
  93. data/lib/xmpp4r/roster/iq/roster.rb +244 -0
  94. data/lib/xmpp4r/roster/x/roster.rb +155 -0
  95. data/lib/xmpp4r/roster.rb +4 -0
  96. data/lib/xmpp4r/sasl.rb +167 -0
  97. data/lib/xmpp4r/stream.rb +543 -0
  98. data/lib/xmpp4r/streamparser.rb +77 -0
  99. data/lib/xmpp4r/vcard/helper/vcard.rb +86 -0
  100. data/lib/xmpp4r/vcard/iq/vcard.rb +102 -0
  101. data/lib/xmpp4r/vcard.rb +3 -0
  102. data/lib/xmpp4r/version/helper/responder.rb +71 -0
  103. data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
  104. data/lib/xmpp4r/version/iq/version.rb +118 -0
  105. data/lib/xmpp4r/version.rb +3 -0
  106. data/lib/xmpp4r/x.rb +43 -0
  107. data/lib/xmpp4r/xmlstanza.rb +174 -0
  108. data/lib/xmpp4r/xmpp4r.rb +16 -0
  109. data/lib/xmpp4r.rb +122 -0
  110. data/setup.rb +1360 -0
  111. data/test/bytestreams/tc_ibb.rb +186 -0
  112. data/test/bytestreams/tc_socks5bytestreams.rb +57 -0
  113. data/test/delay/tc_xdelay.rb +51 -0
  114. data/test/lib/clienttester.rb +110 -0
  115. data/test/muc/tc_muc_mucclient.rb +569 -0
  116. data/test/muc/tc_muc_simplemucclient.rb +72 -0
  117. data/test/roster/.tc_helper.rb.swp +0 -0
  118. data/test/roster/tc_helper.rb +389 -0
  119. data/test/roster/tc_iqqueryroster.rb +140 -0
  120. data/test/roster/tc_xroster.rb +70 -0
  121. data/test/tc_callbacks.rb +128 -0
  122. data/test/tc_class_names.rb +129 -0
  123. data/test/tc_client.rb +30 -0
  124. data/test/tc_error.rb +103 -0
  125. data/test/tc_idgenerator.rb +30 -0
  126. data/test/tc_iq.rb +109 -0
  127. data/test/tc_iqquery.rb +31 -0
  128. data/test/tc_jid.rb +202 -0
  129. data/test/tc_message.rb +114 -0
  130. data/test/tc_presence.rb +148 -0
  131. data/test/tc_stream.rb +182 -0
  132. data/test/tc_streamError.rb +87 -0
  133. data/test/tc_streamSend.rb +59 -0
  134. data/test/tc_streamThreaded.rb +168 -0
  135. data/test/tc_xmlstanza.rb +76 -0
  136. data/test/ts_xmpp4r.rb +34 -0
  137. data/test/vcard/tc_iqvcard.rb +52 -0
  138. data/test/version/tc_helper.rb +46 -0
  139. data/test/version/tc_iqqueryversion.rb +96 -0
  140. data/tools/doctoweb.bash +30 -0
  141. data/tools/gen_requires.bash +10 -0
  142. 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
+