mod_s2s: Handle authentication of s2sin and s2sout the same way
[prosody.git] / plugins / mod_vcard_legacy.lua
blob91497493cc1b1ff15c574654264fe87030b0c59c
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();
14 end);
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"));
21 else
22 origin.send(st.error_reply(stanza, "modify", "undefined-condition", err));
23 end
24 end
26 -- Simple translations
27 -- <foo><text>hey</text></foo> -> <FOO>hey</FOO>
28 local simple_map = {
29 nickname = "text";
30 title = "text";
31 role = "text";
32 categories = "text";
33 note = "text";
34 url = "uri";
35 bday = "date";
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");
51 vcard_temp:tag("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"))
57 :up();
59 for tag in vcard4:childtags() do
60 local typ = simple_map[tag.name];
61 if typ then
62 local text = tag:get_child_text(typ);
63 if text then
64 vcard_temp:text_tag(tag.name:upper(), text);
65 end
66 elseif tag.name == "email" then
67 local text = tag:get_child_text("text");
68 if text then
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();
76 end
77 vcard_temp:up();
78 end
79 elseif tag.name == "tel" then
80 local text = tag:get_child_text("uri");
81 if text then
82 if text:sub(1, 4) == "tel:" then
83 text = text:sub(5)
84 end
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();
90 end
91 vcard_temp:up();
92 end
93 elseif tag.name == "adr" then
94 vcard_temp:tag("ADR")
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();
107 vcard_temp: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"))
116 :up();
119 else
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");
123 if nickname then
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);
132 if data_ok then
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);
141 if data then
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);
146 vcard_temp:up();
150 origin.send(st.reply(stanza):add_child(vcard_temp));
151 return true;
152 end);
154 local node_defaults = {
155 access_model = "open";
156 _defaults_only = true;
159 function vcard_to_pep(vcard_temp)
160 local avatars = {};
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");
169 vcard4:tag("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"))
175 :up();
177 for tag in vcard_temp:childtags() do
178 local typ = simple_map[tag.name:lower()];
179 if typ then
180 local text = tag:get_text();
181 if text then
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");
186 if text then
187 vcard4:tag("email")
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");
199 if text then
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");
211 if text then
212 vcard4:tag("org"):text_tag("text", text):up();
214 elseif tag.name == "DESC" then
215 local text = tag:get_text();
216 if text then
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
221 vcard4:tag("adr")
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
237 vcard4:tag("impp")
238 :text_tag("uri", "xmpp:" .. tag:get_text())
239 :up();
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" })
251 :tag("info", {
252 bytes = tostring(#avatar_raw),
253 id = avatar_hash,
254 type = avatar_type,
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)
269 if avatars then
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;
276 if #avatars > 1 then
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);
285 if ok then
286 ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, avatar_defaults);
288 if not ok then
289 return ok, err;
294 if vcard4 then
295 return pep_service:publish("urn:xmpp:vcard4", actor, "current", vcard4, node_defaults);
298 return true;
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));
308 if ok then
309 origin.send(st.reply(stanza));
310 else
311 handle_error(origin, stanza, err);
314 return true;
315 end);
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");
344 return;
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
350 vcard4 = nil;
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
354 avatars = nil;
356 if not (vcard4 or avatars) then
357 session.log("debug", "Already PEP data, not overwriting with migrated data");
358 vcards:set(username, nil);
359 return;
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");
364 else
365 session.log("info", "Failed to migrate vCard-temp to PEP: %s", err or "problem emptying 'vcard' store");
367 end);