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