From a4bb695fc3e694622f443fa040c0add89ee806f1 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Wed, 27 Dec 2023 08:49:39 +0300 Subject: [PATCH] Support for XEP-0424 "Message Retraction" --- include/mod_mam.hrl | 3 +- mix.exs | 2 +- rebar.config | 2 +- src/mod_mam.erl | 26 ++++++++++-- src/mod_mam_mnesia.erl | 25 ++++++++++-- src/mod_mam_sql.erl | 93 +++++++++++++++++++++++++++++++++++++----- src/mod_muc_room.erl | 2 +- 7 files changed, 131 insertions(+), 22 deletions(-) diff --git a/include/mod_mam.hrl b/include/mod_mam.hrl index 184afb4fb..8b95bf7d4 100644 --- a/include/mod_mam.hrl +++ b/include/mod_mam.hrl @@ -26,7 +26,8 @@ bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid(), packet = #xmlel{} :: xmlel() | message(), nick = <<"">> :: binary(), - type = chat :: chat | groupchat}). + type = chat :: chat | groupchat, + origin_id :: binary()}). -record(archive_prefs, {us = {<<"">>, <<"">>} :: {binary(), binary()}, diff --git a/mix.exs b/mix.exs index 03dc9ea32..d5d7a13f6 100644 --- a/mix.exs +++ b/mix.exs @@ -114,7 +114,7 @@ defmodule Ejabberd.MixProject do {:p1_utils, "~> 1.0"}, {:pkix, "~> 1.0"}, {:stringprep, ">= 1.0.26"}, - {:xmpp, git: "https://github.com/processone/xmpp.git", ref: "ded8be8c169487688b11130eda566b1377ab3301", override: true}, + {:xmpp, git: "https://github.com/processone/xmpp.git", ref: "26dd833dcf66ebb790d9afe212b7a26f3a6c2328", override: true}, {:yconf, "~> 1.0"}] ++ cond_deps() end diff --git a/rebar.config b/rebar.config index a71b5ad34..56b5ccab1 100644 --- a/rebar.config +++ b/rebar.config @@ -77,7 +77,7 @@ {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.29"}}}, {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.10"}}}}, - {xmpp, ".*", {git, "https://github.com/processone/xmpp", "ded8be8c169487688b11130eda566b1377ab3301"}}, + {xmpp, ".*", {git, "https://github.com/processone/xmpp", "26dd833dcf66ebb790d9afe212b7a26f3a6c2328"}}, {yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.15"}}} ]}. diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 58e400ade..f81b6a46b 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -69,7 +69,8 @@ all | chat | groupchat) -> any(). -callback extended_fields() -> [mam_query:property() | #xdata_field{}]. -callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat, - jid(), binary(), recv | send, integer()) -> ok | any(). + jid(), binary(), recv | send, integer(), binary(), + {true, binary()} | false) -> ok | any(). -callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any(). -callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error | {error, db_failure}. -callback select(binary(), jid(), jid(), mam_query:result(), @@ -512,6 +513,17 @@ set_stanza_id(Pkt, JID, ID) -> NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)], xmpp:set_els(Pkt, NewEls). +-spec get_origin_id(stanza()) -> binary(). +get_origin_id(#message{type = groupchat} = Pkt) -> + integer_to_binary(get_stanza_id(Pkt)); +get_origin_id(#message{} = Pkt) -> + case xmpp:get_subtag(Pkt, #origin_id{}) of + #origin_id{id = ID} -> + ID; + _ -> + xmpp:get_id(Pkt) + end. + -spec mark_stored_msg(message(), jid()) -> message(). mark_stored_msg(#message{meta = #{stanza_id := ID}} = Pkt, JID) -> Pkt1 = set_stanza_id(Pkt, JID, integer_to_binary(ID)), @@ -585,7 +597,8 @@ disco_sm_features(empty, From, To, Node, Lang) -> disco_sm_features({result, OtherFeatures}, #jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, <<"">>, _Lang) -> - {result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0 | + {result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2, ?NS_SID_0, + ?NS_MESSAGE_RETRACT | OtherFeatures]}; disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -1038,9 +1051,16 @@ store_mam_message(Pkt, U, S, Peer, Nick, Type, Dir) -> LServer = ejabberd_router:host_of_route(S), US = {U, S}, ID = get_stanza_id(Pkt), + OriginID = get_origin_id(Pkt), + Retract = case xmpp:get_subtag(Pkt, #message_retract{}) of + #message_retract{id = RID} when RID /= <<"">> -> + {true, RID}; + _ -> + false + end, El = xmpp:encode(Pkt), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:store(El, LServer, US, Type, Peer, Nick, Dir, ID), + Mod:store(El, LServer, US, Type, Peer, Nick, Dir, ID, OriginID, Retract), Pkt. write_prefs(LUser, LServer, Host, Default, Always, Never) -> diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index f9e366860..5cbac5ffb 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -28,8 +28,10 @@ %% API -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, - extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, remove_from_archive/3, - is_empty_for_user/2, is_empty_for_room/3, delete_old_messages_batch/5]). + extended_fields/0, store/10, write_prefs/4, get_prefs/2, select/6, + remove_from_archive/3, + is_empty_for_user/2, is_empty_for_room/3, delete_old_messages_batch/5, + transform/1]). -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("xmpp/include/xmpp.hrl"). @@ -187,7 +189,8 @@ delete_old_messages_batch(LServer, TimeStamp, Type, Batch, LastUS) -> extended_fields() -> []. -store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, TS) -> +store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, TS, + OriginID, _Retract) -> case {mnesia:table_info(archive_msg, disc_only_copies), mnesia:table_info(archive_msg, memory)} of {[_|_], TableSize} when TableSize > ?TABLE_SIZE_LIMIT -> @@ -205,7 +208,8 @@ store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, TS) -> bare_peer = {PUser, PServer, <<>>}, type = Type, nick = Nick, - packet = Pkt}) + packet = Pkt, + origin_id = OriginID}) end, case mnesia:transaction(F) of {atomic, ok} -> @@ -330,3 +334,16 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> {lists:sublist(Msgs, Len), length(Msgs) =< Len}; filter_by_max(_Msgs, _Junk) -> {[], true}. + +transform({archive_msg, US, ID, Timestamp, Peer, BarePeer, + Packet, Nick, Type}) -> + #archive_msg{ + us = US, + id = ID, + timestamp = Timestamp, + peer = Peer, + bare_peer = BarePeer, + packet = Packet, + nick = Nick, + type = Type, + origin_id = <<"">>}. diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 4898944ec..ee208b197 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -29,7 +29,7 @@ %% API -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, - extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/7, export/1, remove_from_archive/3, + extended_fields/0, store/10, write_prefs/4, get_prefs/2, select/7, export/1, remove_from_archive/3, is_empty_for_user/2, is_empty_for_room/3, select_with_mucsub/6, delete_old_messages_batch/4, count_messages_to_delete/3]). @@ -49,6 +49,61 @@ init(Host, _Opts) -> schemas() -> [#sql_schema{ + version = 2, + tables = + [#sql_table{ + name = <<"archive">>, + columns = + [#sql_column{name = <<"username">>, type = text}, + #sql_column{name = <<"server_host">>, type = text}, + #sql_column{name = <<"timestamp">>, type = bigint}, + #sql_column{name = <<"peer">>, type = text}, + #sql_column{name = <<"bare_peer">>, type = text}, + #sql_column{name = <<"xml">>, type = {text, big}}, + #sql_column{name = <<"txt">>, type = {text, big}}, + #sql_column{name = <<"id">>, type = bigserial}, + #sql_column{name = <<"kind">>, type = {text, 10}}, + #sql_column{name = <<"nick">>, type = text}, + #sql_column{name = <<"origin_id">>, type = text}, + #sql_column{name = <<"created_at">>, type = timestamp, + default = true}], + indices = [#sql_index{ + columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]}, + #sql_index{ + columns = [<<"server_host">>, <<"username">>, <<"peer">>]}, + #sql_index{ + columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]}, + #sql_index{ + columns = [<<"server_host">>, <<"timestamp">>]}, + #sql_index{ + columns = [<<"server_host">>, <<"username">>, <<"origin_id">>]} + ], + post_create = + fun(mysql, _) -> + ejabberd_sql:sql_query_t( + <<"CREATE FULLTEXT INDEX i_archive_txt ON archive(txt);">>); + (_, _) -> + ok + end}, + #sql_table{ + name = <<"archive_prefs">>, + columns = + [#sql_column{name = <<"username">>, type = text}, + #sql_column{name = <<"server_host">>, type = text}, + #sql_column{name = <<"def">>, type = text}, + #sql_column{name = <<"always">>, type = text}, + #sql_column{name = <<"never">>, type = text}, + #sql_column{name = <<"created_at">>, type = timestamp, + default = true}], + indices = [#sql_index{ + columns = [<<"server_host">>, <<"username">>], + unique = true}]}], + update = + [{add_column, <<"archive">>, <<"origin_id">>}, + {create_index, <<"archive">>, + [<<"server_host">>, <<"username">>, <<"origin_id">>]} + ]}, + #sql_schema{ version = 1, tables = [#sql_table{ @@ -200,7 +255,8 @@ delete_old_messages(ServerHost, TimeStamp, Type) -> extended_fields() -> [{withtext, <<"">>}]. -store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) -> +store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS, + OriginID, Retract) -> SUser = case Type of chat -> LUser; groupchat -> jid:encode({LUser, LHost, <<>>}) @@ -223,8 +279,19 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) -> _ -> fxml:element_to_binary(Pkt) end, - case SqlType of - mssql -> case ejabberd_sql:sql_query( + case Retract of + {true, RID} -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from archive" + " where username=%(SUser)s" + " and %(LServer)H" + " and bare_peer=%(BarePeer)s" + " and origin_id=%(RID)s")); + false -> ok + end, + case SqlType of + mssql -> case ejabberd_sql:sql_query( LServer, ?SQL_INSERT( "archive", @@ -236,13 +303,14 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) -> "xml=N%(XML)s", "txt=N%(Body)s", "kind=%(SType)s", - "nick=%(Nick)s"])) of + "nick=%(Nick)s", + "origin_id=%(OriginID)s"])) of {updated, _} -> ok; Err -> Err end; - _ -> case ejabberd_sql:sql_query( + _ -> case ejabberd_sql:sql_query( LServer, ?SQL_INSERT( "archive", @@ -254,13 +322,14 @@ store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, TS) -> "xml=%(XML)s", "txt=%(Body)s", "kind=%(SType)s", - "nick=%(Nick)s"])) of + "nick=%(Nick)s", + "origin_id=%(OriginID)s"])) of {updated, _} -> ok; Err -> Err end - end. + end. write_prefs(LUser, _LServer, #archive_prefs{default = Default, never = Never, @@ -420,7 +489,7 @@ export(_Server) -> {archive_msg, fun([Host | HostTail], #archive_msg{us ={LUser, LServer}, id = _ID, timestamp = TS, peer = Peer, - type = Type, nick = Nick, packet = Pkt}) + type = Type, nick = Nick, packet = Pkt, origin_id = OriginID}) when (LServer == Host) or ([LServer] == HostTail) -> TStmp = misc:now_to_usec(TS), SUser = case Type of @@ -444,7 +513,8 @@ export(_Server) -> "xml=N%(XML)s", "txt=N%(Body)s", "kind=%(SType)s", - "nick=%(Nick)s"])]; + "nick=%(Nick)s", + "origin_id=%(OriginID)s"])]; _ -> [?SQL_INSERT( "archive", ["username=%(SUser)s", @@ -455,7 +525,8 @@ export(_Server) -> "xml=%(XML)s", "txt=%(Body)s", "kind=%(SType)s", - "nick=%(Nick)s"])] + "nick=%(Nick)s", + "origin_id=%(OriginID)s"])] end; (_Host, _R) -> [] diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 30df7dbaa..5d3099cb4 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -5181,7 +5181,7 @@ process_iq_moderate(From, #iq{type = set, lang = Lang}, sub_els = [ #fasten_apply_to{id = Id, sub_els = [ #message_moderated{by = By, reason = Reason, - retract = #message_retract{}} + retract = #message_retract{id = Id}} ]}]}, {FromNick, _Role} = get_participant_data(From, StateData), Packet = ejabberd_hooks:run_fold(muc_filter_message,