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