examples/ibb.lua
changeset 41 9e39fd8a20df
child 43 7c22b1f2c6e5
equal deleted inserted replaced
40:4e598287dec4 41:9e39fd8a20df
       
     1 
       
     2 -- IN-BAND BYTESTREAMS (XEP-0047)
       
     3 
       
     4 -- library
       
     5 
       
     6 require 'lm'
       
     7 require 'base64'
       
     8 
       
     9 iq = { }
       
    10 
       
    11 function iq.send ( conn, to, smtype, data, success, fail )
       
    12 	data.mtype = 'iq-' .. smtype
       
    13 	data.to    = to
       
    14 	conn:send ( lm.message.create ( data ),
       
    15 		function ( conn, mess )
       
    16 			local mtype, smtype = mess:type ()
       
    17 			if smtype == 'result' then
       
    18 				success ()
       
    19 			elseif smtype == 'error' then
       
    20 				fail ( mess:child( 'error' ):children():name () ) -- FIXME
       
    21 			else
       
    22 				fail ( mess:xml () )
       
    23 				return false
       
    24 			end
       
    25 			return true
       
    26 		end )
       
    27 end
       
    28 
       
    29 -- public
       
    30 
       
    31 ibb = {
       
    32 	block_size    = 4096,
       
    33 	streamhandler =
       
    34 		function ( accept, reject )
       
    35 			reject ()
       
    36 		end,
       
    37 }
       
    38 
       
    39 local ibb_sid = 0 -- private
       
    40 
       
    41 function ibb.send ( conn, to, success, fail, id )
       
    42 	local bs    = ibb.block_size -- local instance
       
    43 	local sid   = id
       
    44 	if not sid then
       
    45 		ibb_sid = ibb_sid + 1
       
    46 		sid     = 'ibb_' .. ibb_sid
       
    47 	end
       
    48 	iq.send ( conn, to, 'set',
       
    49 		{
       
    50 			open = { sid = sid, ['block-size'] = bs, xmlns = 'http://jabber.org/protocol/ibb' }
       
    51 		},
       
    52 		function ()
       
    53 			local seq   = 0
       
    54 			local noerr = true
       
    55 			success (
       
    56 				function ( data, success, fail )
       
    57 					if not data  then
       
    58 						iq.send ( conn, to, 'set',
       
    59 							{
       
    60 								close = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb' },
       
    61 							},
       
    62 							function ()
       
    63 								success ( 'end' ) -- XXX
       
    64 							end,
       
    65 							fail )
       
    66 					elseif data ~= '' then
       
    67 						local encoded = base64.encode ( data )
       
    68 						while encoded:len () > 0 and noerr do
       
    69 							local chunk = encoded:sub ( 1, bs )
       
    70 							local cseq  = seq -- local instance for closure
       
    71 							encoded     = encoded:sub ( bs + 1 )
       
    72 							seq         = seq + 1
       
    73 							iq.send ( conn, to, 'set',
       
    74 								{
       
    75 									data = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
       
    76 										chunk,
       
    77 									},
       
    78 								},
       
    79 								function ()
       
    80 									success ( cseq )
       
    81 								end,
       
    82 								function ( mesg )
       
    83 									noerr = false
       
    84 									fail ( mesg )
       
    85 								end )
       
    86 						end
       
    87 					end
       
    88 				end )
       
    89 		end,
       
    90 		fail )
       
    91 end
       
    92 
       
    93 -- private
       
    94 
       
    95 local ibb_files               = {}
       
    96 local ibb_handler_registered  = false
       
    97 local ibb_incoming_iq_handler = lm.message_handler.new ( 
       
    98 	function ( conn, mess )
       
    99 		local mtype, smtype = mess:type ()
       
   100 		if smtype ~= 'set' then
       
   101 			return false
       
   102 		end
       
   103 
       
   104 		local child = mess:children ()
       
   105 		if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then
       
   106 			return false
       
   107 		end
       
   108 
       
   109 		local id     = mess:attribute ( 'id' )
       
   110 		local from   = mess:attribute ( 'from' )
       
   111 		local action = child:name ()
       
   112 		local sid    = child:attribute ( 'sid' )
       
   113 
       
   114 		if action == 'open' then
       
   115 			if not ibb_files[sid] then
       
   116 				ibb.streamhandler ( from,
       
   117 					function ( success, fail )
       
   118 						ibb_files[sid] = { from = from, success = success, fail = fail }
       
   119 						conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
       
   120 					end,
       
   121 					function ()
       
   122 						conn:send (
       
   123 							lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   124 								error = { code = '405', type = 'cancel',
       
   125 									['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
       
   126 								}
       
   127 							} )
       
   128 					end )
       
   129 			else
       
   130 				conn:send (
       
   131 					lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   132 						error = { code = '409', type = 'cancel',
       
   133 							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
       
   134 						}
       
   135 					} )
       
   136 			end
       
   137 		elseif action == 'data' then
       
   138 			local seq = child:attribute ( 'seq' )
       
   139 			if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then
       
   140 				local data = child:value ()
       
   141 				conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
       
   142 				ibb_files[sid][tonumber(seq)+1] = data
       
   143 -- XXX			ibb_files[sid].success ( seq )
       
   144 			else
       
   145 				if ibb_files[sid] then
       
   146 					ibb_files[sid].fail ( 'conflict' )
       
   147 					ibb_files[sid] = nil -- invalidate session
       
   148 					conn:send (
       
   149 						lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   150 							error = { code = '409', type = 'cancel',
       
   151 								conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
       
   152 							}
       
   153 						} )
       
   154 				else
       
   155 					conn:send (
       
   156 						lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   157 							error = { code = '404', type = 'cancel', -- XXX: check
       
   158 								['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
       
   159 							}
       
   160 						} )
       
   161 				end
       
   162 			end
       
   163 		elseif action == 'close' then
       
   164 			if ibb_files[sid] and from == ibb_files[sid].from then
       
   165 				local data = ''
       
   166 				for seq, chunk in ipairs ( ibb_files[sid] ) do
       
   167 					data = data .. chunk
       
   168 				end
       
   169 				local decoded = base64.decode ( data )
       
   170 				ibb_files[sid].success ( decoded )
       
   171 				conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
       
   172 				ibb_files[sid] = nil
       
   173 			else
       
   174 				if ibb_files[sid] then
       
   175 					ibb_files[sid].fail ( 'conflict' )
       
   176 					ibb_files[sid] = nil -- invalidate session
       
   177 					conn:send (
       
   178 						lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   179 							error = { code = '409', type = 'cancel',
       
   180 								conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
       
   181 							}
       
   182 						} )
       
   183 				else
       
   184 					conn:send (
       
   185 						lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   186 							error = { code = '404', type = 'cancel', -- XXX: check
       
   187 								['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
       
   188 							}
       
   189 						} )
       
   190 				end
       
   191 			end
       
   192 		else
       
   193 			return false
       
   194 		end
       
   195 
       
   196 		return true
       
   197 	end )
       
   198 
       
   199 -- mcabber
       
   200 
       
   201 local mc_incoming_files = { }
       
   202 
       
   203 ibb.streamhandler =
       
   204 	function ( from, accept, reject )
       
   205 		local fid = #mc_incoming_files + 1
       
   206 		mc_incoming_files[fid] = {
       
   207 				from   = from,
       
   208 				accept =
       
   209 					function ( name )
       
   210 						mc_incoming_files[fid].name = name
       
   211 						accept (
       
   212 							function ( data )
       
   213 								local h = io.open ( mc_incoming_files[fid].name, 'w' )
       
   214 								if not h then
       
   215 									print ( 'Cannot open output file: ' .. mc_incoming_files[fid].name )
       
   216 									return
       
   217 								end
       
   218 								h:write ( data )
       
   219 								h:close ()
       
   220 								print ( 'Stream ' .. fid .. ' successfully saved to ' .. mc_incoming_files[fid].name )
       
   221 								mc_incoming_files[fid] = nil
       
   222 							end,
       
   223 							function ( mesg )
       
   224 								main.print_info ( from, 'Stream error: ' .. mesg )
       
   225 								mc_incoming_files[fid] = nil -- XXX
       
   226 							end )
       
   227 					end,
       
   228 				reject =
       
   229 					function ()
       
   230 						reject ()
       
   231 						print ( 'Stream ' .. fid .. ' rejected' )
       
   232 						mc_incoming_files[fid] = nil
       
   233 					end,
       
   234 		}
       
   235 		main.print_info ( from, from .. ' wants you to receive stream. Use /ibb [accept|reject] ' .. fid .. ' to process his request.' )
       
   236 	end
       
   237 
       
   238 main.command ( 'ibb',
       
   239 	function ( args )
       
   240 		local action = args[1]
       
   241 		if action == 'send' then
       
   242 			local who
       
   243 			if args.t then
       
   244 				who = args.t
       
   245 			else
       
   246 				who = main.full_jid ()
       
   247 			end
       
   248 			local fname = args[2]
       
   249 			ibb.send ( lm.connection.bless ( main.connection () ), who,
       
   250 				function ( sender )
       
   251 					main.print_info ( who, 'Stream accepted' )
       
   252 					local noerr = true
       
   253 					local h     = io.open ( fname, 'r' )
       
   254 					if not h then
       
   255 						print ( 'Cannot open file ' .. fname )
       
   256 						return
       
   257 					end
       
   258 					local data = h:read ( '*a' ) -- In fact, it is better to read it in chunks :/
       
   259 					h:close ()
       
   260 					local fail =
       
   261 						function ( mesg )
       
   262 							noerr = false
       
   263 							main.print_info ( who, 'Stream error: ' .. mesg )
       
   264 						end
       
   265 					sender ( data,
       
   266 						function ( seq )
       
   267 							main.print_info ( who, 'Delivery notification of chunk #' .. seq )
       
   268 						end, fail )
       
   269 					if noerr then
       
   270 						sender ( nil,
       
   271 							function ( seq )
       
   272 								main.print_info ( who, 'Stream finalizing notification' )
       
   273 							end, fail )
       
   274 					end
       
   275 					if noerr then
       
   276 						main.print_info ( who, 'Stream sent' )
       
   277 					else
       
   278 						main.print_info ( who, 'Stream error occured' )
       
   279 					end
       
   280 				end,
       
   281 				function ( mesg )
       
   282 					main.print_info ( who, 'Stream initiation error: ' .. mesg )
       
   283 				end )
       
   284 		elseif action == 'accept' then
       
   285 			local id = tonumber(args[2])
       
   286 			if mc_incoming_files[id] then
       
   287 				mc_incoming_files[id].accept ( args[3] )
       
   288 			end
       
   289 		elseif action == 'reject' then
       
   290 			local id = tonumber(args[2])
       
   291 			if mc_incoming_files[id] then
       
   292 				mc_incoming_files[id].reject ()
       
   293 			end
       
   294 		else
       
   295 			local text = ''
       
   296 			for sid, data in pairs ( mc_incoming_files ) do
       
   297 				text = text .. '\n' ..  sid .. ': ' .. data.from .. ' --> ' .. ( data.name or '?' )
       
   298 			end
       
   299 			if text ~= '' then
       
   300 				print ( 'List of incoming streams:' .. text )
       
   301 			else
       
   302 				print ( 'No streams' )
       
   303 			end
       
   304 		end
       
   305 	end, true, { "send", "accept", "reject" } )
       
   306 
       
   307 
       
   308 commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid]\n\nRequests, accepts or rejects sending file via in-band bytestream."
       
   309 
       
   310 hooks_d['hook-post-connect'].ibb =
       
   311 	function ( args )
       
   312 		lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq', 'normal' )
       
   313 		ibb_handler_registered = true
       
   314 		hooks_d['hook-post-connect'].ibb = nil
       
   315 		hooks_d['hook-quit'].ibb =
       
   316 			function ( args )
       
   317 				if ibb_handler_registered then
       
   318 					lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq' )
       
   319 				end
       
   320 			end
       
   321 	end
       
   322 
       
   323 main.add_feature ( 'http://jabber.org/protocol/ibb' )
       
   324 
       
   325 -- vim: se ts=4: --