Diff

util/xmppstream.lua @ 6042:1107d66d2ab2

util.xmppstream: Implement stanza size limiting, default limit 10MB
author Matthew Wild <mwild1@gmail.com>
date Sun, 30 Mar 2014 09:14:39 +0100
parent 5311:86fe6e2fa5ae
child 6052:ce3244c084f9
line wrap: on
line diff
--- a/util/xmppstream.lua	Sun Mar 30 08:44:55 2014 +0100
+++ b/util/xmppstream.lua	Sun Mar 30 09:14:39 2014 +0100
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
 local lxp = require "lxp";
 local st = require "util.stanza";
 local stanza_mt = st.stanza_mt;
@@ -20,6 +19,10 @@
 
 -- COMPAT: w/LuaExpat 1.1.0
 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
+local lxp_supports_xmldecl = pcall(lxp.new, { XmlDecl = false });
+local lxp_supports_bytecount = not not lxp.new({}).getcurrentbytecount;
+
+local default_stanza_size_limit = 1024*1024*10; -- 10MB
 
 module "xmppstream"
 
@@ -40,13 +43,16 @@
 _M.ns_separator = ns_separator;
 _M.ns_pattern = ns_pattern;
 
-function new_sax_handlers(session, stream_callbacks)
+local function dummy_cb() end
+
+function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
 	local xml_handlers = {};
 	
 	local cb_streamopened = stream_callbacks.streamopened;
 	local cb_streamclosed = stream_callbacks.streamclosed;
 	local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end;
 	local cb_handlestanza = stream_callbacks.handlestanza;
+	cb_handleprogress = cb_handleprogress or dummy_cb;
 	
 	local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
 	local stream_tag = stream_callbacks.stream_tag or "stream";
@@ -59,6 +65,7 @@
 	
 	local stack = {};
 	local chardata, stanza = {};
+	local stanza_size = 0;
 	local non_streamns_depth = 0;
 	function xml_handlers:StartElement(tagname, attr)
 		if stanza and #chardata > 0 then
@@ -87,10 +94,17 @@
 		end
 		
 		if not stanza then --if we are not currently inside a stanza
+			if lxp_supports_bytecount then
+				stanza_size = self:getcurrentbytecount();
+			end
 			if session.notopen then
 				if tagname == stream_tag then
 					non_streamns_depth = 0;
 					if cb_streamopened then
+						if lxp_supports_bytecount then
+							cb_handleprogress(stanza_size);
+							stanza_size = 0;
+						end
 						cb_streamopened(session, attr);
 					end
 				else
@@ -105,6 +119,9 @@
 			
 			stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
 		else -- we are inside a stanza, so add a tag
+			if lxp_supports_bytecount then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			end
 			t_insert(stack, stanza);
 			local oldstanza = stanza;
 			stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
@@ -112,12 +129,45 @@
 			t_insert(oldstanza.tags, stanza);
 		end
 	end
+	if lxp_supports_xmldecl then
+		function xml_handlers:XmlDecl(version, encoding, standalone)
+			if lxp_supports_bytecount then
+				cb_handleprogress(self:getcurrentbytecount());
+			end
+		end
+	end
+	function xml_handlers:StartCdataSection()
+		if lxp_supports_bytecount then
+			if stanza then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			else
+				cb_handleprogress(self:getcurrentbytecount());
+			end
+		end
+	end
+	function xml_handlers:EndCdataSection()
+		if lxp_supports_bytecount then
+			if stanza then
+				stanza_size = stanza_size + self:getcurrentbytecount();
+			else
+				cb_handleprogress(self:getcurrentbytecount());
+			end
+		end
+	end
 	function xml_handlers:CharacterData(data)
 		if stanza then
+			if lxp_supports_bytecount then
+				stanza_size = stanza_size + #data --self:getcurrentbytecount();
+			end
 			t_insert(chardata, data);
+		elseif lxp_supports_bytecount then
+			cb_handleprogress(#data--[[self:getcurrentbytecount()]]);
 		end
 	end
 	function xml_handlers:EndElement(tagname)
+		if lxp_supports_bytecount then
+			stanza_size = stanza_size + self:getcurrentbytecount()
+		end
 		if non_streamns_depth > 0 then
 			non_streamns_depth = non_streamns_depth - 1;
 		end
@@ -129,6 +179,10 @@
 			end
 			-- Complete stanza
 			if #stack == 0 then
+				if lxp_supports_bytecount then
+					cb_handleprogress(stanza_size);
+				end
+				stanza_size = 0;
 				if tagname ~= stream_error_tag then
 					cb_handlestanza(session, stanza);
 				else
@@ -159,7 +213,7 @@
 	xml_handlers.ProcessingInstruction = restricted_handler;
 	
 	local function reset()
-		stanza, chardata = nil, {};
+		stanza, chardata, stanza_size = nil, {}, 0;
 		stack = {};
 	end
 	
@@ -170,8 +224,20 @@
 	return xml_handlers, { reset = reset, set_session = set_session };
 end
 
-function new(session, stream_callbacks)
-	local handlers, meta = new_sax_handlers(session, stream_callbacks);
+function new(session, stream_callbacks, stanza_size_limit)
+	-- Used to track parser progress (e.g. to enforce size limits)
+	local n_outstanding_bytes = 0;
+	local handle_progress;
+	if lxp_supports_bytecount then
+		function handle_progress(n_parsed_bytes)
+			n_outstanding_bytes = n_outstanding_bytes - n_parsed_bytes;
+		end
+		stanza_size_limit = stanza_size_limit or default_stanza_size_limit;
+	elseif stanza_size_limit then
+		error("Stanza size limits are not supported on this version of LuaExpat")
+	end
+
+	local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress);
 	local parser = new_parser(handlers, ns_separator);
 	local parse = parser.parse;
 
@@ -179,10 +245,18 @@
 		reset = function ()
 			parser = new_parser(handlers, ns_separator);
 			parse = parser.parse;
+			n_outstanding_bytes = 0;
 			meta.reset();
 		end,
 		feed = function (self, data)
-			return parse(parser, data);
+			if lxp_supports_bytecount then
+				n_outstanding_bytes = n_outstanding_bytes + #data;
+			end
+			local ok, err = parse(parser, data);
+			if lxp_supports_bytecount and n_outstanding_bytes > stanza_size_limit then
+				return nil, "stanza-too-large";
+			end
+			return ok, err;
 		end,
 		set_session = meta.set_session;
 	};