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