Mercurial > prosody-modules
diff mod_profile/mod_profile.lua @ 1419:877dc87539ff
mod_profile: Replacement for mod_vcard with vcard4 support and integration with mod_pep_plus
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 28 May 2014 22:02:27 +0200 |
parents | |
children | 808950ab007b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_profile/mod_profile.lua Wed May 28 22:02:27 2014 +0200 @@ -0,0 +1,151 @@ +-- mod_profile + +local st = require"util.stanza"; +local jid_split, jid_bare = import("util.jid", "split", "bare"); +local is_admin = require"core.usermanager".is_admin; +local vcard = require"util.vcard"; +local base64 = require"util.encodings".base64; +local sha1 = require"util.hashes".sha1; + +local pep_plus; +if module:get_host_type() == "local" and module:get_option_boolean("vcard_to_pep") then + pep_plus = module:depends"pep_plus"; +end + +local storage = module:open_store(); +local legacy_storage = module:open_store("vcard"); + +local function get_item(vcard, name) + local item; + for i=1, #vcard do + item=vcard[i]; + if item.name == name then + return item, i; + end + end +end + +local magic_mime = { + ["\137PNG\r\n\026\n"] = "image/png"; + ["\255\216"] = "image/jpeg"; + ["GIF87a"] = "image/gif"; + ["GIF89a"] = "image/gif"; + ["<?xml"] = "image/svg+xml"; +} +local function identify(data) + for magic, mime in pairs(magic_mime) do + if data:sub(1, #magic) == magic then + return mime; + end + end + return "application/octet-stream"; +end + +local function update_pep(username, data) + local pep = pep_plus.get_pep_service(username.."@"..module.host); + if vcard.to_vcard4 then + pep:publish("urn:xmpp:vcard4", true, "", st.stanza("item"):add_child(vcard.to_vcard4(data))); + end + + local nickname = get_item(data, "NICKNAME"); + if nickname and nickname[1] then + pep:publish("http://jabber.org/protocol/nick", true, "", st.stanza("item") + :tag("nick", { xmlns="http://jabber.org/protocol/nick" }):text(nickname[1])); + end + + local photo = get_item(data, "PHOTO"); + if photo and photo[1] then + local photo_raw = base64.decode(photo[1]); + local photo_hash = sha1(photo_raw, true); + + pep:publish("urn:xmpp:avatar:metadata", true, "", st.stanza("item") + :tag("metadata", { + xmlns="urn:xmpp:avatar:metadata", + bytes = tostring(#photo_raw), + id = photo_hash, + type = identify(photo_raw), + })); + pep:publish("urn:xmpp:avatar:data", true, photo_hash, st.stanza("item") + :tag("data", { xmlns="urn:xmpp:avatar:data" }):text(photo[1])); + end +end + +-- The "temporary" vCard XEP-0054 part +module:add_feature("vcard-temp"); + +local function handle_get(event) + local origin, stanza = event.origin, event.stanza; + local username = origin.username; + local to = stanza.attr.to; + if to then username = jid_split(to); end + local data = storage:get(username); + if not data then + data = legacy_storage:get(username); + data = data and st.deserialize(data); + end + if not data then + return origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + end + return origin.send(st.reply(stanza):add_child(vcard.to_xep54(data))); +end + +local function handle_set(event) + local origin, stanza = event.origin, event.stanza; + local data = vcard.from_xep54(stanza.tags[1]); + local username = origin.username; + local to = stanza.attr.to; + if to then + if not is_admin(jid_bare(stanza.attr.from), module.host) then + return origin.send(st.error_reply(stanza, "auth", "forbidden")); + end + username = jid_split(to); + end + local ok, err = storage:set(username, data); + if not ok then + return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err)); + end + + if pep_plus and username then + update_pep(username, data); + end + + return origin.send(st.reply(stanza)); +end + +module:hook("iq-get/bare/vcard-temp:vCard", handle_get); +module:hook("iq-get/host/vcard-temp:vCard", handle_get); + +module:hook("iq-set/bare/vcard-temp:vCard", handle_set); +module:hook("iq-set/host/vcard-temp:vCard", handle_set); + +-- The vCard4 part +if vcard.to_vcard4 then + module:add_feature("urn:ietf:params:xml:ns:vcard-4.0"); + + module:hook("iq-get/bare/urn:ietf:params:xml:ns:vcard-4.0:vcard", function(event) + local origin, stanza = event.origin, event.stanza; + local username = jid_split(stanza.attr.to) or origin.username; + local data = storage:get(username); + if not data then + return origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + end + return origin.send(st.reply(stanza):add_child(vcard.to_vcard4(data))); + end); + + if vcard.from_vcard4 then + module:hook("iq-set/self/urn:ietf:params:xml:ns:vcard-4.0:vcard", function(event) + local origin, stanza = event.origin, event.stanza; + local ok, err = storage:set(origin.username, vcard.from_vcard4(stanza.tags[1])); + if not ok then + return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err)); + end + return origin.send(st.reply(stanza)); + end); + else + module:hook("iq-set/self/urn:ietf:params:xml:ns:vcard-4.0:vcard", function(event) + local origin, stanza = event.origin, event.stanza; + return origin.send(st.error_reply(stanza, "cancel", "feature-not-implemented")); + end); + end +end +