2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
9 local lxp
= require
"lxp";
10 local st
= require
"util.stanza";
11 local stanza_mt
= st
.stanza_mt
;
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
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);
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
;
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";
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
));
79 local curr_ns
,name
= tagname
:match(ns_pattern
);
81 curr_ns
, name
= "", curr_ns
;
84 if curr_ns
~= stream_default_ns
or non_streamns_depth
> 0 then
86 non_streamns_depth
= non_streamns_depth
+ 1;
92 local xmlk
= xml_namespace
[k
];
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
);
112 cb_streamopened(session
, attr
);
115 -- Garbage before stream?
116 cb_error(session
, "no-stream", tagname
);
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
140 stanza_size
= stanza_size
+ self
:getcurrentbytecount();
142 cb_handleprogress(self
:getcurrentbytecount());
146 function xml_handlers
:EndCdataSection()
147 if lxp_supports_bytecount
then
149 stanza_size
= stanza_size
+ self
:getcurrentbytecount();
151 cb_handleprogress(self
:getcurrentbytecount());
155 function xml_handlers
:CharacterData(data
)
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;
173 if #chardata
> 0 then
174 -- We have some character data in the buffer
175 t_insert(stanza
, t_concat(chardata
));
180 if lxp_supports_bytecount
then
181 cb_handleprogress(stanza_size
);
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
);
190 cb_error(session
, "stream-error", stanza
);
194 stanza
= t_remove(stack
);
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;
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
;
261 ["xmlns:stream"] = "http://etherx.jabber.org/streams",
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());
278 parser
= new_parser(handlers
, ns_separator
, false);
279 parse
= parser
.parse
;
280 n_outstanding_bytes
= 0;
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
298 set_session
= meta
.set_session
;
303 ns_separator
= ns_separator
;
304 ns_pattern
= ns_pattern
;
305 new_sax_handlers
= new_sax_handlers
;