Merging more s2s
[prosody.git] / util / stanza.lua
1 local t_insert      =  table.insert;
2 local t_remove      =  table.remove;
3 local s_format      = string.format;
4 local tostring      =      tostring;
5 local setmetatable  =  setmetatable;
6 local pairs         =         pairs;
7 local ipairs        =        ipairs;
8 local type          =          type;
9 local next          =          next;
10 local print          =          print;
11 local unpack        =        unpack;
12 local s_gsub        =   string.gsub;
13
14 local debug = debug;
15 local log = require "util.logger".init("stanza");
16
17 module "stanza"
18
19 stanza_mt = {};
20 stanza_mt.__index = stanza_mt;
21
22 function stanza(name, attr)
23         local stanza = { name = name, attr = attr or {}, tags = {}, last_add = {}};
24         return setmetatable(stanza, stanza_mt);
25 end
26
27 function stanza_mt:query(xmlns)
28         return self:tag("query", { xmlns = xmlns });
29 end
30 function stanza_mt:tag(name, attrs)
31         local s = stanza(name, attrs);
32         (self.last_add[#self.last_add] or self):add_child(s);
33         t_insert(self.last_add, s);
34         return self;
35 end
36
37 function stanza_mt:text(text)
38         (self.last_add[#self.last_add] or self):add_child(text);
39         return self; 
40 end
41
42 function stanza_mt:up()
43         t_remove(self.last_add);
44         return self;
45 end
46
47 function stanza_mt:add_child(child)
48         if type(child) == "table" then
49                 t_insert(self.tags, child);
50         end
51         t_insert(self, child);
52 end
53
54 function stanza_mt:child_with_name(name)
55         for _, child in ipairs(self) do 
56                 if child.name == name then return child; end
57         end
58 end
59
60 function stanza_mt:children()
61         local i = 0;
62         return function (a)
63                         i = i + 1
64                         local v = a[i]
65                         if v then return v; end
66                 end, self, i;
67                                             
68 end
69 function stanza_mt:childtags()
70         local i = 0;
71         return function (a)
72                         i = i + 1
73                         local v = self.tags[i]
74                         if v then return v; end
75                 end, self.tags[1], i;
76                                             
77 end
78
79 do
80         local xml_entities = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
81         function xml_escape(s) return s_gsub(s, "['&<>\"]", xml_entities); end
82 end
83
84 local xml_escape = xml_escape;
85
86 function stanza_mt.__tostring(t)
87         local children_text = "";
88         for n, child in ipairs(t) do
89                 if type(child) == "string" then 
90                         children_text = children_text .. xml_escape(child);
91                 else
92                         children_text = children_text .. tostring(child);
93                 end
94         end
95
96         local attr_string = "";
97         if t.attr then
98                 for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, tostring(v)); end end
99         end
100         return s_format("<%s%s>%s</%s>", t.name, attr_string, children_text, t.name);
101 end
102
103 function stanza_mt.__add(s1, s2)
104         return s1:add_child(s2);
105 end
106
107
108 do
109         local id = 0;
110         function new_id()
111                 id = id + 1;
112                 return "lx"..id;
113         end
114 end
115
116 function preserialize(stanza)
117         local s = { name = stanza.name, attr = stanza.attr };
118         for _, child in ipairs(stanza) do
119                 if type(child) == "table" then
120                         t_insert(s, preserialize(child));
121                 else
122                         t_insert(s, child);
123                 end
124         end
125         return s;
126 end
127
128 function deserialize(stanza)
129         -- Set metatable
130         if stanza then
131                 setmetatable(stanza, stanza_mt);
132                 for _, child in ipairs(stanza) do
133                         if type(child) == "table" then
134                                 deserialize(child);
135                         end
136                 end
137                 if not stanza.tags then
138                         -- Rebuild tags
139                         local tags = {};
140                         for _, child in ipairs(stanza) do
141                                 if type(child) == "table" then
142                                         t_insert(tags, child);
143                                 end
144                         end
145                         stanza.tags = tags;
146                 end
147         end
148         
149         return stanza;
150 end
151
152 function message(attr, body)
153         if not body then
154                 return stanza("message", attr);
155         else
156                 return stanza("message", attr):tag("body"):text(body);
157         end
158 end
159 function iq(attr)
160         if attr and not attr.id then attr.id = new_id(); end
161         return stanza("iq", attr or { id = new_id() });
162 end
163
164 function reply(orig)
165         return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or nil) });
166 end
167
168 function error_reply(orig, type, condition, message, clone)
169         local t = reply(orig);
170         t.attr.type = "error";
171         -- TODO use clone
172         t:tag("error", {type = type})
173                 :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
174         if (message) then t:tag("text"):text(message):up(); end
175         return t; -- stanza ready for adding app-specific errors
176 end
177
178 function presence(attr)
179         return stanza("presence", attr);
180 end
181
182 return _M;