Mercurial > prosody-modules
view mod_email_pass/vcard.lib.lua @ 5401:c8d04ac200fc
mod_http_oauth2: Reject loopback URIs as client_uri
This really should be a proper website with info, https://localhost is
not good enough. Ideally we'd validate that it's got proper DNS and is
actually reachable, but triggering HTTP or even DNS lookups seems like
it would carry abuse potential that would best to avoid.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Tue, 02 May 2023 16:20:55 +0200 |
parents | 5608dd296d5f |
children |
line wrap: on
line source
-- Copyright (C) 2011-2012 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 next, pairs, ipairs = next, pairs, ipairs; local from_text, to_text, from_xep54, to_xep54; local line_sep = "\n"; local vCard_dtd; -- See end of file local function fold_line() error "Not implemented" --TODO end local function unfold_line() error "Not implemented" -- gsub("\r?\n[ \t]([^\r\n])", "%1"); end 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 espace : 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 _,v in pairs(prop_def.props) do if item[v] then t:tag(v):up(); end end end if prop_def.value then t:tag(prop_def.value):text(item[1]):up(); 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:tag(prop_def.values[i] or repeat_last):text(item[i]):up(); 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 c; -- current item for line in data:gmatch("[^\n]+") do local 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 c = {}; vCards[#vCards+1] = c; elseif name == "END" and value == "VCARD" then c = nil; elseif vCard_dtd[name] then local dtd = vCard_dtd[name]; local p = { name = name }; c[#c+1]=p; --c[name]=p; local up = c; c = p; if dtd.types then for _, t in ipairs(dtd.types) do local t = t:lower(); if ( params.TYPE and params.TYPE[t] == true) or params[t] == true then c.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 c[p]=true; else for _, prop in ipairs(params[p]) do c[p]=prop; end end end end end if dtd == "text" or dtd.value then t_insert(c, value); elseif dtd.values then local value = "\30"..value; for p in value:gmatch("\30([^\30]*)") do t_insert(c, p); end end c = 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 -- 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; return { from_text = from_text; to_text = to_text; from_xep54 = from_xep54; to_xep54 = to_xep54; -- COMPAT: lua_to_text = to_text; lua_to_xep54 = to_xep54; text_to_lua = from_text; text_to_xep54 = function (...) return to_xep54(from_text(...)); end; xep54_to_lua = from_xep54; xep54_to_text = function (...) return to_text(from_xep54(...)) end; };