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