./ct_report/coverage/mod_mam.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Uvarov Michael <arcusfelis@gmail.com>
3 %%% @copyright (C) 2013, Uvarov Michael
4 %%% @doc XEP-0313: Message Archive Management
5 %%%
6 %%% The module uses several backend modules:
7 %%%
8 %%% <ul>
9 %%% <li>Preference manager ({@link mod_mam_muc_rdbms_prefs});</li>
10 %%% <li>Writer ({@link mod_mam_muc_rdbms_arch} or {@link mod_mam_muc_rdbms_async_pool_writer});</li>
11 %%% <li>Archive manager ({@link mod_mam_muc_rdbms_arch});</li>
12 %%% <li>User's ID generator ({@link mod_mam_muc_user}).</li>
13 %%% </ul>
14 %%%
15 %%% Preferences can be also stored in Mnesia ({@link mod_mam_mnesia_prefs}).
16 %%% This module handles simple archives.
17 %%%
18 %%% This module should be started for each host.
19 %%% Message archivation is not shaped here (use standard support for this).
20 %%% MAM's IQs are shaped inside {@link shaper_srv}.
21 %%%
22 %%% Message identifiers (or UIDs in the spec) are generated based on:
23 %%%
24 %%% <ul>
25 %%% <li>date (using `timestamp()');</li>
26 %%% <li>node number (using {@link ejabberd_node_id}).</li>
27 %%% </ul>
28 %%% @end
29 %%%-------------------------------------------------------------------
30 -module(mod_mam).
31 -behavior(gen_mod).
32 -behaviour(mongoose_module_metrics).
33 -xep([{xep, 313}, {version, "0.4.1"}]).
34 -xep([{xep, 313}, {version, "0.5"}]).
35 -xep([{xep, 313}, {version, "0.6"}]).
36 %% ----------------------------------------------------------------------
37 %% Exports
38
39 %% Client API
40 -export([delete_archive/2,
41 archive_size/2,
42 archive_size_with_host_type/3,
43 archive_id/2]).
44
45 %% gen_mod handlers
46 -export([start/2, stop/1, supported_features/0]).
47
48 %% ejabberd handlers
49 -export([disco_local_features/1,
50 process_mam_iq/5,
51 user_send_packet/4,
52 remove_user/3,
53 filter_packet/1,
54 determine_amp_strategy/5,
55 sm_filter_offline_message/4]).
56
57 %% gdpr callbacks
58 -export([get_personal_data/3]).
59
60 %%private
61 -export([archive_message_from_ct/1]).
62 -export([lookup_messages/2]).
63 -export([archive_id_int/2]).
64
65 -export([config_metrics/1]).
66
67 -ignore_xref([archive_message_from_ct/1,
68 archive_size/2, archive_size_with_host_type/3, delete_archive/2,
69 determine_amp_strategy/5, disco_local_features/1, filter_packet/1,
70 get_personal_data/3, remove_user/3, sm_filter_offline_message/4,
71 user_send_packet/4]).
72
73 %% ----------------------------------------------------------------------
74 %% Imports
75
76 %% UID
77 -import(mod_mam_utils,
78 [get_or_generate_mam_id/1,
79 decode_compact_uuid/1]).
80
81 %% XML
82 -import(mod_mam_utils,
83 [maybe_add_arcid_elems/4,
84 wrap_message/6,
85 result_set/4,
86 result_prefs/4,
87 make_fin_element/7,
88 parse_prefs/1,
89 is_mam_result_message/1,
90 features/2]).
91
92 %% Forms
93 -import(mod_mam_utils,
94 [message_form/3]).
95
96 %% Other
97 -import(mod_mam_utils,
98 [mess_id_to_external_binary/1,
99 is_complete_result_page/4]).
100
101 %% ejabberd
102 -import(mod_mam_utils,
103 [is_jid_in_user_roster/3]).
104
105
106 -include("mongoose.hrl").
107 -include("jlib.hrl").
108 -include("amp.hrl").
109 -include("mod_mam.hrl").
110
111 %% ----------------------------------------------------------------------
112 %% Datetime types
113 %% Microseconds from 01.01.1970
114 -type unix_timestamp() :: non_neg_integer().
115 -type host_type() :: mongooseim:host_type().
116
117 %% ----------------------------------------------------------------------
118 %% Other types
119 -type archive_behaviour() :: roster | always | never.
120 -type message_id() :: non_neg_integer().
121
122 -type archive_id() :: non_neg_integer().
123
124 -type borders() :: #mam_borders{}.
125
126 -type message_row() :: #{id := message_id(), jid := jid:jid(), packet := exml:element()}.
127 -type lookup_result() :: {TotalCount :: non_neg_integer() | undefined,
128 Offset :: non_neg_integer() | undefined,
129 MessageRows :: [message_row()]}.
130
131 %% Internal types
132 -type iterator_fun() :: fun(() -> {'ok', {_, _}}).
133 -type rewriter_fun() :: fun((JID :: jid:literal_jid())
134 -> jid:literal_jid()).
135 -type restore_option() :: {rewrite_jids, rewriter_fun() | [{binary(), binary()}]}
136 | new_message_ids.
137
138 -type preference() :: {DefaultMode :: archive_behaviour(),
139 AlwaysJIDs :: [jid:literal_jid()],
140 NeverJIDs :: [jid:literal_jid()]}.
141
142 -type archive_message_params() :: #{message_id := mod_mam:message_id(),
143 archive_id := mod_mam:archive_id(),
144 local_jid := jid:jid(),
145 remote_jid := jid:jid(),
146 source_jid := jid:jid(),
147 origin_id := binary() | none,
148 direction := atom(),
149 packet := exml:element(),
150 %% Only in mod_mam_muc_rdbms_arch:retract_message/2
151 sender_id => mod_mam:archive_id()}.
152
153 -export_type([rewriter_fun/0,
154 borders/0,
155 preference/0,
156 archive_behaviour/0,
157 iterator_fun/0,
158 unix_timestamp/0,
159 archive_id/0,
160 lookup_result/0,
161 message_row/0,
162 message_id/0,
163 restore_option/0,
164 archive_message_params/0
165 ]).
166
167 %% ----------------------------------------------------------------------
168 %% API
169
170 -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) ->
171 gdpr:personal_data().
172 get_personal_data(Acc, HostType, ArcJID) ->
173
:-(
Schema = ["id", "from", "message"],
174
:-(
Entries = mongoose_hooks:get_mam_pm_gdpr_data(HostType, ArcJID),
175
:-(
[{mam_pm, Schema, Entries} | Acc].
176
177 -spec delete_archive(jid:server(), jid:user()) -> 'ok'.
178 delete_archive(Server, User)
179 when is_binary(Server), is_binary(User) ->
180
:-(
?LOG_DEBUG(#{what => mam_delete_archive, user => User, server => Server}),
181
:-(
ArcJID = jid:make(User, Server, <<>>),
182
:-(
HostType = jid_to_host_type(ArcJID),
183
:-(
ArcID = archive_id_int(HostType, ArcJID),
184
:-(
remove_archive_hook(HostType, ArcID, ArcJID),
185
:-(
ok.
186
187 -spec archive_size(jid:server(), jid:user()) -> integer().
188 archive_size(Server, User)
189 when is_binary(Server), is_binary(User) ->
190
:-(
ArcJID = jid:make(User, Server, <<>>),
191
:-(
HostType = jid_to_host_type(ArcJID),
192
:-(
ArcID = archive_id_int(HostType, ArcJID),
193
:-(
archive_size(HostType, ArcID, ArcJID).
194
195 -spec archive_size_with_host_type(host_type(), jid:server(), jid:user()) -> integer().
196 archive_size_with_host_type(HostType, Server, User) ->
197
:-(
ArcJID = jid:make(User, Server, <<>>),
198
:-(
ArcID = archive_id_int(HostType, ArcJID),
199
:-(
archive_size(HostType, ArcID, ArcJID).
200
201 -spec archive_id(jid:server(), jid:user()) -> integer() | undefined.
202 archive_id(Server, User)
203 when is_binary(Server), is_binary(User) ->
204
:-(
ArcJID = jid:make(User, Server, <<>>),
205
:-(
HostType = jid_to_host_type(ArcJID),
206
:-(
archive_id_int(HostType, ArcJID).
207
208 %% gen_mod callbacks
209 %% Starting and stopping functions for users' archives
210
211 -spec start(HostType :: host_type(), Opts :: list()) -> any().
212 start(HostType, Opts) ->
213
:-(
?LOG_INFO(#{what => mam_starting, host_type => HostType}),
214
:-(
ensure_metrics(HostType),
215
:-(
ejabberd_hooks:add(hooks(HostType)),
216
:-(
add_id_handlers(HostType, Opts),
217
:-(
ok.
218
219 -spec stop(HostType :: host_type()) -> any().
220 stop(HostType) ->
221
:-(
?LOG_INFO(#{what => mam_stopping, host_type => HostType}),
222
:-(
ejabberd_hooks:delete(hooks(HostType)),
223
:-(
remove_iq_handlers(HostType),
224
:-(
ok.
225
226 -spec supported_features() -> [atom()].
227 supported_features() ->
228
:-(
[dynamic_domains].
229
230 %% ----------------------------------------------------------------------
231 %% hooks and handlers
232
233 %% `To' is an account or server entity hosting the archive.
234 %% Servers that archive messages on behalf of local users SHOULD expose archives
235 %% to the user on their bare JID (i.e. `From.luser'),
236 %% while a MUC service might allow MAM queries to be sent to the room's bare JID
237 %% (i.e `To.luser').
238 -spec process_mam_iq(Acc :: mongoose_acc:t(),
239 From :: jid:jid(), To :: jid:jid(), IQ :: jlib:iq(),
240 _Extra) -> {mongoose_acc:t(), jlib:iq() | ignore}.
241 process_mam_iq(Acc, From, To, IQ, _Extra) ->
242
:-(
HostType = mongoose_acc:host_type(Acc),
243
:-(
mod_mam_utils:maybe_log_deprecation(IQ),
244
:-(
Action = mam_iq:action(IQ),
245
:-(
case is_action_allowed(HostType, Action, From, To) of
246 true ->
247
:-(
case mod_mam_utils:wait_shaper(HostType, To#jid.lserver, Action, From) of
248 ok ->
249
:-(
handle_error_iq(HostType, Acc, To, Action,
250 handle_mam_iq(Action, From, To, IQ, Acc));
251 {error, max_delay_reached} ->
252
:-(
?LOG_WARNING(#{what => mam_max_delay_reached,
253 text => <<"Return max_delay_reached error IQ from MAM">>,
254
:-(
action => Action, acc => Acc}),
255
:-(
mongoose_metrics:update(HostType, modMamDroppedIQ, 1),
256
:-(
{Acc, return_max_delay_reached_error_iq(IQ)}
257 end;
258 false ->
259
:-(
mongoose_metrics:update(HostType, modMamDroppedIQ, 1),
260
:-(
{Acc, return_action_not_allowed_error_iq(IQ)}
261 end.
262
263 -spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
264 disco_local_features(Acc = #{host_type := HostType, node := <<>>}) ->
265
:-(
mongoose_disco:add_features(features(?MODULE, HostType), Acc);
266 disco_local_features(Acc) ->
267
:-(
Acc.
268
269 %% @doc Handle an outgoing message.
270 %%
271 %% Note: for outgoing messages, the server MUST use the value of the 'to'
272 %% attribute as the target JID.
273 -spec user_send_packet(Acc :: mongoose_acc:t(), From :: jid:jid(),
274 To :: jid:jid(),
275 Packet :: exml:element()) -> mongoose_acc:t().
276 user_send_packet(Acc, From, To, Packet) ->
277
:-(
?LOG_DEBUG(#{what => mam_user_send_packet, acc => Acc}),
278
:-(
{_, Acc2} = handle_package(outgoing, true, From, To, From, Packet, Acc),
279
:-(
Acc2.
280
281 %% @doc Handle an incoming message.
282 %%
283 %% Note: For incoming messages, the server MUST use the value of the
284 %% 'from' attribute as the target JID.
285 %%
286 %% Return drop to drop the packet, or the original input to let it through.
287 %% From and To are jid records.
288 -type fpacket() :: {From :: jid:jid(),
289 To :: jid:jid(),
290 Acc :: mongoose_acc:t(),
291 Packet :: exml:element()}.
292 -spec filter_packet(Value :: fpacket() | drop) -> fpacket() | drop.
293 filter_packet(drop) ->
294
:-(
drop;
295 filter_packet({From, To, Acc1, Packet}) ->
296
:-(
?LOG_DEBUG(#{what => mam_user_receive_packet, acc => Acc1}),
297
:-(
HostType = mongoose_acc:host_type(Acc1),
298
:-(
Type = mongoose_lib:get_message_type(Acc1),
299
:-(
{AmpEvent, PacketAfterArchive, Acc3} =
300 case mongoose_lib:does_local_user_exist(HostType, To, Type) of
301 false ->
302
:-(
{mam_failed, Packet, Acc1};
303 true ->
304
:-(
case process_incoming_packet(From, To, Packet, Acc1) of
305 {undefined, Acc2} ->
306
:-(
{mam_failed, Packet, Acc2};
307 {MessID, Acc2} ->
308
:-(
Packet2 = maybe_add_arcid_elems(
309 To, MessID, Packet,
310 mod_mam_params:add_stanzaid_element(?MODULE, HostType)),
311
:-(
{archived, Packet2, Acc2}
312 end
313 end,
314
:-(
Acc4 = mongoose_acc:update_stanza(#{ element => PacketAfterArchive,
315 from_jid => From,
316 to_jid => To }, Acc3),
317
:-(
Acc5 = mod_amp:check_packet(Acc4, AmpEvent),
318
:-(
{From, To, Acc5, mongoose_acc:element(Acc5)}.
319
320 process_incoming_packet(From, To, Packet, Acc) ->
321
:-(
handle_package(incoming, true, To, From, From, Packet, Acc).
322
323 %% hook handler
324 -spec remove_user(mongoose_acc:t(), jid:user(), jid:server()) -> mongoose_acc:t().
325 remove_user(Acc, User, Server) ->
326
:-(
delete_archive(Server, User),
327
:-(
Acc.
328
329 sm_filter_offline_message(_Drop=false, _From, _To, Packet) ->
330 %% If ...
331
:-(
is_mam_result_message(Packet);
332 %% ... than drop the message
333 sm_filter_offline_message(Other, _From, _To, _Packet) ->
334
:-(
Other.
335
336 %% ----------------------------------------------------------------------
337 %% Internal functions
338
339 -spec jid_to_host_type(jid:jid()) -> host_type().
340 jid_to_host_type(#jid{lserver=LServer}) ->
341
:-(
lserver_to_host_type(LServer).
342
343 lserver_to_host_type(LServer) ->
344
:-(
case mongoose_domain_api:get_domain_host_type(LServer) of
345 {ok, HostType} ->
346
:-(
HostType;
347 {error, not_found} ->
348
:-(
error({get_domain_host_type_failed, LServer})
349 end.
350
351 -spec acc_to_host_type(mongoose_acc:t()) -> host_type().
352 acc_to_host_type(Acc) ->
353
:-(
case mongoose_acc:host_type(Acc) of
354 undefined ->
355
:-(
lserver_to_host_type(mongoose_acc:lserver(Acc));
356 HostType ->
357
:-(
HostType
358 end.
359
360 -spec is_action_allowed(HostType :: host_type(),
361 Action :: mam_iq:action(), From :: jid:jid(),
362 To :: jid:jid()) -> boolean().
363 is_action_allowed(HostType, Action, From, To) ->
364
:-(
case acl:match_rule(HostType, To#jid.lserver, Action, From, default) of
365
:-(
allow -> true;
366
:-(
deny -> false;
367
:-(
default -> is_action_allowed_by_default(Action, From, To)
368 end.
369
370 -spec is_action_allowed_by_default(Action :: mam_iq:action(), From :: jid:jid(),
371 To :: jid:jid()) -> boolean().
372 is_action_allowed_by_default(_Action, From, To) ->
373
:-(
compare_bare_jids(From, To).
374
375 -spec compare_bare_jids(jid:simple_jid() | jid:jid(),
376 jid:simple_jid() | jid:jid()) -> boolean().
377 compare_bare_jids(JID1, JID2) ->
378
:-(
jid:to_bare(JID1) =:= jid:to_bare(JID2).
379
380 -spec handle_mam_iq(mam_iq:action(), From :: jid:jid(), To :: jid:jid(),
381 IQ :: jlib:iq(), Acc :: mongoose_acc:t()) ->
382 jlib:iq() | {error, term(), jlib:iq()}.
383 handle_mam_iq(Action, From, To, IQ, Acc) ->
384
:-(
case Action of
385 mam_get_prefs ->
386
:-(
handle_get_prefs(To, IQ, Acc);
387 mam_set_prefs ->
388
:-(
handle_set_prefs(To, IQ, Acc);
389 mam_set_message_form ->
390
:-(
handle_set_message_form(From, To, IQ, Acc);
391 mam_get_message_form ->
392
:-(
handle_get_message_form(From, To, IQ, Acc)
393 end.
394
395 -spec handle_set_prefs(jid:jid(), jlib:iq(), mongoose_acc:t()) ->
396 jlib:iq() | {error, term(), jlib:iq()}.
397 handle_set_prefs(ArcJID=#jid{}, IQ=#iq{sub_el = PrefsEl}, Acc) ->
398
:-(
{DefaultMode, AlwaysJIDs, NeverJIDs} = parse_prefs(PrefsEl),
399
:-(
?LOG_DEBUG(#{what => mam_set_prefs, default_mode => DefaultMode,
400
:-(
always_jids => AlwaysJIDs, never_jids => NeverJIDs, iq => IQ}),
401
:-(
HostType = acc_to_host_type(Acc),
402
:-(
ArcID = archive_id_int(HostType, ArcJID),
403
:-(
Res = set_prefs(HostType, ArcID, ArcJID, DefaultMode, AlwaysJIDs, NeverJIDs),
404
:-(
handle_set_prefs_result(Res, DefaultMode, AlwaysJIDs, NeverJIDs, IQ).
405
406 handle_set_prefs_result(ok, DefaultMode, AlwaysJIDs, NeverJIDs, IQ) ->
407
:-(
Namespace = IQ#iq.xmlns,
408
:-(
ResultPrefsEl = result_prefs(DefaultMode, AlwaysJIDs, NeverJIDs, Namespace),
409
:-(
IQ#iq{type = result, sub_el = [ResultPrefsEl]};
410 handle_set_prefs_result({error, Reason},
411 _DefaultMode, _AlwaysJIDs, _NeverJIDs, IQ) ->
412
:-(
return_error_iq(IQ, Reason).
413
414 -spec handle_get_prefs(jid:jid(), IQ :: jlib:iq(), Acc :: mongoose_acc:t()) ->
415 jlib:iq() | {error, term(), jlib:iq()}.
416 handle_get_prefs(ArcJID=#jid{}, IQ=#iq{}, Acc) ->
417
:-(
HostType = acc_to_host_type(Acc),
418
:-(
ArcID = archive_id_int(HostType, ArcJID),
419
:-(
Res = get_prefs(HostType, ArcID, ArcJID, always),
420
:-(
handle_get_prefs_result(Res, IQ).
421
422 handle_get_prefs_result({DefaultMode, AlwaysJIDs, NeverJIDs}, IQ) ->
423
:-(
?LOG_DEBUG(#{what => mam_get_prefs_result, default_mode => DefaultMode,
424
:-(
always_jids => AlwaysJIDs, never_jids => NeverJIDs, iq => IQ}),
425
:-(
Namespace = IQ#iq.xmlns,
426
:-(
ResultPrefsEl = result_prefs(DefaultMode, AlwaysJIDs, NeverJIDs, Namespace),
427
:-(
IQ#iq{type = result, sub_el = [ResultPrefsEl]};
428 handle_get_prefs_result({error, Reason}, IQ) ->
429
:-(
return_error_iq(IQ, Reason).
430
431 -spec handle_set_message_form(From :: jid:jid(), ArcJID :: jid:jid(),
432 IQ :: jlib:iq(), Acc :: mongoose_acc:t()) ->
433 jlib:iq() | ignore | {error, term(), jlib:iq()}.
434 handle_set_message_form(#jid{} = From, #jid{} = ArcJID, #iq{} = IQ, Acc) ->
435
:-(
HostType = acc_to_host_type(Acc),
436
:-(
ArcID = archive_id_int(HostType, ArcJID),
437
:-(
try iq_to_lookup_params(HostType, IQ) of
438 Params0 ->
439
:-(
do_handle_set_message_form(Params0, From, ArcID, ArcJID, IQ, HostType)
440 catch _C:R:S ->
441
:-(
report_issue({R, S}, mam_lookup_failed, ArcJID, IQ),
442
:-(
return_error_iq(IQ, R)
443 end.
444
445
446 -spec do_handle_set_message_form(Params :: mam_iq:lookup_params(),
447 From :: jid:jid(),
448 ArcId :: archive_id(),
449 ArcJID :: jid:jid(),
450 IQ :: jlib:iq(),
451 HostType :: mongooseim:host_type()) ->
452 jlib:iq() | ignore | {error, term(), jlib:iq()}.
453 do_handle_set_message_form(Params0, From, ArcID, ArcJID,
454 #iq{xmlns=MamNs, sub_el = QueryEl} = IQ,
455 HostType) ->
456
:-(
QueryID = exml_query:attr(QueryEl, <<"queryid">>, <<>>),
457
:-(
Params = mam_iq:lookup_params_with_archive_details(Params0, ArcID, ArcJID, From),
458
:-(
case lookup_messages(HostType, Params) of
459 {error, Reason} ->
460
:-(
report_issue(Reason, mam_lookup_failed, ArcJID, IQ),
461
:-(
return_error_iq(IQ, Reason);
462 {ok, {TotalCount, Offset, MessageRows}} ->
463 %% Forward messages
464
:-(
{FirstMessID, LastMessID} = forward_messages(HostType, From, ArcJID, MamNs,
465 QueryID, MessageRows, true),
466 %% Make fin iq
467
:-(
IsComplete = is_complete_result_page(TotalCount, Offset, MessageRows, Params),
468
:-(
IsStable = true,
469
:-(
ResultSetEl = result_set(FirstMessID, LastMessID, Offset, TotalCount),
470
:-(
ExtFinMod = mod_mam_params:extra_fin_element_module(?MODULE, HostType),
471
:-(
FinElem = make_fin_element(HostType, Params, IQ#iq.xmlns, IsComplete, IsStable, ResultSetEl, ExtFinMod),
472
:-(
IQ#iq{type = result, sub_el = [FinElem]}
473 end.
474
475 iq_to_lookup_params(HostType, IQ) ->
476
:-(
Max = mod_mam_params:max_result_limit(?MODULE, HostType),
477
:-(
Def = mod_mam_params:default_result_limit(?MODULE, HostType),
478
:-(
Ext = mod_mam_params:extra_params_module(?MODULE, HostType),
479
:-(
mam_iq:form_to_lookup_params(IQ, Max, Def, Ext).
480
481 forward_messages(HostType, From, ArcJID, MamNs, QueryID, MessageRows, SetClientNs) ->
482 %% Forward messages
483
:-(
{FirstMessID, LastMessID} =
484 case MessageRows of
485
:-(
[] -> {undefined, undefined};
486
:-(
[_|_] -> {message_row_to_ext_id(hd(MessageRows)),
487 message_row_to_ext_id(lists:last(MessageRows))}
488 end,
489
:-(
SendModule = mod_mam_params:send_message_mod(?MODULE, HostType),
490
:-(
[send_message(SendModule, Row, ArcJID, From,
491 message_row_to_xml(MamNs, Row, QueryID, SetClientNs))
492
:-(
|| Row <- MessageRows],
493
:-(
{FirstMessID, LastMessID}.
494
495 send_message(SendModule, Row, ArcJID, From, Packet) ->
496
:-(
mam_send_message:call_send_message(SendModule, Row, ArcJID, From, Packet).
497
498 -spec handle_get_message_form(jid:jid(), jid:jid(), jlib:iq(), mongoose_acc:t()) ->
499 jlib:iq().
500 handle_get_message_form(_From=#jid{}, _ArcJID=#jid{}, IQ=#iq{}, Acc) ->
501
:-(
HostType = acc_to_host_type(Acc),
502
:-(
return_message_form_iq(HostType, IQ).
503
504 determine_amp_strategy(Strategy = #amp_strategy{deliver = Deliver},
505 FromJID, ToJID, Packet, initial_check) ->
506
:-(
HostType = jid_to_host_type(ToJID),
507
:-(
ShouldBeStored = is_archivable_message(HostType, incoming, Packet)
508
:-(
andalso is_interesting(ToJID, FromJID)
509
:-(
andalso ejabberd_auth:does_user_exist(ToJID),
510
:-(
case ShouldBeStored of
511
:-(
true -> Strategy#amp_strategy{deliver = amp_deliver_strategy(Deliver)};
512
:-(
false -> Strategy
513 end;
514 determine_amp_strategy(Strategy, _, _, _, _) ->
515
:-(
Strategy.
516
517
:-(
amp_deliver_strategy([none]) -> [stored, none];
518
:-(
amp_deliver_strategy([direct, none]) -> [direct, stored, none].
519
520 -spec handle_package(Dir :: incoming | outgoing, ReturnMessID :: boolean(),
521 LocJID :: jid:jid(), RemJID :: jid:jid(), SrcJID :: jid:jid(),
522 Packet :: exml:element(), Acc :: mongoose_acc:t()) ->
523 {MaybeMessID :: binary() | undefined, Acc :: mongoose_acc:t()}.
524 handle_package(Dir, ReturnMessID,
525 LocJID = #jid{}, RemJID = #jid{}, SrcJID = #jid{}, Packet, Acc) ->
526
:-(
HostType = acc_to_host_type(Acc),
527
:-(
case is_archivable_message(HostType, Dir, Packet)
528
:-(
andalso should_archive_if_groupchat(HostType, exml_query:attr(Packet, <<"type">>)) of
529 true ->
530
:-(
ArcID = archive_id_int(HostType, LocJID),
531
:-(
OriginID = mod_mam_utils:get_origin_id(Packet),
532
:-(
case is_interesting(HostType, LocJID, RemJID, ArcID) of
533 true ->
534
:-(
MessID = get_or_generate_mam_id(Acc),
535
:-(
Params = #{message_id => MessID,
536 archive_id => ArcID,
537 local_jid => LocJID,
538 remote_jid => RemJID,
539 source_jid => SrcJID,
540 origin_id => OriginID,
541 direction => Dir,
542 packet => Packet},
543
:-(
Result = archive_message(HostType, Params),
544
:-(
ExtMessId = return_external_message_id_if_ok(ReturnMessID, Result, MessID),
545
:-(
{ExtMessId, return_acc_with_mam_id_if_configured(ExtMessId, HostType, Acc)};
546 false ->
547
:-(
{undefined, Acc}
548 end;
549 false ->
550
:-(
{undefined, Acc}
551 end.
552
553 should_archive_if_groupchat(HostType, <<"groupchat">>) ->
554
:-(
gen_mod:get_module_opt(HostType, ?MODULE, archive_groupchats);
555 should_archive_if_groupchat(_, _) ->
556
:-(
true.
557
558 -spec return_external_message_id_if_ok(ReturnMessID :: boolean(),
559 ArchivingResult :: ok | any(),
560 MessID :: integer()) -> binary() | undefined.
561
:-(
return_external_message_id_if_ok(true, ok, MessID) -> mess_id_to_external_binary(MessID);
562
:-(
return_external_message_id_if_ok(_, _, _MessID) -> undefined.
563
564 return_acc_with_mam_id_if_configured(undefined, _, Acc) ->
565
:-(
Acc;
566 return_acc_with_mam_id_if_configured(ExtMessId, HostType, Acc) ->
567
:-(
case gen_mod:get_module_opt(HostType, ?MODULE, same_mam_id_for_peers) of
568
:-(
false -> mongoose_acc:set(mam, mam_id, ExtMessId, Acc);
569
:-(
true -> mongoose_acc:set_permanent(mam, mam_id, ExtMessId, Acc)
570 end.
571
572 is_interesting(LocJID, RemJID) ->
573
:-(
HostType = jid_to_host_type(LocJID),
574
:-(
ArcID = archive_id_int(HostType, LocJID),
575
:-(
is_interesting(HostType, LocJID, RemJID, ArcID).
576
577 is_interesting(HostType, LocJID, RemJID, ArcID) ->
578
:-(
case get_behaviour(HostType, ArcID, LocJID, RemJID) of
579
:-(
always -> true;
580
:-(
never -> false;
581
:-(
roster -> is_jid_in_user_roster(HostType, LocJID, RemJID)
582 end.
583
584 %% ----------------------------------------------------------------------
585 %% Backend wrappers
586
587 -spec archive_id_int(host_type(), jid:jid()) ->
588 non_neg_integer() | undefined.
589 archive_id_int(HostType, ArcJID=#jid{}) ->
590
:-(
mongoose_hooks:mam_archive_id(HostType, ArcJID).
591
592 -spec archive_size(host_type(), archive_id(), jid:jid()) -> integer().
593 archive_size(HostType, ArcID, ArcJID=#jid{}) ->
594
:-(
mongoose_hooks:mam_archive_size(HostType, ArcID, ArcJID).
595
596 -spec get_behaviour(host_type(), archive_id(), LocJID :: jid:jid(),
597 RemJID :: jid:jid()) -> atom().
598 get_behaviour(HostType, ArcID, LocJID=#jid{}, RemJID=#jid{}) ->
599
:-(
mongoose_hooks:mam_get_behaviour(HostType, ArcID, LocJID, RemJID).
600
601 -spec set_prefs(host_type(), archive_id(), ArcJID :: jid:jid(),
602 DefaultMode :: atom(), AlwaysJIDs :: [jid:literal_jid()],
603 NeverJIDs :: [jid:literal_jid()]) -> any().
604 set_prefs(HostType, ArcID, ArcJID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
605
:-(
mongoose_hooks:mam_set_prefs(HostType, ArcID, ArcJID, DefaultMode,
606 AlwaysJIDs, NeverJIDs).
607
608 %% @doc Load settings from the database.
609 -spec get_prefs(HostType :: host_type(), ArcID :: archive_id(),
610 ArcJID :: jid:jid(), GlobalDefaultMode :: archive_behaviour()
611 ) -> preference() | {error, Reason :: term()}.
612 get_prefs(HostType, ArcID, ArcJID, GlobalDefaultMode) ->
613
:-(
mongoose_hooks:mam_get_prefs(HostType, GlobalDefaultMode, ArcID, ArcJID).
614
615 -spec remove_archive_hook(host_type(), archive_id(), jid:jid()) -> 'ok'.
616 remove_archive_hook(HostType, ArcID, ArcJID=#jid{}) ->
617
:-(
mongoose_hooks:mam_remove_archive(HostType, ArcID, ArcJID),
618
:-(
ok.
619
620 -spec lookup_messages(HostType :: host_type(), Params :: map()) ->
621 {ok, mod_mam:lookup_result()}
622 | {error, 'policy-violation'}
623 | {error, Reason :: term()}.
624 lookup_messages(HostType, Params) ->
625
:-(
Result = lookup_messages_without_policy_violation_check(HostType, Params),
626 %% If a query returns a number of stanzas greater than this limit and the
627 %% client did not specify a limit using RSM then the server should return
628 %% a policy-violation error to the client.
629
:-(
mod_mam_utils:check_result_for_policy_violation(Params, Result).
630
631 lookup_messages_without_policy_violation_check(
632 HostType, #{search_text := SearchText} = Params) ->
633
:-(
case SearchText /= undefined andalso
634
:-(
not mod_mam_params:has_full_text_search(?MODULE, HostType) of
635 true -> %% Use of disabled full text search
636
:-(
{error, 'not-supported'};
637 false ->
638
:-(
StartT = erlang:monotonic_time(microsecond),
639
:-(
R = mongoose_hooks:mam_lookup_messages(HostType, Params),
640
:-(
Diff = erlang:monotonic_time(microsecond) - StartT,
641
:-(
mongoose_metrics:update(HostType, [backends, ?MODULE, lookup], Diff),
642
:-(
R
643 end.
644
645 archive_message_from_ct(Params = #{local_jid := JID}) ->
646
:-(
HostType = jid_to_host_type(JID),
647
:-(
archive_message(HostType, Params).
648
649 -spec archive_message(host_type(), mod_mam:archive_message_params()) ->
650 ok | {error, timeout}.
651 archive_message(HostType, Params) ->
652
:-(
StartT = erlang:monotonic_time(microsecond),
653
:-(
R = mongoose_hooks:mam_archive_message(HostType, Params),
654
:-(
Diff = erlang:monotonic_time(microsecond) - StartT,
655
:-(
mongoose_metrics:update(HostType, [backends, ?MODULE, archive], Diff),
656
:-(
R.
657
658 %% ----------------------------------------------------------------------
659 %% Helpers
660
661 -spec message_row_to_xml(binary(), message_row(), QueryId :: binary(), boolean()) ->
662 exml:element().
663 message_row_to_xml(MamNs, #{id := MessID, jid := SrcJID, packet := Packet},
664 QueryID, SetClientNs) ->
665
:-(
{Microseconds, _NodeMessID} = decode_compact_uuid(MessID),
666
:-(
TS = calendar:system_time_to_rfc3339(Microseconds, [{offset, "Z"}, {unit, microsecond}]),
667
:-(
BExtMessID = mess_id_to_external_binary(MessID),
668
:-(
Packet1 = mod_mam_utils:maybe_set_client_xmlns(SetClientNs, Packet),
669
:-(
wrap_message(MamNs, Packet1, QueryID, BExtMessID, TS, SrcJID).
670
671 -spec message_row_to_ext_id(message_row()) -> binary().
672 message_row_to_ext_id(#{id := MessID}) ->
673
:-(
mess_id_to_external_binary(MessID).
674
675 handle_error_iq(HostType, Acc, _To, _Action, {error, _Reason, IQ}) ->
676
:-(
mongoose_metrics:update(HostType, modMamDroppedIQ, 1),
677
:-(
{Acc, IQ};
678 handle_error_iq(_Host, Acc, _To, _Action, IQ) ->
679
:-(
{Acc, IQ}.
680
681 -spec return_action_not_allowed_error_iq(jlib:iq()) -> jlib:iq().
682 return_action_not_allowed_error_iq(IQ) ->
683
:-(
ErrorEl = jlib:stanza_errort(<<"">>, <<"cancel">>, <<"not-allowed">>,
684 <<"en">>, <<"The action is not allowed.">>),
685
:-(
IQ#iq{type = error, sub_el = [ErrorEl]}.
686
687 -spec return_max_delay_reached_error_iq(jlib:iq()) -> jlib:iq().
688 return_max_delay_reached_error_iq(IQ) ->
689 %% Message not found.
690
:-(
ErrorEl = mongoose_xmpp_errors:resource_constraint(
691 <<"en">>, <<"The action is cancelled because of flooding.">>),
692
:-(
IQ#iq{type = error, sub_el = [ErrorEl]}.
693
694 -spec return_error_iq(jlib:iq(), Reason :: term()) -> {error, term(), jlib:iq()}.
695 return_error_iq(IQ, {Reason, {stacktrace, _Stacktrace}}) ->
696
:-(
return_error_iq(IQ, Reason);
697 return_error_iq(IQ, timeout) ->
698
:-(
E = mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Timeout">>),
699
:-(
{error, timeout, IQ#iq{type = error, sub_el = [E]}};
700 return_error_iq(IQ, invalid_stanza_id) ->
701
:-(
Text = mongoose_xmpp_errors:not_acceptable(<<"en">>, <<"Invalid stanza id provided">>),
702
:-(
{error, invalid_stanza_id, IQ#iq{type = error, sub_el = [Text]}};
703 return_error_iq(IQ, item_not_found) ->
704
:-(
{error, item_not_found, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:item_not_found()]}};
705 return_error_iq(IQ, not_implemented) ->
706
:-(
{error, not_implemented, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:feature_not_implemented()]}};
707 return_error_iq(IQ, Reason) ->
708
:-(
{error, Reason, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:internal_server_error()]}}.
709
710 return_message_form_iq(HostType, IQ) ->
711
:-(
IQ#iq{type = result, sub_el = [message_form(?MODULE, HostType, IQ#iq.xmlns)]}.
712
713 report_issue({Reason, {stacktrace, Stacktrace}}, Issue, ArcJID, IQ) ->
714
:-(
report_issue(Reason, Stacktrace, Issue, ArcJID, IQ);
715 report_issue(Reason, Issue, ArcJID, IQ) ->
716
:-(
report_issue(Reason, [], Issue, ArcJID, IQ).
717
718 report_issue(invalid_stanza_id, _Stacktrace, _Issue, _ArcJID, _IQ) ->
719
:-(
expected;
720 report_issue(item_not_found, _Stacktrace, _Issue, _ArcJID, _IQ) ->
721
:-(
expected;
722 report_issue(not_implemented, _Stacktrace, _Issue, _ArcJID, _IQ) ->
723
:-(
expected;
724 report_issue(timeout, _Stacktrace, _Issue, _ArcJID, _IQ) ->
725
:-(
expected;
726 report_issue(Reason, Stacktrace, Issue, #jid{lserver=LServer, luser=LUser}, IQ) ->
727
:-(
?LOG_ERROR(#{what => mam_error,
728 issue => Issue, server => LServer, user => LUser,
729
:-(
reason => Reason, iq => IQ, stacktrace => Stacktrace}).
730
731 -spec is_archivable_message(HostType :: host_type(),
732 Dir :: incoming | outgoing,
733 Packet :: exml:element()) -> boolean().
734 is_archivable_message(HostType, Dir, Packet) ->
735
:-(
M = mod_mam_params:is_archivable_message_module(?MODULE, HostType),
736
:-(
ArchiveChatMarkers = mod_mam_params:archive_chat_markers(?MODULE, HostType),
737
:-(
erlang:apply(M, is_archivable_message, [?MODULE, Dir, Packet, ArchiveChatMarkers]).
738
739 config_metrics(HostType) ->
740
:-(
OptsToReport = [{backend, rdbms}], %list of tuples {option, default_value}
741
:-(
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, OptsToReport).
742
743 -spec hooks(jid:lserver()) -> [ejabberd_hooks:hook()].
744 hooks(HostType) ->
745
:-(
[{disco_local_features, HostType, ?MODULE, disco_local_features, 99},
746 {user_send_packet, HostType, ?MODULE, user_send_packet, 60},
747 {rest_user_send_packet, HostType, ?MODULE, user_send_packet, 60},
748 {filter_local_packet, HostType, ?MODULE, filter_packet, 90},
749 {remove_user, HostType, ?MODULE, remove_user, 50},
750 {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50},
751 {amp_determine_strategy, HostType, ?MODULE, determine_amp_strategy, 20},
752 {sm_filter_offline_message, HostType, ?MODULE, sm_filter_offline_message, 50},
753 {get_personal_data, HostType, ?MODULE, get_personal_data, 50}
754 | mongoose_metrics_mam_hooks:get_mam_hooks(HostType)].
755
756 add_id_handlers(HostType, Opts) ->
757
:-(
Component = ejabberd_sm,
758 %% `parallel' is the only one recommended here.
759
:-(
ExecutionType = gen_mod:get_opt(iqdisc, Opts, parallel),
760
:-(
IQHandlerFn = fun ?MODULE:process_mam_iq/5,
761
:-(
Extra = #{},
762
:-(
[gen_iq_handler:add_iq_handler_for_domain(HostType, Namespace,
763 Component, IQHandlerFn,
764 Extra, ExecutionType)
765
:-(
|| Namespace <- [?NS_MAM_04, ?NS_MAM_06]],
766
:-(
ok.
767
768 remove_iq_handlers(HostType) ->
769
:-(
Component = ejabberd_sm,
770
:-(
[gen_iq_handler:remove_iq_handler_for_domain(HostType, Namespace, Component)
771
:-(
|| Namespace <- [?NS_MAM_04, ?NS_MAM_06]],
772
:-(
ok.
773
774 ensure_metrics(HostType) ->
775
:-(
mongoose_metrics:ensure_metric(HostType, [backends, ?MODULE, lookup], histogram),
776
:-(
mongoose_metrics:ensure_metric(HostType, [HostType, modMamLookups, simple], spiral),
777
:-(
mongoose_metrics:ensure_metric(HostType, [backends, ?MODULE, archive], histogram),
778
:-(
lists:foreach(fun(Name) ->
779
:-(
mongoose_metrics:ensure_metric(HostType, Name, spiral)
780 end,
781 spirals()).
782
783 spirals() ->
784
:-(
[modMamPrefsSets,
785 modMamPrefsGets,
786 modMamArchiveRemoved,
787 modMamLookups,
788 modMamForwarded,
789 modMamArchived,
790 modMamFlushed,
791 modMamDropped,
792 modMamDroppedIQ].
Line Hits Source