Comparison

util/dataforms.lua @ 441:e4c0b1d7fd6b

util.dataforms: Update from prosody trunk 5fb6563eee1e keeping from_stanza()
author Kim Alvefur <zash@zash.se>
date Mon, 23 Nov 2020 23:43:58 +0100
parent 335:9e69ee8542d4
child 442:b2ae91f4fec9
comparison
equal deleted inserted replaced
440:14071b3a46df 441:e4c0b1d7fd6b
1 -- Prosody IM 1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild 2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain 3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- 4 --
5 -- This project is MIT/X11 licensed. Please see the 5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information. 6 -- COPYING file in the source package for more information.
7 -- 7 --
8 8
9 local setmetatable = setmetatable; 9 local setmetatable = setmetatable;
10 local pairs, ipairs = pairs, ipairs; 10 local ipairs = ipairs;
11 local tostring, type, next = tostring, type, next; 11 local type, next = type, next;
12 local tonumber = tonumber;
13 local tostring = tostring;
12 local t_concat = table.concat; 14 local t_concat = table.concat;
13 local st = require "util.stanza"; 15 local st = require "util.stanza";
14 local jid_prep = require "util.jid".prep; 16 local jid_prep = require "util.jid".prep;
15 17
16 module "dataforms" 18 local _ENV = nil;
19 -- luacheck: std none
17 20
18 local xmlns_forms = 'jabber:x:data'; 21 local xmlns_forms = 'jabber:x:data';
22 local xmlns_validate = 'http://jabber.org/protocol/xdata-validate';
19 23
20 local form_t = {}; 24 local form_t = {};
21 local form_mt = { __index = form_t }; 25 local form_mt = { __index = form_t };
22 26
23 function new(layout) 27 local function new(layout)
24 return setmetatable(layout, form_mt); 28 return setmetatable(layout, form_mt);
25 end 29 end
26 30
27 function from_stanza(stanza) 31 local function from_stanza(stanza)
28 local layout = { 32 local layout = {
29 title = stanza:get_child_text("title"); 33 title = stanza:get_child_text("title");
30 instructions = stanza:get_child_text("instructions"); 34 instructions = stanza:get_child_text("instructions");
31 }; 35 };
32 for tag in stanza:childtags("field") do 36 for tag in stanza:childtags("field") do
61 end 65 end
62 return new(layout); 66 return new(layout);
63 end 67 end
64 68
65 function form_t.form(layout, data, formtype) 69 function form_t.form(layout, data, formtype)
66 local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" }); 70 if not formtype then formtype = "form" end
67 if layout.title then 71 local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype });
68 form:tag("title"):text(layout.title):up(); 72 if formtype == "cancel" then
69 end 73 return form;
70 if layout.instructions then 74 end
71 form:tag("instructions"):text(layout.instructions):up(); 75 if formtype ~= "submit" then
72 end 76 if layout.title then
73 for n, field in ipairs(layout) do 77 form:tag("title"):text(layout.title):up();
78 end
79 if layout.instructions then
80 form:tag("instructions"):text(layout.instructions):up();
81 end
82 end
83 for _, field in ipairs(layout) do
74 local field_type = field.type or "text-single"; 84 local field_type = field.type or "text-single";
75 -- Add field tag 85 -- Add field tag
76 form:tag("field", { type = field_type, var = field.name, label = field.label }); 86 form:tag("field", { type = field_type, var = field.var or field.name, label = formtype ~= "submit" and field.label or nil });
77 87
78 local value = (data and data[field.name]) or field.value; 88 if formtype ~= "submit" then
79 89 if field.desc then
80 if value then 90 form:text_tag("desc", field.desc);
91 end
92 end
93
94 if formtype == "form" and field.datatype then
95 form:tag("validate", { xmlns = xmlns_validate, datatype = field.datatype });
96 if field.range_min or field.range_max then
97 form:tag("range", {
98 min = field.range_min and tostring(field.range_min),
99 max = field.range_max and tostring(field.range_max),
100 }):up();
101 end
102 -- <basic/> assumed
103 form:up();
104 end
105
106
107 local value = field.value;
108 local options = field.options;
109
110 if data and data[field.name] ~= nil then
111 value = data[field.name];
112
113 if formtype == "form" and type(value) == "table"
114 and (field_type == "list-single" or field_type == "list-multi") then
115 -- Allow passing dynamically generated options as values
116 options, value = value, nil;
117 end
118 end
119
120 if formtype == "form" and options then
121 local defaults = {};
122 for _, val in ipairs(options) do
123 if type(val) == "table" then
124 form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
125 if val.default then
126 defaults[#defaults+1] = val.value;
127 end
128 else
129 form:tag("option", { label= val }):tag("value"):text(val):up():up();
130 end
131 end
132 if not value then
133 if field_type == "list-single" then
134 value = defaults[1];
135 elseif field_type == "list-multi" then
136 value = defaults;
137 end
138 end
139 end
140
141 if value ~= nil then
142 if type(value) == "number" then
143 -- TODO validate that this is ok somehow, eg check field.datatype
144 value = ("%g"):format(value);
145 end
81 -- Add value, depending on type 146 -- Add value, depending on type
82 if field_type == "hidden" then 147 if field_type == "hidden" then
83 if type(value) == "table" then 148 if type(value) == "table" then
84 -- Assume an XML snippet 149 -- Assume an XML snippet
85 form:tag("value") 150 form:tag("value")
86 :add_child(value) 151 :add_child(value)
87 :up(); 152 :up();
88 else 153 else
89 form:tag("value"):text(tostring(value)):up(); 154 form:tag("value"):text(value):up();
90 end 155 end
91 elseif field_type == "boolean" then 156 elseif field_type == "boolean" then
92 form:tag("value"):text((value and "1") or "0"):up(); 157 form:tag("value"):text((value and "1") or "0"):up();
93 elseif field_type == "fixed" then 158 elseif field_type == "fixed" then
94 159 form:tag("value"):text(value):up();
95 elseif field_type == "jid-multi" then 160 elseif field_type == "jid-multi" then
96 for _, jid in ipairs(value) do 161 for _, jid in ipairs(value) do
97 form:tag("value"):text(jid):up(); 162 form:tag("value"):text(jid):up();
98 end 163 end
99 elseif field_type == "jid-single" then 164 elseif field_type == "jid-single" then
104 -- Split into multiple <value> tags, one for each line 169 -- Split into multiple <value> tags, one for each line
105 for line in value:gmatch("([^\r\n]+)\r?\n*") do 170 for line in value:gmatch("([^\r\n]+)\r?\n*") do
106 form:tag("value"):text(line):up(); 171 form:tag("value"):text(line):up();
107 end 172 end
108 elseif field_type == "list-single" then 173 elseif field_type == "list-single" then
109 local has_default = false; 174 form:tag("value"):text(value):up();
110 for _, val in ipairs(value) do
111 if type(val) == "table" then
112 form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
113 if val.default and (not has_default) then
114 form:tag("value"):text(val.value):up();
115 has_default = true;
116 end
117 else
118 form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
119 end
120 end
121 elseif field_type == "list-multi" then 175 elseif field_type == "list-multi" then
122 for _, val in ipairs(value) do 176 for _, val in ipairs(value) do
123 if type(val) == "table" then 177 form:tag("value"):text(val):up();
124 form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up(); 178 end
125 if val.default then 179 end
126 form:tag("value"):text(val.value):up(); 180 end
127 end 181
128 else 182 local media = field.media;
129 form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up(); 183 if media then
130 end 184 form:tag("media", { xmlns = "urn:xmpp:media-element", height = ("%g"):format(media.height), width = ("%g"):format(media.width) });
131 end 185 for _, val in ipairs(media) do
132 end 186 form:tag("uri", { type = val.type }):text(val.uri):up()
133 end 187 end
134 188 form:up();
135 if field.required then 189 end
190
191 if formtype == "form" and field.required then
136 form:tag("required"):up(); 192 form:tag("required"):up();
137 end 193 end
138 194
139 -- Jump back up to list of fields 195 -- Jump back up to list of fields
140 form:up(); 196 form:up();
141 end 197 end
142 return form; 198 return form;
143 end 199 end
144 200
145 local field_readers = {}; 201 local field_readers = {};
146 202 local data_validators = {};
147 function form_t.data(layout, stanza) 203
204 function form_t.data(layout, stanza, current)
148 local data = {}; 205 local data = {};
149 local errors = {}; 206 local errors = {};
207 local present = {};
150 208
151 for _, field in ipairs(layout) do 209 for _, field in ipairs(layout) do
152 local tag; 210 local tag;
153 for field_tag in stanza:childtags() do 211 for field_tag in stanza:childtags("field") do
154 if field.name == field_tag.attr.var then 212 if (field.var or field.name) == field_tag.attr.var then
155 tag = field_tag; 213 tag = field_tag;
156 break; 214 break;
157 end 215 end
158 end 216 end
159 217
160 if not tag then 218 if not tag then
161 if field.required then 219 if current and current[field.name] ~= nil then
220 data[field.name] = current[field.name];
221 elseif field.required then
162 errors[field.name] = "Required value missing"; 222 errors[field.name] = "Required value missing";
163 end 223 end
164 else 224 elseif field.name then
225 present[field.name] = true;
165 local reader = field_readers[field.type]; 226 local reader = field_readers[field.type];
166 if reader then 227 if reader then
167 data[field.name], errors[field.name] = reader(tag, field.required); 228 local value, err = reader(tag, field.required);
229 local validator = field.datatype and data_validators[field.datatype];
230 if value ~= nil and validator then
231 local valid, ret = validator(value, field);
232 if valid then
233 value = ret;
234 else
235 value, err = nil, ret or ("Invalid value for data of type " .. field.datatype);
236 end
237 end
238 data[field.name], errors[field.name] = value, err;
168 end 239 end
169 end 240 end
170 end 241 end
171 if next(errors) then 242 if next(errors) then
172 return data, errors; 243 return data, errors, present;
173 end 244 end
174 return data; 245 return data, nil, present;
175 end 246 end
176 247
177 field_readers["text-single"] = 248 local function simple_text(field_tag, required)
178 function (field_tag, required) 249 local data = field_tag:get_child_text("value");
179 local data = field_tag:get_child_text("value"); 250 -- XEP-0004 does not say if an empty string is acceptable for a required value
180 if data and #data > 0 then 251 -- so we will follow HTML5 which says that empty string means missing
181 return data 252 if required and (data == nil or data == "") then
182 elseif required then 253 return nil, "Required value missing";
183 return nil, "Required value missing"; 254 end
184 end 255 return data; -- Return whatever get_child_text returned, even if empty string
185 end 256 end
186 257
187 field_readers["text-private"] = 258 field_readers["text-single"] = simple_text;
188 field_readers["text-single"]; 259
260 field_readers["text-private"] = simple_text;
189 261
190 field_readers["jid-single"] = 262 field_readers["jid-single"] =
191 function (field_tag, required) 263 function (field_tag, required)
192 local raw_data = field_tag:get_child_text("value") 264 local raw_data, err = simple_text(field_tag, required);
265 if not raw_data then return raw_data, err; end
193 local data = jid_prep(raw_data); 266 local data = jid_prep(raw_data);
194 if data and #data > 0 then 267 if not data then
195 return data
196 elseif raw_data then
197 return nil, "Invalid JID: " .. raw_data; 268 return nil, "Invalid JID: " .. raw_data;
198 elseif required then 269 end
199 return nil, "Required value missing"; 270 return data;
200 end
201 end 271 end
202 272
203 field_readers["jid-multi"] = 273 field_readers["jid-multi"] =
204 function (field_tag, required) 274 function (field_tag, required)
205 local result = {}; 275 local result = {};
223 function (field_tag, required) 293 function (field_tag, required)
224 local result = {}; 294 local result = {};
225 for value in field_tag:childtags("value") do 295 for value in field_tag:childtags("value") do
226 result[#result+1] = value:get_text(); 296 result[#result+1] = value:get_text();
227 end 297 end
228 return result, (required and #result == 0 and "Required value missing" or nil); 298 if #result > 0 then
299 return result;
300 elseif required then
301 return nil, "Required value missing";
302 end
229 end 303 end
230 304
231 field_readers["text-multi"] = 305 field_readers["text-multi"] =
232 function (field_tag, required) 306 function (field_tag, required)
233 local data, err = field_readers["list-multi"](field_tag, required); 307 local data, err = field_readers["list-multi"](field_tag, required);
235 data = t_concat(data, "\n"); 309 data = t_concat(data, "\n");
236 end 310 end
237 return data, err; 311 return data, err;
238 end 312 end
239 313
240 field_readers["list-single"] = 314 field_readers["list-single"] = simple_text;
241 field_readers["text-single"];
242 315
243 local boolean_values = { 316 local boolean_values = {
244 ["1"] = true, ["true"] = true, 317 ["1"] = true, ["true"] = true,
245 ["0"] = false, ["false"] = false, 318 ["0"] = false, ["false"] = false,
246 }; 319 };
247 320
248 field_readers["boolean"] = 321 field_readers["boolean"] =
249 function (field_tag, required) 322 function (field_tag, required)
250 local raw_value = field_tag:get_child_text("value"); 323 local raw_value, err = simple_text(field_tag, required);
251 local value = boolean_values[raw_value ~= nil and raw_value]; 324 if not raw_value then return raw_value, err; end
252 if value ~= nil then 325 local value = boolean_values[raw_value];
253 return value; 326 if value == nil then
254 elseif raw_value then 327 return nil, "Invalid boolean representation:" .. raw_value;
255 return nil, "Invalid boolean representation"; 328 end
256 elseif required then 329 return value;
257 return nil, "Required value missing";
258 end
259 end 330 end
260 331
261 field_readers["hidden"] = 332 field_readers["hidden"] =
262 function (field_tag) 333 function (field_tag)
263 return field_tag:get_child_text("value"); 334 return field_tag:get_child_text("value");
264 end 335 end
265 336
266 return _M; 337 data_validators["xs:integer"] =
338 function (data, field)
339 local n = tonumber(data);
340 if not n then
341 return false, "not a number";
342 elseif n % 1 ~= 0 then
343 return false, "not an integer";
344 end
345 if field.range_max and n > field.range_max then
346 return false, "out of bounds";
347 elseif field.range_min and n < field.range_min then
348 return false, "out of bounds";
349 end
350 return true, n;
351 end
352
353
354 local function get_form_type(form)
355 if not st.is_stanza(form) then
356 return nil, "not a stanza object";
357 elseif form.attr.xmlns ~= "jabber:x:data" or form.name ~= "x" then
358 return nil, "not a dataform element";
359 end
360 for field in form:childtags("field") do
361 if field.attr.var == "FORM_TYPE" then
362 return field:get_child_text("value");
363 end
364 end
365 return "";
366 end
367
368 return {
369 new = new;
370 from_stanza = from_stanza;
371 get_type = get_form_type;
372 };
267 373
268 374
269 --[=[ 375 --[=[
270 376
271 Layout: 377 Layout: