./ct_report/coverage/mod_mam_muc.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 MUC archives.
17 %%%
18 %%% This module should be started for each host.
19 %%% Message archivation is not shaped here (use standard support for this).
20 %%% MAM's IQs are shaped inside {@link shaper_srv}.
21 %%%
22 %%% Message identifiers (or UIDs in the spec) are generated based on:
23 %%%
24 %%% <ul>
25 %%% <li>date (using `timestamp()');</li>
26 %%% <li>node number (using {@link ejabberd_node_id}).</li>
27 %%% </ul>
28 %%% @end
29 %%%-------------------------------------------------------------------
30 -module(mod_mam_muc).
31 -xep([{xep, 313}, {version, "0.4.1"}]).
32 -xep([{xep, 313}, {version, "0.5"}]).
33 -xep([{xep, 45}, {version, "1.25"}]).
34 %% ----------------------------------------------------------------------
35 %% Exports
36
37 %% Client API
38 -export([delete_archive/2,
39 archive_size/2,
40 archive_id/2]).
41
42 %% gen_mod handlers
43 -export([start/2, stop/1, supported_features/0]).
44
45 %% ejabberd room handlers
46 -export([disco_muc_features/1,
47 filter_room_packet/3,
48 room_process_mam_iq/5,
49 forget_room/4]).
50
51 %% gdpr callback
52 -export([get_personal_data/3]).
53
54 %% private
55 -export([archive_message_for_ct/1]).
56 -export([lookup_messages/2]).
57 -export([archive_id_int/2]).
58
59 -ignore_xref([archive_id/2, archive_message_for_ct/1, archive_size/2, behaviour_info/1,
60 delete_archive/2, disco_muc_features/1, filter_room_packet/3,
61 forget_room/4, get_personal_data/3, start/2, stop/1, supported_features/0]).
62
63 %% ----------------------------------------------------------------------
64 %% Imports
65
66 %% UID
67 -import(mod_mam_utils,
68 [generate_message_id/1,
69 decode_compact_uuid/1]).
70
71 %% XML
72 -import(mod_mam_utils,
73 [maybe_add_arcid_elems/4,
74 replace_x_user_element/4,
75 delete_x_user_element/1,
76 packet_to_x_user_jid/1,
77 wrap_message/6,
78 result_set/4,
79 result_query/2,
80 result_prefs/4,
81 make_fin_message/5,
82 make_fin_element/7,
83 parse_prefs/1,
84 borders_decode/1,
85 features/2]).
86
87 %% Forms
88 -import(mod_mam_utils,
89 [message_form/3]).
90
91 %% Other
92 -import(mod_mam_utils,
93 [mess_id_to_external_binary/1,
94 is_complete_result_page/4]).
95
96 -include_lib("mongoose.hrl").
97 -include_lib("jlib.hrl").
98 -include_lib("exml/include/exml.hrl").
99
100 -callback is_complete_message(Module :: atom(), Dir :: atom(), Packet :: any()) ->
101 boolean().
102
103 %% ----------------------------------------------------------------------
104 %% Other types
105 -type packet() :: any().
106 -type row_batch() :: {TotalCount :: non_neg_integer(),
107 Offset :: non_neg_integer(),
108 MessageRows :: [row()]}.
109 -type row() :: mod_mam:message_row().
110 -type host_type() :: mongooseim:host_type().
111 -type muc_action() :: atom().
112
113 -export_type([row/0, row_batch/0]).
114
115 %% ----------------------------------------------------------------------
116 %% API
117
118 -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) ->
119 gdpr:personal_data().
120 get_personal_data(Acc, HostType, ArcJID) ->
121
:-(
Schema = ["id", "message"],
122
:-(
Entries = mongoose_hooks:get_mam_muc_gdpr_data(HostType, ArcJID),
123
:-(
[{mam_muc, Schema, Entries} | Acc].
124
125 -spec delete_archive(jid:server(), jid:user()) -> ok.
126 delete_archive(MucHost, RoomName) when is_binary(MucHost), is_binary(RoomName) ->
127 4 ?LOG_DEBUG(#{what => mam_delete_room, room => RoomName, sub_host => MucHost}),
128 4 ArcJID = jid:make(RoomName, MucHost, <<>>),
129 4 HostType = mod_muc_light_utils:room_jid_to_host_type(ArcJID),
130 4 ArcID = archive_id_int(HostType, ArcJID),
131 4 remove_archive(HostType, ArcID, ArcJID),
132 4 ok.
133
134 -spec archive_size(jid:server(), jid:user()) -> integer().
135 archive_size(MucHost, RoomName) when is_binary(MucHost), is_binary(RoomName) ->
136
:-(
ArcJID = jid:make(RoomName, MucHost, <<>>),
137
:-(
HostType = mod_muc_light_utils:room_jid_to_host_type(ArcJID),
138
:-(
ArcID = archive_id_int(HostType, ArcJID),
139
:-(
archive_size(HostType, ArcID, ArcJID).
140
141 -spec archive_id(jid:server(), jid:user()) -> integer().
142 archive_id(MucHost, RoomName) when is_binary(MucHost), is_binary(RoomName) ->
143
:-(
ArcJID = jid:make(RoomName, MucHost, <<>>),
144
:-(
HostType = mod_muc_light_utils:room_jid_to_host_type(ArcJID),
145
:-(
archive_id_int(HostType, ArcJID).
146
147 %% ----------------------------------------------------------------------
148 %% gen_mod callbacks
149 %% Starting and stopping functions for MUC archives
150
151 -spec start(HostType :: host_type(), Opts :: list()) -> any().
152 start(HostType, Opts) ->
153 6 ?LOG_DEBUG(#{what => mam_muc_starting}),
154 6 ensure_metrics(HostType),
155 6 ejabberd_hooks:add(hooks(HostType)),
156 6 add_iq_handlers(HostType, Opts),
157 6 ok.
158
159 -spec stop(HostType :: host_type()) -> any().
160 stop(HostType) ->
161 6 ?LOG_DEBUG(#{what => mam_muc_stopping}),
162 6 ejabberd_hooks:delete(hooks(HostType)),
163 6 remove_iq_handlers(HostType),
164 6 ok.
165
166 -spec supported_features() -> [atom()].
167 supported_features() ->
168
:-(
[dynamic_domains].
169
170 %% ----------------------------------------------------------------------
171 %% hooks and handlers for MUC
172
173 -spec disco_muc_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
174 disco_muc_features(Acc = #{host_type := HostType, node := <<>>}) ->
175 6 mongoose_disco:add_features(features(?MODULE, HostType), Acc);
176 disco_muc_features(Acc) ->
177
:-(
Acc.
178
179 %% @doc Handle public MUC-message.
180 -spec filter_room_packet(Packet :: packet(), HostType :: host_type(),
181 EventData :: mod_muc:room_event_data()) -> packet().
182 filter_room_packet(Packet, HostType, EventData = #{}) ->
183
:-(
?LOG_DEBUG(#{what => mam_room_packet, text => <<"Incoming room packet">>,
184
:-(
packet => Packet, event_data => EventData}),
185
:-(
IsArchivable = is_archivable_message(HostType, incoming, Packet),
186
:-(
case IsArchivable of
187 true ->
188
:-(
#{from_nick := FromNick, from_jid := FromJID, room_jid := RoomJID,
189 role := Role, affiliation := Affiliation, timestamp := TS} = EventData,
190
:-(
archive_room_packet(HostType, Packet, FromNick, FromJID,
191 RoomJID, Role, Affiliation, TS);
192
:-(
false -> Packet
193 end.
194
195 %% @doc Archive without validation.
196 -spec archive_room_packet(HostType :: host_type(),
197 Packet :: packet(), FromNick :: jid:user(),
198 FromJID :: jid:jid(), RoomJID :: jid:jid(),
199 Role :: mod_muc:role(), Affiliation :: mod_muc:affiliation(),
200 TS :: integer()) -> packet().
201 archive_room_packet(HostType, Packet, FromNick, FromJID = #jid{},
202 RoomJID = #jid{}, Role, Affiliation, TS) ->
203
:-(
ArcID = archive_id_int(HostType, RoomJID),
204 %% Occupant JID <room@service/nick>
205
:-(
SrcJID = jid:replace_resource(RoomJID, FromNick),
206
:-(
IsInteresting =
207 case get_behaviour(HostType, ArcID, RoomJID, SrcJID) of
208
:-(
always -> true;
209
:-(
never -> false;
210
:-(
roster -> true
211 end,
212
:-(
case IsInteresting of
213 true ->
214
:-(
MessID = generate_message_id(TS),
215
:-(
Packet1 = replace_x_user_element(FromJID, Role, Affiliation, Packet),
216
:-(
OriginID = mod_mam_utils:get_origin_id(Packet),
217
:-(
Params = #{message_id => MessID,
218 archive_id => ArcID,
219 local_jid => RoomJID,
220 remote_jid => FromJID,
221 source_jid => SrcJID,
222 origin_id => OriginID,
223 direction => incoming,
224 packet => Packet1},
225 %% Packet to be broadcasted and packet to be archived are
226 %% not 100% the same
227
:-(
Result = archive_message(HostType, Params),
228
:-(
case Result of
229 ok ->
230
:-(
ExtID = mess_id_to_external_binary(MessID),
231
:-(
ShouldAdd = mod_mam_params:add_stanzaid_element(?MODULE, HostType),
232
:-(
maybe_add_arcid_elems(RoomJID, ExtID, Packet, ShouldAdd);
233
:-(
{error, _} -> Packet
234 end;
235
:-(
false -> Packet
236 end.
237
238 %% @doc `To' is an account or server entity hosting the archive.
239 %% Servers that archive messages on behalf of local users SHOULD expose archives
240 %% to the user on their bare JID (i.e. `From.luser'),
241 %% while a MUC service might allow MAM queries to be sent to the room's bare JID
242 %% (i.e `To.luser').
243 -spec room_process_mam_iq(Acc :: mongoose_acc:t(),
244 From :: jid:jid(),
245 To :: jid:jid(),
246 IQ :: jlib:iq(),
247 Extra :: map()) -> {mongoose_acc:t(), jlib:iq() | ignore}.
248 room_process_mam_iq(Acc, From, To, IQ, #{host_type := HostType}) ->
249
:-(
mod_mam_utils:maybe_log_deprecation(IQ),
250
:-(
Action = mam_iq:action(IQ),
251
:-(
MucAction = action_to_muc_action(Action),
252
:-(
case check_action_allowed(HostType, Acc, To#jid.lserver, Action, MucAction, From, To) of
253 ok ->
254
:-(
case mod_mam_utils:wait_shaper(HostType, To#jid.lserver, MucAction, From) of
255 ok ->
256
:-(
handle_error_iq(Acc, HostType, To, Action,
257 handle_mam_iq(HostType, Action, From, To, IQ));
258 {error, max_delay_reached} ->
259
:-(
mongoose_metrics:update(HostType, modMucMamDroppedIQ, 1),
260
:-(
{Acc, return_max_delay_reached_error_iq(IQ)}
261 end;
262 {error, Reason} ->
263
:-(
?LOG_WARNING(#{what => action_not_allowed,
264 action => Action, acc => Acc, reason => Reason,
265
:-(
can_access_room => can_access_room(HostType, Acc, From, To)}),
266
:-(
{Acc, return_action_not_allowed_error_iq(Reason, IQ)}
267 end.
268
269 -spec forget_room(map(), host_type(), jid:lserver(), binary()) -> map().
270 forget_room(Acc, _HostType, MucServer, RoomName) ->
271 4 delete_archive(MucServer, RoomName),
272 4 Acc.
273
274 %% ----------------------------------------------------------------------
275 %% Internal functions
276
277 -spec check_action_allowed(host_type(), mongoose_acc:t(), jid:lserver(), mam_iq:action(), muc_action(),
278 jid:jid(), jid:jid()) -> ok | {error, binary()}.
279 check_action_allowed(HostType, Acc, Domain, Action, MucAction, From, To) ->
280
:-(
case acl:match_rule(HostType, Domain, MucAction, From, default) of
281
:-(
allow -> ok;
282
:-(
deny -> {false, <<"Blocked by service policy.">>};
283
:-(
default -> check_room_action_allowed_by_default(HostType, Acc, Action, From, To)
284 end.
285
286 -spec action_to_muc_action(mam_iq:action()) -> atom().
287 action_to_muc_action(Action) ->
288
:-(
list_to_atom("muc_" ++ atom_to_list(Action)).
289
290 -spec check_room_action_allowed_by_default(HostType :: host_type(),
291 Acc :: mongoose_acc:t(),
292 Action :: mam_iq:action(),
293 From :: jid:jid(),
294 To :: jid:jid()) -> ok | {error, binary()}.
295 check_room_action_allowed_by_default(HostType, Acc, Action, From, To) ->
296
:-(
case mam_iq:action_type(Action) of
297 set ->
298
:-(
case is_room_owner(HostType, Acc, From, To) of
299
:-(
true -> ok;
300
:-(
false -> {error, <<"Not a room owner.">>}
301 end;
302 get ->
303
:-(
case can_access_room(HostType, Acc, From, To) of
304
:-(
true -> ok;
305
:-(
false -> {error, <<"Not allowed to enter the room.">>}
306 end
307 end.
308
309 -spec is_room_owner(HostType :: host_type(),
310 Acc :: mongoose_acc:t(),
311 UserJid :: jid:jid(),
312 RoomJid :: jid:jid()) -> boolean().
313 is_room_owner(HostType, Acc, UserJid, RoomJid) ->
314
:-(
mongoose_hooks:is_muc_room_owner(HostType, Acc, UserJid, RoomJid).
315
316 %% @doc Return true if user element should be removed from results
317 -spec is_user_identity_hidden(HostType :: host_type(),
318 UserJid :: jid:jid(),
319 RoomJid :: jid:jid()) -> boolean().
320 is_user_identity_hidden(HostType, UserJid, RoomJid) ->
321
:-(
case mongoose_hooks:can_access_identity(HostType, RoomJid, UserJid) of
322
:-(
CanAccess when is_boolean(CanAccess) -> not CanAccess
323 end.
324
325 -spec can_access_room(HostType :: host_type(),
326 Acc :: mongoose_acc:t(),
327 UserJid :: jid:jid(),
328 RoomJid :: jid:jid()) -> boolean().
329 can_access_room(HostType, Acc, UserJid, RoomJid) ->
330
:-(
mongoose_hooks:can_access_room(HostType, Acc, RoomJid, UserJid).
331
332 -spec handle_mam_iq(HostType :: host_type(), mam_iq:action(),
333 From :: jid:jid(), jid:jid(), jlib:iq()) ->
334 jlib:iq() | {error, any(), jlib:iq()} | ignore.
335 handle_mam_iq(HostType, Action, From, To, IQ) ->
336
:-(
case Action of
337 mam_get_prefs ->
338
:-(
handle_get_prefs(HostType, To, IQ);
339 mam_set_prefs ->
340
:-(
handle_set_prefs(HostType, To, IQ);
341 mam_set_message_form ->
342
:-(
handle_set_message_form(HostType, From, To, IQ);
343 mam_get_message_form ->
344
:-(
handle_get_message_form(HostType, From, To, IQ)
345 end.
346
347 -spec handle_set_prefs(host_type(), jid:jid(), jlib:iq()) ->
348 jlib:iq() | {error, any(), jlib:iq()}.
349 handle_set_prefs(HostType, ArcJID = #jid{},
350 IQ = #iq{sub_el = PrefsEl}) ->
351
:-(
{DefaultMode, AlwaysJIDs, NeverJIDs} = parse_prefs(PrefsEl),
352
:-(
?LOG_DEBUG(#{what => mam_muc_set_prefs, archive_jid => ArcJID,
353 default_mode => DefaultMode,
354
:-(
always_jids => AlwaysJIDs, never_jids => NeverJIDs, iq => IQ}),
355
:-(
ArcID = archive_id_int(HostType, ArcJID),
356
:-(
Res = set_prefs(HostType, ArcID, ArcJID, DefaultMode, AlwaysJIDs, NeverJIDs),
357
:-(
handle_set_prefs_result(Res, DefaultMode, AlwaysJIDs, NeverJIDs, IQ).
358
359 handle_set_prefs_result(ok, DefaultMode, AlwaysJIDs, NeverJIDs, IQ) ->
360
:-(
ResultPrefsEl = result_prefs(DefaultMode, AlwaysJIDs, NeverJIDs, IQ#iq.xmlns),
361
:-(
IQ#iq{type = result, sub_el = [ResultPrefsEl]};
362 handle_set_prefs_result({error, Reason},
363 _DefaultMode, _AlwaysJIDs, _NeverJIDs, IQ) ->
364
:-(
return_error_iq(IQ, Reason).
365
366 -spec handle_get_prefs(host_type(), jid:jid(), jlib:iq()) ->
367 jlib:iq() | {error, any(), jlib:iq()}.
368 handle_get_prefs(HostType, ArcJID=#jid{}, IQ=#iq{}) ->
369
:-(
ArcID = archive_id_int(HostType, ArcJID),
370
:-(
Res = get_prefs(HostType, ArcID, ArcJID, always),
371
:-(
handle_get_prefs_result(ArcJID, Res, IQ).
372
373 handle_get_prefs_result(ArcJID, {DefaultMode, AlwaysJIDs, NeverJIDs}, IQ) ->
374
:-(
?LOG_DEBUG(#{what => mam_muc_get_prefs_result, archive_jid => ArcJID,
375 default_mode => DefaultMode,
376
:-(
always_jids => AlwaysJIDs, never_jids => NeverJIDs, iq => IQ}),
377
:-(
ResultPrefsEl = result_prefs(DefaultMode, AlwaysJIDs, NeverJIDs, IQ#iq.xmlns),
378
:-(
IQ#iq{type = result, sub_el = [ResultPrefsEl]};
379 handle_get_prefs_result(_ArcJID, {error, Reason}, IQ) ->
380
:-(
return_error_iq(IQ, Reason).
381
382 -spec handle_set_message_form(HostType :: host_type(),
383 From :: jid:jid(), ArcJID :: jid:jid(),
384 IQ :: jlib:iq()) ->
385 jlib:iq() | ignore | {error, term(), jlib:iq()}.
386 handle_set_message_form(HostType, #jid{} = From, #jid{} = ArcJID, IQ) ->
387
:-(
ArcID = archive_id_int(HostType, ArcJID),
388
:-(
ResLimit = mod_mam_params:max_result_limit(?MODULE, HostType),
389
:-(
DefLimit = mod_mam_params:default_result_limit(?MODULE, HostType),
390
:-(
ExtMod = mod_mam_params:extra_params_module(?MODULE, HostType),
391
:-(
Params0 = mam_iq:form_to_lookup_params(IQ, ResLimit, DefLimit, ExtMod),
392
:-(
Params = mam_iq:lookup_params_with_archive_details(Params0, ArcID, ArcJID, From),
393
:-(
Result = lookup_messages(HostType, Params),
394
:-(
handle_lookup_result(Result, HostType, From, IQ, Params).
395
396 -spec handle_lookup_result({ok, mod_mam:lookup_result()} | {error, term()},
397 host_type(), jid:jid(), jlib:iq(), map()) ->
398 jlib:iq() | ignore | {error, term(), jlib:iq()}.
399 handle_lookup_result(Result, HostType, From, IQ, #{owner_jid := ArcJID} = Params) ->
400
:-(
case Result of
401 {error, Reason} ->
402
:-(
report_issue(Reason, mam_muc_lookup_failed, ArcJID, IQ),
403
:-(
return_error_iq(IQ, Reason);
404 {ok, Res} ->
405
:-(
send_messages_and_iq_result(Res, HostType, From, IQ, Params)
406 end.
407
408 send_messages_and_iq_result({TotalCount, Offset, MessageRows}, HostType, From,
409 #iq{xmlns = MamNs, sub_el = QueryEl} = IQ,
410 #{owner_jid := ArcJID} = Params) ->
411 %% Forward messages
412
:-(
QueryID = exml_query:attr(QueryEl, <<"queryid">>, <<>>),
413
:-(
{FirstMessID, LastMessID} = forward_messages(HostType, From, ArcJID, MamNs,
414 QueryID, MessageRows, true),
415
416 %% Make fin iq
417
:-(
IsComplete = is_complete_result_page(TotalCount, Offset, MessageRows, Params),
418
:-(
IsStable = true,
419
:-(
ResultSetEl = result_set(FirstMessID, LastMessID, Offset, TotalCount),
420
:-(
ExtFinMod = mod_mam_params:extra_fin_element_module(?MODULE, HostType),
421
:-(
FinElem = make_fin_element(HostType, Params, IQ#iq.xmlns, IsComplete, IsStable, ResultSetEl, ExtFinMod),
422
:-(
IQ#iq{type = result, sub_el = [FinElem]}.
423
424 forward_messages(HostType, From, ArcJID, MamNs, QueryID, MessageRows, SetClientNs) ->
425 %% Forward messages
426
:-(
{FirstMessID, LastMessID, HideUser} =
427 case MessageRows of
428
:-(
[] -> {undefined, undefined, undefined};
429
:-(
[_ | _] -> {message_row_to_ext_id(hd(MessageRows)),
430 message_row_to_ext_id(lists:last(MessageRows)),
431 is_user_identity_hidden(HostType, From, ArcJID)}
432 end,
433
:-(
SendModule = mod_mam_params:send_message_mod(?MODULE, HostType),
434
:-(
[send_message(SendModule, Row, ArcJID, From,
435 message_row_to_xml(MamNs, From, HideUser, SetClientNs, Row,
436 QueryID))
437
:-(
|| Row <- MessageRows],
438
:-(
{FirstMessID, LastMessID}.
439
440 send_message(SendModule, Row, ArcJID, From, Packet) ->
441
:-(
mam_send_message:call_send_message(SendModule, Row, ArcJID, From, Packet).
442
443 -spec handle_get_message_form(host_type(), jid:jid(), jid:jid(), jlib:iq()) ->
444 jlib:iq().
445 handle_get_message_form(HostType,
446 _From = #jid{}, _ArcJID = #jid{}, IQ = #iq{}) ->
447
:-(
return_message_form_iq(HostType, IQ).
448
449 %% ----------------------------------------------------------------------
450 %% Backend wrappers
451
452 -spec archive_id_int(HostType :: host_type(), ArcJID :: jid:jid()) ->
453 integer() | undefined.
454 archive_id_int(HostType, ArcJID = #jid{}) ->
455 4 mongoose_hooks:mam_muc_archive_id(HostType, ArcJID).
456
457 -spec archive_size(HostType :: host_type(), ArcID :: mod_mam:archive_id(),
458 ArcJID ::jid:jid()) -> non_neg_integer().
459 archive_size(HostType, ArcID, ArcJID = #jid{}) ->
460
:-(
mongoose_hooks:mam_muc_archive_size(HostType, ArcID, ArcJID).
461
462 -spec get_behaviour(HostType :: host_type(), ArcID :: mod_mam:archive_id(),
463 LocJID :: jid:jid(), RemJID :: jid:jid()) -> any().
464 get_behaviour(HostType, ArcID, LocJID = #jid{}, RemJID = #jid{}) ->
465
:-(
mongoose_hooks:mam_muc_get_behaviour(HostType, ArcID, LocJID, RemJID).
466
467 -spec set_prefs(HostType :: host_type(), ArcID :: mod_mam:archive_id(),
468 ArcJID :: jid:jid(), DefaultMode :: mod_mam:archive_behaviour(),
469 AlwaysJIDs :: [jid:literal_jid()],
470 NeverJIDs :: [jid:literal_jid()]) -> any().
471 set_prefs(HostType, ArcID, ArcJID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
472
:-(
mongoose_hooks:mam_muc_set_prefs(HostType, ArcID, ArcJID, DefaultMode,
473 AlwaysJIDs, NeverJIDs).
474
475 %% @doc Load settings from the database.
476 -spec get_prefs(HostType :: host_type(), ArcID :: mod_mam:archive_id(),
477 ArcJID :: jid:jid(), GlobalDefaultMode :: mod_mam:archive_behaviour())
478 -> mod_mam:preference() | {error, Reason :: term()}.
479 get_prefs(HostType, ArcID, ArcJID, GlobalDefaultMode) ->
480
:-(
mongoose_hooks:mam_muc_get_prefs(HostType, GlobalDefaultMode, ArcID, ArcJID).
481
482 -spec remove_archive(host_type(), mod_mam:archive_id() | undefined,
483 jid:jid()) -> ok.
484 remove_archive(HostType, ArcID, ArcJID = #jid{}) ->
485 4 mongoose_hooks:mam_muc_remove_archive(HostType, ArcID, ArcJID),
486 4 ok.
487
488 %% See description in mod_mam.
489 -spec lookup_messages(HostType :: host_type(), Params :: map()) ->
490 {ok, mod_mam:lookup_result()}
491 | {error, 'policy-violation'}
492 | {error, Reason :: term()}.
493 lookup_messages(HostType, Params) ->
494
:-(
Result = lookup_messages_without_policy_violation_check(HostType, Params),
495 %% If a query returns a number of stanzas greater than this limit and the
496 %% client did not specify a limit using RSM then the server should return
497 %% a policy-violation error to the client.
498
:-(
mod_mam_utils:check_result_for_policy_violation(Params, Result).
499
500 lookup_messages_without_policy_violation_check(HostType,
501 #{search_text := SearchText} = Params) ->
502
:-(
case SearchText /= undefined andalso
503
:-(
not mod_mam_params:has_full_text_search(?MODULE, HostType) of
504 true -> %% Use of disabled full text search
505
:-(
{error, 'not-supported'};
506 false ->
507
:-(
mongoose_hooks:mam_muc_lookup_messages(HostType, Params)
508 end.
509
510 archive_message_for_ct(Params = #{local_jid := RoomJid}) ->
511
:-(
HostType = mod_muc_light_utils:room_jid_to_host_type(RoomJid),
512
:-(
archive_message(HostType, Params).
513
514 -spec archive_message(host_type(), mod_mam:archive_message_params()) -> ok | {error, timeout}.
515 archive_message(HostType, Params) ->
516
:-(
mongoose_hooks:mam_muc_archive_message(HostType, Params).
517
518 %% ----------------------------------------------------------------------
519 %% Helpers
520
521 -spec message_row_to_xml(binary(), jid:jid(), boolean(), boolean(), row(), binary() | undefined) ->
522 exml:element().
523 message_row_to_xml(MamNs, ReceiverJID, HideUser, SetClientNs,
524 #{id := MessID, jid := SrcJID, packet := Packet}, QueryID) ->
525
:-(
{Microseconds, _NodeMessID} = decode_compact_uuid(MessID),
526
:-(
TS = calendar:system_time_to_rfc3339(erlang:convert_time_unit(Microseconds, microsecond, second), [{offset, "Z"}]),
527
:-(
BExtMessID = mess_id_to_external_binary(MessID),
528
:-(
Packet1 = maybe_delete_x_user_element(HideUser, ReceiverJID, Packet),
529
:-(
Packet2 = mod_mam_utils:maybe_set_client_xmlns(SetClientNs, Packet1),
530
:-(
Packet3 = replace_from_to_attributes(SrcJID, Packet2),
531
:-(
wrap_message(MamNs, Packet3, QueryID, BExtMessID, TS, SrcJID).
532
533 maybe_delete_x_user_element(true, ReceiverJID, Packet) ->
534
:-(
PacketJID = packet_to_x_user_jid(Packet),
535
:-(
case jid:are_bare_equal(ReceiverJID, PacketJID) of
536 false ->
537
:-(
delete_x_user_element(Packet);
538 true -> %% expose identity for user's own messages
539
:-(
Packet
540 end;
541 maybe_delete_x_user_element(false, _ReceiverJID, Packet) ->
542
:-(
Packet.
543
544 %% From XEP-0313:
545 %% When sending out the archives to a requesting client, the 'to' of the
546 %% forwarded stanza MUST be empty, and the 'from' MUST be the occupant JID
547 %% of the sender of the archived message.
548 %% However, Smack crashes if 'to' is present, so it is removed.
549 replace_from_to_attributes(SrcJID, Packet = #xmlel{attrs = Attrs}) ->
550
:-(
NewAttrs = jlib:replace_from_to_attrs(jid:to_binary(SrcJID), undefined, Attrs),
551
:-(
Packet#xmlel{attrs = NewAttrs}.
552
553 -spec message_row_to_ext_id(row()) -> binary().
554 message_row_to_ext_id(#{id := MessID}) ->
555
:-(
mess_id_to_external_binary(MessID).
556
557 -spec handle_error_iq(mongoose_acc:t(), host_type(), jid:jid(), atom(),
558 {error, term(), jlib:iq()} | jlib:iq() | ignore) -> {mongoose_acc:t(), jlib:iq() | ignore}.
559 handle_error_iq(Acc, HostType, _To, _Action, {error, _Reason, IQ}) ->
560
:-(
mongoose_metrics:update(HostType, modMucMamDroppedIQ, 1),
561
:-(
{Acc, IQ};
562 handle_error_iq(Acc, _HostType, _To, _Action, IQ) ->
563
:-(
{Acc, IQ}.
564
565 return_error_iq(IQ, {Reason, {stacktrace, _Stacktrace}}) ->
566
:-(
return_error_iq(IQ, Reason);
567 return_error_iq(IQ, timeout) ->
568
:-(
{error, timeout, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:service_unavailable(<<"en">>, <<"Timeout in mod_mam_muc">>)]}};
569 return_error_iq(IQ, item_not_found) ->
570
:-(
{error, item_not_found, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:item_not_found()]}};
571 return_error_iq(IQ, not_implemented) ->
572
:-(
{error, not_implemented, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:feature_not_implemented(<<"en">>, <<"From mod_mam_muc">>)]}};
573 return_error_iq(IQ, missing_with_jid) ->
574
:-(
Error = mongoose_xmpp_errors:bad_request(<<"en">>,
575 <<"Limited set of queries allowed in the conversation mode.",
576 "Missing with_jid filter">>),
577
:-(
{error, bad_request, IQ#iq{type = error, sub_el = [Error]}};
578 return_error_iq(IQ, Reason) ->
579
:-(
{error, Reason, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:internal_server_error()]}}.
580
581 -spec return_action_not_allowed_error_iq(Reason :: binary(), jlib:iq()) -> jlib:iq().
582 return_action_not_allowed_error_iq(Reason, IQ) ->
583
:-(
ErrorEl = jlib:stanza_errort(<<"">>, <<"cancel">>, <<"not-allowed">>,
584 <<"en">>, <<"The action is not allowed. ", Reason/binary>>),
585
:-(
IQ#iq{type = error, sub_el = [ErrorEl]}.
586
587 -spec return_max_delay_reached_error_iq(jlib:iq()) -> jlib:iq().
588 return_max_delay_reached_error_iq(IQ) ->
589 %% Message not found.
590
:-(
ErrorEl = mongoose_xmpp_errors:resource_constraint(
591 <<"en">>, <<"The action is cancelled because of flooding.">>),
592
:-(
IQ#iq{type = error, sub_el = [ErrorEl]}.
593
594 return_message_form_iq(HostType, IQ) ->
595
:-(
IQ#iq{type = result, sub_el = [message_form(?MODULE, HostType, IQ#iq.xmlns)]}.
596
597 % the stacktrace is a big lie
598 report_issue({Reason, {stacktrace, Stacktrace}}, Issue, ArcJID, IQ) ->
599
:-(
report_issue(Reason, Stacktrace, Issue, ArcJID, IQ);
600 report_issue(Reason, Issue, ArcJID, IQ) ->
601
:-(
report_issue(Reason, [], Issue, ArcJID, IQ).
602
603 report_issue(item_not_found, _Stacktrace, _Issue, _ArcJID, _IQ) ->
604
:-(
expected;
605 report_issue(missing_with_jid, _Stacktrace, _Issue, _ArcJID, _IQ) ->
606
:-(
expected;
607 report_issue(not_implemented, _Stacktrace, _Issue, _ArcJID, _IQ) ->
608
:-(
expected;
609 report_issue(timeout, _Stacktrace, _Issue, _ArcJID, _IQ) ->
610
:-(
expected;
611 report_issue(Reason, Stacktrace, Issue, #jid{lserver = LServer, luser = LUser}, IQ) ->
612
:-(
?LOG_ERROR(#{what => mam_muc_error, issue => Issue, reason => Reason,
613
:-(
user => LUser, server => LServer, iq => IQ, stacktrace => Stacktrace}).
614
615 -spec is_archivable_message(HostType :: host_type(),
616 Dir :: incoming | outgoing,
617 Packet :: exml:element()) -> boolean().
618 is_archivable_message(HostType, Dir, Packet) ->
619
:-(
{M, F} = mod_mam_params:is_archivable_message_fun(?MODULE, HostType),
620
:-(
ArchiveChatMarkers = mod_mam_params:archive_chat_markers(?MODULE, HostType),
621
:-(
erlang:apply(M, F, [?MODULE, Dir, Packet, ArchiveChatMarkers]).
622
623 -spec hooks(host_type()) -> [ejabberd_hooks:hook()].
624 hooks(HostType) ->
625 12 [{disco_muc_features, HostType, ?MODULE, disco_muc_features, 99},
626 {filter_room_packet, HostType, ?MODULE, filter_room_packet, 60},
627 {forget_room, HostType, ?MODULE, forget_room, 90},
628 {get_personal_data, HostType, ?MODULE, get_personal_data, 50}
629 | mongoose_metrics_mam_hooks:get_mam_muc_hooks(HostType)].
630
631 add_iq_handlers(HostType, Opts) ->
632 6 IQDisc = gen_mod:get_opt(iqdisc, Opts, parallel),
633 6 MUCSubdomainPattern = gen_mod:get_module_opt(HostType, ?MODULE, host,
634 mod_muc:default_host()),
635
636 6 gen_iq_handler:add_iq_handler_for_subdomain(HostType, MUCSubdomainPattern,
637 ?NS_MAM_04, mod_muc_iq,
638 fun ?MODULE:room_process_mam_iq/5,
639 #{}, IQDisc),
640 6 gen_iq_handler:add_iq_handler_for_subdomain(HostType, MUCSubdomainPattern,
641 ?NS_MAM_06, mod_muc_iq,
642 fun ?MODULE:room_process_mam_iq/5,
643 #{}, IQDisc),
644 6 ok.
645
646 remove_iq_handlers(HostType) ->
647 6 MUCSubdomainPattern = gen_mod:get_module_opt(HostType, ?MODULE, host,
648 mod_muc:default_host()),
649 6 gen_iq_handler:remove_iq_handler_for_subdomain(HostType, MUCSubdomainPattern,
650 ?NS_MAM_04, mod_muc_iq),
651 6 gen_iq_handler:remove_iq_handler_for_subdomain(HostType, MUCSubdomainPattern,
652 ?NS_MAM_06, mod_muc_iq),
653 6 ok.
654
655 ensure_metrics(HostType) ->
656 6 lists:foreach(fun(Name) ->
657 54 mongoose_metrics:ensure_metric(HostType, Name, spiral)
658 end,
659 spirals()).
660
661 spirals() ->
662 6 [modMucMamPrefsSets,
663 modMucMamPrefsGets,
664 modMucMamArchiveRemoved,
665 modMucMamLookups,
666 modMucMamForwarded,
667 modMucMamArchived,
668 modMucMamFlushed,
669 modMucMamDropped,
670 modMucMamDroppedIQ].
Line Hits Source