util.x509: Only collect commonNames that pass idna
[prosody.git] / util / xmppstream.lua
blob6aa1def3ce4fef61fdf211797f26a7522b46d951
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
9 local lxp = require "lxp";
10 local st = require "util.stanza";
11 local stanza_mt = st.stanza_mt;
13 local error = error;
14 local tostring = tostring;
15 local t_insert = table.insert;
16 local t_concat = table.concat;
17 local t_remove = table.remove;
18 local setmetatable = setmetatable;
20 -- COMPAT: w/LuaExpat 1.1.0
21 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
22 local lxp_supports_xmldecl = pcall(lxp.new, { XmlDecl = false });
23 local lxp_supports_bytecount = not not lxp.new({}).getcurrentbytecount;
25 local default_stanza_size_limit = 1024*1024*10; -- 10MB
27 local _ENV = nil;
28 -- luacheck: std none
30 local new_parser = lxp.new;
32 local xml_namespace = {
33 ["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang";
34 ["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space";
35 ["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base";
36 ["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id";
39 local xmlns_streams = "http://etherx.jabber.org/streams";
41 local ns_separator = "\1";
42 local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
44 local function dummy_cb() end
46 local function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
47 local xml_handlers = {};
49 local cb_streamopened = stream_callbacks.streamopened;
50 local cb_streamclosed = stream_callbacks.streamclosed;
51 local cb_error = stream_callbacks.error or
52 function(_, e, stanza)
53 error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2);
54 end;
55 local cb_handlestanza = stream_callbacks.handlestanza;
56 cb_handleprogress = cb_handleprogress or dummy_cb;
58 local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
59 local stream_tag = stream_callbacks.stream_tag or "stream";
60 if stream_ns ~= "" then
61 stream_tag = stream_ns..ns_separator..stream_tag;
62 end
63 local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
65 local stream_default_ns = stream_callbacks.default_ns;
67 local stream_lang = "en";
69 local stack = {};
70 local chardata, stanza = {};
71 local stanza_size = 0;
72 local non_streamns_depth = 0;
73 function xml_handlers:StartElement(tagname, attr)
74 if stanza and #chardata > 0 then
75 -- We have some character data in the buffer
76 t_insert(stanza, t_concat(chardata));
77 chardata = {};
78 end
79 local curr_ns,name = tagname:match(ns_pattern);
80 if name == "" then
81 curr_ns, name = "", curr_ns;
82 end
84 if curr_ns ~= stream_default_ns or non_streamns_depth > 0 then
85 attr.xmlns = curr_ns;
86 non_streamns_depth = non_streamns_depth + 1;
87 end
89 for i=1,#attr do
90 local k = attr[i];
91 attr[i] = nil;
92 local xmlk = xml_namespace[k];
93 if xmlk then
94 attr[xmlk] = attr[k];
95 attr[k] = nil;
96 end
97 end
99 if not stanza then --if we are not currently inside a stanza
100 if lxp_supports_bytecount then
101 stanza_size = self:getcurrentbytecount();
103 if session.notopen then
104 if tagname == stream_tag then
105 non_streamns_depth = 0;
106 stream_lang = attr["xml:lang"] or stream_lang;
107 if cb_streamopened then
108 if lxp_supports_bytecount then
109 cb_handleprogress(stanza_size);
110 stanza_size = 0;
112 cb_streamopened(session, attr);
114 else
115 -- Garbage before stream?
116 cb_error(session, "no-stream", tagname);
118 return;
120 if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
121 cb_error(session, "invalid-top-level-element");
124 stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
125 else -- we are inside a stanza, so add a tag
126 if lxp_supports_bytecount then
127 stanza_size = stanza_size + self:getcurrentbytecount();
129 t_insert(stack, stanza);
130 local oldstanza = stanza;
131 stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
132 t_insert(oldstanza, stanza);
133 t_insert(oldstanza.tags, stanza);
137 function xml_handlers:StartCdataSection()
138 if lxp_supports_bytecount then
139 if stanza then
140 stanza_size = stanza_size + self:getcurrentbytecount();
141 else
142 cb_handleprogress(self:getcurrentbytecount());
146 function xml_handlers:EndCdataSection()
147 if lxp_supports_bytecount then
148 if stanza then
149 stanza_size = stanza_size + self:getcurrentbytecount();
150 else
151 cb_handleprogress(self:getcurrentbytecount());
155 function xml_handlers:CharacterData(data)
156 if stanza then
157 if lxp_supports_bytecount then
158 stanza_size = stanza_size + self:getcurrentbytecount();
160 t_insert(chardata, data);
161 elseif lxp_supports_bytecount then
162 cb_handleprogress(self:getcurrentbytecount());
165 function xml_handlers:EndElement(tagname)
166 if lxp_supports_bytecount then
167 stanza_size = stanza_size + self:getcurrentbytecount()
169 if non_streamns_depth > 0 then
170 non_streamns_depth = non_streamns_depth - 1;
172 if stanza then
173 if #chardata > 0 then
174 -- We have some character data in the buffer
175 t_insert(stanza, t_concat(chardata));
176 chardata = {};
178 -- Complete stanza
179 if #stack == 0 then
180 if lxp_supports_bytecount then
181 cb_handleprogress(stanza_size);
183 stanza_size = 0;
184 if stanza.attr["xml:lang"] == nil then
185 stanza.attr["xml:lang"] = stream_lang;
187 if tagname ~= stream_error_tag then
188 cb_handlestanza(session, stanza);
189 else
190 cb_error(session, "stream-error", stanza);
192 stanza = nil;
193 else
194 stanza = t_remove(stack);
196 else
197 if cb_streamclosed then
198 cb_streamclosed(session);
203 local function restricted_handler(parser)
204 cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
205 if not parser.stop or not parser:stop() then
206 error("Failed to abort parsing");
210 if lxp_supports_xmldecl then
211 function xml_handlers:XmlDecl(version, encoding, standalone)
212 if lxp_supports_bytecount then
213 cb_handleprogress(self:getcurrentbytecount());
215 if (encoding and encoding:lower() ~= "utf-8")
216 or (standalone == "no")
217 or (version and version ~= "1.0") then
218 return restricted_handler(self);
222 if lxp_supports_doctype then
223 xml_handlers.StartDoctypeDecl = restricted_handler;
225 xml_handlers.Comment = restricted_handler;
226 xml_handlers.ProcessingInstruction = restricted_handler;
228 local function reset()
229 stanza, chardata, stanza_size = nil, {}, 0;
230 stack = {};
233 local function set_session(stream, new_session) -- luacheck: ignore 212/stream
234 session = new_session;
237 return xml_handlers, { reset = reset, set_session = set_session };
240 local function new(session, stream_callbacks, stanza_size_limit)
241 -- Used to track parser progress (e.g. to enforce size limits)
242 local n_outstanding_bytes = 0;
243 local handle_progress;
244 if lxp_supports_bytecount then
245 function handle_progress(n_parsed_bytes)
246 n_outstanding_bytes = n_outstanding_bytes - n_parsed_bytes;
248 stanza_size_limit = stanza_size_limit or default_stanza_size_limit;
249 elseif stanza_size_limit then
250 error("Stanza size limits are not supported on this version of LuaExpat")
253 local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress);
254 local parser = new_parser(handlers, ns_separator, false);
255 local parse = parser.parse;
257 function session.open_stream(session, from, to) -- luacheck: ignore 432/session
258 local send = session.sends2s or session.send;
260 local attr = {
261 ["xmlns:stream"] = "http://etherx.jabber.org/streams",
262 ["xml:lang"] = "en",
263 xmlns = stream_callbacks.default_ns,
264 version = session.version and (session.version > 0 and "1.0" or nil),
265 id = session.streamid,
266 from = from or session.host, to = to,
268 if session.stream_attrs then
269 session:stream_attrs(from, to, attr)
271 send("<?xml version='1.0'?>");
272 send(st.stanza("stream:stream", attr):top_tag());
273 return true;
276 return {
277 reset = function ()
278 parser = new_parser(handlers, ns_separator, false);
279 parse = parser.parse;
280 n_outstanding_bytes = 0;
281 meta.reset();
282 end,
283 feed = function (self, data) -- luacheck: ignore 212/self
284 if lxp_supports_bytecount then
285 n_outstanding_bytes = n_outstanding_bytes + #data;
287 local _parser = parser;
288 local ok, err = parse(_parser, data);
289 if lxp_supports_bytecount and n_outstanding_bytes > stanza_size_limit then
290 return nil, "stanza-too-large";
292 if parser ~= _parser then
293 _parser:parse();
294 _parser:close();
296 return ok, err;
297 end,
298 set_session = meta.set_session;
302 return {
303 ns_separator = ns_separator;
304 ns_pattern = ns_pattern;
305 new_sax_handlers = new_sax_handlers;
306 new = new;