changeset 14153:aa29d5144c20 0.12

util.xmppstream: Support limiting total number of descendent elements in a stanza
author Matthew Wild <mwild1@gmail.com>
date Thu, 16 Apr 2026 18:52:58 +0100
parents fed0dd8dda09
children 363a79f54109
files util/xmppstream.lua
diffstat 1 files changed, 30 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/util/xmppstream.lua	Tue Apr 07 20:35:41 2026 +0200
+++ b/util/xmppstream.lua	Thu Apr 16 18:52:58 2026 +0100
@@ -43,7 +43,7 @@
 
 local function dummy_cb() end
 
-local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
+local function new_sax_handlers(session, stream_callbacks, cb_handleprogress, max_elements)
 	local xml_handlers = {};
 
 	local cb_streamopened = stream_callbacks.streamopened;
@@ -69,6 +69,7 @@
 	local stack = {};
 	local chardata, stanza = {};
 	local stanza_size = 0;
+	local child_quota = 0;
 	local non_streamns_depth = 0;
 	function xml_handlers:StartElement(tagname, attr)
 		if stanza and #chardata > 0 then
@@ -86,6 +87,18 @@
 			non_streamns_depth = non_streamns_depth + 1;
 		end
 
+		if stanza and child_quota then
+			-- Check stanza complexity limits
+			child_quota = child_quota - 1;
+			if child_quota <= 0 then
+				cb_error(session, "parse-error", "resource-constraint", "Stanza exceeds permitted children");
+				if not self.stop or not self:stop() then
+					error("Failed to abort parsing");
+				end
+				return;
+			end
+		end
+
 		for i=1,#attr do
 			local k = attr[i];
 			attr[i] = nil;
@@ -122,6 +135,7 @@
 			end
 
 			stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
+			child_quota = max_elements;
 		else -- we are inside a stanza, so add a tag
 			if lxp_supports_bytecount then
 				stanza_size = stanza_size + self:getcurrentbytecount();
@@ -237,10 +251,18 @@
 		session = new_session;
 	end
 
-	return xml_handlers, { reset = reset, set_session = set_session };
+	local function set_max_elements(new_max_elements)
+		max_elements = new_max_elements;
+	end
+
+	return xml_handlers, {
+		reset = reset;
+		set_session = set_session;
+		set_max_elements = set_max_elements;
+	};
 end
 
-local function new(session, stream_callbacks, stanza_size_limit)
+local function new(session, stream_callbacks, stanza_size_limit, ex)
 	-- Used to track parser progress (e.g. to enforce size limits)
 	local n_outstanding_bytes = 0;
 	local handle_progress;
@@ -253,8 +275,10 @@
 		error("Stanza size limits are not supported on this version of LuaExpat")
 	end
 
+	local max_elements = ex and ex.max_elements;
+
 	local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress);
-	local parser = new_parser(handlers, ns_separator, false);
+	local parser = new_parser(handlers, ns_separator, false, max_elements);
 	local parse = parser.parse;
 
 	function session.open_stream(session, from, to) -- luacheck: ignore 432/session
@@ -298,8 +322,9 @@
 			return ok, err;
 		end,
 		set_session = meta.set_session;
-		set_stanza_size_limit = function (_, new_stanza_size_limit)
+		set_stanza_size_limit = function (_, new_stanza_size_limit, new_max_elements)
 			stanza_size_limit = new_stanza_size_limit;
+			meta.set_max_elements(new_max_elements);
 		end;
 	};
 end