util.dataforms: Add field verification logic
[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                 local reader = field_readers[field.type];
124                 local verifier = field.verifier or field_verifiers[field.type];
125                 if reader then
126                         data[field.name] = reader(tag);
127                         if verifier then
128                                 errors[field.name] = verifier(data[field.name], tag, field.required);
129                         end
130                 end
131         end
132         if next(errors) then
133                 return data, errors;
134         end
135         return data;
136 end
137
138 field_readers["text-single"] =
139         function (field_tag)
140                 local value = field_tag:child_with_name("value");
141                 if value then
142                         return value[1];
143                 end
144         end
145
146 field_verifiers["text-single"] =
147         function (data, field_tag, required)
148                 if ((not data) or (#data == 0)) and required then
149                         return "Required value missing";
150                 end
151         end
152
153 field_readers["text-private"] =
154         field_readers["text-single"];
155
156 field_verifiers["text-private"] =
157         field_verifiers["text-single"];
158
159 field_readers["jid-single"] =
160         field_readers["text-single"];
161
162 field_verifiers["jid-single"] =
163         function (data, field_tag, required)
164                 if #data == 0 and required then
165                         return "Required value missing";
166                 end
167                 if not jid_prep(data) then
168                         return "Invalid JID";
169                 end
170         end
171
172 field_readers["jid-multi"] =
173         function (field_tag)
174                 local result = {};
175                 for value_tag in field_tag:childtags() do
176                         if value_tag.name == "value" then
177                                 result[#result+1] = value_tag[1];
178                         end
179                 end
180                 return result;
181         end
182
183 field_verifiers["jid-multi"] =
184         function (data, field_tag, required)
185                 if #data == 0 and required then
186                         return "Required value missing";
187                 end
188
189                 for _, jid in ipairs(data) do
190                         if not jid_prep(jid) then
191                                 return "Invalid JID";
192                         end
193                 end
194         end
195
196 field_readers["text-multi"] =
197         function (field_tag)
198                 local result = {};
199                 for value_tag in field_tag:childtags() do
200                         if value_tag.name == "value" then
201                                 result[#result+1] = value_tag[1];
202                         end
203                 end
204                 return t_concat(result, "\n");
205         end
206
207 field_verifiers["text-multi"] =
208         field_verifiers["text-single"];
209
210 field_readers["list-single"] =
211         field_readers["text-single"];
212
213 field_verifiers["list-single"] =
214         field_verifiers["text-single"];
215
216 field_readers["list-multi"] =
217         function (field_tag)
218                 local result = {};
219                 for value_tag in field_tag:childtags() do
220                         if value_tag.name == "value" then
221                                 result[#result+1] = value_tag[1];
222                         end
223                 end
224                 return result;
225         end
226
227 field_verifiers["list-multi"] =
228         function (data, field_tag, required)
229                 if #data == 0 and required then
230                         return "Required value missing";
231                 end
232         end
233
234 field_readers["boolean"] =
235         function (field_tag)
236                 local value = field_tag:child_with_name("value");
237                 if value then
238                         if value[1] == "1" or value[1] == "true" then
239                                 return true;
240                         else
241                                 return false;
242                         end
243                 end
244         end
245
246 field_verifiers["boolean"] =
247         function (data, field_tag, required)
248                 data = field_readers["text-single"](field_tag);
249                 if #data == 0 and required then
250                         return "Required value missing";
251                 end
252                 if data ~= "1" and data ~= "true" and data ~= "0" and data ~= "false" then
253                         return "Invalid boolean representation";
254                 end
255         end
256
257 field_readers["hidden"] =
258         function (field_tag)
259                 local value = field_tag:child_with_name("value");
260                 if value then
261                         return value[1];
262                 end
263         end
264
265 field_verifiers["hidden"] =
266         function (data, field_tag, required)
267                 return nil;
268         end
269         
270 return _M;
271
272
273 --[=[
274
275 Layout:
276 {
277
278         title = "MUC Configuration",
279         instructions = [[Use this form to configure options for this MUC room.]],
280
281         { name = "FORM_TYPE", type = "hidden", required = true };
282         { name = "field-name", type = "field-type", required = false };
283 }
284
285
286 --]=]