util.dataforms: Fix form verification
[prosody.git] / util / dataforms.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 local setmetatable = setmetatable;
10 local pairs, ipairs = pairs, ipairs;
11 local tostring, type, next = tostring, type, next;
12 local t_concat = table.concat;
13 local st = require "util.stanza";
14 local jid_prep = require "util.jid".prep;
15
16 module "dataforms"
17
18 local xmlns_forms = 'jabber:x:data';
19
20 local form_t = {};
21 local form_mt = { __index = form_t };
22
23 function new(layout)
24         return setmetatable(layout, form_mt);
25 end
26
27 function form_t.form(layout, data, formtype)
28         local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" });
29         if layout.title then
30                 form:tag("title"):text(layout.title):up();
31         end
32         if layout.instructions then
33                 form:tag("instructions"):text(layout.instructions):up();
34         end
35         for n, field in ipairs(layout) do
36                 local field_type = field.type or "text-single";
37                 -- Add field tag
38                 form:tag("field", { type = field_type, var = field.name, label = field.label });
39
40                 local value = (data and data[field.name]) or field.value;
41                 
42                 if value then
43                         -- Add value, depending on type
44                         if field_type == "hidden" then
45                                 if type(value) == "table" then
46                                         -- Assume an XML snippet
47                                         form:tag("value")
48                                                 :add_child(value)
49                                                 :up();
50                                 else
51                                         form:tag("value"):text(tostring(value)):up();
52                                 end
53                         elseif field_type == "boolean" then
54                                 form:tag("value"):text((value and "1") or "0"):up();
55                         elseif field_type == "fixed" then
56                                 
57                         elseif field_type == "jid-multi" then
58                                 for _, jid in ipairs(value) do
59                                         form:tag("value"):text(jid):up();
60                                 end
61                         elseif field_type == "jid-single" then
62                                 form:tag("value"):text(value):up();
63                         elseif field_type == "text-single" or field_type == "text-private" then
64                                 form:tag("value"):text(value):up();
65                         elseif field_type == "text-multi" then
66                                 -- Split into multiple <value> tags, one for each line
67                                 for line in value:gmatch("([^\r\n]+)\r?\n*") do
68                                         form:tag("value"):text(line):up();
69                                 end
70                         elseif field_type == "list-single" then
71                                 local has_default = false;
72                                 for _, val in ipairs(value) do
73                                         if type(val) == "table" then
74                                                 form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
75                                                 if val.default and (not has_default) then
76                                                         form:tag("value"):text(val.value):up();
77                                                         has_default = true;
78                                                 end
79                                         else
80                                                 form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
81                                         end
82                                 end
83                         elseif field_type == "list-multi" then
84                                 for _, val in ipairs(value) do
85                                         if type(val) == "table" then
86                                                 form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
87                                                 if val.default then
88                                                         form:tag("value"):text(val.value):up();
89                                                 end
90                                         else
91                                                 form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
92                                         end
93                                 end
94                         end
95                 end
96                 
97                 if field.required then
98                         form:tag("required"):up();
99                 end
100                 
101                 -- Jump back up to list of fields
102                 form:up();
103         end
104         return form;
105 end
106
107 local field_readers = {};
108 local field_verifiers = {};
109
110 function form_t.data(layout, stanza)
111         local data = {};
112         local errors = {};
113
114         for _, field in ipairs(layout) do
115                 local tag;
116                 for field_tag in stanza:childtags() do
117                         if field.name == field_tag.attr.var then
118                                 tag = field_tag;
119                                 break;
120                         end
121                 end
122
123                 if not tag then
124                         if field.required then
125                                 errors[field.name] = "Required value missing";
126                         end
127                 else
128                         local reader = field_readers[field.type];
129                         local verifier = field.verifier or field_verifiers[field.type];
130                         if reader then
131                                 data[field.name] = reader(tag);
132                                 if verifier then
133                                         errors[field.name] = verifier(data[field.name], tag, field.required);
134                                 end
135                         end
136                 end
137         end
138         if next(errors) then
139                 return data, errors;
140         end
141         return data;
142 end
143
144 field_readers["text-single"] =
145         function (field_tag)
146                 local value = field_tag:child_with_name("value");
147                 if value then
148                         return value[1];
149                 end
150         end
151
152 field_verifiers["text-single"] =
153         function (data, field_tag, required)
154                 if ((not data) or (#data == 0)) and required then
155                         return "Required value missing";
156                 end
157         end
158
159 field_readers["text-private"] =
160         field_readers["text-single"];
161
162 field_verifiers["text-private"] =
163         field_verifiers["text-single"];
164
165 field_readers["jid-single"] =
166         field_readers["text-single"];
167
168 field_verifiers["jid-single"] =
169         function (data, field_tag, required)
170                 if ((not data) or (#data == 0)) and required then
171                         return "Required value missing";
172                 end
173                 if not jid_prep(data) then
174                         return "Invalid JID";
175                 end
176         end
177
178 field_readers["jid-multi"] =
179         function (field_tag)
180                 local result = {};
181                 for value_tag in field_tag:childtags() do
182                         if value_tag.name == "value" then
183                                 result[#result+1] = value_tag[1];
184                         end
185                 end
186                 return result;
187         end
188
189 field_verifiers["jid-multi"] =
190         function (data, field_tag, required)
191                 if #data == 0 and required then
192                         return "Required value missing";
193                 end
194
195                 for _, jid in ipairs(data) do
196                         if not jid_prep(jid) then
197                                 return "Invalid JID";
198                         end
199                 end
200         end
201
202 field_readers["text-multi"] =
203         function (field_tag)
204                 local result = {};
205                 for value_tag in field_tag:childtags() do
206                         if value_tag.name == "value" then
207                                 result[#result+1] = value_tag[1];
208                         end
209                 end
210                 return t_concat(result, "\n");
211         end
212
213 field_verifiers["text-multi"] =
214         field_verifiers["text-single"];
215
216 field_readers["list-single"] =
217         field_readers["text-single"];
218
219 field_verifiers["list-single"] =
220         field_verifiers["text-single"];
221
222 field_readers["list-multi"] =
223         function (field_tag)
224                 local result = {};
225                 for value_tag in field_tag:childtags() do
226                         if value_tag.name == "value" then
227                                 result[#result+1] = value_tag[1];
228                         end
229                 end
230                 return result;
231         end
232
233 field_verifiers["list-multi"] =
234         function (data, field_tag, required)
235                 if #data == 0 and required then
236                         return "Required value missing";
237                 end
238         end
239
240 field_readers["boolean"] =
241         function (field_tag)
242                 local value = field_tag:child_with_name("value");
243                 if value then
244                         if value[1] == "1" or value[1] == "true" then
245                                 return true;
246                         else
247                                 return false;
248                         end
249                 end
250         end
251
252 field_verifiers["boolean"] =
253         function (data, field_tag, required)
254                 data = field_readers["text-single"](field_tag);
255                 if #data == 0 and required then
256                         return "Required value missing";
257                 end
258                 if data ~= "1" and data ~= "true" and data ~= "0" and data ~= "false" then
259                         return "Invalid boolean representation";
260                 end
261         end
262
263 field_readers["hidden"] =
264         function (field_tag)
265                 local value = field_tag:child_with_name("value");
266                 if value then
267                         return value[1];
268                 end
269         end
270
271 field_verifiers["hidden"] =
272         function (data, field_tag, required)
273                 return nil;
274         end
275         
276 return _M;
277
278
279 --[=[
280
281 Layout:
282 {
283
284         title = "MUC Configuration",
285         instructions = [[Use this form to configure options for this MUC room.]],
286
287         { name = "FORM_TYPE", type = "hidden", required = true };
288         { name = "field-name", type = "field-type", required = false };
289 }
290
291
292 --]=]