xmpp.chapril.org-ejabberd/test/ejabberd_SUITE.erl

1077 lines
36 KiB
Erlang
Raw Normal View History

%%%-------------------------------------------------------------------
2017-01-03 15:58:52 +01:00
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 2 Jun 2013 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
2017-01-03 15:58:52 +01:00
%%%
%%%
2020-01-28 13:34:02 +01:00
%%% ejabberd, Copyright (C) 2002-2020 ProcessOne
2017-01-03 15:58:52 +01:00
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_SUITE).
-compile(export_all).
2016-10-17 12:37:23 +02:00
-import(suite, [init_config/1, connect/1, disconnect/1, recv_message/1,
recv/1, recv_presence/1, send/2, send_recv/2, my_jid/1,
server_jid/1, pubsub_jid/1, proxy_jid/1, muc_jid/1,
muc_room_jid/1, my_muc_jid/1, peer_muc_jid/1,
mix_jid/1, mix_room_jid/1, get_features/2, recv_iq/1,
re_register/1, is_feature_advertised/2, subscribe_to_events/1,
is_feature_advertised/3, set_opt/3,
auth_SASL/2, auth_SASL/3, auth_SASL/4,
2016-10-17 12:37:23 +02:00
wait_for_master/1, wait_for_slave/1, flush/1,
make_iq_result/1, start_event_relay/0, alt_room_jid/1,
2013-06-26 19:55:29 +02:00
stop_event_relay/1, put_event/2, get_event/1,
2016-09-20 13:04:07 +02:00
bind/1, auth/1, auth/2, open_session/1, open_session/2,
2016-09-23 11:30:33 +02:00
zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1,
auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2,
set_roster/3, del_roster/1]).
2013-06-26 19:55:29 +02:00
-include("suite.hrl").
suite() ->
[{timetrap, {seconds, 120}}].
init_per_suite(Config) ->
2013-06-26 19:55:29 +02:00
NewConfig = init_config(Config),
DataDir = proplists:get_value(data_dir, NewConfig),
{ok, CWD} = file:get_cwd(),
2013-06-26 19:55:29 +02:00
ExtAuthScript = filename:join([DataDir, "extauth.py"]),
LDIFFile = filename:join([DataDir, "ejabberd.ldif"]),
2013-06-26 04:29:50 +02:00
{ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])),
{ok, _} = ldap_srv:start(LDIFFile),
2016-09-23 11:30:33 +02:00
inet_db:add_host({127,0,0,1}, [binary_to_list(?S2S_VHOST),
binary_to_list(?MNESIA_VHOST),
binary_to_list(?UPLOAD_VHOST)]),
2018-07-05 10:53:04 +02:00
inet_db:set_domain(binary_to_list(p1_rand:get_string())),
2016-09-23 11:30:33 +02:00
inet_db:set_lookup([file, native]),
start_ejabberd(NewConfig),
2013-06-26 19:55:29 +02:00
NewConfig.
2019-06-14 11:33:26 +02:00
start_ejabberd(_) ->
{ok, _} = application:ensure_all_started(ejabberd, transient).
end_per_suite(_Config) ->
application:stop(ejabberd).
init_per_group(Group, Config) ->
case lists:member(Group, ?BACKENDS) of
false ->
%% Not a backend related group, do default init:
do_init_per_group(Group, Config);
true ->
case proplists:get_value(backends, Config) of
all ->
%% All backends enabled
do_init_per_group(Group, Config);
Backends ->
%% Skipped backends that were not explicitely enabled
2019-06-14 11:33:26 +02:00
case lists:member(Group, Backends) of
true ->
do_init_per_group(Group, Config);
false ->
{skip, {disabled_backend, Group}}
end
end
end.
do_init_per_group(no_db, Config) ->
2013-06-26 19:55:29 +02:00
re_register(Config),
2016-10-17 12:37:23 +02:00
set_opt(persistent_room, false, Config);
do_init_per_group(mnesia, Config) ->
mod_muc:shutdown_rooms(?MNESIA_VHOST),
set_opt(server, ?MNESIA_VHOST, Config);
do_init_per_group(redis, Config) ->
2016-02-19 15:06:41 +01:00
mod_muc:shutdown_rooms(?REDIS_VHOST),
set_opt(server, ?REDIS_VHOST, Config);
do_init_per_group(mysql, Config) ->
2016-04-20 11:27:32 +02:00
case catch ejabberd_sql:sql_query(?MYSQL_VHOST, [<<"select 1;">>]) of
{selected, _, _} ->
mod_muc:shutdown_rooms(?MYSQL_VHOST),
clear_sql_tables(mysql, ?config(base_dir, Config)),
set_opt(server, ?MYSQL_VHOST, Config);
Err ->
{skip, {mysql_not_available, Err}}
end;
do_init_per_group(pgsql, Config) ->
2016-04-20 11:27:32 +02:00
case catch ejabberd_sql:sql_query(?PGSQL_VHOST, [<<"select 1;">>]) of
{selected, _, _} ->
mod_muc:shutdown_rooms(?PGSQL_VHOST),
clear_sql_tables(pgsql, ?config(base_dir, Config)),
set_opt(server, ?PGSQL_VHOST, Config);
Err ->
{skip, {pgsql_not_available, Err}}
end;
do_init_per_group(sqlite, Config) ->
2016-04-20 11:27:32 +02:00
case catch ejabberd_sql:sql_query(?SQLITE_VHOST, [<<"select 1;">>]) of
2015-03-16 19:53:19 +01:00
{selected, _, _} ->
mod_muc:shutdown_rooms(?SQLITE_VHOST),
set_opt(server, ?SQLITE_VHOST, Config);
Err ->
{skip, {sqlite_not_available, Err}}
end;
do_init_per_group(ldap, Config) ->
2013-06-21 19:23:56 +02:00
set_opt(server, ?LDAP_VHOST, Config);
do_init_per_group(extauth, Config) ->
2013-06-26 04:29:50 +02:00
set_opt(server, ?EXTAUTH_VHOST, Config);
2016-09-23 11:30:33 +02:00
do_init_per_group(s2s, Config) ->
2019-06-14 11:33:26 +02:00
ejabberd_config:set_option({s2s_use_starttls, ?COMMON_VHOST}, required),
ejabberd_config:set_option(ca_file, "ca.pem"),
2016-09-23 11:30:33 +02:00
Port = ?config(s2s_port, Config),
set_opt(server, ?COMMON_VHOST,
set_opt(xmlns, ?NS_SERVER,
set_opt(type, server,
set_opt(server_port, Port,
set_opt(stream_from, ?S2S_VHOST,
set_opt(lang, <<"">>, Config))))));
2016-09-21 09:45:11 +02:00
do_init_per_group(component, Config) ->
Server = ?config(server, Config),
Port = ?config(component_port, Config),
set_opt(xmlns, ?NS_COMPONENT,
set_opt(server, <<"component.", Server/binary>>,
set_opt(type, component,
set_opt(server_port, Port,
2016-09-23 11:30:33 +02:00
set_opt(stream_version, undefined,
2016-09-21 09:45:11 +02:00
set_opt(lang, <<"">>, Config))))));
2016-09-25 08:57:56 +02:00
do_init_per_group(GroupName, Config) ->
2013-06-15 15:28:14 +02:00
Pid = start_event_relay(),
2016-09-25 08:57:56 +02:00
NewConfig = set_opt(event_relay, Pid, Config),
case GroupName of
anonymous -> set_opt(anonymous, true, NewConfig);
_ -> NewConfig
end.
end_per_group(mnesia, _Config) ->
ok;
2016-02-19 15:06:41 +01:00
end_per_group(redis, _Config) ->
ok;
end_per_group(mysql, _Config) ->
ok;
end_per_group(pgsql, _Config) ->
ok;
2015-03-16 19:53:19 +01:00
end_per_group(sqlite, _Config) ->
ok;
end_per_group(no_db, _Config) ->
ok;
2013-06-21 19:23:56 +02:00
end_per_group(ldap, _Config) ->
ok;
2013-06-26 04:29:50 +02:00
end_per_group(extauth, _Config) ->
ok;
2016-09-21 09:45:11 +02:00
end_per_group(component, _Config) ->
ok;
2019-06-14 11:33:26 +02:00
end_per_group(s2s, Config) ->
Server = ?config(server, Config),
ejabberd_config:set_option({s2s_use_starttls, Server}, false);
2013-06-15 15:28:14 +02:00
end_per_group(_GroupName, Config) ->
stop_event_relay(Config),
2016-09-25 08:57:56 +02:00
set_opt(anonymous, false, Config).
init_per_testcase(stop_ejabberd, Config) ->
NewConfig = set_opt(resource, <<"">>,
set_opt(anonymous, true, Config)),
open_session(bind(auth(connect(NewConfig))));
init_per_testcase(TestCase, OrigConfig) ->
ct:print(80, "Testcase '~p' starting", [TestCase]),
Test = atom_to_list(TestCase),
IsMaster = lists:suffix("_master", Test),
IsSlave = lists:suffix("_slave", Test),
if IsMaster or IsSlave ->
subscribe_to_events(OrigConfig);
true ->
ok
end,
2016-09-20 13:04:07 +02:00
TestGroup = proplists:get_value(
name, ?config(tc_group_properties, OrigConfig)),
2013-06-15 18:45:18 +02:00
Server = ?config(server, OrigConfig),
2016-09-20 13:04:07 +02:00
Resource = case TestGroup of
2016-09-25 08:57:56 +02:00
anonymous ->
<<"">>;
2016-09-20 13:04:07 +02:00
legacy_auth ->
2018-07-05 10:53:04 +02:00
p1_rand:get_string();
2016-09-20 13:04:07 +02:00
_ ->
?config(resource, OrigConfig)
end,
2014-07-20 20:43:16 +02:00
MasterResource = ?config(master_resource, OrigConfig),
SlaveResource = ?config(slave_resource, OrigConfig),
2016-10-17 12:37:23 +02:00
Mode = if IsSlave -> slave;
IsMaster -> master;
true -> single
end,
2014-07-20 20:43:16 +02:00
IsCarbons = lists:prefix("carbons_", Test),
2016-09-20 13:04:07 +02:00
IsReplaced = lists:prefix("replaced_", Test),
User = if IsReplaced -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>;
2016-11-18 11:38:08 +01:00
IsCarbons and not (IsMaster or IsSlave) ->
<<"test_single!#$%^*()`~+-;_=[]{}|\\">>;
2016-09-20 13:04:07 +02:00
IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>;
IsSlave -> <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>;
true -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>
2013-06-15 15:28:14 +02:00
end,
2016-10-17 12:37:23 +02:00
Nick = if IsSlave -> ?config(slave_nick, OrigConfig);
IsMaster -> ?config(master_nick, OrigConfig);
true -> ?config(nick, OrigConfig)
end,
2014-07-20 20:43:16 +02:00
MyResource = if IsMaster and IsCarbons -> MasterResource;
IsSlave and IsCarbons -> SlaveResource;
true -> Resource
end,
Slave = if IsCarbons ->
jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, SlaveResource);
2016-09-20 13:04:07 +02:00
IsReplaced ->
jid:make(User, Server, Resource);
2014-07-20 20:43:16 +02:00
true ->
jid:make(<<"test_slave!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource)
2014-07-20 20:43:16 +02:00
end,
Master = if IsCarbons ->
jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, MasterResource);
2016-09-20 13:04:07 +02:00
IsReplaced ->
jid:make(User, Server, Resource);
2014-07-20 20:43:16 +02:00
true ->
jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource)
2014-07-20 20:43:16 +02:00
end,
2016-10-17 12:37:23 +02:00
Config1 = set_opt(user, User,
set_opt(slave, Slave,
set_opt(master, Master,
set_opt(resource, MyResource,
set_opt(nick, Nick,
set_opt(mode, Mode, OrigConfig)))))),
Config2 = if IsSlave ->
set_opt(peer_nick, ?config(master_nick, Config1), Config1);
IsMaster ->
set_opt(peer_nick, ?config(slave_nick, Config1), Config1);
true ->
Config1
end,
Config = if IsSlave -> set_opt(peer, Master, Config2);
IsMaster -> set_opt(peer, Slave, Config2);
true -> Config2
end,
2016-09-20 13:04:07 +02:00
case Test of
"test_connect" ++ _ ->
Config;
2017-01-23 11:51:05 +01:00
"test_legacy_auth_feature" ->
connect(Config);
2016-09-20 13:04:07 +02:00
"test_legacy_auth" ++ _ ->
2016-09-23 11:30:33 +02:00
init_stream(set_opt(stream_version, undefined, Config));
2016-09-20 13:04:07 +02:00
"test_auth" ++ _ ->
connect(Config);
2016-09-20 13:04:07 +02:00
"test_starttls" ++ _ ->
2013-06-14 16:16:32 +02:00
connect(Config);
2016-09-20 13:04:07 +02:00
"test_zlib" ->
2019-06-14 11:33:26 +02:00
auth(connect(starttls(connect(Config))));
2016-09-20 13:04:07 +02:00
"test_register" ->
2013-06-14 10:47:50 +02:00
connect(Config);
2016-09-20 13:04:07 +02:00
"auth_md5" ->
connect(Config);
2016-09-20 13:04:07 +02:00
"auth_plain" ->
connect(Config);
"auth_external" ++ _ ->
connect(Config);
2016-09-20 13:04:07 +02:00
"unauthenticated_" ++ _ ->
connect(Config);
"test_bind" ->
auth(connect(Config));
2016-09-20 13:04:07 +02:00
"sm_resume" ->
2014-07-22 11:59:31 +02:00
auth(connect(Config));
2016-09-20 13:04:07 +02:00
"sm_resume_failed" ->
auth(connect(Config));
2016-09-20 13:04:07 +02:00
"test_open_session" ->
bind(auth(connect(Config)));
2016-09-20 13:04:07 +02:00
"replaced" ++ _ ->
auth(connect(Config));
2013-06-15 15:28:14 +02:00
_ when IsMaster or IsSlave ->
Password = ?config(password, Config),
ejabberd_auth:try_register(User, Server, Password),
open_session(bind(auth(connect(Config))));
2016-09-23 11:30:33 +02:00
_ when TestGroup == s2s_tests ->
auth(connect(starttls(connect(Config))));
_ ->
open_session(bind(auth(connect(Config))))
end.
end_per_testcase(_TestCase, _Config) ->
ok.
2016-09-20 13:04:07 +02:00
legacy_auth_tests() ->
{legacy_auth, [parallel],
2017-01-23 11:51:05 +01:00
[test_legacy_auth_feature,
test_legacy_auth,
2016-09-20 13:04:07 +02:00
test_legacy_auth_digest,
test_legacy_auth_no_resource,
test_legacy_auth_bad_jid,
test_legacy_auth_fail]}.
2013-06-21 19:23:56 +02:00
no_db_tests() ->
2016-09-25 08:57:56 +02:00
[{anonymous, [parallel],
2016-09-20 13:04:07 +02:00
[test_connect_bad_xml,
2016-09-23 11:30:33 +02:00
test_connect_unexpected_xml,
2016-09-20 13:04:07 +02:00
test_connect_unknown_ns,
2016-09-21 09:45:11 +02:00
test_connect_bad_xmlns,
2016-09-20 13:04:07 +02:00
test_connect_bad_ns_stream,
test_connect_bad_lang,
test_connect_bad_to,
test_connect_missing_to,
test_connect,
unauthenticated_iq,
2017-01-23 11:51:05 +01:00
unauthenticated_message,
unauthenticated_presence,
test_starttls,
test_auth,
2019-06-14 11:33:26 +02:00
test_zlib,
test_bind,
test_open_session,
2016-09-20 13:04:07 +02:00
codec_failure,
unsupported_query,
bad_nonza,
invalid_from,
ping,
version,
time,
stats,
disco]},
{presence_and_s2s, [sequence],
[test_auth_fail,
presence,
s2s_dialback,
s2s_optional,
2019-06-14 11:33:26 +02:00
s2s_required]},
auth_external,
auth_external_no_jid,
auth_external_no_user,
auth_external_malformed_jid,
auth_external_wrong_jid,
auth_external_wrong_server,
auth_external_invalid_cert,
jidprep_tests:single_cases(),
2016-11-18 11:38:08 +01:00
sm_tests:single_cases(),
2017-02-21 10:38:03 +01:00
sm_tests:master_slave_cases(),
2016-10-17 12:37:23 +02:00
muc_tests:single_cases(),
muc_tests:master_slave_cases(),
2016-11-18 11:38:08 +01:00
proxy65_tests:single_cases(),
proxy65_tests:master_slave_cases(),
2018-05-17 11:02:00 +02:00
replaced_tests:master_slave_cases(),
2019-06-14 11:33:26 +02:00
upload_tests:single_cases(),
carbons_tests:single_cases(),
carbons_tests:master_slave_cases()].
2016-02-19 15:06:41 +01:00
db_tests(DB) when DB == mnesia; DB == redis ->
2014-07-09 14:38:45 +02:00
[{single_user, [sequence],
[test_register,
2016-09-20 13:04:07 +02:00
legacy_auth_tests(),
2014-07-09 14:38:45 +02:00
auth_plain,
auth_md5,
presence_broadcast,
last,
2016-11-07 08:10:57 +01:00
roster_tests:single_cases(),
2018-11-23 14:24:44 +01:00
private_tests:single_cases(),
privacy_tests:single_cases(),
2016-11-18 11:38:08 +01:00
vcard_tests:single_cases(),
pubsub_tests:single_cases(),
2016-10-17 12:37:23 +02:00
muc_tests:single_cases(),
2016-11-08 13:15:19 +01:00
offline_tests:single_cases(),
2016-11-18 11:38:08 +01:00
mam_tests:single_cases(),
csi_tests:single_cases(),
push_tests:single_cases(),
2014-07-09 14:38:45 +02:00
test_unregister]},
2016-10-17 12:37:23 +02:00
muc_tests:master_slave_cases(),
privacy_tests:master_slave_cases(),
2016-11-18 11:38:08 +01:00
pubsub_tests:master_slave_cases(),
2016-11-07 08:10:57 +01:00
roster_tests:master_slave_cases(),
offline_tests:master_slave_cases(DB),
2016-11-18 11:38:08 +01:00
mam_tests:master_slave_cases(),
vcard_tests:master_slave_cases(),
announce_tests:master_slave_cases(),
csi_tests:master_slave_cases(),
push_tests:master_slave_cases()];
db_tests(DB) ->
2014-07-09 14:38:45 +02:00
[{single_user, [sequence],
[test_register,
2016-09-20 13:04:07 +02:00
legacy_auth_tests(),
2016-10-17 12:37:23 +02:00
auth_plain,
auth_md5,
presence_broadcast,
last,
2016-11-07 08:10:57 +01:00
roster_tests:single_cases(),
2018-11-23 14:24:44 +01:00
private_tests:single_cases(),
privacy_tests:single_cases(),
2016-11-18 11:38:08 +01:00
vcard_tests:single_cases(),
pubsub_tests:single_cases(),
2016-10-17 12:37:23 +02:00
muc_tests:single_cases(),
2016-11-08 13:15:19 +01:00
offline_tests:single_cases(),
2016-11-18 11:38:08 +01:00
mam_tests:single_cases(),
2017-10-26 19:11:43 +02:00
push_tests:single_cases(),
2016-10-17 12:37:23 +02:00
test_unregister]},
muc_tests:master_slave_cases(),
privacy_tests:master_slave_cases(),
2016-11-18 11:38:08 +01:00
pubsub_tests:master_slave_cases(),
2016-11-07 08:10:57 +01:00
roster_tests:master_slave_cases(),
offline_tests:master_slave_cases(DB),
2016-11-18 11:38:08 +01:00
mam_tests:master_slave_cases(),
vcard_tests:master_slave_cases(),
2017-03-30 09:31:51 +02:00
announce_tests:master_slave_cases(),
2017-10-26 19:11:43 +02:00
push_tests:master_slave_cases()].
2014-07-09 14:38:45 +02:00
2013-06-21 19:23:56 +02:00
ldap_tests() ->
[{ldap_tests, [sequence],
[test_auth,
2016-09-20 13:04:07 +02:00
test_auth_fail,
vcard_get,
ldap_shared_roster_get]}].
2013-06-21 19:23:56 +02:00
2013-06-26 04:29:50 +02:00
extauth_tests() ->
[{extauth_tests, [sequence],
[test_auth,
2016-09-20 13:04:07 +02:00
test_auth_fail,
2013-06-26 04:29:50 +02:00
test_unregister]}].
2016-09-21 09:45:11 +02:00
component_tests() ->
2016-09-23 11:30:33 +02:00
[{component_connect, [parallel],
2016-09-21 09:45:11 +02:00
[test_connect_bad_xml,
2016-09-23 11:30:33 +02:00
test_connect_unexpected_xml,
2016-09-21 09:45:11 +02:00
test_connect_unknown_ns,
test_connect_bad_xmlns,
test_connect_bad_ns_stream,
test_connect_missing_to,
test_connect,
test_auth,
2016-09-23 11:30:33 +02:00
test_auth_fail]},
{component_tests, [sequence],
2017-01-23 11:51:05 +01:00
[test_missing_from,
test_missing_to,
2016-09-23 11:30:33 +02:00
test_invalid_from,
test_component_send,
bad_nonza,
codec_failure]}].
s2s_tests() ->
[{s2s_connect, [parallel],
[test_connect_bad_xml,
test_connect_unexpected_xml,
test_connect_unknown_ns,
test_connect_bad_xmlns,
test_connect_bad_ns_stream,
test_connect,
test_connect_s2s_starttls_required,
test_starttls,
test_connect_s2s_unauthenticated_iq,
test_auth_starttls]},
{s2s_tests, [sequence],
2017-01-23 11:51:05 +01:00
[test_missing_from,
test_missing_to,
2016-09-23 11:30:33 +02:00
test_invalid_from,
2016-09-21 09:45:11 +02:00
bad_nonza,
codec_failure]}].
groups() ->
2013-06-21 19:23:56 +02:00
[{ldap, [sequence], ldap_tests()},
2013-06-26 04:29:50 +02:00
{extauth, [sequence], extauth_tests()},
2013-06-21 19:23:56 +02:00
{no_db, [sequence], no_db_tests()},
2016-09-21 09:45:11 +02:00
{component, [sequence], component_tests()},
2016-09-23 11:30:33 +02:00
{s2s, [sequence], s2s_tests()},
2014-07-09 14:38:45 +02:00
{mnesia, [sequence], db_tests(mnesia)},
2016-02-19 15:06:41 +01:00
{redis, [sequence], db_tests(redis)},
2014-07-09 14:38:45 +02:00
{mysql, [sequence], db_tests(mysql)},
{pgsql, [sequence], db_tests(pgsql)},
{sqlite, [sequence], db_tests(sqlite)}].
all() ->
[{group, ldap},
2013-06-21 19:23:56 +02:00
{group, no_db},
{group, mnesia},
{group, redis},
{group, mysql},
{group, pgsql},
{group, sqlite},
{group, extauth},
{group, component},
{group, s2s},
2013-06-15 18:45:18 +02:00
stop_ejabberd].
stop_ejabberd(Config) ->
ok = application:stop(ejabberd),
?recv1(#stream_error{reason = 'system-shutdown'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
Config.
2016-09-20 13:04:07 +02:00
test_connect_bad_xml(Config) ->
2016-09-23 11:30:33 +02:00
Config0 = tcp_connect(Config),
send_text(Config0, <<"<'/>">>),
Version = ?config(stream_version, Config0),
?recv1(#stream_start{version = Version}),
2017-12-21 11:43:09 +01:00
?recv1(#stream_error{reason = 'not-well-formed'}),
2016-09-20 13:04:07 +02:00
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
2016-09-23 11:30:33 +02:00
test_connect_unexpected_xml(Config) ->
Config0 = tcp_connect(Config),
send(Config0, #caps{}),
Version = ?config(stream_version, Config0),
?recv1(#stream_start{version = Version}),
?recv1(#stream_error{reason = 'invalid-xml'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
2016-09-20 13:04:07 +02:00
test_connect_unknown_ns(Config) ->
2016-09-21 09:45:11 +02:00
Config0 = init_stream(set_opt(xmlns, <<"wrong">>, Config)),
?recv1(#stream_error{reason = 'invalid-xml'}),
2016-09-20 13:04:07 +02:00
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
2016-09-21 09:45:11 +02:00
test_connect_bad_xmlns(Config) ->
2016-09-23 11:30:33 +02:00
NS = case ?config(type, Config) of
client -> ?NS_SERVER;
_ -> ?NS_CLIENT
end,
Config0 = init_stream(set_opt(xmlns, NS, Config)),
2016-09-20 13:04:07 +02:00
?recv1(#stream_error{reason = 'invalid-namespace'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
test_connect_bad_ns_stream(Config) ->
Config0 = init_stream(set_opt(ns_stream, <<"wrong">>, Config)),
?recv1(#stream_error{reason = 'invalid-namespace'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
test_connect_bad_lang(Config) ->
2016-10-17 12:37:23 +02:00
Lang = iolist_to_binary(lists:duplicate(36, $x)),
Config0 = init_stream(set_opt(lang, Lang, Config)),
2018-11-23 12:11:14 +01:00
?recv1(#stream_error{reason = 'invalid-xml'}),
2016-09-20 13:04:07 +02:00
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
test_connect_bad_to(Config) ->
Config0 = init_stream(set_opt(server, <<"wrong.com">>, Config)),
?recv1(#stream_error{reason = 'host-unknown'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
test_connect_missing_to(Config) ->
Config0 = init_stream(set_opt(server, <<"">>, Config)),
?recv1(#stream_error{reason = 'improper-addressing'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config0).
test_connect(Config) ->
disconnect(connect(Config)).
2016-09-23 11:30:33 +02:00
test_connect_s2s_starttls_required(Config) ->
Config1 = connect(Config),
2017-01-23 11:51:05 +01:00
send(Config1, #presence{}),
2016-09-23 11:30:33 +02:00
?recv1(#stream_error{reason = 'policy-violation'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config1).
2016-09-21 09:45:11 +02:00
2016-09-23 11:30:33 +02:00
test_connect_s2s_unauthenticated_iq(Config) ->
Config1 = connect(starttls(connect(Config))),
unauthenticated_iq(Config1).
2016-09-21 09:45:11 +02:00
2013-06-14 16:16:32 +02:00
test_starttls(Config) ->
case ?config(starttls, Config) of
true ->
2016-09-23 11:30:33 +02:00
disconnect(connect(starttls(Config)));
2013-06-14 16:16:32 +02:00
_ ->
{skipped, 'starttls_not_available'}
end.
2013-06-14 16:45:04 +02:00
test_zlib(Config) ->
case ?config(compression, Config) of
[_|_] = Ms ->
case lists:member(<<"zlib">>, Ms) of
true ->
disconnect(zlib(Config));
2013-06-14 16:45:04 +02:00
false ->
{skipped, 'zlib_not_available'}
end;
_ ->
{skipped, 'compression_not_available'}
end.
2013-06-14 16:16:32 +02:00
test_register(Config) ->
2013-06-14 10:47:50 +02:00
case ?config(register, Config) of
true ->
disconnect(register(Config));
2013-06-14 16:16:32 +02:00
_ ->
{skipped, 'registration_not_available'}
2013-06-14 10:47:50 +02:00
end.
2013-06-14 16:16:32 +02:00
register(Config) ->
2013-06-16 20:00:19 +02:00
#iq{type = result,
2016-09-13 11:30:05 +02:00
sub_els = [#register{username = <<>>,
password = <<>>}]} =
2013-06-16 20:00:19 +02:00
send_recv(Config, #iq{type = get, to = server_jid(Config),
sub_els = [#register{}]}),
#iq{type = result, sub_els = []} =
send_recv(
Config,
#iq{type = set,
sub_els = [#register{username = ?config(user, Config),
password = ?config(password, Config)}]}),
Config.
2013-06-14 10:47:50 +02:00
2013-06-14 18:58:26 +02:00
test_unregister(Config) ->
case ?config(register, Config) of
true ->
2013-06-14 19:22:36 +02:00
try_unregister(Config);
2013-06-14 18:58:26 +02:00
_ ->
{skipped, 'registration_not_available'}
end.
try_unregister(Config) ->
2013-06-14 19:22:36 +02:00
true = is_feature_advertised(Config, ?NS_REGISTER),
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = []} =
send_recv(
Config,
#iq{type = set,
sub_els = [#register{remove = true}]}),
?recv1(#stream_error{reason = conflict}),
2013-06-14 18:58:26 +02:00
Config.
2017-01-23 11:51:05 +01:00
unauthenticated_presence(Config) ->
unauthenticated_packet(Config, #presence{}).
unauthenticated_message(Config) ->
unauthenticated_packet(Config, #message{}).
2016-09-20 13:04:07 +02:00
unauthenticated_iq(Config) ->
2017-01-23 11:51:05 +01:00
IQ = #iq{type = get, sub_els = [#disco_info{}]},
unauthenticated_packet(Config, IQ).
unauthenticated_packet(Config, Pkt) ->
2016-09-23 11:30:33 +02:00
From = my_jid(Config),
To = server_jid(Config),
2017-01-23 11:51:05 +01:00
send(Config, xmpp:set_from_to(Pkt, From, To)),
#stream_error{reason = 'not-authorized'} = recv(Config),
{xmlstreamend, <<"stream:stream">>} = recv(Config),
close_socket(Config).
2016-09-20 13:04:07 +02:00
bad_nonza(Config) ->
%% Unsupported and invalid nonza should be silently dropped.
send(Config, #caps{}),
send(Config, #stanza_error{type = wrong}),
disconnect(Config).
invalid_from(Config) ->
2018-07-05 10:53:04 +02:00
send(Config, #message{from = jid:make(p1_rand:get_string())}),
2016-09-20 13:04:07 +02:00
?recv1(#stream_error{reason = 'invalid-from'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config).
2017-01-23 11:51:05 +01:00
test_missing_from(Config) ->
2016-09-21 09:45:11 +02:00
Server = server_jid(Config),
2017-01-23 11:51:05 +01:00
send(Config, #message{to = Server}),
?recv1(#stream_error{reason = 'improper-addressing'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config).
test_missing_to(Config) ->
Server = server_jid(Config),
send(Config, #message{from = Server}),
?recv1(#stream_error{reason = 'improper-addressing'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config).
2016-09-21 09:45:11 +02:00
2016-09-23 11:30:33 +02:00
test_invalid_from(Config) ->
2018-07-05 10:53:04 +02:00
From = jid:make(p1_rand:get_string()),
To = jid:make(p1_rand:get_string()),
2017-01-23 11:51:05 +01:00
send(Config, #message{from = From, to = To}),
?recv1(#stream_error{reason = 'invalid-from'}),
?recv1({xmlstreamend, <<"stream:stream">>}),
close_socket(Config).
2016-09-21 09:45:11 +02:00
2016-09-23 11:30:33 +02:00
test_component_send(Config) ->
To = jid:make(?COMMON_VHOST),
From = server_jid(Config),
#iq{type = result, from = To, to = From} =
send_recv(Config, #iq{type = get, to = To, from = From,
sub_els = [#ping{}]}),
disconnect(Config).
s2s_dialback(Config) ->
2019-06-14 11:33:26 +02:00
Server = ?config(server, Config),
2017-08-07 15:38:17 +02:00
ejabberd_s2s:stop_s2s_connections(),
2019-06-14 11:33:26 +02:00
ejabberd_config:set_option({s2s_use_starttls, Server}, false),
ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, false),
ejabberd_config:set_option(ca_file, pkix:get_cafile()),
2016-09-23 11:30:33 +02:00
s2s_ping(Config).
s2s_optional(Config) ->
2019-06-14 11:33:26 +02:00
Server = ?config(server, Config),
2017-08-07 15:38:17 +02:00
ejabberd_s2s:stop_s2s_connections(),
2019-06-14 11:33:26 +02:00
ejabberd_config:set_option({s2s_use_starttls, Server}, optional),
ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, optional),
ejabberd_config:set_option(ca_file, pkix:get_cafile()),
2016-09-23 11:30:33 +02:00
s2s_ping(Config).
s2s_required(Config) ->
2019-06-14 11:33:26 +02:00
Server = ?config(server, Config),
2017-08-07 15:38:17 +02:00
ejabberd_s2s:stop_s2s_connections(),
2019-06-14 11:33:26 +02:00
gen_mod:stop_module(Server, mod_s2s_dialback),
gen_mod:stop_module(?MNESIA_VHOST, mod_s2s_dialback),
ejabberd_config:set_option({s2s_use_starttls, Server}, required),
ejabberd_config:set_option({s2s_use_starttls, ?MNESIA_VHOST}, required),
ejabberd_config:set_option(ca_file, "ca.pem"),
2016-09-23 11:30:33 +02:00
s2s_ping(Config).
s2s_ping(Config) ->
From = my_jid(Config),
To = jid:make(?MNESIA_VHOST),
2018-07-05 10:53:04 +02:00
ID = p1_rand:get_string(),
ejabberd_s2s:route(#iq{from = From, to = To, id = ID,
type = get, sub_els = [#ping{}]}),
#iq{type = result, id = ID, sub_els = []} = recv_iq(Config),
2016-09-21 09:45:11 +02:00
disconnect(Config).
2013-06-26 19:55:29 +02:00
auth_md5(Config) ->
Mechs = ?config(mechs, Config),
case lists:member(<<"DIGEST-MD5">>, Mechs) of
true ->
disconnect(auth_SASL(<<"DIGEST-MD5">>, Config));
false ->
disconnect(Config),
{skipped, 'DIGEST-MD5_not_available'}
end.
2013-06-26 19:55:29 +02:00
auth_plain(Config) ->
Mechs = ?config(mechs, Config),
2013-06-26 19:55:29 +02:00
case lists:member(<<"PLAIN">>, Mechs) of
true ->
disconnect(auth_SASL(<<"PLAIN">>, Config));
false ->
disconnect(Config),
{skipped, 'PLAIN_not_available'}
end.
auth_external(Config0) ->
Config = connect(starttls(Config0)),
disconnect(auth_SASL(<<"EXTERNAL">>, Config)).
auth_external_no_jid(Config0) ->
Config = connect(starttls(Config0)),
disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShoudFail = false,
{<<"">>, <<"">>, <<"">>})).
auth_external_no_user(Config0) ->
Config = set_opt(user, <<"">>, connect(starttls(Config0))),
disconnect(auth_SASL(<<"EXTERNAL">>, Config)).
auth_external_malformed_jid(Config0) ->
Config = connect(starttls(Config0)),
disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true,
{<<"">>, <<"@">>, <<"">>})).
auth_external_wrong_jid(Config0) ->
Config = set_opt(user, <<"wrong">>,
connect(starttls(Config0))),
disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true)).
auth_external_wrong_server(Config0) ->
Config = connect(starttls(Config0)),
disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true,
{<<"">>, <<"wrong.com">>, <<"">>})).
auth_external_invalid_cert(Config0) ->
Config = connect(starttls(
set_opt(certfile, "self-signed-cert.pem", Config0))),
disconnect(auth_SASL(<<"EXTERNAL">>, Config, _ShouldFail = true)).
2017-01-23 11:51:05 +01:00
test_legacy_auth_feature(Config) ->
true = ?config(legacy_auth, Config),
disconnect(Config).
2016-09-20 13:04:07 +02:00
test_legacy_auth(Config) ->
disconnect(auth_legacy(Config, _Digest = false)).
test_legacy_auth_digest(Config) ->
disconnect(auth_legacy(Config, _Digest = true)).
test_legacy_auth_no_resource(Config0) ->
Config = set_opt(resource, <<"">>, Config0),
disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)).
test_legacy_auth_bad_jid(Config0) ->
Config = set_opt(user, <<"@">>, Config0),
disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)).
test_legacy_auth_fail(Config0) ->
Config = set_opt(user, <<"wrong">>, Config0),
disconnect(auth_legacy(Config, _Digest = false, _ShouldFail = true)).
2013-06-26 19:55:29 +02:00
test_auth(Config) ->
disconnect(auth(Config)).
2016-09-23 11:30:33 +02:00
test_auth_starttls(Config) ->
disconnect(auth(connect(starttls(Config)))).
2016-09-20 13:04:07 +02:00
test_auth_fail(Config0) ->
2016-09-21 09:45:11 +02:00
Config = set_opt(user, <<"wrong">>,
set_opt(password, <<"wrong">>, Config0)),
2016-09-20 13:04:07 +02:00
disconnect(auth(Config, _ShouldFail = true)).
test_bind(Config) ->
disconnect(bind(Config)).
test_open_session(Config) ->
2016-09-20 13:04:07 +02:00
disconnect(open_session(Config, true)).
2016-09-20 13:04:07 +02:00
codec_failure(Config) ->
2016-09-21 09:45:11 +02:00
JID = my_jid(Config),
#iq{type = error} =
send_recv(Config, #iq{type = wrong, from = JID, to = JID}),
2016-09-20 13:04:07 +02:00
disconnect(Config).
unsupported_query(Config) ->
ServerJID = server_jid(Config),
#iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID}),
#iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID,
sub_els = [#caps{}]}),
#iq{type = error} = send_recv(Config, #iq{type = get, to = ServerJID,
sub_els = [#roster_query{},
#disco_info{},
#privacy_query{}]}),
disconnect(Config).
presence(Config) ->
JID = my_jid(Config),
#presence{from = JID, to = JID} = send_recv(Config, #presence{}),
disconnect(Config).
presence_broadcast(Config) ->
2018-07-05 10:53:04 +02:00
Feature = <<"p1:tmp:", (p1_rand:get_string())/binary>>,
2015-07-30 17:47:15 +02:00
Ver = crypto:hash(sha, ["client", $/, "bot", $/, "en", $/,
"ejabberd_ct", $<, Feature, $<]),
B64Ver = base64:encode(Ver),
Node = <<(?EJABBERD_CT_URI)/binary, $#, B64Ver/binary>>,
Server = ?config(server, Config),
Info = #disco_info{identities =
[#identity{category = <<"client">>,
type = <<"bot">>,
lang = <<"en">>,
name = <<"ejabberd_ct">>}],
node = Node, features = [Feature]},
2016-09-13 11:30:05 +02:00
Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, version = B64Ver},
send(Config, #presence{sub_els = [Caps]}),
JID = my_jid(Config),
%% We receive:
%% 1) disco#info iq request for CAPS
%% 2) welcome message
%% 3) presence broadcast
IQ = #iq{type = get,
2017-11-10 18:58:13 +01:00
from = JID,
sub_els = [#disco_info{node = Node}]} = recv_iq(Config),
#message{type = normal} = recv_message(Config),
#presence{from = JID, to = JID} = recv_presence(Config),
send(Config, #iq{type = result, id = IQ#iq.id,
2017-11-10 18:58:13 +01:00
to = JID, sub_els = [Info]}),
%% We're trying to read our feature from ejabberd database
%% with exponential back-off as our IQ response may be delayed.
[Feature] =
lists:foldl(
fun(Time, []) ->
timer:sleep(Time),
2016-09-13 11:30:05 +02:00
mod_caps:get_features(Server, Caps);
(_, Acc) ->
Acc
end, [], [0, 100, 200, 2000, 5000, 10000]),
disconnect(Config).
ping(Config) ->
true = is_feature_advertised(Config, ?NS_PING),
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = []} =
send_recv(
Config,
#iq{type = get, sub_els = [#ping{}], to = server_jid(Config)}),
disconnect(Config).
version(Config) ->
true = is_feature_advertised(Config, ?NS_VERSION),
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = [#version{}]} =
send_recv(
Config, #iq{type = get, sub_els = [#version{}],
to = server_jid(Config)}),
disconnect(Config).
time(Config) ->
true = is_feature_advertised(Config, ?NS_TIME),
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = [#time{}]} =
send_recv(Config, #iq{type = get, sub_els = [#time{}],
to = server_jid(Config)}),
disconnect(Config).
disco(Config) ->
true = is_feature_advertised(Config, ?NS_DISCO_INFO),
true = is_feature_advertised(Config, ?NS_DISCO_ITEMS),
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = [#disco_items{items = Items}]} =
send_recv(
Config, #iq{type = get, sub_els = [#disco_items{}],
to = server_jid(Config)}),
lists:foreach(
fun(#disco_item{jid = JID, node = Node}) ->
2013-06-16 20:00:19 +02:00
#iq{type = result} =
send_recv(Config,
#iq{type = get, to = JID,
sub_els = [#disco_info{node = Node}]})
end, Items),
disconnect(Config).
last(Config) ->
true = is_feature_advertised(Config, ?NS_LAST),
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = [#last{}]} =
send_recv(Config, #iq{type = get, sub_els = [#last{}],
to = server_jid(Config)}),
disconnect(Config).
2013-06-21 19:23:56 +02:00
vcard_get(Config) ->
true = is_feature_advertised(Config, ?NS_VCARD),
%% TODO: check if VCard corresponds to LDIF data from ejabberd.ldif
#iq{type = result, sub_els = [_VCard]} =
2016-09-13 11:30:05 +02:00
send_recv(Config, #iq{type = get, sub_els = [#vcard_temp{}]}),
2013-06-21 19:23:56 +02:00
disconnect(Config).
ldap_shared_roster_get(Config) ->
Item = #roster_item{jid = jid:decode(<<"user2@ldap.localhost">>), name = <<"Test User 2">>,
groups = [<<"group1">>], subscription = both},
2016-09-13 11:30:05 +02:00
#iq{type = result, sub_els = [#roster_query{items = [Item]}]} =
send_recv(Config, #iq{type = get, sub_els = [#roster_query{}]}),
disconnect(Config).
stats(Config) ->
2016-09-13 11:30:05 +02:00
#iq{type = result, sub_els = [#stats{list = Stats}]} =
2013-06-16 20:00:19 +02:00
send_recv(Config, #iq{type = get, sub_els = [#stats{}],
to = server_jid(Config)}),
lists:foreach(
2013-06-15 15:28:14 +02:00
fun(#stat{} = Stat) ->
2013-06-16 20:00:19 +02:00
#iq{type = result, sub_els = [_|_]} =
send_recv(Config, #iq{type = get,
2016-09-13 11:30:05 +02:00
sub_els = [#stats{list = [Stat]}],
2013-06-16 20:00:19 +02:00
to = server_jid(Config)})
end, Stats),
disconnect(Config).
%%%===================================================================
%%% Aux functions
%%%===================================================================
bookmark_conference() ->
#bookmark_conference{name = <<"Some name">>,
autojoin = true,
jid = jid:make(
<<"some">>,
<<"some.conference.org">>,
<<>>)}.
2013-06-14 16:45:04 +02:00
2016-11-18 11:38:08 +01:00
'$handle_undefined_function'(F, [Config]) when is_list(Config) ->
case re:split(atom_to_list(F), "_", [{return, list}, {parts, 2}]) of
[M, T] ->
Module = list_to_atom(M ++ "_tests"),
Function = list_to_atom(T),
case erlang:function_exported(Module, Function, 1) of
true ->
Module:Function(Config);
false ->
erlang:error({undef, F})
end;
_ ->
erlang:error({undef, F})
end;
'$handle_undefined_function'(_, _) ->
erlang:error(undef).
%%%===================================================================
%%% SQL stuff
%%%===================================================================
clear_sql_tables(sqlite, _BaseDir) ->
2015-03-16 19:53:19 +01:00
ok;
clear_sql_tables(Type, BaseDir) ->
{VHost, File} = case Type of
mysql ->
2018-04-12 15:42:43 +02:00
Path = case ejabberd_sql:use_new_schema() of
true ->
"mysql.new.sql";
false ->
"mysql.sql"
end,
{?MYSQL_VHOST, Path};
pgsql ->
2018-04-12 15:42:43 +02:00
Path = case ejabberd_sql:use_new_schema() of
true ->
"pg.new.sql";
false ->
"pg.sql"
end,
{?PGSQL_VHOST, Path}
end,
SQLFile = filename:join([BaseDir, "sql", File]),
CreationQueries = read_sql_queries(SQLFile),
ClearTableQueries = clear_table_queries(CreationQueries),
2016-04-20 11:27:32 +02:00
case ejabberd_sql:sql_transaction(
VHost, ClearTableQueries) of
{atomic, ok} ->
ok;
Err ->
ct:fail({failed_to_clear_sql_tables, Type, Err})
end.
read_sql_queries(File) ->
case file:open(File, [read, binary]) of
{ok, Fd} ->
read_lines(Fd, File, []);
Err ->
ct:fail({open_file_failed, File, Err})
end.
clear_table_queries(Queries) ->
lists:foldl(
fun(Query, Acc) ->
case split(str:to_lower(Query)) of
[<<"create">>, <<"table">>, Table|_] ->
[<<"DELETE FROM ", Table/binary, ";">>|Acc];
_ ->
Acc
end
end, [], Queries).
read_lines(Fd, File, Acc) ->
case file:read_line(Fd) of
{ok, Line} ->
NewAcc = case str:strip(str:strip(Line, both, $\r), both, $\n) of
<<"--", _/binary>> ->
Acc;
<<>> ->
Acc;
_ ->
[Line|Acc]
end,
read_lines(Fd, File, NewAcc);
eof ->
QueryList = str:tokens(list_to_binary(lists:reverse(Acc)), <<";">>),
lists:flatmap(
fun(Query) ->
case str:strip(str:strip(Query, both, $\r), both, $\n) of
<<>> ->
[];
Q ->
[<<Q/binary, $;>>]
end
end, QueryList);
{error, _} = Err ->
ct:fail({read_file_failed, File, Err})
end.
split(Data) ->
lists:filter(
fun(<<>>) ->
false;
(_) ->
true
end, re:split(Data, <<"\s">>)).