aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README4
-rw-r--r--rebar.config.script1
-rw-r--r--src/ejabberd_config.erl3
-rw-r--r--src/ejabberd_web_admin.erl69
-rw-r--r--src/mod_http_fileserver.erl13
-rw-r--r--src/mod_mam.erl317
-rw-r--r--src/mod_muc.erl3
-rw-r--r--src/mod_muc_admin.erl14
-rw-r--r--src/mod_muc_room.erl40
-rw-r--r--test/ejabberd_SUITE.erl74
-rw-r--r--test/ejabberd_SUITE_data/ejabberd.yml15
-rw-r--r--tools/xmpp_codec.erl85
-rw-r--r--tools/xmpp_codec.hrl6
-rw-r--r--tools/xmpp_codec.spec11
14 files changed, 419 insertions, 236 deletions
diff --git a/README b/README
index 2f78d1973..31a76778b 100644
--- a/README
+++ b/README
@@ -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,