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