Updated to library module ibb
authorMyhailo Danylenko <isbear@ukrpost.net>
Sat, 21 Mar 2009 03:42:49 +0200
changeset 41 9e39fd8a20df
parent 40 4e598287dec4
child 42 18d801679feb
Updated to library module ibb
examples/ibb.lua
examples/mcabberrc.lua
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/ibb.lua	Sat Mar 21 03:42:49 2009 +0200
@@ -0,0 +1,325 @@
+
+-- IN-BAND BYTESTREAMS (XEP-0047)
+
+-- library
+
+require 'lm'
+require 'base64'
+
+iq = { }
+
+function iq.send ( conn, to, smtype, data, success, fail )
+	data.mtype = 'iq-' .. smtype
+	data.to    = to
+	conn:send ( lm.message.create ( data ),
+		function ( conn, mess )
+			local mtype, smtype = mess:type ()
+			if smtype == 'result' then
+				success ()
+			elseif smtype == 'error' then
+				fail ( mess:child( 'error' ):children():name () ) -- FIXME
+			else
+				fail ( mess:xml () )
+				return false
+			end
+			return true
+		end )
+end
+
+-- public
+
+ibb = {
+	block_size    = 4096,
+	streamhandler =
+		function ( accept, reject )
+			reject ()
+		end,
+}
+
+local ibb_sid = 0 -- private
+
+function ibb.send ( conn, to, success, fail, id )
+	local bs    = ibb.block_size -- local instance
+	local sid   = id
+	if not sid then
+		ibb_sid = ibb_sid + 1
+		sid     = 'ibb_' .. ibb_sid
+	end
+	iq.send ( conn, to, 'set',
+		{
+			open = { sid = sid, ['block-size'] = bs, xmlns = 'http://jabber.org/protocol/ibb' }
+		},
+		function ()
+			local seq   = 0
+			local noerr = true
+			success (
+				function ( data, success, fail )
+					if not data  then
+						iq.send ( conn, to, 'set',
+							{
+								close = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb' },
+							},
+							function ()
+								success ( 'end' ) -- XXX
+							end,
+							fail )
+					elseif data ~= '' then
+						local encoded = base64.encode ( data )
+						while encoded:len () > 0 and noerr do
+							local chunk = encoded:sub ( 1, bs )
+							local cseq  = seq -- local instance for closure
+							encoded     = encoded:sub ( bs + 1 )
+							seq         = seq + 1
+							iq.send ( conn, to, 'set',
+								{
+									data = { sid = sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
+										chunk,
+									},
+								},
+								function ()
+									success ( cseq )
+								end,
+								function ( mesg )
+									noerr = false
+									fail ( mesg )
+								end )
+						end
+					end
+				end )
+		end,
+		fail )
+end
+
+-- private
+
+local ibb_files               = {}
+local ibb_handler_registered  = false
+local ibb_incoming_iq_handler = lm.message_handler.new ( 
+	function ( conn, mess )
+		local mtype, smtype = mess:type ()
+		if smtype ~= 'set' then
+			return false
+		end
+
+		local child = mess:children ()
+		if not child or child:attribute ( 'xmlns' ) ~= 'http://jabber.org/protocol/ibb' then
+			return false
+		end
+
+		local id     = mess:attribute ( 'id' )
+		local from   = mess:attribute ( 'from' )
+		local action = child:name ()
+		local sid    = child:attribute ( 'sid' )
+
+		if action == 'open' then
+			if not ibb_files[sid] then
+				ibb.streamhandler ( from,
+					function ( success, fail )
+						ibb_files[sid] = { from = from, success = success, fail = fail }
+						conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+					end,
+					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' }
+								}
+							} )
+					end )
+			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 action == 'data' then
+			local seq = child:attribute ( 'seq' )
+			if ibb_files[sid] and from == ibb_files[sid].from and not ibb_files[sid][tonumber(seq)+1] then
+				local data = child:value ()
+				conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+				ibb_files[sid][tonumber(seq)+1] = data
+-- XXX			ibb_files[sid].success ( seq )
+			else
+				if ibb_files[sid] then
+					ibb_files[sid].fail ( 'conflict' )
+					ibb_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' }
+							}
+						} )
+				else
+					conn:send (
+						lm.message.create { to = from, mtype = 'iq-error', id = id,
+							error = { code = '404', type = 'cancel', -- XXX: check
+								['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+							}
+						} )
+				end
+			end
+		elseif action == 'close' then
+			if ibb_files[sid] and from == ibb_files[sid].from then
+				local data = ''
+				for seq, chunk in ipairs ( ibb_files[sid] ) do
+					data = data .. chunk
+				end
+				local decoded = base64.decode ( data )
+				ibb_files[sid].success ( decoded )
+				conn:send ( lm.message.create { to = from, mtype = 'iq-result', id = id } )
+				ibb_files[sid] = nil
+			else
+				if ibb_files[sid] then
+					ibb_files[sid].fail ( 'conflict' )
+					ibb_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' }
+							}
+						} )
+				else
+					conn:send (
+						lm.message.create { to = from, mtype = 'iq-error', id = id,
+							error = { code = '404', type = 'cancel', -- XXX: check
+								['item-not-found'] = { xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas' }
+							}
+						} )
+				end
+			end
+		else
+			return false
+		end
+
+		return true
+	end )
+
+-- mcabber
+
+local mc_incoming_files = { }
+
+ibb.streamhandler =
+	function ( from, accept, reject )
+		local fid = #mc_incoming_files + 1
+		mc_incoming_files[fid] = {
+				from   = from,
+				accept =
+					function ( name )
+						mc_incoming_files[fid].name = name
+						accept (
+							function ( data )
+								local h = io.open ( mc_incoming_files[fid].name, 'w' )
+								if not h then
+									print ( 'Cannot open output file: ' .. mc_incoming_files[fid].name )
+									return
+								end
+								h:write ( data )
+								h:close ()
+								print ( 'Stream ' .. fid .. ' successfully saved to ' .. mc_incoming_files[fid].name )
+								mc_incoming_files[fid] = nil
+							end,
+							function ( mesg )
+								main.print_info ( from, 'Stream error: ' .. mesg )
+								mc_incoming_files[fid] = nil -- XXX
+							end )
+					end,
+				reject =
+					function ()
+						reject ()
+						print ( 'Stream ' .. fid .. ' rejected' )
+						mc_incoming_files[fid] = nil
+					end,
+		}
+		main.print_info ( from, from .. ' wants you to receive stream. Use /ibb [accept|reject] ' .. fid .. ' to process his request.' )
+	end
+
+main.command ( 'ibb',
+	function ( args )
+		local action = args[1]
+		if action == 'send' then
+			local who
+			if args.t then
+				who = args.t
+			else
+				who = main.full_jid ()
+			end
+			local fname = args[2]
+			ibb.send ( lm.connection.bless ( main.connection () ), who,
+				function ( sender )
+					main.print_info ( who, 'Stream accepted' )
+					local noerr = true
+					local h     = io.open ( fname, 'r' )
+					if not h then
+						print ( 'Cannot open file ' .. fname )
+						return
+					end
+					local data = h:read ( '*a' ) -- In fact, it is better to read it in chunks :/
+					h:close ()
+					local fail =
+						function ( mesg )
+							noerr = false
+							main.print_info ( who, 'Stream error: ' .. mesg )
+						end
+					sender ( data,
+						function ( seq )
+							main.print_info ( who, 'Delivery notification of chunk #' .. seq )
+						end, fail )
+					if noerr then
+						sender ( nil,
+							function ( seq )
+								main.print_info ( who, 'Stream finalizing notification' )
+							end, fail )
+					end
+					if noerr then
+						main.print_info ( who, 'Stream sent' )
+					else
+						main.print_info ( who, 'Stream error occured' )
+					end
+				end,
+				function ( mesg )
+					main.print_info ( who, 'Stream initiation error: ' .. mesg )
+				end )
+		elseif action == 'accept' then
+			local id = tonumber(args[2])
+			if mc_incoming_files[id] then
+				mc_incoming_files[id].accept ( args[3] )
+			end
+		elseif action == 'reject' then
+			local id = tonumber(args[2])
+			if mc_incoming_files[id] then
+				mc_incoming_files[id].reject ()
+			end
+		else
+			local text = ''
+			for sid, data in pairs ( mc_incoming_files ) do
+				text = text .. '\n' ..  sid .. ': ' .. data.from .. ' --> ' .. ( data.name or '?' )
+			end
+			if text ~= '' then
+				print ( 'List of incoming streams:' .. text )
+			else
+				print ( 'No streams' )
+			end
+		end
+	end, true, { "send", "accept", "reject" } )
+
+
+commands_help['ibb'] = "[[-t target_jid] send filename | accept sid filename | reject sid]\n\nRequests, accepts or rejects sending file via in-band bytestream."
+
+hooks_d['hook-post-connect'].ibb =
+	function ( args )
+		lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq', 'normal' )
+		ibb_handler_registered = true
+		hooks_d['hook-post-connect'].ibb = nil
+		hooks_d['hook-quit'].ibb =
+			function ( args )
+				if ibb_handler_registered then
+					lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq' )
+				end
+			end
+	end
+
+main.add_feature ( 'http://jabber.org/protocol/ibb' )
+
+-- vim: se ts=4: --
--- a/examples/mcabberrc.lua	Fri Mar 20 19:39:34 2009 +0200
+++ b/examples/mcabberrc.lua	Sat Mar 21 03:42:49 2009 +0200
@@ -153,7 +153,7 @@
 			print ( "\n /" .. args .. ' ' .. commands_help[args] )
 		else
 			print ( "No help for this command." )
-			list = "Help available for commands: "
+			local list = "Help available for commands: "
 			for k in pairs (commands_help) do
 				list = list .. k .. ', '
 			end
@@ -257,9 +257,9 @@
 
 dopath 'xep0030'
 
--- IN-BOUND BYTESTREAMS (XEP-0047)
+-- IN-BAND BYTESTREAMS (XEP-0047)
 
-dopath 'xep0047'
+dopath 'ibb'
 
 -- PUBLISH-SUBSRIBE (XEP-0060)