2003-01-05 21:24:59 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : mod_offline.erl
|
2007-08-07 18:43:02 +02:00
|
|
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
2012-04-27 11:52:05 +02:00
|
|
|
%%% Purpose : Store and manage offline messages.
|
2007-08-07 18:43:02 +02:00
|
|
|
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
|
2007-12-24 13:58:05 +01:00
|
|
|
%%%
|
|
|
|
%%%
|
2018-01-05 21:18:58 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2018 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-05 21:24:59 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(mod_offline).
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2007-08-07 18:43:02 +02:00
|
|
|
-author('alexey@process-one.net').
|
2015-05-21 17:02:36 +02:00
|
|
|
|
2016-02-08 21:35:46 +01:00
|
|
|
-protocol({xep, 13, '1.2'}).
|
2015-05-21 17:02:36 +02:00
|
|
|
-protocol({xep, 22, '1.4'}).
|
|
|
|
-protocol({xep, 23, '1.3'}).
|
|
|
|
-protocol({xep, 160, '1.0'}).
|
2015-12-09 22:28:44 +01:00
|
|
|
-protocol({xep, 334, '0.2'}).
|
2015-05-21 17:02:36 +02:00
|
|
|
|
2003-01-24 21:18:33 +01:00
|
|
|
-behaviour(gen_mod).
|
|
|
|
|
2005-06-20 05:18:13 +02:00
|
|
|
-export([start/2,
|
|
|
|
stop/1,
|
2017-02-22 17:46:47 +01:00
|
|
|
reload/3,
|
2017-02-23 16:55:35 +01:00
|
|
|
store_packet/1,
|
2017-05-21 22:21:13 +02:00
|
|
|
store_offline_msg/1,
|
2017-01-09 15:02:17 +01:00
|
|
|
c2s_self_presence/1,
|
2009-06-30 18:51:15 +02:00
|
|
|
get_sm_features/5,
|
2016-02-09 15:59:54 +01:00
|
|
|
get_sm_identity/5,
|
|
|
|
get_sm_items/5,
|
|
|
|
get_info/5,
|
2016-07-19 06:56:14 +02:00
|
|
|
handle_offline_query/1,
|
2012-04-27 11:52:05 +02:00
|
|
|
remove_expired_messages/1,
|
|
|
|
remove_old_messages/2,
|
2007-08-23 02:51:54 +02:00
|
|
|
remove_user/2,
|
2016-11-22 14:48:01 +01:00
|
|
|
import_info/0,
|
|
|
|
import_start/2,
|
|
|
|
import/5,
|
2015-10-07 00:06:58 +02:00
|
|
|
export/1,
|
2010-01-12 13:02:50 +01:00
|
|
|
get_queue_length/2,
|
2016-03-04 11:09:14 +01:00
|
|
|
count_offline_messages/2,
|
2015-10-07 00:06:58 +02:00
|
|
|
get_offline_els/2,
|
2016-04-15 12:44:33 +02:00
|
|
|
find_x_expire/2,
|
2016-12-11 13:03:37 +01:00
|
|
|
c2s_handle_info/2,
|
2017-01-09 15:02:17 +01:00
|
|
|
c2s_copy_session/2,
|
2007-08-23 02:51:54 +02:00
|
|
|
webadmin_page/3,
|
2008-10-12 16:16:05 +02:00
|
|
|
webadmin_user/4,
|
|
|
|
webadmin_user_parse_query/5]).
|
2003-01-05 21:24:59 +01:00
|
|
|
|
2018-01-23 08:54:52 +01:00
|
|
|
-export([mod_opt_type/1, mod_options/1, depends/2]).
|
2014-07-31 12:25:37 +02:00
|
|
|
|
2016-03-04 11:09:14 +01:00
|
|
|
-deprecated({get_queue_length,2}).
|
|
|
|
|
2013-04-08 11:12:54 +02:00
|
|
|
-include("logger.hrl").
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-include("xmpp.hrl").
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2013-04-08 11:12:54 +02:00
|
|
|
-include("ejabberd_http.hrl").
|
2013-03-14 10:33:02 +01:00
|
|
|
|
2013-04-08 11:12:54 +02:00
|
|
|
-include("ejabberd_web_admin.hrl").
|
2003-01-05 21:24:59 +01:00
|
|
|
|
2015-10-07 00:06:58 +02:00
|
|
|
-include("mod_offline.hrl").
|
2014-07-31 12:25:37 +02:00
|
|
|
|
2004-08-22 23:54:14 +02:00
|
|
|
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
2003-01-05 21:24:59 +01:00
|
|
|
|
2009-06-15 19:43:18 +02:00
|
|
|
%% default value for the maximum number of user messages
|
|
|
|
-define(MAX_USER_MESSAGES, infinity).
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-type c2s_state() :: ejabberd_c2s:state().
|
|
|
|
|
2016-04-15 12:44:33 +02:00
|
|
|
-callback init(binary(), gen_mod:opts()) -> any().
|
2016-11-22 14:48:01 +01:00
|
|
|
-callback import(#offline_msg{}) -> ok.
|
2017-05-21 22:21:13 +02:00
|
|
|
-callback store_message(#offline_msg{}) -> ok | {error, any()}.
|
2016-04-15 12:44:33 +02:00
|
|
|
-callback pop_messages(binary(), binary()) ->
|
2017-02-18 07:36:27 +01:00
|
|
|
{ok, [#offline_msg{}]} | {error, any()}.
|
2016-04-15 12:44:33 +02:00
|
|
|
-callback remove_expired_messages(binary()) -> {atomic, any()}.
|
|
|
|
-callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}.
|
2017-02-18 07:36:27 +01:00
|
|
|
-callback remove_user(binary(), binary()) -> any().
|
2016-11-18 11:38:08 +01:00
|
|
|
-callback read_message_headers(binary(), binary()) ->
|
|
|
|
[{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}].
|
2016-04-15 12:44:33 +02:00
|
|
|
-callback read_message(binary(), binary(), non_neg_integer()) ->
|
|
|
|
{ok, #offline_msg{}} | error.
|
2016-11-08 13:15:19 +01:00
|
|
|
-callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}.
|
2016-04-15 12:44:33 +02:00
|
|
|
-callback read_all_messages(binary(), binary()) -> [#offline_msg{}].
|
|
|
|
-callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
|
|
|
|
-callback count_messages(binary(), binary()) -> non_neg_integer().
|
|
|
|
|
2016-07-06 13:58:48 +02:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[].
|
2014-07-31 12:25:37 +02:00
|
|
|
|
2017-05-21 22:21:13 +02:00
|
|
|
start(Host, Opts) ->
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
|
|
|
Mod:init(Host, Opts),
|
2013-03-14 10:33:02 +01:00
|
|
|
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
|
|
|
|
store_packet, 50),
|
2017-01-09 15:02:17 +01:00
|
|
|
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
|
2005-06-20 05:18:13 +02:00
|
|
|
ejabberd_hooks:add(remove_user, Host,
|
2004-12-19 21:47:35 +01:00
|
|
|
?MODULE, remove_user, 50),
|
2009-06-30 18:51:15 +02:00
|
|
|
ejabberd_hooks:add(disco_sm_features, Host,
|
|
|
|
?MODULE, get_sm_features, 50),
|
|
|
|
ejabberd_hooks:add(disco_local_features, Host,
|
|
|
|
?MODULE, get_sm_features, 50),
|
2016-02-09 15:59:54 +01:00
|
|
|
ejabberd_hooks:add(disco_sm_identity, Host,
|
|
|
|
?MODULE, get_sm_identity, 50),
|
|
|
|
ejabberd_hooks:add(disco_sm_items, Host,
|
|
|
|
?MODULE, get_sm_items, 50),
|
|
|
|
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
|
2016-12-11 13:03:37 +01:00
|
|
|
ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE, c2s_handle_info, 50),
|
2017-01-09 15:02:17 +01:00
|
|
|
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
|
2007-08-23 02:51:54 +02:00
|
|
|
ejabberd_hooks:add(webadmin_page_host, Host,
|
|
|
|
?MODULE, webadmin_page, 50),
|
|
|
|
ejabberd_hooks:add(webadmin_user, Host,
|
|
|
|
?MODULE, webadmin_user, 50),
|
2008-10-12 16:16:05 +02:00
|
|
|
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
2015-10-07 00:06:58 +02:00
|
|
|
?MODULE, webadmin_user_parse_query, 50),
|
2016-02-09 15:59:54 +01:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
|
2018-02-11 10:54:15 +01:00
|
|
|
?MODULE, handle_offline_query).
|
2014-07-31 12:25:37 +02:00
|
|
|
|
2017-05-21 22:21:13 +02:00
|
|
|
stop(Host) ->
|
2014-07-31 12:25:37 +02:00
|
|
|
ejabberd_hooks:delete(offline_message_hook, Host,
|
|
|
|
?MODULE, store_packet, 50),
|
2017-01-09 15:02:17 +01:00
|
|
|
ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
|
2014-07-31 12:25:37 +02:00
|
|
|
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
|
|
|
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
|
|
|
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
|
2016-02-09 15:59:54 +01:00
|
|
|
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),
|
|
|
|
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50),
|
|
|
|
ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 50),
|
2016-12-11 13:03:37 +01:00
|
|
|
ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE, c2s_handle_info, 50),
|
2017-01-09 15:02:17 +01:00
|
|
|
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
|
2014-07-31 12:25:37 +02:00
|
|
|
ejabberd_hooks:delete(webadmin_page_host, Host,
|
|
|
|
?MODULE, webadmin_page, 50),
|
|
|
|
ejabberd_hooks:delete(webadmin_user, Host,
|
|
|
|
?MODULE, webadmin_user, 50),
|
|
|
|
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
|
|
|
?MODULE, webadmin_user_parse_query, 50),
|
2017-05-21 22:21:13 +02:00
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE).
|
2003-02-13 20:39:13 +01:00
|
|
|
|
2017-05-21 22:21:13 +02:00
|
|
|
reload(Host, NewOpts, OldOpts) ->
|
|
|
|
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
|
|
|
|
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
|
|
|
|
if NewMod /= OldMod ->
|
|
|
|
NewMod:init(Host, NewOpts);
|
|
|
|
true ->
|
|
|
|
ok
|
2009-06-15 19:43:18 +02:00
|
|
|
end.
|
|
|
|
|
2017-05-21 22:21:13 +02:00
|
|
|
-spec store_offline_msg(#offline_msg{}) -> ok | {error, full | any()}.
|
|
|
|
store_offline_msg(#offline_msg{us = {User, Server}} = Msg) ->
|
|
|
|
Mod = gen_mod:db_mod(Server, ?MODULE),
|
|
|
|
case get_max_user_messages(User, Server) of
|
|
|
|
infinity ->
|
|
|
|
Mod:store_message(Msg);
|
|
|
|
Limit ->
|
|
|
|
Num = count_offline_messages(User, Server),
|
|
|
|
if Num < Limit ->
|
|
|
|
Mod:store_message(Msg);
|
|
|
|
true ->
|
|
|
|
{error, full}
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
get_max_user_messages(User, Server) ->
|
2018-01-23 08:54:52 +01:00
|
|
|
Access = gen_mod:get_module_opt(Server, ?MODULE, access_max_user_messages),
|
2017-05-21 22:21:13 +02:00
|
|
|
case acl:match_rule(Server, Access, jid:make(User, Server)) of
|
2009-06-15 19:43:18 +02:00
|
|
|
Max when is_integer(Max) -> Max;
|
|
|
|
infinity -> infinity;
|
|
|
|
_ -> ?MAX_USER_MESSAGES
|
2003-02-13 20:39:13 +01:00
|
|
|
end.
|
|
|
|
|
2016-08-05 07:41:08 +02:00
|
|
|
get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
|
2009-06-30 18:51:15 +02:00
|
|
|
Feats = case Acc of
|
|
|
|
{result, I} -> I;
|
|
|
|
_ -> []
|
|
|
|
end,
|
2016-02-09 15:59:54 +01:00
|
|
|
{result, Feats ++ [?NS_FEATURE_MSGOFFLINE, ?NS_FLEX_OFFLINE]};
|
2009-06-30 18:51:15 +02:00
|
|
|
|
|
|
|
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
|
|
|
|
%% override all lesser features...
|
|
|
|
{result, []};
|
|
|
|
|
2016-02-09 15:59:54 +01:00
|
|
|
get_sm_features(_Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
|
|
|
|
?NS_FLEX_OFFLINE, _Lang) ->
|
|
|
|
{result, [?NS_FLEX_OFFLINE]};
|
|
|
|
|
2009-06-30 18:51:15 +02:00
|
|
|
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
get_sm_identity(Acc, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S},
|
2016-02-09 15:59:54 +01:00
|
|
|
?NS_FLEX_OFFLINE, _Lang) ->
|
2016-07-19 06:56:14 +02:00
|
|
|
[#identity{category = <<"automation">>,
|
|
|
|
type = <<"message-list">>}|Acc];
|
2016-02-09 15:59:54 +01:00
|
|
|
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-12-11 13:03:37 +01:00
|
|
|
get_sm_items(_Acc, #jid{luser = U, lserver = S} = JID,
|
2016-02-09 15:59:54 +01:00
|
|
|
#jid{luser = U, lserver = S},
|
|
|
|
?NS_FLEX_OFFLINE, _Lang) ->
|
2016-12-11 13:03:37 +01:00
|
|
|
ejabberd_sm:route(JID, {resend_offline, false}),
|
2016-07-19 06:56:14 +02:00
|
|
|
Mod = gen_mod:db_mod(S, ?MODULE),
|
|
|
|
Hdrs = Mod:read_message_headers(U, S),
|
|
|
|
BareJID = jid:remove_resource(JID),
|
2016-02-09 15:59:54 +01:00
|
|
|
{result, lists:map(
|
2016-11-13 08:44:53 +01:00
|
|
|
fun({Seq, From, _To, _TS, _El}) ->
|
2016-07-19 06:56:14 +02:00
|
|
|
Node = integer_to_binary(Seq),
|
|
|
|
#disco_item{jid = BareJID,
|
|
|
|
node = Node,
|
2017-02-26 08:07:12 +01:00
|
|
|
name = jid:encode(From)}
|
2016-02-09 15:59:54 +01:00
|
|
|
end, Hdrs)};
|
|
|
|
get_sm_items(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2016-08-09 09:56:32 +02:00
|
|
|
-spec get_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
|
|
|
|
([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
|
2016-12-11 13:03:37 +01:00
|
|
|
get_info(_Acc, #jid{luser = U, lserver = S} = JID,
|
2016-10-07 09:31:03 +02:00
|
|
|
#jid{luser = U, lserver = S}, ?NS_FLEX_OFFLINE, Lang) ->
|
2016-12-11 13:03:37 +01:00
|
|
|
ejabberd_sm:route(JID, {resend_offline, false}),
|
2016-07-19 06:56:14 +02:00
|
|
|
[#xdata{type = result,
|
2016-10-07 09:31:03 +02:00
|
|
|
fields = flex_offline:encode(
|
|
|
|
[{number_of_messages, count_offline_messages(U, S)}],
|
2017-03-20 07:57:11 +01:00
|
|
|
Lang)}];
|
2016-02-09 15:59:54 +01:00
|
|
|
get_info(Acc, _From, _To, _Node, _Lang) ->
|
|
|
|
Acc.
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec c2s_handle_info(c2s_state(), term()) -> c2s_state().
|
2016-12-28 07:47:11 +01:00
|
|
|
c2s_handle_info(State, {resend_offline, Flag}) ->
|
|
|
|
{stop, State#{resend_offline => Flag}};
|
|
|
|
c2s_handle_info(State, _) ->
|
|
|
|
State.
|
2016-12-11 13:03:37 +01:00
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
|
|
|
c2s_copy_session(State, #{resend_offline := Flag}) ->
|
|
|
|
State#{resend_offline => Flag};
|
|
|
|
c2s_copy_session(State, _) ->
|
|
|
|
State.
|
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec handle_offline_query(iq()) -> iq().
|
2016-11-08 13:15:19 +01:00
|
|
|
handle_offline_query(#iq{from = #jid{luser = U1, lserver = S1},
|
|
|
|
to = #jid{luser = U2, lserver = S2},
|
|
|
|
lang = Lang,
|
|
|
|
sub_els = [#offline{}]} = IQ)
|
|
|
|
when {U1, S1} /= {U2, S2} ->
|
|
|
|
Txt = <<"Query to another users is forbidden">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
|
2016-07-19 06:56:14 +02:00
|
|
|
handle_offline_query(#iq{from = #jid{luser = U, lserver = S} = From,
|
|
|
|
to = #jid{luser = U, lserver = S} = _To,
|
2016-11-08 13:15:19 +01:00
|
|
|
type = Type, lang = Lang,
|
|
|
|
sub_els = [#offline{} = Offline]} = IQ) ->
|
|
|
|
case {Type, Offline} of
|
|
|
|
{get, #offline{fetch = true, items = [], purge = false}} ->
|
|
|
|
%% TODO: report database errors
|
|
|
|
handle_offline_fetch(From),
|
|
|
|
xmpp:make_iq_result(IQ);
|
|
|
|
{get, #offline{fetch = false, items = [_|_] = Items, purge = false}} ->
|
|
|
|
case handle_offline_items_view(From, Items) of
|
|
|
|
true -> xmpp:make_iq_result(IQ);
|
|
|
|
false -> xmpp:make_error(IQ, xmpp:err_item_not_found())
|
2016-02-09 15:59:54 +01:00
|
|
|
end;
|
2016-11-08 13:15:19 +01:00
|
|
|
{set, #offline{fetch = false, items = [], purge = true}} ->
|
|
|
|
case delete_all_msgs(U, S) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
xmpp:make_iq_result(IQ);
|
|
|
|
_Err ->
|
|
|
|
Txt = <<"Database failure">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
|
|
|
end;
|
|
|
|
{set, #offline{fetch = false, items = [_|_] = Items, purge = false}} ->
|
|
|
|
case handle_offline_items_remove(From, Items) of
|
|
|
|
true -> xmpp:make_iq_result(IQ);
|
|
|
|
false -> xmpp:make_error(IQ, xmpp:err_item_not_found())
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
xmpp:make_error(IQ, xmpp:err_bad_request())
|
|
|
|
end;
|
2016-07-19 06:56:14 +02:00
|
|
|
handle_offline_query(#iq{lang = Lang} = IQ) ->
|
2016-11-08 13:15:19 +01:00
|
|
|
Txt = <<"No module is handling this query">>,
|
|
|
|
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
2016-02-09 15:59:54 +01:00
|
|
|
|
2016-11-08 13:15:19 +01:00
|
|
|
-spec handle_offline_items_view(jid(), [offline_item()]) -> boolean().
|
2016-07-19 06:56:14 +02:00
|
|
|
handle_offline_items_view(JID, Items) ->
|
2016-02-09 15:59:54 +01:00
|
|
|
{U, S, R} = jid:tolower(JID),
|
2016-11-08 13:15:19 +01:00
|
|
|
lists:foldl(
|
|
|
|
fun(#offline_item{node = Node, action = view}, Acc) ->
|
2016-02-09 15:59:54 +01:00
|
|
|
case fetch_msg_by_node(JID, Node) of
|
|
|
|
{ok, OfflineMsg} ->
|
|
|
|
case offline_msg_to_route(S, OfflineMsg) of
|
2017-02-16 09:00:26 +01:00
|
|
|
{route, El} ->
|
2016-02-09 15:59:54 +01:00
|
|
|
NewEl = set_offline_tag(El, Node),
|
|
|
|
case ejabberd_sm:get_session_pid(U, S, R) of
|
|
|
|
Pid when is_pid(Pid) ->
|
2017-02-16 09:00:26 +01:00
|
|
|
Pid ! {route, NewEl};
|
2016-02-09 15:59:54 +01:00
|
|
|
none ->
|
|
|
|
ok
|
2016-11-08 13:15:19 +01:00
|
|
|
end,
|
|
|
|
Acc or true;
|
2016-02-09 15:59:54 +01:00
|
|
|
error ->
|
2016-11-08 13:15:19 +01:00
|
|
|
Acc or false
|
2016-02-09 15:59:54 +01:00
|
|
|
end;
|
|
|
|
error ->
|
2016-11-08 13:15:19 +01:00
|
|
|
Acc or false
|
|
|
|
end
|
|
|
|
end, false, Items).
|
2016-02-09 15:59:54 +01:00
|
|
|
|
2016-11-08 13:15:19 +01:00
|
|
|
-spec handle_offline_items_remove(jid(), [offline_item()]) -> boolean().
|
2016-07-19 06:56:14 +02:00
|
|
|
handle_offline_items_remove(JID, Items) ->
|
2016-11-08 13:15:19 +01:00
|
|
|
lists:foldl(
|
|
|
|
fun(#offline_item{node = Node, action = remove}, Acc) ->
|
|
|
|
Acc or remove_msg_by_node(JID, Node)
|
|
|
|
end, false, Items).
|
2016-02-09 15:59:54 +01:00
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec set_offline_tag(message(), binary()) -> message().
|
|
|
|
set_offline_tag(Msg, Node) ->
|
|
|
|
xmpp:set_subtag(Msg, #offline{items = [#offline_item{node = Node}]}).
|
2016-02-09 15:59:54 +01:00
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec handle_offline_fetch(jid()) -> ok.
|
2016-12-11 13:03:37 +01:00
|
|
|
handle_offline_fetch(#jid{luser = U, lserver = S} = JID) ->
|
|
|
|
ejabberd_sm:route(JID, {resend_offline, false}),
|
2016-02-09 15:59:54 +01:00
|
|
|
lists:foreach(
|
2016-11-13 08:44:53 +01:00
|
|
|
fun({Node, El}) ->
|
2017-01-09 15:02:17 +01:00
|
|
|
El1 = set_offline_tag(El, Node),
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route(El1)
|
2016-12-11 13:03:37 +01:00
|
|
|
end, read_messages(U, S)).
|
2016-02-09 15:59:54 +01:00
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec fetch_msg_by_node(jid(), binary()) -> error | {ok, #offline_msg{}}.
|
2016-04-15 12:44:33 +02:00
|
|
|
fetch_msg_by_node(To, Seq) ->
|
|
|
|
case catch binary_to_integer(Seq) of
|
|
|
|
I when is_integer(I), I >= 0 ->
|
|
|
|
LUser = To#jid.luser,
|
|
|
|
LServer = To#jid.lserver,
|
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:read_message(LUser, LServer, I);
|
|
|
|
_ ->
|
|
|
|
error
|
2016-02-09 15:59:54 +01:00
|
|
|
end.
|
|
|
|
|
2016-11-08 13:15:19 +01:00
|
|
|
-spec remove_msg_by_node(jid(), binary()) -> boolean().
|
2016-04-15 12:44:33 +02:00
|
|
|
remove_msg_by_node(To, Seq) ->
|
|
|
|
case catch binary_to_integer(Seq) of
|
|
|
|
I when is_integer(I), I>= 0 ->
|
|
|
|
LUser = To#jid.luser,
|
|
|
|
LServer = To#jid.lserver,
|
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-11-08 13:15:19 +01:00
|
|
|
Mod:remove_message(LUser, LServer, I),
|
|
|
|
true;
|
2016-04-15 12:44:33 +02:00
|
|
|
_ ->
|
2016-11-08 13:15:19 +01:00
|
|
|
false
|
2016-02-09 15:59:54 +01:00
|
|
|
end.
|
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec need_to_store(binary(), message()) -> boolean().
|
|
|
|
need_to_store(_LServer, #message{type = error}) -> false;
|
2017-12-02 21:35:09 +01:00
|
|
|
need_to_store(_LServer, #message{type = groupchat}) -> false;
|
2016-11-08 13:15:19 +01:00
|
|
|
need_to_store(LServer, #message{type = Type} = Packet) ->
|
2016-07-19 06:56:14 +02:00
|
|
|
case xmpp:has_subtag(Packet, #offline{}) of
|
|
|
|
false ->
|
|
|
|
case check_store_hint(Packet) of
|
|
|
|
store ->
|
|
|
|
true;
|
|
|
|
no_store ->
|
|
|
|
false;
|
2017-12-02 21:35:09 +01:00
|
|
|
none when Type == headline ->
|
2016-11-08 13:15:19 +01:00
|
|
|
false;
|
2016-07-19 06:56:14 +02:00
|
|
|
none ->
|
|
|
|
case gen_mod:get_module_opt(
|
2018-01-23 08:54:52 +01:00
|
|
|
LServer, ?MODULE, store_empty_body) of
|
2016-08-12 21:13:10 +02:00
|
|
|
true ->
|
2016-02-09 15:59:54 +01:00
|
|
|
true;
|
2016-07-19 06:56:14 +02:00
|
|
|
false ->
|
|
|
|
Packet#message.body /= [];
|
|
|
|
unless_chat_state ->
|
2016-11-12 11:27:15 +01:00
|
|
|
not xmpp_util:is_standalone_chat_state(Packet)
|
2016-07-19 06:56:14 +02:00
|
|
|
end
|
2014-10-27 12:14:52 +01:00
|
|
|
end;
|
2016-07-19 06:56:14 +02:00
|
|
|
true ->
|
2014-10-27 12:14:52 +01:00
|
|
|
false
|
|
|
|
end.
|
|
|
|
|
2017-02-23 16:55:35 +01:00
|
|
|
-spec store_packet({any(), message()}) -> {any(), message()}.
|
|
|
|
store_packet({_Action, #message{from = From, to = To} = Packet} = Acc) ->
|
2014-10-27 12:14:52 +01:00
|
|
|
case need_to_store(To#jid.lserver, Packet) of
|
|
|
|
true ->
|
2017-02-16 09:00:26 +01:00
|
|
|
case check_event(Packet) of
|
2015-12-08 21:46:36 +01:00
|
|
|
true ->
|
|
|
|
#jid{luser = LUser, lserver = LServer} = To,
|
2016-08-15 22:30:08 +02:00
|
|
|
case ejabberd_hooks:run_fold(store_offline_message, LServer,
|
2017-02-24 19:25:25 +01:00
|
|
|
Packet, []) of
|
2016-08-15 22:30:08 +02:00
|
|
|
drop ->
|
2017-01-21 11:47:47 +01:00
|
|
|
Acc;
|
2016-08-15 22:30:08 +02:00
|
|
|
NewPacket ->
|
|
|
|
TimeStamp = p1_time_compat:timestamp(),
|
2016-11-12 11:27:15 +01:00
|
|
|
Expire = find_x_expire(TimeStamp, NewPacket),
|
2017-05-21 22:21:13 +02:00
|
|
|
OffMsg = #offline_msg{us = {LUser, LServer},
|
|
|
|
timestamp = TimeStamp,
|
|
|
|
expire = Expire,
|
|
|
|
from = From,
|
|
|
|
to = To,
|
|
|
|
packet = NewPacket},
|
|
|
|
case store_offline_msg(OffMsg) of
|
|
|
|
ok ->
|
|
|
|
{offlined, NewPacket};
|
|
|
|
{error, Reason} ->
|
|
|
|
discard_warn_sender(Packet, Reason),
|
|
|
|
stop
|
|
|
|
end
|
2016-08-15 22:30:08 +02:00
|
|
|
end;
|
2017-01-21 11:47:47 +01:00
|
|
|
_ -> Acc
|
2015-10-07 00:06:58 +02:00
|
|
|
end;
|
2017-01-21 11:47:47 +01:00
|
|
|
false -> Acc
|
2003-01-05 21:24:59 +01:00
|
|
|
end.
|
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec check_store_hint(message()) -> store | no_store | none.
|
2015-12-08 21:46:36 +01:00
|
|
|
check_store_hint(Packet) ->
|
|
|
|
case has_store_hint(Packet) of
|
|
|
|
true ->
|
|
|
|
store;
|
|
|
|
false ->
|
|
|
|
case has_no_store_hint(Packet) of
|
|
|
|
true ->
|
|
|
|
no_store;
|
|
|
|
false ->
|
|
|
|
none
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec has_store_hint(message()) -> boolean().
|
2015-12-08 21:46:36 +01:00
|
|
|
has_store_hint(Packet) ->
|
2016-07-19 06:56:14 +02:00
|
|
|
xmpp:has_subtag(Packet, #hint{type = 'store'}).
|
2015-12-08 21:46:36 +01:00
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec has_no_store_hint(message()) -> boolean().
|
2015-07-28 21:08:33 +02:00
|
|
|
has_no_store_hint(Packet) ->
|
2016-07-19 06:56:14 +02:00
|
|
|
xmpp:has_subtag(Packet, #hint{type = 'no-store'})
|
|
|
|
orelse
|
|
|
|
xmpp:has_subtag(Packet, #hint{type = 'no-storage'}).
|
2016-02-09 15:59:54 +01:00
|
|
|
|
2015-12-08 21:58:54 +01:00
|
|
|
%% Check if the packet has any content about XEP-0022
|
2017-02-16 09:00:26 +01:00
|
|
|
-spec check_event(message()) -> boolean().
|
2017-04-25 16:59:26 +02:00
|
|
|
check_event(#message{from = From, to = To, id = ID, type = Type} = Msg) ->
|
2016-07-19 06:56:14 +02:00
|
|
|
case xmpp:get_subtag(Msg, #xevent{}) of
|
|
|
|
false ->
|
|
|
|
true;
|
|
|
|
#xevent{id = undefined, offline = false} ->
|
|
|
|
true;
|
|
|
|
#xevent{id = undefined, offline = true} ->
|
2017-04-25 16:59:26 +02:00
|
|
|
NewMsg = #message{from = To, to = From, id = ID, type = Type,
|
2017-04-25 16:21:24 +02:00
|
|
|
sub_els = [#xevent{id = ID, offline = true}]},
|
2017-02-16 09:00:26 +01:00
|
|
|
ejabberd_router:route(NewMsg),
|
2016-07-19 06:56:14 +02:00
|
|
|
true;
|
|
|
|
_ ->
|
|
|
|
false
|
2003-01-05 21:24:59 +01:00
|
|
|
end.
|
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec find_x_expire(erlang:timestamp(), message()) -> erlang:timestamp() | never.
|
|
|
|
find_x_expire(TimeStamp, Msg) ->
|
2017-02-18 07:36:27 +01:00
|
|
|
case xmpp:get_subtag(Msg, #expire{seconds = 0}) of
|
2016-07-19 06:56:14 +02:00
|
|
|
#expire{seconds = Int} ->
|
|
|
|
{MegaSecs, Secs, MicroSecs} = TimeStamp,
|
|
|
|
S = MegaSecs * 1000000 + Secs + Int,
|
|
|
|
MegaSecs1 = S div 1000000,
|
|
|
|
Secs1 = S rem 1000000,
|
|
|
|
{MegaSecs1, Secs1, MicroSecs};
|
|
|
|
false ->
|
|
|
|
never
|
2004-09-10 22:57:00 +02:00
|
|
|
end.
|
|
|
|
|
2017-01-23 11:51:05 +01:00
|
|
|
c2s_self_presence({_Pres, #{resend_offline := false}} = Acc) ->
|
|
|
|
Acc;
|
2017-01-09 15:02:17 +01:00
|
|
|
c2s_self_presence({#presence{type = available} = NewPres, State} = Acc) ->
|
|
|
|
NewPrio = get_priority_from_presence(NewPres),
|
2017-01-23 14:30:16 +01:00
|
|
|
LastPrio = case maps:get(pres_last, State, error) of
|
|
|
|
error -> -1;
|
2017-01-09 15:02:17 +01:00
|
|
|
LastPres -> get_priority_from_presence(LastPres)
|
|
|
|
end,
|
|
|
|
if LastPrio < 0 andalso NewPrio >= 0 ->
|
|
|
|
route_offline_messages(State);
|
|
|
|
true ->
|
|
|
|
ok
|
|
|
|
end,
|
|
|
|
Acc;
|
|
|
|
c2s_self_presence(Acc) ->
|
|
|
|
Acc.
|
|
|
|
|
|
|
|
-spec route_offline_messages(c2s_state()) -> ok.
|
|
|
|
route_offline_messages(#{jid := #jid{luser = LUser, lserver = LServer}} = State) ->
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
case Mod:pop_messages(LUser, LServer) of
|
2017-01-09 15:02:17 +01:00
|
|
|
{ok, OffMsgs} ->
|
|
|
|
lists:foreach(
|
|
|
|
fun(OffMsg) ->
|
|
|
|
route_offline_message(State, OffMsg)
|
|
|
|
end, OffMsgs);
|
2012-11-06 16:58:08 +01:00
|
|
|
_ ->
|
2017-01-09 15:02:17 +01:00
|
|
|
ok
|
2004-01-18 21:42:09 +01:00
|
|
|
end.
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec route_offline_message(c2s_state(), #offline_msg{}) -> ok.
|
|
|
|
route_offline_message(#{lserver := LServer} = State,
|
|
|
|
#offline_msg{expire = Expire} = OffMsg) ->
|
|
|
|
case offline_msg_to_route(LServer, OffMsg) of
|
|
|
|
error ->
|
|
|
|
ok;
|
2017-02-16 09:00:26 +01:00
|
|
|
{route, Msg} ->
|
2017-01-09 15:02:17 +01:00
|
|
|
case is_message_expired(Expire, Msg) of
|
|
|
|
true ->
|
|
|
|
ok;
|
|
|
|
false ->
|
|
|
|
case privacy_check_packet(State, Msg, in) of
|
2017-02-16 09:00:26 +01:00
|
|
|
allow -> ejabberd_router:route(Msg);
|
2017-02-18 07:36:27 +01:00
|
|
|
deny -> ok
|
2017-01-09 15:02:17 +01:00
|
|
|
end
|
|
|
|
end
|
2017-01-13 12:20:25 +01:00
|
|
|
end.
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec is_message_expired(erlang:timestamp() | never, message()) -> boolean().
|
|
|
|
is_message_expired(Expire, Msg) ->
|
|
|
|
TS = p1_time_compat:timestamp(),
|
|
|
|
Expire1 = case Expire of
|
|
|
|
undefined -> find_x_expire(TS, Msg);
|
|
|
|
_ -> Expire
|
|
|
|
end,
|
|
|
|
Expire1 /= never andalso Expire1 =< TS.
|
|
|
|
|
|
|
|
-spec privacy_check_packet(c2s_state(), stanza(), in | out) -> allow | deny.
|
|
|
|
privacy_check_packet(#{lserver := LServer} = State, Pkt, Dir) ->
|
|
|
|
ejabberd_hooks:run_fold(privacy_check_packet,
|
|
|
|
LServer, allow, [State, Pkt, Dir]).
|
|
|
|
|
2012-04-27 11:52:05 +02:00
|
|
|
remove_expired_messages(Server) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LServer = jid:nameprep(Server),
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:remove_expired_messages(LServer).
|
2004-09-10 22:57:00 +02:00
|
|
|
|
2012-04-27 11:52:05 +02:00
|
|
|
remove_old_messages(Days, Server) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LServer = jid:nameprep(Server),
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:remove_old_messages(Days, LServer).
|
2003-10-24 21:21:07 +02:00
|
|
|
|
2016-08-09 09:56:32 +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-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2016-08-09 09:56:32 +02:00
|
|
|
Mod:remove_user(LUser, LServer),
|
|
|
|
ok.
|
2007-08-13 12:27:28 +02:00
|
|
|
|
|
|
|
%% Helper functions:
|
|
|
|
|
|
|
|
%% Warn senders that their messages have been discarded:
|
2017-05-21 22:21:13 +02:00
|
|
|
-spec discard_warn_sender(message(), full | any()) -> ok.
|
|
|
|
discard_warn_sender(Packet, full) ->
|
|
|
|
ErrText = <<"Your contact offline message queue is "
|
|
|
|
"full. The message has been discarded.">>,
|
|
|
|
Lang = xmpp:get_lang(Packet),
|
|
|
|
Err = xmpp:err_resource_constraint(ErrText, Lang),
|
|
|
|
ejabberd_router:route_error(Packet, Err);
|
|
|
|
discard_warn_sender(Packet, _) ->
|
|
|
|
ErrText = <<"Database failure">>,
|
|
|
|
Lang = xmpp:get_lang(Packet),
|
|
|
|
Err = xmpp:err_internal_server_error(ErrText, Lang),
|
|
|
|
ejabberd_router:route_error(Packet, Err).
|
2007-08-23 02:51:54 +02:00
|
|
|
|
|
|
|
webadmin_page(_, Host,
|
2013-03-14 10:33:02 +01:00
|
|
|
#request{us = _US, path = [<<"user">>, U, <<"queue">>],
|
|
|
|
q = Query, lang = Lang} =
|
|
|
|
_Request) ->
|
|
|
|
Res = user_queue(U, Host, Query, Lang), {stop, Res};
|
2007-08-23 02:51:54 +02:00
|
|
|
webadmin_page(Acc, _, _) -> Acc.
|
|
|
|
|
2013-06-24 12:04:56 +02:00
|
|
|
get_offline_els(LUser, LServer) ->
|
2016-11-13 08:44:53 +01:00
|
|
|
[Packet || {_Seq, Packet} <- read_messages(LUser, LServer)].
|
2013-06-24 12:04:56 +02:00
|
|
|
|
2016-11-13 08:44:53 +01:00
|
|
|
-spec offline_msg_to_route(binary(), #offline_msg{}) ->
|
2017-02-16 09:00:26 +01:00
|
|
|
{route, message()} | error.
|
2017-01-09 15:02:17 +01:00
|
|
|
offline_msg_to_route(LServer, #offline_msg{from = From, to = To} = R) ->
|
2018-02-09 16:12:50 +01:00
|
|
|
CodecOpts = ejabberd_config:codec_options(LServer),
|
|
|
|
try xmpp:decode(R#offline_msg.packet, ?NS_CLIENT, CodecOpts) of
|
2016-11-13 08:44:53 +01:00
|
|
|
Pkt ->
|
2017-01-09 15:02:17 +01:00
|
|
|
Pkt1 = xmpp:set_from_to(Pkt, From, To),
|
|
|
|
Pkt2 = add_delay_info(Pkt1, LServer, R#offline_msg.timestamp),
|
2017-02-16 09:00:26 +01:00
|
|
|
{route, Pkt2}
|
2016-11-13 08:44:53 +01:00
|
|
|
catch _:{xmpp_codec, Why} ->
|
|
|
|
?ERROR_MSG("failed to decode packet ~p of user ~s: ~s",
|
2017-02-26 08:07:12 +01:00
|
|
|
[R#offline_msg.packet, jid:encode(To),
|
2016-11-13 08:44:53 +01:00
|
|
|
xmpp:format_error(Why)]),
|
|
|
|
error
|
|
|
|
end.
|
|
|
|
|
|
|
|
-spec read_messages(binary(), binary()) -> [{binary(), message()}].
|
|
|
|
read_messages(LUser, LServer) ->
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2018-02-09 16:12:50 +01:00
|
|
|
CodecOpts = ejabberd_config:codec_options(LServer),
|
2016-11-13 08:44:53 +01:00
|
|
|
lists:flatmap(
|
|
|
|
fun({Seq, From, To, TS, El}) ->
|
2016-04-15 12:44:33 +02:00
|
|
|
Node = integer_to_binary(Seq),
|
2018-02-09 16:12:50 +01:00
|
|
|
try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of
|
2016-11-13 08:44:53 +01:00
|
|
|
Pkt ->
|
|
|
|
Node = integer_to_binary(Seq),
|
|
|
|
Pkt1 = add_delay_info(Pkt, LServer, TS),
|
|
|
|
Pkt2 = xmpp:set_from_to(Pkt1, From, To),
|
|
|
|
[{Node, Pkt2}]
|
|
|
|
catch _:{xmpp_codec, Why} ->
|
|
|
|
?ERROR_MSG("failed to decode packet ~p "
|
|
|
|
"of user ~s: ~s",
|
2017-02-26 08:07:12 +01:00
|
|
|
[El, jid:encode(To),
|
2016-11-13 08:44:53 +01:00
|
|
|
xmpp:format_error(Why)]),
|
|
|
|
[]
|
|
|
|
end
|
2016-04-15 12:44:33 +02:00
|
|
|
end, Mod:read_message_headers(LUser, LServer)).
|
2012-04-27 11:52:05 +02:00
|
|
|
|
2016-04-15 12:44:33 +02:00
|
|
|
format_user_queue(Hdrs) ->
|
|
|
|
lists:map(
|
2016-11-13 08:44:53 +01:00
|
|
|
fun({Seq, From, To, TS, El}) ->
|
2016-04-15 12:44:33 +02:00
|
|
|
ID = integer_to_binary(Seq),
|
|
|
|
FPacket = ejabberd_web_admin:pretty_print_xml(El),
|
2017-02-26 08:07:12 +01:00
|
|
|
SFrom = jid:encode(From),
|
|
|
|
STo = jid:encode(To),
|
2016-11-13 08:44:53 +01:00
|
|
|
Time = case TS of
|
|
|
|
undefined ->
|
|
|
|
Stamp = fxml:get_path_s(El, [{elem, <<"delay">>},
|
|
|
|
{attr, <<"stamp">>}]),
|
|
|
|
try xmpp_util:decode_timestamp(Stamp) of
|
|
|
|
{_, _, _} = Now -> format_time(Now)
|
|
|
|
catch _:_ ->
|
|
|
|
<<"">>
|
|
|
|
end;
|
2016-04-15 12:44:33 +02:00
|
|
|
{_, _, _} = Now ->
|
2016-11-13 08:44:53 +01:00
|
|
|
format_time(Now)
|
2016-04-15 12:44:33 +02:00
|
|
|
end,
|
|
|
|
?XE(<<"tr">>,
|
|
|
|
[?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
|
|
|
|
[?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
|
|
|
|
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
|
|
|
|
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
|
|
|
|
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
|
|
|
|
?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
|
|
|
|
[?XC(<<"pre">>, FPacket)])])
|
|
|
|
end, Hdrs).
|
2012-04-27 11:52:05 +02:00
|
|
|
|
2016-11-13 08:44:53 +01:00
|
|
|
format_time(Now) ->
|
|
|
|
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(Now),
|
|
|
|
str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
|
|
|
[Year, Month, Day, Hour, Minute, Second]).
|
|
|
|
|
2007-08-23 02:51:54 +02:00
|
|
|
user_queue(User, Server, Query, Lang) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LUser = jid:nodeprep(User),
|
|
|
|
LServer = jid:nameprep(Server),
|
2012-04-27 11:52:05 +02:00
|
|
|
US = {LUser, LServer},
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Res = user_queue_parse_query(LUser, LServer, Query),
|
|
|
|
HdrsAll = Mod:read_message_headers(LUser, LServer),
|
2017-06-30 14:17:20 +02:00
|
|
|
Hdrs = get_messages_subset(User, Server, HdrsAll),
|
2016-04-15 12:44:33 +02:00
|
|
|
FMsgs = format_user_queue(Hdrs),
|
2013-03-14 10:33:02 +01:00
|
|
|
[?XC(<<"h1">>,
|
2016-11-24 13:06:06 +01:00
|
|
|
(str:format(?T(<<"~s's Offline Messages Queue">>),
|
2013-03-14 10:33:02 +01:00
|
|
|
[us_to_list(US)])))]
|
|
|
|
++
|
|
|
|
case Res of
|
|
|
|
ok -> [?XREST(<<"Submitted">>)];
|
|
|
|
nothing -> []
|
|
|
|
end
|
|
|
|
++
|
|
|
|
[?XAE(<<"form">>,
|
|
|
|
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
|
|
|
|
[?XE(<<"table">>,
|
|
|
|
[?XE(<<"thead">>,
|
|
|
|
[?XE(<<"tr">>,
|
|
|
|
[?X(<<"td">>), ?XCT(<<"td">>, <<"Time">>),
|
|
|
|
?XCT(<<"td">>, <<"From">>),
|
|
|
|
?XCT(<<"td">>, <<"To">>),
|
|
|
|
?XCT(<<"td">>, <<"Packet">>)])]),
|
|
|
|
?XE(<<"tbody">>,
|
|
|
|
if FMsgs == [] ->
|
|
|
|
[?XE(<<"tr">>,
|
|
|
|
[?XAC(<<"td">>, [{<<"colspan">>, <<"4">>}],
|
|
|
|
<<" ">>)])];
|
|
|
|
true -> FMsgs
|
|
|
|
end)]),
|
2007-08-23 02:51:54 +02:00
|
|
|
?BR,
|
2013-03-14 10:33:02 +01:00
|
|
|
?INPUTT(<<"submit">>, <<"delete">>,
|
|
|
|
<<"Delete Selected">>)])].
|
2007-08-23 02:51:54 +02:00
|
|
|
|
2016-04-15 12:44:33 +02:00
|
|
|
user_queue_parse_query(LUser, LServer, Query) ->
|
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
2012-11-13 13:56:27 +01:00
|
|
|
case lists:keysearch(<<"delete">>, 1, Query) of
|
2016-04-15 12:44:33 +02:00
|
|
|
{value, _} ->
|
2017-08-25 12:44:53 +02:00
|
|
|
user_queue_parse_query(LUser, LServer, Query, Mod);
|
2016-04-15 12:44:33 +02:00
|
|
|
_ ->
|
|
|
|
nothing
|
2007-08-23 02:51:54 +02:00
|
|
|
end.
|
|
|
|
|
2017-08-25 12:44:53 +02:00
|
|
|
user_queue_parse_query(LUser, LServer, Query, Mod) ->
|
|
|
|
case lists:keytake(<<"selected">>, 1, Query) of
|
|
|
|
{value, {_, Seq}, Query2} ->
|
|
|
|
case catch binary_to_integer(Seq) of
|
|
|
|
I when is_integer(I), I>=0 ->
|
|
|
|
Mod:remove_message(LUser, LServer, I);
|
|
|
|
_ ->
|
|
|
|
nothing
|
|
|
|
end,
|
|
|
|
user_queue_parse_query(LUser, LServer, Query2, Mod);
|
|
|
|
false ->
|
|
|
|
nothing
|
|
|
|
end.
|
|
|
|
|
2007-08-23 02:51:54 +02:00
|
|
|
us_to_list({User, Server}) ->
|
2017-02-26 08:07:12 +01:00
|
|
|
jid:encode({User, Server, <<"">>}).
|
2007-08-23 02:51:54 +02:00
|
|
|
|
2012-04-27 11:52:05 +02:00
|
|
|
get_queue_length(LUser, LServer) ->
|
2016-02-12 14:25:09 +01:00
|
|
|
count_offline_messages(LUser, LServer).
|
2010-01-12 13:02:50 +01:00
|
|
|
|
2016-04-15 12:44:33 +02:00
|
|
|
get_messages_subset(User, Host, MsgsAll) ->
|
2017-05-21 22:21:13 +02:00
|
|
|
MaxOfflineMsgs = case get_max_user_messages(User, Host) of
|
2013-03-14 10:33:02 +01:00
|
|
|
Number when is_integer(Number) -> Number;
|
|
|
|
_ -> 100
|
2010-01-13 00:58:22 +01:00
|
|
|
end,
|
|
|
|
Length = length(MsgsAll),
|
2016-04-15 12:44:33 +02:00
|
|
|
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
|
2010-01-13 00:58:22 +01:00
|
|
|
|
2016-04-15 12:44:33 +02:00
|
|
|
get_messages_subset2(Max, Length, MsgsAll) when Length =< Max * 2 ->
|
2010-01-13 00:58:22 +01:00
|
|
|
MsgsAll;
|
2016-04-15 12:44:33 +02:00
|
|
|
get_messages_subset2(Max, Length, MsgsAll) ->
|
2010-01-13 00:58:22 +01:00
|
|
|
FirstN = Max,
|
|
|
|
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
|
2013-03-14 10:33:02 +01:00
|
|
|
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
|
|
|
|
Msgs2),
|
2017-02-25 08:01:01 +01:00
|
|
|
NoJID = jid:make(<<"...">>, <<"...">>),
|
2016-04-15 12:44:33 +02:00
|
|
|
Seq = <<"0">>,
|
2013-03-14 10:33:02 +01:00
|
|
|
IntermediateMsg = #xmlel{name = <<"...">>, attrs = [],
|
|
|
|
children = []},
|
2016-04-15 12:44:33 +02:00
|
|
|
MsgsFirstN ++ [{Seq, NoJID, NoJID, IntermediateMsg}] ++ MsgsLastN.
|
2010-01-13 00:58:22 +01:00
|
|
|
|
2007-08-23 02:51:54 +02:00
|
|
|
webadmin_user(Acc, User, Server, Lang) ->
|
2016-03-04 11:09:14 +01:00
|
|
|
QueueLen = count_offline_messages(jid:nodeprep(User),
|
2015-11-24 16:44:13 +01:00
|
|
|
jid:nameprep(Server)),
|
2013-03-14 10:33:02 +01:00
|
|
|
FQueueLen = [?AC(<<"queue/">>,
|
2016-11-24 13:06:06 +01:00
|
|
|
(integer_to_binary(QueueLen)))],
|
2013-03-14 10:33:02 +01:00
|
|
|
Acc ++
|
|
|
|
[?XCT(<<"h3">>, <<"Offline Messages:">>)] ++
|
|
|
|
FQueueLen ++
|
|
|
|
[?C(<<" ">>),
|
|
|
|
?INPUTT(<<"submit">>, <<"removealloffline">>,
|
|
|
|
<<"Remove All Offline Messages">>)].
|
2008-10-12 16:16:05 +02:00
|
|
|
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec delete_all_msgs(binary(), binary()) -> {atomic, any()}.
|
2012-04-27 11:52:05 +02:00
|
|
|
delete_all_msgs(User, Server) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LUser = jid:nodeprep(User),
|
|
|
|
LServer = jid:nameprep(Server),
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:remove_all_messages(LUser, LServer).
|
2012-04-27 11:52:05 +02:00
|
|
|
|
2013-03-14 10:33:02 +01:00
|
|
|
webadmin_user_parse_query(_, <<"removealloffline">>,
|
|
|
|
User, Server, _Query) ->
|
2012-04-27 11:52:05 +02:00
|
|
|
case delete_all_msgs(User, Server) of
|
2017-02-18 07:36:27 +01:00
|
|
|
{atomic, ok} ->
|
|
|
|
?INFO_MSG("Removed all offline messages for ~s@~s",
|
|
|
|
[User, Server]),
|
|
|
|
{stop, ok};
|
|
|
|
Err ->
|
|
|
|
?ERROR_MSG("Failed to remove offline messages: ~p",
|
|
|
|
[Err]),
|
|
|
|
{stop, error}
|
2008-10-12 16:16:05 +02:00
|
|
|
end;
|
2013-03-14 10:33:02 +01:00
|
|
|
webadmin_user_parse_query(Acc, _Action, _User, _Server,
|
|
|
|
_Query) ->
|
2008-10-12 16:16:05 +02:00
|
|
|
Acc.
|
2012-04-27 11:52:05 +02:00
|
|
|
|
|
|
|
%% Returns as integer the number of offline messages for a given user
|
2016-07-19 06:56:14 +02:00
|
|
|
-spec count_offline_messages(binary(), binary()) -> non_neg_integer().
|
2012-11-13 13:56:27 +01:00
|
|
|
count_offline_messages(User, Server) ->
|
2015-11-24 16:44:13 +01:00
|
|
|
LUser = jid:nodeprep(User),
|
|
|
|
LServer = jid:nameprep(Server),
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:count_messages(LUser, LServer).
|
2012-11-13 13:56:27 +01:00
|
|
|
|
2016-11-13 08:44:53 +01:00
|
|
|
-spec add_delay_info(message(), binary(),
|
|
|
|
undefined | erlang:timestamp()) -> message().
|
2017-01-18 12:09:39 +01:00
|
|
|
add_delay_info(Packet, LServer, TS) ->
|
|
|
|
NewTS = case TS of
|
|
|
|
undefined -> p1_time_compat:timestamp();
|
|
|
|
_ -> TS
|
|
|
|
end,
|
2017-01-09 15:02:17 +01:00
|
|
|
Packet1 = xmpp:put_meta(Packet, from_offline, true),
|
2017-01-20 17:35:46 +01:00
|
|
|
xmpp_util:add_delay_info(Packet1, jid:make(LServer), NewTS,
|
2016-11-13 08:44:53 +01:00
|
|
|
<<"Offline storage">>).
|
|
|
|
|
2017-01-09 15:02:17 +01:00
|
|
|
-spec get_priority_from_presence(presence()) -> integer().
|
|
|
|
get_priority_from_presence(#presence{priority = Prio}) ->
|
|
|
|
case Prio of
|
|
|
|
undefined -> 0;
|
|
|
|
_ -> Prio
|
|
|
|
end.
|
|
|
|
|
2016-04-15 12:44:33 +02:00
|
|
|
export(LServer) ->
|
|
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
|
|
Mod:export(LServer).
|
2013-07-21 12:24:36 +02:00
|
|
|
|
2016-11-22 14:48:01 +01:00
|
|
|
import_info() ->
|
|
|
|
[{<<"spool">>, 4}].
|
2016-04-15 12:44:33 +02:00
|
|
|
|
2016-11-22 14:48:01 +01:00
|
|
|
import_start(LServer, DBType) ->
|
|
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
|
|
|
Mod:import(LServer, []).
|
|
|
|
|
|
|
|
import(LServer, {sql, _}, DBType, <<"spool">>,
|
|
|
|
[LUser, XML, _Seq, _TimeStamp]) ->
|
|
|
|
El = fxml_stream:parse_element(XML),
|
2017-02-18 07:36:27 +01:00
|
|
|
#message{from = From, to = To} = Msg = xmpp:decode(El, ?NS_CLIENT, [ignore_els]),
|
|
|
|
TS = case xmpp:get_subtag(Msg, #delay{stamp = {0,0,0}}) of
|
|
|
|
#delay{stamp = {MegaSecs, Secs, _}} ->
|
|
|
|
{MegaSecs, Secs, 0};
|
|
|
|
false ->
|
|
|
|
p1_time_compat:timestamp()
|
|
|
|
end,
|
2016-11-22 14:48:01 +01:00
|
|
|
US = {LUser, LServer},
|
2017-02-18 07:36:27 +01:00
|
|
|
Expire = find_x_expire(TS, Msg),
|
|
|
|
OffMsg = #offline_msg{us = US, packet = El,
|
|
|
|
from = From, to = To,
|
|
|
|
timestamp = TS, expire = Expire},
|
2016-04-15 12:44:33 +02:00
|
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
2017-02-18 07:36:27 +01:00
|
|
|
Mod:import(OffMsg).
|
2015-06-01 14:38:27 +02:00
|
|
|
|
|
|
|
mod_opt_type(access_max_user_messages) ->
|
2016-06-21 13:18:24 +02:00
|
|
|
fun acl:shaper_rules_validator/1;
|
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(store_empty_body) ->
|
2015-11-25 08:58:22 +01:00
|
|
|
fun (V) when is_boolean(V) -> V;
|
|
|
|
(unless_chat_state) -> unless_chat_state
|
2018-02-11 10:54:15 +01:00
|
|
|
end.
|
2018-01-23 08:54:52 +01:00
|
|
|
|
|
|
|
mod_options(Host) ->
|
|
|
|
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
|
|
|
|
{access_max_user_messages, max_user_offline_messages},
|
|
|
|
{store_empty_body, unless_chat_state}].
|