1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
|
local st = require "prosody.util.stanza";
local jid_split = require "prosody.util.jid".split;
local mod_pep = module:depends("pep");
local sha1 = require "prosody.util.hashes".sha1;
local base64_decode = require "prosody.util.encodings".base64.decode;
local vcards = module:open_store("vcard");
module:add_feature("vcard-temp");
module:hook("account-disco-info", function (event)
event.reply:tag("feature", { var = "urn:xmpp:pep-vcard-conversion:0" }):up();
end);
local function handle_error(origin, stanza, err)
if err == "forbidden" then
origin.send(st.error_reply(stanza, "auth", "forbidden"));
elseif err == "internal-server-error" then
origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
else
origin.send(st.error_reply(stanza, "modify", "undefined-condition", err));
end
end
-- Simple translations
-- <foo><text>hey</text></foo> -> <FOO>hey</FOO>
local simple_map = {
nickname = "text";
title = "text";
role = "text";
categories = "text";
note = "text";
url = "uri";
bday = "date";
}
module:hook("iq-get/bare/vcard-temp:vCard", function (event)
local origin, stanza = event.origin, event.stanza;
local pep_service = mod_pep.get_pep_service(jid_split(stanza.attr.to) or origin.username);
local ok, _, vcard4_item = pep_service:get_last_item("urn:xmpp:vcard4", stanza.attr.from);
local vcard_temp = st.stanza("vCard", { xmlns = "vcard-temp" });
if ok and vcard4_item then
local vcard4 = vcard4_item.tags[1];
local fn = vcard4:get_child("fn");
vcard_temp:text_tag("FN", fn and fn:get_child_text("text"));
local v4n = vcard4:get_child("n");
vcard_temp:tag("N")
:text_tag("FAMILY", v4n and v4n:get_child_text("surname"))
:text_tag("GIVEN", v4n and v4n:get_child_text("given"))
:text_tag("MIDDLE", v4n and v4n:get_child_text("additional"))
:text_tag("PREFIX", v4n and v4n:get_child_text("prefix"))
:text_tag("SUFFIX", v4n and v4n:get_child_text("suffix"))
:up();
for tag in vcard4:childtags() do
local typ = simple_map[tag.name];
if typ then
local text = tag:get_child_text(typ);
if text then
vcard_temp:text_tag(tag.name:upper(), text);
end
elseif tag.name == "email" then
local text = tag:get_child_text("text");
if text then
vcard_temp:tag("EMAIL")
:text_tag("USERID", text)
:tag("INTERNET"):up();
if tag:find"parameters/type/text#" == "home" then
vcard_temp:tag("HOME"):up();
elseif tag:find"parameters/type/text#" == "work" then
vcard_temp:tag("WORK"):up();
end
vcard_temp:up();
end
elseif tag.name == "tel" then
local text = tag:get_child_text("uri");
if text then
if text:sub(1, 4) == "tel:" then
text = text:sub(5)
end
vcard_temp:tag("TEL"):text_tag("NUMBER", text);
if tag:find"parameters/type/text#" == "home" then
vcard_temp:tag("HOME"):up();
elseif tag:find"parameters/type/text#" == "work" then
vcard_temp:tag("WORK"):up();
end
vcard_temp:up();
end
elseif tag.name == "adr" then
vcard_temp:tag("ADR")
:text_tag("POBOX", tag:get_child_text("pobox"))
:text_tag("EXTADD", tag:get_child_text("ext"))
:text_tag("STREET", tag:get_child_text("street"))
:text_tag("LOCALITY", tag:get_child_text("locality"))
:text_tag("REGION", tag:get_child_text("region"))
:text_tag("PCODE", tag:get_child_text("code"))
:text_tag("CTRY", tag:get_child_text("country"));
if tag:find"parameters/type/text#" == "home" then
vcard_temp:tag("HOME"):up();
elseif tag:find"parameters/type/text#" == "work" then
vcard_temp:tag("WORK"):up();
end
vcard_temp:up();
elseif tag.name == "impp" then
local uri = tag:get_child_text("uri");
if uri and uri:sub(1, 5) == "xmpp:" then
vcard_temp:text_tag("JABBERID", uri:sub(6))
end
elseif tag.name == "org" then
vcard_temp:tag("ORG")
:text_tag("ORGNAME", tag:get_child_text("text"))
:up();
end
end
else
local ok, _, nick_item = pep_service:get_last_item("http://jabber.org/protocol/nick", stanza.attr.from);
if ok and nick_item then
local nickname = nick_item:get_child_text("nick", "http://jabber.org/protocol/nick");
if nickname then
vcard_temp:text_tag("NICKNAME", nickname);
end
end
end
local ok, avatar_hash, meta = pep_service:get_last_item("urn:xmpp:avatar:metadata", stanza.attr.from);
if ok and avatar_hash then
local info = meta.tags[1]:get_child("info");
if info then
vcard_temp:tag("PHOTO");
if info.attr.type then
vcard_temp:text_tag("TYPE", info.attr.type);
end
if info.attr.url then
vcard_temp:text_tag("EXTVAL", info.attr.url);
elseif info.attr.id then
local data_ok, avatar_data = pep_service:get_items("urn:xmpp:avatar:data", stanza.attr.from, { info.attr.id });
if data_ok and avatar_data and avatar_data[info.attr.id] then
local data = avatar_data[info.attr.id];
vcard_temp:text_tag("BINVAL", data.tags[1]:get_text());
end
end
vcard_temp:up();
end
end
origin.send(st.reply(stanza):add_child(vcard_temp));
return true;
end);
local node_defaults = {
access_model = "open";
_defaults_only = true;
};
function vcard_to_pep(vcard_temp)
local avatar = {};
local vcard4 = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = "current" })
:tag("vcard", { xmlns = 'urn:ietf:params:xml:ns:vcard-4.0' });
vcard4:tag("fn"):text_tag("text", vcard_temp:get_child_text("FN")):up();
local N = vcard_temp:get_child("N");
vcard4:tag("n")
:text_tag("surname", N and N:get_child_text("FAMILY"))
:text_tag("given", N and N:get_child_text("GIVEN"))
:text_tag("additional", N and N:get_child_text("MIDDLE"))
:text_tag("prefix", N and N:get_child_text("PREFIX"))
:text_tag("suffix", N and N:get_child_text("SUFFIX"))
:up();
for tag in vcard_temp:childtags() do
local typ = simple_map[tag.name:lower()];
if typ then
local text = tag:get_text();
if text then
vcard4:tag(tag.name:lower()):text_tag(typ, text):up();
end
elseif tag.name == "EMAIL" then
local text = tag:get_child_text("USERID");
if text then
vcard4:tag("email")
vcard4:text_tag("text", text)
vcard4:tag("parameters"):tag("type");
if tag:get_child("HOME") then
vcard4:text_tag("text", "home");
elseif tag:get_child("WORK") then
vcard4:text_tag("text", "work");
end
vcard4:up():up():up();
end
elseif tag.name == "TEL" then
local text = tag:get_child_text("NUMBER");
if text then
vcard4:tag("tel"):text_tag("uri", "tel:"..text);
end
vcard4:tag("parameters"):tag("type");
if tag:get_child("HOME") then
vcard4:text_tag("text", "home");
elseif tag:get_child("WORK") then
vcard4:text_tag("text", "work");
end
vcard4:up():up():up();
elseif tag.name == "ORG" then
local text = tag:get_child_text("ORGNAME");
if text then
vcard4:tag("org"):text_tag("text", text):up();
end
elseif tag.name == "DESC" then
local text = tag:get_text();
if text then
vcard4:tag("note"):text_tag("text", text):up();
end
-- <note> gets mapped into <NOTE> in the other direction
elseif tag.name == "ADR" then
vcard4:tag("adr")
:text_tag("pobox", tag:get_child_text("POBOX"))
:text_tag("ext", tag:get_child_text("EXTADD"))
:text_tag("street", tag:get_child_text("STREET"))
:text_tag("locality", tag:get_child_text("LOCALITY"))
:text_tag("region", tag:get_child_text("REGION"))
:text_tag("code", tag:get_child_text("PCODE"))
:text_tag("country", tag:get_child_text("CTRY"));
vcard4:tag("parameters"):tag("type");
if tag:get_child("HOME") then
vcard4:text_tag("text", "home");
elseif tag:get_child("WORK") then
vcard4:text_tag("text", "work");
end
vcard4:up():up():up();
elseif tag.name == "JABBERID" then
vcard4:tag("impp")
:text_tag("uri", "xmpp:" .. tag:get_text())
:up();
elseif tag.name == "PHOTO" then
local avatar_type = tag:get_child_text("TYPE");
local avatar_payload = tag:get_child_text("BINVAL");
-- Can EXTVAL be translated? No way to know the sha1 of the data?
if avatar_payload then
local avatar_raw = base64_decode(avatar_payload);
local avatar_hash = sha1(avatar_raw, true);
avatar.hash = avatar_hash;
avatar.meta = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" })
:tag("metadata", { xmlns="urn:xmpp:avatar:metadata" })
:tag("info", {
bytes = tostring(#avatar_raw),
id = avatar_hash,
type = avatar_type,
});
avatar.data = st.stanza("item", { id = avatar_hash, xmlns = "http://jabber.org/protocol/pubsub" })
:tag("data", { xmlns="urn:xmpp:avatar:data" })
:text(avatar_payload);
end
end
end
return vcard4, avatar;
end
function save_to_pep(pep_service, actor, vcard4, avatar)
if avatar then
if pep_service:purge("urn:xmpp:avatar:metadata", actor) then
pep_service:purge("urn:xmpp:avatar:data", actor);
end
if avatar.data and avatar.meta then
local ok, err = assert(pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults));
if ok then
ok, err = assert(pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults));
end
if not ok then
return ok, err;
end
end
end
if vcard4 then
return pep_service:publish("urn:xmpp:vcard4", actor, "current", vcard4, node_defaults);
end
return true;
end
module:hook("iq-set/self/vcard-temp:vCard", function (event)
local origin, stanza = event.origin, event.stanza;
local pep_service = mod_pep.get_pep_service(origin.username);
local vcard_temp = stanza.tags[1];
local ok, err = save_to_pep(pep_service, origin.full_jid, vcard_to_pep(vcard_temp));
if ok then
origin.send(st.reply(stanza));
else
handle_error(origin, stanza, err);
end
return true;
end);
local function inject_xep153(event)
local origin, stanza = event.origin, event.stanza;
local username = origin.username;
if not username then return end
if stanza.attr.type then return end
local pep_service = mod_pep.get_pep_service(username);
local x_update = stanza:get_child("x", "vcard-temp:x:update");
if not x_update then
x_update = st.stanza("x", { xmlns = "vcard-temp:x:update" }):tag("photo");
stanza:add_direct_child(x_update);
elseif x_update:get_child("photo") then
return; -- XEP implies that these should be left alone
else
x_update:tag("photo");
end
local ok, avatar_hash = pep_service:get_last_item("urn:xmpp:avatar:metadata", true);
if ok and avatar_hash then
x_update:text(avatar_hash);
end
end
module:hook("pre-presence/full", inject_xep153, 1);
module:hook("pre-presence/bare", inject_xep153, 1);
module:hook("pre-presence/host", inject_xep153, 1);
if module:get_option_boolean("upgrade_legacy_vcards", true) then
module:hook("resource-bind", function (event)
local session = event.session;
local username = session.username;
local vcard_temp = vcards:get(username);
if not vcard_temp then
session.log("debug", "No legacy vCard to migrate or already migrated");
return;
end
local pep_service = mod_pep.get_pep_service(username);
vcard_temp = st.deserialize(vcard_temp);
local vcard4, avatars = vcard_to_pep(vcard_temp);
if pep_service:get_last_item("urn:xmpp:vcard4", true) then
vcard4 = nil;
end
if pep_service:get_last_item("urn:xmpp:avatar:metadata", true)
or pep_service:get_last_item("urn:xmpp:avatar:data", true) then
avatars = nil;
end
if not (vcard4 or avatars) then
session.log("debug", "Already PEP data, not overwriting with migrated data");
vcards:set(username, nil);
return;
end
local ok, err = save_to_pep(pep_service, true, vcard4, avatars);
if ok and vcards:set(username, nil) then
session.log("info", "Migrated vCard-temp to PEP");
else
session.log("info", "Failed to migrate vCard-temp to PEP: %s", err or "problem emptying 'vcard' store");
end
end);
end
|