examples/ibb.lua
changeset 68 742878c74b8e
parent 66 542f61e113cb
equal deleted inserted replaced
67:d33ca5572e91 68:742878c74b8e
     1 
     1 
     2 -- IN-BAND BYTESTREAMS (XEP-0047)
     2 local lm  = require 'lm'
       
     3 local ibb = require 'lm.ibb'
     3 
     4 
     4 -- TODO bidirectionality
     5 local mc_incoming_files = { }
     5 --        thus, on stream accept we can add our sid to incoming files structure,
       
     6 --        as if we received and accepted incoming request.
       
     7 --      message stanzas
       
     8 
     6 
     9 -- library
     7 ibb.handler (
       
     8 	function ( from, accept, reject )
       
     9 		local fid = #mc_incoming_files + 1
       
    10 		mc_incoming_files[fid] = {
       
    11 				from   = from,
       
    12 				accept =
       
    13 					function ( name )
       
    14 						mc_incoming_files[fid].name = name
       
    15 						accept (
       
    16 							function ( data )
       
    17 								local h = io.open ( mc_incoming_files[fid].name, 'w' )
       
    18 								if not h then
       
    19 									print ( 'Cannot open output file: ' .. mc_incoming_files[fid].name )
       
    20 									return
       
    21 								end
       
    22 								h:write ( data )
       
    23 								h:close ()
       
    24 								print ( 'Stream ' .. fid .. ' successfully saved to ' .. mc_incoming_files[fid].name )
       
    25 								mc_incoming_files[fid] = nil
       
    26 							end,
       
    27 							function ( mesg )
       
    28 								main.print_info ( from, 'Stream error: ' .. mesg )
       
    29 								mc_incoming_files[fid] = nil -- XXX
       
    30 							end )
       
    31 					end,
       
    32 				reject =
       
    33 					function ()
       
    34 						reject ()
       
    35 						print ( 'Stream ' .. fid .. ' rejected' )
       
    36 						mc_incoming_files[fid] = nil
       
    37 					end,
       
    38 		}
       
    39 		main.print_info ( from, from .. ' wants you to receive stream. Use /ibb [accept|reject] ' .. fid .. ' to process his request.' )
       
    40 	end )
    10 
    41 
    11 local lm     = require 'lm'
    42 local ibb_sid = 0
    12 local iq     = require 'iq'
       
    13 local base64 = require 'base64'
       
    14 
    43 
    15 --
    44 main.command ( 'ibb',
    16 
    45 	function ( args )
    17 local F = { }
    46 		local action = args[1]
    18 local M = { }
    47 		if action == 'send' then
    19 M.__index = M
    48 			local who
    20 local O = {
    49 			if args.t then
    21 	handler =
    50 				who = args.t
    22 		function ( accept, reject )
    51 			else
    23 			reject ()
    52 				who = main.full_jid ()
    24 		end,
    53 			end
    25 }
    54 			local fname = args[2]
    26 
    55 			local conn  = lm.connection.bless ( main.connection () )
    27 function F.new ( conn, to, bs, sid )
    56 			local sid   = ibb_sid
    28 	local obj = {
    57 			ibb_sid     = ibb_sid + 1
    29 		conn = conn,
    58 			local stream = ibb.new ( conn, who, 4096, sid )
    30 		to   = to,
    59 			stream:open (
    31 		sbs  = bs,
       
    32 		bs   = math.floor ( bs * 3 / 4 ),
       
    33 		sid  = sid,
       
    34 		seq  = 0,
       
    35 	}
       
    36 	setmetatable ( obj, M )
       
    37 	return obj
       
    38 end
       
    39 
       
    40 function M.open ( obj, success, fail )
       
    41 	iq.send ( obj.conn, obj.to, 'set',
       
    42 		{
       
    43 			open = { sid = obj.sid, ['block-size'] = obj.sbs, xmlns = 'http://jabber.org/protocol/ibb' }
       
    44 		}, success, fail )
       
    45 end
       
    46 
       
    47 function M.send ( obj, data, success, fail )
       
    48 	if data and data ~= '' then
       
    49 		local start = 0
       
    50 		while start < data:len () do
       
    51 			local chunk = base64.encode ( data:sub ( start, obj.bs ) )
       
    52 			local cseq  = obj.seq -- local instance
       
    53 			iq.send ( obj.conn, obj.to, 'set',
       
    54 				{
       
    55 					data = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
       
    56 						chunk,
       
    57 					},
       
    58 				},
       
    59 				function ()
    60 				function ()
    60 					success ( cseq )
    61 					main.print_info ( who, 'Stream accepted' )
       
    62 					local noerr = true
       
    63 					local h     = io.open ( fname, 'r' )
       
    64 					if not h then
       
    65 						print ( 'Cannot open file ' .. fname )
       
    66 						return
       
    67 					end
       
    68 					local data = h:read ( '*a' ) -- In fact, it is better to read it in chunks :/
       
    69 					h:close ()
       
    70 					local fail =
       
    71 						function ( mesg )
       
    72 							noerr = false
       
    73 							main.print_info ( who, 'Stream error: ' .. mesg )
       
    74 						end
       
    75 					stream:send ( data,
       
    76 						function ( seq )
       
    77 							main.print_info ( who, 'Delivery notification of chunk #' .. seq )
       
    78 						end, fail )
       
    79 					if noerr then
       
    80 						stream:close (
       
    81 							function ()
       
    82 								main.print_info ( who, 'Stream finalizing notification' )
       
    83 							end, fail )
       
    84 					end
       
    85 					if noerr then
       
    86 						main.print_info ( who, 'Stream sent' )
       
    87 					else
       
    88 						main.print_info ( who, 'Stream error occured' )
       
    89 					end
    61 				end,
    90 				end,
    62 				function ( mesg )
    91 				function ( mesg )
    63 					noerr = false
    92 					main.print_info ( who, 'Stream initiation error: ' .. mesg )
    64 					fail ( mesg )
       
    65 				end )
    93 				end )
    66 			start   = start + obj.bs
    94 		elseif action == 'accept' then
    67 			obj.seq = obj.seq + 1
    95 			local id = tonumber(args[2])
       
    96 			if mc_incoming_files[id] then
       
    97 				mc_incoming_files[id].accept ( args[3] )
       
    98 			end
       
    99 		elseif action == 'reject' then
       
   100 			local id = tonumber(args[2])
       
   101 			if mc_incoming_files[id] then
       
   102 				mc_incoming_files[id].reject ()
       
   103 			end
       
   104 		else
       
   105 			local text = ''
       
   106 			for sid, data in pairs ( mc_incoming_files ) do
       
   107 				text = text .. '\n' ..  sid .. ': ' .. data.from .. ' --> ' .. ( data.name or '?' )
       
   108 			end
       
   109 			if text ~= '' then
       
   110 				print ( 'List of incoming streams:' .. text )
       
   111 			else
       
   112 				print ( 'No streams' )
       
   113 			end
    68 		end
   114 		end
    69 	end
   115 	end, true, { "send", "accept", "reject" } )
    70 end
       
    71 
   116 
    72 function M.close ( obj, success, fail )
       
    73 	iq.send ( obj.conn, obj.to, 'set',
       
    74 		{
       
    75 			close = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb' },
       
    76 		}, success, fail )
       
    77 end
       
    78 
   117 
    79 function F.handler ( handler )
   118 commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid]\n\nRequests, accepts or rejects sending file via in-band bytestream."
    80 	O.handler = handler
       
    81 end
       
    82 
   119 
    83 local ibb_files = {}
   120 local ibb_handler            = lm.message_handler.new ( ibb.iq_handler )
       
   121 local ibb_handler_registered = false
    84 
   122 
    85 function F.iq_handler ( conn, mess )
   123 hooks_d['hook-post-connect'].ibb =
    86 	local mtype, smtype = mess:type ()
   124 	function ( args )
    87 	if smtype ~= 'set' then
   125 		lm.connection.bless( main.connection () ):handler ( ibb_handler, 'iq', 'normal' )
    88 		return false
   126 		ibb_handler_registered = true
       
   127 		hooks_d['hook-post-connect'].ibb = nil
       
   128 		hooks_d['hook-quit'].ibb =
       
   129 			function ( args )
       
   130 				if ibb_handler_registered then
       
   131 					lm.connection.bless( main.connection () ):handler ( ibb_handler, 'iq' )
       
   132 				end
       
   133 			end
    89 	end
   134 	end
    90 
   135 
    91 	local child = mess:child ()
   136 main.add_feature ( 'http://jabber.org/protocol/ibb' )
    92 	if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then
       
    93 		return false
       
    94 	end
       
    95 
       
    96 	local id     = mess:attribute ( 'id' )
       
    97 	local from   = mess:attribute ( 'from' )
       
    98 	local action = child:name ()
       
    99 	local sid    = child:attribute ( 'sid' )
       
   100 
       
   101 	if action == 'open' then
       
   102 		if not ibb_files[sid] then
       
   103 			O.handler ( from,
       
   104 				function ( success, fail )
       
   105 					ibb_files[sid] = { from = from, success = success, fail = fail }
       
   106 					conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
       
   107 				end,
       
   108 				function ()
       
   109 					conn:send (
       
   110 						lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   111 							error = { code = '405', type = 'cancel',
       
   112 								['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
       
   113 							},
       
   114 						} )
       
   115 				end )
       
   116 		else
       
   117 			conn:send (
       
   118 				lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   119 					error = { code = '409', type = 'cancel',
       
   120 						conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
       
   121 					},
       
   122 				} )
       
   123 		end
       
   124 	elseif action == 'data' then
       
   125 		local seq = child:attribute ( 'seq' )
       
   126 		if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then
       
   127 			local data = child:value ()
       
   128 			conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
       
   129 			ibb_files[sid][tonumber(seq)+1] = data
       
   130 -- XXX		ibb_files[sid].success ( seq )
       
   131 		else
       
   132 			if ibb_files[sid] then
       
   133 				ibb_files[sid].fail ( 'conflict' )
       
   134 				ibb_files[sid] = nil -- invalidate session
       
   135 				conn:send (
       
   136 					lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   137 						error = { code = '409', type = 'cancel',
       
   138 							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
       
   139 						},
       
   140 					} )
       
   141 			else
       
   142 				conn:send (
       
   143 					lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   144 						error = { code = '404', type = 'cancel', -- XXX: check
       
   145 							['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
       
   146 						},
       
   147 					} )
       
   148 			end
       
   149 		end
       
   150 	elseif action == 'close' then
       
   151 		if ibb_files[sid] and from == ibb_files[sid].from then
       
   152 			local data = ''
       
   153 			for seq, chunk in ipairs ( ibb_files[sid] ) do
       
   154 				data = data .. chunk
       
   155 			end
       
   156 			local decoded = base64.decode ( data )
       
   157 			ibb_files[sid].success ( decoded )
       
   158 			conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
       
   159 			ibb_files[sid] = nil
       
   160 		else
       
   161 			if ibb_files[sid] then
       
   162 				ibb_files[sid].fail ( 'conflict' )
       
   163 				ibb_files[sid] = nil -- invalidate session
       
   164 				conn:send (
       
   165 					lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   166 						error = { code = '409', type = 'cancel',
       
   167 							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
       
   168 						},
       
   169 					} )
       
   170 			else
       
   171 				conn:send (
       
   172 					lm.message.create { to = from, mtype = 'iq-error', id = id,
       
   173 						error = { code = '404', type = 'cancel', -- XXX: check
       
   174 							['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' },
       
   175 						},
       
   176 					} )
       
   177 			end
       
   178 		end
       
   179 	else
       
   180 		return false
       
   181 	end
       
   182 
       
   183 	return true
       
   184 end
       
   185 
       
   186 return F
       
   187 
   137 
   188 -- vim: se ts=4: --
   138 -- vim: se ts=4: --