xmpp.chapril.org-ejabberd/src/mod_mam.erl

1083 lines
35 KiB
Erlang
Raw Normal View History

2015-06-22 15:56:08 +02:00
%%%-------------------------------------------------------------------
%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% @doc
%%% Message Archive Management (XEP-0313)
%%% @end
%%% Created : 4 Jul 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
2016-01-13 12:29:14 +01:00
%%% ejabberd, Copyright (C) 2013-2016 ProcessOne
2015-06-22 15:56:08 +02: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.
%%%
2015-08-05 09:52:54 +02: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.
2015-06-22 15:56:08 +02:00
%%%
%%%-------------------------------------------------------------------
-module(mod_mam).
2016-06-22 11:24:01 +02:00
-protocol({xep, 313, '0.5.1'}).
2015-12-09 22:28:44 +01:00
-protocol({xep, 334, '0.2'}).
2015-06-24 11:58:37 +02:00
2015-06-22 15:56:08 +02:00
-behaviour(gen_mod).
%% API
-export([start/2, stop/1, depends/2]).
2015-06-22 15:56:08 +02:00
-export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
2015-06-22 15:56:08 +02:00
-include("jlib.hrl").
-include("logger.hrl").
2015-08-06 12:33:39 +02:00
-include("mod_muc_room.hrl").
-include("ejabberd_commands.hrl").
-include("mod_mam.hrl").
2015-06-22 15:56:08 +02:00
-define(DEF_PAGE_SIZE, 50).
-define(MAX_PAGE_SIZE, 250).
-callback init(binary(), gen_mod:opts()) -> any().
-callback remove_user(binary(), binary()) -> any().
-callback remove_room(binary(), binary(), binary()) -> any().
-callback delete_old_messages(binary() | global,
erlang:timestamp(),
all | chat | groupchat) -> any().
-callback extended_fields() -> [xmlel()].
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
jid(), binary(), recv | send) -> {ok, binary()} | any().
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
-callback select(binary(), jid(), jid(),
none | erlang:timestamp(),
none | erlang:timestamp(),
none | ljid() | {text, binary()},
none | #rsm_in{},
chat | groupchat) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
2015-06-22 15:56:08 +02:00
%%%===================================================================
%%% API
%%%===================================================================
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
2015-10-07 00:06:58 +02:00
one_queue),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Opts),
2015-06-22 15:56:08 +02:00
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
2015-06-22 15:56:08 +02:00
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc),
2015-06-22 15:56:08 +02:00
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
2015-06-22 15:56:08 +02:00
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc),
2015-09-22 11:18:06 +02:00
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MAM_1, ?MODULE, process_iq_v0_3, IQDisc),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
user_receive_packet, 88),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet, 88),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet_strip_tag, 500),
2015-08-06 12:33:39 +02:00
ejabberd_hooks:add(muc_filter_message, Host, ?MODULE,
muc_filter_message, 50),
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
muc_process_iq, 50),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
disco_sm_features, 50),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(remove_room, Host, ?MODULE,
remove_room, 50),
ejabberd_hooks:add(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
set_room_option, 50),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
remove_user, 50),
case gen_mod:get_opt(assume_mam_usage, Opts,
fun(B) when is_boolean(B) -> B end, false) of
true ->
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
message_is_archived, 50);
false ->
ok
end,
ejabberd_commands:register_commands(get_commands_spec()),
2015-06-22 15:56:08 +02:00
ok.
init_cache(Opts) ->
2015-06-22 15:56:08 +02:00
MaxSize = gen_mod:get_opt(cache_size, Opts,
2015-10-07 00:06:58 +02:00
fun(I) when is_integer(I), I>0 -> I end,
1000),
2015-06-22 15:56:08 +02:00
LifeTime = gen_mod:get_opt(cache_life_time, Opts,
2015-10-07 00:06:58 +02:00
fun(I) when is_integer(I), I>0 -> I end,
2015-06-22 15:56:08 +02:00
timer:hours(1) div 1000),
cache_tab:new(archive_prefs, [{max_size, MaxSize},
{life_time, LifeTime}]).
stop(Host) ->
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet, 88),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
user_receive_packet, 88),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet_strip_tag, 500),
2015-08-06 12:33:39 +02:00
ejabberd_hooks:delete(muc_filter_message, Host, ?MODULE,
muc_filter_message, 50),
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
muc_process_iq, 50),
2015-06-22 15:56:08 +02:00
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_TMP),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_TMP),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_0),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_0),
2015-09-22 11:18:06 +02:00
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MAM_1),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_1),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
disco_sm_features, 50),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(remove_room, Host, ?MODULE,
remove_room, 50),
ejabberd_hooks:delete(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
set_room_option, 50),
2015-06-22 15:56:08 +02:00
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
fun(B) when is_boolean(B) -> B end, false) of
true ->
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
message_is_archived, 50);
false ->
ok
end,
ejabberd_commands:unregister_commands(get_commands_spec()),
2015-06-22 15:56:08 +02:00
ok.
depends(_Host, _Opts) ->
[].
2015-06-22 15:56:08 +02:00
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
2015-06-22 15:56:08 +02:00
remove_room(LServer, Name, Host) ->
LName = jid:nodeprep(Name),
LHost = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_room(LServer, LName, LHost).
2015-06-22 15:56:08 +02:00
get_room_config(X, RoomState, _From, Lang) ->
Config = RoomState#state.config,
Label = <<"Enable message archiving">>,
Var = <<"muc#roomconfig_mam">>,
Val = case Config#config.mam of
true -> <<"1">>;
_ -> <<"0">>
end,
XField = #xmlel{name = <<"field">>,
attrs =
[{<<"type">>, <<"boolean">>},
{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}],
children =
[#xmlel{name = <<"value">>, attrs = [],
children = [{xmlcdata, Val}]}]},
X ++ [XField].
2016-07-10 07:45:24 +02:00
set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
try
Val = case Vals of
[<<"0">>|_] -> false;
[<<"false">>|_] -> false;
[<<"1">>|_] -> true;
[<<"true">>|_] -> true
end,
{#config.mam, Val}
catch _:{case_clause, _} ->
Txt = <<"Value of '~s' should be boolean">>,
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
{error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
end;
set_room_option(Acc, _Opt, _Vals, _Lang) ->
Acc.
user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
2015-06-22 15:56:08 +02:00
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
IsBareCopy = is_bare_copy(JID, To),
case should_archive(Pkt, LServer) of
2015-10-07 00:06:58 +02:00
true when not IsBareCopy ->
NewPkt = strip_my_archived_tag(Pkt, LServer),
2015-08-06 12:33:39 +02:00
case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
2015-10-07 00:06:58 +02:00
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
2015-10-07 00:06:58 +02:00
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
2015-10-07 00:06:58 +02:00
NewPkt#xmlel{children = NewEls};
_ ->
NewPkt
end;
_ ->
Pkt
2015-06-22 15:56:08 +02:00
end.
user_send_packet(Pkt, C2SState, JID, Peer) ->
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
case should_archive(Pkt, LServer) of
2015-08-06 12:33:39 +02:00
true ->
2015-10-07 00:06:58 +02:00
NewPkt = strip_my_archived_tag(Pkt, LServer),
case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
LUser, LServer, Peer, send) of
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
NewPkt#xmlel{children = NewEls};
_ ->
NewPkt
end;
2015-10-07 00:06:58 +02:00
false ->
Pkt
2015-06-22 15:56:08 +02:00
end.
user_send_packet_strip_tag(Pkt, _C2SState, JID, _Peer) ->
LServer = JID#jid.lserver,
strip_my_archived_tag(Pkt, LServer).
2015-08-06 12:33:39 +02:00
muc_filter_message(Pkt, #state{config = Config} = MUCState,
RoomJID, From, FromNick) ->
if Config#config.mam ->
LServer = RoomJID#jid.lserver,
NewPkt = strip_my_archived_tag(Pkt, LServer),
StorePkt = strip_x_jid_tags(NewPkt),
case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of
{ok, ID} ->
Archived = #xmlel{name = <<"archived">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_MAM_TMP},
{<<"id">>, ID}]},
StanzaID = #xmlel{name = <<"stanza-id">>,
attrs = [{<<"by">>, LServer},
{<<"xmlns">>, ?NS_SID_0},
{<<"id">>, ID}]},
NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
NewPkt#xmlel{children = NewEls};
_ ->
NewPkt
end;
2015-08-06 12:33:39 +02:00
true ->
Pkt
end.
% Query archive v0.2
process_iq_v0_2(#jid{lserver = LServer} = From,
2015-10-07 00:06:58 +02:00
#jid{lserver = LServer} = To,
#iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
Fs = parse_query_v0_2(SubEl),
2015-08-06 12:33:39 +02:00
process_iq(LServer, From, To, IQ, SubEl, Fs, chat);
process_iq_v0_2(From, To, IQ) ->
process_iq(From, To, IQ).
% Query archive v0.3
process_iq_v0_3(#jid{lserver = LServer} = From,
2015-10-07 00:06:58 +02:00
#jid{lserver = LServer} = To,
#iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
2015-08-06 12:33:39 +02:00
process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat);
process_iq_v0_3(#jid{lserver = LServer},
#jid{lserver = LServer},
#iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) ->
process_iq(LServer, IQ);
2015-08-06 12:33:39 +02:00
process_iq_v0_3(From, To, IQ) ->
process_iq(From, To, IQ).
2016-01-14 01:15:11 +01:00
muc_process_iq(#iq{type = set,
2015-08-06 12:33:39 +02:00
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
2016-02-03 19:03:17 +01:00
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl));
_ ->
2015-08-06 12:33:39 +02:00
IQ
end;
muc_process_iq(#iq{type = get,
sub_el = #xmlel{name = <<"query">>,
attrs = Attrs} = SubEl} = IQ,
MUCState, From, To) ->
2016-02-03 19:03:17 +01:00
case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_TMP ->
muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl));
NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
LServer = MUCState#state.server_host,
process_iq(LServer, IQ);
_ ->
IQ
end;
2015-08-06 12:33:39 +02:00
muc_process_iq(IQ, _MUCState, _From, _To) ->
IQ.
get_xdata_fields(SubEl) ->
2016-02-03 19:03:17 +01:00
case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
2015-10-07 00:06:58 +02:00
{#xmlel{} = XData, false} ->
jlib:parse_xdata_submit(XData);
{#xmlel{} = XData, #xmlel{}} ->
[{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)];
{false, #xmlel{}} ->
[{<<"set">>, SubEl}];
{false, false} ->
[]
2015-08-06 12:33:39 +02:00
end.
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
disco_sm_features({result, OtherFeatures},
#jid{luser = U, lserver = S},
#jid{luser = U, lserver = S}, <<>>, _Lang) ->
{result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]};
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
message_is_archived(true, _C2SState, _Peer, _JID, _Pkt) ->
true;
message_is_archived(false, C2SState, Peer,
#jid{luser = LUser, lserver = LServer}, Pkt) ->
case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage,
fun(B) when is_boolean(B) -> B end, false) of
true ->
should_archive(strip_my_archived_tag(Pkt, LServer), LServer)
andalso should_archive_peer(C2SState, get_prefs(LUser, LServer),
Peer);
false ->
false
end.
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
TypeBin == <<"groupchat">>;
TypeBin == <<"all">> ->
Diff = Days * 24 * 60 * 60 * 1000000,
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
Type = jlib:binary_to_atom(TypeBin),
DBTypes = lists:usort(
lists:map(
fun(Host) ->
case gen_mod:db_type(Host, ?MODULE) of
2016-04-20 11:27:32 +02:00
sql -> {sql, Host};
Other -> {Other, global}
end
end, ?MYHOSTS)),
Results = lists:map(
fun({DBType, ServerHost}) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:delete_old_messages(ServerHost, TimeStamp, Type)
end, DBTypes),
case lists:filter(fun(Res) -> Res /= ok end, Results) of
[] -> ok;
[NotOk|_] -> NotOk
end;
delete_old_messages(_TypeBin, _Days) ->
unsupported_type.
%%%===================================================================
%%% Internal functions
%%%===================================================================
process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
2016-02-03 19:03:17 +01:00
NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_MAM_0 ->
?NS_MAM_0;
_ ->
?NS_MAM_1
end,
CommonFields = [#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"hidden">>},
{<<"var">>, <<"FORM_TYPE">>}],
children = [#xmlel{name = <<"value">>,
children = [{xmlcdata, NS}]}]},
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"jid-single">>},
{<<"var">>, <<"with">>}]},
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"start">>}]},
#xmlel{name = <<"field">>,
attrs = [{<<"type">>, <<"text-single">>},
{<<"var">>, <<"end">>}]}],
Mod = gen_mod:db_mod(LServer, ?MODULE),
ExtendedFields = Mod:extended_fields(),
Fields = ExtendedFields ++ CommonFields,
Form = #xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
children = Fields},
IQ#iq{type = result,
sub_el = [#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, NS}],
children = [Form]}]}.
% Preference setting (both v0.2 & v0.3)
2015-06-22 15:56:08 +02:00
process_iq(#jid{luser = LUser, lserver = LServer},
2015-10-07 00:06:58 +02:00
#jid{lserver = LServer},
#iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
2016-02-03 19:03:17 +01:00
try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of
2015-10-07 00:06:58 +02:00
<<"always">> -> always;
<<"never">> -> never;
<<"roster">> -> roster
end,
lists:foldl(
fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) ->
{get_jids(Els) ++ A, N};
(#xmlel{name = <<"never">>, children = Els}, {A, N}) ->
{A, get_jids(Els) ++ N};
(_, {A, N}) ->
{A, N}
end, {[], []}, SubEl#xmlel.children)} of
{Default, {Always0, Never0}} ->
Always = lists:usort(Always0),
Never = lists:usort(Never0),
case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
2015-10-07 00:06:58 +02:00
ok ->
NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns),
IQ#iq{type = result, sub_el = [NewPrefs]};
2015-10-07 00:06:58 +02:00
_Err ->
Txt = <<"Database failure">>,
2015-10-07 00:06:58 +02:00
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
2015-10-07 00:06:58 +02:00
end
2015-06-22 15:56:08 +02:00
catch _:_ ->
2015-10-07 00:06:58 +02:00
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
2015-06-22 15:56:08 +02:00
end;
process_iq(#jid{luser = LUser, lserver = LServer},
#jid{lserver = LServer},
#iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
Prefs = get_prefs(LUser, LServer),
PrefsEl = prefs_el(Prefs#archive_prefs.default,
Prefs#archive_prefs.always,
Prefs#archive_prefs.never,
IQ#iq.xmlns),
IQ#iq{type = result, sub_el = [PrefsEl]};
2015-06-22 15:56:08 +02:00
process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
case MsgType of
chat ->
maybe_activate_mam(LUser, LServer);
{groupchat, _Role, _MUCState} ->
ok
end,
case catch lists:foldl(
fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
{{_, _, _} = jlib:datetime_string_to_timestamp(Data),
End, With, RSM};
({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
{Start,
{_, _, _} = jlib:datetime_string_to_timestamp(Data),
With, RSM};
({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, jid:tolower(jid:from_string(Data)), RSM};
({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
{Start, End, {text, Data}, RSM};
({<<"set">>, El}, {Start, End, With, _}) ->
{Start, End, With, jlib:rsm_decode(El)};
(_, Acc) ->
Acc
end, {none, [], none, none}, Fs) of
{'EXIT', _} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
{_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
{Start, End, With, RSM} ->
2016-02-03 19:03:17 +01:00
NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
2015-08-06 12:33:39 +02:00
select_and_send(LServer, From, To, Start, End,
With, limit_max(RSM, NS), IQ, MsgType)
end.
muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) ->
case may_enter_room(From, MUCState) of
true ->
LServer = MUCState#state.server_host,
Role = mod_muc_room:get_role(From, MUCState),
process_iq(LServer, From, To, IQ, SubEl, Fs,
{groupchat, Role, MUCState});
false ->
Text = <<"Only members may query archives of this room">>,
Error = ?ERRT_FORBIDDEN(Lang, Text),
IQ#iq{type = error, sub_el = [SubEl, Error]}
end.
parse_query_v0_2(Query) ->
lists:flatmap(
fun (#xmlel{name = <<"start">>} = El) ->
2016-02-03 19:03:17 +01:00
[{<<"start">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"end">>} = El) ->
2016-02-03 19:03:17 +01:00
[{<<"end">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"with">>} = El) ->
2016-02-03 19:03:17 +01:00
[{<<"with">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"withtext">>} = El) ->
2016-02-03 19:03:17 +01:00
[{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
(#xmlel{name = <<"set">>}) ->
[{<<"set">>, Query}];
(_) ->
[]
end, Query#xmlel.children).
should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
case is_resent(Pkt, LServer) of
true ->
false;
false ->
case {check_store_hint(Pkt),
fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs)} of
{_Hint, <<"error">>} ->
false;
{store, _Type} ->
true;
{no_store, _Type} ->
false;
{none, <<"groupchat">>} ->
false;
{none, <<"headline">>} ->
false;
{none, _Type} ->
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
%% Empty body
false;
_ ->
true
end
end
2015-06-22 15:56:08 +02:00
end;
should_archive(#xmlel{}, _LServer) ->
2015-06-22 15:56:08 +02:00
false.
strip_my_archived_tag(Pkt, LServer) ->
NewEls = lists:filter(
2015-10-07 00:06:58 +02:00
fun(#xmlel{name = Tag, attrs = Attrs})
when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
case catch jid:nameprep(
2016-02-03 19:03:17 +01:00
fxml:get_attr_s(
2015-10-07 00:06:58 +02:00
<<"by">>, Attrs)) of
LServer ->
false;
_ ->
true
end;
(_) ->
true
end, Pkt#xmlel.children),
2015-06-22 15:56:08 +02:00
Pkt#xmlel{children = NewEls}.
strip_x_jid_tags(Pkt) ->
NewEls = lists:filter(
fun(#xmlel{name = <<"x">>} = XEl) ->
not lists:any(fun(ItemEl) ->
2016-02-03 19:03:17 +01:00
fxml:get_tag_attr(<<"jid">>, ItemEl)
/= false
2016-02-03 19:03:17 +01:00
end, fxml:get_subtags(XEl, <<"item">>));
(_) ->
true
end, Pkt#xmlel.children),
Pkt#xmlel{children = NewEls}.
2015-06-22 15:56:08 +02:00
should_archive_peer(C2SState,
2015-10-07 00:06:58 +02:00
#archive_prefs{default = Default,
always = Always,
never = Never},
Peer) ->
LPeer = jid:tolower(Peer),
2015-06-22 15:56:08 +02:00
case lists:member(LPeer, Always) of
2015-10-07 00:06:58 +02:00
true ->
true;
false ->
case lists:member(LPeer, Never) of
true ->
false;
false ->
case Default of
always -> true;
never -> false;
roster ->
case ejabberd_c2s:get_subscription(
LPeer, C2SState) of
both -> true;
from -> true;
to -> true;
_ -> false
end
end
end
2015-06-22 15:56:08 +02:00
end.
should_archive_muc(Pkt) ->
2016-02-03 19:03:17 +01:00
case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
<<"groupchat">> ->
case check_store_hint(Pkt) of
store ->
true;
no_store ->
false;
none ->
2016-02-03 19:03:17 +01:00
case fxml:get_subtag_cdata(Pkt, <<"body">>) of
<<>> ->
2016-02-03 19:03:17 +01:00
case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
<<>> ->
false;
_ ->
true
end;
_ ->
true
end
end;
_ ->
false
end.
2015-06-22 15:56:08 +02:00
check_store_hint(Pkt) ->
case has_store_hint(Pkt) of
true ->
store;
false ->
case has_no_store_hint(Pkt) of
true ->
no_store;
false ->
none
end
end.
has_store_hint(Message) ->
2016-02-03 19:03:17 +01:00
fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
/= false.
has_no_store_hint(Message) ->
2016-02-03 19:03:17 +01:00
fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
/= false orelse
2016-02-03 19:03:17 +01:00
fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
/= false orelse
2016-02-03 19:03:17 +01:00
fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
/= false orelse
2016-02-03 19:03:17 +01:00
fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
/= false.
is_resent(Pkt, LServer) ->
2016-02-03 19:03:17 +01:00
case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
#xmlel{attrs = Attrs} ->
2016-02-03 19:03:17 +01:00
case fxml:get_attr(<<"by">>, Attrs) of
{value, LServer} ->
true;
_ ->
false
end;
false ->
false
end.
may_enter_room(From,
#state{config = #config{members_only = false}} = MUCState) ->
mod_muc_room:get_affiliation(From, MUCState) /= outcast;
may_enter_room(From, MUCState) ->
mod_muc_room:is_occupant_or_admin(From, MUCState).
2015-08-06 12:33:39 +02:00
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
2015-06-22 15:56:08 +02:00
Prefs = get_prefs(LUser, LServer),
case should_archive_peer(C2SState, Prefs, Peer) of
2015-10-07 00:06:58 +02:00
true ->
2015-08-06 12:33:39 +02:00
US = {LUser, LServer},
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
2015-10-07 00:06:58 +02:00
false ->
pass
2015-06-22 15:56:08 +02:00
end.
2015-08-06 12:33:39 +02:00
store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
case should_archive_muc(Pkt) of
2015-08-06 12:33:39 +02:00
true ->
LServer = MUCState#state.server_host,
{U, S, _} = jid:tolower(RoomJID),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
2015-08-06 12:33:39 +02:00
false ->
pass
end.
2015-06-22 15:56:08 +02:00
write_prefs(LUser, LServer, Host, Default, Always, Never) ->
Prefs = #archive_prefs{us = {LUser, LServer},
2015-10-07 00:06:58 +02:00
default = Default,
always = Always,
never = Never},
Mod = gen_mod:db_mod(Host, ?MODULE),
2015-06-22 15:56:08 +02:00
cache_tab:dirty_insert(
archive_prefs, {LUser, LServer}, Prefs,
fun() -> Mod:write_prefs(LUser, LServer, Prefs, Host) end).
2015-06-22 15:56:08 +02:00
get_prefs(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
2015-06-22 15:56:08 +02:00
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() -> Mod:get_prefs(LUser, LServer) end),
2015-06-22 15:56:08 +02:00
case Res of
2015-10-07 00:06:58 +02:00
{ok, Prefs} ->
Prefs;
error ->
ActivateOpt = gen_mod:get_module_opt(
LServer, ?MODULE, request_activates_archiving,
fun(B) when is_boolean(B) -> B end, false),
case ActivateOpt of
true ->
#archive_prefs{us = {LUser, LServer}, default = never};
false ->
Default = gen_mod:get_module_opt(
LServer, ?MODULE, default,
fun(always) -> always;
(never) -> never;
(roster) -> roster
end, never),
#archive_prefs{us = {LUser, LServer}, default = Default}
end
2015-06-22 15:56:08 +02:00
end.
prefs_el(Default, Always, Never, NS) ->
Default1 = jlib:atom_to_binary(Default),
JFun = fun(L) ->
[#xmlel{name = <<"jid">>,
children = [{xmlcdata, jid:to_string(J)}]}
|| J <- L]
end,
Always1 = #xmlel{name = <<"always">>,
children = JFun(Always)},
Never1 = #xmlel{name = <<"never">>,
children = JFun(Never)},
#xmlel{name = <<"prefs">>,
attrs = [{<<"xmlns">>, NS},
{<<"default">>, Default1}],
children = [Always1, Never1]}.
maybe_activate_mam(LUser, LServer) ->
ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
request_activates_archiving,
fun(B) when is_boolean(B) -> B end,
false),
case ActivateOpt of
true ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Res = cache_tab:lookup(archive_prefs, {LUser, LServer},
fun() ->
Mod:get_prefs(LUser, LServer)
end),
case Res of
{ok, _Prefs} ->
ok;
error ->
Default = gen_mod:get_module_opt(LServer, ?MODULE, default,
fun(always) -> always;
(never) -> never;
(roster) -> roster
end, never),
write_prefs(LUser, LServer, LServer, Default, [], [])
end;
false ->
ok
end.
2015-08-06 12:33:39 +02:00
select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
{Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
With, RSM, MsgType),
2015-06-22 15:56:08 +02:00
SortedMsgs = lists:keysort(2, Msgs),
send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
2015-06-22 15:56:08 +02:00
select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
2015-08-06 12:33:39 +02:00
case MsgType of
chat ->
select(LServer, From, From, Start, End, With, RSM, MsgType);
{groupchat, _Role, _MUCState} ->
select(LServer, From, To, Start, End, With, RSM, MsgType)
2015-08-06 12:33:39 +02:00
end.
2015-06-22 15:56:08 +02:00
select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
history = History}} = MsgType) ->
#lqueue{len = L, queue = Q} = History,
{Msgs0, _} =
lists:mapfoldl(
fun({Nick, Pkt, _HaveSubject, UTCDateTime, _Size}, I) ->
Now = datetime_to_now(UTCDateTime, I),
TS = now_to_usec(Now),
case match_interval(Now, Start, End) and
match_rsm(Now, RSM) of
true ->
{[{jlib:integer_to_binary(TS), TS,
msg_to_el(#archive_msg{
type = groupchat,
timestamp = Now,
peer = undefined,
nick = Nick,
packet = Pkt},
MsgType, JidRequestor, JidArchive)}],
I+1};
false ->
{[], I+1}
end
end, 0, queue:to_list(Q)),
Msgs = lists:flatten(Msgs0),
case RSM of
#rsm_in{max = Max, direction = before} ->
{NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
{NewMsgs, IsComplete, L};
#rsm_in{max = Max} ->
{NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
{NewMsgs, IsComplete, L};
_ ->
{Msgs, true, L}
end;
select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
MsgType).
2015-06-22 15:56:08 +02:00
2015-08-06 12:33:39 +02:00
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
Nick),
Pkt3 = #xmlel{name = <<"forwarded">>,
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
2016-02-03 19:03:17 +01:00
children = [fxml:replace_tag_attr(
<<"xmlns">>, <<"jabber:client">>, Pkt2)]},
jlib:add_delay_info(Pkt3, LServer, TS).
2015-06-22 15:56:08 +02:00
maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
Peer, {groupchat, Role,
#state{config = #config{anonymous = Anon}}},
Nick) ->
ExposeJID = case {Peer, JidRequestor} of
{undefined, _JidRequestor} ->
false;
{{U, S, _R}, #jid{luser = U, lserver = S}} ->
true;
{_Peer, _JidRequestor} when not Anon; Role == moderator ->
true;
{_Peer, _JidRequestor} ->
false
end,
Items = case ExposeJID of
true ->
2015-08-06 12:33:39 +02:00
[#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
2015-08-06 12:33:39 +02:00
children =
[#xmlel{name = <<"item">>,
attrs = [{<<"jid">>,
jid:to_string(Peer)}]}]}];
false ->
2015-08-06 12:33:39 +02:00
[]
end,
Pkt1 = Pkt#xmlel{children = Items ++ Els},
Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
jlib:remove_attr(<<"to">>, Pkt2);
maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
Pkt.
2015-06-22 15:56:08 +02:00
is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
PrioRes = ejabberd_sm:get_user_present_resources(U, S),
MaxRes = case catch lists:max(PrioRes) of
{_Prio, Res} when is_binary(Res) ->
Res;
_ ->
undefined
end,
IsBareTo = case To of
#jid{lresource = <<"">>} ->
true;
#jid{lresource = LRes} ->
%% Unavailable resources are handled like bare JIDs.
lists:keyfind(LRes, 2, PrioRes) =:= false
end,
case {IsBareTo, R} of
{true, MaxRes} ->
?DEBUG("Recipient of message to bare JID has top priority: ~s@~s/~s",
[U, S, R]),
false;
{true, _R} ->
%% The message was sent to our bare JID, and we currently have
%% multiple resources with the same highest priority, so the session
%% manager routes the message to each of them. We store the message
%% only from the resource where R equals MaxRes.
?DEBUG("Additional recipient of message to bare JID: ~s@~s/~s",
[U, S, R]),
true;
{false, _R} ->
false
end.
send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
2016-02-03 19:03:17 +01:00
QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
2015-06-22 15:56:08 +02:00
QIDAttr = if QID /= <<>> ->
[{<<"queryid">>, QID}];
true ->
[]
end,
CompleteAttr = if NS == ?NS_MAM_TMP ->
[];
2015-09-22 11:18:06 +02:00
NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
[{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
end,
2015-06-22 15:56:08 +02:00
Els = lists:map(
fun({ID, _IDInt, El}) ->
#xmlel{name = <<"message">>,
children = [#xmlel{name = <<"result">>,
attrs = [{<<"xmlns">>, NS},
{<<"id">>, ID}|QIDAttr],
children = [El]}]}
end, Msgs),
RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
2015-09-22 11:18:06 +02:00
if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
2015-06-22 15:56:08 +02:00
lists:foreach(
fun(El) ->
ejabberd_router:route(To, From, El)
end, Els),
IQ#iq{type = result, sub_el = RSMOut};
2015-09-22 11:18:06 +02:00
NS == ?NS_MAM_0 ->
2015-06-22 15:56:08 +02:00
ejabberd_router:route(
To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})),
lists:foreach(
fun(El) ->
ejabberd_router:route(To, From, El)
end, Els),
ejabberd_router:route(
To, From, #xmlel{name = <<"message">>,
children = RSMOut}),
ignore
end.
2015-08-24 12:22:18 +02:00
make_rsm_out([], _, Count, Attrs, NS) ->
2015-06-22 15:56:08 +02:00
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
children = jlib:rsm_encode(#rsm_out{count = Count})}];
2015-08-24 12:22:18 +02:00
make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
2015-06-22 15:56:08 +02:00
{LastID, _, _} = lists:last(Msgs),
Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
true -> <<"fin">>
end,
[#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
children = jlib:rsm_encode(
#rsm_out{first = FirstID, count = Count,
last = LastID})}].
2015-06-22 15:56:08 +02:00
filter_by_max(Msgs, undefined) ->
{Msgs, true};
2015-06-22 15:56:08 +02:00
filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
{lists:sublist(Msgs, Len), length(Msgs) =< Len};
2015-06-22 15:56:08 +02:00
filter_by_max(_Msgs, _Junk) ->
{[], true}.
2015-06-22 15:56:08 +02:00
limit_max(RSM, ?NS_MAM_TMP) ->
RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
limit_max(none, _NS) ->
#rsm_in{max = ?DEF_PAGE_SIZE};
limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
RSM#rsm_in{max = ?DEF_PAGE_SIZE};
limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
RSM#rsm_in{max = ?MAX_PAGE_SIZE};
limit_max(RSM, _NS) ->
RSM.
match_interval(Now, Start, End) ->
(Now >= Start) and (Now =< End).
match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> ->
Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
Now > Now1;
match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
Now < Now1;
match_rsm(_Now, _) ->
true.
2015-06-22 15:56:08 +02:00
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
usec_to_now(Int) ->
Secs = Int div 1000000,
USec = Int rem 1000000,
MSec = Secs div 1000000,
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
datetime_to_now(DateTime, USecs) ->
Seconds = calendar:datetime_to_gregorian_seconds(DateTime) -
calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
{Seconds div 1000000, Seconds rem 1000000, USecs}.
2015-06-22 15:56:08 +02:00
get_jids(Els) ->
lists:flatmap(
fun(#xmlel{name = <<"jid">>} = El) ->
2016-02-03 19:03:17 +01:00
J = jid:from_string(fxml:get_tag_cdata(El)),
[jid:tolower(jid:remove_resource(J)),
jid:tolower(J)];
2015-10-07 00:06:58 +02:00
(_) ->
[]
2015-06-22 15:56:08 +02:00
end, Els).
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
desc = "Delete MAM messages older than DAYS",
longdesc = "Valid message TYPEs: "
"\"chat\", \"groupchat\", \"all\".",
module = ?MODULE, function = delete_old_messages,
args = [{type, binary}, {days, integer}],
result = {res, rescode}}].
mod_opt_type(assume_mam_usage) ->
fun (B) when is_boolean(B) -> B end;
2015-06-22 15:56:08 +02:00
mod_opt_type(cache_life_time) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(cache_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
2015-06-22 15:56:08 +02:00
mod_opt_type(default) ->
fun (always) -> always;
(never) -> never;
(roster) -> roster
end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(request_activates_archiving) ->
fun (B) when is_boolean(B) -> B end;
2015-06-22 15:56:08 +02:00
mod_opt_type(_) ->
[assume_mam_usage, cache_life_time, cache_size, db_type, default, iqdisc,
request_activates_archiving].