diff --git a/doc/guide.tex b/doc/guide.tex index 4ae4ba033..6cf369842 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -105,6 +105,7 @@ \newcommand{\modvcardldap}{\module{mod\_vcard\_ldap}} \newcommand{\modvcardodbc}{\module{mod\_vcard\_odbc}} \newcommand{\modvcardxupdate}{\module{mod\_vcard\_xupdate}} +\newcommand{\modvcardxupdateodbc}{\module{mod\_vcard\_xupdate\_odbc}} \newcommand{\modversion}{\module{mod\_version}} %% Contributed modules @@ -2622,6 +2623,7 @@ The following table lists all modules included in \ejabberd{}. \hline \ahrefloc{modvcardldap}{\modvcardldap{}} & vcard-temp (\xepref{0054}) & LDAP server \\ \hline \ahrefloc{modvcard}{\modvcardodbc{}} & vcard-temp (\xepref{0054}) & supported DB (*) \\ \hline \ahrefloc{modvcardxupdate}{\modvcardxupdate{}} & vCard-Based Avatars (\xepref{0153}) & \modvcard{} or \modvcardodbc{} \\ + \hline \ahrefloc{modvcardxupdate}{\modvcardxupdateodbc{}} & vCard-Based Avatars (\xepref{0153}) & \modvcard{} or \modvcardodbc{} \\ \hline \ahrefloc{modversion}{\modversion{}} & Software Version (\xepref{0092}) & \\ \hline \end{tabular} @@ -2653,6 +2655,7 @@ database for the following data: \term{mod\_offline}. \item Rosters: Use \term{mod\_roster\_odbc} instead of \term{mod\_roster}. \item Users' VCARD: Use \term{mod\_vcard\_odbc} instead of \term{mod\_vcard}. +\item vCard-Based Avatars: Use \term{mod\_vcard\_xupdate\_odbc} instead of \term{mod\_vcard\_xupdate}. \item Private XML storage: Use \term{mod\_private\_odbc} instead of \term{mod\_private}. \item User rules for blocking communications: Use \term{mod\_privacy\_odbc} instead of \term{mod\_privacy}. \item Pub-Sub nodes, items and subscriptions: Use \term{mod\_pubsub\_odbc} instead of \term{mod\_pubsub}. diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl index 4f09cd7b2..d9ed22248 100644 --- a/src/ejd2odbc.erl +++ b/src/ejd2odbc.erl @@ -34,6 +34,7 @@ export_last/2, export_vcard/2, export_vcard_search/2, + export_vcard_xupdate/2, export_private_storage/2, export_privacy/2, export_motd/2, @@ -49,6 +50,7 @@ -record(offline_msg, {us, timestamp, expire, from, to, packet}). -record(last_activity, {us, timestamp, status}). -record(vcard, {us, vcard}). +-record(vcard_xupdate, {us, hash}). -record(vcard_search, {us, user, luser, fn, lfn, @@ -260,6 +262,20 @@ export_vcard_search(Server, Output) -> [] end). +export_vcard_xupdate(Server, Output) -> + export_common( + Server, vcard_xupdate, Output, + fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + SHash = ejabberd_odbc:escape(Hash), + ["delete from vcard_xupdate where username='", Username, "';" + "insert into vcard_xupdate(username, hash) " + "values ('", Username, "', '", SHash, "');"]; + (_Host, _R) -> + [] + end). + export_private_storage(Server, Output) -> export_common( Server, private_storage, Output, diff --git a/src/mod_vcard_xupdate_odbc.erl b/src/mod_vcard_xupdate_odbc.erl new file mode 100644 index 000000000..c3d87a70b --- /dev/null +++ b/src/mod_vcard_xupdate_odbc.erl @@ -0,0 +1,157 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_vcard_xupdate_odbc.erl +%%% Author : Igor Goryachev +%%% Purpose : Add avatar hash in presence on behalf of client (XEP-0153) +%%% Created : 9 Mar 2007 by Igor Goryachev +%%%---------------------------------------------------------------------- + +-module(mod_vcard_xupdate_odbc). + +-behaviour(gen_mod). + +%% gen_mod callbacks +-export([start/2, + stop/1]). + +%% hooks +-export([update_presence/3, + vcard_set/3]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). + +-record(vcard_xupdate, {us, hash}). + +%%==================================================================== +%% gen_mod callbacks +%%==================================================================== + +start(Host, _Opts) -> + mnesia:create_table(vcard_xupdate, + [{disc_copies, [node()]}, + {attributes, record_info(fields, vcard_xupdate)}]), + ejabberd_hooks:add(c2s_update_presence, Host, + ?MODULE, update_presence, 100), + ejabberd_hooks:add(vcard_set, Host, + ?MODULE, vcard_set, 100), + ok. + +stop(Host) -> + ejabberd_hooks:delete(c2s_update_presence, Host, + ?MODULE, update_presence, 100), + ejabberd_hooks:delete(vcard_set, Host, + ?MODULE, vcard_set, 100), + ok. + +%%==================================================================== +%% Hooks +%%==================================================================== + +update_presence({xmlelement, "presence", Attrs, _Els} = Packet, User, Host) -> + case xml:get_attr_s("type", Attrs) of + [] -> + presence_with_xupdate(Packet, User, Host); + _ -> + Packet + end; +update_presence(Packet, _User, _Host) -> + Packet. + +vcard_set(LUser, LServer, VCARD) -> + US = {LUser, LServer}, + case xml:get_path_s(VCARD, [{elem, "PHOTO"}, {elem, "BINVAL"}, cdata]) of + [] -> + remove_xupdate(LUser, LServer); + BinVal -> + add_xupdate(LUser, LServer, sha:sha(jlib:decode_base64(BinVal))) + end, + ejabberd_sm:force_update_presence(US). + +%%==================================================================== +%% Mnesia storage +%%==================================================================== + +add_xupdate(LUser, LServer, Hash) -> + Username = ejabberd_odbc:escape(LUser), + SHash = ejabberd_odbc:escape(Hash), + F = fun() -> + update_t( + ["vcard_xupdate"], + ["username", "hash"], + [Username, SHash], + ["username='", Username, "'"]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +get_xupdate(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + case ejabberd_odbc:sql_query( + LServer, ["select hash from vcard_xupdate " + "where username='", Username, "';"]) of + {selected, ["hash"], [{Hash}]} -> + Hash; + _ -> + undefined + end. + +remove_xupdate(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + F = fun() -> + ejabberd_odbc:sql_query_t( + ["delete from vcard_xupdate where " + "username='", Username, "';"]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +%%%---------------------------------------------------------------------- +%%% Presence stanza rebuilding +%%%---------------------------------------------------------------------- + +presence_with_xupdate({xmlelement, "presence", Attrs, Els}, User, Host) -> + XPhotoEl = build_xphotoel(User, Host), + Els2 = presence_with_xupdate2(Els, [], XPhotoEl), + {xmlelement, "presence", Attrs, Els2}. + +presence_with_xupdate2([], Els2, XPhotoEl) -> + lists:reverse([XPhotoEl | Els2]); +%% This clause assumes that the x element contains only the XMLNS attribute: +presence_with_xupdate2([{xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], _} + | Els], Els2, XPhotoEl) -> + presence_with_xupdate2(Els, Els2, XPhotoEl); +presence_with_xupdate2([El | Els], Els2, XPhotoEl) -> + presence_with_xupdate2(Els, [El | Els2], XPhotoEl). + +build_xphotoel(User, Host) -> + Hash = get_xupdate(User, Host), + PhotoSubEls = case Hash of + Hash when is_list(Hash) -> + [{xmlcdata, Hash}]; + _ -> + [] + end, + PhotoEl = [{xmlelement, "photo", [], PhotoSubEls}], + {xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], PhotoEl}. + +%% Almost a copy of string:join/2. +%% We use this version because string:join/2 is relatively +%% new function (introduced in R12B-0). +join([], _Sep) -> + []; +join([H|T], Sep) -> + [H, [[Sep, X] || X <- T]]. + +%% Safe atomic update. +update_t(Table, Fields, Vals, Where) -> + UPairs = lists:zipwith(fun(A, B) -> A ++ "='" ++ B ++ "'" end, + Fields, Vals), + case ejabberd_odbc:sql_query_t( + ["update ", Table, " set ", + join(UPairs, ", "), + " where ", Where, ";"]) of + {updated, 1} -> + ok; + _ -> + ejabberd_odbc:sql_query_t( + ["insert into ", Table, "(", join(Fields, ", "), + ") values ('", join(Vals, "', '"), "');"]) + end. diff --git a/src/odbc/mysql.sql b/src/odbc/mysql.sql index 104b44481..ecb2b08e9 100644 --- a/src/odbc/mysql.sql +++ b/src/odbc/mysql.sql @@ -76,6 +76,11 @@ CREATE TABLE vcard ( created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ) CHARACTER SET utf8; +CREATE TABLE vcard_xupdate ( + username varchar(250) PRIMARY KEY, + hash text NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) CHARACTER SET utf8; CREATE TABLE vcard_search ( username varchar(250) NOT NULL, diff --git a/src/odbc/pg.sql b/src/odbc/pg.sql index f28cf8af2..30ea19ca5 100644 --- a/src/odbc/pg.sql +++ b/src/odbc/pg.sql @@ -74,6 +74,12 @@ CREATE TABLE vcard ( created_at TIMESTAMP NOT NULL DEFAULT now() ); +CREATE TABLE vcard_xupdate ( + username text PRIMARY KEY, + hash text NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now() +); + CREATE TABLE vcard_search ( username text NOT NULL, lusername text PRIMARY KEY,