File

util/xml.lua @ 13319:6d6291dfe735

net.http: Add simple connection pooling This should speed up repeated requests to the same site by keeping their connections around and sending more requests on them. Sending multiple requests at the same time is not supported, instead a request started while another to the same authority is in progress would open a new one and the first one to complete would go back in the pool. This could be investigated in the future. Some http servers limit the number of requests per connection and this is not tested and could cause one request to fail, but hopefully it will close the connection and prevent it from being reused.
author Kim Alvefur <zash@zash.se>
date Sat, 11 Nov 2023 23:08:34 +0100
parent 12975:d10957394a3c
line wrap: on
line source


local st = require "prosody.util.stanza";
local lxp = require "lxp";
local t_insert = table.insert;
local t_remove = table.remove;
local error = error;

local _ENV = nil;
-- luacheck: std none

local parse_xml = (function()
	local ns_prefixes = {
		["http://www.w3.org/XML/1998/namespace"] = "xml";
	};
	local ns_separator = "\1";
	local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
	return function(xml, options)
		--luacheck: ignore 212/self
		local handler = {};
		local stanza = st.stanza("root");
		local namespaces = {};
		local prefixes = {};
		function handler:StartNamespaceDecl(prefix, url)
			if prefix ~= nil then
				t_insert(namespaces, url);
				t_insert(prefixes, prefix);
			end
		end
		function handler:EndNamespaceDecl(prefix)
			if prefix ~= nil then
				-- we depend on each StartNamespaceDecl having a paired EndNamespaceDecl
				t_remove(namespaces);
				t_remove(prefixes);
			end
		end
		function handler:StartElement(tagname, attr)
			local curr_ns,name = tagname:match(ns_pattern);
			if name == "" then
				curr_ns, name = "", curr_ns;
			end
			if curr_ns ~= "" then
				attr.xmlns = curr_ns;
			end
			for i=1,#attr do
				local k = attr[i];
				attr[i] = nil;
				local ns, nm = k:match(ns_pattern);
				if nm ~= "" then
					ns = ns_prefixes[ns];
					if ns then
						attr[ns..":"..nm] = attr[k];
						attr[k] = nil;
					end
				end
			end
			local n = {}
			for i=1,#namespaces do
				n[prefixes[i]] = namespaces[i];
			end
			stanza:tag(name, attr, n);
		end
		function handler:CharacterData(data)
			stanza:text(data);
		end
		function handler:EndElement()
			stanza:up();
		end
		-- SECURITY: These two handlers, especially the Doctype one, are required to prevent exploits such as Billion Laughs.
		local function restricted_handler(parser)
			if not parser.stop or not parser:stop() then
				error("Failed to abort parsing");
			end
		end
		handler.StartDoctypeDecl = restricted_handler;
		if not options or not options.allow_comments then
			-- NOTE: comments are generally harmless and can be useful when parsing configuration files or other data, even user-provided data
			handler.Comment = restricted_handler;
		end
		if not options or not options.allow_processing_instructions then
			-- Processing instructions should generally be safe to just ignore
			handler.ProcessingInstruction = restricted_handler;
		end
		local parser = lxp.new(handler, ns_separator);
		local ok, err, line, col = parser:parse(xml);
		if ok then ok, err, line, col = parser:parse(); end
		--parser:close();
		if ok then
			return stanza.tags[1];
		else
			return ok, ("%s (line %d, col %d))"):format(err, line, col);
		end
	end;
end)();

return {
	parse = parse_xml;
};