Mercurial > prosody-hg
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 |
| parents | 86fe6e2fa5ae |
| children | 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 |
