Software /
code /
prosody
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 |