./ct_report/coverage/mod_mam_utils.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Uvarov Michael <arcusfelis@gmail.com>
3 %%% @copyright (C) 2013, Uvarov Michael
4 %%% @doc General functions for MAM.
5 %%% @end
6 %%%-------------------------------------------------------------------
7 -module(mod_mam_utils).
8 %% Time
9 -export([maybe_microseconds/1]).
10
11 %% UID
12 -export([get_or_generate_mam_id/1,
13 generate_message_id/1,
14 encode_compact_uuid/2,
15 decode_compact_uuid/1,
16 mess_id_to_external_binary/1,
17 external_binary_to_mess_id/1,
18 wrapper_id/0]).
19
20 %% XML
21 -export([maybe_add_arcid_elems/4,
22 maybe_log_deprecation/1,
23 is_arcid_elem_for/3,
24 replace_arcid_elem/4,
25 replace_x_user_element/4,
26 append_arcid_elem/4,
27 delete_arcid_elem/3,
28 delete_x_user_element/1,
29 packet_to_x_user_jid/1,
30 get_one_of_path/2,
31 get_one_of_path/3,
32 is_archivable_message/4,
33 has_message_retraction/2,
34 get_retract_id/2,
35 get_origin_id/1,
36 tombstone/2,
37 wrap_message/6,
38 wrap_message/7,
39 result_set/4,
40 result_query/2,
41 result_prefs/4,
42 make_fin_element/7,
43 parse_prefs/1,
44 form_borders_decode/1,
45 form_decode_optimizations/1,
46 is_mam_result_message/1,
47 features/2]).
48
49 %% Forms
50 -export([
51 message_form/3,
52 form_to_text/1
53 ]).
54
55 %% Text search
56 -export([
57 normalize_search_text/1,
58 normalize_search_text/2,
59 packet_to_search_body/2,
60 has_full_text_search/2
61 ]).
62
63 %% JID serialization
64 -export([jid_to_opt_binary/2,
65 expand_minified_jid/2]).
66
67 %% Other
68 -export([maybe_integer/2,
69 maybe_min/2,
70 maybe_max/2,
71 maybe_last/1,
72 apply_start_border/2,
73 apply_end_border/2,
74 bare_jid/1,
75 full_jid/1,
76 calculate_msg_id_borders/3,
77 calculate_msg_id_borders/4,
78 maybe_encode_compact_uuid/2,
79 wait_shaper/4,
80 check_for_item_not_found/3,
81 is_mam_muc_enabled/2]).
82
83 %% Ejabberd
84 -export([send_message/4,
85 maybe_set_client_xmlns/2,
86 is_jid_in_user_roster/3]).
87
88 %% Shared logic
89 -export([check_result_for_policy_violation/2,
90 lookup/3,
91 incremental_delete_domain/5,
92 db_message_codec/2, db_jid_codec/2]).
93
94 -callback extra_fin_element(mongooseim:host_type(),
95 mam_iq:lookup_params(),
96 exml:element()) -> exml:element().
97
98 -ignore_xref([behaviour_info/1, append_arcid_elem/4, delete_arcid_elem/3,
99 get_one_of_path/3, is_arcid_elem_for/3, maybe_encode_compact_uuid/2,
100 maybe_last/1, result_query/2, send_message/4, wrap_message/7, wrapper_id/0]).
101
102 %-define(MAM_INLINE_UTILS, true).
103
104 -ifdef(MAM_INLINE_UTILS).
105 -compile({inline, [
106 is_valid_message/4,
107 is_valid_message_type/3,
108 encode_compact_uuid/2,
109 get_one_of_path/3,
110 delay/2,
111 forwarded/3,
112 result/4,
113 valid_behavior/1]}).
114 -endif.
115
116 -include("jlib.hrl").
117 -include_lib("exml/include/exml.hrl").
118
119 -ifdef(TEST).
120 -include_lib("eunit/include/eunit.hrl").
121 -export([is_valid_message/4]).
122 -endif.
123
124 -include("mod_mam.hrl").
125 -include("mongoose_rsm.hrl").
126 -include("mongoose_ns.hrl").
127
128 -define(MAYBE_BIN(X), (is_binary(X) orelse (X) =:= undefined)).
129
130 -export_type([direction/0, retraction_id/0, retraction_info/0]).
131
132 %% ----------------------------------------------------------------------
133 %% Datetime types
134 -type ne_binary() :: <<_:8, _:_*8>>.
135 -type iso8601_datetime_binary() :: ne_binary().
136 %% Microseconds from 01.01.1970
137 -type unix_timestamp() :: mod_mam:unix_timestamp().
138
139 -type archive_behaviour() :: mod_mam:archive_behaviour().
140 -type archive_behaviour_bin() :: binary(). % `<<"roster">> | <<"always">> | <<"never">>'.
141
142 -type direction() :: incoming | outgoing.
143 -type retraction_id() :: {origin_id | stanza_id, binary()}.
144 -type retraction_info() :: #{retract_on := origin_id | stanza_id,
145 packet := exml:element(),
146 message_id := mod_mam:message_id(),
147 origin_id := null | binary()}.
148
149 %% -----------------------------------------------------------------------
150 %% Time
151
152 %% @doc Return a unix timestamp in microseconds.
153 %%
154 %% "maybe" means, that the function may return `undefined'.
155 %% @end
156 -spec maybe_microseconds(iso8601_datetime_binary()) -> unix_timestamp();
157 (<<>>) -> undefined.
158
:-(
maybe_microseconds(<<>>) -> undefined;
159 maybe_microseconds(ISODateTime) ->
160 201 try calendar:rfc3339_to_system_time(binary_to_list(ISODateTime), [{unit, microsecond}])
161 12 catch error:_Error -> undefined
162 end.
163
164 %% -----------------------------------------------------------------------
165 %% UID
166
167 -spec get_or_generate_mam_id(mongoose_acc:t()) -> integer().
168 get_or_generate_mam_id(Acc) ->
169 2169 case mongoose_acc:get(mam, mam_id, undefined, Acc) of
170 undefined ->
171 2148 CandidateStamp = mongoose_acc:timestamp(Acc),
172 2148 generate_message_id(CandidateStamp);
173 ExtMessId ->
174 21 mod_mam_utils:external_binary_to_mess_id(ExtMessId)
175 end.
176
177 -spec generate_message_id(integer()) -> integer().
178 generate_message_id(CandidateStamp) ->
179 2688 NodeNum = mongoose_node_num:node_num(),
180 2688 UniqueStamp = mongoose_mam_id:next_unique(CandidateStamp),
181 2688 encode_compact_uuid(UniqueStamp, NodeNum).
182
183 %% @doc Create a message ID (UID).
184 %%
185 %% It removes a leading 0 from 64-bit binary representation.
186 %% It puts node id as a last byte.
187 %% The maximum date, that can be encoded is `{{4253, 5, 31}, {22, 20, 37}}'.
188 -spec encode_compact_uuid(integer(), integer()) -> integer().
189 encode_compact_uuid(Microseconds, NodeNum)
190 when is_integer(Microseconds), is_integer(NodeNum) ->
191 8081 (Microseconds bsl 8) + NodeNum.
192
193
194 %% @doc Extract date and node id from a message id.
195 -spec decode_compact_uuid(integer()) -> {integer(), byte()}.
196 decode_compact_uuid(Id) ->
197 4222 Microseconds = Id bsr 8,
198 4222 NodeNum = Id band 255,
199 4222 {Microseconds, NodeNum}.
200
201
202 %% @doc Encode a message ID to pass it to the user.
203 -spec mess_id_to_external_binary(integer()) -> binary().
204 mess_id_to_external_binary(MessID) when is_integer(MessID) ->
205 8681 integer_to_binary(MessID, 32).
206
207 %% @doc Decode a message ID received from the user.
208 -spec external_binary_to_mess_id(binary()) -> integer().
209 external_binary_to_mess_id(BExtMessID) when is_binary(BExtMessID) ->
210 518 try binary_to_integer(BExtMessID, 32)
211 21 catch error:badarg -> throw(invalid_stanza_id)
212 end.
213
214 %% -----------------------------------------------------------------------
215 %% XML
216
217 -spec maybe_add_arcid_elems(To :: jid:simple_jid() | jid:jid(),
218 MessID :: binary(), Packet :: exml:element(),
219 AddStanzaid :: boolean()) ->
220 AlteredPacket :: exml:element().
221 maybe_add_arcid_elems(To, MessID, Packet, AddStanzaid) ->
222 1606 BareTo = jid:to_bare_binary(To),
223 1606 case AddStanzaid of
224 true ->
225 1592 replace_arcid_elem(<<"stanza-id">>, BareTo, MessID, Packet);
226 14 _ -> Packet
227 end.
228
229 maybe_log_deprecation(_IQ) ->
230 1758 ok. %% May be reused for future MAM versions.
231
232 %% @doc Return true, if the first element points on `By'.
233 -spec is_arcid_elem_for(ElemName :: binary(), exml:element(), By :: binary()) -> boolean().
234 is_arcid_elem_for(<<"archived">>, #xmlel{name = <<"archived">>, attrs=As}, By) ->
235
:-(
lists:member({<<"by">>, By}, As);
236 is_arcid_elem_for(<<"stanza-id">>, #xmlel{name = <<"stanza-id">>, attrs=As}, By) ->
237 29 lists:member({<<"by">>, By}, As) andalso
238
:-(
lists:member({<<"xmlns">>, ?NS_STANZAID}, As);
239 is_arcid_elem_for(_, _, _) ->
240 1828 false.
241
242 -spec replace_arcid_elem(ElemName :: binary(), By :: binary(), Id :: binary(),
243 Packet :: exml:element()) -> exml:element().
244 replace_arcid_elem(ElemName, By, Id, Packet) ->
245 1599 append_arcid_elem(ElemName, By, Id,
246 delete_arcid_elem(ElemName, By, Packet)).
247
248 -spec append_arcid_elem(ElemName :: binary(), By :: binary(), Id :: binary(),
249 Packet :: exml:element()) ->exml:element().
250 append_arcid_elem(<<"stanza-id">>, By, Id, Packet) ->
251 1599 Archived = #xmlel{
252 name = <<"stanza-id">>,
253 attrs=[{<<"by">>, By}, {<<"id">>, Id}, {<<"xmlns">>, ?NS_STANZAID}]},
254 1599 xml:append_subtags(Packet, [Archived]);
255 append_arcid_elem(ElemName, By, Id, Packet) ->
256
:-(
Archived = #xmlel{
257 name = ElemName,
258 attrs=[{<<"by">>, By}, {<<"id">>, Id}]},
259
:-(
xml:append_subtags(Packet, [Archived]).
260
261 -spec delete_arcid_elem(ElemName :: binary(), By :: binary(), exml:element()) -> exml:element().
262 delete_arcid_elem(ElemName, By, Packet=#xmlel{children=Cs}) ->
263 1599 Packet#xmlel{children=[C || C <- Cs, not is_arcid_elem_for(ElemName, C, By)]}.
264
265
266 is_x_user_element(#xmlel{name = <<"x">>, attrs = As}) ->
267 391 lists:member({<<"xmlns">>, ?NS_MUC_USER}, As);
268 is_x_user_element(_) ->
269 812 false.
270
271 -spec replace_x_user_element(FromJID :: jid:jid(), Role :: mod_muc:role(),
272 Affiliation :: mod_muc:affiliation(), exml:element()) -> exml:element().
273 replace_x_user_element(FromJID, Role, Affiliation, Packet) ->
274 540 append_x_user_element(FromJID, Role, Affiliation,
275 delete_x_user_element(Packet)).
276
277 append_x_user_element(FromJID, Role, Affiliation, Packet) ->
278 540 ItemElem = x_user_item(FromJID, Role, Affiliation),
279 540 X = #xmlel{
280 name = <<"x">>,
281 attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
282 children = [ItemElem]},
283 540 xml:append_subtags(Packet, [X]).
284
285 x_user_item(FromJID, Role, Affiliation) ->
286 540 #xmlel{
287 name = <<"item">>,
288 attrs = [{<<"affiliation">>, atom_to_binary(Affiliation, latin1)},
289 {<<"jid">>, jid:to_binary(FromJID)},
290 {<<"role">>, atom_to_binary(Role, latin1)}]}.
291
292 -spec delete_x_user_element(exml:element()) -> exml:element().
293 delete_x_user_element(Packet=#xmlel{children=Cs}) ->
294 640 Packet#xmlel{children=[C || C <- Cs, not is_x_user_element(C)]}.
295
296 -spec packet_to_x_user_jid(exml:element()) -> jid:jid() | error | undefined.
297 packet_to_x_user_jid(#xmlel{children=Cs}) ->
298 122 case [C || C <- Cs, is_x_user_element(C)] of
299 14 [] -> undefined;
300 [X|_] ->
301 108 case exml_query:path(X, [{element, <<"item">>}, {attr, <<"jid">>}]) of
302
:-(
undefined -> undefined;
303 108 BinaryJid -> jid:from_binary(BinaryJid)
304 end
305 end.
306
307 -spec get_one_of_path(_, list(T)) -> T when T :: any().
308 get_one_of_path(Elem, List) ->
309 1247 get_one_of_path(Elem, List, <<>>).
310
311
312 -spec get_one_of_path(_, list(T), T) -> T when T :: any().
313 get_one_of_path(Elem, [H|T], Def) ->
314 1864 case exml_query:path(Elem, H) of
315 1234 undefined -> get_one_of_path(Elem, T, Def);
316 630 Val -> Val
317 end;
318 get_one_of_path(_Elem, [], Def) ->
319 617 Def.
320
321
322 %% @doc In order to be archived, the message must be of type "normal", "chat" or "groupchat".
323 %% It also must include a body or chat marker, as long as it doesn't include
324 %% "result", "delay" or "no-store" elements.
325 %% @end
326 -spec is_archivable_message(module(), direction(), exml:element(), boolean()) -> boolean().
327 is_archivable_message(Mod, Dir, Packet=#xmlel{name = <<"message">>}, ArchiveChatMarkers) ->
328 6024 Type = exml_query:attr(Packet, <<"type">>, <<"normal">>),
329 6024 is_valid_message_type(Mod, Dir, Type) andalso
330 5864 is_valid_message(Mod, Dir, Packet, ArchiveChatMarkers);
331 is_archivable_message(_, _, _, _) ->
332 7901 false.
333
334 284 is_valid_message_type(_, _, <<"normal">>) -> true;
335 3067 is_valid_message_type(_, _, <<"chat">>) -> true;
336 1578 is_valid_message_type(mod_inbox, _, <<"groupchat">>) -> true;
337 935 is_valid_message_type(_, incoming, <<"groupchat">>) -> true;
338 160 is_valid_message_type(_, _, _) -> false.
339
340 is_valid_message(_Mod, _Dir, Packet, ArchiveChatMarkers) ->
341 5864 Body = exml_query:subelement(Packet, <<"body">>, false),
342 5864 ChatMarker = ArchiveChatMarkers
343 2634 andalso has_chat_marker(Packet),
344 5864 Retract = get_retract_id(Packet) =/= none,
345 %% Used in MAM
346 5864 Result = exml_query:subelement(Packet, <<"result">>, false),
347 %% Used in mod_offline
348 5864 Delay = exml_query:subelement(Packet, <<"delay">>, false),
349 %% Message Processing Hints (XEP-0334)
350 5864 NoStore = exml_query:path(Packet, [{element_with_ns, <<"no-store">>, ?NS_HINTS}], false),
351 %% Message Processing Hints (XEP-0334)
352 5864 Store = exml_query:path(Packet, [{element_with_ns, <<"store">>, ?NS_HINTS}], false),
353
354 5864 has_any([Store, Body, ChatMarker, Retract]) andalso not has_any([Result, Delay, NoStore]).
355
356 has_any(Elements) ->
357 11472 lists:any(fun(El) -> El =/= false end, Elements).
358
359 has_chat_marker(Packet) ->
360 2634 mongoose_chat_markers:has_chat_markers(Packet).
361
362 -spec get_retract_id(false, exml:element()) -> none;
363 (true, exml:element()) -> none | retraction_id().
364 get_retract_id(true = _Enabled, Packet) ->
365 6935 get_retract_id(Packet);
366 get_retract_id(false, _Packet) ->
367 28 none.
368
369 -spec get_retract_id(exml:element()) -> none | retraction_id().
370 get_retract_id(Packet) ->
371 12799 case exml_query:path(Packet, [{element_with_ns, <<"apply-to">>, ?NS_FASTEN}], none) of
372 12645 none -> none;
373 Fasten ->
374 154 case {exml_query:path(Fasten, [{element, <<"retract">>}, {attr, <<"xmlns">>}], none),
375 exml_query:path(Fasten, [{attr, <<"id">>}], none)} of
376
:-(
{none, _} -> none;
377 14 {_, none} -> none;
378 98 {?NS_RETRACT, OriginId} -> {origin_id, OriginId};
379 42 {?NS_ESL_RETRACT, StanzaId} -> {stanza_id, StanzaId}
380 end
381 end.
382
383 get_origin_id(Packet) ->
384 3003 exml_query:path(Packet, [{element_with_ns, <<"origin-id">>, ?NS_STANZAID},
385 {attr, <<"id">>}], none).
386
387 tombstone(RetractionInfo = #{packet := Packet}, LocJid) ->
388 42 Packet#xmlel{children = [retracted_element(RetractionInfo, LocJid)]}.
389
390 -spec retracted_element(retraction_info(), jid:jid()) -> exml:element().
391 retracted_element(#{retract_on := origin_id,
392 origin_id := OriginID}, _LocJid) ->
393 21 Timestamp = calendar:system_time_to_rfc3339(erlang:system_time(second), [{offset, "Z"}]),
394 21 #xmlel{name = <<"retracted">>,
395 attrs = [{<<"xmlns">>, ?NS_RETRACT},
396 {<<"stamp">>, list_to_binary(Timestamp)}],
397 children = [#xmlel{name = <<"origin-id">>,
398 attrs = [{<<"xmlns">>, ?NS_STANZAID},
399 {<<"id">>, OriginID}]}
400 ]};
401 retracted_element(#{retract_on := stanza_id,
402 message_id := MessID} = Env, LocJid) ->
403 21 Timestamp = calendar:system_time_to_rfc3339(erlang:system_time(second), [{offset, "Z"}]),
404 21 StanzaID = mod_mam_utils:mess_id_to_external_binary(MessID),
405 21 MaybeOriginId = maybe_append_origin_id(Env),
406 21 #xmlel{name = <<"retracted">>,
407 attrs = [{<<"xmlns">>, ?NS_ESL_RETRACT},
408 {<<"stamp">>, list_to_binary(Timestamp)}],
409 children = [#xmlel{name = <<"stanza-id">>,
410 attrs = [{<<"xmlns">>, ?NS_STANZAID},
411 {<<"id">>, StanzaID},
412 {<<"by">>, jid:to_bare_binary(LocJid)}]} |
413 MaybeOriginId
414 ]}.
415
416 -spec maybe_append_origin_id(retraction_info()) -> [exml:element()].
417 maybe_append_origin_id(#{origin_id := OriginID}) when is_binary(OriginID), <<>> =/= OriginID ->
418 21 [#xmlel{name = <<"origin-id">>, attrs = [{<<"xmlns">>, ?NS_STANZAID}, {<<"id">>, OriginID}]}];
419 maybe_append_origin_id(_) ->
420
:-(
[].
421
422 %% @doc Forms `<forwarded/>' element, according to the XEP.
423 -spec wrap_message(MamNs :: binary(), Packet :: exml:element(), QueryID :: binary(),
424 MessageUID :: term(), TS :: jlib:rfc3339_string(),
425 SrcJID :: jid:jid()) -> Wrapper :: exml:element().
426 wrap_message(MamNs, Packet, QueryID, MessageUID, TS, SrcJID) ->
427 4066 wrap_message(MamNs, Packet, QueryID, MessageUID, wrapper_id(), TS, SrcJID).
428
429 -spec wrap_message(MamNs :: binary(), Packet :: exml:element(), QueryID :: binary(),
430 MessageUID :: term(), WrapperI :: binary(),
431 TS :: jlib:rfc3339_string(),
432 SrcJID :: jid:jid()) -> Wrapper :: exml:element().
433 wrap_message(MamNs, Packet, QueryID, MessageUID, WrapperID, TS, SrcJID) ->
434 4066 #xmlel{ name = <<"message">>,
435 attrs = [{<<"id">>, WrapperID}],
436 children = [result(MamNs, QueryID, MessageUID,
437 [forwarded(Packet, TS, SrcJID)])] }.
438
439 -spec forwarded(exml:element(), jlib:rfc3339_string(), jid:jid())
440 -> exml:element().
441 forwarded(Packet, TS, SrcJID) ->
442 4066 #xmlel{
443 name = <<"forwarded">>,
444 attrs = [{<<"xmlns">>, ?NS_FORWARD}],
445 %% Two places to include SrcJID:
446 %% - delay.from - optional XEP-0297 (TODO: depricate adding it?)
447 %% - message.from - required XEP-0313
448 %% Also, mod_mam_muc will replace it again with SrcJID
449 children = [delay(TS, SrcJID), replace_from_attribute(SrcJID, Packet)]}.
450
451 -spec delay(jlib:rfc3339_string(), jid:jid()) -> exml:element().
452 delay(TS, SrcJID) ->
453 4066 jlib:timestamp_to_xml(TS, SrcJID, <<>>).
454
455 replace_from_attribute(From, Packet=#xmlel{attrs = Attrs}) ->
456 4066 Attrs1 = lists:keydelete(<<"from">>, 1, Attrs),
457 4066 Attrs2 = [{<<"from">>, jid:to_binary(From)} | Attrs1],
458 4066 Packet#xmlel{attrs = Attrs2}.
459
460 %% @doc Generates tag `<result />'.
461 %% This element will be added in each forwarded message.
462 -spec result(binary(), _, MessageUID :: binary(), Children :: [exml:element(), ...])
463 -> exml:element().
464 result(MamNs, QueryID, MessageUID, Children) when is_list(Children) ->
465 %% <result xmlns='urn:xmpp:mam:tmp' queryid='f27' id='28482-98726-73623' />
466 4066 #xmlel{
467 name = <<"result">>,
468 2932 attrs = [{<<"queryid">>, QueryID} || QueryID =/= undefined, QueryID =/= <<>>] ++
469 [{<<"xmlns">>, MamNs},
470 {<<"id">>, MessageUID}],
471 children = Children}.
472
473
474 %% @doc Generates `<set />' tag.
475 %%
476 %% This element will be added into "iq/query".
477 %% @end
478 -spec result_set(FirstId :: binary() | undefined,
479 LastId :: binary() | undefined,
480 FirstIndexI :: non_neg_integer() | undefined,
481 CountI :: non_neg_integer() | undefined) -> exml:element().
482 result_set(FirstId, LastId, undefined, undefined)
483 when ?MAYBE_BIN(FirstId), ?MAYBE_BIN(LastId) ->
484 %% Simple response
485 1075 FirstEl = [#xmlel{name = <<"first">>,
486 children = [#xmlcdata{content = FirstId}]
487 }
488 1075 || FirstId =/= undefined],
489 1075 LastEl = [#xmlel{name = <<"last">>,
490 children = [#xmlcdata{content = LastId}]
491 }
492 1075 || LastId =/= undefined],
493 1075 #xmlel{
494 name = <<"set">>,
495 attrs = [{<<"xmlns">>, ?NS_RSM}],
496 children = FirstEl ++ LastEl};
497 result_set(FirstId, LastId, FirstIndexI, CountI)
498 when ?MAYBE_BIN(FirstId), ?MAYBE_BIN(LastId) ->
499 1002 FirstEl = [#xmlel{name = <<"first">>,
500 attrs = [{<<"index">>, integer_to_binary(FirstIndexI)}],
501 children = [#xmlcdata{content = FirstId}]
502 }
503 1002 || FirstId =/= undefined],
504 1002 LastEl = [#xmlel{name = <<"last">>,
505 children = [#xmlcdata{content = LastId}]
506 }
507 1002 || LastId =/= undefined],
508 1002 CountEl = #xmlel{
509 name = <<"count">>,
510 children = [#xmlcdata{content = integer_to_binary(CountI)}]},
511 1002 #xmlel{
512 name = <<"set">>,
513 attrs = [{<<"xmlns">>, ?NS_RSM}],
514 children = FirstEl ++ LastEl ++ [CountEl]}.
515
516
517 -spec result_query(jlib:xmlcdata() | exml:element(), binary()) -> exml:element().
518 result_query(SetEl, Namespace) ->
519 28 #xmlel{
520 name = <<"query">>,
521 attrs = [{<<"xmlns">>, Namespace}],
522 children = [SetEl]}.
523
524 -spec result_prefs(DefaultMode :: archive_behaviour(),
525 AlwaysJIDs :: [jid:literal_jid()],
526 NeverJIDs :: [jid:literal_jid()],
527 Namespace :: binary()) -> exml:element().
528 result_prefs(DefaultMode, AlwaysJIDs, NeverJIDs, Namespace) ->
529 469 AlwaysEl = #xmlel{name = <<"always">>,
530 children = encode_jids(AlwaysJIDs)},
531 469 NeverEl = #xmlel{name = <<"never">>,
532 children = encode_jids(NeverJIDs)},
533 469 #xmlel{
534 name = <<"prefs">>,
535 attrs = [{<<"xmlns">>, Namespace},
536 {<<"default">>, atom_to_binary(DefaultMode, utf8)}],
537 children = [AlwaysEl, NeverEl]
538 }.
539
540
541 -spec encode_jids([binary() | string()]) -> [exml:element()].
542 encode_jids(JIDs) ->
543 938 [#xmlel{name = <<"jid">>, children = [#xmlcdata{content = JID}]}
544 938 || JID <- JIDs].
545
546
547 %% MAM v0.4.1 and above
548 -spec make_fin_element(mongooseim:host_type(),
549 mam_iq:lookup_params(),
550 binary(),
551 boolean(),
552 boolean(),
553 exml:element(),
554 module()) ->
555 exml:element().
556 make_fin_element(HostType, Params, MamNs, IsComplete, IsStable, ResultSetEl, ExtFinMod) ->
557 1177 FinEl = #xmlel{
558 name = <<"fin">>,
559 attrs = [{<<"xmlns">>, MamNs}]
560 834 ++ [{<<"complete">>, <<"true">>} || IsComplete]
561
:-(
++ [{<<"stable">>, <<"false">>} || not IsStable],
562 children = [ResultSetEl]},
563 1177 maybe_transform_fin_elem(ExtFinMod, HostType, Params, FinEl).
564
565 maybe_transform_fin_elem(undefined, _HostType, _Params, FinEl) ->
566 1177 FinEl;
567 maybe_transform_fin_elem(Module, HostType, Params, FinEl) ->
568
:-(
Module:extra_fin_element(HostType, Params, FinEl).
569
570 -spec parse_prefs(PrefsEl :: exml:element()) -> mod_mam:preference().
571 parse_prefs(El = #xmlel{ name = <<"prefs">> }) ->
572 308 Default = exml_query:attr(El, <<"default">>),
573 308 AlwaysJIDs = parse_jid_list(El, <<"always">>),
574 308 NeverJIDs = parse_jid_list(El, <<"never">>),
575 308 {valid_behavior(Default), AlwaysJIDs, NeverJIDs}.
576
577
578 -spec valid_behavior(archive_behaviour_bin()) -> archive_behaviour().
579 98 valid_behavior(<<"always">>) -> always;
580 98 valid_behavior(<<"never">>) -> never;
581 112 valid_behavior(<<"roster">>) -> roster.
582
583
584 -spec parse_jid_list(exml:element(), binary()) -> [jid:literal_jid()].
585 parse_jid_list(El, Name) ->
586 616 case exml_query:subelement(El, Name) of
587
:-(
undefined -> [];
588 #xmlel{children = JIDEls} ->
589 %% Ignore cdata between jid elements
590 616 MaybeJids = [binary_jid_to_lower(exml_query:cdata(JIDEl))
591 616 || JIDEl <- JIDEls, is_jid_element(JIDEl)],
592 616 skip_bad_jids(MaybeJids)
593 end.
594
595 is_jid_element(#xmlel{name = <<"jid">>}) ->
596 364 true;
597 is_jid_element(_) -> %% ignore cdata
598
:-(
false.
599
600 %% @doc Normalize JID to be used when comparing JIDs in DB
601 binary_jid_to_lower(BinJid) when is_binary(BinJid) ->
602 364 Jid = jid:from_binary(BinJid),
603 364 case jid:to_lower(Jid) of
604 error ->
605
:-(
error;
606 LowerJid ->
607 364 jid:to_binary(LowerJid)
608 end.
609
610 skip_bad_jids(MaybeJids) ->
611 616 [Jid || Jid <- MaybeJids, is_binary(Jid)].
612
613 -spec form_borders_decode(mongoose_data_forms:kv_map()) -> 'undefined' | mod_mam:borders().
614 form_borders_decode(KVs) ->
615 1247 AfterID = form_field_mess_id(KVs, <<"after_id">>),
616 1247 BeforeID = form_field_mess_id(KVs, <<"before_id">>),
617 1247 FromID = form_field_mess_id(KVs, <<"from_id">>),
618 1226 ToID = form_field_mess_id(KVs, <<"to_id">>),
619 1226 borders(AfterID, BeforeID, FromID, ToID).
620
621
622 -spec borders(AfterID :: 'undefined' | non_neg_integer(),
623 BeforeID :: 'undefined' | non_neg_integer(),
624 FromID :: 'undefined' | non_neg_integer(),
625 ToID :: 'undefined' | non_neg_integer()
626 ) -> 'undefined' | mod_mam:borders().
627 borders(undefined, undefined, undefined, undefined) ->
628 1142 undefined;
629 borders(AfterID, BeforeID, FromID, ToID) ->
630 84 #mam_borders{
631 after_id = AfterID,
632 before_id = BeforeID,
633 from_id = FromID,
634 to_id = ToID
635 }.
636
637 -spec form_field_mess_id(mongoose_data_forms:kv_map(), binary()) -> 'undefined' | integer().
638 form_field_mess_id(KVs, Name) ->
639 4967 case KVs of
640 126 #{Name := [BExtMessID]} -> external_binary_to_mess_id(BExtMessID);
641 4841 #{} -> undefined
642 end.
643
644 -spec form_decode_optimizations(mongoose_data_forms:kv_map()) -> boolean().
645 form_decode_optimizations(#{<<"simple">> := [<<"true">>]}) ->
646 168 true;
647 form_decode_optimizations(#{}) ->
648 1051 false.
649
650 is_mam_result_message(Packet = #xmlel{name = <<"message">>}) ->
651 10 Ns = maybe_get_result_namespace(Packet),
652 10 is_mam_namespace(Ns);
653 is_mam_result_message(_) ->
654
:-(
false.
655
656 maybe_get_result_namespace(Packet) ->
657 10 exml_query:path(Packet, [{element, <<"result">>}, {attr, <<"xmlns">>}], <<>>).
658
659 is_mam_namespace(NS) ->
660 10 lists:member(NS, mam_features()).
661
662 features(Module, HostType) ->
663 61 mam_features() ++ retraction_features(Module, HostType).
664
665 mam_features() ->
666 71 [?NS_MAM_04, ?NS_MAM_06].
667
668 retraction_features(Module, HostType) ->
669 61 case has_message_retraction(Module, HostType) of
670 54 true -> [?NS_RETRACT, ?NS_RETRACT_TOMBSTONE, ?NS_ESL_RETRACT];
671 7 false -> [?NS_RETRACT]
672 end.
673
674 %% -----------------------------------------------------------------------
675 %% Forms
676
677 -spec message_form(Mod :: mod_mam_pm | mod_mam_muc,
678 HostType :: mongooseim:host_type(), binary()) ->
679 exml:element().
680 message_form(Module, HostType, MamNs) ->
681 28 Fields = message_form_fields(Module, HostType),
682 28 Form = mongoose_data_forms:form(#{ns => MamNs, fields => Fields}),
683 28 result_query(Form, MamNs).
684
685 message_form_fields(Mod, HostType) ->
686 28 TextSearch =
687 case has_full_text_search(Mod, HostType) of
688 21 true -> [#{type => <<"text-single">>, var => <<"full-text-search">>}];
689 7 false -> []
690 end,
691 28 [#{type => <<"jid-single">>, var => <<"with">>},
692 #{type => <<"text-single">>, var => <<"start">>},
693 #{type => <<"text-single">>, var => <<"end">>} | TextSearch].
694
695 -spec form_to_text(_) -> 'undefined' | binary().
696 form_to_text(#{<<"full-text-search">> := [Text]}) ->
697 49 Text;
698 form_to_text(#{}) ->
699 1198 undefined.
700
701 %% -----------------------------------------------------------------------
702 %% Text search tokenization
703 %% -----------------------------------------------------------------------
704
705 %% -----------------------------------------------------------------------
706 %% @doc
707 %% Normalize given text to improve text search in some MAM backends.
708 %% This normalization involves making text all lowercase, replacing some word separators
709 %% ([, .:;-?!]) with given one (by default "%") and removing all unicode characters that are
710 %% considered non-alphanumerical.
711 %% For example, text: "My cat, was eaten by: my dog?!? Why...?!?" will be normalized as:
712 %% "my%cat%was%eaten%by%my%dog%why"
713 %% @end
714 %% -----------------------------------------------------------------------
715 -spec normalize_search_text(binary() | undefined) -> binary() | undefined.
716 normalize_search_text(Text) ->
717 1289 normalize_search_text(Text, <<"%">>).
718
719 -spec normalize_search_text(binary() | undefined, binary()) -> binary() | undefined.
720 normalize_search_text(undefined, _WordSeparator) ->
721 1247 undefined;
722 normalize_search_text(Text, WordSeparator) ->
723 7205 BodyString = unicode:characters_to_list(Text),
724 7205 LowerBody = string:to_lower(BodyString),
725 7205 ReOpts = [{return, list}, global, unicode, ucp],
726 7205 Re0 = re:replace(LowerBody, "[, .:;-?!]+", " ", ReOpts),
727 7205 Re1 = re:replace(Re0, "([^\\w ]+)|(^\\s+)|(\\s+$)", "", ReOpts),
728 7205 Re2 = re:replace(Re1, "\s+", unicode:characters_to_list(WordSeparator), ReOpts),
729 7205 unicode:characters_to_binary(Re2).
730
731 -spec packet_to_search_body(Enabled :: boolean(),
732 Packet :: exml:element()) -> binary().
733 packet_to_search_body(true, Packet) ->
734 7163 BodyValue = exml_query:path(Packet, [{element, <<"body">>}, cdata], <<>>),
735 7163 mod_mam_utils:normalize_search_text(BodyValue, <<" ">>);
736 packet_to_search_body(false, _Packet) ->
737
:-(
<<>>.
738
739 -spec has_full_text_search(Module :: mod_mam_pm | mod_mam_muc,
740 HostType :: mongooseim:host_type()) -> boolean().
741 has_full_text_search(Module, HostType) ->
742 12675 gen_mod:get_module_opt(HostType, Module, full_text_search).
743
744 %% Message retraction
745
746 -spec has_message_retraction(Module :: mod_mam_pm | mod_mam_muc,
747 HostType :: mongooseim:host_type()) -> boolean().
748 has_message_retraction(Module, HostType) ->
749 12708 gen_mod:get_module_opt(HostType, Module, message_retraction).
750
751 %% -----------------------------------------------------------------------
752 %% JID serialization
753
754 -spec jid_to_opt_binary(UserJID :: jid:jid(), JID :: jid:jid()
755 ) -> jid:literal_jid().
756 jid_to_opt_binary(#jid{lserver = LServer},
757 #jid{lserver = LServer, luser = <<>>, lresource = <<>>}) ->
758
:-(
<<$:>>;
759 jid_to_opt_binary(#jid{lserver = LServer, luser = LUser},
760 #jid{lserver = LServer, luser = LUser, lresource = <<>>}) ->
761 1471 <<>>;
762 jid_to_opt_binary(#jid{lserver = LServer, luser = LUser},
763 #jid{lserver = LServer, luser = LUser, lresource = LResource}) ->
764 997 <<$/, LResource/binary>>;
765 jid_to_opt_binary(#jid{lserver = LServer},
766 #jid{lserver = LServer, luser = LUser, lresource = <<>>}) ->
767 %% Both clients are on the same server.
768 6231 <<LUser/binary>>;
769 jid_to_opt_binary(#jid{lserver = LServer},
770 #jid{lserver = LServer, luser = <<>>, lresource = LResource}) ->
771 %% Both clients are on the same server.
772
:-(
<<$:, $/, LResource/binary>>;
773 jid_to_opt_binary(#jid{lserver = LServer},
774 #jid{lserver = LServer, luser = LUser, lresource = LResource}) ->
775 %% Both clients are on the same server.
776 906 <<LUser/binary, $/, LResource/binary>>;
777 jid_to_opt_binary(_,
778 #jid{lserver = LServer, luser = LUser, lresource = <<>>}) ->
779 208 <<LServer/binary, $:, LUser/binary>>;
780 jid_to_opt_binary(_,
781 #jid{lserver = LServer, luser = LUser, lresource = LResource}) ->
782 96 <<LServer/binary, $@, LUser/binary, $/, LResource/binary>>.
783
784
785 -spec expand_minified_jid(UserJID :: jid:jid(),
786 OptJID :: jid:literal_jid()) -> jid:literal_jid().
787 expand_minified_jid(#jid{lserver = LServer, luser = LUser}, <<>>) ->
788 378 <<LUser/binary, $@, LServer/binary>>;
789 expand_minified_jid(#jid{lserver = LServer, luser = <<>>}, <<$/, LResource/binary>>) ->
790
:-(
<<LServer/binary, $/, LResource/binary>>;
791 expand_minified_jid(#jid{lserver = LServer, luser = LUser}, <<$/, LResource/binary>>) ->
792 1747 <<LUser/binary, $@, LServer/binary, $/, LResource/binary>>;
793 expand_minified_jid(UserJID, Encoded) ->
794 378 Part = binary:match(Encoded, [<<$@>>, <<$/>>, <<$:>>]),
795 378 expand_minified_jid(Part, UserJID, Encoded).
796
797 -spec expand_minified_jid('nomatch' | {non_neg_integer(), 1}, jid:jid(),
798 Encoded :: jid:luser() | binary()) -> binary().
799 expand_minified_jid(nomatch, #jid{lserver = ThisServer}, LUser) ->
800 22 <<LUser/binary, $@, ThisServer/binary>>;
801 expand_minified_jid({Pos, 1}, #jid{lserver = ThisServer}, Encoded) ->
802 356 case Encoded of
803 <<$:, $/, LResource/binary>> ->
804
:-(
<<ThisServer/binary, $/, LResource/binary>>;
805 <<$:>> ->
806
:-(
ThisServer;
807 <<LServer:Pos/binary, $:>> ->
808
:-(
<<LServer/binary>>;
809 <<LServer:Pos/binary, $:, LUser/binary>> ->
810 30 <<LUser/binary, $@, LServer/binary>>;
811 <<LServer:Pos/binary, $@, $/, LResource/binary>> ->
812
:-(
<<LServer/binary, $/, LResource/binary>>;
813 <<LServer:Pos/binary, $@, Tail/binary>> ->
814 29 [LUser, LResource] = binary:split(Tail, <<$/>>),
815 29 <<LUser/binary, $@, LServer/binary, $/, LResource/binary>>;
816 <<LUser:Pos/binary, $/, LResource/binary>> ->
817 297 <<LUser/binary, $@, ThisServer/binary, $/, LResource/binary>>
818 end.
819
820 -ifdef(TEST).
821
822 jid_to_opt_binary_test_() ->
823 check_stringprep(),
824 UserJID = jid:from_binary(<<"alice@room">>),
825 [?_assertEqual(JID,
826 (expand_minified_jid(UserJID,
827 jid_to_opt_binary(UserJID, jid:from_binary(JID)))))
828 || JID <- test_jids()].
829
830 test_jids() ->
831 [<<"alice@room">>,
832 <<"alice@room/computer">>,
833 <<"alice@street/mobile">>,
834 <<"bob@room">>,
835 <<"bob@room/mobile">>,
836 <<"bob@street">>,
837 <<"bob@street/mobile">>].
838
839 check_stringprep() ->
840 is_loaded_application(jid) orelse start_stringprep().
841
842 start_stringprep() ->
843 EJ = code:lib_dir(mongooseim),
844 code:add_path(filename:join([EJ, "..", "..", "deps", "jid", "ebin"])),
845 {ok, _} = application:ensure_all_started(jid).
846
847 is_loaded_application(AppName) when is_atom(AppName) ->
848 lists:keymember(AppName, 1, application:loaded_applications()).
849
850 -endif.
851
852 %% -----------------------------------------------------------------------
853 %% Other
854 -spec bare_jid(undefined | jid:jid()) -> undefined | binary().
855
:-(
bare_jid(undefined) -> undefined;
856 bare_jid(JID) ->
857
:-(
jid:to_bare_binary(jid:to_lower(JID)).
858
859 -spec full_jid(jid:jid()) -> binary().
860 full_jid(JID) ->
861
:-(
jid:to_binary(jid:to_lower(JID)).
862
863 -spec maybe_integer(binary(), Default :: integer()) -> integer().
864 617 maybe_integer(<<>>, Def) -> Def;
865 maybe_integer(Bin, _Def) when is_binary(Bin) ->
866 630 binary_to_integer(Bin).
867
868 -spec apply_start_border('undefined' | mod_mam:borders(), undefined | integer()) ->
869 undefined | integer().
870 apply_start_border(undefined, StartID) ->
871 1205 StartID;
872 apply_start_border(#mam_borders{after_id=AfterID, from_id=FromID}, StartID) ->
873 84 maybe_max(maybe_next_id(AfterID), maybe_max(FromID, StartID)).
874
875
876 -spec apply_end_border('undefined' | mod_mam:borders(), undefined | integer()) ->
877 undefined | integer().
878 apply_end_border(undefined, EndID) ->
879 1205 EndID;
880 apply_end_border(#mam_borders{before_id=BeforeID, to_id=ToID}, EndID) ->
881 84 maybe_min(maybe_previous_id(BeforeID), maybe_min(ToID, EndID)).
882
883 -spec calculate_msg_id_borders(mod_mam:borders() | undefined,
884 mod_mam:unix_timestamp() | undefined,
885 mod_mam:unix_timestamp() | undefined) -> R when
886 R :: {integer() | undefined, integer() | undefined}.
887 calculate_msg_id_borders(Borders, Start, End) ->
888
:-(
StartID = maybe_encode_compact_uuid(Start, 0),
889
:-(
EndID = maybe_encode_compact_uuid(End, 255),
890
:-(
{apply_start_border(Borders, StartID),
891 apply_end_border(Borders, EndID)}.
892
893 -spec calculate_msg_id_borders(RSM, Borders, Start, End) -> R when
894 RSM :: jlib:rsm_in() | undefined,
895 Borders :: mod_mam:borders() | undefined,
896 Start :: mod_mam:unix_timestamp() | undefined,
897 End :: mod_mam:unix_timestamp() | undefined,
898 R :: {integer() | undefined, integer() | undefined}.
899 calculate_msg_id_borders(undefined, Borders, Start, End) ->
900
:-(
calculate_msg_id_borders(Borders, Start, End);
901 calculate_msg_id_borders(#rsm_in{id = undefined}, Borders, Start, End) ->
902
:-(
calculate_msg_id_borders(Borders, Start, End);
903 calculate_msg_id_borders(#rsm_in{direction = aft, id = Id}, Borders, Start, End)
904 when Id =/= undefined ->
905
:-(
{StartId, EndId} = mod_mam_utils:calculate_msg_id_borders(Borders, Start, End),
906
:-(
{mod_mam_utils:maybe_max(StartId, Id), EndId};
907 calculate_msg_id_borders(#rsm_in{direction = before, id = Id}, Borders, Start, End)
908 when Id =/= undefined ->
909
:-(
{StartId, EndId} = mod_mam_utils:calculate_msg_id_borders(Borders, Start, End),
910
:-(
{StartId, mod_mam_utils:maybe_min(EndId, Id)}.
911
912 -spec maybe_encode_compact_uuid(mod_mam:unix_timestamp() | undefined, integer()) ->
913 undefined | integer().
914 maybe_encode_compact_uuid(undefined, _) ->
915
:-(
undefined;
916 maybe_encode_compact_uuid(Microseconds, NodeID) ->
917
:-(
mod_mam_utils:encode_compact_uuid(Microseconds, NodeID).
918
919
920 -spec maybe_min('undefined' | integer(), undefined | integer()) -> integer().
921 maybe_min(undefined, Y) ->
922 147 Y;
923 maybe_min(X, undefined) ->
924 21 X;
925 maybe_min(X, Y) ->
926
:-(
min(X, Y).
927
928
929 -spec maybe_max('undefined' | integer(), undefined | integer()) -> integer().
930 maybe_max(undefined, Y) ->
931 84 Y;
932 maybe_max(X, undefined) ->
933 84 X;
934 maybe_max(X, Y) ->
935
:-(
max(X, Y).
936
937 -spec maybe_last([T]) -> undefined | {ok, T}.
938 21 maybe_last([]) -> undefined;
939 49 maybe_last([_|_] = L) -> {ok, lists:last(L)}.
940
941 -spec maybe_next_id('undefined' | non_neg_integer()) -> 'undefined' | pos_integer().
942 maybe_next_id(undefined) ->
943
:-(
undefined;
944 maybe_next_id(X) ->
945 84 X + 1.
946
947 -spec maybe_previous_id('undefined' | non_neg_integer()) -> 'undefined' | integer().
948 maybe_previous_id(undefined) ->
949 63 undefined;
950 maybe_previous_id(X) ->
951 21 X - 1.
952
953
954 %% @doc Returns true, if the current page is the final one in the result set.
955 %% If there are more pages with messages, than this function returns false.
956 %%
957 %% PageSize - maximum number of messages extracted in one lookup.
958 %% TotalCount - total number of messages in the Result Set.
959 %% Result Set - is subset of all messages in user's archive,
960 %% in a specified time period.
961 %% MessageRows - stuff we are about to send to the user.
962 %% Params - lookup parameters, coming from mam_iq module.
963 %%
964 %% TotalCount and Offset can be undefined, in case we use IsSimple=true.
965 %% IsSimple=true tells the server not to do heavy `SELECT COUNT(*)' queries.
966 %%
967 %% TODO It is possible to set complete flag WITH IsSimple=true,
968 %% if we select one extra message from archive, but don't send it to the client.
969 %% It's the most efficient way to query archive, if the client side does
970 %% not care about the total number of messages and if it's stateless
971 %% (i.e. web interface).
972 %% Handles case when we have TotalCount and Offset as integers
973 -spec is_complete_result_page_using_offset(Params, Result) ->
974 boolean() when
975 Params :: mam_iq:lookup_params(),
976 Result :: mod_mam:lookup_result_map().
977 is_complete_result_page_using_offset(#{page_size := PageSize} = Params,
978 #{total_count := TotalCount, offset := Offset,
979 messages := MessageRows})
980 when is_integer(TotalCount), is_integer(Offset) ->
981 1002 case maps:get(ordering_direction, Params, forward) of
982 forward ->
983 841 is_most_recent_page(PageSize, TotalCount, Offset, MessageRows);
984 backward ->
985 161 Offset =:= 0
986 end.
987
988 %% @doc Returns true, if the current page contains the most recent messages.
989 %% If there are some more recent messages in archive, this function returns false.
990 -spec is_most_recent_page(PageSize, TotalCount, Offset, MessageRows) -> boolean() when
991 PageSize :: non_neg_integer(),
992 TotalCount :: non_neg_integer()|undefined,
993 Offset :: non_neg_integer()|undefined,
994 MessageRows :: list().
995 is_most_recent_page(PageSize, _TotalCount, _Offset, MessageRows)
996 when length(MessageRows) < PageSize ->
997 652 true;
998 is_most_recent_page(PageSize, TotalCount, Offset, MessageRows)
999 when is_integer(TotalCount), is_integer(Offset),
1000 length(MessageRows) =:= PageSize ->
1001 %% Number of messages on skipped pages from the beginning plus the current page
1002 189 PagedCount = Offset + PageSize,
1003 189 TotalCount =:= PagedCount; %% false means full page but not the last one in the result set
1004 is_most_recent_page(_PageSize, _TotalCount, _Offset, _MessageRows) ->
1005 %% When is_integer(TotalCount), is_integer(Offset)
1006 %% it's not possible case: the page is bigger than page size.
1007 %% Otherwise either TotalCount or Offset is undefined because of optimizations.
1008
:-(
false.
1009
1010 -spec maybe_set_client_xmlns(boolean(), exml:element()) -> exml:element().
1011 maybe_set_client_xmlns(true, Packet) ->
1012 4066 xml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, Packet);
1013 maybe_set_client_xmlns(false, Packet) ->
1014
:-(
Packet.
1015
1016 -spec action_to_shaper_name(mam_iq:action()) -> atom().
1017 action_to_shaper_name(Action) ->
1018 1744 list_to_atom(atom_to_list(Action) ++ "_shaper").
1019
1020 -spec action_to_global_shaper_name(mam_iq:action()) -> atom().
1021 action_to_global_shaper_name(Action) ->
1022 1744 list_to_atom(atom_to_list(Action) ++ "_global_shaper").
1023
1024 -spec wait_shaper(mongooseim:host_type(), jid:server(), mam_iq:action(), jid:jid()) ->
1025 'ok' | {'error', 'max_delay_reached'}.
1026 wait_shaper(HostType, Host, Action, From) ->
1027 1744 case shaper_srv:wait(HostType, Host, action_to_shaper_name(Action), From, 1) of
1028 ok ->
1029 1744 shaper_srv:wait(HostType, Host, action_to_global_shaper_name(Action), global, 1);
1030 Err ->
1031
:-(
Err
1032 end.
1033
1034 %% -----------------------------------------------------------------------
1035 %% Ejabberd
1036
1037 -spec send_message(mod_mam:message_row(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
1038 send_message(_Row, From, To, Mess) ->
1039 4066 ejabberd_sm:route(From, To, Mess).
1040
1041 -spec is_jid_in_user_roster(mongooseim:host_type(), jid:jid(), jid:jid()) -> boolean().
1042 is_jid_in_user_roster(HostType, #jid{} = ToJID, #jid{} = RemJID) ->
1043 84 RemBareJID = jid:to_bare(RemJID),
1044 84 {Subscription, _G} = mongoose_hooks:roster_get_jid_info(HostType, ToJID, RemBareJID),
1045 84 Subscription == from orelse Subscription == both.
1046
1047 %% @doc Returns a UUIDv4 canonical form binary.
1048 -spec wrapper_id() -> binary().
1049 wrapper_id() ->
1050 4066 uuid:uuid_to_string(uuid:get_v4(), binary_standard).
1051
1052
1053 -spec check_result_for_policy_violation(Params, Result) -> Result when
1054 Params :: mam_iq:lookup_params(),
1055 Result :: {ok, mod_mam:lookup_result()}
1056 | {error, 'policy-violation'}
1057 | {error, Reason :: term()}.
1058 check_result_for_policy_violation(
1059 _Params = #{limit_passed := LimitPassed,
1060 max_result_limit := MaxResultLimit},
1061 Result = {ok, {TotalCount, Offset, _MessageRows}})
1062 when is_integer(TotalCount), is_integer(Offset) ->
1063 1002 case is_policy_violation(TotalCount, Offset, MaxResultLimit, LimitPassed) of
1064 true ->
1065
:-(
{error, 'policy-violation'};
1066 false ->
1067 1002 Result
1068 end;
1069 check_result_for_policy_violation(_Params, Result) ->
1070 294 Result.
1071
1072 is_policy_violation(TotalCount, Offset, MaxResultLimit, LimitPassed) ->
1073 1002 TotalCount - Offset > MaxResultLimit andalso not LimitPassed.
1074
1075 %% @doc Check for XEP-313 `item-not-found' error condition,
1076 %% that is if a message ID passed in a `before'/`after' query is actually present in the archive.
1077 %% See https://xmpp.org/extensions/xep-0313.html#query-paging for details.
1078 %%
1079 %% In a backend it's reasonable to query for PageSize + 1 messages,
1080 %% so that once the interval endpoint with requested ID is discarded we actually
1081 %% return (up to) PageSize messages.
1082 %% @end
1083 -spec check_for_item_not_found(RSM, PageSize, LookupResult) -> R when
1084 RSM :: jlib:rsm_in() | undefined,
1085 PageSize :: non_neg_integer(),
1086 LookupResult :: mod_mam:lookup_result(),
1087 R :: {ok, mod_mam:lookup_result()} | {error, item_not_found}.
1088 check_for_item_not_found(#rsm_in{direction = before, id = ID},
1089 _PageSize, {TotalCount, Offset, MessageRows}) ->
1090 70 case maybe_last(MessageRows) of
1091 {ok, #{id := ID}} ->
1092 49 {ok, {TotalCount, Offset, lists:droplast(MessageRows)}};
1093 undefined ->
1094 21 {error, item_not_found}
1095 end;
1096 check_for_item_not_found(#rsm_in{direction = aft, id = ID},
1097 _PageSize, {TotalCount, Offset, MessageRows0}) ->
1098 91 case MessageRows0 of
1099 [#{id := ID} | MessageRows] ->
1100 70 {ok, {TotalCount, Offset, MessageRows}};
1101 _ ->
1102 21 {error, item_not_found}
1103 end.
1104
1105 -spec lookup(HostType :: mongooseim:host_type(),
1106 Params :: mam_iq:lookup_params(),
1107 F :: fun()) ->
1108 {ok, mod_mam:lookup_result_map()} | {error, Reason :: term()}.
1109 lookup(HostType, Params, F) ->
1110 1226 F1 = patch_fun_to_make_result_as_map(F),
1111 1226 process_lookup_with_complete_check(HostType, Params, F1).
1112
1113 process_lookup_with_complete_check(HostType, Params = #{is_simple := true}, F) ->
1114 175 process_simple_lookup_with_complete_check(HostType, Params, F);
1115 process_lookup_with_complete_check(HostType, Params, F) ->
1116 1051 case F(HostType, Params) of
1117 {ok, Result} ->
1118 1002 IsComplete = is_complete_result_page_using_offset(Params, Result),
1119 1002 {ok, Result#{is_complete => IsComplete}};
1120 Other ->
1121 49 Other
1122 end.
1123
1124 patch_fun_to_make_result_as_map(F) ->
1125 1226 fun(HostType, Params) -> result_to_map(F(HostType, Params)) end.
1126
1127 result_to_map({ok, {TotalCount, Offset, MessageRows}}) ->
1128 1177 {ok, #{total_count => TotalCount, offset => Offset, messages => MessageRows}};
1129 result_to_map(Other) ->
1130 49 Other.
1131
1132 %% We query an extra message by changing page_size.
1133 %% After that we remove this message from the result set when returning.
1134 process_simple_lookup_with_complete_check(HostType, Params = #{page_size := PageSize}, F) ->
1135 175 Params2 = Params#{page_size => PageSize + 1},
1136 175 case F(HostType, Params2) of
1137 {ok, Result} ->
1138 175 {ok, set_complete_result_page_using_extra_message(PageSize, Params, Result)};
1139 Other ->
1140
:-(
Other
1141 end.
1142
1143 set_complete_result_page_using_extra_message(PageSize, Params, Result = #{messages := MessageRows}) ->
1144 175 case length(MessageRows) =:= (PageSize + 1) of
1145 true ->
1146 63 Result#{is_complete => false, messages => remove_extra_message(Params, MessageRows)};
1147 false ->
1148 112 Result#{is_complete => true}
1149 end.
1150
1151 remove_extra_message(Params, Messages) ->
1152 63 case maps:get(ordering_direction, Params, forward) of
1153 forward ->
1154 21 lists:droplast(Messages);
1155 backward ->
1156 42 tl(Messages)
1157 end.
1158
1159 -spec db_jid_codec(mongooseim:host_type(), module()) -> module().
1160 db_jid_codec(HostType, Module) ->
1161 12647 gen_mod:get_module_opt(HostType, Module, db_jid_format).
1162
1163 -spec db_message_codec(mongooseim:host_type(), module()) -> module().
1164 db_message_codec(HostType, Module) ->
1165 12647 gen_mod:get_module_opt(HostType, Module, db_message_format).
1166
1167 -spec incremental_delete_domain(
1168 mongooseim:host_type(), jid:lserver(), non_neg_integer(), [atom()], non_neg_integer()) ->
1169 non_neg_integer().
1170 incremental_delete_domain(_HostType, _Domain, _Limit, [], TotalDeleted) ->
1171
:-(
TotalDeleted;
1172 incremental_delete_domain(HostType, Domain, Limit, [Query | MoreQueries] = AllQueries, TotalDeleted) ->
1173
:-(
R1 = mongoose_rdbms:execute_successfully(HostType, Query, [Domain]),
1174
:-(
case is_removing_done(R1, Limit) of
1175 {done, N} ->
1176
:-(
incremental_delete_domain(HostType, Domain, Limit, MoreQueries, N + TotalDeleted);
1177 {remove_more, N} ->
1178
:-(
incremental_delete_domain(HostType, Domain, Limit, AllQueries, N + TotalDeleted)
1179 end.
1180
1181 -spec is_removing_done(LastResult :: {updated, non_neg_integer()}, Limit :: non_neg_integer()) ->
1182 {done | remove_more, non_neg_integer()}.
1183 is_removing_done({updated, N}, Limit) when N < Limit ->
1184
:-(
{done, N};
1185 is_removing_done({updated, N}, _)->
1186
:-(
{remove_more, N}.
1187
1188 -spec is_mam_muc_enabled(jid:lserver(), mongooseim:host_type()) -> boolean().
1189 is_mam_muc_enabled(MucDomain, HostType) ->
1190 544 HostPattern = mongoose_config:get_opt([{modules, HostType}, mod_mam_muc, host]),
1191 544 {ok, #{subdomain_pattern := SubDomainPattern}} =
1192 mongoose_domain_api:get_subdomain_info(MucDomain),
1193 544 HostPattern =:= SubDomainPattern.
Line Hits Source