24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-16 22:05:29 +02:00

Make common tests working again

This commit is contained in:
Evgeniy Khramtsov 2016-11-18 13:38:08 +03:00
parent 3765210698
commit b8dcc911a3
41 changed files with 3116 additions and 2519 deletions

13
include/mam_query.hrl Normal file
View File

@ -0,0 +1,13 @@
%% Created automatically by xdata generator (xdata_codec.erl)
%% Source: mam_query.xdata
%% Form type: urn:xmpp:mam:1
%% Document: XEP-0313
-type property() :: {'with', jid:jid()} |
{'start', erlang:timestamp()} |
{'end', erlang:timestamp()} |
{'withtext', binary()}.
-type result() :: [property()].
-type form() :: [property() | xdata_field()].

9
specs/mam_query.cfg Normal file
View File

@ -0,0 +1,9 @@
[{decode, [{<<"start">>, {xmpp_util, decode_timestamp, []}},
{<<"end">>, {xmpp_util, decode_timestamp, []}}]},
{specs, [{<<"start">>, "erlang:timestamp()"},
{<<"end">>, "erlang:timestamp()"}]}].
%% Local Variables:
%% mode: erlang
%% End:
%% vim: set filetype=erlang tabstop=8:

23
specs/mam_query.xdata Normal file
View File

@ -0,0 +1,23 @@
<form_type>
<name>urn:xmpp:mam:1</name>
<doc>XEP-0313</doc>
<desc>Form to query message archives</desc>
<field var='with'
type='jid-single'
label='User JID'/>
<field var='start'
type='text-single'
label='Search from the date'/>
<field var='end'
type='text-single'
label='Search until the date'/>
<field var='withtext'
type='text-single'
label='Search the text'/>
</form_type>
<!--
Local Variables:
mode: xml
End:
-->

View File

@ -177,15 +177,19 @@ unregister_iq_handler(Host, XMLNS) ->
refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
-spec bounce_resource_packet(jid(), jid(), stanza()) -> ok.
bounce_resource_packet(_From, _To, #presence{}) ->
-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
bounce_resource_packet(_From, #jid{lresource = <<"">>}, #presence{}) ->
ok;
bounce_resource_packet(_From, #jid{lresource = <<"">>},
#message{type = headline}) ->
ok;
bounce_resource_packet(From, To, Packet) ->
Lang = xmpp:get_lang(Packet),
Txt = <<"No available resource found">>,
Err = xmpp:make_error(Packet,
xmpp:err_item_not_found(Txt, Lang)),
ejabberd_router:route(To, From, Err).
ejabberd_router:route(To, From, Err),
stop.
%%====================================================================
%% gen_server callbacks
@ -283,7 +287,7 @@ do_route(From, To, Packet) ->
ejabberd_sm:route(From, To, Packet);
is_record(Packet, iq), To#jid.lresource == <<"">> ->
process_iq(From, To, Packet);
Type == result; Type == error; Type == headline ->
Type == result; Type == error ->
ok;
true ->
ejabberd_hooks:run(local_send_to_resource_hook,

View File

@ -12,7 +12,7 @@
-include("flex_offline.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_int(Val, Min, Max) ->
case list_to_integer(binary_to_list(Val)) of

220
src/mam_query.erl Normal file
View File

@ -0,0 +1,220 @@
%% Created automatically by xdata generator (xdata_codec.erl)
%% Source: mam_query.xdata
%% Form type: urn:xmpp:mam:1
%% Document: XEP-0313
-module(mam_query).
-export([decode/1, decode/2, encode/1, encode/2,
format_error/1]).
-include("xmpp_codec.hrl").
-include("mam_query.hrl").
-export_type([property/0, result/0, form/0]).
enc_jid(J) -> jid:to_string(J).
dec_jid(Val) ->
case jid:from_string(Val) of
error -> erlang:error(badarg);
J -> J
end.
format_error({form_type_mismatch, Type}) ->
<<"FORM_TYPE doesn't match '", Type/binary, "'">>;
format_error({bad_var_value, Var, Type}) ->
<<"Bad value of field '", Var/binary, "' of type '",
Type/binary, "'">>;
format_error({missing_value, Var, Type}) ->
<<"Missing value of field '", Var/binary, "' of type '",
Type/binary, "'">>;
format_error({too_many_values, Var, Type}) ->
<<"Too many values for field '", Var/binary,
"' of type '", Type/binary, "'">>;
format_error({unknown_var, Var, Type}) ->
<<"Unknown field '", Var/binary, "' of type '",
Type/binary, "'">>;
format_error({missing_required_var, Var, Type}) ->
<<"Missing required field '", Var/binary, "' of type '",
Type/binary, "'">>.
decode(Fs) -> decode(Fs, []).
decode(Fs, Acc) ->
case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
Fs)
of
false -> decode(Fs, Acc, []);
#xdata_field{values = [<<"urn:xmpp:mam:1">>]} ->
decode(Fs, Acc, []);
_ ->
erlang:error({?MODULE,
{form_type_mismatch, <<"urn:xmpp:mam:1">>}})
end.
encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
encode(List, Translate) when is_list(List) ->
Fs = [case Opt of
{with, Val} -> [encode_with(Val, Translate)];
{with, _, _} -> erlang:error({badarg, Opt});
{start, Val} -> [encode_start(Val, Translate)];
{start, _, _} -> erlang:error({badarg, Opt});
{'end', Val} -> [encode_end(Val, Translate)];
{'end', _, _} -> erlang:error({badarg, Opt});
{withtext, Val} -> [encode_withtext(Val, Translate)];
{withtext, _, _} -> erlang:error({badarg, Opt});
#xdata_field{} -> [Opt];
_ -> []
end
|| Opt <- List],
FormType = #xdata_field{var = <<"FORM_TYPE">>,
type = hidden, values = [<<"urn:xmpp:mam:1">>]},
[FormType | lists:flatten(Fs)].
decode([#xdata_field{var = <<"with">>, values = [Value]}
| Fs],
Acc, Required) ->
try dec_jid(Value) of
Result ->
decode(Fs, [{with, Result} | Acc],
lists:delete(<<"with">>, Required))
catch
_:_ ->
erlang:error({?MODULE,
{bad_var_value, <<"with">>, <<"urn:xmpp:mam:1">>}})
end;
decode([#xdata_field{var = <<"with">>, values = []} = F
| Fs],
Acc, Required) ->
decode([F#xdata_field{var = <<"with">>, values = [<<>>]}
| Fs],
Acc, Required);
decode([#xdata_field{var = <<"with">>} | _], _, _) ->
erlang:error({?MODULE,
{too_many_values, <<"with">>, <<"urn:xmpp:mam:1">>}});
decode([#xdata_field{var = <<"start">>,
values = [Value]}
| Fs],
Acc, Required) ->
try xmpp_util:decode_timestamp(Value) of
Result ->
decode(Fs, [{start, Result} | Acc],
lists:delete(<<"start">>, Required))
catch
_:_ ->
erlang:error({?MODULE,
{bad_var_value, <<"start">>, <<"urn:xmpp:mam:1">>}})
end;
decode([#xdata_field{var = <<"start">>, values = []} = F
| Fs],
Acc, Required) ->
decode([F#xdata_field{var = <<"start">>,
values = [<<>>]}
| Fs],
Acc, Required);
decode([#xdata_field{var = <<"start">>} | _], _, _) ->
erlang:error({?MODULE,
{too_many_values, <<"start">>, <<"urn:xmpp:mam:1">>}});
decode([#xdata_field{var = <<"end">>, values = [Value]}
| Fs],
Acc, Required) ->
try xmpp_util:decode_timestamp(Value) of
Result ->
decode(Fs, [{'end', Result} | Acc],
lists:delete(<<"end">>, Required))
catch
_:_ ->
erlang:error({?MODULE,
{bad_var_value, <<"end">>, <<"urn:xmpp:mam:1">>}})
end;
decode([#xdata_field{var = <<"end">>, values = []} = F
| Fs],
Acc, Required) ->
decode([F#xdata_field{var = <<"end">>, values = [<<>>]}
| Fs],
Acc, Required);
decode([#xdata_field{var = <<"end">>} | _], _, _) ->
erlang:error({?MODULE,
{too_many_values, <<"end">>, <<"urn:xmpp:mam:1">>}});
decode([#xdata_field{var = <<"withtext">>,
values = [Value]}
| Fs],
Acc, Required) ->
try Value of
Result ->
decode(Fs, [{withtext, Result} | Acc],
lists:delete(<<"withtext">>, Required))
catch
_:_ ->
erlang:error({?MODULE,
{bad_var_value, <<"withtext">>, <<"urn:xmpp:mam:1">>}})
end;
decode([#xdata_field{var = <<"withtext">>,
values = []} =
F
| Fs],
Acc, Required) ->
decode([F#xdata_field{var = <<"withtext">>,
values = [<<>>]}
| Fs],
Acc, Required);
decode([#xdata_field{var = <<"withtext">>} | _], _,
_) ->
erlang:error({?MODULE,
{too_many_values, <<"withtext">>,
<<"urn:xmpp:mam:1">>}});
decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
if Var /= <<"FORM_TYPE">> ->
erlang:error({?MODULE,
{unknown_var, Var, <<"urn:xmpp:mam:1">>}});
true -> decode(Fs, Acc, Required)
end;
decode([], _, [Var | _]) ->
erlang:error({?MODULE,
{missing_required_var, Var, <<"urn:xmpp:mam:1">>}});
decode([], Acc, []) -> Acc.
encode_with(Value, Translate) ->
Values = case Value of
undefined -> [];
Value -> [enc_jid(Value)]
end,
Opts = [],
#xdata_field{var = <<"with">>, values = Values,
required = false, type = 'jid-single', options = Opts,
desc = <<>>, label = Translate(<<"User JID">>)}.
encode_start(Value, Translate) ->
Values = case Value of
undefined -> [];
Value -> [Value]
end,
Opts = [],
#xdata_field{var = <<"start">>, values = Values,
required = false, type = 'text-single', options = Opts,
desc = <<>>,
label = Translate(<<"Search from the date">>)}.
encode_end(Value, Translate) ->
Values = case Value of
undefined -> [];
Value -> [Value]
end,
Opts = [],
#xdata_field{var = <<"end">>, values = Values,
required = false, type = 'text-single', options = Opts,
desc = <<>>,
label = Translate(<<"Search until the date">>)}.
encode_withtext(Value, Translate) ->
Values = case Value of
<<>> -> [];
Value -> [Value]
end,
Opts = [],
#xdata_field{var = <<"withtext">>, values = Values,
required = false, type = 'text-single', options = Opts,
desc = <<>>, label = Translate(<<"Search the text">>)}.

View File

@ -228,8 +228,8 @@ complete_packet(_From, Msg, _Direction) ->
-spec is_chat_message(stanza()) -> boolean().
is_chat_message(#message{type = chat}) ->
true;
is_chat_message(#message{type = normal, body = Body}) ->
xmpp:get_text(Body) /= <<"">>;
is_chat_message(#message{type = normal, body = [_|_]}) ->
true;
is_chat_message(_) ->
false.

View File

@ -54,12 +54,13 @@
-callback delete_old_messages(binary() | global,
erlang:timestamp(),
all | chat | groupchat) -> any().
-callback extended_fields() -> [xdata_field()].
-callback extended_fields() -> [mam_query:property() | #xdata_field{}].
-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
jid(), binary(), recv | send) -> {ok, binary()} | any().
-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
-callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) ->
-callback select(binary(), jid(), jid(), mam_query:result(),
#rsm_set{} | undefined, chat | groupchat) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
%%%===================================================================
@ -259,8 +260,9 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
end.
set_stanza_id(Pkt, JID, ID) ->
Archived = #mam_archived{by = JID, id = ID},
StanzaID = #stanza_id{by = JID, id = ID},
BareJID = jid:remove_resource(JID),
Archived = #mam_archived{by = BareJID, id = ID},
StanzaID = #stanza_id{by = BareJID, id = ID},
NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
xmpp:set_els(Pkt, NewEls).
@ -308,43 +310,24 @@ muc_process_iq(#iq{type = get,
muc_process_iq(IQ, _MUCState) ->
IQ.
parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) ->
try
lists:foldl(
fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) ->
try xmpp_util:decode_timestamp(Data) of
{_, _, _} = TS -> Q#mam_query{start = TS}
catch _:{bad_timestamp, _} -> throw({error, <<"start">>})
end;
(#xdata_field{var = <<"end">>, values = [Data|_]}, Q) ->
try xmpp_util:decode_timestamp(Data) of
{_, _, _} = TS -> Q#mam_query{start = TS}
catch _:{bad_timestamp, _} -> throw({error, <<"end">>})
end;
(#xdata_field{var = <<"with">>, values = [Data|_]}, Q) ->
case jid:from_string(Data) of
error -> throw({error, <<"with">>});
J -> Q#mam_query{with = J}
end;
(#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) ->
case Data of
<<"">> -> throw({error, <<"withtext">>});
_ -> Q#mam_query{withtext = Data}
end;
(#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) ->
case Query#mam_query.xmlns of
NS -> Q;
_ -> throw({error, <<"FORM_TYPE">>})
end;
(#xdata_field{}, Acc) ->
Acc
end, Query, Fs)
catch throw:{error, Var} ->
Txt = io_lib:format("Incorrect value of field '~s'", [Var]),
{error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)}
parse_query(#mam_query{xmlns = ?NS_MAM_TMP,
start = Start, 'end' = End,
with = With, withtext = Text}, _Lang) ->
{ok, [{start, Start}, {'end', End},
{with, With}, {withtext, Text}]};
parse_query(#mam_query{xdata = #xdata{}} = Query, Lang) ->
X = xmpp_util:set_xdata_field(
#xdata_field{var = <<"FORM_TYPE">>,
type = hidden, values = [?NS_MAM_1]},
Query#mam_query.xdata),
try mam_query:decode(X#xdata.fields) of
Form -> {ok, Form}
catch _:{mam_query, Why} ->
Txt = mam_query:format_error(Why),
{error, xmpp:err_bad_request(Txt, Lang)}
end;
parse_query(Query, _Lang) ->
Query.
parse_query(#mam_query{}, _Lang) ->
{ok, []}.
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
@ -402,17 +385,16 @@ delete_old_messages(_TypeBin, _Days) ->
%%%===================================================================
process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
CommonFields = [#xdata_field{type = hidden,
var = <<"FORM_TYPE">>,
values = [NS]},
#xdata_field{type = 'jid-single', var = <<"with">>},
#xdata_field{type = 'text-single', var = <<"start">>},
#xdata_field{type = 'text-single', var = <<"end">>}],
Mod = gen_mod:db_mod(LServer, ?MODULE),
CommonFields = [{with, undefined},
{start, undefined},
{'end', undefined}],
ExtendedFields = Mod:extended_fields(),
Fields = CommonFields ++ ExtendedFields,
Form = #xdata{type = form, fields = Fields},
xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}).
Fields = mam_query:encode(CommonFields ++ ExtendedFields),
X = xmpp_util:set_xdata_field(
#xdata_field{var = <<"FORM_TYPE">>, type = hidden, values = [NS]},
#xdata{type = form, fields = Fields}),
xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = X}).
% Preference setting (both v0.2 & v0.3)
process_iq(#iq{type = set, lang = Lang,
@ -457,15 +439,17 @@ process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
{groupchat, _Role, _MUCState} ->
ok
end,
case parse_query(SubEl, Lang) of
case SubEl of
#mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
#mam_query{rsm = RSM, xmlns = NS} = Query ->
NewRSM = limit_max(RSM, NS),
NewQuery = Query#mam_query{rsm = NewRSM},
select_and_send(LServer, NewQuery, IQ, MsgType);
{error, Err} ->
xmpp:make_error(IQ, Err)
#mam_query{rsm = RSM, xmlns = NS} ->
case parse_query(SubEl, Lang) of
{ok, Query} ->
NewRSM = limit_max(RSM, NS),
select_and_send(LServer, Query, NewRSM, IQ, MsgType);
{error, Err} ->
xmpp:make_error(IQ, Err)
end
end.
should_archive(#message{type = error}, _LServer) ->
@ -493,57 +477,57 @@ should_archive(_, _LServer) ->
-spec strip_my_archived_tag(stanza(), binary()) -> stanza().
strip_my_archived_tag(Pkt, LServer) ->
NewPkt = xmpp:decode_els(
Pkt, ?NS_CLIENT,
fun(El) ->
case xmpp:get_name(El) of
<<"archived">> ->
xmpp:get_ns(El) == ?NS_MAM_TMP;
<<"stanza-id">> ->
xmpp:get_ns(El) == ?NS_SID_0;
_ ->
false
end
end),
Els = xmpp:get_els(Pkt),
NewEls = lists:filter(
fun(#mam_archived{by = By}) ->
By#jid.lserver /= LServer;
(#stanza_id{by = By}) ->
By#jid.lserver /= LServer;
(_) ->
true
end, xmpp:get_els(NewPkt)),
xmpp:set_els(NewPkt, NewEls).
fun(El) ->
Name = xmpp:get_name(El),
NS = xmpp:get_ns(El),
if (Name == <<"archived">> andalso NS == ?NS_MAM_TMP);
(Name == <<"stanza-id">> andalso NS == ?NS_SID_0) ->
try xmpp:decode(El) of
#mam_archived{by = By} ->
By#jid.lserver /= LServer;
#stanza_id{by = By} ->
By#jid.lserver /= LServer
catch _:{xmpp_codec, _} ->
false
end;
true ->
true
end
end, Els),
xmpp:set_els(Pkt, NewEls).
-spec strip_x_jid_tags(stanza()) -> stanza().
strip_x_jid_tags(Pkt) ->
NewPkt = xmpp:decode_els(
Pkt, ?NS_CLIENT,
Els = xmpp:get_els(Pkt),
NewEls = lists:filter(
fun(El) ->
case xmpp:get_name(El) of
<<"x">> ->
case xmpp:get_ns(El) of
?NS_MUC_USER -> true;
?NS_MUC_ADMIN -> true;
?NS_MUC_OWNER -> true;
_ -> false
end;
_ ->
false
end
end),
NewEls = lists:filter(
fun(El) ->
Items = case El of
#muc_user{items = Is} -> Is;
#muc_admin{items = Is} -> Is;
#muc_owner{items = Is} -> Is;
_ -> []
end,
not lists:any(fun(#muc_item{jid = JID}) ->
NS = xmpp:get_ns(El),
Items = if NS == ?NS_MUC_USER;
NS == ?NS_MUC_ADMIN;
NS == ?NS_MUC_OWNER ->
try xmpp:decode(El) of
#muc_user{items = Is} -> Is;
#muc_admin{items = Is} -> Is;
#muc_owner{items = Is} -> Is
catch _:{xmpp_codec, _} ->
[]
end;
true ->
[]
end,
not lists:any(
fun(#muc_item{jid = JID}) ->
JID /= undefined
end, Items)
end, xmpp:get_els(NewPkt)),
xmpp:set_els(NewPkt, NewEls).
end, Items);
_ ->
true
end
end, Els),
xmpp:set_els(Pkt, NewEls).
should_archive_peer(C2SState,
#archive_prefs{default = Default,
@ -625,7 +609,7 @@ has_no_store_hint(Message) ->
-spec is_resent(message(), binary()) -> boolean().
is_resent(Pkt, LServer) ->
case xmpp:get_subtag(Pkt, #stanza_id{}) of
#stanza_id{by = #jid{luser = <<>>, lserver = LServer}} ->
#stanza_id{by = #jid{lserver = LServer}} ->
true;
_ ->
false
@ -741,21 +725,22 @@ maybe_activate_mam(LUser, LServer) ->
ok
end.
select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) ->
select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) ->
{Msgs, IsComplete, Count} =
case MsgType of
chat ->
select(LServer, From, From, Query, MsgType);
select(LServer, From, From, Query, RSM, MsgType);
{groupchat, _Role, _MUCState} ->
select(LServer, From, To, Query, MsgType)
select(LServer, From, To, Query, RSM, MsgType)
end,
SortedMsgs = lists:keysort(2, Msgs),
send(SortedMsgs, Count, IsComplete, IQ).
select(_LServer, JidRequestor, JidArchive,
#mam_query{start = Start, 'end' = End, rsm = RSM},
select(_LServer, JidRequestor, JidArchive, Query, RSM,
{groupchat, _Role, #state{config = #config{mam = false},
history = History}} = MsgType) ->
Start = proplists:get_value(start, Query),
End = proplists:get_value('end', Query),
#lqueue{len = L, queue = Q} = History,
Msgs =
lists:flatmap(
@ -786,9 +771,9 @@ select(_LServer, JidRequestor, JidArchive,
_ ->
{Msgs, true, L}
end;
select(LServer, JidRequestor, JidArchive, Query, MsgType) ->
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType).
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType).
msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->

View File

@ -12,7 +12,7 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@ -132,8 +132,10 @@ get_prefs(LUser, LServer) ->
select(_LServer, JidRequestor,
#jid{luser = LUser, lserver = LServer} = JidArchive,
#mam_query{start = Start, 'end' = End,
with = With, rsm = RSM}, MsgType) ->
Query, RSM, MsgType) ->
Start = proplists:get_value(start, Query),
End = proplists:get_value('end', Query),
With = proplists:get_value(with, Query),
LWith = if With /= undefined -> jid:tolower(With);
true -> undefined
end,

View File

@ -14,7 +14,7 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@ -51,7 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
ok.
extended_fields() ->
[#xdata_field{type = 'text-single', var = <<"withtext">>}].
[{withtext, <<"">>}].
store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
TSinteger = p1_time_compat:system_time(micro_seconds),
@ -124,12 +124,12 @@ get_prefs(LUser, LServer) ->
end.
select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
MAMQuery, MsgType) ->
MAMQuery, RSM, MsgType) ->
User = case MsgType of
chat -> LUser;
{groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
end,
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery),
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM),
% TODO from XEP-0313 v0.2: "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
@ -139,7 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
case {ejabberd_sql:sql_query(LServer, Query),
ejabberd_sql:sql_query(LServer, CountQuery)} of
{{selected, _, Res}, {selected, _, [[Count]]}} ->
{Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm),
{Max, Direction, _} = get_max_direction_id(RSM),
{Res1, IsComplete} =
if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
if Direction == before ->
@ -194,9 +194,11 @@ usec_to_now(Int) ->
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
make_sql_query(User, LServer,
#mam_query{start = Start, 'end' = End, with = With,
withtext = WithText, rsm = RSM}) ->
make_sql_query(User, LServer, MAMQuery, RSM) ->
Start = proplists:get_value(start, MAMQuery),
End = proplists:get_value('end', MAMQuery),
With = proplists:get_value(with, MAMQuery),
WithText = proplists:get_value(withtext, MAMQuery),
{Max, Direction, ID} = get_max_direction_id(RSM),
ODBCType = ejabberd_config:get_option(
{sql_type, LServer},

View File

@ -99,7 +99,8 @@
-callback remove_expired_messages(binary()) -> {atomic, any()}.
-callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}.
-callback remove_user(binary(), binary()) -> {atomic, any()}.
-callback read_message_headers(binary(), binary()) -> any().
-callback read_message_headers(binary(), binary()) ->
[{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}].
-callback read_message(binary(), binary(), non_neg_integer()) ->
{ok, #offline_msg{}} | error.
-callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}.

View File

@ -88,7 +88,7 @@ read_message_headers(LUser, LServer) ->
fun(#offline_msg{from = From, to = To, packet = Pkt,
timestamp = TS}) ->
Seq = now_to_integer(TS),
{Seq, From, To, Pkt}
{Seq, From, To, TS, Pkt}
end, Rs),
lists:keysort(1, Hdrs);
_Err ->

View File

@ -103,8 +103,9 @@ read_message_headers(LUser, LServer) ->
case xml_to_offline_msg(XML) of
{ok, #offline_msg{from = From,
to = To,
timestamp = TS,
packet = El}} ->
[{Seq, From, To, El}];
[{Seq, From, To, TS, El}];
_ ->
[]
end

View File

@ -330,7 +330,7 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
transaction(
LServer,
fun() ->
roster_subscribe_t(LUser, LServer, LJID, Item)
update_roster_t(LUser, LServer, LJID, Item)
end).
del_roster(LUser, LServer, LJID) ->

View File

@ -12,7 +12,7 @@
-include("muc_register.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_bool(<<"1">>) -> true;
dec_bool(<<"0">>) -> false;

View File

@ -12,7 +12,7 @@
-include("muc_request.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_enum(Val, Enums) ->
AtomVal = erlang:binary_to_existing_atom(Val, utf8),

View File

@ -12,7 +12,7 @@
-include("muc_roomconfig.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_int(Val, Min, Max) ->
case list_to_integer(binary_to_list(Val)) of

View File

@ -12,7 +12,7 @@
-include("muc_roominfo.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_int(Val, Min, Max) ->
case list_to_integer(binary_to_list(Val)) of

View File

@ -12,7 +12,7 @@
-include("pubsub_get_pending.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
format_error({form_type_mismatch, Type}) ->
<<"FORM_TYPE doesn't match '", Type/binary, "'">>;

View File

@ -12,7 +12,7 @@
-include("pubsub_node_config.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_int(Val, Min, Max) ->
case list_to_integer(binary_to_list(Val)) of

View File

@ -12,7 +12,7 @@
-include("pubsub_publish_options.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_enum(Val, Enums) ->
AtomVal = erlang:binary_to_existing_atom(Val, utf8),

View File

@ -12,7 +12,7 @@
-include("pubsub_subscribe_authorization.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_bool(<<"1">>) -> true;
dec_bool(<<"0">>) -> false;

View File

@ -12,7 +12,7 @@
-include("pubsub_subscribe_options.hrl").
-export_type([{property, 0}, {result, 0}, {form, 0}]).
-export_type([property/0, result/0, form/0]).
dec_enum(Val, Enums) ->
AtomVal = erlang:binary_to_existing_atom(Val, utf8),

View File

@ -11,7 +11,8 @@
%% API
-export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
is_standalone_chat_state/1, get_xdata_values/2,
has_xdata_var/2, make_adhoc_response/1, make_adhoc_response/2,
set_xdata_field/2, has_xdata_var/2,
make_adhoc_response/1, make_adhoc_response/2,
decode_timestamp/1, encode_timestamp/1]).
-include("xmpp.hrl").
@ -78,6 +79,12 @@ get_xdata_values(Var, #xdata{fields = Fields}) ->
false -> []
end.
-spec set_xdata_field(xdata_field(), xdata()) -> xdata().
set_xdata_field(Field, #xdata{fields = Fields} = X) ->
NewFields = lists:keystore(Field#xdata_field.var, #xdata_field.var,
Fields, Field),
X#xdata{fields = NewFields}.
-spec has_xdata_var(binary(), xdata()) -> boolean().
has_xdata_var(Var, #xdata{fields = Fields}) ->
lists:keymember(Var, #xdata_field.var, Fields).

61
test/announce_tests.erl Normal file
View File

@ -0,0 +1,61 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(announce_tests).
%% API
-compile(export_all).
-import(suite, [server_jid/1, send_recv/2, recv_message/1, disconnect/1,
send/2, wait_for_master/1, wait_for_slave/1]).
-include("suite.hrl").
%%%===================================================================
%%% API
%%%===================================================================
%%%===================================================================
%%% Single user tests
%%%===================================================================
single_cases() ->
{announce_single, [sequence], []}.
%%%===================================================================
%%% Master-slave tests
%%%===================================================================
master_slave_cases() ->
{announce_master_slave, [sequence],
[master_slave_test(set_motd)]}.
set_motd_master(Config) ->
ServerJID = server_jid(Config),
MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
Body = xmpp:mk_text(<<"motd">>),
#presence{} = send_recv(Config, #presence{}),
wait_for_slave(Config),
send(Config, #message{to = MotdJID, body = Body}),
#message{from = ServerJID, body = Body} = recv_message(Config),
disconnect(Config).
set_motd_slave(Config) ->
ServerJID = server_jid(Config),
Body = xmpp:mk_text(<<"motd">>),
#presence{} = send_recv(Config, #presence{}),
wait_for_master(Config),
#message{from = ServerJID, body = Body} = recv_message(Config),
disconnect(Config).
%%%===================================================================
%%% Internal functions
%%%===================================================================
single_test(T) ->
list_to_atom("announce_" ++ atom_to_list(T)).
master_slave_test(T) ->
{list_to_atom("announce_" ++ atom_to_list(T)), [parallel],
[list_to_atom("announce_" ++ atom_to_list(T) ++ "_master"),
list_to_atom("announce_" ++ atom_to_list(T) ++ "_slave")]}.

202
test/carbons_tests.erl Normal file
View File

@ -0,0 +1,202 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(carbons_tests).
%% API
-compile(export_all).
-import(suite, [is_feature_advertised/2, disconnect/1, send_recv/2,
recv_presence/1, send/2, get_event/1, recv_message/1,
my_jid/1, wait_for_slave/1, wait_for_master/1,
put_event/2]).
-include("suite.hrl").
%%%===================================================================
%%% API
%%%===================================================================
%%%===================================================================
%%% Single user tests
%%%===================================================================
single_cases() ->
{carbons_single, [sequence],
[single_test(feature_enabled),
single_test(unsupported_iq)]}.
feature_enabled(Config) ->
true = is_feature_advertised(Config, ?NS_CARBONS_2),
disconnect(Config).
unsupported_iq(Config) ->
lists:foreach(
fun({Type, SubEl}) ->
#iq{type = error} =
send_recv(Config, #iq{type = Type, sub_els = [SubEl]})
end, [{Type, SubEl} ||
Type <- [get, set],
SubEl <- [#carbons_sent{forwarded = #forwarded{}},
#carbons_received{forwarded = #forwarded{}},
#carbons_private{}]] ++
[{get, SubEl} || SubEl <- [#carbons_enable{}, #carbons_disable{}]]),
disconnect(Config).
%%%===================================================================
%%% Master-slave tests
%%%===================================================================
master_slave_cases() ->
{carbons_master_slave, [sequence],
[master_slave_test(send_recv),
master_slave_test(enable_disable)]}.
send_recv_master(Config) ->
Peer = ?config(peer, Config),
prepare_master(Config),
ct:comment("Waiting for the peer to be ready"),
ready = get_event(Config),
send_messages(Config),
ct:comment("Waiting for the peer to disconnect"),
#presence{from = Peer, type = unavailable} = recv_presence(Config),
disconnect(Config).
send_recv_slave(Config) ->
prepare_slave(Config),
ok = enable(Config),
put_event(Config, ready),
recv_carbons(Config),
disconnect(Config).
enable_disable_master(Config) ->
prepare_master(Config),
ct:comment("Waiting for the peer to be ready"),
ready = get_event(Config),
send_messages(Config),
disconnect(Config).
enable_disable_slave(Config) ->
Peer = ?config(peer, Config),
prepare_slave(Config),
ok = enable(Config),
ok = disable(Config),
put_event(Config, ready),
ct:comment("Waiting for the peer to disconnect"),
#presence{from = Peer, type = unavailable} = recv_presence(Config),
disconnect(Config).
%%%===================================================================
%%% Internal functions
%%%===================================================================
single_test(T) ->
list_to_atom("carbons_" ++ atom_to_list(T)).
master_slave_test(T) ->
{list_to_atom("carbons_" ++ atom_to_list(T)), [parallel],
[list_to_atom("carbons_" ++ atom_to_list(T) ++ "_master"),
list_to_atom("carbons_" ++ atom_to_list(T) ++ "_slave")]}.
prepare_master(Config) ->
MyJID = my_jid(Config),
Peer = ?config(peer, Config),
#presence{from = MyJID} = send_recv(Config, #presence{priority = 10}),
wait_for_slave(Config),
ct:comment("Receiving initial presence from the peer"),
#presence{from = Peer} = recv_presence(Config),
Config.
prepare_slave(Config) ->
Peer = ?config(peer, Config),
MyJID = my_jid(Config),
ok = enable(Config),
wait_for_master(Config),
#presence{from = MyJID} = send_recv(Config, #presence{priority = 5}),
ct:comment("Receiving initial presence from the peer"),
#presence{from = Peer} = recv_presence(Config),
Config.
send_messages(Config) ->
Server = ?config(server, Config),
MyJID = my_jid(Config),
JID = jid:make(randoms:get_string(), Server),
lists:foreach(
fun({send, #message{type = Type} = Msg}) ->
I = send(Config, Msg#message{to = JID}),
if Type /= error ->
#message{id = I, type = error} = recv_message(Config);
true ->
ok
end;
({recv, #message{} = Msg}) ->
ejabberd_router:route(
JID, MyJID, Msg#message{from = JID, to = MyJID}),
ct:comment("Receiving message ~s", [xmpp:pp(Msg)]),
#message{} = recv_message(Config)
end, message_iterator(Config)).
recv_carbons(Config) ->
Peer = ?config(peer, Config),
BarePeer = jid:remove_resource(Peer),
MyJID = my_jid(Config),
lists:foreach(
fun({_, #message{sub_els = [#hint{type = 'no-copy'}]}}) ->
ok;
({_, #message{sub_els = [#carbons_private{}]}}) ->
ok;
({_, #message{sub_els = [#carbons_sent{}]}}) ->
ok;
({_, #message{sub_els = [#carbons_received{}]}}) ->
ok;
({_, #message{type = T}}) when T /= normal, T /= chat ->
ok;
({Dir, #message{type = T, body = Body} = M})
when (T == chat) or (T == normal andalso Body /= []) ->
ct:comment("Receiving carbon ~s", [xmpp:pp(M)]),
#message{from = BarePeer, to = MyJID} = CarbonMsg =
recv_message(Config),
case Dir of
send ->
#carbons_sent{forwarded = #forwarded{xml_els = [El]}} =
xmpp:get_subtag(CarbonMsg, #carbons_sent{}),
#message{body = Body} = xmpp:decode(El);
recv ->
#carbons_received{forwarded = #forwarded{xml_els = [El]}}=
xmpp:get_subtag(CarbonMsg, #carbons_received{}),
#message{body = Body} = xmpp:decode(El)
end;
(_) ->
false
end, message_iterator(Config)).
enable(Config) ->
case send_recv(
Config, #iq{type = set,
sub_els = [#carbons_enable{}]}) of
#iq{type = result, sub_els = []} ->
ok;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
disable(Config) ->
case send_recv(
Config, #iq{type = set,
sub_els = [#carbons_disable{}]}) of
#iq{type = result, sub_els = []} ->
ok;
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
message_iterator(_Config) ->
[{Dir, #message{type = Type, body = Body, sub_els = Els}}
|| Dir <- [send, recv],
Type <- [error, chat, normal, groupchat, headline],
Body <- [[], xmpp:mk_text(<<"body">>)],
Els <- [[],
[#hint{type = 'no-copy'}],
[#carbons_private{}],
[#carbons_sent{forwarded = #forwarded{}}],
[#carbons_received{forwarded = #forwarded{}}]]].

147
test/csi_tests.erl Normal file
View File

<
@ -0,0 +1,147 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(csi_tests).
%% API
-compile(export_all).
-import(suite, [disconnect/1, wait_for_slave/1, wait_for_master/1,
send/2, send_recv/2, recv_presence/1, recv_message/1,
server_jid/1]).
-include("suite.hrl").
%%%===================================================================