util.stanza: Optimisation, remove useless if...then in stanza:children() iterator
[prosody.git] / util / xmppstream.lua
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 --
8
9
10 local lxp = require "lxp";
11 local st = require "util.stanza";
12
13 local tostring = tostring;
14 local t_insert = table.insert;
15 local t_concat = table.concat;
16
17 local default_log = require "util.logger".init("xmlhandlers");
18
19 local error = error;
20
21 module "xmppstream"
22
23 local new_parser = lxp.new;
24
25 local ns_prefixes = {
26         ["http://www.w3.org/XML/1998/namespace"] = "xml";
27 };
28
29 local xmlns_streams = "http://etherx.jabber.org/streams";
30
31 local ns_separator = "\1";
32 local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
33
34 function new_sax_handlers(session, stream_callbacks)
35         local xml_handlers = {};
36         
37         local log = session.log or default_log;
38         
39         local cb_streamopened = stream_callbacks.streamopened;
40         local cb_streamclosed = stream_callbacks.streamclosed;
41         local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
42         local cb_handlestanza = stream_callbacks.handlestanza;
43         
44         local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
45         local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
46         local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
47         
48         local stream_default_ns = stream_callbacks.default_ns;
49         
50         local chardata, stanza = {};
51         function xml_handlers:StartElement(tagname, attr)
52                 if stanza and #chardata > 0 then
53                         -- We have some character data in the buffer
54                         stanza:text(t_concat(chardata));
55                         chardata = {};
56                 end
57                 local curr_ns,name = tagname:match(ns_pattern);
58                 if name == "" then
59                         curr_ns, name = "", curr_ns;
60                 end
61
62                 if curr_ns ~= stream_default_ns then
63                         attr.xmlns = curr_ns;
64                 end
65                 
66                 -- FIXME !!!!!
67                 for i=1,#attr do
68                         local k = attr[i];
69                         attr[i] = nil;
70                         local ns, nm = k:match(ns_pattern);
71                         if nm ~= "" then
72                                 ns = ns_prefixes[ns]; 
73                                 if ns then 
74                                         attr[ns..":"..nm] = attr[k];
75                                         attr[k] = nil;
76                                 end
77                         end
78                 end
79                 
80                 if not stanza then --if we are not currently inside a stanza
81                         if session.notopen then
82                                 if tagname == stream_tag then
83                                         if cb_streamopened then
84                                                 cb_streamopened(session, attr);
85                                         end
86                                 else
87                                         -- Garbage before stream?
88                                         cb_error(session, "no-stream");
89                                 end
90                                 return;
91                         end
92                         if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
93                                 cb_error(session, "invalid-top-level-element");
94                         end
95                         
96                         stanza = st.stanza(name, attr);
97                 else -- we are inside a stanza, so add a tag
98                         stanza:tag(name, attr);
99                 end
100         end
101         function xml_handlers:CharacterData(data)
102                 if stanza then
103                         t_insert(chardata, data);
104                 end
105         end
106         function xml_handlers:EndElement(tagname)
107                 if stanza then
108                         if #chardata > 0 then
109                                 -- We have some character data in the buffer
110                                 stanza:text(t_concat(chardata));
111                                 chardata = {};
112                         end
113                         -- Complete stanza
114                         if #stanza.last_add == 0 then
115                                 if tagname ~= stream_error_tag then
116                                         cb_handlestanza(session, stanza);
117                                 else
118                                         cb_error(session, "stream-error", stanza);
119                                 end
120                                 stanza = nil;
121                         else
122                                 stanza:up();
123                         end
124                 else
125                         if tagname == stream_tag then
126                                 if cb_streamclosed then
127                                         cb_streamclosed(session);
128                                 end
129                         else
130                                 local curr_ns,name = tagname:match(ns_pattern);
131                                 if name == "" then
132                                         curr_ns, name = "", curr_ns;
133                                 end
134                                 cb_error(session, "parse-error", "unexpected-element-close", name);
135                         end
136                         stanza, chardata = nil, {};
137                 end
138         end
139         
140         local function reset()
141                 stanza, chardata = nil, {};
142         end
143         
144         local function set_session(stream, new_session)
145                 session = new_session;
146                 log = new_session.log or default_log;
147         end
148         
149         return xml_handlers, { reset = reset, set_session = set_session };
150 end
151
152 function new(session, stream_callbacks)
153         local handlers, meta = new_sax_handlers(session, stream_callbacks);
154         local parser = new_parser(handlers, ns_separator);
155         local parse = parser.parse;
156
157         return {
158                 reset = function ()
159                         parser = new_parser(handlers, ns_separator);
160                         parse = parser.parse;
161                         meta.reset();
162                 end,
163                 feed = function (self, data)
164                         return parse(parser, data);
165                 end,
166                 set_session = meta.set_session;
167         };
168 end
169
170 return _M;