net.dns: Ensure all pending requests get notified of a timeout when looking up a...
[prosody.git] / util / dataforms.lua
index a57e183b007d0f776d5e9e20275cc10c11ed1c7e..01a8eef327c1ac566a0ecfabb997c03207f6f237 100644 (file)
@@ -1,9 +1,17 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
 local setmetatable = setmetatable;
 local pairs, ipairs = pairs, ipairs;
-local tostring, type = tostring, type;
+local tostring, type, next = tostring, type, next;
 local t_concat = table.concat;
-
 local st = require "util.stanza";
+local jid_prep = require "util.jid".prep;
 
 module "dataforms"
 
@@ -16,10 +24,8 @@ function new(layout)
        return setmetatable(layout, form_mt);
 end
 
-local form_x_attr = { xmlns = xmlns_forms };
-
-function form_t.form(layout, data)
-       local form = st.stanza("x", form_x_attr);
+function form_t.form(layout, data, formtype)
+       local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" });
        if layout.title then
                form:tag("title"):text(layout.title):up();
        end
@@ -31,35 +37,70 @@ function form_t.form(layout, data)
                -- Add field tag
                form:tag("field", { type = field_type, var = field.name, label = field.label });
 
-               local value = data[field.name];
+               local value = (data and data[field.name]) or field.value;
                
-               -- Add value, depending on type
-               if field_type == "hidden" then
-                       if type(value) == "table" then
-                               -- Assume an XML snippet
-                               form:tag("value")
-                                       :add_child(value)
-                                       :up();
-                       elseif value then
-                               form:tag("value"):text(tostring(value)):up();
-                       end
-               elseif field_type == "boolean" then
-                       form:tag("value"):text((value and "1") or "0"):up();
-               elseif field_type == "fixed" then
-                       
-               elseif field_type == "jid-multi" then
-                       for _, jid in ipairs(value) do
-                               form:tag("value"):text(jid):up();
+               if value then
+                       -- Add value, depending on type
+                       if field_type == "hidden" then
+                               if type(value) == "table" then
+                                       -- Assume an XML snippet
+                                       form:tag("value")
+                                               :add_child(value)
+                                               :up();
+                               else
+                                       form:tag("value"):text(tostring(value)):up();
+                               end
+                       elseif field_type == "boolean" then
+                               form:tag("value"):text((value and "1") or "0"):up();
+                       elseif field_type == "fixed" then
+                               form:tag("value"):text(value):up();
+                       elseif field_type == "jid-multi" then
+                               for _, jid in ipairs(value) do
+                                       form:tag("value"):text(jid):up();
+                               end
+                       elseif field_type == "jid-single" then
+                               form:tag("value"):text(value):up();
+                       elseif field_type == "text-single" or field_type == "text-private" then
+                               form:tag("value"):text(value):up();
+                       elseif field_type == "text-multi" then
+                               -- Split into multiple <value> tags, one for each line
+                               for line in value:gmatch("([^\r\n]+)\r?\n*") do
+                                       form:tag("value"):text(line):up();
+                               end
+                       elseif field_type == "list-single" then
+                               local has_default = false;
+                               for _, val in ipairs(value) do
+                                       if type(val) == "table" then
+                                               form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
+                                               if val.default and (not has_default) then
+                                                       form:tag("value"):text(val.value):up();
+                                                       has_default = true;
+                                               end
+                                       else
+                                               form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
+                                       end
+                               end
+                       elseif field_type == "list-multi" then
+                               for _, val in ipairs(value) do
+                                       if type(val) == "table" then
+                                               form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
+                                               if val.default then
+                                                       form:tag("value"):text(val.value):up();
+                                               end
+                                       else
+                                               form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
+                                       end
+                               end
                        end
-               elseif field_type == "jid-single" then
-                       form:tag("value"):text(value):up();
-               elseif field_type == "text-single" or field_type == "text-private" then
-                       form:tag("value"):text(value):up();
-               elseif field_type == "text-multi" then
-                       -- Split into multiple <value> tags, one for each line
-                       for line in value:gmatch("([^\r\n]+)\r?\n*") do
-                               form:tag("value"):text(line):up();
+               end
+
+               local media = field.media;
+               if media then
+                       form:tag("media", { xmlns = "urn:xmpp:media-element", height = media.height, width = media.width });
+                       for _, val in ipairs(media) do
+                               form:tag("uri", { type = val.type }):text(val.uri):up()
                        end
+                       form:up();
                end
                
                if field.required then
@@ -76,61 +117,127 @@ local field_readers = {};
 
 function form_t.data(layout, stanza)
        local data = {};
-       
-       for field_tag in stanza:childtags() do
-               local field_type = field_tag.attr.type;
-               
-               local reader = field_readers[field_type];
-               if reader then
-                       data[field_tag.attr.var] = reader(field_tag);
+       local errors = {};
+
+       for _, field in ipairs(layout) do
+               local tag;
+               for field_tag in stanza:childtags() do
+                       if field.name == field_tag.attr.var then
+                               tag = field_tag;
+                               break;
+                       end
                end
-               
+
+               if not tag then
+                       if field.required then
+                               errors[field.name] = "Required value missing";
+                       end
+               else
+                       local reader = field_readers[field.type];
+                       if reader then
+                               data[field.name], errors[field.name] = reader(tag, field.required);
+                       end
+               end
+       end
+       if next(errors) then
+               return data, errors;
        end
        return data;
 end
 
-field_readers["text-single"] = 
-       function (field_tag)
-               local value = field_tag:child_with_name("value");
-               if value then
-                       return value[1];
+field_readers["text-single"] =
+       function (field_tag, required)
+               local data = field_tag:get_child_text("value");
+               if data and #data > 0 then
+                       return data
+               elseif required then
+                       return nil, "Required value missing";
                end
        end
 
-field_readers["text-private"] = 
+field_readers["text-private"] =
        field_readers["text-single"];
 
-field_readers["text-multi"] = 
-       function (field_tag)
+field_readers["jid-single"] =
+       function (field_tag, required)
+               local raw_data = field_tag:get_child_text("value")
+               local data = jid_prep(raw_data);
+               if data and #data > 0 then
+                       return data
+               elseif raw_data then
+                       return nil, "Invalid JID: " .. raw_data;
+               elseif required then
+                       return nil, "Required value missing";
+               end
+       end
+
+field_readers["jid-multi"] =
+       function (field_tag, required)
                local result = {};
-               for value_tag in field_tag:childtags() do
-                       if value_tag.name == "value" then
-                               result[#result+1] = value_tag[1];
+               local err = {};
+               for value_tag in field_tag:childtags("value") do
+                       local raw_value = value_tag:get_text();
+                       local value = jid_prep(raw_value);
+                       result[#result+1] = value;
+                       if raw_value and not value then
+                               err[#err+1] = ("Invalid JID: " .. raw_value);
                        end
                end
-               return t_concat(result, "\n");
+               if #result > 0 then
+                       return result, (#err > 0 and t_concat(err, "\n") or nil);
+               elseif required then
+                       return nil, "Required value missing";
+               end
        end
 
-field_readers["boolean"] = 
-       function (field_tag)
-               local value = field_tag:child_with_name("value");
-               if value then
-                       if value[1] == "1" or value[1] == "true" then
-                               return true;
-                       else
-                               return false;
-                       end
-               end             
+field_readers["list-multi"] =
+       function (field_tag, required)
+               local result = {};
+               for value in field_tag:childtags("value") do
+                       result[#result+1] = value:get_text();
+               end
+               if #result > 0 then
+                       return result;
+               elseif required then
+                       return nil, "Required value missing";
+               end
        end
 
-field_readers["hidden"] = 
-       function (field_tag)
-               local value = field_tag:child_with_name("value");
-               if value then
-                       return value[1];
+field_readers["text-multi"] =
+       function (field_tag, required)
+               local data, err = field_readers["list-multi"](field_tag, required);
+               if data then
+                       data = t_concat(data, "\n");
                end
+               return data, err;
        end
-       
+
+field_readers["list-single"] =
+       field_readers["text-single"];
+
+local boolean_values = {
+       ["1"] = true, ["true"] = true,
+       ["0"] = false, ["false"] = false,
+};
+
+field_readers["boolean"] =
+       function (field_tag, required)
+               local raw_value = field_tag:get_child_text("value");
+               local value = boolean_values[raw_value ~= nil and raw_value];
+               if value ~= nil then
+                       return value;
+               elseif raw_value then
+                       return nil, "Invalid boolean representation";
+               elseif required then
+                       return nil, "Required value missing";
+               end
+       end
+
+field_readers["hidden"] =
+       function (field_tag)
+               return field_tag:get_child_text("value");
+       end
+
 return _M;