examples/xep0047.lua
changeset 5 cba039bd6f13
child 7 eb6d89bf1fbf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/xep0047.lua	Mon Feb 23 23:23:42 2009 +0200
@@ -0,0 +1,202 @@
+
+receiving_files = {}
+ibb_block_size  = 4096
+current_sid_number = 0
+
+-- FIXME: read from /dev/urandom?
+function gen_unique_sid ()
+	current_sid_number = current_sid_number + 1
+	return 'mc-' .. tostring ( current_sid_number )
+end
+
+ibb_incoming_iq_handler = lm.message_handler.new ( 
+	function ( conn, mess )
+		local id   = mess:attribute ( 'id' )
+		local from = mess:attribute ( 'from' )
+		if mess:child ( 'open' ) and mess:child( 'open' ):attribute ( 'xmlns' ) == 'http://jabber.org/protocol/ibb' then
+			local sid  = mess:child( 'open' ):attribute ( 'sid' )
+			if not receiving_files[sid] then
+				local buffer = ''
+				receiving_files[sid] = { from = from, status = 'pending' }
+				receiving_files[sid].accept =
+					function ( name )
+						main.print_info ( from, string.format ( "Receiving stream from %s, id %s", from, sid ) )
+						conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+						receiving_files[sid].name = name
+						receiving_files[sid].status = 'accepted'
+					end
+				receiving_files[sid].reject =
+					function ()
+						conn:send (
+							lm.message.create { to = from, mtype = 'iq-error', id = id,
+								error = { code = '405', type = 'cancel',
+									['not-allowed'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+								}
+							} )
+						receiving_files[sid].status = 'rejected'
+					end
+				print ( 'You have a new bytestream to receive. To save it use /ibb accept ' .. sid .. ' filename' )
+			else
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '409', type = 'cancel',
+							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+						}
+					} )
+			end
+		elseif mess:child ( 'data' ) and mess:child( 'data' ):attribute ( 'xmlns' ) == 'http://jabber.org/protocol/ibb' then
+			local sid  = mess:child( 'data' ):attribute ( 'sid' )
+			local seq  = mess:child( 'data' ):attribute ( 'seq' )
+			if receiving_files[sid] and from == receiving_files[sid].from and not receiving_files[sid][tonumber(seq)+1] then
+				local data = mess:child( 'data' ):value ()
+				main.print_info ( from, string.format ( " - stream part %s, id %s, %d bytes", seq, sid, data:len() ) )
+				conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+				receiving_files[sid][tonumber(seq)+1] = data
+			else
+				receiving_files[sid] = nil -- invalidate session
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '409', type = 'cancel',
+							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+						}
+					} )
+			end
+		elseif mess:child ( 'close' ) and mess:child( 'close' ):attribute ( 'xmlns' ) == 'http://jabber.org/protocol/ibb' then
+			local sid  = mess:child( 'close' ):attribute ( 'sid' )
+			if receiving_files[sid] and from == receiving_files[sid].from then
+				main.print_info ( from, "Done with stream id " .. sid )
+				conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+				local decoder = io.popen ( string.format ( "base64 -d -i >%q", receiving_files[sid].name ), "w" )
+				if not decoder then
+					main.print_info ( from, "Error opening decoder" )
+				else
+					for i, v in ipairs ( receiving_files[sid] ) do
+						decoder:write ( v )
+					end
+					decoder:close ()
+				end
+			else
+				receiving_files[sid] = nil -- invalidate session
+				conn:send (
+					lm.message.create { to = from, mtype = 'iq-error', id = id,
+						error = { code = '409', type = 'cancel',
+							conflict = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+						}
+					} )
+			end
+		else
+			return false
+		end
+		return true
+	end )
+
+-- You must specify a full jid with resource!
+function send_file ( to, name )
+	local sid = gen_unique_sid ()
+	local conn = lm.connection.bless ( main.connection () )
+	conn:send (
+		lm.message.create { to = to, mtype = 'iq-set',
+			open = { sid = sid, ['block-size'] = ibb_block_size, xmlns = 'http://jabber.org/protocol/ibb' }
+		},
+		function ( conn, message )
+			if message:child ( 'error' ) then
+				main.print_info ( to, "Stream request refused: " .. message:child( 'error' ):children():name () )
+			else
+				main.print_info ( to, "Stream accepted, starting sequence" )
+				local buffer = ''
+				main.bgread ( string.format ( 'base64 -w 0 %q', name ),
+					function ( data )
+						if data then
+							buffer = buffer .. data
+							return true
+						else
+							local seq = 0
+							local msgbuf = buffer:sub ( 1, ibb_block_size )
+							buffer = buffer:sub ( ibb_block_size + 1 )
+							local function handler ( conn, message )
+								if message:child ( 'error' ) then
+									main.print_info ( to, "Stream error, transfer ceased at seq = " .. seq .. ": " .. message:child( 'error' ):children():name () )
+								else
+									main.print_info ( to, " - acquired seq = " .. seq )
+									seq = seq + 1
+									if buffer:len () == 0 then
+										conn:send (
+											lm.message.create { to = to, mtype = 'iq-set',
+												close = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb' }
+											},
+											function ( conn, message )
+												if message:child ( 'error' ) then
+													main.print_info ( to, "Error at closing stream: " .. message:child( 'error' ):children():name () )
+												else
+													main.print_info ( to, "File successfully transferred" )
+												end
+												return true
+											end )
+									else
+										local msgbuf = buffer:sub ( 1, ibb_block_size )
+										buffer = buffer:sub ( ibb_block_size )
+										conn:send (
+											lm.message.create { to = to, mtype = 'iq-set',
+												data = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb', seq = seq,
+													msgbuf
+												}
+											},
+											handler )
+									end
+								end
+								return true
+							end
+							conn:send (
+								lm.message.create { to = to, mtype = 'iq-set',
+									data = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb', seq = seq,
+										msgbuf
+									}
+								},
+								handler )
+							return false
+						end
+					end )
+			end
+			return true
+		end )
+end
+
+main.add_command ( 'ibb',
+	function ( args )
+		args = parse_args ( args )
+		if args[1] == 'send' then
+			local who
+			if args.t then
+				who = args.t
+				args.t = nil
+			else
+				who = full_current_jid ()
+			end
+			args[1] = nil
+			send_file ( who, rebuild_args_string ( args ) )
+		elseif args[1] == 'accept' then
+			local id = args[2]
+			args[1] = nil
+			args[2] = nil
+			if receiving_files[id] then
+				receiving_files[id].accept ( rebuild_args_string ( args ) )
+			end
+		elseif args[1] == 'reject' then
+			local id = args[2]
+			if receiving_files[id] then
+				receiving_files[id].reject ()
+			end
+		elseif args[1] == 'del' then
+			local id = args[2]
+			receiving_files[id] = nil
+		else
+			print ( 'List of incoming streams:' )
+			for sid, data in pairs ( receiving_files ) do
+				print ( sid .. ': ' .. ( data.name or '(not set)' ) .. ' [' .. data.status .. ']' )
+			end
+		end
+	end )
+
+commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid filename | del sid]\n\nRequests, accepts or rejects sending file via in-band bytestream."
+
+-- vim: se ts=4: --