Software /
code /
verse
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: |