examples/lm/ibb.lua
author Myhailo Danylenko <isbear@ukrpost.net>
Wed, 28 Nov 2012 20:17:53 +0200
changeset 146 04d19c9c1196
parent 68 742878c74b8e
permissions -rw-r--r--
Fix module loading problem


-- IN-BAND BYTESTREAMS (XEP-0047)

-- TODO bidirectionality
--        thus, on stream accept we can add our sid to incoming files structure,
--        as if we received and accepted incoming request.
--      message stanzas

-- library

local lm     = require 'lm'
local iq     = require 'lm.iq'
local base64 = require 'base64'

--

local F = { }
local M = { }
M.__index = M
local O = {
	handler =
		function ( accept, reject )
			reject ()
		end,
}

function F.new ( conn, to, bs, sid )
	local obj = {
		conn = conn,
		to   = to,
		sbs  = bs,
		bs   = math.floor ( bs * 3 / 4 ),
		sid  = sid,
		seq  = 0,
	}
	setmetatable ( obj, M )
	return obj
end

function M.open ( obj, success, fail )
	iq.send ( obj.conn, obj.to, 'set',
		{
			open = { sid = obj.sid, ['block-size'] = obj.sbs, xmlns = 'http://jabber.org/protocol/ibb' }
		}, success, fail )
end

function M.send ( obj, data, success, fail )
	if data and data ~= '' then
		local start = 0
		while start < data:len () do
			local chunk = base64.encode ( data:sub ( start, obj.bs ) )
			local cseq  = obj.seq -- local instance
			iq.send ( obj.conn, obj.to, 'set',
				{
					data = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb', seq = cseq,
						chunk,
					},
				},
				function ()
					success ( cseq )
				end,
				function ( mesg )
					noerr = false
					fail ( mesg )
				end )
			start   = start + obj.bs
			obj.seq = obj.seq + 1
		end
	end
end

function M.close ( obj, success, fail )
	iq.send ( obj.conn, obj.to, 'set',
		{
			close = { sid = obj.sid, xmlns = 'http://jabber.org/protocol/ibb' },
		}, success, fail )
end

function F.handler ( handler )
	O.handler = handler
end

local ibb_files = {}

function F.iq_handler ( conn, mess )
	local mtype, smtype = mess:type ()
	if smtype ~= 'set' then
		return false
	end

	local child = mess:child ()
	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
			O.handler ( 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

return F

-- vim: se ts=4: --