1 local st
= require
"util.stanza";
2 local jid_split
= require
"util.jid".split
;
4 local mod_pep
= module
:depends("pep");
6 local sha1
= require
"util.hashes".sha1
;
7 local base64_decode
= require
"util.encodings".base64
.decode
;
9 local vcards
= module
:open_store("vcard");
11 module
:add_feature("vcard-temp");
12 module
:hook("account-disco-info", function (event
)
13 event
.reply
:tag("feature", { var
= "urn:xmpp:pep-vcard-conversion:0" }):up();
16 local function handle_error(origin
, stanza
, err
)
17 if err
== "forbidden" then
18 origin
.send(st
.error_reply(stanza
, "auth", "forbidden"));
19 elseif err
== "internal-server-error" then
20 origin
.send(st
.error_reply(stanza
, "wait", "internal-server-error"));
22 origin
.send(st
.error_reply(stanza
, "modify", "undefined-condition", err
));
26 -- Simple translations
27 -- <foo><text>hey</text></foo> -> <FOO>hey</FOO>
38 module
:hook("iq-get/bare/vcard-temp:vCard", function (event
)
39 local origin
, stanza
= event
.origin
, event
.stanza
;
40 local pep_service
= mod_pep
.get_pep_service(jid_split(stanza
.attr
.to
) or origin
.username
);
41 local ok
, id
, vcard4_item
= pep_service
:get_last_item("urn:xmpp:vcard4", stanza
.attr
.from
);
43 local vcard_temp
= st
.stanza("vCard", { xmlns
= "vcard-temp" });
44 if ok
and vcard4_item
then
45 local vcard4
= vcard4_item
.tags
[1];
47 local fn
= vcard4
:get_child("fn");
48 vcard_temp
:text_tag("FN", fn
and fn
:get_child_text("text"));
50 local v4n
= vcard4
:get_child("n");
52 :text_tag("FAMILY", v4n
and v4n
:get_child_text("surname"))
53 :text_tag("GIVEN", v4n
and v4n
:get_child_text("given"))
54 :text_tag("MIDDLE", v4n
and v4n
:get_child_text("additional"))
55 :text_tag("PREFIX", v4n
and v4n
:get_child_text("prefix"))
56 :text_tag("SUFFIX", v4n
and v4n
:get_child_text("suffix"))
59 for tag in vcard4
:childtags() do
60 local typ
= simple_map
[tag.name
];
62 local text
= tag:get_child_text(typ
);
64 vcard_temp
:text_tag(tag.name
:upper(), text
);
66 elseif tag.name
== "email" then
67 local text
= tag:get_child_text("text");
69 vcard_temp
:tag("EMAIL")
70 :text_tag("USERID", text
)
71 :tag("INTERNET"):up();
72 if tag:find
"parameters/type/text#" == "home" then
73 vcard_temp
:tag("HOME"):up();
74 elseif tag:find
"parameters/type/text#" == "work" then
75 vcard_temp
:tag("WORK"):up();
79 elseif tag.name
== "tel" then
80 local text
= tag:get_child_text("uri");
82 if text
:sub(1, 4) == "tel:" then
85 vcard_temp
:tag("TEL"):text_tag("NUMBER", text
);
86 if tag:find
"parameters/type/text#" == "home" then
87 vcard_temp
:tag("HOME"):up();
88 elseif tag:find
"parameters/type/text#" == "work" then
89 vcard_temp
:tag("WORK"):up();
93 elseif tag.name
== "adr" then
95 :text_tag("POBOX", tag:get_child_text("pobox"))
96 :text_tag("EXTADD", tag:get_child_text("ext"))
97 :text_tag("STREET", tag:get_child_text("street"))
98 :text_tag("LOCALITY", tag:get_child_text("locality"))
99 :text_tag("REGION", tag:get_child_text("region"))
100 :text_tag("PCODE", tag:get_child_text("code"))
101 :text_tag("CTRY", tag:get_child_text("country"));
102 if tag:find
"parameters/type/text#" == "home" then
103 vcard_temp
:tag("HOME"):up();
104 elseif tag:find
"parameters/type/text#" == "work" then
105 vcard_temp
:tag("WORK"):up();
108 elseif tag.name
== "impp" then
109 local uri
= tag:get_child_text("uri");
110 if uri
and uri
:sub(1, 5) == "xmpp:" then
111 vcard_temp
:text_tag("JABBERID", uri
:sub(6))
113 elseif tag.name
== "org" then
114 vcard_temp
:tag("ORG")
115 :text_tag("ORGNAME", tag:get_child_text("text"))
120 local ok
, _
, nick_item
= pep_service
:get_last_item("http://jabber.org/protocol/nick", stanza
.attr
.from
);
121 if ok
and nick_item
then
122 local nickname
= nick_item
:get_child_text("nick", "http://jabber.org/protocol/nick");
124 vcard_temp
:text_tag("NICKNAME", nickname
);
129 local meta_ok
, avatar_meta
= pep_service
:get_items("urn:xmpp:avatar:metadata", stanza
.attr
.from
);
130 local data_ok
, avatar_data
= pep_service
:get_items("urn:xmpp:avatar:data", stanza
.attr
.from
);
133 for _
, hash
in ipairs(avatar_data
) do
134 local meta
= meta_ok
and avatar_meta
[hash
];
135 local data
= avatar_data
[hash
];
136 local info
= meta
and meta
.tags
[1]:get_child("info");
137 vcard_temp
:tag("PHOTO");
138 if info
and info
.attr
.type then
139 vcard_temp
:text_tag("TYPE", info
.attr
.type);
142 vcard_temp
:text_tag("BINVAL", data
.tags
[1]:get_text());
143 elseif info
and info
.attr
.url
then
144 vcard_temp
:text_tag("EXTVAL", info
.attr
.url
);
150 origin
.send(st
.reply(stanza
):add_child(vcard_temp
));
154 local node_defaults
= {
155 access_model
= "open";
156 _defaults_only
= true;
159 function vcard_to_pep(vcard_temp
)
162 local vcard4
= st
.stanza("item", { xmlns
= "http://jabber.org/protocol/pubsub", id
= "current" })
163 :tag("vcard", { xmlns
= 'urn:ietf:params:xml:ns:vcard-4.0' });
165 vcard4
:tag("fn"):text_tag("text", vcard_temp
:get_child_text("FN")):up();
167 local N
= vcard_temp
:get_child("N");
170 :text_tag("surname", N
and N
:get_child_text("FAMILY"))
171 :text_tag("given", N
and N
:get_child_text("GIVEN"))
172 :text_tag("additional", N
and N
:get_child_text("MIDDLE"))
173 :text_tag("prefix", N
and N
:get_child_text("PREFIX"))
174 :text_tag("suffix", N
and N
:get_child_text("SUFFIX"))
177 for tag in vcard_temp
:childtags() do
178 local typ
= simple_map
[tag.name
:lower()];
180 local text
= tag:get_text();
182 vcard4
:tag(tag.name
:lower()):text_tag(typ
, text
):up();
184 elseif tag.name
== "EMAIL" then
185 local text
= tag:get_child_text("USERID");
188 vcard4
:text_tag("text", text
)
189 vcard4
:tag("parameters"):tag("type");
190 if tag:get_child("HOME") then
191 vcard4
:text_tag("text", "home");
192 elseif tag:get_child("WORK") then
193 vcard4
:text_tag("text", "work");
195 vcard4
:up():up():up();
197 elseif tag.name
== "TEL" then
198 local text
= tag:get_child_text("NUMBER");
200 vcard4
:tag("tel"):text_tag("uri", "tel:"..text
);
202 vcard4
:tag("parameters"):tag("type");
203 if tag:get_child("HOME") then
204 vcard4
:text_tag("text", "home");
205 elseif tag:get_child("WORK") then
206 vcard4
:text_tag("text", "work");
208 vcard4
:up():up():up();
209 elseif tag.name
== "ORG" then
210 local text
= tag:get_child_text("ORGNAME");
212 vcard4
:tag("org"):text_tag("text", text
):up();
214 elseif tag.name
== "DESC" then
215 local text
= tag:get_text();
217 vcard4
:tag("note"):text_tag("text", text
):up();
219 -- <note> gets mapped into <NOTE> in the other direction
220 elseif tag.name
== "ADR" then
222 :text_tag("pobox", tag:get_child_text("POBOX"))
223 :text_tag("ext", tag:get_child_text("EXTADD"))
224 :text_tag("street", tag:get_child_text("STREET"))
225 :text_tag("locality", tag:get_child_text("LOCALITY"))
226 :text_tag("region", tag:get_child_text("REGION"))
227 :text_tag("code", tag:get_child_text("PCODE"))
228 :text_tag("country", tag:get_child_text("CTRY"));
229 vcard4
:tag("parameters"):tag("type");
230 if tag:get_child("HOME") then
231 vcard4
:text_tag("text", "home");
232 elseif tag:get_child("WORK") then
233 vcard4
:text_tag("text", "work");
235 vcard4
:up():up():up();
236 elseif tag.name
== "JABBERID" then
238 :text_tag("uri", "xmpp:" .. tag:get_text())
240 elseif tag.name
== "PHOTO" then
241 local avatar_type
= tag:get_child_text("TYPE");
242 local avatar_payload
= tag:get_child_text("BINVAL");
243 -- Can EXTVAL be translated? No way to know the sha1 of the data?
245 if avatar_payload
then
246 local avatar_raw
= base64_decode(avatar_payload
);
247 local avatar_hash
= sha1(avatar_raw
, true);
249 local avatar_meta
= st
.stanza("item", { id
= avatar_hash
, xmlns
= "http://jabber.org/protocol/pubsub" })
250 :tag("metadata", { xmlns
="urn:xmpp:avatar:metadata" })
252 bytes
= tostring(#avatar_raw
),
257 local avatar_data
= st
.stanza("item", { id
= avatar_hash
, xmlns
= "http://jabber.org/protocol/pubsub" })
258 :tag("data", { xmlns
="urn:xmpp:avatar:data" })
259 :text(avatar_payload
);
261 table.insert(avatars
, { hash
= avatar_hash
, meta
= avatar_meta
, data
= avatar_data
});
265 return vcard4
, avatars
;
268 function save_to_pep(pep_service
, actor
, vcard4
, avatars
)
271 if pep_service
:purge("urn:xmpp:avatar:metadata", actor
) then
272 pep_service
:purge("urn:xmpp:avatar:data", actor
);
275 local avatar_defaults
= node_defaults
;
277 avatar_defaults
= {};
278 for k
,v
in pairs(node_defaults
) do
279 avatar_defaults
[k
] = v
;
281 avatar_defaults
.max_items
= #avatars
;
283 for _
, avatar
in ipairs(avatars
) do
284 local ok
, err
= pep_service
:publish("urn:xmpp:avatar:data", actor
, avatar
.hash
, avatar
.data
, avatar_defaults
);
286 ok
, err
= pep_service
:publish("urn:xmpp:avatar:metadata", actor
, avatar
.hash
, avatar
.meta
, avatar_defaults
);
295 return pep_service
:publish("urn:xmpp:vcard4", actor
, "current", vcard4
, node_defaults
);
301 module
:hook("iq-set/self/vcard-temp:vCard", function (event
)
302 local origin
, stanza
= event
.origin
, event
.stanza
;
303 local pep_service
= mod_pep
.get_pep_service(origin
.username
);
305 local vcard_temp
= stanza
.tags
[1];
307 local ok
, err
= save_to_pep(pep_service
, origin
.full_jid
, vcard_to_pep(vcard_temp
));
309 origin
.send(st
.reply(stanza
));
311 handle_error(origin
, stanza
, err
);
317 local function inject_xep153(event
)
318 local origin
, stanza
= event
.origin
, event
.stanza
;
319 local username
= origin
.username
;
320 if not username
then return end
321 if stanza
.attr
.type then return end
322 local pep_service
= mod_pep
.get_pep_service(username
);
324 stanza
:remove_children("x", "vcard-temp:x:update");
325 local x_update
= st
.stanza("x", { xmlns
= "vcard-temp:x:update" });
326 local ok
, avatar_hash
= pep_service
:get_last_item("urn:xmpp:avatar:metadata", true);
327 if ok
and avatar_hash
then
328 x_update
:text_tag("photo", avatar_hash
);
330 stanza
:add_direct_child(x_update
);
333 module
:hook("pre-presence/full", inject_xep153
, 1);
334 module
:hook("pre-presence/bare", inject_xep153
, 1);
335 module
:hook("pre-presence/host", inject_xep153
, 1);
337 if module
:get_option_boolean("upgrade_legacy_vcards", true) then
338 module
:hook("resource-bind", function (event
)
339 local session
= event
.session
;
340 local username
= session
.username
;
341 local vcard_temp
= vcards
:get(username
);
342 if not vcard_temp
then
343 session
.log("debug", "No legacy vCard to migrate or already migrated");
346 local pep_service
= mod_pep
.get_pep_service(username
);
347 vcard_temp
= st
.deserialize(vcard_temp
);
348 local vcard4
, avatars
= vcard_to_pep(vcard_temp
);
349 if pep_service
:get_last_item("urn:xmpp:vcard4", true) then
352 if pep_service
:get_last_item("urn:xmpp:avatar:metadata", true)
353 or pep_service
:get_last_item("urn:xmpp:avatar:data", true) then
356 if not (vcard4
or avatars
) then
357 session
.log("debug", "Already PEP data, not overwriting with migrated data");
358 vcards
:set(username
, nil);
361 local ok
, err
= save_to_pep(pep_service
, true, vcard4
, avatars
);
362 if ok
and vcards
:set(username
, nil) then
363 session
.log("info", "Migrated vCard-temp to PEP");
365 session
.log("info", "Failed to migrate vCard-temp to PEP: %s", err
or "problem emptying 'vcard' store");