Comparison

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
comparison
equal deleted inserted replaced
6041:a97591d2e1ad 6042:1107d66d2ab2
4 -- 4 --
5 -- This project is MIT/X11 licensed. Please see the 5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information. 6 -- COPYING file in the source package for more information.
7 -- 7 --
8 8
9
10 local lxp = require "lxp"; 9 local lxp = require "lxp";
11 local st = require "util.stanza"; 10 local st = require "util.stanza";
12 local stanza_mt = st.stanza_mt; 11 local stanza_mt = st.stanza_mt;
13 12
14 local error = error; 13 local error = error;
18 local t_remove = table.remove; 17 local t_remove = table.remove;
19 local setmetatable = setmetatable; 18 local setmetatable = setmetatable;
20 19
21 -- COMPAT: w/LuaExpat 1.1.0 20 -- COMPAT: w/LuaExpat 1.1.0
22 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false }); 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;
24
25 local default_stanza_size_limit = 1024*1024*10; -- 10MB
23 26
24 module "xmppstream" 27 module "xmppstream"
25 28
26 local new_parser = lxp.new; 29 local new_parser = lxp.new;
27 30
38 local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$"; 41 local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
39 42
40 _M.ns_separator = ns_separator; 43 _M.ns_separator = ns_separator;
41 _M.ns_pattern = ns_pattern; 44 _M.ns_pattern = ns_pattern;
42 45
43 function new_sax_handlers(session, stream_callbacks) 46 local function dummy_cb() end
47
48 function new_sax_handlers(session, stream_callbacks, cb_handleprogress)
44 local xml_handlers = {}; 49 local xml_handlers = {};
45 50
46 local cb_streamopened = stream_callbacks.streamopened; 51 local cb_streamopened = stream_callbacks.streamopened;
47 local cb_streamclosed = stream_callbacks.streamclosed; 52 local cb_streamclosed = stream_callbacks.streamclosed;
48 local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end; 53 local cb_error = stream_callbacks.error or function(session, e, stanza) error("XML stream error: "..tostring(e)..(stanza and ": "..tostring(stanza) or ""),2); end;
49 local cb_handlestanza = stream_callbacks.handlestanza; 54 local cb_handlestanza = stream_callbacks.handlestanza;
55 cb_handleprogress = cb_handleprogress or dummy_cb;
50 56
51 local stream_ns = stream_callbacks.stream_ns or xmlns_streams; 57 local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
52 local stream_tag = stream_callbacks.stream_tag or "stream"; 58 local stream_tag = stream_callbacks.stream_tag or "stream";
53 if stream_ns ~= "" then 59 if stream_ns ~= "" then
54 stream_tag = stream_ns..ns_separator..stream_tag; 60 stream_tag = stream_ns..ns_separator..stream_tag;
57 63
58 local stream_default_ns = stream_callbacks.default_ns; 64 local stream_default_ns = stream_callbacks.default_ns;
59 65
60 local stack = {}; 66 local stack = {};
61 local chardata, stanza = {}; 67 local chardata, stanza = {};
68 local stanza_size = 0;
62 local non_streamns_depth = 0; 69 local non_streamns_depth = 0;
63 function xml_handlers:StartElement(tagname, attr) 70 function xml_handlers:StartElement(tagname, attr)
64 if stanza and #chardata > 0 then 71 if stanza and #chardata > 0 then
65 -- We have some character data in the buffer 72 -- We have some character data in the buffer
66 t_insert(stanza, t_concat(chardata)); 73 t_insert(stanza, t_concat(chardata));
85 attr[k] = nil; 92 attr[k] = nil;
86 end 93 end
87 end 94 end
88 95
89 if not stanza then --if we are not currently inside a stanza 96 if not stanza then --if we are not currently inside a stanza
97 if lxp_supports_bytecount then
98 stanza_size = self:getcurrentbytecount();
99 end
90 if session.notopen then 100 if session.notopen then
91 if tagname == stream_tag then 101 if tagname == stream_tag then
92 non_streamns_depth = 0; 102 non_streamns_depth = 0;
93 if cb_streamopened then 103 if cb_streamopened then
104 if lxp_supports_bytecount then
105 cb_handleprogress(stanza_size);
106 stanza_size = 0;
107 end
94 cb_streamopened(session, attr); 108 cb_streamopened(session, attr);
95 end 109 end
96 else 110 else
97 -- Garbage before stream? 111 -- Garbage before stream?
98 cb_error(session, "no-stream"); 112 cb_error(session, "no-stream");
103 cb_error(session, "invalid-top-level-element"); 117 cb_error(session, "invalid-top-level-element");
104 end 118 end
105 119
106 stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); 120 stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
107 else -- we are inside a stanza, so add a tag 121 else -- we are inside a stanza, so add a tag
122 if lxp_supports_bytecount then
123 stanza_size = stanza_size + self:getcurrentbytecount();
124 end
108 t_insert(stack, stanza); 125 t_insert(stack, stanza);
109 local oldstanza = stanza; 126 local oldstanza = stanza;
110 stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); 127 stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt);
111 t_insert(oldstanza, stanza); 128 t_insert(oldstanza, stanza);
112 t_insert(oldstanza.tags, stanza); 129 t_insert(oldstanza.tags, stanza);
113 end 130 end
114 end 131 end
132 if lxp_supports_xmldecl then
133 function xml_handlers:XmlDecl(version, encoding, standalone)
134 if lxp_supports_bytecount then
135 cb_handleprogress(self:getcurrentbytecount());
136 end
137 end
138 end
139 function xml_handlers:StartCdataSection()
140 if lxp_supports_bytecount then
141 if stanza then
142 stanza_size = stanza_size + self:getcurrentbytecount();
143 else
144 cb_handleprogress(self:getcurrentbytecount());
145 end
146 end
147 end
148 function xml_handlers:EndCdataSection()
149 if lxp_supports_bytecount then
150 if stanza then
151 stanza_size = stanza_size + self:getcurrentbytecount();
152 else
153 cb_handleprogress(self:getcurrentbytecount());
154 end
155 end
156 end
115 function xml_handlers:CharacterData(data) 157 function xml_handlers:CharacterData(data)
116 if stanza then 158 if stanza then
159 if lxp_supports_bytecount then
160 stanza_size = stanza_size + #data --self:getcurrentbytecount();
161 end
117 t_insert(chardata, data); 162 t_insert(chardata, data);
163 elseif lxp_supports_bytecount then
164 cb_handleprogress(#data--[[self:getcurrentbytecount()]]);
118 end 165 end
119 end 166 end
120 function xml_handlers:EndElement(tagname) 167 function xml_handlers:EndElement(tagname)
168 if lxp_supports_bytecount then
169 stanza_size = stanza_size + self:getcurrentbytecount()
170 end
121 if non_streamns_depth > 0 then 171 if non_streamns_depth > 0 then
122 non_streamns_depth = non_streamns_depth - 1; 172 non_streamns_depth = non_streamns_depth - 1;
123 end 173 end
124 if stanza then 174 if stanza then
125 if #chardata > 0 then 175 if #chardata > 0 then
127 t_insert(stanza, t_concat(chardata)); 177 t_insert(stanza, t_concat(chardata));
128 chardata = {}; 178 chardata = {};
129 end 179 end
130 -- Complete stanza 180 -- Complete stanza
131 if #stack == 0 then 181 if #stack == 0 then
182 if lxp_supports_bytecount then
183 cb_handleprogress(stanza_size);
184 end
185 stanza_size = 0;
132 if tagname ~= stream_error_tag then 186 if tagname ~= stream_error_tag then
133 cb_handlestanza(session, stanza); 187 cb_handlestanza(session, stanza);
134 else 188 else
135 cb_error(session, "stream-error", stanza); 189 cb_error(session, "stream-error", stanza);
136 end 190 end
157 end 211 end
158 xml_handlers.Comment = restricted_handler; 212 xml_handlers.Comment = restricted_handler;
159 xml_handlers.ProcessingInstruction = restricted_handler; 213 xml_handlers.ProcessingInstruction = restricted_handler;
160 214
161 local function reset() 215 local function reset()
162 stanza, chardata = nil, {}; 216 stanza, chardata, stanza_size = nil, {}, 0;
163 stack = {}; 217 stack = {};
164 end 218 end
165 219
166 local function set_session(stream, new_session) 220 local function set_session(stream, new_session)
167 session = new_session; 221 session = new_session;
168 end 222 end
169 223
170 return xml_handlers, { reset = reset, set_session = set_session }; 224 return xml_handlers, { reset = reset, set_session = set_session };
171 end 225 end
172 226
173 function new(session, stream_callbacks) 227 function new(session, stream_callbacks, stanza_size_limit)
174 local handlers, meta = new_sax_handlers(session, stream_callbacks); 228 -- Used to track parser progress (e.g. to enforce size limits)
229 local n_outstanding_bytes = 0;
230 local handle_progress;
231 if lxp_supports_bytecount then
232 function handle_progress(n_parsed_bytes)
233 n_outstanding_bytes = n_outstanding_bytes - n_parsed_bytes;
234 end
235 stanza_size_limit = stanza_size_limit or default_stanza_size_limit;
236 elseif stanza_size_limit then
237 error("Stanza size limits are not supported on this version of LuaExpat")
238 end
239
240 local handlers, meta = new_sax_handlers(session, stream_callbacks, handle_progress);
175 local parser = new_parser(handlers, ns_separator); 241 local parser = new_parser(handlers, ns_separator);
176 local parse = parser.parse; 242 local parse = parser.parse;
177 243
178 return { 244 return {
179 reset = function () 245 reset = function ()
180 parser = new_parser(handlers, ns_separator); 246 parser = new_parser(handlers, ns_separator);
181 parse = parser.parse; 247 parse = parser.parse;
248 n_outstanding_bytes = 0;
182 meta.reset(); 249 meta.reset();
183 end, 250 end,
184 feed = function (self, data) 251 feed = function (self, data)
185 return parse(parser, data); 252 if lxp_supports_bytecount then
253 n_outstanding_bytes = n_outstanding_bytes + #data;
254 end
255 local ok, err = parse(parser, data);
256 if lxp_supports_bytecount and n_outstanding_bytes > stanza_size_limit then
257 return nil, "stanza-too-large";
258 end
259 return ok, err;
186 end, 260 end,
187 set_session = meta.set_session; 261 set_session = meta.set_session;
188 }; 262 };
189 end 263 end
190 264