2003-01-02 22:01:12 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : mod_vcard.erl
|
2007-12-24 13:58:05 +01:00
|
|
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
2012-04-27 11:52:05 +02:00
|
|
|
%%% Purpose : Vcard management
|
2007-12-24 13:58:05 +01:00
|
|
|
%%% Created : 2 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2017-01-02 21:41:53 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
2007-12-24 13:58:05 +01:00
|
|
|
%%%
|
|
|
|
%%% 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.
|
2009-01-12 15:44:42 +01:00
|
|
|
%%%
|
2014-02-22 11:27:40 +01:00
|
|
|
%%% 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.
|
2007-12-24 13:58:05 +01:00
|
|
|
%%%
|
2003-01-02 22:01:12 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(mod_vcard).
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2007-12-24 13:58:05 +01:00
|
|
|
-author('alexey@process-one.net').
|
2003-01-02 22:01:12 +01:00
|
|
|
|
2015-05-21 17:02:36 +02:00
|
|
|
-protocol({xep, 54, '1.2'}).
|
|
|
|
-protocol({xep, 55, '1.3'}).
|
|
|
|
|
2017-02-14 08:25:08 +01:00
|
|
|
-behaviour(gen_server).
|
2003-01-24 21:18:33 +01:00
|
|
|
-behaviour(gen_mod).
|
|
|
|
|
2017-02-14 08:25:08 +01:00
|
|
|
-export([start/2, stop/1, get_sm_features/5,
|
2016-07-18 14:01:32 +02:00
|
|
|
process_local_iq/1, process_sm_iq/1, string2lower/1,
|
2016-11-22 14:48:01 +01:00
|
|
|
remove_user/2, export/1, import_info/0, import/5, import_start/2,
|
|
|
|
depends/2, process_search/1, process_vcard/1, get_vcard/2,
|
2016-07-18 14:01:32 +02:00
|
|
|
disco_items/5, disco_features/5, disco_identity/5,
|
2017-09-17 09:26:48 +02:00
|
|
|
vcard_iq_set/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
|
2017-02-14 10:39:26 +01:00
|
|
|
-export([init/1, handle_call/3, handle_cast/2,
|
2017-02-14 08:25:08 +01:00
|
|
|
handle_info/2, terminate/2, code_change/3]).
|
2003-01-02 22:01:12 +01:00
|
|
|
|
|
|
|
-include("ejabberd.hrl").
|
2013-04-08 11:12:54 +02:00
|
|
|
-include("logger.hrl").
|
2016-07-18 14:01:32 +02:00
|
|
|
-include("xmpp.hrl").
|
2016-04-13 16:37:52 +02:00
|
|
|
-include("mod_vcard.hrl").
|
2003-01-02 22:01:12 +01:00
|
|
|
|
2004-09-10 22:57:00 +02:00
|
|
|
-define(JUD_MATCHES, 30).
|
2017-05-17 16:13:34 +02:00
|
|
|
-define(VCARD_CACHE, vcard_cache).
|
2003-10-28 21:26:43 +01:00
|
|
|
|
2016-04-13 16:37:52 +02:00
|
|
|
-callback init(binary(), gen_mod:opts()) -> any().
|
2016-07-29 16:39:13 +02:00
|
|
|
-callback stop(binary()) -> any().
|
2016-11-22 14:48:01 +01:00
|
|
|
-callback import(binary(), binary(), [binary()]) -> ok.
|
2017-05-17 16:13:34 +02:00
|
|
|
-callback get_vcard(binary(), binary()) -> {ok, [xmlel()]} | error.
|
2016-04-13 16:37:52 +02:00
|
|
|
-callback set_vcard(binary(), binary(),
|
|
|
|
xmlel(), #vcard_search{}) -> {atomic, any()}.
|
2016-07-29 16:39:13 +02:00
|
|
|
-callback search_fields(binary()) -> [{binary(), binary()}].
|
|
|
|
-callback search_reported(binary()) -> [{binary(), binary()}].
|
2016-04-13 16:37:52 +02:00
|
|
|
-callback search(binary(), [{binary(), [binary()]}], boolean(),
|
2016-07-29 16:39:13 +02:00
|
|
|
infinity | pos_integer()) -> [{binary(), binary()}].
|
2016-04-13 16:37:52 +02:00
|
|
|
-callback remove_user(binary(), binary()) -> {atomic, any()}.
|
2016-11-22 14:48:01 +01:00
|
|
|
-callback is_search_supported(binary()) -> boolean().
|
2017-05-17 16:13:34 +02:00
|
|
|
-callback use_cache(binary()) -> boolean().
|
|
|
|
-callback cache_nodes(binary()) -> [node()].
|
|
|
|
|
|
|
|
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
2016-04-13 16:37:52 +02:00
|
|
|
|
2017-08-08 16:46:26 +02:00
|
|
|
-record(state, {hosts :: [binary()], server_host :: binary()}).
|
2017-02-14 08:25:08 +01:00
|
|
|
|
|
|
|
%%====================================================================
|
|
|
|
%% gen_mod callbacks
|
|
|
|
%%====================================================================
|
2005-06-20 05:18:13 +02:00
|
|
|
start(Host, Opts) ->
|
2017-02-14 10:39:26 +01:00
|
|
|
gen_mod:start_child(?MODULE, Host, Opts).
|
2017-02-14 08:25:08 +01:00
|
|
|
|
|
|
|
stop(Host) ->
|
2017-02-14 10:39:26 +01:00
|
|
|
gen_mod:stop_child(?MODULE, Host).
|
2017-02-14 08:25:08 +01:00
|
|
|
|
|
|
|
%%====================================================================
|
|
|
|
%% gen_server callbacks
|
|
|
|
%%====================================================================
|
|
|
|
init([Host, Opts]) ->
|
|
|
|
process_flag(trap_exit, true),
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
|
|
|
Mod:init(Host, Opts),
|
2017-05-17 16:13:34 +02:00
|
|
|
init_cache(Mod, Host, Opts),
|
2013-03-14 10:33:02 +01:00
|
|
|
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
2017-05-04 11:24:47 +02:00
|
|
|
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
2013-03-14 10:33:02 +01:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
|
|
|
?NS_VCARD, ?MODULE, process_local_iq, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
|
|
|
|
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
|
|
|
get_sm_features, 50),
|
2017-09-17 09:26:48 +02:00
|
|
|
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
|
2017-08-08 16:46:26 +02:00
|
|
|
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
|
2017-04-30 18:01:47 +02:00
|
|
|
Search = gen_mod:get_opt(search, Opts, false),
|
2016-07-18 14:01:32 +02:00
|
|
|
if Search ->
|
2017-08-08 16:46:26 +02:00
|
|
|
lists:foreach(
|
|
|
|
fun(MyHost) ->
|
|
|
|
ejabberd_hooks:add(
|
|
|
|
disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
|
|
|
ejabberd_hooks:add(
|
|
|
|
disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
|
|
|
ejabberd_hooks:add(
|
|
|
|
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
|
|
|
gen_iq_handler:add_iq_handler(
|
|
|
|
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(
|
|
|
|
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(
|
|
|
|
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
|
|
|
|
process_local_iq_items, IQDisc),
|
|
|
|
gen_iq_handler:add_iq_handler(
|
|
|
|
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
|
|
|
|
process_local_iq_info, IQDisc),
|
|
|
|
case Mod:is_search_supported(Host) of
|
|
|
|
false ->
|
|
|
|
?WARNING_MSG("vcard search functionality is "
|
|
|
|
"not implemented for ~s backend",
|
|
|
|
[gen_mod:db_type(Host, Opts, ?MODULE)]);
|
|
|
|
true ->
|
|
|
|
ejabberd_router:register_route(MyHost, Host)
|
|
|
|
end
|
|
|
|
end, MyHosts);
|
2016-07-18 14:01:32 +02:00
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end,
|
2017-08-08 16:46:26 +02:00
|
|
|
{ok, #state{hosts = MyHosts, server_host = Host}}.
|
2003-01-02 22:01:12 +01:00
|
|
|
|
2017-02-14 08:25:08 +01:00
|
|
|
handle_call(_Call, _From, State) ->
|
|
|
|
{noreply, State}.
|
|
|
|
|
|
|
|
handle_cast(Cast, State) ->
|
|
|
|
?WARNING_MSG("unexpected cast: ~p", [Cast]),
|
|
|
|
{noreply, State}.
|
|
|
|
|
2017-02-16 09:00:26 +01:00
|
|
|
handle_info({route, Packet}, State) ->
|
|
|
|
case catch do_route(Packet) of
|
2017-02-14 08:25:08 +01:00
|
|
|
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
|
|
|
_ -> ok
|
|
|
|
end,
|
|
|
|
{noreply, State};
|
|
|
|
handle_info(Info, State) ->
|
|
|
|
?WARNING_MSG("unexpected info: ~p", [Info]),
|
|
|
|
{noreply, State}.
|
|
|
|
|
2017-08-08 16:46:26 +02:00
|
|
|
terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
|
2017-02-14 08:25:08 +01:00
|
|
|
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
|
|
|
|
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),
|
2017-09-17 09:26:48 +02:00
|
|
|
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
|
2016-09-13 11:30:05 +02:00
|
|
|
Mod = gen_mod:db_mod(Host, ?MODULE),
|
2016-07-29 16:39:13 +02:00
|
|
|
Mod:stop(Host),
|
2017-08-08 16:46:26 +02:00
|
|
|
lists:foreach(
|
|
|
|
fun(MyHost) ->
|
|
|
|
ejabberd_router:unregister_route(MyHost),
|
|
|
|
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
|
|
|
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
|
|
|
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO)
|
|
|
|
end, MyHosts).
|
2017-02-14 08:25:08 +01:00
|
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
|
{ok, State}.
|
2003-01-02 22:01:12 +01:00
|
|
|
|
2017-02-16 09:00:26 +01:00
|
|
|
do_route(#iq{} = IQ) ->
|
|
|
|
ejabberd_router:process_iq(IQ);
|
|
|
|
do_route(_) ->
|
2016-07-18 14:01:32 +02:00
|
|
|
ok.
|
|
|
|
|
2016-09-08 16:08:48 +02:00
|
|
|
-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]},
|
2016-08-05 07:41:08 +02:00
|
|
|
jid(), jid(), binary(), binary()) ->
|
2016-09-08 16:08:48 +02:00
|
|
|
{error, stanza_error()} | empty | {result, [binary()]}.
|
2013-03-14 10:33:02 +01:00
|
|
|
get_sm_features({error, _Error} = Acc, _From, _To,
|
|
|
|
_Node, _Lang) ->
|
2005-09-04 03:58:47 +02:00
|
|
|
Acc;
|
|
|
|
get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
|
|
|
case Node of
|
2016-08-05 07:41:08 +02:00
|
|
|
<<"">> ->
|
2013-03-14 10:33:02 +01:00
|
|
|
case Acc of
|
|
|
|
{result, Features} ->
|
|
|
|
{result, [?NS_DISCO_INFO, ?NS_VCARD | Features]};
|
|
|
|
empty -> {result, [?NS_DISCO_INFO, ?NS_VCARD]}
|
|
|
|
end;
|
|
|
|
_ -> Acc
|
2003-01-02 22:01:12 +01:00
|
|
|
end.
|
|
|
|
|
2016-07-18 14:01:32 +02:00
|
|
|
-spec process_local_iq(iq()) -> iq().
|
|
|
|
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
|
|
|
|
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
|
|
|
process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
|
|
|
|
Desc = translate:translate(Lang, <<"Erlang Jabber Server">>),
|
|
|
|
xmpp:make_iq_result(
|
|
|
|
IQ, #vcard_temp{fn = <<"ejabberd">>,
|
|
|
|
url = ?EJABBERD_URI,
|
2016-11-23 13:51:48 +01:00
|
|
|
desc = <<Desc/binary, $\n, ?COPYRIGHT>>,
|
2016-07-18 14:01:32 +02:00
|
|
|
bday = <<"2002-11-16">>}).
|
|
|
|
|
|
|
|
-spec process_sm_iq(iq()) -> iq().
|
2017-09-17 09:26:48 +02:00
|
|
|
process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) ->
|
|
|
|
#jid{lserver = LServer} = From,
|
2016-07-18 14:01:32 +02:00
|
|
|
case lists:member(LServer, ?MYHOSTS) of
|
|
|
|
true ->
|
2017-09-17 09:26:48 +02:00
|
|
|
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;
|
2016-07-18 14:01:32 +02:00
|
|
|
false ->
|
|
|
|
Txt = <<"The query is only allowed from local users">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
|
|
|
|
end;
|
|
|
|
process_sm_iq(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
|
|
|
|
#jid{luser = LUser, lserver = LServer} = To,
|
|
|
|
case get_vcard(LUser, LServer) of
|
|
|
|
error ->
|
|
|
|
Txt = <<"Database failure">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
|
|
|
|
[] ->
|
|
|
|
xmpp:make_iq_result(IQ, #vcard_temp{});
|
|
|
|
Els ->
|
|
|
|
IQ#iq{type = result, to = From, from = To, sub_els = Els}
|
2012-04-27 11:52:05 +02:00
|
|
|
end.
|
|
|
|
|
2016-07-18 14:01:32 +02:00
|
|
|
-spec process_vcard(iq()) -> iq().
|
|
|
|
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
|
|
|
|
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
|
|
|
process_vcard(#iq{type = get, lang = Lang} = IQ) ->
|
|
|
|
Desc = translate:translate(Lang, <<"ejabberd vCard module">>),
|
|
|
|
xmpp:make_iq_result(
|
|
|
|
IQ, #vcard_temp{fn = <<"ejabberd/mod_vcard">>,
|
|
|
|
url = ?EJABBERD_URI,
|
2016-11-23 13:51:48 +01:00
|
|
|
desc = <<Desc/binary, $\n, ?COPYRIGHT>>}).
|
2016-07-18 14:01:32 +02:00
|
|
|
|
|
|
|
-spec process_search(iq()) -> iq().
|
|
|
|
process_search(#iq{type = get, to = To, lang = Lang} = IQ) ->
|
2016-07-29 16:39:13 +02:00
|
|
|
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
|
|
|
|
xmpp:make_iq_result(IQ, mk_search_form(To, ServerHost, Lang));
|
2016-07-18 14:01:32 +02:00
|
|
|
process_search(#iq{type = set, to = To, lang = Lang,
|
|
|
|
sub_els = [#search{xdata = #xdata{type = submit,
|
|
|
|
fields = Fs}}]} = IQ) ->
|
|
|
|
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
|
|
|
|
ResultXData = search_result(Lang, To, ServerHost, Fs),
|
|
|
|
xmpp:make_iq_result(IQ, #search{xdata = ResultXData});
|
|
|
|
process_search(#iq{type = set, lang = Lang} = IQ) ->
|
|
|
|
Txt = <<"Incorrect data form">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
|
|
|
|
|
2016-09-08 16:08:48 +02:00
|
|
|
-spec disco_items({error, stanza_error()} | {result, [disco_item()]} | empty,
|
2016-08-05 07:41:08 +02:00
|
|
|
jid(), jid(), binary(), binary()) ->
|
2016-09-08 16:08:48 +02:00
|
|
|
{error, stanza_error()} | {result, [disco_item()]}.
|
2016-08-05 07:41:08 +02:00
|
|
|
disco_items(empty, _From, _To, <<"">>, _Lang) ->
|
2016-07-18 14:01:32 +02:00
|
|
|
{result, []};
|
|
|
|
disco_items(empty, _From, _To, _Node, Lang) ->
|
|
|
|
{error, xmpp:err_item_not_found(<<"No services available">>, Lang)};
|
|
|
|
disco_items(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-09-08 16:08:48 +02:00
|
|
|
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
|
2016-08-05 07:41:08 +02:00
|
|
|
jid(), jid(), binary(), binary()) ->
|
2016-09-08 16:08:48 +02:00
|
|
|
{error, stanza_error()} | {result, [binary()]}.
|
2016-07-18 14:01:32 +02:00
|
|
|
disco_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc;
|
2016-08-05 07:41:08 +02:00
|
|
|
disco_features(Acc, _From, _To, <<"">>, _Lang) ->
|
2016-07-18 14:01:32 +02:00
|
|
|
Features = case Acc of
|
|
|
|
{result, Fs} -> Fs;
|
|
|
|
empty -> []
|
|
|
|
end,
|
|
|
|
{result, [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
|
|
|
?NS_VCARD, ?NS_SEARCH | Features]};
|
|
|
|
disco_features(empty, _From, _To, _Node, Lang) ->
|
|
|
|
Txt = <<"No features available">>,
|
|
|
|
{error, xmpp:err_item_not_found(Txt, Lang)};
|
|
|
|
disco_features(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-08-05 07:41:08 +02:00
|
|
|
-spec disco_identity([identity()], jid(), jid(),
|
|
|
|
binary(), binary()) -> [identity()].
|
|
|
|
disco_identity(Acc, _From, _To, <<"">>, Lang) ->
|
2016-07-18 14:01:32 +02:00
|
|
|
[#identity{category = <<"directory">>,
|
|
|
|
type = <<"user">>,
|
|
|
|
name = translate:translate(Lang, <<"vCard User Search">>)}|Acc];
|
|
|
|
disco_identity(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
|
|
|
-spec get_vcard(binary(), binary()) -> [xmlel()] | error.
|
2012-04-27 11:52:05 +02:00
|
|
|
get_vcard(LUser, LServer) ->
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2017-05-17 16:13:34 +02:00
|
|
|
Result = case use_cache(Mod, LServer) of
|
|
|
|
true ->
|
|
|
|
ets_cache:lookup(
|
|
|
|
?VCARD_CACHE, {LUser, LServer},
|
|
|
|
fun() -> Mod:get_vcard(LUser, LServer) end);
|
|
|
|
false ->
|
|
|
|
Mod:get_vcard(LUser, LServer)
|
|
|
|
end,
|
|
|
|
case Result of
|
|
|
|
{ok, Els} -> Els;
|
|
|
|
error -> error
|
|
|
|
end.
|
2003-01-02 22:01:12 +01:00
|
|
|
|
2016-07-18 14:01:32 +02:00
|
|
|
-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}.
|
2016-04-13 16:37:52 +02:00
|
|
|
make_vcard_search(User, LUser, LServer, VCARD) ->
|
2016-02-03 19:03:17 +01:00
|
|
|
FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
|
|
|
|
Family = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
Given = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
Middle = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
Nickname = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"NICKNAME">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
BDay = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"BDAY">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
CTRY = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
Locality = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
|
|
|
|
cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
EMail1 = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
EMail2 = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"EMAIL">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
OrgName = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
|
2016-02-03 19:03:17 +01:00
|
|
|
OrgUnit = fxml:get_path_s(VCARD,
|
2013-03-14 10:33:02 +01:00
|
|
|
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
|
2004-03-08 21:20:15 +01:00
|
|
|
EMail = case EMail1 of
|
2013-03-14 10:33:02 +01:00
|
|
|
<<"">> -> EMail2;
|
|
|
|
_ -> EMail1
|
2004-03-08 21:20:15 +01:00
|
|
|
end,
|
2013-03-14 10:33:02 +01:00
|
|
|
LFN = string2lower(FN),
|
|
|
|
LFamily = string2lower(Family),
|
|
|
|
LGiven = string2lower(Given),
|
|
|
|
LMiddle = string2lower(Middle),
|
2012-02-14 13:18:18 +01:00
|
|
|
LNickname = string2lower(Nickname),
|
2013-03-14 10:33:02 +01:00
|
|
|
LBDay = string2lower(BDay),
|
|
|
|
LCTRY = string2lower(CTRY),
|
2012-02-14 13:18:18 +01:00
|
|
|
LLocality = string2lower(Locality),
|
2013-03-14 10:33:02 +01:00
|
|
|
LEMail = string2lower(EMail),
|
|
|
|
LOrgName = string2lower(OrgName),
|
|
|
|
LOrgUnit = string2lower(OrgUnit),
|
2016-04-13 16:37:52 +02:00
|
|
|
US = {LUser, LServer},
|
|
|
|
#vcard_search{us = US,
|
|
|
|
user = {User, LServer},
|
|
|
|
luser = LUser, fn = FN,
|
|
|
|
lfn = LFN,
|
|
|
|
family = Family,
|
|
|
|
lfamily = LFamily,
|
|
|
|
given = Given,
|
|
|
|
lgiven = LGiven,
|
|
|
|
middle = Middle,
|
|
|
|
lmiddle = LMiddle,
|
|
|
|
nickname = Nickname,
|
|
|
|
lnickname = LNickname,
|
|
|
|
bday = BDay,
|
|
|
|
lbday = LBDay,
|
|
|
|
ctry = CTRY,
|
|
|
|
lctry = LCTRY,
|
|
|
|
locality = Locality,
|
|
|
|
llocality = LLocality,
|
|
|
|
email = EMail,
|
|
|
|
lemail = LEMail,
|
|
|
|
orgname = OrgName,
|
|
|
|
lorgname = LOrgName,
|
|
|
|
orgunit = OrgUnit,
|
|
|
|
lorgunit = LOrgUnit}.
|
|
|
|
|
2017-09-17 09:26:48 +02:00
|
|
|
-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.
|
2016-04-13 16:37:52 +02:00
|
|
|
set_vcard(User, LServer, VCARD) ->
|
|
|
|
case jid:nodeprep(User) of
|
|
|
|
error ->
|
|
|
|
{error, badarg};
|
|
|
|
LUser ->
|
2017-09-17 09:26:48 +02:00
|
|
|
VCardEl = xmpp:encode(VCARD),
|
|
|
|
VCardSearch = make_vcard_search(User, LUser, LServer, VCardEl),
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2017-09-17 09:26:48 +02:00
|
|
|
Mod:set_vcard(LUser, LServer, VCardEl, VCardSearch),
|
2017-05-17 16:13:34 +02:00
|
|
|
ets_cache:delete(?VCARD_CACHE, {LUser, LServer},
|
|
|
|
cache_nodes(Mod, LServer)),
|
2017-09-17 09:26:48 +02:00
|
|
|
ok
|
2003-10-20 20:23:30 +02:00
|
|
|
end.
|
2003-01-02 22:01:12 +01:00
|
|
|
|
2016-07-18 14:01:32 +02:00
|
|
|
-spec string2lower(binary()) -> binary().
|
2012-02-14 13:18:18 +01:00
|
|
|
string2lower(String) ->
|
|
|
|
case stringprep:tolower(String) of
|
2013-03-14 10:33:02 +01:00
|
|
|
Lower when is_binary(Lower) -> Lower;
|
|
|
|
error -> str:to_lower(String)
|
2012-02-14 13:18:18 +01:00
|
|
|
end.
|
|
|
|
|
2016-08-05 07:41:08 +02:00
|
|
|
-spec mk_tfield(binary(), binary(), binary()) -> xdata_field().
|
2016-07-18 14:01:32 +02:00
|
|
|
mk_tfield(Label, Var, Lang) ->
|
|
|
|
#xdata_field{type = 'text-single',
|
|
|
|
label = translate:translate(Lang, Label),
|
|
|
|
var = Var}.
|
|
|
|
|
|
|
|
-spec mk_field(binary(), binary()) -> xdata_field().
|
|
|
|
mk_field(Var, Val) ->
|
|
|
|
#xdata_field{var = Var, values = [Val]}.
|
|
|
|
|
2016-08-05 07:41:08 +02:00
|
|
|
-spec mk_search_form(jid(), binary(), binary()) -> search().
|
2016-07-29 16:39:13 +02:00
|
|
|
mk_search_form(JID, ServerHost, Lang) ->
|
2016-07-18 14:01:32 +02:00
|
|
|
Title = <<(translate:translate(Lang, <<"Search users in ">>))/binary,
|
2017-02-26 08:07:12 +01:00
|
|
|
(jid:encode(JID))/binary>>,
|
2016-07-29 16:39:13 +02:00
|
|
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
|
|
|
SearchFields = Mod:search_fields(ServerHost),
|
|
|
|
Fs = [mk_tfield(Label, Var, Lang) || {Label, Var} <- SearchFields],
|
2016-07-18 14:01:32 +02:00
|
|
|
X = #xdata{type = form,
|
|
|
|
title = Title,
|
|
|
|
instructions =
|
|
|
|
[translate:translate(
|
|
|
|
Lang,
|
|
|
|
<<"Fill in the form to search for any matching "
|
|
|
|
"Jabber User (Add * to the end of field "
|
|
|
|
"to match substring)">>)],
|
|
|
|
fields = Fs},
|
|
|
|
#search{instructions =
|
|
|
|
translate:translate(
|
|
|
|
Lang, <<"You need an x:data capable client to search">>),
|
|
|
|
xdata = X}.
|
|
|
|
|
2016-08-05 07:41:08 +02:00
|
|
|
-spec search_result(binary(), jid(), binary(), [xdata_field()]) -> xdata().
|
2016-07-18 14:01:32 +02:00
|
|
|
search_result(Lang, JID, ServerHost, XFields) ->
|
2016-07-29 16:39:13 +02:00
|
|
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
|
|
|
Reported = [mk_tfield(Label, Var, Lang) ||
|
|
|
|
{Label, Var} <- Mod:search_reported(ServerHost)],
|
2016-07-18 14:01:32 +02:00
|
|
|
#xdata{type = result,
|
|
|
|
title = <<(translate:translate(Lang,
|
|
|
|
<<"Search Results for ">>))/binary,
|
2017-02-26 08:07:12 +01:00
|
|
|
(jid:encode(JID))/binary>>,
|
2016-07-29 16:39:13 +02:00
|
|
|
reported = Reported,
|
|
|
|
items = lists:map(fun (Item) -> item_to_field(Item) end,
|
2016-07-18 14:01:32 +02:00
|
|
|
search(ServerHost, XFields))}.
|
|
|
|
|
2016-07-29 16:39:13 +02:00
|
|
|
-spec item_to_field([{binary(), binary()}]) -> [xdata_field()].
|
|
|
|
item_to_field(Items) ->
|
|
|
|
[mk_field(Var, Value) || {Var, Value} <- Items].
|
2016-07-18 14:01:32 +02:00
|
|
|
|
|
|
|
-spec search(binary(), [xdata_field()]) -> [binary()].
|
|
|
|
search(LServer, XFields) ->
|
|
|
|
Data = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- XFields],
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2013-03-14 10:33:02 +01:00
|
|
|
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
|
|
|
|
false),
|
2017-04-30 18:01:47 +02:00
|
|
|
MaxMatch = gen_mod:get_module_opt(LServer, ?MODULE, matches, ?JUD_MATCHES),
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod:search(LServer, Data, AllowReturnAll, MaxMatch).
|
2005-04-17 20:08:34 +02:00
|
|
|
|
2003-01-09 20:59:16 +01:00
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
2017-05-17 16:13:34 +02:00
|
|
|
-spec remove_user(binary(), binary()) -> ok.
|
2005-04-17 20:08:34 +02:00
|
|
|
remove_user(User, Server) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LUser = jid:nodeprep(User),
|
|
|
|
LServer = jid:nameprep(Server),
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2017-05-17 16:13:34 +02:00
|
|
|
Mod:remove_user(LUser, LServer),
|
|
|
|
ets_cache:delete(?VCARD_CACHE, {LUser, LServer}, cache_nodes(Mod, LServer)).
|
|
|
|
|
|
|
|
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
|
|
|
init_cache(Mod, Host, Opts) ->
|
|
|
|
case use_cache(Mod, Host) of
|
|
|
|
true ->
|
|
|
|
CacheOpts = cache_opts(Host, Opts),
|
|
|
|
ets_cache:new(?VCARD_CACHE, CacheOpts);
|
|
|
|
false ->
|
|
|
|
ets_cache:delete(?VCARD_CACHE)
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
|
|
|
|
cache_opts(Host, Opts) ->
|
|
|
|
MaxSize = gen_mod:get_opt(
|
|
|
|
cache_size, Opts,
|
|
|
|
ejabberd_config:cache_size(Host)),
|
|
|
|
CacheMissed = gen_mod:get_opt(
|
|
|
|
cache_missed, Opts,
|
|
|
|
ejabberd_config:cache_missed(Host)),
|
|
|
|
LifeTime = case gen_mod:get_opt(
|
|
|
|
cache_life_time, Opts,
|
|
|
|
ejabberd_config:cache_life_time(Host)) of
|
|
|
|
infinity -> infinity;
|
|
|
|
I -> timer:seconds(I)
|
|
|
|
end,
|
|
|
|
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
|
|
|
|
|
|
|
-spec use_cache(module(), binary()) -> boolean().
|
|
|
|
use_cache(Mod, Host) ->
|
|
|
|
case erlang:function_exported(Mod, use_cache, 1) of
|
|
|
|
true -> Mod:use_cache(Host);
|
|
|
|
false ->
|
|
|
|
gen_mod:get_module_opt(
|
|
|
|
Host, ?MODULE, use_cache,
|
|
|
|
ejabberd_config:use_cache(Host))
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec cache_nodes(module(), binary()) -> [node()].
|
|
|
|
cache_nodes(Mod, Host) ->
|
|
|
|
case erlang:function_exported(Mod, cache_nodes, 1) of
|
|
|
|
true -> Mod:cache_nodes(Host);
|
|
|
|
false -> ejabberd_cluster:get_nodes()
|
|
|
|
end.
|
2005-04-17 20:08:34 +02:00
|
|
|
|
2016-11-22 14:48:01 +01:00
|
|
|
import_info() ->
|
|
|
|
[{<<"vcard">>, 3}, {<<"vcard_search">>, 24}].
|
2013-07-21 12:24:36 +02:00
|
|
|
|
2016-11-22 14:48:01 +01:00
|
|
|
import_start(LServer, DBType) ->
|
|
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
|
|
|
Mod:init(LServer, []).
|
2013-07-21 12:24:36 +02:00
|
|
|
|
2016-11-22 14:48:01 +01:00
|
|
|
import(LServer, {sql, _}, DBType, Tab, L) ->
|
2016-04-13 16:37:52 +02:00
|
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
2016-11-22 14:48:01 +01:00
|
|
|
Mod:import(LServer, Tab, L).
|
|
|
|
|
|
|
|
export(LServer) ->
|
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:export(LServer).
|
2015-06-01 14:38:27 +02:00
|
|
|
|
2016-07-06 13:58:48 +02:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[].
|
|
|
|
|
2015-06-01 14:38:27 +02:00
|
|
|
mod_opt_type(allow_return_all) ->
|
|
|
|
fun (B) when is_boolean(B) -> B end;
|
2016-04-27 16:10:50 +02:00
|
|
|
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
2015-06-01 14:38:27 +02:00
|
|
|
mod_opt_type(host) -> fun iolist_to_binary/1;
|
2017-08-08 16:46:26 +02:00
|
|
|
mod_opt_type(hosts) ->
|
|
|
|
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
2015-06-01 14:38:27 +02:00
|
|
|
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
|
|
|
mod_opt_type(matches) ->
|
|
|
|
fun (infinity) -> infinity;
|
|
|
|
(I) when is_integer(I), I > 0 -> I
|
|
|
|
end;
|
|
|
|
mod_opt_type(search) ->
|
|
|
|
fun (B) when is_boolean(B) -> B end;
|
|
|
|
mod_opt_type(search_all_hosts) ->
|
|
|
|
fun (B) when is_boolean(B) -> B end;
|
2017-05-17 16:13:34 +02:00
|
|
|
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
|
|
|
fun (I) when is_integer(I), I > 0 -> I;
|
|
|
|
(infinity) -> infinity
|
|
|
|
end;
|
|
|
|
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
|
|
|
fun (B) when is_boolean(B) -> B end;
|
2015-06-01 14:38:27 +02:00
|
|
|
mod_opt_type(_) ->
|
2017-08-08 16:46:26 +02:00
|
|
|
[allow_return_all, db_type, host, hosts, iqdisc, matches,
|
2017-05-17 16:13:34 +02:00
|
|
|
search, search_all_hosts, cache_life_time, cache_size,
|
|
|
|
use_cache, cache_missed].
|