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 = {}; |
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: |