aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/array.lua16
-rw-r--r--util/logger.lua16
-rw-r--r--util/openmetrics.lua7
-rw-r--r--util/prosodyctl/shell.lua19
-rw-r--r--util/sslconfig.lua73
-rw-r--r--util/stanza.lua32
-rw-r--r--util/vcard.lua574
7 files changed, 130 insertions, 607 deletions
diff --git a/util/array.lua b/util/array.lua
index c33a5ef1..9d438940 100644
--- a/util/array.lua
+++ b/util/array.lua
@@ -8,6 +8,7 @@
local t_insert, t_sort, t_remove, t_concat
= table.insert, table.sort, table.remove, table.concat;
+local t_move = require "util.table".move;
local setmetatable = setmetatable;
local getmetatable = getmetatable;
@@ -137,13 +138,11 @@ function array_base.slice(outa, ina, i, j)
return outa;
end
- for idx = 1, 1+j-i do
- outa[idx] = ina[i+(idx-1)];
- end
+
+ t_move(ina, i, j, 1, outa);
if ina == outa then
- for idx = 2+j-i, #outa do
- outa[idx] = nil;
- end
+ -- Clear (nil) remainder of range
+ t_move(ina, #outa+1, #outa*2, 2+j-i, ina);
end
return outa;
end
@@ -209,10 +208,7 @@ function array_methods:shuffle()
end
function array_methods:append(ina)
- local len, len2 = #self, #ina;
- for i = 1, len2 do
- self[len+i] = ina[i];
- end
+ t_move(ina, 1, #ina, #self+1, self);
return self;
end
diff --git a/util/logger.lua b/util/logger.lua
index 20a5cef2..148b98dc 100644
--- a/util/logger.lua
+++ b/util/logger.lua
@@ -10,6 +10,7 @@
local pairs = pairs;
local ipairs = ipairs;
local require = require;
+local t_remove = table.remove;
local _ENV = nil;
-- luacheck: std none
@@ -78,6 +79,20 @@ local function add_simple_sink(simple_sink_function, levels)
for _, level in ipairs(levels or {"debug", "info", "warn", "error"}) do
add_level_sink(level, sink_function);
end
+ return sink_function;
+end
+
+local function remove_sink(sink_function)
+ local removed;
+ for level, sinks in pairs(level_sinks) do
+ for i = #sinks, 1, -1 do
+ if sinks[i] == sink_function then
+ t_remove(sinks, i);
+ removed = true;
+ end
+ end
+ end
+ return removed;
end
return {
@@ -87,4 +102,5 @@ return {
add_level_sink = add_level_sink;
add_simple_sink = add_simple_sink;
new = make_logger;
+ remove_sink = remove_sink;
};
diff --git a/util/openmetrics.lua b/util/openmetrics.lua
index cb7791ec..634f9de1 100644
--- a/util/openmetrics.lua
+++ b/util/openmetrics.lua
@@ -35,10 +35,11 @@ local t_pack, t_unpack = require "util.table".pack, table.unpack or unpack; --lu
-- `with_partial_label` by the moduleapi in order to pre-set the `host` label
-- on metrics created in non-global modules.
local metric_proxy_mt = {}
+metric_proxy_mt.__name = "metric_proxy"
metric_proxy_mt.__index = metric_proxy_mt
local function new_metric_proxy(metric_family, with_labels_proxy_fun)
- return {
+ return setmetatable({
_family = metric_family,
with_labels = function(self, ...)
return with_labels_proxy_fun(self._family, ...)
@@ -48,7 +49,7 @@ local function new_metric_proxy(metric_family, with_labels_proxy_fun)
return family:with_labels(label, ...)
end)
end
- }
+ }, metric_proxy_mt);
end
-- END of Utility: "metric proxy"
@@ -128,6 +129,7 @@ end
-- BEGIN of generic MetricFamily implementation
local metric_family_mt = {}
+metric_family_mt.__name = "metric_family"
metric_family_mt.__index = metric_family_mt
local function histogram_metric_ctor(orig_ctor, buckets)
@@ -278,6 +280,7 @@ local function compose_name(name, unit)
end
local metric_registry_mt = {}
+metric_registry_mt.__name = "metric_registry"
metric_registry_mt.__index = metric_registry_mt
local function new_metric_registry(backend)
diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua
index bce27b94..8f910301 100644
--- a/util/prosodyctl/shell.lua
+++ b/util/prosodyctl/shell.lua
@@ -4,6 +4,8 @@ local st = require "util.stanza";
local path = require "util.paths";
local parse_args = require "util.argparse".parse;
local unpack = table.unpack or _G.unpack;
+local tc = require "util.termcolours";
+local isatty = require "util.pposix".isatty;
local have_readline, readline = pcall(require, "readline");
@@ -27,7 +29,7 @@ local function read_line(prompt_string)
end
local function send_line(client, line)
- client.send(st.stanza("repl-input"):text(line));
+ client.send(st.stanza("repl-input", { width = os.getenv "COLUMNS" }):text(line));
end
local function repl(client)
@@ -64,6 +66,7 @@ end
local function start(arg) --luacheck: ignore 212/arg
local client = adminstream.client();
local opts, err, where = parse_args(arg);
+ local ttyout = isatty(io.stdout);
if not opts then
if err == "param-not-found" then
@@ -89,11 +92,15 @@ local function start(arg) --luacheck: ignore 212/arg
local errors = 0; -- TODO This is weird, but works for now.
client.events.add_handler("received", function(stanza)
if stanza.name == "repl-output" or stanza.name == "repl-result" then
+ local dest = io.stdout;
if stanza.attr.type == "error" then
errors = errors + 1;
- io.stderr:write(stanza:get_text(), "\n");
+ dest = io.stderr;
+ end
+ if stanza.attr.eol == "0" then
+ dest:write(stanza:get_text());
else
- print(stanza:get_text());
+ dest:write(stanza:get_text(), "\n");
end
end
if stanza.name == "repl-result" then
@@ -118,7 +125,11 @@ local function start(arg) --luacheck: ignore 212/arg
client.events.add_handler("received", function (stanza)
if stanza.name == "repl-output" or stanza.name == "repl-result" then
local result_prefix = stanza.attr.type == "error" and "!" or "|";
- print(result_prefix.." "..stanza:get_text());
+ local out = result_prefix.." "..stanza:get_text();
+ if ttyout and stanza.attr.type == "error" then
+ out = tc.getstring(tc.getstyle("red"), out);
+ end
+ print(out);
end
if stanza.name == "repl-result" then
repl(client);
diff --git a/util/sslconfig.lua b/util/sslconfig.lua
index 6074a1fb..0078365b 100644
--- a/util/sslconfig.lua
+++ b/util/sslconfig.lua
@@ -3,9 +3,12 @@
local type = type;
local pairs = pairs;
local rawset = rawset;
+local rawget = rawget;
+local error = error;
local t_concat = table.concat;
local t_insert = table.insert;
local setmetatable = setmetatable;
+local resolve_path = require"util.paths".resolve_relative_path;
local _ENV = nil;
-- luacheck: std none
@@ -34,7 +37,7 @@ function handlers.options(config, field, new)
options[value] = true;
end
end
- config[field] = options;
+ rawset(config, field, options)
end
handlers.verifyext = handlers.options;
@@ -70,6 +73,20 @@ finalisers.curveslist = finalisers.ciphers;
-- TLS 1.3 ciphers
finalisers.ciphersuites = finalisers.ciphers;
+-- Path expansion
+function finalisers.key(path, config)
+ if type(path) == "string" then
+ return resolve_path(config._basedir, path);
+ else
+ return nil
+ end
+end
+finalisers.certificate = finalisers.key;
+finalisers.cafile = finalisers.key;
+finalisers.capath = finalisers.key;
+-- XXX: copied from core/certmanager.lua, but this seems odd, because it would remove a dhparam function from the config
+finalisers.dhparam = finalisers.key;
+
-- protocol = "x" should enable only that protocol
-- protocol = "x+" should enable x and later versions
@@ -89,37 +106,81 @@ end
-- Merge options from 'new' config into 'config'
local function apply(config, new)
+ rawset(config, "_cache", nil);
if type(new) == "table" then
for field, value in pairs(new) do
- (handlers[field] or rawset)(config, field, value);
+ -- exclude keys which are internal to the config builder
+ if field:sub(1, 1) ~= "_" then
+ (handlers[field] or rawset)(config, field, value);
+ end
end
end
+ return config
end
-- Finalize the config into the form LuaSec expects
local function final(config)
local output = { };
for field, value in pairs(config) do
- output[field] = (finalisers[field] or id)(value);
+ -- exclude keys which are internal to the config builder
+ if field:sub(1, 1) ~= "_" then
+ output[field] = (finalisers[field] or id)(value, config);
+ end
end
-- Need to handle protocols last because it adds to the options list
protocol(output);
return output;
end
+local function build(config)
+ local cached = rawget(config, "_cache");
+ if cached then
+ return cached, nil
+ end
+
+ local ctx, err = rawget(config, "_context_factory")(config:final(), config);
+ if ctx then
+ rawset(config, "_cache", ctx);
+ end
+ return ctx, err
+end
+
local sslopts_mt = {
__index = {
apply = apply;
final = final;
+ build = build;
};
+ __newindex = function()
+ error("SSL config objects cannot be modified directly. Use :apply()")
+ end;
};
-local function new()
- return setmetatable({options={}}, sslopts_mt);
+
+-- passing basedir through everything is required to avoid sslconfig depending
+-- on prosody.paths.config
+local function new(context_factory, basedir)
+ return setmetatable({
+ _context_factory = context_factory,
+ _basedir = basedir,
+ options={},
+ }, sslopts_mt);
end
+local function clone(config)
+ local result = new();
+ for k, v in pairs(config) do
+ -- note that we *do* copy the internal keys on clone -- we have to carry
+ -- both the factory and the cache with us
+ rawset(result, k, v);
+ end
+ return result
+end
+
+sslopts_mt.__index.clone = clone;
+
return {
apply = apply;
final = final;
- new = new;
+ _new = new;
};
diff --git a/util/stanza.lua b/util/stanza.lua
index a38f80b3..a14be5f0 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -21,6 +21,8 @@ local type = type;
local s_gsub = string.gsub;
local s_sub = string.sub;
local s_find = string.find;
+local t_move = table.move or require "util.table".move;
+local t_create = require"util.table".create;
local valid_utf8 = require "util.encodings".utf8.valid;
@@ -275,25 +277,33 @@ function stanza_mt:find(path)
end
local function _clone(stanza, only_top)
- local attr, tags = {}, {};
+ local attr = {};
for k,v in pairs(stanza.attr) do attr[k] = v; end
local old_namespaces, namespaces = stanza.namespaces;
if old_namespaces then
namespaces = {};
for k,v in pairs(old_namespaces) do namespaces[k] = v; end
end
- local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
+ local tags, new;
+ if only_top then
+ tags = {};
+ new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
+ else
+ tags = t_create(#stanza.tags, 0);
+ new = t_create(#stanza, 4);
+ new.name = stanza.name;
+ new.attr = attr;
+ new.namespaces = namespaces;
+ new.tags = tags;
+ end
+
+ setmetatable(new, stanza_mt);
if not only_top then
- for i=1,#stanza do
- local child = stanza[i];
- if child.name then
- child = _clone(child);
- t_insert(tags, child);
- end
- t_insert(new, child);
- end
+ t_move(stanza, 1, #stanza, 1, new);
+ t_move(stanza.tags, 1, #stanza.tags, 1, tags);
+ new:maptags(_clone);
end
- return setmetatable(new, stanza_mt);
+ return new;
end
local function clone(stanza, only_top)
diff --git a/util/vcard.lua b/util/vcard.lua
deleted file mode 100644
index e311f73f..00000000
--- a/util/vcard.lua
+++ /dev/null
@@ -1,574 +0,0 @@
--- Copyright (C) 2011-2014 Kim Alvefur
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
--- TODO
--- Fix folding.
-
-local st = require "util.stanza";
-local t_insert, t_concat = table.insert, table.concat;
-local type = type;
-local pairs, ipairs = pairs, ipairs;
-
-local from_text, to_text, from_xep54, to_xep54;
-
-local line_sep = "\n";
-
-local vCard_dtd; -- See end of file
-local vCard4_dtd;
-
-local function vCard_esc(s)
- return s:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n");
-end
-
-local function vCard_unesc(s)
- return s:gsub("\\?[\\nt:;,]", {
- ["\\\\"] = "\\",
- ["\\n"] = "\n",
- ["\\r"] = "\r",
- ["\\t"] = "\t",
- ["\\:"] = ":", -- FIXME Shouldn't need to escape : in values, just params
- ["\\;"] = ";",
- ["\\,"] = ",",
- [":"] = "\29",
- [";"] = "\30",
- [","] = "\31",
- });
-end
-
-local function item_to_xep54(item)
- local t = st.stanza(item.name, { xmlns = "vcard-temp" });
-
- local prop_def = vCard_dtd[item.name];
- if prop_def == "text" then
- t:text(item[1]);
- elseif type(prop_def) == "table" then
- if prop_def.types and item.TYPE then
- if type(item.TYPE) == "table" then
- for _,v in pairs(prop_def.types) do
- for _,typ in pairs(item.TYPE) do
- if typ:upper() == v then
- t:tag(v):up();
- break;
- end
- end
- end
- else
- t:tag(item.TYPE:upper()):up();
- end
- end
-
- if prop_def.props then
- for _,prop in pairs(prop_def.props) do
- if item[prop] then
- for _, v in ipairs(item[prop]) do
- t:text_tag(prop, v);
- end
- end
- end
- end
-
- if prop_def.value then
- t:text_tag(prop_def.value, item[1]);
- elseif prop_def.values then
- local prop_def_values = prop_def.values;
- local repeat_last = prop_def_values.behaviour == "repeat-last" and prop_def_values[#prop_def_values];
- for i=1,#item do
- t:text_tag(prop_def.values[i] or repeat_last, item[i]);
- end
- end
- end
-
- return t;
-end
-
-local function vcard_to_xep54(vCard)
- local t = st.stanza("vCard", { xmlns = "vcard-temp" });
- for i=1,#vCard do
- t:add_child(item_to_xep54(vCard[i]));
- end
- return t;
-end
-
-function to_xep54(vCards)
- if not vCards[1] or vCards[1].name then
- return vcard_to_xep54(vCards)
- else
- local t = st.stanza("xCard", { xmlns = "vcard-temp" });
- for i=1,#vCards do
- t:add_child(vcard_to_xep54(vCards[i]));
- end
- return t;
- end
-end
-
-function from_text(data)
- data = data -- unfold and remove empty lines
- :gsub("\r\n","\n")
- :gsub("\n ", "")
- :gsub("\n\n+","\n");
- local vCards = {};
- local current;
- for line in data:gmatch("[^\n]+") do
- line = vCard_unesc(line);
- local name, params, value = line:match("^([-%a]+)(\30?[^\29]*)\29(.*)$");
- value = value:gsub("\29",":");
- if #params > 0 then
- local _params = {};
- for k,isval,v in params:gmatch("\30([^=]+)(=?)([^\30]*)") do
- k = k:upper();
- local _vt = {};
- for _p in v:gmatch("[^\31]+") do
- _vt[#_vt+1]=_p
- _vt[_p]=true;
- end
- if isval == "=" then
- _params[k]=_vt;
- else
- _params[k]=true;
- end
- end
- params = _params;
- end
- if name == "BEGIN" and value == "VCARD" then
- current = {};
- vCards[#vCards+1] = current;
- elseif name == "END" and value == "VCARD" then
- current = nil;
- elseif current and vCard_dtd[name] then
- local dtd = vCard_dtd[name];
- local item = { name = name };
- t_insert(current, item);
- local up = current;
- current = item;
- if dtd.types then
- for _, t in ipairs(dtd.types) do
- t = t:lower();
- if ( params.TYPE and params.TYPE[t] == true)
- or params[t] == true then
- current.TYPE=t;
- end
- end
- end
- if dtd.props then
- for _, p in ipairs(dtd.props) do
- if params[p] then
- if params[p] == true then
- current[p]=true;
- else
- for _, prop in ipairs(params[p]) do
- current[p]=prop;
- end
- end
- end
- end
- end
- if dtd == "text" or dtd.value then
- t_insert(current, value);
- elseif dtd.values then
- for p in ("\30"..value):gmatch("\30([^\30]*)") do
- t_insert(current, p);
- end
- end
- current = up;
- end
- end
- return vCards;
-end
-
-local function item_to_text(item)
- local value = {};
- for i=1,#item do
- value[i] = vCard_esc(item[i]);
- end
- value = t_concat(value, ";");
-
- local params = "";
- for k,v in pairs(item) do
- if type(k) == "string" and k ~= "name" then
- params = params .. (";%s=%s"):format(k, type(v) == "table" and t_concat(v,",") or v);
- end
- end
-
- return ("%s%s:%s"):format(item.name, params, value)
-end
-
-local function vcard_to_text(vcard)
- local t={};
- t_insert(t, "BEGIN:VCARD")
- for i=1,#vcard do
- t_insert(t, item_to_text(vcard[i]));
- end
- t_insert(t, "END:VCARD")
- return t_concat(t, line_sep);
-end
-
-function to_text(vCards)
- if vCards[1] and vCards[1].name then
- return vcard_to_text(vCards)
- else
- local t = {};
- for i=1,#vCards do
- t[i]=vcard_to_text(vCards[i]);
- end
- return t_concat(t, line_sep);
- end
-end
-
-local function from_xep54_item(item)
- local prop_name = item.name;
- local prop_def = vCard_dtd[prop_name];
-
- local prop = { name = prop_name };
-
- if prop_def == "text" then
- prop[1] = item:get_text();
- elseif type(prop_def) == "table" then
- if prop_def.value then --single item
- prop[1] = item:get_child_text(prop_def.value) or "";
- elseif prop_def.values then --array
- local value_names = prop_def.values;
- if value_names.behaviour == "repeat-last" then
- for i=1,#item.tags do
- t_insert(prop, item.tags[i]:get_text() or "");
- end
- else
- for i=1,#value_names do
- t_insert(prop, item:get_child_text(value_names[i]) or "");
- end
- end
- elseif prop_def.names then
- local names = prop_def.names;
- for i=1,#names do
- if item:get_child(names[i]) then
- prop[1] = names[i];
- break;
- end
- end
- end
-
- if prop_def.props_verbatim then
- for k,v in pairs(prop_def.props_verbatim) do
- prop[k] = v;
- end
- end
-
- if prop_def.types then
- local types = prop_def.types;
- prop.TYPE = {};
- for i=1,#types do
- if item:get_child(types[i]) then
- t_insert(prop.TYPE, types[i]:lower());
- end
- end
- if #prop.TYPE == 0 then
- prop.TYPE = nil;
- end
- end
-
- -- A key-value pair, within a key-value pair?
- if prop_def.props then
- local params = prop_def.props;
- for i=1,#params do
- local name = params[i]
- local data = item:get_child_text(name);
- if data then
- prop[name] = prop[name] or {};
- t_insert(prop[name], data);
- end
- end
- end
- else
- return nil
- end
-
- return prop;
-end
-
-local function from_xep54_vCard(vCard)
- local tags = vCard.tags;
- local t = {};
- for i=1,#tags do
- t_insert(t, from_xep54_item(tags[i]));
- end
- return t
-end
-
-function from_xep54(vCard)
- if vCard.attr.xmlns ~= "vcard-temp" then
- return nil, "wrong-xmlns";
- end
- if vCard.name == "xCard" then -- A collection of vCards
- local t = {};
- local vCards = vCard.tags;
- for i=1,#vCards do
- t[i] = from_xep54_vCard(vCards[i]);
- end
- return t
- elseif vCard.name == "vCard" then -- A single vCard
- return from_xep54_vCard(vCard)
- end
-end
-
-local vcard4 = { }
-
-function vcard4:text(node, params, value) -- luacheck: ignore 212/params
- self:tag(node:lower())
- -- FIXME params
- if type(value) == "string" then
- self:text_tag("text", value);
- elseif vcard4[node] then
- vcard4[node](value);
- end
- self:up();
-end
-
-function vcard4.N(value)
- for i, k in ipairs(vCard_dtd.N.values) do
- value:text_tag(k, value[i]);
- end
-end
-
-local xmlns_vcard4 = "urn:ietf:params:xml:ns:vcard-4.0"
-
-local function item_to_vcard4(item)
- local typ = item.name:lower();
- local t = st.stanza(typ, { xmlns = xmlns_vcard4 });
-
- local prop_def = vCard4_dtd[typ];
- if prop_def == "text" then
- t:text_tag("text", item[1]);
- elseif prop_def == "uri" then
- if item.ENCODING and item.ENCODING[1] == 'b' then
- t:text_tag("uri", "data:;base64," .. item[1]);
- else
- t:text_tag("uri", item[1]);
- end
- elseif type(prop_def) == "table" then
- if prop_def.values then
- for i, v in ipairs(prop_def.values) do
- t:text_tag(v:lower(), item[i]);
- end
- else
- t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"})
- end
- else
- t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"})
- end
- return t;
-end
-
-local function vcard_to_vcard4xml(vCard)
- local t = st.stanza("vcard", { xmlns = xmlns_vcard4 });
- for i=1,#vCard do
- t:add_child(item_to_vcard4(vCard[i]));
- end
- return t;
-end
-
-local function vcards_to_vcard4xml(vCards)
- if not vCards[1] or vCards[1].name then
- return vcard_to_vcard4xml(vCards)
- else
- local t = st.stanza("vcards", { xmlns = xmlns_vcard4 });
- for i=1,#vCards do
- t:add_child(vcard_to_vcard4xml(vCards[i]));
- end
- return t;
- end
-end
-
--- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd
-vCard_dtd = {
- VERSION = "text", --MUST be 3.0, so parsing is redundant
- FN = "text",
- N = {
- values = {
- "FAMILY",
- "GIVEN",
- "MIDDLE",
- "PREFIX",
- "SUFFIX",
- },
- },
- NICKNAME = "text",
- PHOTO = {
- props_verbatim = { ENCODING = { "b" } },
- props = { "TYPE" },
- value = "BINVAL", --{ "EXTVAL", },
- },
- BDAY = "text",
- ADR = {
- types = {
- "HOME",
- "WORK",
- "POSTAL",
- "PARCEL",
- "DOM",
- "INTL",
- "PREF",
- },
- values = {
- "POBOX",
- "EXTADD",
- "STREET",
- "LOCALITY",
- "REGION",
- "PCODE",
- "CTRY",
- }
- },
- LABEL = {
- types = {
- "HOME",
- "WORK",
- "POSTAL",
- "PARCEL",
- "DOM",
- "INTL",
- "PREF",
- },
- value = "LINE",
- },
- TEL = {
- types = {
- "HOME",
- "WORK",
- "VOICE",
- "FAX",
- "PAGER",
- "MSG",
- "CELL",
- "VIDEO",
- "BBS",
- "MODEM",
- "ISDN",
- "PCS",
- "PREF",
- },
- value = "NUMBER",
- },
- EMAIL = {
- types = {
- "HOME",
- "WORK",
- "INTERNET",
- "PREF",
- "X400",
- },
- value = "USERID",
- },
- JABBERID = "text",
- MAILER = "text",
- TZ = "text",
- GEO = {
- values = {
- "LAT",
- "LON",
- },
- },
- TITLE = "text",
- ROLE = "text",
- LOGO = "copy of PHOTO",
- AGENT = "text",
- ORG = {
- values = {
- behaviour = "repeat-last",
- "ORGNAME",
- "ORGUNIT",
- }
- },
- CATEGORIES = {
- values = "KEYWORD",
- },
- NOTE = "text",
- PRODID = "text",
- REV = "text",
- SORTSTRING = "text",
- SOUND = "copy of PHOTO",
- UID = "text",
- URL = "text",
- CLASS = {
- names = { -- The item.name is the value if it's one of these.
- "PUBLIC",
- "PRIVATE",
- "CONFIDENTIAL",
- },
- },
- KEY = {
- props = { "TYPE" },
- value = "CRED",
- },
- DESC = "text",
-};
-vCard_dtd.LOGO = vCard_dtd.PHOTO;
-vCard_dtd.SOUND = vCard_dtd.PHOTO;
-
-vCard4_dtd = {
- source = "uri",
- kind = "text",
- xml = "text",
- fn = "text",
- n = {
- values = {
- "family",
- "given",
- "middle",
- "prefix",
- "suffix",
- },
- },
- nickname = "text",
- photo = "uri",
- bday = "date-and-or-time",
- anniversary = "date-and-or-time",
- gender = "text",
- adr = {
- values = {
- "pobox",
- "ext",
- "street",
- "locality",
- "region",
- "code",
- "country",
- }
- },
- tel = "text",
- email = "text",
- impp = "uri",
- lang = "language-tag",
- tz = "text",
- geo = "uri",
- title = "text",
- role = "text",
- logo = "uri",
- org = "text",
- member = "uri",
- related = "uri",
- categories = "text",
- note = "text",
- prodid = "text",
- rev = "timestamp",
- sound = "uri",
- uid = "uri",
- clientpidmap = "number, uuid",
- url = "uri",
- version = "text",
- key = "uri",
- fburl = "uri",
- caladruri = "uri",
- caluri = "uri",
-};
-
-return {
- from_text = from_text;
- to_text = to_text;
-
- from_xep54 = from_xep54;
- to_xep54 = to_xep54;
-
- to_vcard4 = vcards_to_vcard4xml;
-};