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