diff options
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | rebar.config.script | 1 | ||||
-rw-r--r-- | src/ejabberd_config.erl | 3 | ||||
-rw-r--r-- | src/ejabberd_web_admin.erl | 69 | ||||
-rw-r--r-- | src/mod_http_fileserver.erl | 13 | ||||
-rw-r--r-- | src/mod_mam.erl | 317 | ||||
-rw-r--r-- | src/mod_muc.erl | 3 | ||||
-rw-r--r-- | src/mod_muc_admin.erl | 14 | ||||
-rw-r--r-- | src/mod_muc_room.erl | 40 | ||||
-rw-r--r-- | test/ejabberd_SUITE.erl | 74 | ||||
-rw-r--r-- | test/ejabberd_SUITE_data/ejabberd.yml | 15 | ||||
-rw-r--r-- | tools/xmpp_codec.erl | 85 | ||||
-rw-r--r-- | tools/xmpp_codec.hrl | 6 | ||||
-rw-r--r-- | tools/xmpp_codec.spec | 11 |
14 files changed, 419 insertions, 236 deletions
@@ -159,6 +159,10 @@ In order to assist in the development of ejabberd, and particularly the execution of the test suite, a Vagrant environment is available at https://github.com/processone/ejabberd-vagrant-dev. +To start ejabberd in development mode from the repository directory, you can +type a command like: + + EJABBERD_CONFIG_PATH=ejabberd.yml erl -pa ebin -pa deps/*/ebin -pa test -s ejabberd Links ----- diff --git a/rebar.config.script b/rebar.config.script index b4dfaa3ed..a38d85898 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -61,7 +61,6 @@ Deps = [{p1_cache_tab, ".*", {git, "https://github.com/processone/cache_tab"}}, {esip, ".*", {git, "https://github.com/processone/p1_sip"}}, {p1_stun, ".*", {git, "https://github.com/processone/stun"}}, {p1_yaml, ".*", {git, "https://github.com/processone/p1_yaml"}}, - {ehyperloglog, ".*", {git, "https://github.com/vaxelfel/eHyperLogLog.git"}}, {p1_utils, ".*", {git, "https://github.com/processone/p1_utils"}}], ConfigureCmd = fun(Pkg, Flags) -> diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 43e4154d5..81a87a648 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -1152,7 +1152,8 @@ opt_type(language) -> opt_type(_) -> [hosts, language]. --spec may_hide_data(string()) -> string(). +-spec may_hide_data(string()) -> string(); + (binary()) -> binary(). may_hide_data(Data) -> case ejabberd_config:get_option( diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index da166581c..3347f3e6e 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -167,15 +167,15 @@ process([<<"doc">>, LocalFile], _Request) -> ?DEBUG("Delivering content.", []), {200, [{<<"Server">>, <<"ejabberd">>}], FileContents}; {error, Error} -> - ?DEBUG("Delivering error: ~p", [Error]), Help = <<" ", FileName/binary, " - Try to specify the path to ejabberd " "documentation with the environment variable " "EJABBERD_DOC_PATH. Check the ejabberd " "Guide for more information.">>, + ?INFO_MSG("Problem '~p' accessing the local Guide file ~s", [Error, Help]), case Error of eacces -> {403, [], <<"Forbidden", Help/binary>>}; - enoent -> {404, [], <<"Not found", Help/binary>>}; + enoent -> {307, [{<<"Location">>, <<"http://docs.ejabberd.im/admin/guide/configuration/">>}], <<"Not found", Help/binary>>}; _Else -> {404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>} end @@ -296,7 +296,7 @@ make_xhtml(Els, Host, Node, Lang, JID) -> #xmlel{name = <<"html">>, attrs = [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}, - {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}], + {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}]++direction(Lang), children = [#xmlel{name = <<"head">>, attrs = [], children = @@ -340,8 +340,15 @@ make_xhtml(Els, Host, Node, Lang, JID) -> [{xmlcdata, <<"">>}])]), ?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}], [?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}], - [?XC(<<"p">>, - <<"ejabberd (c) 2002-2015 ProcessOne">>)])])])]}}. + [?XE(<<"p">>, + [?AC(<<"https://www.ejabberd.im/">>, <<"ejabberd">>), + ?C(<<" (c) 2002-2015 ">>), + ?AC(<<"https://www.process-one.net/">>, <<"ProcessOne">>)] + )])])])]}}. + +direction(ltr) -> [{<<"dir">>, <<"ltr">>}]; +direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}]; +direction(_) -> []. get_base_path(global, cluster) -> <<"/admin/">>; get_base_path(Host, cluster) -> @@ -510,7 +517,7 @@ css(Host) -> "0px;\n}\n\nh3 {\n color: #000044;\n " " font-family: Verdana, Arial, Helvetica, " "sans-serif; \n font-size: 10pt;\n " - "font-weight: bold;\n text-align: left;\n " + "font-weight: bold;\n " " padding-top: 20px;\n padding-bottom: " "2px;\n margin-top: 0px;\n margin-bottom: " "0px;\n}\n\n#content a:link {\n color: " @@ -590,7 +597,7 @@ logo_fill() -> process_admin(global, #request{path = [], auth = {_, _, AJID}, lang = Lang}) -> - make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>, + make_xhtml((?H1GL((?T(<<"Administration">>)), <<"">>, <<"Contents">>)) ++ [?XE(<<"ul">>, @@ -659,7 +666,7 @@ process_admin(Host, [{{acl, '$1', '$2'}}]}])), {NumLines, ACLsP} = term_to_paragraph(ACLs, 80), make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), - <<"ACLDefinition">>, <<"ACL Definition">>)) + <<"acl-definition">>, <<"ACL Definition">>)) ++ case Res of ok -> [?XREST(<<"Submitted">>)]; @@ -668,7 +675,7 @@ process_admin(Host, end ++ [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), [?TEXTAREA(<<"acls">>, (iolist_to_binary(integer_to_list(lists:max([16, NumLines])))), @@ -695,7 +702,7 @@ process_admin(Host, [{{acl, {'$1', Host}, '$2'}, [], [{{acl, '$1', '$2'}}]}])), make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), - <<"ACLDefinition">>, <<"ACL Definition">>)) + <<"acl-definition">>, <<"ACL Definition">>)) ++ case Res of ok -> [?XREST(<<"Submitted">>)]; @@ -703,9 +710,9 @@ process_admin(Host, nothing -> [] end ++ - [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++ + [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++ [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), [acls_to_xhtml(ACLs), ?BR, ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>), @@ -761,7 +768,7 @@ process_admin(Host, [{{access, '$1', '$2'}}]}]), {NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80), make_xhtml((?H1GL((?T(<<"Access Rules">>)), - <<"AccessRights">>, <<"Access Rights">>)) + <<"access-rights">>, <<"Access Rights">>)) ++ case Res of ok -> [?XREST(<<"Submitted">>)]; @@ -770,7 +777,7 @@ process_admin(Host, end ++ [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), [?TEXTAREA(<<"access">>, (iolist_to_binary(integer_to_list(lists:max([16, NumLines])))), @@ -794,7 +801,7 @@ process_admin(Host, [{{access, {'$1', Host}, '$2'}, [], [{{access, '$1', '$2'}}]}]), make_xhtml((?H1GL((?T(<<"Access Rules">>)), - <<"AccessRights">>, <<"Access Rights">>)) + <<"access-rights">>, <<"Access Rights">>)) ++ case Res of ok -> [?XREST(<<"Submitted">>)]; @@ -802,10 +809,10 @@ process_admin(Host, nothing -> [] end ++ - [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])] + [?XAE(<<"p">>, direction(ltr), [?ACT(<<"../access-raw/">>, <<"Raw">>)])] ++ [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}]++direction(ltr), [access_rules_to_xhtml(AccessRules, Lang), ?BR, ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)])], @@ -853,7 +860,7 @@ process_admin(global, lang = Lang}) -> Res = list_vhosts(Lang, AJID), make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)), - <<"virtualhost">>, <<"Virtual Hosting">>)) + <<"virtual-hosting">>, <<"Virtual Hosting">>)) ++ Res, global, Lang, AJID); process_admin(Host, @@ -1552,16 +1559,18 @@ user_info(User, Server, Query, Lang) -> http_bind -> <<"http-bind">> end, - <<" (", ConnS/binary, + <<ConnS/binary, "://", (jlib:ip_to_list(IP))/binary, ":", (jlib:integer_to_binary(Port))/binary, "#", - (jlib:atom_to_binary(Node))/binary, - ")">> + (jlib:atom_to_binary(Node))/binary>> end, - ?LI([?C((<<R/binary, FIP/binary>>))]) + case direction(Lang) of + [{_, <<"rtl">>}] -> ?LI([?C((<<FIP/binary, " - ", R/binary>>))]); + _ -> ?LI([?C((<<R/binary, " - ", FIP/binary>>))]) + end end, lists:sort(Resources))))] end, @@ -1872,9 +1881,7 @@ get_node(global, Node, [<<"backup">>], Query, Lang) -> [?XRES(<<(?T(<<"Error">>))/binary, ": ", (list_to_binary(io_lib:format("~p", [Error])))/binary>>)] end, - (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])), - <<"list-eja-commands">>, - <<"List of ejabberd Commands">>)) + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])))] ++ ResS ++ [?XCT(<<"p">>, @@ -2036,7 +2043,7 @@ get_node(global, Node, [<<"ports">>], Query, Lang) -> []])), H1String = <<(?T(<<"Listened Ports at ">>))/binary, (iolist_to_binary(atom_to_list(Node)))/binary>>, - (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>)) + (?H1GL(H1String, <<"listening-ports">>, <<"Listening Ports">>)) ++ case Res of ok -> [?XREST(<<"Submitted">>)]; @@ -2064,7 +2071,7 @@ get_node(Host, Node, [<<"modules">>], Query, Lang) NewModules = lists:sort(rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host])), H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])), - (?H1GL(H1String, <<"modoverview">>, + (?H1GL(H1String, <<"modules-overview">>, <<"Modules Overview">>)) ++ case Res of @@ -2381,7 +2388,7 @@ node_ports_to_xhtml(Ports, Lang) -> [?INPUTS(<<"text">>, <<"module", SSPort/binary>>, SModule, <<"15">>)]), - ?XE(<<"td">>, + ?XAE(<<"td">>, direction(ltr), [?TEXTAREA(<<"opts", SSPort/binary>>, (iolist_to_binary(integer_to_list(NumLines))), <<"35">>, SOptsClean)]), @@ -2407,7 +2414,7 @@ node_ports_to_xhtml(Ports, Lang) -> ?XE(<<"td">>, [?INPUTS(<<"text">>, <<"modulenew">>, <<"">>, <<"15">>)]), - ?XE(<<"td">>, + ?XAE(<<"td">>, direction(ltr), [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>, <<"[]">>)]), ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], @@ -2524,7 +2531,7 @@ node_modules_to_xhtml(Modules, Lang) -> 40), ?XE(<<"tr">>, [?XC(<<"td">>, SModule), - ?XE(<<"td">>, + ?XAE(<<"td">>, direction(ltr), [?TEXTAREA(<<"opts", SModule/binary>>, (iolist_to_binary(integer_to_list(NumLines))), <<"40">>, SOpts)]), @@ -2543,7 +2550,7 @@ node_modules_to_xhtml(Modules, Lang) -> [?XE(<<"tr">>, [?XE(<<"td">>, [?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]), - ?XE(<<"td">>, + ?XAE(<<"td">>, direction(ltr), [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>, <<"[]">>)]), ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index 68ebf8835..8b4d7337e 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -167,10 +167,17 @@ initialize(Host, Opts) -> ?DEFAULT_CONTENT_TYPE), ContentTypes = build_list_content_types( gen_mod:get_opt(content_types, Opts, - fun(L) when is_list(L) -> L end, - []), + fun(L) when is_list(L) -> + lists:map( + fun({K, V}) -> + {iolist_to_binary(K), + iolist_to_binary(V)} + end, L) + end, []), ?DEFAULT_CONTENT_TYPES), - ?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++ + ?INFO_MSG("known content types: ~s", + [str:join([[$*, K, " -> ", V] || {K, V} <- ContentTypes], + <<", ">>)]), {DocRoot, AccessLog, AccessLogFD, DirectoryIndices, CustomHeaders, DefaultContentType, ContentTypes}. diff --git a/src/mod_mam.erl b/src/mod_mam.erl index edbc4f91a..c3c8a2b92 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -34,7 +34,8 @@ -export([start/2, stop/1]). -export([user_send_packet/4, user_receive_packet/5, - process_iq/3, remove_user/2, mod_opt_type/1]). + process_iq_v0_2/3, process_iq_v0_3/3, remove_user/2, + mod_opt_type/1]). -include_lib("stdlib/include/ms_transform.hrl"). -include("jlib.hrl"). @@ -64,13 +65,13 @@ start(Host, Opts) -> init_db(DBType, Host), init_cache(DBType, Opts), gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_MAM_TMP, ?MODULE, process_iq, IQDisc), + ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_MAM_TMP, ?MODULE, process_iq, IQDisc), + ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_MAM_0, ?MODULE, process_iq, IQDisc), + ?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, - ?NS_MAM_0, ?MODULE, process_iq, IQDisc), + ?NS_MAM_0, ?MODULE, process_iq_v0_3, IQDisc), ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, user_receive_packet, 500), ejabberd_hooks:add(user_send_packet, Host, ?MODULE, @@ -197,13 +198,11 @@ user_send_packet(Pkt, C2SState, JID, Peer) -> Pkt end. -process_iq(#jid{lserver = LServer} = From, +% Query archive v0.2 +process_iq_v0_2(#jid{lserver = LServer} = From, #jid{lserver = LServer} = To, #iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> - NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl), - Fs = case NS of - ?NS_MAM_TMP -> - lists:flatmap( + Fs = lists:flatmap( fun(#xmlel{name = <<"start">>} = El) -> [{<<"start">>, [xml:get_tag_cdata(El)]}]; (#xmlel{name = <<"end">>} = El) -> @@ -218,9 +217,16 @@ process_iq(#jid{lserver = LServer} = From, [{<<"set">>, SubEl}]; (_) -> [] - end, SubEl#xmlel.children); - ?NS_MAM_0 -> - case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), + end, SubEl#xmlel.children), + process_iq(From, To, IQ, SubEl, Fs); +process_iq_v0_2(From, To, IQ) -> + process_iq(From, To, IQ). + +% Query archive v0.3 +process_iq_v0_3(#jid{lserver = LServer} = From, + #jid{lserver = LServer} = To, + #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> + Fs = case {xml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), xml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of {#xmlel{} = XData, false} -> jlib:parse_xdata_submit(XData); @@ -230,34 +236,16 @@ process_iq(#jid{lserver = LServer} = From, [{<<"set">>, SubEl}]; {false, false} -> [] - end end, - case catch lists:foldl( - fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) -> - {{_, _, _} = jlib:datetime_string_to_timestamp(Data), - End, With, RSM}; - ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) -> - {Start, - {_, _, _} = jlib:datetime_string_to_timestamp(Data), - With, RSM}; - ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM}; - ({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, - {room, jlib:jid_tolower(jlib:string_to_jid(Data))}, - RSM}; - ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, {text, Data}, RSM}; - ({<<"set">>, El}, {Start, End, With, _}) -> - {Start, End, With, jlib:rsm_decode(El)}; - (_, Acc) -> - Acc - end, {none, [], none, none}, Fs) of - {'EXIT', _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - {Start, End, With, RSM} -> - select_and_send(From, To, Start, End, With, RSM, IQ) - end; + process_iq(From, To, IQ, SubEl, Fs); +process_iq_v0_3(From, To, IQ) -> + process_iq(From, To, IQ). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +% Preference setting (both v0.2 & v0.3) process_iq(#jid{luser = LUser, lserver = LServer}, #jid{lserver = LServer}, #iq{type = set, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) -> @@ -289,9 +277,34 @@ process_iq(#jid{luser = LUser, lserver = LServer}, process_iq(_, _, #iq{sub_el = SubEl} = IQ) -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. -%%%=================================================================== -%%% Internal functions -%%%=================================================================== +process_iq(From, To, IQ, SubEl, Fs) -> + case catch lists:foldl( + fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) -> + {{_, _, _} = jlib:datetime_string_to_timestamp(Data), + End, With, RSM}; + ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) -> + {Start, + {_, _, _} = jlib:datetime_string_to_timestamp(Data), + With, RSM}; + ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) -> + {Start, End, jlib:jid_tolower(jlib:string_to_jid(Data)), RSM}; + ({<<"withroom">>, [Data|_]}, {Start, End, _, RSM}) -> + {Start, End, + {room, jlib:jid_tolower(jlib:string_to_jid(Data))}, + RSM}; + ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) -> + {Start, End, {text, Data}, RSM}; + ({<<"set">>, El}, {Start, End, With, _}) -> + {Start, End, With, jlib:rsm_decode(El)}; + (_, Acc) -> + Acc + end, {none, [], none, none}, Fs) of + {'EXIT', _} -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + {Start, End, With, RSM} -> + select_and_send(From, To, Start, End, With, RSM, IQ) + end. + should_archive(#xmlel{name = <<"message">>} = Pkt) -> case {xml:get_attr_s(<<"type">>, Pkt#xmlel.attrs), xml:get_subtag_cdata(Pkt, <<"body">>)} of @@ -505,10 +518,10 @@ select_and_send(#jid{lserver = LServer} = From, DBType). select_and_send(From, To, Start, End, With, RSM, IQ, DBType) -> - {Msgs, Count} = select_and_start(From, To, Start, End, With, - RSM, DBType), + {Msgs, IsComplete, Count} = select_and_start(From, To, Start, End, With, + RSM, DBType), SortedMsgs = lists:keysort(2, Msgs), - send(From, To, SortedMsgs, RSM, Count, IQ). + send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ). select_and_start(From, _To, StartUser, End, With, RSM, DB) -> {JidRequestor, Start, With2} = case With of @@ -525,21 +538,41 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, Start, End, With, RSM, mnesia) -> MS = make_matchspec(LUser, LServer, Start, End, With), Msgs = mnesia:dirty_select(archive_msg, MS), - FilteredMsgs = filter_by_rsm(Msgs, RSM), + {FilteredMsgs, IsComplete} = filter_by_rsm(Msgs, RSM), Count = length(Msgs), {lists:map( fun(Msg) -> {Msg#archive_msg.id, jlib:binary_to_integer(Msg#archive_msg.id), msg_to_el(Msg, JidRequestor)} - end, FilteredMsgs), Count}; + end, FilteredMsgs), IsComplete, Count}; select(#jid{luser = LUser, lserver = LServer} = JidRequestor, Start, End, With, RSM, {odbc, Host}) -> - {Query, CountQuery} = make_sql_query(LUser, LServer, + {Query, CountQuery} = make_sql_query(LUser, LServer, Start, End, With, RSM), + % XXX TODO from XEP-0313: + % To conserve resources, a server MAY place a reasonable limit on + % how many stanzas may be pushed to a client in one request. If a + % query returns a number of stanzas greater than this limit and + % the client did not specify a limit using RSM then the server + % should return a policy-violation error to the client. case {ejabberd_odbc:sql_query(Host, Query), ejabberd_odbc:sql_query(Host, CountQuery)} of {{selected, _, Res}, {selected, _, [[Count]]}} -> + {Max, Direction} = case RSM of + #rsm_in{max = M, direction = D} -> {M, D}; + _ -> {undefined, undefined} + end, + {Res1, IsComplete} = + if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> + if Direction == before -> + {lists:nthtail(1, Res), false}; + true -> + {lists:sublist(Res, Max), false} + end; + true -> + {Res, true} + end, {lists:map( fun([TS, XML, PeerBin]) -> #xmlel{} = El = xml_stream:parse_element(XML), @@ -550,9 +583,9 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, packet = El, peer = PeerJid}, JidRequestor)} - end, Res), jlib:binary_to_integer(Count)}; - _ -> - {[], 0} + end, Res1), IsComplete, jlib:binary_to_integer(Count)}; + _ -> + {[], false, 0} end. msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer}, @@ -580,14 +613,19 @@ maybe_update_from_to(Pkt, JidRequestor, Peer) -> _ -> Pkt end. -send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> +send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) -> QID = xml:get_tag_attr_s(<<"queryid">>, SubEl), NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl), QIDAttr = if QID /= <<>> -> - [{<<"queryid">>, QID}]; - true -> - [] - end, + [{<<"queryid">>, QID}]; + true -> + [] + end, + CompleteAttr = if NS == ?NS_MAM_TMP -> + []; + NS == ?NS_MAM_0 -> + [{<<"complete">>, jlib:atom_to_binary(IsComplete)}] + end, Els = lists:map( fun({ID, _IDInt, El}) -> #xmlel{name = <<"message">>, @@ -596,7 +634,7 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> {<<"id">>, ID}|QIDAttr], children = [El]}]} end, Msgs), - RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr, NS), + RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS), case NS of ?NS_MAM_TMP -> lists:foreach( @@ -618,55 +656,57 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> end. -make_rsm_out(_Msgs, none, _Count, _QIDAttr, ?NS_MAM_TMP) -> +make_rsm_out(_Msgs, none, _Count, _Attrs, ?NS_MAM_TMP) -> []; -make_rsm_out(_Msgs, none, _Count, QIDAttr, ?NS_MAM_0) -> - [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|QIDAttr]}]; -make_rsm_out([], #rsm_in{}, Count, QIDAttr, NS) -> +make_rsm_out(_Msgs, none, _Count, Attrs, ?NS_MAM_0) -> + [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|Attrs]}]; +make_rsm_out([], #rsm_in{}, Count, Attrs, NS) -> Tag = if NS == ?NS_MAM_TMP -> <<"query">>; true -> <<"fin">> end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr], - children = jlib:rsm_encode(#rsm_out{count = Count})}]; -make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, QIDAttr, NS) -> + [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], + children = jlib:rsm_encode(#rsm_out{count = Count})}]; +make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, Attrs, NS) -> {LastID, _, _} = lists:last(Msgs), Tag = if NS == ?NS_MAM_TMP -> <<"query">>; true -> <<"fin">> end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr], - children = jlib:rsm_encode( - #rsm_out{first = FirstID, count = Count, - last = LastID})}]. + [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], + children = jlib:rsm_encode( + #rsm_out{first = FirstID, count = Count, + last = LastID})}]. filter_by_rsm(Msgs, none) -> - Msgs; -filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max =< 0 -> - []; + {Msgs, true}; +filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 -> + {[], true}; filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> NewMsgs = case Direction of - aft -> - lists:filter( - fun(#archive_msg{id = I}) -> - I > ID - end, Msgs); - before -> - lists:foldl( - fun(#archive_msg{id = I} = Msg, Acc) when I < ID -> - [Msg|Acc]; - (_, Acc) -> - Acc - end, [], Msgs); - _ -> - Msgs - end, + aft when ID /= <<"">> -> + lists:filter( + fun(#archive_msg{id = I}) -> + I > ID + end, Msgs); + before when ID /= <<"">> -> + lists:foldl( + fun(#archive_msg{id = I} = Msg, Acc) when I < ID -> + [Msg|Acc]; + (_, Acc) -> + Acc + end, [], Msgs); + before when ID == <<"">> -> + lists:reverse(Msgs); + _ -> + Msgs + end, filter_by_max(NewMsgs, Max). filter_by_max(Msgs, undefined) -> - Msgs; + {Msgs, true}; filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> - lists:sublist(Msgs, Len); + {lists:sublist(Msgs, Len), length(Msgs) =< Len}; filter_by_max(_Msgs, _Junk) -> - []. + {[], true}. make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) -> ets:fun2ms( @@ -705,45 +745,43 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) -> RSM#rsm_in.direction, RSM#rsm_in.id}; none -> - {none, none, none} + {none, none, <<>>} end, LimitClause = if is_integer(Max), Max >= 0 -> - [<<" limit ">>, jlib:integer_to_binary(Max)]; - true -> - [] - end, + [<<" limit ">>, jlib:integer_to_binary(Max+1)]; + true -> + [] + end, WithClause = case With of - {text, <<>>} -> - []; - {text, Txt} -> - [<<" and match (txt) against ('">>, - ejabberd_odbc:escape(Txt), <<"')">>]; - {_, _, <<>>} -> - [<<" and bare_peer='">>, - ejabberd_odbc:escape(jlib:jid_to_string(With)), - <<"'">>]; - {_, _, _} -> - [<<" and peer='">>, - ejabberd_odbc:escape(jlib:jid_to_string(With)), - <<"'">>]; - none -> - [] - end, - DirectionClause = case catch jlib:binary_to_integer(ID) of - I when is_integer(I), I >= 0 -> - case Direction of - before -> - [<<" and timestamp < ">>, ID, - <<" order by timestamp desc">>]; - aft -> - [<<" and timestamp > ">>, ID, - <<" order by timestamp asc">>]; - _ -> - [] - end; - _ -> - [] - end, + {text, <<>>} -> + []; + {text, Txt} -> + [<<" and match (txt) against ('">>, + ejabberd_odbc:escape(Txt), <<"')">>]; + {_, _, <<>>} -> + [<<" and bare_peer='">>, + ejabberd_odbc:escape(jlib:jid_to_string(With)), + <<"'">>]; + {_, _, _} -> + [<<" and peer='">>, + ejabberd_odbc:escape(jlib:jid_to_string(With)), + <<"'">>]; + none -> + [] + end, + PageClause = case catch jlib:binary_to_integer(ID) of + I when is_integer(I), I >= 0 -> + case Direction of + before -> + [<<" AND timestamp < ">>, ID]; + aft -> + [<<" AND timestamp > ">>, ID]; + _ -> + [] + end; + _ -> + [] + end, StartClause = case Start of {_, _, _} -> [<<" and timestamp >= ">>, @@ -759,11 +797,28 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) -> [] end, SUser = ejabberd_odbc:escape(LUser), - {[<<"select timestamp, xml, peer from archive where username='">>, - SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++ - DirectionClause ++ LimitClause ++ [<<";">>], - [<<"select count(*) from archive where username='">>, - SUser, <<"'">>] ++ WithClause ++ StartClause ++ EndClause ++ [<<";">>]}. + + Query = [<<"SELECT timestamp, xml, peer" + " FROM archive WHERE username='">>, + SUser, <<"'">>, WithClause, StartClause, EndClause, + PageClause], + + QueryPage = + case Direction of + before -> + % ID can be empty because of + % XEP-0059: Result Set Management + % 2.5 Requesting the Last Page in a Result Set + [<<"SELECT timestamp, xml, peer FROM (">>, Query, + <<" ORDER BY timestamp DESC ">>, + LimitClause, <<") AS t ORDER BY timestamp ASC;">>]; + _ -> + [Query, <<" ORDER BY timestamp ASC ">>, + LimitClause, <<";">>] + end, + {QueryPage, + [<<"SELECT COUNT(*) FROM archive WHERE username='">>, + SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}. now_to_usec({MSec, Sec, USec}) -> (MSec*1000000 + Sec)*1000000 + USec. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 6226ca3b2..e798790c5 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -115,7 +115,8 @@ shutdown_rooms(Host) -> Rooms = mnesia:dirty_select(muc_online_room, [{#muc_online_room{name_host = '$1', pid = '$2'}, - [{'==', {element, 2, '$1'}, MyHost}], + [{'==', {element, 2, '$1'}, MyHost}, + {'==', {node, '$2'}, node()}], ['$2']}]), [Pid ! shutdown || Pid <- Rooms], Rooms. diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 1804ba7e5..6aa3482dc 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -241,8 +241,8 @@ web_menu_host(Acc, _Host, Lang) -> ])). web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) -> - Res = [?XC(<<"h1">>, <<"Multi-User Chat">>), - ?XC(<<"h3">>, <<"Statistics">>), + Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>), + ?XCT(<<"h3">>, <<"Statistics">>), ?XAE(<<"table">>, [], [?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)), ?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)), @@ -301,22 +301,22 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) -> <<"Persistent">>, <<"Logging">>, <<"Just created">>, - <<"Title">>], + <<"Room title">>], {Titles_TR, _} = lists:mapfoldl( fun(Title, Num_column) -> NCS = jlib:integer_to_binary(Num_column), TD = ?XE(<<"td">>, [?CT(Title), ?C(<<" ">>), - ?ACT(<<"?sort=", NCS/binary>>, <<"<">>), + ?AC(<<"?sort=", NCS/binary>>, <<"<">>), ?C(<<" ">>), - ?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]), + ?AC(<<"?sort=-", NCS/binary>>, <<">">>)]), {TD, Num_column+1} end, 1, Titles), - [?XC(<<"h1">>, <<"Multi-User Chat">>), - ?XC(<<"h2">>, <<"Rooms">>), + [?XCT(<<"h1">>, <<"Multi-User Chat">>), + ?XCT(<<"h2">>, <<"Chatrooms">>), ?XE(<<"table">>, [?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)] diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 7b4cfbf6d..df06bce4f 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -947,20 +947,32 @@ process_groupchat_message(From, end, case IsAllowed of true -> - send_multiple( - jlib:jid_replace_resource(StateData#state.jid, FromNick), - StateData#state.server_host, - StateData#state.users, - Packet), - NewStateData2 = case has_body_or_subject(Packet) of - true -> - add_message_to_history(FromNick, From, - Packet, - NewStateData1); - false -> - NewStateData1 - end, - {next_state, normal_state, NewStateData2}; + case + ejabberd_hooks:run_fold(muc_filter_packet, + StateData#state.server_host, + Packet, + [StateData, + StateData#state.jid, + From, FromNick]) + of + drop -> + {next_state, normal_state, StateData}; + NewPacket -> + send_multiple(jlib:jid_replace_resource(StateData#state.jid, + FromNick), + StateData#state.server_host, + StateData#state.users, + Packet), + NewStateData2 = case has_body_or_subject(Packet) of + true -> + add_message_to_history(FromNick, From, + Packet, + NewStateData1); + false -> + NewStateData1 + end, + {next_state, normal_state, NewStateData2} + end; _ -> Err = case (StateData#state.config)#config.allow_change_subj diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index dc528d79a..075a53fe2 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -1663,7 +1663,11 @@ mam_query_all(Config, NS) -> QID = randoms:get_string(), MyJID = my_jid(Config), Peer = ?config(slave, Config), - I = send(Config, #iq{type = get, sub_els = [#mam_query{xmlns = NS, id = QID}]}), + Type = case NS of + ?NS_MAM_TMP -> get; + _ -> set + end, + I = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, id = QID}]}), maybe_recv_iq_result(NS, I), Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5); true -> lists:seq(1, 5) ++ lists:seq(1, 5) @@ -1686,21 +1690,21 @@ mam_query_all(Config, NS) -> if NS == ?NS_MAM_TMP -> ?recv1(#iq{type = result, id = I, sub_els = []}); true -> - ?recv1(#message{sub_els = [#mam_fin{id = QID}]}) + ?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]}) end. mam_query_with(Config, JID, NS) -> MyJID = my_jid(Config), Peer = ?config(slave, Config), - Query = if NS == ?NS_MAM_TMP -> - #mam_query{xmlns = NS, with = JID}; + {Query, Type} = if NS == ?NS_MAM_TMP -> + {#mam_query{xmlns = NS, with = JID}, get}; true -> Fs = [#xdata_field{var = <<"jid">>, values = [jlib:jid_to_string(JID)]}], - #mam_query{xmlns = NS, - xdata = #xdata{type = submit, fields = Fs}} + {#mam_query{xmlns = NS, + xdata = #xdata{type = submit, fields = Fs}}, set} end, - I = send(Config, #iq{type = get, sub_els = [Query]}), + I = send(Config, #iq{type = Type, sub_els = [Query]}), Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5); true -> lists:seq(1, 5) ++ lists:seq(1, 5) end, @@ -1722,7 +1726,7 @@ mam_query_with(Config, JID, NS) -> if NS == ?NS_MAM_TMP -> ?recv1(#iq{type = result, id = I, sub_els = []}); true -> - ?recv1(#message{sub_els = [#mam_fin{}]}) + ?recv1(#message{sub_els = [#mam_fin{complete = true}]}) end. maybe_recv_iq_result(?NS_MAM_0, I1) -> @@ -1733,9 +1737,13 @@ maybe_recv_iq_result(_, _) -> mam_query_rsm(Config, NS) -> MyJID = my_jid(Config), Peer = ?config(slave, Config), + Type = case NS of + ?NS_MAM_TMP -> get; + _ -> set + end, %% Get the first 3 items out of 5 I1 = send(Config, - #iq{type = get, + #iq{type = Type, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}), maybe_recv_iq_result(NS, I1), lists:foreach( @@ -1759,12 +1767,13 @@ mam_query_rsm(Config, NS) -> rsm = #rsm_set{last = Last, count = 5}}]}); true -> ?recv1(#message{sub_els = [#mam_fin{ + complete = false, rsm = #rsm_set{last = Last, count = 10}}]}) end, %% Get the next items starting from the `Last`. %% Limit the response to 2 items. I2 = send(Config, - #iq{type = get, + #iq{type = Type, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 2, 'after' = Last}}]}), @@ -1794,15 +1803,16 @@ mam_query_rsm(Config, NS) -> true -> ?recv1(#message{ sub_els = [#mam_fin{ + complete = false, rsm = #rsm_set{ count = 10, first = #rsm_first{data = First}}}]}) end, - %% Paging back. Should receive 2 elements: 2, 3. + %% Paging back. Should receive 3 elements: 1, 2, 3. I3 = send(Config, - #iq{type = get, + #iq{type = Type, sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{max = 2, + rsm = #rsm_set{max = 3, before = First}}]}), maybe_recv_iq_result(NS, I3), lists:foreach( @@ -1819,17 +1829,18 @@ mam_query_rsm(Config, NS) -> [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]}) - end, lists:seq(2, 3)), + end, lists:seq(1, 3)), if NS == ?NS_MAM_TMP -> ?recv1(#iq{type = result, id = I3, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); true -> ?recv1(#message{ - sub_els = [#mam_fin{rsm = #rsm_set{count = 10}}]}) + sub_els = [#mam_fin{complete = true, + rsm = #rsm_set{count = 10}}]}) end, %% Getting the item count. Should be 5 (or 10). I4 = send(Config, - #iq{type = get, + #iq{type = Type, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 0}}]}), maybe_recv_iq_result(NS, I4), @@ -1843,9 +1854,40 @@ mam_query_rsm(Config, NS) -> true -> ?recv1(#message{ sub_els = [#mam_fin{ + complete = false, rsm = #rsm_set{count = 10, first = undefined, last = undefined}}]}) + end, + %% Should receive 2 last messages + I5 = send(Config, + #iq{type = Type, + sub_els = [#mam_query{xmlns = NS, + rsm = #rsm_set{max = 2, + before = none}}]}), + maybe_recv_iq_result(NS, I5), + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + ?recv1(#message{to = MyJID, + sub_els = + [#mam_result{ + xmlns = NS, + sub_els = + [#forwarded{ + delay = #delay{}, + sub_els = + [#message{ + from = MyJID, to = Peer, + body = [Text]}]}]}]}) + end, lists:seq(4, 5)), + if NS == ?NS_MAM_TMP -> + ?recv1(#iq{type = result, id = I5, + sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); + true -> + ?recv1(#message{ + sub_els = [#mam_fin{complete = false, + rsm = #rsm_set{count = 10}}]}) end. client_state_master(Config) -> diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index b1c95ffdf..111d72f79 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -12,8 +12,7 @@ host_config: mod_announce: db_type: odbc access: local - mod_blocking: - db_type: odbc + mod_blocking: [] mod_caps: db_type: odbc mod_last: @@ -65,8 +64,7 @@ Welcome to this XMPP server." mod_announce: db_type: odbc access: local - mod_blocking: - db_type: odbc + mod_blocking: [] mod_caps: db_type: odbc mod_last: @@ -124,8 +122,7 @@ Welcome to this XMPP server." mod_announce: db_type: odbc access: local - mod_blocking: - db_type: odbc + mod_blocking: [] mod_caps: db_type: odbc mod_last: @@ -176,8 +173,7 @@ Welcome to this XMPP server." mod_announce: db_type: internal access: local - mod_blocking: - db_type: internal + mod_blocking: [] mod_caps: db_type: internal mod_last: @@ -232,8 +228,7 @@ Welcome to this XMPP server." mod_announce: db_type: riak access: local - mod_blocking: - db_type: riak + mod_blocking: [] mod_caps: db_type: riak mod_last: diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl index 0dbc62808..987eb7c41 100644 --- a/tools/xmpp_codec.erl +++ b/tools/xmpp_codec.erl @@ -2082,7 +2082,7 @@ encode({mam_result, _, _, _, _} = Result) -> encode_mam_result(Result, []); encode({mam_prefs, _, _, _, _} = Prefs) -> encode_mam_prefs(Prefs, []); -encode({mam_fin, _, _} = Fin) -> +encode({mam_fin, _, _, _, _} = Fin) -> encode_mam_fin(Fin, [{<<"xmlns">>, <<"urn:xmpp:mam:0">>}]); encode({forwarded, _, _} = Forwarded) -> @@ -2308,7 +2308,7 @@ get_ns({rsm_first, _, _}) -> get_ns({rsm_set, _, _, _, _, _, _, _}) -> <<"http://jabber.org/protocol/rsm">>; get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>; -get_ns({mam_fin, _, _}) -> <<"urn:xmpp:mam:0">>; +get_ns({mam_fin, _, _, _, _}) -> <<"urn:xmpp:mam:0">>; get_ns({forwarded, _, _}) -> <<"urn:xmpp:forward:0">>; get_ns({carbons_disable}) -> <<"urn:xmpp:carbons:2">>; get_ns({carbons_enable}) -> <<"urn:xmpp:carbons:2">>; @@ -2505,7 +2505,7 @@ pp(mam_query, 7) -> pp(mam_archived, 2) -> [by, id]; pp(mam_result, 4) -> [xmlns, queryid, id, sub_els]; pp(mam_prefs, 4) -> [xmlns, default, always, never]; -pp(mam_fin, 2) -> [id, rsm]; +pp(mam_fin, 4) -> [id, rsm, stable, complete]; pp(forwarded, 2) -> [delay, sub_els]; pp(carbons_disable, 0) -> []; pp(carbons_enable, 0) -> []; @@ -3709,9 +3709,10 @@ decode_mam_fin(__TopXMLNS, __IgnoreEls, {xmlel, <<"fin">>, _attrs, _els}) -> Rsm = decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, undefined), - Id = decode_mam_fin_attrs(__TopXMLNS, _attrs, - undefined), - {mam_fin, Id, Rsm}. + {Id, Stable, Complete} = + decode_mam_fin_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined), + {mam_fin, Id, Rsm, Stable, Complete}. decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [], Rsm) -> Rsm; @@ -3729,16 +3730,37 @@ decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [_ | _els], decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm). decode_mam_fin_attrs(__TopXMLNS, - [{<<"queryid">>, _val} | _attrs], _Id) -> - decode_mam_fin_attrs(__TopXMLNS, _attrs, _val); -decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id) -> - decode_mam_fin_attrs(__TopXMLNS, _attrs, Id); -decode_mam_fin_attrs(__TopXMLNS, [], Id) -> - decode_mam_fin_attr_queryid(__TopXMLNS, Id). - -encode_mam_fin({mam_fin, Id, Rsm}, _xmlns_attrs) -> + [{<<"queryid">>, _val} | _attrs], _Id, Stable, + Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, _val, Stable, + Complete); +decode_mam_fin_attrs(__TopXMLNS, + [{<<"stable">>, _val} | _attrs], Id, _Stable, + Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, _val, + Complete); +decode_mam_fin_attrs(__TopXMLNS, + [{<<"complete">>, _val} | _attrs], Id, Stable, + _Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable, + _val); +decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id, + Stable, Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable, + Complete); +decode_mam_fin_attrs(__TopXMLNS, [], Id, Stable, + Complete) -> + {decode_mam_fin_attr_queryid(__TopXMLNS, Id), + decode_mam_fin_attr_stable(__TopXMLNS, Stable), + decode_mam_fin_attr_complete(__TopXMLNS, Complete)}. + +encode_mam_fin({mam_fin, Id, Rsm, Stable, Complete}, + _xmlns_attrs) -> _els = lists:reverse('encode_mam_fin_$rsm'(Rsm, [])), - _attrs = encode_mam_fin_attr_queryid(Id, _xmlns_attrs), + _attrs = encode_mam_fin_attr_complete(Complete, + encode_mam_fin_attr_stable(Stable, + encode_mam_fin_attr_queryid(Id, + _xmlns_attrs))), {xmlel, <<"fin">>, _attrs, _els}. 'encode_mam_fin_$rsm'(undefined, _acc) -> _acc; @@ -3755,6 +3777,35 @@ encode_mam_fin_attr_queryid(undefined, _acc) -> _acc; encode_mam_fin_attr_queryid(_val, _acc) -> [{<<"queryid">>, _val} | _acc]. +decode_mam_fin_attr_stable(__TopXMLNS, undefined) -> + undefined; +decode_mam_fin_attr_stable(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"stable">>, <<"fin">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mam_fin_attr_stable(undefined, _acc) -> _acc; +encode_mam_fin_attr_stable(_val, _acc) -> + [{<<"stable">>, enc_bool(_val)} | _acc]. + +decode_mam_fin_attr_complete(__TopXMLNS, undefined) -> + undefined; +decode_mam_fin_attr_complete(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"complete">>, <<"fin">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_mam_fin_attr_complete(undefined, _acc) -> _acc; +encode_mam_fin_attr_complete(_val, _acc) -> + [{<<"complete">>, enc_bool(_val)} | _acc]. + decode_mam_prefs(__TopXMLNS, __IgnoreEls, {xmlel, <<"prefs">>, _attrs, _els}) -> {Never, Always} = decode_mam_prefs_els(__TopXMLNS, @@ -4746,10 +4797,10 @@ encode_rsm_before(Cdata, _xmlns_attrs) -> _attrs = _xmlns_attrs, {xmlel, <<"before">>, _attrs, _els}. -decode_rsm_before_cdata(__TopXMLNS, <<>>) -> undefined; +decode_rsm_before_cdata(__TopXMLNS, <<>>) -> none; decode_rsm_before_cdata(__TopXMLNS, _val) -> _val. -encode_rsm_before_cdata(undefined, _acc) -> _acc; +encode_rsm_before_cdata(none, _acc) -> _acc; encode_rsm_before_cdata(_val, _acc) -> [{xmlcdata, _val} | _acc]. diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl index 567946fe2..7996f6a11 100644 --- a/tools/xmpp_codec.hrl +++ b/tools/xmpp_codec.hrl @@ -280,7 +280,7 @@ units = [] :: [binary()]}). -record(rsm_set, {'after' :: binary(), - before :: binary(), + before :: 'none' | binary(), count :: non_neg_integer(), first :: #rsm_first{}, index :: non_neg_integer(), @@ -288,7 +288,9 @@ max :: non_neg_integer()}). -record(mam_fin, {id :: binary(), - rsm :: #rsm_set{}}). + rsm :: #rsm_set{}, + stable :: any(), + complete :: any()}). -record(vcard_tel, {home = false :: boolean(), work = false :: boolean(), diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index dfa516ef5..38508ce6c 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2071,6 +2071,7 @@ -xml(rsm_before, #elem{name = <<"before">>, xmlns = <<"http://jabber.org/protocol/rsm">>, + cdata = #cdata{default = none}, result = '$cdata'}). -xml(rsm_last, @@ -2209,8 +2210,14 @@ -xml(mam_fin, #elem{name = <<"fin">>, xmlns = <<"urn:xmpp:mam:0">>, - result = {mam_fin, '$id', '$rsm'}, - attrs = [#attr{name = <<"queryid">>, label = '$id'}], + result = {mam_fin, '$id', '$rsm', '$stable', '$complete'}, + attrs = [#attr{name = <<"queryid">>, label = '$id'}, + #attr{name = <<"stable">>, label = '$stable', + dec = {dec_bool, []}, + enc = {enc_bool, []}}, + #attr{name = <<"complete">>, label = '$complete', + dec = {dec_bool, []}, + enc = {enc_bool, []}}], refs = [#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}]}). -xml(forwarded, |