mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
Introduce mod_avatar
The purpose of the module is to cope with legacy and modern XMPP clients posting avatars. It automatically converts vCard based avatars (XEP-0153) to PEP based avatars (XEP-0084) and vice versa. Also, the module supports convertation between avatar image formats on the fly: this is controlled by `convert` option. For example, to convert all avatars into PNG format, configure the module as: mod_avatar: convert: default: png In order to convert only `webp` format to `jpeg`, set the following: mod_avatar: convert: webp: jpeg Note: the module depends on mod_vcard, mod_vcard_xupdate and mod_pubsub. Also, ejabberd should be built with --enable-graphics option.
This commit is contained in:
parent
5414cbe821
commit
e4d21c1941
@ -236,6 +236,14 @@ AC_ARG_ENABLE(sip,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
|
||||
esac],[if test "x$sip" = "x"; then sip=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(graphics,
|
||||
[AC_HELP_STRING([--enable-graphics], [enable support for graphic images manipulation (default: yes)])],
|
||||
[case "${enableval}" in
|
||||
yes) graphics=true ;;
|
||||
no) graphics=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-graphics) ;;
|
||||
esac],[if test "x$graphics" = "x"; then graphics=true; fi])
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
vars.config
|
||||
src/ejabberd.app.src])
|
||||
@ -280,6 +288,7 @@ AC_SUBST(iconv)
|
||||
AC_SUBST(stun)
|
||||
AC_SUBST(sip)
|
||||
AC_SUBST(debug)
|
||||
AC_SUBST(graphics)
|
||||
AC_SUBST(tools)
|
||||
AC_SUBST(latest_deps)
|
||||
AC_SUBST(system_deps)
|
||||
|
@ -25,7 +25,7 @@
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.15"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.14"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "d98be4a3159"}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
|
||||
@ -44,6 +44,7 @@
|
||||
{tag, "1.0.2"}}}},
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/processone/riak-erlang-client.git",
|
||||
{tag, "2.5.3"}}}},
|
||||
{if_var_true, graphics, {eimp, ".*", {git, "https://github.com/processone/eimp.git"}}},
|
||||
%% Elixir support, needed to run tests
|
||||
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
|
||||
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
|
||||
@ -74,6 +75,7 @@
|
||||
p1_oauth2,
|
||||
epam,
|
||||
ezlib,
|
||||
eimp,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
|
||||
@ -87,6 +89,7 @@
|
||||
{if_var_true, debug, debug_info},
|
||||
{if_var_true, sip, {d, 'SIP'}},
|
||||
{if_var_true, stun, {d, 'STUN'}},
|
||||
{if_var_true, graphics, {d, 'GRAPHICS'}},
|
||||
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
|
||||
{if_var_match, db_type, mssql, {d, 'mssql'}},
|
||||
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
|
||||
@ -154,6 +157,7 @@
|
||||
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
|
||||
{if_var_true, pam, {"epam", []}},
|
||||
{if_var_true, zlib, {"ezlib", []}},
|
||||
{if_var_true, graphics, {"eimp", []}},
|
||||
{if_var_true, iconv, {"iconv", []}}]}.
|
||||
|
||||
{port_env, [{"CFLAGS", "-g -O2 -Wall"}]}.
|
||||
|
@ -146,7 +146,8 @@ start_apps() ->
|
||||
ejabberd:start_app(fast_yaml),
|
||||
ejabberd:start_app(fast_tls),
|
||||
ejabberd:start_app(xmpp),
|
||||
ejabberd:start_app(cache_tab).
|
||||
ejabberd:start_app(cache_tab),
|
||||
start_eimp().
|
||||
|
||||
setup_if_elixir_conf_used() ->
|
||||
case ejabberd_config:is_using_elixir_config() of
|
||||
@ -170,3 +171,11 @@ start_elixir_application() ->
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-ifdef(GRAPHICS).
|
||||
start_eimp() ->
|
||||
ejabberd:start_app(eimp).
|
||||
-else.
|
||||
start_eimp() ->
|
||||
ok.
|
||||
-endif.
|
||||
|
436
src/mod_avatar.erl
Normal file
436
src/mod_avatar.erl
Normal file
@ -0,0 +1,436 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 13 Sep 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_avatar).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% gen_mod API
|
||||
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]).
|
||||
%% Hooks
|
||||
-export([pubsub_publish_item/6, vcard_iq_convert/1, vcard_iq_publish/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("pubsub.hrl").
|
||||
|
||||
-type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(Host, _Opts) ->
|
||||
case have_eimp() of
|
||||
true ->
|
||||
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE,
|
||||
pubsub_publish_item, 50),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
|
||||
vcard_iq_convert, 30),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
|
||||
vcard_iq_publish, 100);
|
||||
false ->
|
||||
?CRITICAL_MSG("ejabberd is built without "
|
||||
"graphics support: reconfigure it with "
|
||||
"--enable-graphics or disable '~s'",
|
||||
[?MODULE]),
|
||||
{error, graphics_not_compiled}
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE,
|
||||
pubsub_publish_item, 50),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_convert, 30),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_publish, 100).
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_vcard, hard}, {mod_vcard_xupdate, hard}, {mod_pubsub, hard}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Hooks
|
||||
%%%===================================================================
|
||||
pubsub_publish_item(LServer, ?NS_AVATAR_METADATA,
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
#jid{luser = LUser, lserver = LServer} = Host,
|
||||
ItemId, [Payload|_]) ->
|
||||
try xmpp:decode(Payload) of
|
||||
#avatar_meta{info = []} ->
|
||||
delete_vcard_avatar(From);
|
||||
#avatar_meta{info = Info} ->
|
||||
Rules = get_converting_rules(LServer),
|
||||
case get_meta_info(Info, Rules) of
|
||||
#avatar_info{type = MimeType, id = ID, url = <<"">>} = I ->
|
||||
case get_avatar_data(Host, ID) of
|
||||
{ok, Data} ->
|
||||
Meta = #avatar_meta{info = [I]},
|
||||
Photo = #vcard_photo{type = MimeType,
|
||||
binval = Data},
|
||||
set_vcard_avatar(From, Photo,
|
||||
#{avatar_meta => {ID, Meta}});
|
||||
{error, _} ->
|
||||
ok
|
||||
end;
|
||||
#avatar_info{type = MimeType, url = URL} ->
|
||||
Photo = #vcard_photo{type = MimeType,
|
||||
extval = URL},
|
||||
set_vcard_avatar(From, Photo, #{})
|
||||
end;
|
||||
_ ->
|
||||
?WARNING_MSG("invalid avatar metadata of ~s@~s published "
|
||||
"with item id ~s",
|
||||
[LUser, LServer, ItemId])
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?WARNING_MSG("failed to decode avatar metadata of ~s@~s: ~s",
|
||||
[LUser, LServer, xmpp:format_error(Why)])
|
||||
end;
|
||||
pubsub_publish_item(_, _, _, _, _, _) ->
|
||||
ok.
|
||||
|
||||
-spec vcard_iq_convert(iq()) -> iq() | {stop, stanza_error()}.
|
||||
vcard_iq_convert(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
case convert_avatar(LUser, LServer, VCard) of
|
||||
{ok, MimeType, Data} ->
|
||||
VCard1 = VCard#vcard_temp{
|
||||
photo = #vcard_photo{type = MimeType,
|
||||
binval = Data}},
|
||||
IQ#iq{sub_els = [VCard1]};
|
||||
pass ->
|
||||
IQ;
|
||||
{error, Reason} ->
|
||||
stop_with_error(Lang, Reason)
|
||||
end;
|
||||
vcard_iq_convert(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec vcard_iq_publish(iq()) -> iq() | {stop, stanza_error()}.
|
||||
vcard_iq_publish(#iq{sub_els = [#vcard_temp{photo = undefined}]} = IQ) ->
|
||||
publish_avatar(IQ, #avatar_meta{}, <<>>, <<>>, <<>>);
|
||||
vcard_iq_publish(#iq{sub_els = [#vcard_temp{
|
||||
photo = #vcard_photo{
|
||||
type = MimeType,
|
||||
binval = Data}}]} = IQ)
|
||||
when is_binary(Data), Data /= <<>> ->
|
||||
SHA1 = str:sha(Data),
|
||||
M = get_avatar_meta(IQ),
|
||||
case M of
|
||||
{ok, SHA1, _} ->
|
||||
IQ;
|
||||
{ok, _ItemID, #avatar_meta{info = Info} = Meta} ->
|
||||
case lists:keyfind(SHA1, #avatar_info.id, Info) of
|
||||
#avatar_info{} ->
|
||||
IQ;
|
||||
false ->
|
||||
Info1 = lists:filter(
|
||||
fun(#avatar_info{url = URL}) -> URL /= <<"">> end,
|
||||
Info),
|
||||
Meta1 = Meta#avatar_meta{info = Info1},
|
||||
publish_avatar(IQ, Meta1, MimeType, Data, SHA1)
|
||||
end;
|
||||
{error, _} ->
|
||||
publish_avatar(IQ, #avatar_meta{}, MimeType, Data, SHA1)
|
||||
end;
|
||||
vcard_iq_publish(Acc) ->
|
||||
Acc.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec get_meta_info([avatar_info()], convert_rules()) -> avatar_info().
|
||||
get_meta_info(Info, Rules) ->
|
||||
case lists:foldl(
|
||||
fun(_, #avatar_info{} = Acc) ->
|
||||
Acc;
|
||||
(#avatar_info{url = URL}, Acc) when URL /= <<"">> ->
|
||||
Acc;
|
||||
(#avatar_info{} = I, _) when Rules == [] ->
|
||||
I;
|
||||
(#avatar_info{type = MimeType} = I, Acc) ->
|
||||
T = decode_mime_type(MimeType),
|
||||
case lists:keymember(T, 2, Rules) of
|
||||
true ->
|
||||
I;
|
||||
false ->
|
||||
case convert_to_type(T, Rules) of
|
||||
undefined ->
|
||||
Acc;
|
||||
_ ->
|
||||
[I|Acc]
|
||||
end
|
||||
end
|
||||
end, [], Info) of
|
||||
#avatar_info{} = I -> I;
|
||||
[] -> hd(Info);
|
||||
Is -> hd(lists:reverse(Is))
|
||||
end.
|
||||
|
||||
-spec get_avatar_data(jid(), binary()) -> {ok, binary()} |
|
||||
{error,
|
||||
notfound | invalid_data | internal_error}.
|
||||
get_avatar_data(JID, ItemID) ->
|
||||
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
case mod_pubsub:get_item(LBJID, ?NS_AVATAR_DATA, ItemID) of
|
||||
#pubsub_item{payload = [Payload|_]} ->
|
||||
try xmpp:decode(Payload) of
|
||||
#avatar_data{data = Data} ->
|
||||
{ok, Data};
|
||||
_ ->
|
||||
?WARNING_MSG("invalid avatar data detected "
|
||||
"for ~s@~s with item id ~s",
|
||||
[LUser, LServer, ItemID]),
|
||||
{error, invalid_data}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?WARNING_MSG("failed to decode avatar data for "
|
||||
"~s@~s with item id ~s: ~s",
|
||||
[LUser, LServer, ItemID,
|
||||
xmpp:format_error(Why)]),
|
||||
{error, invalid_data}
|
||||
end;
|
||||
{error, #stanza_error{reason = 'item-not-found'}} ->
|
||||
{error, notfound};
|
||||
{error, Reason} ->
|
||||
?WARNING_MSG("failed to get item for ~s@~s at node ~s "
|
||||
"with item id ~s: ~p",
|
||||
[LUser, LServer, ?NS_AVATAR_METADATA, ItemID, Reason]),
|
||||
{error, internal_error}
|
||||
end.
|
||||
|
||||
-spec get_avatar_meta(iq()) -> {ok, binary(), avatar_meta()} |
|
||||
{error,
|
||||
notfound | invalid_metadata | internal_error}.
|
||||
get_avatar_meta(#iq{meta = #{avatar_meta := {ItemID, Meta}}}) ->
|
||||
{ok, ItemID, Meta};
|
||||
get_avatar_meta(#iq{from = JID}) ->
|
||||
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
case mod_pubsub:get_items(LBJID, ?NS_AVATAR_METADATA) of
|
||||
[#pubsub_item{itemid = {ItemID, _}, payload = [Payload|_]}|_] ->
|
||||
try xmpp:decode(Payload) of
|
||||
#avatar_meta{} = Meta ->
|
||||
{ok, ItemID, Meta};
|
||||
_ ->
|
||||
?WARNING_MSG("invalid metadata payload detected "
|
||||
"for ~s@~s with item id ~s",
|
||||
[LUser, LServer, ItemID]),
|
||||
{error, invalid_metadata}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?WARNING_MSG("failed to decode metadata for "
|
||||
"~s@~s with item id ~s: ~s",
|
||||
[LUser, LServer, ItemID,
|
||||
xmpp:format_error(Why)]),
|
||||
{error, invalid_metadata}
|
||||
end;
|
||||
{error, #stanza_error{reason = 'item-not-found'}} ->
|
||||
{error, notfound};
|
||||
{error, Reason} ->
|
||||
?WARNING_MSG("failed to get items for ~s@~s at node ~s: ~p",
|
||||
[LUser, LServer, ?NS_AVATAR_METADATA, Reason]),
|
||||
{error, internal_error}
|
||||
end.
|
||||
|
||||
-spec publish_avatar(iq(), avatar_meta(), binary(), binary(), binary()) ->
|
||||
iq() | {stop, stanza_error()}.
|
||||
publish_avatar(#iq{from = JID} = IQ, Meta, <<>>, <<>>, <<>>) ->
|
||||
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
case mod_pubsub:publish_item(
|
||||
LBJID, LServer, ?NS_AVATAR_METADATA,
|
||||
JID, <<>>, [xmpp:encode(Meta)]) of
|
||||
{result, _} ->
|
||||
IQ;
|
||||
{error, StanzaErr} ->
|
||||
{stop, StanzaErr}
|
||||
end;
|
||||
publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) ->
|
||||
#avatar_meta{info = Info} = Meta,
|
||||
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
Payload = xmpp:encode(#avatar_data{data = Data}),
|
||||
case mod_pubsub:publish_item(
|
||||
LBJID, LServer, ?NS_AVATAR_DATA,
|
||||
JID, ItemID, [Payload]) of
|
||||
{result, _} ->
|
||||
I = #avatar_info{id = ItemID,
|
||||
type = MimeType,
|
||||
bytes = size(Data)},
|
||||
Meta1 = Meta#avatar_meta{info = [I|Info]},
|
||||
case mod_pubsub:publish_item(
|
||||
LBJID, LServer, ?NS_AVATAR_METADATA,
|
||||
JID, ItemID, [xmpp:encode(Meta1)]) of
|
||||
{result, _} ->
|
||||
IQ;
|
||||
{error, StanzaErr} ->
|
||||
?ERROR_MSG("Failed to publish avatar metadata for ~s: ~p",
|
||||
[jid:encode(JID), StanzaErr]),
|
||||
{stop, StanzaErr}
|
||||
end;
|
||||
{error, StanzaErr} ->
|
||||
?ERROR_MSG("Failed to publish avatar data for ~s: ~p",
|
||||
[jid:encode(JID), StanzaErr]),
|
||||
{stop, StanzaErr}
|
||||
end.
|
||||
|
||||
-spec convert_avatar(binary(), binary(), vcard_temp()) ->
|
||||
{ok, binary(), binary()} |
|
||||
{error, eimp:error_reason() | base64_error} |
|
||||
pass.
|
||||
convert_avatar(LUser, LServer, VCard) ->
|
||||
case get_converting_rules(LServer) of
|
||||
[] ->
|
||||
pass;
|
||||
Rules ->
|
||||
case VCard#vcard_temp.photo of
|
||||
#vcard_photo{binval = Data} when is_binary(Data) ->
|
||||
convert_avatar(LUser, LServer, Data, Rules);
|
||||
_ ->
|
||||
pass
|
||||
end
|
||||
end.
|
||||
|
||||
-spec convert_avatar(binary(), binary(), binary(), convert_rules()) ->
|
||||
{ok, eimp:img_type(), binary()} |
|
||||
{error, eimp:error_reason()} |
|
||||
pass.
|
||||
convert_avatar(LUser, LServer, Data, Rules) ->
|
||||
Type = get_type(Data),
|
||||
NewType = convert_to_type(Type, Rules),
|
||||
if NewType == undefined orelse Type == NewType ->
|
||||
pass;
|
||||
true ->
|
||||
?DEBUG("Converting avatar of ~s@~s: ~s -> ~s",
|
||||
[LUser, LServer, Type, NewType]),
|
||||
case eimp:convert(Data, NewType) of
|
||||
{ok, NewData} ->
|
||||
{ok, encode_mime_type(NewType), NewData};
|
||||
{error, Reason} = Err ->
|
||||
?ERROR_MSG("Failed to convert avatar of "
|
||||
"~s@~s (~s -> ~s): ~s",
|
||||
[LUser, LServer, Type, NewType,
|
||||
eimp:format_error(Reason)]),
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
-spec set_vcard_avatar(jid(), vcard_photo() | undefined, map()) -> ok.
|
||||
set_vcard_avatar(JID, VCardPhoto, Meta) ->
|
||||
case get_vcard(JID) of
|
||||
{ok, #vcard_temp{photo = VCardPhoto}} ->
|
||||
ok;
|
||||
{ok, VCard} ->
|
||||
VCard1 = VCard#vcard_temp{photo = VCardPhoto},
|
||||
IQ = #iq{from = JID, to = JID, id = randoms:get_string(),
|
||||
type = set, sub_els = [VCard1], meta = Meta},
|
||||
LServer = JID#jid.lserver,
|
||||
ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []),
|
||||
ok;
|
||||
{error, _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec delete_vcard_avatar(jid()) -> ok.
|
||||
delete_vcard_avatar(JID) ->
|
||||
set_vcard_avatar(JID, undefined, #{}).
|
||||
|
||||
-spec get_vcard(jid()) -> {ok, vcard_temp()} | {error, invalid_vcard}.
|
||||
get_vcard(#jid{luser = LUser, lserver = LServer}) ->
|
||||
VCardEl = case mod_vcard:get_vcard(LUser, LServer) of
|
||||
[El] -> El;
|
||||
_ -> #vcard_temp{}
|
||||
end,
|
||||
try xmpp:decode(VCardEl, ?NS_VCARD, []) of
|
||||
#vcard_temp{} = VCard ->
|
||||
{ok, VCard};
|
||||
_ ->
|
||||
?ERROR_MSG("invalid vCard of ~s@~s in the database",
|
||||
[LUser, LServer]),
|
||||
{error, invalid_vcard}
|
||||
catch _:{xmpp_codec, Why} ->
|
||||
?ERROR_MSG("failed to decode vCard of ~s@~s: ~s",
|
||||
[LUser, LServer, xmpp:format_error(Why)]),
|
||||
{error, invalid_vcard}
|
||||
end.
|
||||
|
||||
-spec stop_with_error(binary(), eimp:error_reason()) ->
|
||||
{stop, stanza_error()}.
|
||||
stop_with_error(Lang, Reason) ->
|
||||
Txt = eimp:format_error(Reason),
|
||||
{stop, xmpp:err_internal_server_error(Txt, Lang)}.
|
||||
|
||||
-spec get_converting_rules(binary()) -> convert_rules().
|
||||
get_converting_rules(LServer) ->
|
||||
gen_mod:get_module_opt(LServer, ?MODULE, convert, []).
|
||||
|
||||
-spec get_type(binary()) -> eimp:img_type() | unknown.
|
||||
get_type(Data) ->
|
||||
eimp:get_type(Data).
|
||||
|
||||
-spec convert_to_type(eimp:img_type() | unknown, convert_rules()) ->
|
||||
eimp:img_type() | undefined.
|
||||
convert_to_type(unknown, _Rules) ->
|
||||
undefined;
|
||||
convert_to_type(Type, Rules) ->
|
||||
case proplists:get_value(Type, Rules) of
|
||||
undefined ->
|
||||
proplists:get_value(default, Rules);
|
||||
T ->
|
||||
T
|
||||
end.
|
||||
|
||||
-spec decode_mime_type(binary()) -> eimp:img_type() | unknown.
|
||||
decode_mime_type(MimeType) ->
|
||||
case str:to_lower(MimeType) of
|
||||
<<"image/jpeg">> -> jpeg;
|
||||
<<"image/png">> -> png;
|
||||
<<"image/webp">> -> webp;
|
||||
_ -> unknown
|
||||
end.
|
||||
|
||||
-spec encode_mime_type(eimp:img_type()) -> binary().
|
||||
encode_mime_type(Type) ->
|
||||
<<"image/", (atom_to_binary(Type, latin1))/binary>>.
|
||||
|
||||
-ifdef(GRAPHICS).
|
||||
have_eimp() -> true.
|
||||
-else.
|
||||
have_eimp() -> false.
|
||||
-endif.
|
||||
|
||||
mod_opt_type({convert, png}) ->
|
||||
fun(jpeg) -> jpeg;
|
||||
(webp) -> webp
|
||||
end;
|
||||
mod_opt_type({convert, webp}) ->
|
||||
fun(jpeg) -> jpeg;
|
||||
(png) -> png
|
||||
end;
|
||||
mod_opt_type({convert, jpeg}) ->
|
||||
fun(png) -> png;
|
||||
(webp) -> webp
|
||||
end;
|
||||
mod_opt_type({convert, default}) ->
|
||||
fun(png) -> png;
|
||||
(webp) -> webp;
|
||||
(jpeg) -> jpeg
|
||||
end;
|
||||
mod_opt_type(_) ->
|
||||
[{convert, default},
|
||||
{convert, webp},
|
||||
{convert, png},
|
||||
{convert, jpeg}].
|
@ -1796,8 +1796,6 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
|
||||
broadcast -> Payload;
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
|
||||
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
|
||||
set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload),
|
||||
case get_option(Options, deliver_notifications) of
|
||||
true ->
|
||||
@ -1806,6 +1804,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
|
||||
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
|
@ -38,7 +38,7 @@
|
||||
remove_user/2, export/1, import_info/0, import/5, import_start/2,
|
||||
depends/2, process_search/1, process_vcard/1, get_vcard/2,
|
||||
disco_items/5, disco_features/5, disco_identity/5,
|
||||
decode_iq_subel/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
|
||||
vcard_iq_set/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
@ -95,6 +95,7 @@ init([Host, Opts]) ->
|
||||
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
|
||||
Search = gen_mod:get_opt(search, Opts, false),
|
||||
if Search ->
|
||||
@ -152,6 +153,7 @@ terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:stop(Host),
|
||||
lists:foreach(
|
||||
@ -191,14 +193,6 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
||||
_ -> Acc
|
||||
end.
|
||||
|
||||
-spec decode_iq_subel(xmpp_element() | xmlel()) -> xmpp_element() | xmlel().
|
||||
%% Tell gen_iq_handler not to decode vcard elements
|
||||
decode_iq_subel(El) ->
|
||||
case xmpp:get_ns(El) of
|
||||
?NS_VCARD -> xmpp:encode(El);
|
||||
_ -> xmpp:decode(El)
|
||||
end.
|
||||
|
||||
-spec process_local_iq(iq()) -> iq().
|
||||
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||
@ -212,13 +206,15 @@ process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||
bday = <<"2002-11-16">>}).
|
||||
|
||||
-spec process_sm_iq(iq()) -> iq().
|
||||
process_sm_iq(#iq{type = set, lang = Lang, from = From,
|
||||
sub_els = [SubEl]} = IQ) ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) ->
|
||||
#jid{lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true ->
|
||||
set_vcard(User, LServer, SubEl),
|
||||
xmpp:make_iq_result(IQ);
|
||||
case ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []) of
|
||||
drop -> ignore;
|
||||
#stanza_error{} = Err -> xmpp:make_error(IQ, Err);
|
||||
_ -> xmpp:make_iq_result(IQ)
|
||||
end;
|
||||
false ->
|
||||
Txt = <<"The query is only allowed from local users">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
|
||||
@ -380,19 +376,33 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
|
||||
orgunit = OrgUnit,
|
||||
lorgunit = LOrgUnit}.
|
||||
|
||||
-spec set_vcard(binary(), binary(), xmlel()) -> {error, badarg} | ok.
|
||||
-spec vcard_iq_set(iq()) -> iq() | {stop, stanza_error()}.
|
||||
vcard_iq_set(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
|
||||
#jid{user = User, lserver = LServer} = From,
|
||||
case set_vcard(User, LServer, VCard) of
|
||||
{error, badarg} ->
|
||||
%% Should not be here?
|
||||
Txt = <<"Nodeprep has failed">>,
|
||||
{stop, xmpp:err_internal_server_error(Txt, Lang)};
|
||||
ok ->
|
||||
IQ
|
||||
end;
|
||||
vcard_iq_set(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec set_vcard(binary(), binary(), xmlel() | vcard_temp()) -> {error, badarg} | ok.
|
||||
set_vcard(User, LServer, VCARD) ->
|
||||
case jid:nodeprep(User) of
|
||||
error ->
|
||||
{error, badarg};
|
||||
LUser ->
|
||||
VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
|
||||
VCardEl = xmpp:encode(VCARD),
|
||||
VCardSearch = make_vcard_search(User, LUser, LServer, VCardEl),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
|
||||
Mod:set_vcard(LUser, LServer, VCardEl, VCardSearch),
|
||||
ets_cache:delete(?VCARD_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer)),
|
||||
ejabberd_hooks:run(vcard_set, LServer,
|
||||
[LUser, LServer, VCARD])
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec string2lower(binary()) -> binary().
|
||||
|
@ -30,7 +30,7 @@
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, stop/1, reload/3]).
|
||||
|
||||
-export([update_presence/1, vcard_set/3, remove_user/2,
|
||||
-export([update_presence/1, vcard_set/1, remove_user/2,
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -47,15 +47,15 @@ start(Host, Opts) ->
|
||||
init_cache(Host, Opts),
|
||||
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE,
|
||||
update_presence, 100),
|
||||
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
|
||||
100),
|
||||
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_set,
|
||||
90),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(c2s_self_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:delete(vcard_set, Host, ?MODULE,
|
||||
vcard_set, 100),
|
||||
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE,
|
||||
vcard_set, 90),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50).
|
||||
|
||||
reload(Host, NewOpts, _OldOpts) ->
|
||||
@ -71,16 +71,21 @@ depends(_Host, _Opts) ->
|
||||
-> {presence(), ejabberd_c2s:state()}.
|
||||
update_presence({#presence{type = available} = Pres,
|
||||
#{jid := #jid{luser = LUser, lserver = LServer}} = State}) ->
|
||||
Hash = get_xupdate(LUser, LServer),
|
||||
Pres1 = xmpp:set_subtag(Pres, #vcard_xupdate{hash = Hash}),
|
||||
Pres1 = case get_xupdate(LUser, LServer) of
|
||||
undefined -> xmpp:remove_subtag(Pres, #vcard_xupdate{});
|
||||
XUpdate -> xmpp:set_subtag(Pres, XUpdate)
|
||||
end,
|
||||
{Pres1, State};
|
||||
update_presence(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec vcard_set(binary(), binary(), xmlel()) -> ok.
|
||||
vcard_set(LUser, LServer, _VCARD) ->
|
||||
-spec vcard_set(iq()) -> iq().
|
||||
vcard_set(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
|
||||
ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}),
|
||||
ejabberd_sm:force_update_presence({LUser, LServer}).
|
||||
ejabberd_sm:force_update_presence({LUser, LServer}),
|
||||
IQ;
|
||||
vcard_set(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
remove_user(User, Server) ->
|
||||
@ -91,7 +96,7 @@ remove_user(User, Server) ->
|
||||
%%====================================================================
|
||||
%% Storage
|
||||
%%====================================================================
|
||||
-spec get_xupdate(binary(), binary()) -> binary() | undefined.
|
||||
-spec get_xupdate(binary(), binary()) -> vcard_xupdate() | undefined.
|
||||
get_xupdate(LUser, LServer) ->
|
||||
Result = case use_cache(LServer) of
|
||||
true ->
|
||||
@ -102,11 +107,12 @@ get_xupdate(LUser, LServer) ->
|
||||
db_get_xupdate(LUser, LServer)
|
||||
end,
|
||||
case Result of
|
||||
{ok, Hash} -> Hash;
|
||||
error -> undefined
|
||||
{ok, external} -> undefined;
|
||||
{ok, Hash} -> #vcard_xupdate{hash = Hash};
|
||||
error -> #vcard_xupdate{}
|
||||
end.
|
||||
|
||||
-spec db_get_xupdate(binary(), binary()) -> {ok, binary()} | error.
|
||||
-spec db_get_xupdate(binary(), binary()) -> {ok, binary() | external} | error.
|
||||
db_get_xupdate(LUser, LServer) ->
|
||||
case mod_vcard:get_vcard(LUser, LServer) of
|
||||
[VCard] ->
|
||||
@ -147,17 +153,21 @@ use_cache(Host) ->
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host)).
|
||||
|
||||
-spec compute_hash(xmlel()) -> binary().
|
||||
-spec compute_hash(xmlel()) -> binary() | external.
|
||||
compute_hash(VCard) ->
|
||||
case fxml:get_path_s(VCard,
|
||||
[{elem, <<"PHOTO">>},
|
||||
{elem, <<"BINVAL">>},
|
||||
cdata]) of
|
||||
<<>> ->
|
||||
case fxml:get_subtag(VCard, <<"PHOTO">>) of
|
||||
false ->
|
||||
<<>>;
|
||||
BinVal ->
|
||||
try str:sha(base64:decode(BinVal))
|
||||
catch _:badarg -> <<>>
|
||||
Photo ->
|
||||
try xmpp:decode(Photo, ?NS_VCARD, []) of
|
||||
#vcard_photo{binval = <<_, _/binary>> = BinVal} ->
|
||||
str:sha(BinVal);
|
||||
#vcard_photo{extval = <<_, _/binary>>} ->
|
||||
external;
|
||||
_ ->
|
||||
<<>>
|
||||
catch _:{xmpp_codec, _} ->
|
||||
<<>>
|
||||
end
|
||||
end.
|
||||
|
||||
|
@ -42,6 +42,7 @@
|
||||
{iconv, @iconv@}.
|
||||
{stun, @stun@}.
|
||||
{sip, @sip@}.
|
||||
{graphics, @graphics@}.
|
||||
|
||||
%% Version
|
||||
{vsn, "@PACKAGE_VERSION@"}.
|
||||
|
Loading…
Reference in New Issue
Block a user