diff --git a/big_tests/dynamic_domains.spec b/big_tests/dynamic_domains.spec index 5d165193b98..696f92b2c92 100644 --- a/big_tests/dynamic_domains.spec +++ b/big_tests/dynamic_domains.spec @@ -12,14 +12,19 @@ {suites, "tests", acc_e2e_SUITE}. {suites, "tests", carboncopy_SUITE}. -{skip_cases, "tests", carboncopy_SUITE, [discovering_support], - "at the moment mod_disco doesn't support dynamic domains"}. -{suites, "tests", offline_stub_SUITE}. +{suites, "tests", disco_and_caps_SUITE}. +{skip_cases, "tests", disco_and_caps_SUITE, + [caps_feature_is_advertised, + user_can_query_server_caps_via_disco], + "at the moment mod_caps doesn't support dynamic domains"}. +{skip_cases, "tests", disco_and_caps_SUITE, + [user_can_query_friend_resources, + user_can_query_friend_features, + user_cannot_query_friend_resources_with_unknown_node], + "at the moment mod_roster doesn't support dynamic domains"}. {suites, "tests", inbox_SUITE}. -{skip_cases, "tests", inbox_SUITE, [disco_service], - "at the moment mod_disco doesn't support dynamic domains"}. {skip_cases, "tests", inbox_SUITE, [msg_sent_to_offline_user], "at the moment mod_offline doesn't support dynamic domains"}. @@ -28,38 +33,20 @@ {suites, "tests", mam_SUITE}. {skip_cases, "tests", mam_SUITE, [muc_service_discovery, mam_service_discovery], - "at the moment mod_disco doesn't support dynamic domains"}. + "at the moment mod_mam config options don't support dynamic domains"}. {skip_cases, "tests", mam_SUITE, [messages_filtered_when_prefs_default_policy_is_roster], "at the moment mod_roster doesn't support dynamic domains"}. {suites, "tests", mod_ping_SUITE}. -{skip_cases, "tests", mod_ping_SUITE, [disco], - "at the moment mod_disco doesn't support dynamic domains"}. {suites, "tests", muc_SUITE}. -{skip_groups, "tests", muc_SUITE, - [disco, disco_non_parallel, disco_rsm, disco_rsm_with_offline], - "at the moment mod_disco doesn't support dynamic domains"}. {skip_groups, "tests", muc_SUITE, [register_over_s2s], "at the moment S2S doesn't support dynamic domains " "(requires mod_register creating CT users)"}. {suites, "tests", muc_light_SUITE}. -{skip_cases, "tests", muc_light_SUITE, - [disco_service, - disco_features, - disco_features_with_mam, - disco_info, - disco_info_with_mam, - disco_rooms, - disco_rooms_rsm, - disco_rooms_created_page_1, - disco_rooms_created_page_infinity, - disco_rooms_empty_page_infinity, - disco_rooms_empty_page_1], - "at the moment mod_disco doesn't support dynamic domains"}. {skip_cases, "tests", muc_light_SUITE, [rooms_in_rosters, rooms_in_rosters_doesnt_break_disco_info, @@ -67,15 +54,8 @@ "at the moment mod_roster doesn't support dynamic domains"}. {suites, "tests", muc_light_legacy_SUITE}. -{skip_cases, "tests", muc_light_legacy_SUITE, - [disco_service, - disco_features, - disco_features_with_mam, - disco_info, - disco_info_with_mam, - disco_rooms, - disco_rooms_rsm], - "at the moment mod_disco doesn't support dynamic domains"}. + +{suites, "tests", offline_stub_SUITE}. {suites, "tests", sm_SUITE}. {skip_cases, "tests", sm_SUITE, @@ -93,6 +73,7 @@ resume_expired_session_returns_correct_h, unacknowledged_message_hook_offline], "at the moment mod_offline doesn't support dynamic domains"}. + {suites, "tests", rest_client_SUITE}. {skip_groups, "tests", rest_client_SUITE, [roster], "at the moment mod_roster doesn't support dynamic domains"}. diff --git a/big_tests/tests/disco_and_caps_SUITE.erl b/big_tests/tests/disco_and_caps_SUITE.erl index 32c476cb45b..17bfc2e27ae 100644 --- a/big_tests/tests/disco_and_caps_SUITE.erl +++ b/big_tests/tests/disco_and_caps_SUITE.erl @@ -2,37 +2,54 @@ -compile(export_all). -include_lib("eunit/include/eunit.hrl"). +-include_lib("escalus/include/escalus_xmlns.hrl"). + +-import(domain_helper, [host_type/0]). all() -> - [{group, all_tests}]. + [{group, disco_with_caps}, + {group, disco_with_extra_features}]. groups() -> - G = [{all_tests, [parallel], all_test_cases()}], + G = [{disco_with_caps, [parallel], basic_test_cases() ++ caps_test_cases()}, + {disco_with_extra_features, [parallel], basic_test_cases() ++ extra_feature_test_cases()}], ct_helper:repeat_all_until_all_ok(G). -all_test_cases() -> - [caps_feature_is_advertised, - user_can_query_server_caps_via_disco, - user_cannot_query_stranger_resources, +basic_test_cases() -> + [user_cannot_query_stranger_resources, + user_cannot_query_stranger_features, user_can_query_friend_resources, + user_can_query_friend_features, user_cannot_query_own_resources_with_unknown_node, user_cannot_query_friend_resources_with_unknown_node, - extra_domains_are_advertised]. + user_can_query_server_features]. + +caps_test_cases() -> + [caps_feature_is_advertised, + user_can_query_server_caps_via_disco]. + +extra_feature_test_cases() -> + [user_can_query_extra_domains, + user_can_query_server_info]. domain() -> ct:get_config({hosts, mim, domain}). init_per_suite(C) -> - C2 = escalus:init_per_suite(dynamic_modules:save_modules(domain(), C)), - dynamic_modules:ensure_modules(domain(), [{mod_caps, []}, - {mod_disco, [{extra_domains, [extra_domain()]}]}]), - C2. + C. end_per_suite(C) -> escalus_fresh:clean(), - dynamic_modules:restore_modules(domain(), C), escalus:end_per_suite(C). +init_per_group(Name, C) -> + C2 = escalus:init_per_suite(dynamic_modules:save_modules(host_type(), C)), + dynamic_modules:ensure_modules(host_type(), required_modules(Name)), + C2. + +end_per_group(Name, C) -> + dynamic_modules:restore_modules(host_type(), C). + init_per_testcase(Name, C) -> escalus:init_per_testcase(Name, C). @@ -74,6 +91,17 @@ user_cannot_query_stranger_resources(Config) -> escalus:assert(is_stanza_from, [BobJid], Stanza) end). +user_cannot_query_stranger_features(Config) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + BobJid = escalus_client:short_jid(Bob), + Request = escalus_stanza:disco_info(BobJid), + escalus:send(Alice, Request), + Stanza = escalus:wait_for_stanza(Alice), + escalus:assert(is_iq_error, [Request], Stanza), + escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Stanza), + escalus:assert(is_stanza_from, [BobJid], Stanza) + end). + user_can_query_friend_resources(Config) -> escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> escalus_story:make_all_clients_friends([Alice, Bob]), @@ -88,6 +116,16 @@ user_can_query_friend_resources(Config) -> escalus:assert(is_stanza_from, [BobJid], Stanza) end). +user_can_query_friend_features(Config) -> + escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> + escalus_story:make_all_clients_friends([Alice, Bob]), + BobJid = escalus_client:short_jid(Bob), + escalus:send(Alice, escalus_stanza:disco_info(BobJid)), + Stanza = escalus:wait_for_stanza(Alice), + escalus:assert(has_identity, [<<"account">>, <<"registered">>], Stanza), + escalus:assert(is_stanza_from, [BobJid], Stanza) + end). + user_cannot_query_own_resources_with_unknown_node(Config) -> escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> AliceJid = escalus_client:short_jid(Alice), @@ -111,7 +149,7 @@ user_cannot_query_friend_resources_with_unknown_node(Config) -> escalus:assert(is_stanza_from, [BobJid], Stanza) end). -extra_domains_are_advertised(Config) -> +user_can_query_extra_domains(Config) -> escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> Server = escalus_client:server(Alice), escalus:send(Alice, escalus_stanza:service_discovery(Server)), @@ -120,5 +158,68 @@ extra_domains_are_advertised(Config) -> escalus:assert(is_stanza_from, [domain()], Stanza) end). +user_can_query_server_features(Config) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> + Server = escalus_client:server(Alice), + escalus:send(Alice, escalus_stanza:disco_info(Server)), + Stanza = escalus:wait_for_stanza(Alice), + escalus:assert(has_identity, [<<"server">>, <<"im">>], Stanza), + escalus:assert(has_feature, [<<"iq">>], Stanza), + escalus:assert(has_feature, [<<"presence">>], Stanza), + escalus:assert(has_feature, [<<"presence-invisible">>], Stanza), + escalus:assert(is_stanza_from, [domain()], Stanza) + end). + +%% XEP-0157: Contact Addresses for XMPP Services +user_can_query_server_info(Config) -> + escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> + Server = escalus_client:server(Alice), + escalus:send(Alice, escalus_stanza:disco_info(Server)), + Stanza = escalus:wait_for_stanza(Alice), + escalus:assert(is_stanza_from, [domain()], Stanza), + + %% 'sales' is hidden for mod_disco, so only 'abuse' and 'admin' are expected + [HiddenField, AbuseField, AdminField] = get_form_fields(Stanza), + ?assertEqual(<<"FORM_TYPE">>, exml_query:attr(HiddenField, <<"var">>)), + ?assertEqual(<<"hidden">>, exml_query:attr(HiddenField, <<"type">>)), + ?assertEqual([?NS_SERVERINFO], exml_query:paths(HiddenField, [{element, <<"value">>}, + cdata])), + ?assertEqual(name(abuse), exml_query:attr(AbuseField, <<"var">>)), + ?assertEqual(urls(abuse), exml_query:paths(AbuseField, [{element, <<"value">>}, + cdata])), + ?assertEqual(name(admin), exml_query:attr(AdminField, <<"var">>)), + ?assertEqual(urls(admin), exml_query:paths(AdminField, [{element, <<"value">>}, + cdata])) + end). + +%% Helpers + +required_modules(disco_with_caps) -> + [{mod_caps, []}, + {mod_disco, []}]; +required_modules(disco_with_extra_features) -> + [{mod_disco, [{extra_domains, [extra_domain()]}, + {server_info, [server_info(abuse, []), + server_info(admin, [{modules, [mod_disco]}]), + server_info(sales, [{modules, [mod_pubsub]}])] + }] + }]. + +get_form_fields(Stanza) -> + exml_query:paths(Stanza, [{element_with_ns, <<"query">>, ?NS_DISCO_INFO}, + {element_with_ns, <<"x">>, ?NS_DATA_FORMS}, + {element, <<"field">>}]). + extra_domain() -> <<"eXtra.example.com">>. + +server_info(Type, Extra) -> + [{name, name(Type)}, {urls, urls(Type)} | Extra]. + +name(abuse) -> <<"abuse-addresses">>; +name(admin) -> <<"admin-addresses">>; +name(sales) -> <<"sales-addresses">>. + +urls(abuse) -> [<<"abuse@example.com">>]; +urls(admin) -> [<<"admin@example.com">>, <<"operations@example.com">>]; +urls(sales) -> [<<"sales@example.com">>]. diff --git a/rel/mim1.vars-toml.config b/rel/mim1.vars-toml.config index 8dc74dbaa48..b7c4f240747 100644 --- a/rel/mim1.vars-toml.config +++ b/rel/mim1.vars-toml.config @@ -23,7 +23,8 @@ auth.dummy.base_time = 1 auth.dummy.variance = 5 [host_config.modules.mod_carboncopy] - [host_config.modules.mod_stream_management]"}. + [host_config.modules.mod_stream_management] + [host_config.modules.mod_disco]"}. {password_format, "password.format = \"scram\" password.hash = [\"sha256\"]"}. {scram_iterations, 64}. diff --git a/src/config/mongoose_config_validator.erl b/src/config/mongoose_config_validator.erl index c914b6e2e11..89e88334b59 100644 --- a/src/config/mongoose_config_validator.erl +++ b/src/config/mongoose_config_validator.erl @@ -22,6 +22,7 @@ -spec validate(mongoose_config_parser_toml:option_value(), mongoose_config_spec:option_type(), validator()) -> any(). validate(V, binary, domain) -> validate_binary_domain(V); +validate(V, binary, url) -> validate_non_empty_binary(V); validate(V, binary, non_empty) -> validate_non_empty_binary(V); validate(V, binary, {module, Prefix}) -> validate_module(list_to_atom(atom_to_list(Prefix) ++ "_" ++ binary_to_list(V))); diff --git a/src/domain/mongoose_domain_api.erl b/src/domain/mongoose_domain_api.erl index d0e5d6b69bd..442e524d7c7 100644 --- a/src/domain/mongoose_domain_api.erl +++ b/src/domain/mongoose_domain_api.erl @@ -18,7 +18,8 @@ -export([register_subdomain/3, unregister_subdomain/2, get_subdomain_host_type/1, - get_subdomain_info/1]). + get_subdomain_info/1, + get_all_subdomains_for_domain/1]). -type domain() :: jid:lserver(). -type host_type() :: mongooseim:host_type(). @@ -168,3 +169,8 @@ register_subdomain(HostType, SubdomainPattern, PacketHandler) -> -spec unregister_subdomain(host_type(), subdomain_pattern()) -> ok. unregister_subdomain(HostType, SubdomainPattern) -> mongoose_subdomain_core:unregister_subdomain(HostType, SubdomainPattern). + +-spec get_all_subdomains_for_domain(domain()) -> + [mongoose_subdomain_core:subdomain_info()]. +get_all_subdomains_for_domain(Domain) -> + mongoose_subdomain_core:get_all_subdomains_for_domain(Domain). diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 6cd5c58b027..1c9a1676269 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -55,7 +55,7 @@ %% Hooks callbacks -export([node_cleanup/2, - add_local_features/5]). + disco_local_features/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -237,13 +237,11 @@ register_host(Host) -> unregister_host(Host) -> gen_server:call(?MODULE, {unregister_host, Host}). --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, #jid{lserver = LServer}, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}) -> Features = ets:lookup_element(?NSTABLE, LServer, 2), mongoose_disco:add_features(Features, Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. %%==================================================================== @@ -371,7 +369,9 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- hooks() -> - [{node_cleanup, global, ?MODULE, node_cleanup, 50}]. + [{node_cleanup, global, ?MODULE, node_cleanup, 50} | + [{disco_local_features, HostType, ?MODULE, disco_local_features, 99} || + HostType <- ?ALL_HOST_TYPES]]. -spec do_route(Acc :: mongoose_acc:t(), From :: jid:jid(), @@ -464,9 +464,7 @@ cancel_timer(TRef) -> end. do_register_host(Host) -> - ejabberd_router:register_route(Host, mongoose_packet_handler:new(?MODULE)), - ejabberd_hooks:add(disco_local_features, Host, ?MODULE, add_local_features, 99). + ejabberd_router:register_route(Host, mongoose_packet_handler:new(?MODULE)). do_unregister_host(Host) -> - ejabberd_router:unregister_route(Host), - ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, add_local_features, 99). + ejabberd_router:unregister_route(Host). diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index 0cb01880990..4e5d833d27b 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -40,6 +40,7 @@ dirty_get_all_domains/0, dirty_get_all_routes/1, dirty_get_all_domains/1, + dirty_get_all_components/1, register_components/2, register_components/3, register_components/4, @@ -402,6 +403,13 @@ all_routes(only_public) -> ++ mnesia:dirty_select(external_component_global, [MatchNonHidden]). +-spec dirty_get_all_components(return_hidden()) -> [jid:lserver()]. +dirty_get_all_components(all) -> + mnesia:dirty_all_keys(external_component_global); +dirty_get_all_components(only_public) -> + MatchNonHidden = {#external_component{ domain = '$1', is_hidden = false, _ = '_' }, [], ['$1']}, + mnesia:dirty_select(external_component_global, [MatchNonHidden]). + %%==================================================================== %% gen_server callbacks diff --git a/src/global_distrib/mod_global_distrib_disco.erl b/src/global_distrib/mod_global_distrib_disco.erl index c0ece59371e..a5f570601b4 100644 --- a/src/global_distrib/mod_global_distrib_disco.erl +++ b/src/global_distrib/mod_global_distrib_disco.erl @@ -23,7 +23,7 @@ -include("mongoose.hrl"). -include("jlib.hrl"). --export([start/2, stop/1, get_disco_items/5]). +-export([start/2, stop/1, disco_local_items/1]). %%-------------------------------------------------------------------- %% API @@ -41,14 +41,13 @@ stop(Host) -> %% Hooks implementation %%-------------------------------------------------------------------- --spec get_disco_items(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> - mongoose_disco:item_acc(). -get_disco_items(Acc, From, To, <<>>, _Lang) -> +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{from_jid := From, to_jid := To, node := <<>>}) -> Domains = domains_for_disco(To#jid.lserver, From), ?LOG_DEBUG(#{what => gd_domains_fetched_for_disco, domains => Domains}), Items = [#{jid => Domain} || Domain <- Domains], mongoose_disco:add_items(Items, Acc); -get_disco_items(Acc, _From, _To, _Node, _Lang) -> +disco_local_items(Acc) -> Acc. %%-------------------------------------------------------------------- @@ -58,12 +57,12 @@ get_disco_items(Acc, _From, _To, _Node, _Lang) -> -spec start() -> any(). start() -> Host = opt(global_host), - ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_disco_items, 99). + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_local_items, 99). -spec stop() -> any(). stop() -> Host = opt(global_host), - ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_disco_items, 99). + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_local_items, 99). -spec opt(Key :: atom()) -> term(). opt(Key) -> diff --git a/src/http_upload/mod_http_upload.erl b/src/http_upload/mod_http_upload.erl index 6b4400774fb..3c113327016 100644 --- a/src/http_upload/mod_http_upload.erl +++ b/src/http_upload/mod_http_upload.erl @@ -32,7 +32,7 @@ -export([start/2, stop/1, process_iq/4, process_disco_iq/4, get_urls/5, config_spec/0]). %% Hook handlers --export([disco_local_items/5]). +-export([disco_local_items/1]). -export([config_metrics/1]). @@ -176,12 +176,10 @@ s3_spec() -> required = [<<"bucket_url">>, <<"region">>, <<"access_key_id">>, <<"secret_access_key">>] }. --spec disco_local_items(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -disco_local_items(Acc, _From, #jid{lserver = Host} = _To, <<>>, Lang) -> +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{to_jid := #jid{lserver = Host}, node := <<>>, lang := Lang}) -> mongoose_disco:add_items([#{jid => subhost(Host), name => my_disco_name(Lang)}], Acc); -disco_local_items(Acc, _From, _To, _Node, _Lang) -> +disco_local_items(Acc) -> Acc. %%-------------------------------------------------------------------- diff --git a/src/inbox/mod_inbox.erl b/src/inbox/mod_inbox.erl index 80e7254e3f1..8138d8e83a0 100644 --- a/src/inbox/mod_inbox.erl +++ b/src/inbox/mod_inbox.erl @@ -34,7 +34,7 @@ inbox_unread_count/2, remove_user/3, remove_domain/3, - add_local_features/5 + disco_local_features/1 ]). -export([config_metrics/1]). @@ -287,12 +287,10 @@ remove_domain(Acc, HostType, Domain) -> mod_inbox_backend:remove_domain(HostType, Domain), Acc. --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_ESL_INBOX], Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. -spec maybe_process_message(Acc :: mongoose_acc:t(), @@ -582,7 +580,7 @@ hooks(HostType) -> {filter_local_packet, HostType, ?MODULE, filter_packet, 90}, {inbox_unread_count, HostType, ?MODULE, inbox_unread_count, 80}, {get_personal_data, HostType, ?MODULE, get_personal_data, 50}, - {disco_local_features, HostType, ?MODULE, add_local_features, 99} + {disco_local_features, HostType, ?MODULE, disco_local_features, 99} ]. add_default_backend(Opts) -> diff --git a/src/mam/mod_mam.erl b/src/mam/mod_mam.erl index 77632f67eec..398a5d5c309 100644 --- a/src/mam/mod_mam.erl +++ b/src/mam/mod_mam.erl @@ -45,7 +45,7 @@ -export([start/2, stop/1]). %% ejabberd handlers --export([add_local_features/5, +-export([disco_local_features/1, process_mam_iq/5, user_send_packet/4, remove_user/3, @@ -244,12 +244,10 @@ process_mam_iq(Acc, From, To, IQ, _Extra) -> {Acc, return_action_not_allowed_error_iq(IQ)} end. --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, #jid{lserver = LServer}, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}) -> mongoose_disco:add_features(features(?MODULE, LServer), Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. %% @doc Handle an outgoing message. @@ -695,7 +693,7 @@ config_metrics(HostType) -> -spec hooks(jid:lserver()) -> [ejabberd_hooks:hook()]. hooks(HostType) -> - [{disco_local_features, HostType, ?MODULE, add_local_features, 99}, + [{disco_local_features, HostType, ?MODULE, disco_local_features, 99}, {user_send_packet, HostType, ?MODULE, user_send_packet, 60}, {rest_user_send_packet, HostType, ?MODULE, user_send_packet, 60}, {filter_local_packet, HostType, ?MODULE, filter_packet, 90}, diff --git a/src/mam/mod_mam_muc.erl b/src/mam/mod_mam_muc.erl index 51c03c94c58..4889ec7a887 100644 --- a/src/mam/mod_mam_muc.erl +++ b/src/mam/mod_mam_muc.erl @@ -43,7 +43,7 @@ -export([start/2, stop/1]). %% ejabberd room handlers --export([add_local_features/5, +-export([disco_muc_features/1, filter_room_packet/3, room_process_mam_iq/5, forget_room/4]). @@ -161,12 +161,10 @@ stop(HostType) -> %% ---------------------------------------------------------------------- %% hooks and handlers for MUC --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, #jid{lserver = LServer}, <<>>, _Lang) -> +-spec disco_muc_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_muc_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}) -> mongoose_disco:add_features(features(?MODULE, LServer), Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_muc_features(Acc) -> Acc. %% @doc Handle public MUC-message. @@ -608,9 +606,7 @@ is_archivable_message(HostType, Dir, Packet) -> -spec hooks(host_type()) -> [ejabberd_hooks:hook()]. hooks(HostType) -> - %% TODO multitenancy - MUCHost = gen_mod:get_module_opt_subhost(HostType, mod_mam_muc, mod_muc:default_host()), - [{disco_local_features, MUCHost, ?MODULE, add_local_features, 99}, + [{disco_muc_features, HostType, ?MODULE, disco_muc_features, 99}, {filter_room_packet, HostType, ?MODULE, filter_room_packet, 60}, {forget_room, HostType, ?MODULE, forget_room, 90}, {get_personal_data, HostType, ?MODULE, get_personal_data, 50} diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index 17373176de5..069f5245041 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -36,12 +36,12 @@ config_spec/0, process_local_iq/4, process_sm_iq/4, - get_local_commands/5, - get_local_identity/5, - get_local_features/5, - get_sm_commands/5, - get_sm_identity/5, - get_sm_features/5, + disco_local_items/1, + disco_local_identity/1, + disco_local_features/1, + disco_sm_items/1, + disco_sm_identity/1, + disco_sm_features/1, ping_command/4]). -include("mongoose.hrl"). @@ -65,12 +65,12 @@ stop(Host) -> gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS). hooks(Host) -> - [{disco_local_identity, Host, ?MODULE, get_local_identity, 99}, - {disco_local_features, Host, ?MODULE, get_local_features, 99}, - {disco_local_items, Host, ?MODULE, get_local_commands, 99}, - {disco_sm_identity, Host, ?MODULE, get_sm_identity, 99}, - {disco_sm_features, Host, ?MODULE, get_sm_features, 99}, - {disco_sm_items, Host, ?MODULE, get_sm_commands, 99}, + [{disco_local_identity, Host, ?MODULE, disco_local_identity, 99}, + {disco_local_features, Host, ?MODULE, disco_local_features, 99}, + {disco_local_items, Host, ?MODULE, disco_local_items, 99}, + {disco_sm_identity, Host, ?MODULE, disco_sm_identity, 99}, + {disco_sm_features, Host, ?MODULE, disco_sm_features, 99}, + {disco_sm_items, Host, ?MODULE, disco_sm_items, 99}, {adhoc_local_commands, Host, ?MODULE, ping_command, 100}]. -spec config_spec() -> mongoose_config_spec:config_section(). @@ -82,10 +82,8 @@ config_spec() -> %%------------------------------------------------------------------------- --spec get_local_commands(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -get_local_commands(Acc, _From, #jid{lserver = LServer}, <<>>, Lang) -> +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>, lang := Lang}) -> Items = case are_commands_visible(LServer) of false -> []; @@ -93,20 +91,18 @@ get_local_commands(Acc, _From, #jid{lserver = LServer}, <<>>, Lang) -> [item(LServer, ?NS_COMMANDS, <<"Commands">>, Lang)] end, mongoose_disco:add_items(Items, Acc); -get_local_commands(Acc, _From, #jid{lserver = LServer}, ?NS_COMMANDS, Lang) -> +disco_local_items(Acc = #{to_jid := #jid{lserver = LServer}, node := ?NS_COMMANDS, lang := Lang}) -> Items = [item(LServer, <<"ping">>, <<"Ping">>, Lang)], mongoose_disco:add_items(Items, Acc); -get_local_commands(_Acc, _From, _To, <<"ping">>, _Lang) -> - {result, []}; % override the result -get_local_commands(Acc, _From, _To, _Node, _Lang) -> +disco_local_items(Acc = #{node := <<"ping">>}) -> + Acc#{result := []}; % override the result +disco_local_items(Acc) -> Acc. %%------------------------------------------------------------------------- --spec get_sm_commands(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, <<>>, Lang) -> +-spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_sm_items(Acc = #{to_jid := #jid{lserver = LServer} = To, node := <<>>, lang := Lang}) -> Items = case are_commands_visible(LServer) of false -> []; @@ -114,7 +110,7 @@ get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, <<>>, Lang) -> [item(jid:to_binary(To), ?NS_COMMANDS, <<"Commands">>, Lang)] end, mongoose_disco:add_items(Items, Acc); -get_sm_commands(Acc, _From, _To, _Node, _Lang) -> +disco_sm_items(Acc) -> Acc. are_commands_visible(LServer) -> @@ -126,60 +122,56 @@ item(LServer, Node, Name, Lang) -> %%------------------------------------------------------------------------- %% @doc On disco info request to the ad-hoc node, return automation/command-list. --spec get_local_identity([mongoose_disco:identity()], jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - [mongoose_disco:identity()]. -get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> - [#{category => <<"automation">>, - type => <<"command-list">>, - name => translate:translate(Lang, <<"Commands">>)} | Acc]; -get_local_identity(Acc, _From, _To, <<"ping">>, Lang) -> - [#{category => <<"automation">>, - type => <<"command-node">>, - name => translate:translate(Lang, <<"Ping">>)} | Acc]; -get_local_identity(Acc, _From, _To, _Node, _Lang) -> +-spec disco_local_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_local_identity(Acc = #{node := ?NS_COMMANDS, lang := Lang}) -> + mongoose_disco:add_identities([command_list_identity(Lang)], Acc); +disco_local_identity(Acc = #{node := <<"ping">>, lang := Lang}) -> + mongoose_disco:add_identities([ping_identity(Lang)], Acc); +disco_local_identity(Acc) -> Acc. %%------------------------------------------------------------------------- %% @doc On disco info request to the ad-hoc node, return automation/command-list. --spec get_sm_identity([mongoose_disco:identity()], jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - [mongoose_disco:identity()]. -get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> - [#{category => <<"automation">>, - type => <<"command-list">>, - name => translate:translate(Lang, <<"Commands">>)} | Acc]; -get_sm_identity(Acc, _From, _To, _Node, _Lang) -> +-spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_sm_identity(Acc = #{node := ?NS_COMMANDS, lang := Lang}) -> + mongoose_disco:add_identities([command_list_identity(Lang)], Acc); +disco_sm_identity(Acc) -> Acc. +ping_identity(Lang) -> + #{category => <<"automation">>, + type => <<"command-node">>, + name => translate:translate(Lang, <<"Ping">>)}. + +command_list_identity(Lang) -> + #{category => <<"automation">>, + type => <<"command-list">>, + name => translate:translate(Lang, <<"Commands">>)}. + %%------------------------------------------------------------------------- --spec get_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -get_local_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_COMMANDS], Acc); -get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) -> +disco_local_features(Acc = #{node := ?NS_COMMANDS}) -> %% override all lesser features... - {result, []}; -get_local_features(_Acc, _From, _To, <<"ping">>, _Lang) -> + Acc#{result := []}; +disco_local_features(Acc = #{node := <<"ping">>}) -> %% override all lesser features... - {result, [?NS_COMMANDS]}; -get_local_features(Acc, _From, _To, _Node, _Lang) -> + Acc#{result := [?NS_COMMANDS]}; +disco_local_features(Acc) -> Acc. %%------------------------------------------------------------------------- --spec get_sm_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -get_sm_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_sm_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_sm_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_COMMANDS], Acc); -get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) -> +disco_sm_features(Acc = #{node := ?NS_COMMANDS}) -> %% override all lesser features... - {result, []}; -get_sm_features(Acc, _From, _To, _Node, _Lang) -> + Acc#{result := []}; +disco_sm_features(Acc) -> Acc. %%------------------------------------------------------------------------- diff --git a/src/mod_amp.erl b/src/mod_amp.erl index 5864823096d..f2e27ec8379 100644 --- a/src/mod_amp.erl +++ b/src/mod_amp.erl @@ -11,7 +11,7 @@ -export([start/2, stop/1]). -export([run_initial_check/2, check_packet/2, - add_local_features/5, + disco_local_features/1, add_stream_feature/2 ]). @@ -33,7 +33,7 @@ stop(Host) -> hooks(Host) -> [{c2s_stream_features, Host, ?MODULE, add_stream_feature, 50}, - {disco_local_features, Host, ?MODULE, add_local_features, 99}, + {disco_local_features, Host, ?MODULE, disco_local_features, 99}, {c2s_preprocessing_hook, Host, ?MODULE, run_initial_check, 10}, {amp_verify_support, Host, ?AMP_RESOLVER, verify_support, 10}, {amp_check_condition, Host, ?AMP_RESOLVER, check_condition, 10}, @@ -57,10 +57,8 @@ check_packet(Acc, Event) -> Rules -> process_event(Acc, Rules, Event) end. --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, _To, Node, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := Node}) -> case amp_features(Node) of [] -> Acc; Features -> mongoose_disco:add_features(Features, Acc) @@ -112,7 +110,7 @@ process_event(Acc, Rules, Event) when Event =/= initial_check -> NewRules = process_rules(Packet, From, Event, Rules), mongoose_acc:set_permanent(amp, rules, NewRules, Acc). --spec amp_features(binary()) -> [binary()]. +-spec amp_features(binary()) -> [mongoose_disco:feature()]. amp_features(?NS_AMP) -> [<> || Suffix <- amp_feature_suffixes()]; amp_features(<<>>) -> diff --git a/src/mod_auth_token.erl b/src/mod_auth_token.erl index 0ba30932dda..bf811f98c13 100644 --- a/src/mod_auth_token.erl +++ b/src/mod_auth_token.erl @@ -19,7 +19,7 @@ %% Hook handlers -export([clean_tokens/3, - add_local_features/5]). + disco_local_features/1]). %% gen_iq_handler handlers -export([process_iq/4]). @@ -133,7 +133,7 @@ default_opts(Opts) -> hook_handlers() -> [{remove_user, clean_tokens, 50}, - {disco_local_features, add_local_features, 90}]. + {disco_local_features, disco_local_features, 90}]. -spec commands() -> [ejabberd_commands:cmd()]. commands() -> @@ -473,10 +473,8 @@ config_metrics(Host) -> OptsToReport = [{backend, rdbms}], %list of tuples {option, default_value} mongoose_module_metrics:opts_for_module(Host, ?MODULE, OptsToReport). --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_ESL_TOKEN_AUTH], Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index d5cd0b005a1..f5601dd4d70 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -16,7 +16,7 @@ process_iq_get/5, process_iq_set/4, stop/1, - add_local_features/5 + disco_local_features/1 ]). -include("mongoose.hrl"). @@ -32,16 +32,14 @@ stop(Host) -> ejabberd_hooks:delete(hooks(Host)). hooks(Host) -> - [{disco_local_features, Host, ?MODULE, add_local_features, 99}, + [{disco_local_features, Host, ?MODULE, disco_local_features, 99}, {privacy_iq_get, Host, ?MODULE, process_iq_get, 50}, {privacy_iq_set, Host, ?MODULE, process_iq_set, 50}]. --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_BLOCKING], Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 4eeae2fc4b5..44e9670af9b 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -36,7 +36,7 @@ -behaviour(mongoose_module_metrics). -export([read_caps/1, caps_stream_features/2, - disco_features/5, disco_identity/5, disco_info/5]). + disco_local_features/1, disco_local_identity/1, disco_info/1]). %% gen_mod callbacks -export([start/2, start_link/2, stop/1, config_spec/0]). @@ -173,7 +173,8 @@ user_send_packet(Acc, user_send_packet(Acc, _From, _To, _Pkt) -> Acc. --spec user_receive_packet(mongoose_acc:t(), jid:jid(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t(). +-spec user_receive_packet(mongoose_acc:t(), jid:jid(), jid:jid(), jid:jid(), exml:element()) -> + mongoose_acc:t(). user_receive_packet(Acc, #jid{lserver = LServer}, From, _To, #xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) -> Type = xml:get_attr_s(<<"type">>, Attrs), @@ -191,7 +192,6 @@ user_receive_packet(Acc, _JID, _From, _To, _Pkt) -> Acc. -spec caps_stream_features([exml:element()], binary()) -> [exml:element()]. - caps_stream_features(Acc, MyHost) -> case make_my_disco_hash(MyHost) of <<"">> -> Acc; @@ -204,29 +204,25 @@ caps_stream_features(Acc, MyHost) -> | Acc] end. -disco_features(Acc, From, To, Node, Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := Node}) -> case is_valid_node(Node) of - true -> - mongoose_hooks:disco_local_features(To#jid.lserver, - From, To, <<"">>, Lang); - false -> - Acc + true -> Acc#{node := <<>>}; + false -> Acc end. -disco_identity(Acc, From, To, Node, Lang) -> +-spec disco_local_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_local_identity(Acc = #{node := Node}) -> case is_valid_node(Node) of - true -> - mongoose_hooks:disco_local_identity(To#jid.lserver, From, To, <<"">>, Lang); - false -> - Acc + true -> Acc#{node := <<>>}; + false -> Acc end. -disco_info(Acc, Host, Module, Node, Lang) -> +-spec disco_info(mongoose_disco:info_acc()) -> mongoose_disco:info_acc(). +disco_info(Acc = #{node := Node}) -> case is_valid_node(Node) of - true -> - mongoose_hooks:disco_info(Host, Module, <<"">>, Lang); - false -> - Acc + true -> Acc#{node := <<>>}; + false -> Acc end. c2s_presence_in(C2SState, @@ -352,11 +348,11 @@ init([Host, Opts]) -> ejabberd_hooks:add(s2s_stream_features, Host, ?MODULE, caps_stream_features, 75), ejabberd_hooks:add(disco_local_features, Host, ?MODULE, - disco_features, 75), + disco_local_features, 1), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, - disco_identity, 75), + disco_local_identity, 1), ejabberd_hooks:add(disco_info, Host, ?MODULE, - disco_info, 75), + disco_info, 1), {ok, #state{host = Host}}. handle_call(stop, _From, State) -> @@ -385,11 +381,11 @@ terminate(_Reason, State) -> ejabberd_hooks:delete(s2s_stream_features, Host, ?MODULE, caps_stream_features, 75), ejabberd_hooks:delete(disco_local_features, Host, - ?MODULE, disco_features, 75), + ?MODULE, disco_local_features, 1), ejabberd_hooks:delete(disco_local_identity, Host, - ?MODULE, disco_identity, 75), + ?MODULE, disco_local_identity, 1), ejabberd_hooks:delete(disco_info, Host, ?MODULE, - disco_info, 75), + disco_info, 1), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -489,24 +485,15 @@ caps_delete_fun(Node) -> end. make_my_disco_hash(Host) -> - JID = jid:make(<<"">>, Host, <<"">>), - case {mongoose_hooks:disco_local_features(Host, JID, JID, <<"">>, <<"">>), - mongoose_hooks:disco_local_identity(Host, JID, JID, <<"">>, <<"">>), - mongoose_hooks:disco_info(Host, undefined, <<"">>, <<"">>)} - of - {{result, Features}, Identities, Info} -> - Feats = lists:map(fun ({{Feat, _Host}}) -> - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], - children = []}; - (Feat) -> - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, Feat}], - children = []} - end, - Features), - make_disco_hash(Identities ++ Info ++ Feats, sha1); - _Err -> <<"">> + JID = jid:make(<<>>, Host, <<>>), + %% TODO run the hooks for host type when adding support for dynamic domains + case mongoose_disco:get_local_features(Host, JID, JID, <<>>, <<>>) of + empty -> + <<>>; + {result, FeaturesXML} -> + IdentityXML = mongoose_disco:get_local_identity(Host, JID, JID, <<>>, <<>>), + InfoXML = mongoose_disco:get_info(Host, undefined, <<>>, <<>>), + make_disco_hash(IdentityXML ++ InfoXML ++ FeaturesXML, sha1) end. make_disco_hash(DiscoEls, Algo) -> diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index a437080aaf8..398084735e2 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -38,7 +38,7 @@ is_carbon_copy/1]). %% Hooks --export([add_local_features/5, +-export([disco_local_features/1, user_send_packet/4, user_receive_packet/5, iq_handler2/5, @@ -87,7 +87,7 @@ stop(HostType) -> ok. hooks(HostType) -> - [{disco_local_features, HostType, ?MODULE, add_local_features, 99}, + [{disco_local_features, HostType, ?MODULE, disco_local_features, 99}, {unset_presence_hook, HostType, ?MODULE, remove_connection, 10}, {user_send_packet, HostType, ?MODULE, user_send_packet, 89}, {user_receive_packet, HostType, ?MODULE, user_receive_packet, 89}]. @@ -96,12 +96,10 @@ hooks(HostType) -> config_spec() -> #section{items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc()}}. --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_CC_1, ?NS_CC_2, ?NS_CC_RULES], Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. iq_handler2(Acc, From, _To, IQ, _Extra) -> diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 7725a2aecca..abfa0df9f08 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -30,20 +30,25 @@ -behaviour(gen_mod). -behaviour(mongoose_module_metrics). +%% gen_mod callbacks -export([start/2, stop/1, config_spec/0, - process_server_info/1, - process_local_iq_items/4, - process_local_iq_info/4, - get_local_identity/5, - get_local_features/5, - get_local_services/5, - process_sm_iq_items/4, - process_sm_iq_info/4, - get_sm_identity/5, - get_sm_items/5, - get_info/5]). + supported_features/0]). + +%% iq handlers +-export([process_local_iq_items/5, + process_local_iq_info/5, + process_sm_iq_items/5, + process_sm_iq_info/5]). + +%% hook handlers +-export([disco_local_identity/1, + disco_sm_identity/1, + disco_local_items/1, + disco_sm_items/1, + disco_local_features/1, + disco_info/1]). -include("mongoose.hrl"). -include("jlib.hrl"). @@ -51,35 +56,33 @@ -type return_hidden() :: ejabberd_router:return_hidden(). --spec start(jid:server(), list()) -> 'ok'. -start(Host, Opts) -> +-spec start(mongooseim:host_type(), list()) -> 'ok'. +start(HostType, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - [gen_iq_handler:add_iq_handler(Module, Host, NS, ?MODULE, Handler, IQDisc) || - {Module, NS, Handler} <- iq_handlers()], - ejabberd_hooks:add(hooks(Host)). - --spec stop(jid:server()) -> ok. -stop(Host) -> - ejabberd_hooks:delete(hooks(Host)), - [gen_iq_handler:remove_iq_handler(Module, Host, NS) || - {Module, NS, _Handler} <- iq_handlers()], + [gen_iq_handler:add_iq_handler_for_domain(HostType, NS, Component, Handler, #{}, IQDisc) || + {Component, NS, Handler} <- iq_handlers()], + ejabberd_hooks:add(hooks(HostType)). + +-spec stop(mongooseim:host_type()) -> ok. +stop(HostType) -> + ejabberd_hooks:delete(hooks(HostType)), + [gen_iq_handler:remove_iq_handler_for_domain(HostType, NS, Component) || + {Component, NS, _Handler} <- iq_handlers()], ok. -hooks(Host) -> - [{disco_local_items, Host, ?MODULE, get_local_services, 100}, - {disco_local_features, Host, ?MODULE, get_local_features, 100}, - {disco_local_identity, Host, ?MODULE, get_local_identity, 100}, - {disco_sm_items, Host, ?MODULE, get_sm_items, 100}, - {disco_sm_identity, Host, ?MODULE, get_sm_identity, 100}, - {disco_info, Host, ?MODULE, get_info, 100}]. +hooks(HostType) -> + [{disco_local_items, HostType, ?MODULE, disco_local_items, 100}, + {disco_local_features, HostType, ?MODULE, disco_local_features, 100}, + {disco_local_identity, HostType, ?MODULE, disco_local_identity, 100}, + {disco_sm_items, HostType, ?MODULE, disco_sm_items, 100}, + {disco_sm_identity, HostType, ?MODULE, disco_sm_identity, 100}, + {disco_info, HostType, ?MODULE, disco_info, 100}]. iq_handlers() -> - [{ejabberd_local, ?NS_DISCO_ITEMS, process_local_iq_items}, - {ejabberd_local, ?NS_DISCO_INFO, process_local_iq_info}, - {ejabberd_sm, ?NS_DISCO_ITEMS, process_sm_iq_items}, - {ejabberd_sm, ?NS_DISCO_INFO, process_sm_iq_info}]. - -%% Configuration + [{ejabberd_local, ?NS_DISCO_ITEMS, fun ?MODULE:process_local_iq_items/5}, + {ejabberd_local, ?NS_DISCO_INFO, fun ?MODULE:process_local_iq_info/5}, + {ejabberd_sm, ?NS_DISCO_ITEMS, fun ?MODULE:process_sm_iq_items/5}, + {ejabberd_sm, ?NS_DISCO_INFO, fun ?MODULE:process_sm_iq_info/5}]. -spec config_spec() -> mongoose_config_spec:config_section(). config_spec() -> @@ -94,118 +97,177 @@ config_spec() -> server_info_spec() -> #section{ - items = #{<<"name">> => #option{type = string, + items = #{<<"name">> => #option{type = binary, validate = non_empty}, - <<"urls">> => #list{items = #option{type = string, + <<"urls">> => #list{items = #option{type = binary, validate = url}}, <<"modules">> => #list{items = #option{type = atom, validate = module}} }, - required = [<<"name">>, <<"urls">>], - process = fun ?MODULE:process_server_info/1 + required = [<<"name">>, <<"urls">>] }. -process_server_info(KVs) -> - {[[{name, Name}], [{urls, URLs}]], _} = proplists:split(KVs, [name, urls]), - Modules = proplists:get_value(modules, KVs, all), - {Modules, Name, URLs}. +supported_features() -> [dynamic_domains]. %% IQ handlers --spec process_local_iq_items(jid:jid(), jid:jid(), mongoose_acc:t(), jlib:iq()) -> +-spec process_local_iq_items(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> {mongoose_acc:t(), jlib:iq()}. -process_local_iq_items(_From, _To, Acc, #iq{type = set, sub_el = SubEl} = IQ) -> +process_local_iq_items(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) -> {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; -process_local_iq_items(From, To, Acc, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ) -> +process_local_iq_items(Acc, From, To, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ, _Extra) -> + HostType = mongoose_acc:host_type(Acc), Node = xml:get_tag_attr_s(<<"node">>, SubEl), - Host = To#jid.lserver, - case mongoose_hooks:disco_local_items(Host, From, To, Node, Lang) of - {result, Items} -> - ANode = make_node_attr(Node), - {Acc, IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode], - children = mongoose_disco:items_to_xml(Items)}]}}; + case mongoose_disco:get_local_items(HostType, From, To, Node, Lang) of empty -> Error = mongoose_xmpp_errors:item_not_found(), - {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}} + {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}}; + {result, ItemsXML} -> + {Acc, make_iq_result(IQ, ?NS_DISCO_ITEMS, Node, ItemsXML)} end. --spec process_local_iq_info(jid:jid(), jid:jid(), mongoose_acc:t(), jlib:iq()) -> +-spec process_local_iq_info(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> {mongoose_acc:t(), jlib:iq()}. -process_local_iq_info(_From, _To, Acc, #iq{type = set, sub_el = SubEl} = IQ) -> +process_local_iq_info(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) -> {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; -process_local_iq_info(From, To, Acc, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ) -> - Host = To#jid.lserver, +process_local_iq_info(Acc, From, To, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ, _Extra) -> + HostType = mongoose_acc:host_type(Acc), Node = xml:get_tag_attr_s(<<"node">>, SubEl), - Identities = mongoose_hooks:disco_local_identity(Host, From, To, Node, Lang), - Info = mongoose_hooks:disco_info(Host, ?MODULE, Node, Lang), - case mongoose_hooks:disco_local_features(Host, From, To, Node, Lang) of - {result, Features} -> - ANode = make_node_attr(Node), - IdentityXML = mongoose_disco:identities_to_xml(Identities), - FeatureXML = mongoose_disco:features_to_xml(Features), - {Acc, IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode], - children = IdentityXML ++ Info ++ FeatureXML}]}}; + case mongoose_disco:get_local_features(HostType, From, To, Node, Lang) of empty -> Error = mongoose_xmpp_errors:item_not_found(), - {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}} + {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}}; + {result, FeaturesXML} -> + IdentityXML = mongoose_disco:get_local_identity(HostType, From, To, Node, Lang), + InfoXML = mongoose_disco:get_info(HostType, ?MODULE, Node, Lang), + {Acc, make_iq_result(IQ, ?NS_DISCO_INFO, Node, IdentityXML ++ InfoXML ++ FeaturesXML)} + end. + +-spec process_sm_iq_items(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> + {mongoose_acc:t(), jlib:iq()}. +process_sm_iq_items(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) -> + {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; +process_sm_iq_items(Acc, From, To, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ, _Extra) -> + case is_presence_subscribed(From, To) of + true -> + HostType = mongoose_acc:host_type(Acc), + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + case mongoose_disco:get_sm_items(HostType, From, To, Node, Lang) of + empty -> + Error = sm_error(From, To), + {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}}; + {result, ItemsXML} -> + {Acc, make_iq_result(IQ, ?NS_DISCO_ITEMS, Node, ItemsXML)} + end; + false -> + {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]}} + end. + +-spec process_sm_iq_info(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> + {mongoose_acc:t(), jlib:iq()}. +process_sm_iq_info(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) -> + {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; +process_sm_iq_info(Acc, From, To, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ, _Extra) -> + case is_presence_subscribed(From, To) of + true -> + HostType = mongoose_acc:host_type(Acc), + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + case mongoose_disco:get_sm_features(HostType, From, To, Node, Lang) of + empty -> + Error = sm_error(From, To), + {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}}; + {result, FeaturesXML} -> + IdentityXML = mongoose_disco:get_sm_identity(HostType, From, To, Node, Lang), + {Acc, make_iq_result(IQ, ?NS_DISCO_INFO, Node, IdentityXML ++ FeaturesXML)} + end; + false -> + {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]}} end. --spec get_local_identity([mongoose_disco:identity()], jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - [mongoose_disco:identity()]. -get_local_identity(Acc, _From, _To, <<>>, _Lang) -> - [#{category => <<"server">>, - type => <<"im">>, - name => <<"MongooseIM">>}] ++ Acc; -get_local_identity(Acc, _From, _To, Node, _Lang) when is_binary(Node) -> +%% Hook handlers + +-spec disco_local_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_local_identity(Acc = #{node := <<>>}) -> + mongoose_disco:add_identities([#{category => <<"server">>, + type => <<"im">>, + name => <<"MongooseIM">>}], Acc); +disco_local_identity(Acc) -> Acc. --spec get_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -get_local_features(Acc, _From, _To, <<>>, _Lang) -> +-spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_sm_identity(Acc = #{to_jid := JID}) -> + case ejabberd_auth:does_user_exist(JID) of + true -> mongoose_disco:add_identities([#{category => <<"account">>, + type => <<"registered">>}], Acc); + false -> Acc + end. + +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{host_type := HostType, from_jid := From, to_jid := To, node := <<>>}) -> + ReturnHidden = should_return_hidden(HostType, From), + Subdomains = get_subdomains(To#jid.lserver), + Components = get_external_components(To#jid.lserver, ReturnHidden), + ExtraDomains = get_extra_domains(HostType), + Domains = Subdomains ++ Components ++ ExtraDomains, + mongoose_disco:add_items([#{jid => Domain} || Domain <- Domains], Acc); +disco_local_items(Acc) -> + Acc. + +-spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_sm_items(Acc = #{to_jid := To, node := <<>>}) -> + Items = get_user_resources(To), + mongoose_disco:add_items(Items, Acc); +disco_sm_items(Acc) -> + Acc. + +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([<<"iq">>, <<"presence">>, <<"presence-invisible">>], Acc); -get_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. --spec get_local_services(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -get_local_services(Acc, From, To, <<>>, _Lang) -> - Host = To#jid.lserver, - ReturnHidden = should_return_hidden(Host, From), - Domains = get_vh_services(Host, ReturnHidden) ++ get_extra_domains(Host), - mongoose_disco:add_items([#{jid => Domain} || Domain <- Domains], Acc); -get_local_services(Acc, _From, _To, _Node, _Lang) -> +%% @doc Support for: XEP-0157 Contact Addresses for XMPP Services +-spec disco_info(mongoose_disco:info_acc()) -> mongoose_disco:info_acc(). +disco_info(Acc = #{host_type := HostType, module := Module, node := <<>>}) -> + ServerInfoList = gen_mod:get_module_opt(HostType, ?MODULE, server_info, []), + Fields = lists:filtermap(fun(Info) -> process_server_info(Module, Info) end, ServerInfoList), + mongoose_disco:add_info([#{xmlns => ?NS_SERVERINFO, fields => Fields}], Acc); +disco_info(Acc) -> Acc. -get_extra_domains(Host) -> - gen_mod:get_module_opt(Host, ?MODULE, extra_domains, []). +-spec get_extra_domains(mongooseim:host_type()) -> [jid:lserver()]. +get_extra_domains(HostType) -> + gen_mod:get_module_opt(HostType, ?MODULE, extra_domains, []). + +%% Internal functions --spec should_return_hidden(Host :: jid:lserver(), From :: jid:jid()) -> return_hidden(). -should_return_hidden(_Host, #jid{ luser = <<>> } = _From) -> +-spec should_return_hidden(mongooseim:host_type(), From :: jid:jid()) -> return_hidden(). +should_return_hidden(_HostType, #jid{ luser = <<>> } = _From) -> %% We respect "is hidden" flag only when a client performs the query all; -should_return_hidden(Host, _From) -> - case gen_mod:get_module_opt(Host, ?MODULE, users_can_see_hidden_services, true) of +should_return_hidden(HostType, _From) -> + case gen_mod:get_module_opt(HostType, ?MODULE, users_can_see_hidden_services, true) of true -> all; false -> only_public end. --type route() :: binary(). --spec get_vh_services(jid:server(), return_hidden()) -> [route()]. -get_vh_services(Host, ReturnHidden) -> - VHosts = lists:sort(fun(H1, H2) -> size(H1) >= size(H2) end, ?MYHOSTS), - lists:filter(fun(Route) -> - check_if_host_is_the_shortest_suffix_for_route(Route, Host, VHosts) - end, ejabberd_router:dirty_get_all_routes(ReturnHidden)). +-spec get_subdomains(jid:lserver()) -> [jid:lserver()]. +get_subdomains(Domain) -> + [maps:get(subdomain, SubdomainInfo) || + SubdomainInfo <- mongoose_domain_api:get_all_subdomains_for_domain(Domain)]. + +%% TODO: This code can be removed when components register subdomains in the domain API. +%% Until then, it works only for static domains. +-spec get_external_components(jid:server(), return_hidden()) -> [jid:lserver()]. +get_external_components(Domain, ReturnHidden) -> + StaticDomains = lists:sort(fun(H1, H2) -> size(H1) >= size(H2) end, ?MYHOSTS), + lists:filter( + fun(Component) -> + check_if_host_is_the_shortest_suffix_for_route(Component, Domain, StaticDomains) + end, ejabberd_router:dirty_get_all_components(ReturnHidden)). -spec check_if_host_is_the_shortest_suffix_for_route( - Route :: route(), Host :: binary(), VHosts :: [binary()]) -> boolean(). + Route :: jid:lserver(), Host :: jid:lserver(), VHosts :: [jid:lserver()]) -> boolean(). check_if_host_is_the_shortest_suffix_for_route(Route, Host, VHosts) -> RouteS = binary_to_list(Route), case lists:dropwhile( @@ -218,41 +280,8 @@ check_if_host_is_the_shortest_suffix_for_route(Route, Host, VHosts) -> VH == Host end. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec process_sm_iq_items(jid:jid(), jid:jid(), mongoose_acc:t(), jlib:iq()) -> - {string(), jlib:iq()}. -process_sm_iq_items(_From, _To, Acc, #iq{type = set, sub_el = SubEl} = IQ) -> - {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; -process_sm_iq_items(From, To, Acc, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ) -> - case is_presence_subscribed(From, To) of - true -> - Host = To#jid.lserver, - Node = xml:get_tag_attr_s(<<"node">>, SubEl), - case mongoose_hooks:disco_sm_items(Host, From, To, Node, Lang) of - {result, Items} -> - ANode = make_node_attr(Node), - {Acc, IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS} | ANode], - children = mongoose_disco:items_to_xml(Items)}]}}; - empty -> - Error = sm_error(From, To), - {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}} - end; - false -> - {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]}} - end. - --spec get_sm_items(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> - mongoose_disco:item_acc(). -get_sm_items(Acc, _From, To, <<>>, _Lang) -> - Items = get_user_resources(To), - mongoose_disco:add_items(Items, Acc); -get_sm_items(Acc, _From, _To, _Node, _Lang) -> - Acc. - -spec is_presence_subscribed(jid:jid(), jid:jid()) -> boolean(). is_presence_subscribed(#jid{luser = LFromUser, lserver = LFromServer} = FromJID, #jid{luser = LToUser, lserver = LToServer} = _To) -> @@ -270,49 +299,12 @@ is_presence_subscribed(#jid{luser = LFromUser, lserver = LFromServer} = FromJID, Roster) orelse LFromUser == LToUser andalso LFromServer == LToServer. - --spec process_sm_iq_info(jid:jid(), jid:jid(), mongoose_acc:t(), jlib:iq()) -> - {mongoose_acc:t(), jlib:iq()}. -process_sm_iq_info(_From, _To, Acc, #iq{type = set, sub_el = SubEl} = IQ) -> - {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}}; -process_sm_iq_info(From, To, Acc, #iq{type = get, lang = Lang, sub_el = SubEl} = IQ) -> - case is_presence_subscribed(From, To) of - true -> - Host = To#jid.lserver, - Node = xml:get_tag_attr_s(<<"node">>, SubEl), - Identities = mongoose_hooks:disco_sm_identity(Host, From, To, Node, Lang), - case mongoose_hooks:disco_sm_features(Host, From, To, Node, Lang) of - {result, Features} -> - ANode = make_node_attr(Node), - IdentityXML = mongoose_disco:identities_to_xml(Identities), - FeatureXML = mongoose_disco:features_to_xml(Features), - {Acc, IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_INFO} | ANode], - children = IdentityXML ++ FeatureXML}]}}; - empty -> - Error = sm_error(From, To), - {Acc, IQ#iq{type = error, sub_el = [SubEl, Error]}} - end; - false -> - {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]}} - end. - sm_error(#jid{luser = LUser, lserver = LServer}, #jid{luser = LUser, lserver = LServer}) -> mongoose_xmpp_errors:item_not_found(); sm_error(_From, _To) -> mongoose_xmpp_errors:not_allowed(). --spec get_sm_identity([mongoose_disco:identity()], jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - [mongoose_disco:identity()]. -get_sm_identity(Acc, _From, JID = #jid{}, _Node, _Lang) -> - case ejabberd_auth:does_user_exist(JID) of - true -> [#{category => <<"account">>, type => <<"registered">>} | Acc]; - false -> Acc - end. - -spec get_user_resources(jid:jid()) -> [mongoose_disco:item()]. get_user_resources(JID) -> #jid{user = User, server = Server} = JID, @@ -324,64 +316,27 @@ get_user_resources(JID) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec make_node_attr(Node :: binary()) -> [{binary(), binary()}]. -make_node_attr(<<>>) -> []; -make_node_attr(Node) -> [{<<"node">>, Node}]. - -%%% Support for: XEP-0157 Contact Addresses for XMPP Services - --spec get_info(Acc :: [exml:element()], jid:server(), module(), Node :: binary(), - Lang :: ejabberd:lang()) -> [exml:element()]. -get_info(Acc, Host, Mod, Node, _Lang) when Node == <<>> -> - Module = case Mod of - undefined -> - ?MODULE; - _ -> - Mod - end, - ServerInfoFields = get_fields_xml(Host, Module), - FormTypeField = #xmlel{name = <<"field">>, - attrs = [{<<"var">>, <<"FORM_TYPE">>}, {<<"type">>, <<"hidden">>}], - children = [#xmlel{name = <<"value">>, - children = [#xmlcdata{content = ?NS_SERVERINFO}]}]}, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = [FormTypeField | ServerInfoFields]} | Acc]; -get_info(Acc, _, _, _Node, _) -> - Acc. - +-spec make_iq_result(jlib:iq(), binary(), binary(), [exml:element()]) -> jlib:iq(). +make_iq_result(IQ, NameSpace, Node, ChildrenXML) -> + IQ#iq{type = result, + sub_el = [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, NameSpace} | make_node_attrs(Node)], + children = ChildrenXML + }]}. --spec get_fields_xml(jid:server(), module()) -> [exml:element()]. -get_fields_xml(Host, Module) -> - Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []), +-spec make_node_attrs(Node :: binary()) -> [{binary(), binary()}]. +make_node_attrs(<<>>) -> []; +make_node_attrs(Node) -> [{<<"node">>, Node}]. - %% filter, and get only the ones allowed for this module - FilteredFields = lists:filter( - fun({Modules, _, _}) -> - case Modules of - all -> true; - Modules -> lists:member(Module, Modules) - end - end, - Fields), - - fields_to_xml(FilteredFields). - - --spec fields_to_xml([{Modules :: [module()], Var :: string(), Values :: [string()]}]) -> - [exml:element()]. -fields_to_xml(Fields) -> - [ field_to_xml(Field) || Field <- Fields]. - - --spec field_to_xml({Modules :: [module()], Var :: string(), Values :: [string()]}) -> exml:element(). -field_to_xml({_Module, Var, Values}) -> - #xmlel{name = <<"field">>, attrs = [{<<"var">>, list_to_binary(Var)}], - children = values_to_xml(Values)}. - - --spec values_to_xml([binary()]) -> [exml:element()]. -values_to_xml(Values) -> - [ #xmlel{name = <<"value">>, children = [#xmlcdata{content = list_to_binary(Value)}]} - || Value <- Values ]. +process_server_info(Module, ServerInfo) -> + case is_module_allowed(Module, proplists:get_value(modules, ServerInfo, all)) of + true -> + {true, #{var => proplists:get_value(name, ServerInfo), + values => proplists:get_value(urls, ServerInfo)}}; + false -> + false + end. +is_module_allowed(_Module, all) -> true; +is_module_allowed(undefined, Modules) -> is_module_allowed(?MODULE, Modules); +is_module_allowed(Module, Modules) -> lists:member(Module, Modules). diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 2dc1efc4a51..b573dfca4fd 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -62,7 +62,7 @@ -export([is_muc_room_owner/4, can_access_room/4, can_access_identity/4, - disco_local_items/5]). + disco_local_items/1]). %% Stats -export([online_rooms_number/0]). @@ -724,11 +724,14 @@ route_by_type(<<"iq">>, {From, To, Acc, Packet}, #state{} = State) -> MucHost = To#jid.lserver, case jlib:iq_query_info(Packet) of #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, lang = Lang} = IQ -> - Info = mongoose_hooks:disco_info(HostType, ?MODULE, <<"">>, Lang), + IdentityXML = mongoose_disco:identities_to_xml([identity(Lang)]), + FeatureXML = mongoose_disco:get_muc_features(HostType, From, To, <<>>, Lang, + features()), + InfoXML = mongoose_disco:get_info(HostType, ?MODULE, <<>>, Lang), Res = IQ#iq{type = result, sub_el = [#xmlel{name = <<"query">>, attrs = [{<<"xmlns">>, XMLNS}], - children = iq_disco_info(Lang, From, To) ++ Info}]}, + children = IdentityXML ++ FeatureXML ++ InfoXML}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ -> proc_lib:spawn(fun() -> process_iq_disco_items(MucHost, From, To, IQ) end); @@ -890,14 +893,10 @@ room_jid_to_pid(#jid{luser=RoomName, lserver=MucService}) -> default_host() -> mongoose_subdomain_utils:make_subdomain_pattern(<<"conference.@HOST@">>). --spec iq_disco_info(ejabberd:lang(), jid:jid(), jid:jid()) -> [exml:element(), ...]. -iq_disco_info(Lang, From, To) -> - RegisteredFeatures = mongoose_disco:get_local_features(To#jid.lserver, From, To, <<>>, Lang), - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, translate:translate(Lang, <<"Chatrooms">>)}]} | - mongoose_disco:features_to_xml(features() ++ RegisteredFeatures)]. +identity(Lang) -> + #{category => <<"conference">>, + type => <<"text">>, + name => translate:translate(Lang, <<"Chatrooms">>)}. features() -> [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, ?NS_MUC_UNIQUE, ?NS_REGISTER, ?NS_RSM, ?NS_VCARD]. @@ -1310,15 +1309,14 @@ subdomain_pattern(HostType) -> server_host_to_muc_host(HostType, ServerHost) -> mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost). --spec disco_local_items(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -disco_local_items(Acc, _From, #jid{lserver = ServerHost} = _To, <<>>, _Lang) -> - HostType = mod_muc_light_utils:server_host_to_host_type(ServerHost), +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{host_type := HostType, + to_jid := #jid{lserver = ServerHost}, + node := <<>>}) -> MUCHost = server_host_to_muc_host(HostType, ServerHost), Items = [#{jid => MUCHost, node => ?NS_MUC}], mongoose_disco:add_items(Items, Acc); -disco_local_items(Acc, _From, _To, _Node, _Lang) -> +disco_local_items(Acc) -> Acc. make_server_host(To, #state{host_type = HostType, diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 7bf95f7089c..c7c60d42d55 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -3923,21 +3923,23 @@ config_opt_to_feature(Opt, Fiftrue, Fiffalse) -> -spec process_iq_disco_info(jid:jid(), 'get' | 'set', ejabberd:lang(), state()) -> {'error', exml:element()} - | {'result', [exml:element(), ...], state()}. + | {'result', [exml:element()], state()}. process_iq_disco_info(_From, set, _Lang, _StateData) -> {error, mongoose_xmpp_errors:not_allowed()}; process_iq_disco_info(From, get, Lang, StateData) -> RoomJID = StateData#state.jid, Config = StateData#state.config, - Host = StateData#state.host, - RegisteredFeatures = mongoose_disco:get_local_features(Host, From, RoomJID, <<>>, Lang), - XML = [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, get_title(StateData)}]} | - mongoose_disco:features_to_xml(RegisteredFeatures ++ room_features(Config))] ++ - iq_disco_info_extras(Lang, StateData), - {result, XML, StateData}. + HostType = StateData#state.host_type, + IdentityXML = mongoose_disco:identities_to_xml([identity(get_title(StateData))]), + FeatureXML = mongoose_disco:get_muc_features(HostType, From, RoomJID, <<>>, Lang, + room_features(Config)), + InfoXML = iq_disco_info_extras(Lang, StateData), + {result, IdentityXML ++ FeatureXML ++ InfoXML, StateData}. + +identity(Name) -> + #{category => <<"conference">>, + type => <<"text">>, + name => Name}. -spec room_features(config()) -> [moongoose_disco:feature()]. room_features(Config) -> @@ -3955,37 +3957,18 @@ room_features(Config) -> config_opt_to_feature((Config#config.password_protected), <<"muc_passwordprotected">>, <<"muc_unsecured">>)]. --spec rfieldt(binary(), binary(), binary()) -> exml:element(). -rfieldt(Type, Var, Val) -> - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = [#xmlel{name = <<"value">>, - children = [#xmlcdata{content = Val}]}]}. - - --spec rfield(binary(), binary(), binary() | iolist(), ejabberd:lang()) -> exml:element(). -rfield(Label, Var, Val, Lang) -> - #xmlel{name = <<"field">>, - attrs = [{<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = [#xmlel{name = <<"value">>, - children = [#xmlcdata{content = Val}]}]}. - - --spec iq_disco_info_extras(ejabberd:lang(), state()) -> [exml:element(), ...]. +-spec iq_disco_info_extras(ejabberd:lang(), state()) -> [exml:element()]. iq_disco_info_extras(Lang, StateData) -> - Len = maps:size(StateData#state.users), - RoomDescription = (StateData#state.config)#config.description, - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = [rfieldt(<<"hidden">>, <<"FORM_TYPE">>, - <<"http://jabber.org/protocol/muc#roominfo">>), - rfield(<<"Room description">>, <<"muc#roominfo_description">>, - RoomDescription, Lang), - rfield(<<"Number of occupants">>, <<"muc#roominfo_occupants">>, - (integer_to_binary(Len)), Lang) - ]}]. - + Len = integer_to_binary(maps:size(StateData#state.users)), + Description = (StateData#state.config)#config.description, + Fields = [info_field(<<"Room description">>, <<"muc#roominfo_description">>, Description, Lang), + info_field(<<"Number of occupants">>, <<"muc#roominfo_occupants">>, Len, Lang)], + Info = #{xmlns => <<"http://jabber.org/protocol/muc#roominfo">>, fields => Fields}, + mongoose_disco:info_list_to_xml([Info]). + +-spec info_field(binary(), binary(), binary(), ejabberd:lang()) -> mongoose_disco:info_field(). +info_field(Label, Var, Value, Lang) -> + #{label => translate:translate(Lang, Label), var => Var, values => [Value]}. -spec process_iq_disco_items(jid:jid(), 'get' | 'set', ejabberd:lang(), state()) -> {'error', exml:element()} diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 540e87bd9b1..f3a85e76665 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -67,7 +67,6 @@ %% Hook handlers -export([process_local_iq/4, process_sm_iq/4, - get_local_features/5, remove_user/3, set_vcard/3]). @@ -353,7 +352,6 @@ hook_handlers() -> %% Hook, Module, Function, Priority [{remove_user, ?MODULE, remove_user, 50}, {anonymous_purge_hook, ?MODULE, remove_user, 50}, - {disco_local_features, ?MODULE, get_local_features, 50}, {host_config_update, ?MODULE, config_change, 50}, {set_vcard, ?MODULE, set_vcard, 50}, {get_personal_data, ?MODULE, get_personal_data, 50}]. @@ -475,26 +473,6 @@ set_vcard({error, no_handler_defined}, From, VCARD) -> end; set_vcard({error, _} = E, _From, _VCARD) -> E. --spec get_local_features(Acc :: {result, [XMLNS :: binary()]} | empty | {error, any()}, - From :: jid:jid(), - To :: jid:jid(), - Node :: binary(), - ejabberd:lang()) -> {result, [exml:element()]} | empty | {error, any()}. -get_local_features({error, _Error}=Acc, _From, _To, _Node, _Lang) -> - Acc; -get_local_features(Acc, _From, _To, Node, _Lang) -> - case Node of - <<>> -> - case Acc of - {result, Features} -> - {result, [?NS_VCARD | Features]}; - empty -> - {result, [?NS_VCARD]} - end; - _ -> - Acc - end. - %% #rh remove_user(Acc, User, Server) -> LUser = jid:nodeprep(User), @@ -575,22 +553,13 @@ do_route(_VHost, From, To, Acc, #iq{type = set, do_route(VHost, From, To, _Acc, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ) -> - Info = mongoose_hooks:disco_info(VHost, ?MODULE, <<"">>, <<"">>), - NameTxt = translate:translate(Lang, <<"vCard User Search">>), + IdentityXML = mongoose_disco:identities_to_xml([identity(Lang)]), + FeatureXML = mongoose_disco:features_to_xml(features()), + InfoXML = mongoose_disco:get_info(VHost, ?MODULE, <<>>, <<>>), ResIQ = IQ#iq{type = result, sub_el = [#xmlel{name = <<"query">>, - attrs =[{<<"xmlns">>, ?NS_DISCO_INFO}], - children = [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"directory">>}, - {<<"type">>, <<"user">>}, - {<<"name">>, NameTxt}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_SEARCH}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}]} - ] ++ Info}]}, + attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], + children = IdentityXML ++ FeatureXML ++ InfoXML}]}, ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); do_route(_VHost, From, To, Acc, #iq{type=set, xmlns = ?NS_DISCO_ITEMS}) -> @@ -638,6 +607,14 @@ find_xdata_el1([XE = #xmlel{attrs = Attrs} | Els]) -> find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). +features() -> + [?NS_DISCO_INFO, ?NS_SEARCH, ?NS_VCARD]. + +identity(Lang) -> + #{category => <<"directory">>, + type => <<"user">>, + name => translate:translate(Lang, <<"vCard User Search">>)}. + search_result(Lang, JID, VHost, Data, RSMIn) -> Text = translate:translate(Lang, <<"Search Results for ">>), TitleEl = #xmlel{name = <<"title">>, diff --git a/src/mod_version.erl b/src/mod_version.erl index 26e4d191516..51363246224 100644 --- a/src/mod_version.erl +++ b/src/mod_version.erl @@ -7,7 +7,7 @@ -include("mongoose.hrl"). -include("mongoose_config_spec.hrl"). --export([start/2, stop/1, config_spec/0, add_local_features/5, process_iq/4]). +-export([start/2, stop/1, config_spec/0, process_iq/4]). -xep([{xep, 92}, {version, "1.1"}]). @@ -15,17 +15,12 @@ start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), gen_iq_handler:add_iq_handler(ejabberd_local, Host, - ?NS_VERSION, ?MODULE, process_iq, IQDisc), - ejabberd_hooks:add(hooks(Host)). + ?NS_VERSION, ?MODULE, process_iq, IQDisc). -spec stop(jid:server()) -> any(). stop(Host) -> - ejabberd_hooks:delete(hooks(Host)), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VERSION). -hooks(Host) -> - [{disco_local_features, Host, ?MODULE, add_local_features, 99}]. - -spec config_spec() -> mongoose_config_spec:config_section(). config_spec() -> #section{ @@ -34,14 +29,6 @@ config_spec() -> } }. --spec add_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -add_local_features(Acc, _From, _To, <<>>, _Lang) -> - mongoose_disco:add_features([?NS_VERSION], Acc); -add_local_features(Acc, _From, _To, _Node, _Lang) -> - Acc. - -spec process_iq(jid:jid(), jid:jid(), mongoose_acc:t(), jlib:iq()) -> {mongoose_acc:t(), jlib:iq()}. process_iq(_From, _To, Acc, #iq{type = set, sub_el = SubEl} = IQ) -> diff --git a/src/mongoose_disco.erl b/src/mongoose_disco.erl index a0bf554bb12..9104f5b3a44 100644 --- a/src/mongoose_disco.erl +++ b/src/mongoose_disco.erl @@ -1,57 +1,156 @@ +%%% @doc This module is responsible for running disco_* hooks that collect information +%%% for service discovery. It also contains helpers for direct construction of XML elements +%%% representing the advertised services. + -module(mongoose_disco). --export([get_local_features/5, - add_features/2, - features_to_xml/1, +%% Hooks wrappers +-export([get_local_identity/5, + get_sm_identity/5, + get_local_items/5, + get_sm_items/5, + get_local_features/5, + get_sm_features/5, + get_muc_features/6, + get_info/4]). + +%% Helpers used by hook handlers for Acc manipulation +-export([add_identities/2, add_items/2, + add_features/2, + add_info/2]). + +%% XML construction helpers +-export([identities_to_xml/1, items_to_xml/1, - identities_to_xml/1]). + features_to_xml/1, + info_list_to_xml/1]). -include("mongoose.hrl"). -include("jlib.hrl"). --type feature_acc() :: empty | {result, [feature()]}. +-type feature_acc() :: acc(feature()). -type feature() :: binary(). --type item_acc() :: empty | {result, [item()]}. +-type item_acc() :: acc(item()). -type item() :: #{jid := jid:lserver(), name => binary(), node => binary()}. +-type identity_acc() :: acc(identity()). -type identity() :: #{category := binary(), type := binary(), name => binary()}. --export_type([item_acc/0, feature_acc/0, item/0, feature/0, identity/0]). - -%% @doc Run the 'disco_local_features' hook and unpack the results. -%% Used by extension modules which support their own subdomains -%% and add their own features to the end result. --spec get_local_features(jid:lserver(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> - [feature()]. -get_local_features(Host, From, To, Node, Lang) -> - case mongoose_hooks:disco_local_features(Host, From, To, Node, Lang) of - empty -> - []; - {result, Features} -> - Features +-type info_acc() :: #{host_type := mongooseim:host_type(), + module := module() | undefined, + node := binary(), + lang := ejabberd:lang(), + result := empty | [info()]}. +-type info() :: #{xmlns := binary(), fields := [info_field()]}. +-type info_field() :: #{var := binary(), values := [binary()], label => binary()}. + +-type acc(Elem) :: #{host_type := mongooseim:host_type(), + from_jid := jid:jid(), + to_jid := jid:jid(), + node := binary(), + lang := ejabberd:lang(), + result := empty | [Elem]}. + +-export_type([item_acc/0, feature_acc/0, identity_acc/0, + item/0, feature/0, identity/0, + info_field/0, info/0]). + +%% Hook wrapper API + +-spec get_local_identity(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + [exml:element()]. +get_local_identity(HostType, From, To, Node, Lang) -> + InitialAcc = new_acc(HostType, From, To, Node, Lang), + FinalAcc = mongoose_hooks:disco_local_identity(InitialAcc), + Identities = extract_result(FinalAcc), + identities_to_xml(Identities). + +-spec get_sm_identity(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + [exml:element()]. +get_sm_identity(HostType, From, To, Node, Lang) -> + InitialAcc = new_acc(HostType, From, To, Node, Lang), + IdentityAcc = mongoose_hooks:disco_sm_identity(InitialAcc), + Identities = extract_result(IdentityAcc), + identities_to_xml(Identities). + +-spec get_local_items(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + {result, [exml:element()]} | empty. +get_local_items(HostType, From, To, Node, Lang) -> + Acc = new_acc(HostType, From, To, Node, Lang), + case mongoose_hooks:disco_local_items(Acc) of + #{result := empty} -> empty; + #{result := Items} -> {result, items_to_xml(Items)} end. --spec add_features([feature()], feature_acc()) -> feature_acc(). -add_features(Features, empty) -> - {result, Features}; -add_features(Features, {result, InitialFeatures}) -> - {result, Features ++ InitialFeatures}. +-spec get_sm_items(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + {result, [exml:element()]} | empty. +get_sm_items(HostType, From, To, Node, Lang) -> + Acc = new_acc(HostType, From, To, Node, Lang), + case mongoose_hooks:disco_sm_items(Acc) of + #{result := empty} -> empty; + #{result := Items} -> {result, items_to_xml(Items)} + end. --spec features_to_xml([feature()]) -> [exml:element()]. -features_to_xml(Features) -> - %% Avoid duplicating features - [feature_to_xml(Feature) || Feature <- lists:usort(Features)]. +-spec get_local_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + {result, [exml:element()]} | empty. +get_local_features(HostType, From, To, Node, Lang) -> + Acc = new_acc(HostType, From, To, Node, Lang), + case mongoose_hooks:disco_local_features(Acc) of + #{result := empty} -> empty; + #{result := Features} -> {result, features_to_xml(Features)} + end. -feature_to_xml(Feature) when is_binary(Feature) -> - #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Feature}]}. +-spec get_sm_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + {result, [exml:element()]} | empty. +get_sm_features(HostType, From, To, Node, Lang) -> + Acc = new_acc(HostType, From, To, Node, Lang), + case mongoose_hooks:disco_sm_features(Acc) of + #{result := empty} -> empty; + #{result := Features} -> {result, features_to_xml(Features)} + end. + +-spec get_muc_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang(), + [mongoose_disco:feature()]) -> + [exml:element()]. +get_muc_features(HostType, From, To, Node, Lang, ExtraFeatures) -> + InitialAcc = new_acc(HostType, From, To, Node, Lang), + FinalAcc = mongoose_hooks:disco_muc_features(InitialAcc), + Features = ExtraFeatures ++ extract_result(FinalAcc), + features_to_xml(Features). + +-spec get_info(mongooseim:host_type(), module() | undefined, binary(), ejabberd:lang()) -> + [exml:element()]. +get_info(HostType, Module, Node, Lang) -> + InitialAcc = new_info_acc(HostType, Module, Node, Lang), + FinalAcc = mongoose_hooks:disco_info(InitialAcc), + InfoList = extract_result(FinalAcc), + info_list_to_xml(InfoList). + +%% Helper API + +-spec add_identities([identity()], identity_acc()) -> identity_acc(). +add_identities(Identities, Acc) -> + add(Identities, Acc). + +-spec add_items([item()], item_acc()) -> item_acc(). +add_items(Items, Acc) -> + add(Items, Acc). + +-spec add_features([feature()], feature_acc()) -> feature_acc(). +add_features(Features, Acc) -> + add(Features, Acc). + +-spec add_info([info()], info_acc()) -> info_acc(). +add_info(InfoList, Acc) -> + add(InfoList, Acc). --spec add_items([item()], item_acc()) -> item_acc(). -add_items(Items, empty) -> - {result, Items}; -add_items(Items, {result, InitialItems}) -> - {result, Items ++ InitialItems}. +%% XML construction API + +-spec identities_to_xml([identity()]) -> [exml:element()]. +identities_to_xml(Identities) -> + lists:map(fun identity_to_xml/1, Identities). -spec items_to_xml([item()]) -> [exml:element()]. items_to_xml(Items) -> @@ -60,16 +159,84 @@ items_to_xml(Items) -> %% than the default which is obtained from the registered routes and contains only the JID. maps:values(maps:from_list([{JID, item_to_xml(Item)} || #{jid := JID} = Item <- Items])). +-spec features_to_xml([feature()]) -> [exml:element()]. +features_to_xml(Features) -> + %% Avoid duplicating features + [feature_to_xml(Feature) || Feature <- lists:usort(Features)]. + +-spec info_list_to_xml([info()]) -> [exml:element()]. +info_list_to_xml(InfoList) -> + [info_to_xml(Info) || Info <- InfoList]. + +%% Acc manipulation + +-spec new_acc(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> + acc(any()). +new_acc(HostType, From, To, Node, Lang) -> + #{host_type => HostType, + from_jid => From, + to_jid => To, + node => Node, + lang => Lang, + result => empty}. + +-spec new_info_acc(mongooseim:host_type(), module(), binary(), ejabberd:lang()) -> info_acc(). +new_info_acc(HostType, Module, Node, Lang) -> + #{host_type => HostType, + module => Module, + node => Node, + lang => Lang, + result => empty}. + +-spec add([Elem], acc(Elem)) -> acc(Elem); + ([info()], info_acc()) -> info_acc(). +add(Elements, Acc = #{result := empty}) -> + Acc#{result := Elements}; +add(Elements, Acc = #{result := InitialElements}) -> + Acc#{result := Elements ++ InitialElements}. + +-spec extract_result(acc(Elem)) -> [Elem]; + (info_acc()) -> [info()]. +extract_result(#{result := empty}) -> []; +extract_result(#{result := Elements}) -> Elements. + +%% Conversion to XML + +feature_to_xml(Feature) when is_binary(Feature) -> + #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Feature}]}. + item_to_xml(Item) -> #xmlel{name = <<"item">>, attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end, maps:to_list(Item))}. --spec identities_to_xml([identity()]) -> [exml:element()]. -identities_to_xml(Identities) -> - lists:map(fun identity_to_xml/1, Identities). - identity_to_xml(Identity) -> #xmlel{name = <<"identity">>, attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end, maps:to_list(Identity))}. + +-spec info_to_xml(info()) -> exml:element(). +info_to_xml(#{xmlns := NS, fields := Fields}) -> + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], + children = [form_type_field_xml(NS) | + [info_field_to_xml(Field) || Field <- Fields]]}. + +-spec info_field_to_xml(info_field()) -> exml:element(). +info_field_to_xml(InfoField) -> + {Values, Attrs} = maps:take(values, InfoField), + #xmlel{name = <<"field">>, + attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end, + maps:to_list(Attrs)), + children = values_to_xml(Values)}. + +-spec values_to_xml([binary()]) -> [exml:element()]. +values_to_xml(Values) -> + [#xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]} || Value <- Values]. + +-spec form_type_field_xml(binary()) -> exml:element(). +form_type_field_xml(NS) -> + #xmlel{name = <<"field">>, + attrs = [{<<"var">>, <<"FORM_TYPE">>}, {<<"type">>, <<"hidden">>}], + children = [#xmlel{name = <<"value">>, + children = [#xmlcdata{content = NS}]}]}. diff --git a/src/mongoose_hooks.erl b/src/mongoose_hooks.erl index 1bf3c9a1f19..49576f594e2 100644 --- a/src/mongoose_hooks.erl +++ b/src/mongoose_hooks.erl @@ -114,13 +114,14 @@ s2s_stream_features/2, s2s_send_packet/4]). --export([disco_info/4, - disco_local_features/5, - disco_local_identity/5, - disco_local_items/5, - disco_sm_features/5, - disco_sm_identity/5, - disco_sm_items/5]). +-export([disco_local_identity/1, + disco_sm_identity/1, + disco_local_items/1, + disco_sm_items/1, + disco_local_features/1, + disco_sm_features/1, + disco_muc_features/1, + disco_info/1]). -export([amp_check_condition/3, amp_determine_strategy/5, @@ -1203,88 +1204,50 @@ s2s_receive_packet(Acc) -> %% Discovery related hooks -%%% @doc `disco_info' hook is called to extract information about the server. --spec disco_info(Server, Module, Node, Lang) -> Result when - Server :: jid:server(), - Module :: module(), - Node :: binary(), - Lang :: ejabberd:lang(), - Result :: [exml:element()]. -disco_info(Server, Module, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_info, Server, [], - [Server, Module, Node, Lang]). +%%% @doc `disco_local_identity' hook is called to get the identity of the server. +-spec disco_local_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_local_identity(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_local_identity, HostType, Acc, []). -%%% @doc `disco_local_features' hook is called to extract features -%%% offered by the server. --spec disco_local_features(Server, From, To, Node, Lang) -> Result when - Server :: jid:server(), - From :: jid:jid(), - To :: jid:jid(), - Node :: binary(), - Lang :: ejabberd:lang(), - Result :: mongoose_disco:feature_acc(). -disco_local_features(Server, From, To, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_local_features, Server, empty, - [From, To, Node, Lang]). +%%% @doc `disco_sm_identity' hook is called to get the identity of the +%%% client when a discovery IQ gets to session management. +-spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_sm_identity(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_sm_identity, HostType, Acc, []). %%% @doc `disco_local_items' hook is called to extract items associated with the server. --spec disco_local_items(HostType, From, To, Node, Lang) -> Result when - HostType :: mongooseim:host_type(), - From :: jid:jid(), - To :: jid:jid(), - Node :: binary(), - Lang :: ejabberd:lang(), - Result :: mongoose_disco:item_acc(). -disco_local_items(HostType, From, To, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_local_items, HostType, empty, - [From, To, Node, Lang]). +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_local_items, HostType, Acc, []). -%%% @doc `disco_local_identity' hook is called to get the identity of the server. --spec disco_local_identity(Server, From, To, Node, Lang) -> Result when - Server :: jid:server(), - From :: jid:jid(), - To :: jid:jid(), - Node :: binary(), - Lang :: ejabberd:lang(), - Result :: [mongoose_disco:identity()]. -disco_local_identity(Server, From, To, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_local_identity, Server, [], - [From, To, Node, Lang]). +%%% @doc `disco_sm_items' hook is called to get the items associated +%%% with the client when a discovery IQ gets to session management. +-spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_sm_items(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_sm_items, HostType, Acc, []). + +%%% @doc `disco_local_features' hook is called to extract features +%%% offered by the server. +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_local_features, HostType, Acc, []). %%% @doc `disco_sm_features' hook is called to get the features of the client %%% when a discovery IQ gets to session management. --spec disco_sm_features(Server, From, To, Node, Lang) -> Result when - Server :: jid:server(), - From :: jid:jid(), - To :: jid:jid(), - Node :: binary(), - Lang :: ejabberd:lang(), - Result :: mongoose_disco:feature_acc(). -disco_sm_features(Server, From, To, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_sm_features, Server, empty, - [From, To, Node, Lang]). +-spec disco_sm_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_sm_features(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_sm_features, HostType, Acc, []). -%%% @doc `disco_sm_identity' hook is called to get the identity of the -%%% client when a discovery IQ gets to session management. --spec disco_sm_identity(Server :: jid:server(), - From :: jid:jid(), - To :: jid:jid(), - Node :: mod_pubsub:nodeId(), - Lang :: ejabberd:lang()) -> [mongoose_disco:identity()]. -disco_sm_identity(Server, From, To, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_sm_identity, Server, [], - [From, To, Node, Lang]). +%%% @doc `disco_muc_features' hook is called to get the features +%%% supported by the MUC (Light) service. +-spec disco_muc_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_muc_features(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_muc_features, HostType, Acc, []). -%%% @doc `disco_sm_items' hook is called to get the items associated -%%% with the client when a discovery IQ gets to session management. --spec disco_sm_items(Server :: jid:server(), - From :: jid:jid(), - To :: jid:jid(), - Node :: binary(), - Lang :: ejabberd:lang()) -> mongoose_disco:item_acc(). -disco_sm_items(Server, From, To, Node, Lang) -> - ejabberd_hooks:run_for_host_type(disco_sm_items, Server, empty, - [From, To, Node, Lang]). +%%% @doc `disco_info' hook is called to extract information about the server. +-spec disco_info(mongoose_disco:info_acc()) -> mongoose_disco:info_acc(). +disco_info(Acc = #{host_type := HostType}) -> + ejabberd_hooks:run_for_host_type(disco_info, HostType, Acc, []). %% AMP related hooks diff --git a/src/muc_light/mod_muc_light.erl b/src/muc_light/mod_muc_light.erl index 60b174dcd2f..85430ddc67d 100644 --- a/src/muc_light/mod_muc_light.erl +++ b/src/muc_light/mod_muc_light.erl @@ -44,7 +44,7 @@ %% Hook handlers -export([prevent_service_unavailable/4, - disco_local_items/5, + disco_local_items/1, remove_user/3, remove_domain/3, add_rooms_to_roster/2, @@ -278,7 +278,7 @@ hooks(HostType) -> {offline_groupchat_message_hook, HostType, ?MODULE, prevent_service_unavailable, 90}, {remove_user, HostType, ?MODULE, remove_user, 50}, {remove_domain, HostType, ?MODULE, remove_domain, 50}, - {disco_local_items, HostType, ?MODULE, disco_local_items, 250}] ++ + {disco_local_items, HostType, ?MODULE, disco_local_items, 50}] ++ case Codec of legacy -> [{privacy_iq_get, HostType, ?MODULE, process_iq_get, 1}, @@ -372,11 +372,10 @@ prevent_service_unavailable(Acc, _From, _To, Packet) -> _Type -> Acc end. --spec disco_local_items(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -disco_local_items(Acc, _From, #jid{lserver = ServerHost} = _To, <<>>, _Lang) -> - HostType = mod_muc_light_utils:server_host_to_host_type(ServerHost), +-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_local_items(Acc = #{host_type := HostType, + to_jid := #jid{lserver = ServerHost}, + node := <<>>}) -> XMLNS = case legacy_mode(HostType) of true -> ?NS_MUC; false -> ?NS_MUC_LIGHT @@ -384,7 +383,7 @@ disco_local_items(Acc, _From, #jid{lserver = ServerHost} = _To, <<>>, _Lang) -> MUCHost = server_host_to_muc_host(HostType, ServerHost), Items = [#{jid => MUCHost, node => XMLNS}], mongoose_disco:add_items(Items, Acc); -disco_local_items(Acc, _From, _To, _Node, _Lang) -> +disco_local_items(Acc) -> Acc. legacy_mode(HostType) -> diff --git a/src/muc_light/mod_muc_light_codec_legacy.erl b/src/muc_light/mod_muc_light_codec_legacy.erl index 1631a462d65..03c38315446 100644 --- a/src/muc_light/mod_muc_light_codec_legacy.erl +++ b/src/muc_light/mod_muc_light_codec_legacy.erl @@ -247,14 +247,12 @@ parse_blocking_list([Item | RItemsEls], ItemsAcc) -> {iq_reply, ID :: binary()} | {iq_reply, XMLNS :: binary(), Els :: [jlib:xmlch()], ID :: binary()} | noreply. -encode_meta({get, #disco_info{ id = ID }}, RoomJID, SenderJID, _HandleFun, _Acc) -> - LServer = RoomJID#jid.lserver, - RegisteredFeatures = mongoose_disco:get_local_features(LServer, SenderJID, RoomJID, <<>>, <<>>), - DiscoEls = [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, <<"MUC Light (legacy)">>}]} | - mongoose_disco:features_to_xml([?NS_MUC | RegisteredFeatures])], +encode_meta({get, #disco_info{ id = ID }}, RoomJID, SenderJID, _HandleFun, Acc) -> + HostType = mod_muc_light_utils:acc_to_host_type(Acc), + IdentityXML = mongoose_disco:identities_to_xml([identity()]), + FeatureXML = mongoose_disco:get_muc_features(HostType, SenderJID, RoomJID, <<>>, <<>>, + [?NS_MUC]), + DiscoEls = IdentityXML ++ FeatureXML, {iq_reply, ?NS_DISCO_INFO, DiscoEls, ID}; encode_meta({get, #disco_items{ rooms = Rooms, id = ID, rsm = RSMOut }}, _RoomJID, _SenderJID, _HandleFun, _Acc) -> @@ -351,6 +349,10 @@ encode_meta({set, #config{} = Config, AffUsers}, RoomJID, _SenderJID, HandleFun, %% --------------------------- Helpers --------------------------- +-spec identity() -> mongoose_disco:identity(). +identity() -> + #{category => <<"conference">>, type => <<"text">>, name => <<"MUC Light (legacy)">>}. + -spec aff_user_to_item(aff_user()) -> exml:element(). aff_user_to_item({User, Aff}) -> UserBin = jid:to_binary(User), diff --git a/src/muc_light/mod_muc_light_codec_modern.erl b/src/muc_light/mod_muc_light_codec_modern.erl index 82c7706ecb4..cc21673517a 100644 --- a/src/muc_light/mod_muc_light_codec_modern.erl +++ b/src/muc_light/mod_muc_light_codec_modern.erl @@ -255,14 +255,12 @@ parse_blocking_list(_, _) -> %% Encoding %%==================================================================== -encode_iq({get, #disco_info{ id = ID }}, Sender, RoomJID, _RoomBin, _HandleFun, _Acc) -> - LServer = RoomJID#jid.lserver, - RegisteredFeatures = mongoose_disco:get_local_features(LServer, Sender, RoomJID, <<>>, <<>>), - DiscoEls = [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, <<"MUC Light (modern)">>}]} | - mongoose_disco:features_to_xml([?NS_MUC_LIGHT | RegisteredFeatures])], +encode_iq({get, #disco_info{ id = ID }}, Sender, RoomJID, _RoomBin, _HandleFun, Acc) -> + HostType = mod_muc_light_utils:acc_to_host_type(Acc), + IdentityXML = mongoose_disco:identities_to_xml([identity()]), + FeatureXML = mongoose_disco:get_muc_features(HostType, Sender, RoomJID, <<>>, <<>>, + [?NS_MUC_LIGHT]), + DiscoEls = IdentityXML ++ FeatureXML, {reply, ?NS_DISCO_INFO, DiscoEls, ID}; encode_iq({get, #disco_items{ rooms = Rooms, id = ID, rsm = RSMOut }}, _Sender, _RoomJID, _RoomBin, _HandleFun, _Acc) -> @@ -408,6 +406,10 @@ encode_set_config(Config, RoomBin) -> %% --------------------------- Helpers --------------------------- +-spec identity() -> mongoose_disco:identity(). +identity() -> + #{category => <<"conference">>, type => <<"text">>, name => <<"MUC Light (modern)">>}. + -spec aff_user_to_el(aff_user()) -> exml:element(). aff_user_to_el({User, Aff}) -> #xmlel{ name = <<"user">>, diff --git a/src/offline/mod_offline.erl b/src/offline/mod_offline.erl index 337fb318e55..331a632b1ba 100644 --- a/src/offline/mod_offline.erl +++ b/src/offline/mod_offline.erl @@ -39,7 +39,7 @@ %% Hook handlers -export([inspect_packet/4, pop_offline_messages/2, - get_sm_features/5, + disco_features/1, remove_expired_messages/1, remove_old_messages/2, remove_user/2, % for tests @@ -166,8 +166,8 @@ hooks(Host) -> {resend_offline_messages_hook, Host, ?MODULE, pop_offline_messages, 50}, {remove_user, Host, ?MODULE, remove_user, 50}, {anonymous_purge_hook, Host, ?MODULE, remove_user, 50}, - {disco_sm_features, Host, ?MODULE, get_sm_features, 50}, - {disco_local_features, Host, ?MODULE, get_sm_features, 50}, + {disco_sm_features, Host, ?MODULE, disco_features, 50}, + {disco_local_features, Host, ?MODULE, disco_features, 50}, {amp_determine_strategy, Host, ?MODULE, determine_amp_strategy, 30}, {failed_to_store_message, Host, ?MODULE, amp_failed_event, 30}, {get_personal_data, Host, ?MODULE, get_personal_data, 50} @@ -361,15 +361,13 @@ code_change(_OldVsn, State, _Extra) -> %% Handlers %% ------------------------------------------------------------------ --spec get_sm_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -get_sm_features(Acc, _From, _To, <<"">> = _Node, _Lang) -> +-spec disco_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_features(Acc = #{node := <<>>}) -> mongoose_disco:add_features([?NS_FEATURE_MSGOFFLINE], Acc); -get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) -> +disco_features(Acc = #{node := ?NS_FEATURE_MSGOFFLINE}) -> %% override all lesser features... - {result, []}; -get_sm_features(Acc, _From, _To, _Node, _Lang) -> + Acc#{result := []}; +disco_features(Acc) -> Acc. %% This function should be called only from a hook diff --git a/src/pubsub/mod_pubsub.erl b/src/pubsub/mod_pubsub.erl index 689dc62a807..6e881718f7c 100644 --- a/src/pubsub/mod_pubsub.erl +++ b/src/pubsub/mod_pubsub.erl @@ -69,9 +69,9 @@ -export([presence_probe/4, caps_recognised/4, in_subscription/5, out_subscription/4, on_user_offline/5, remove_user/3, - disco_local_features/5, - disco_sm_identity/5, - disco_sm_features/5, disco_sm_items/5, handle_pep_authorization_response/1, + disco_local_features/1, + disco_sm_identity/1, + disco_sm_features/1, disco_sm_items/1, handle_pep_authorization_response/1, handle_remote_hook/4]). %% exported iq handlers @@ -642,22 +642,17 @@ node_identity(Host, Type) -> false -> [] end. --spec disco_local_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -disco_local_features(Acc, _From, To, <<>>, _Lang) -> - Host = To#jid.lserver, - Features = [?NS_PUBSUB | [feature(F) || F <- features(Host, <<>>)]], +-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}) -> + Features = [?NS_PUBSUB | [feature(F) || F <- features(LServer, <<>>)]], mongoose_disco:add_features(Features, Acc); -disco_local_features(Acc, _From, _To, _Node, _Lang) -> +disco_local_features(Acc) -> Acc. --spec disco_sm_identity([mongoose_disco:identity()], jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - [mongoose_disco:identity()]. -disco_sm_identity(Acc, From, To, Node, _Lang) -> - disco_identity(jid:to_lower(jid:to_bare(To)), Node, From) - ++ Acc. +-spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). +disco_sm_identity(Acc = #{from_jid := From, to_jid := To, node := Node}) -> + Identities = disco_identity(jid:to_lower(jid:to_bare(To)), Node, From), + mongoose_disco:add_identities(Identities, Acc). disco_identity(error, _Node, _From) -> []; @@ -688,10 +683,8 @@ pep_identity(Options) -> pep_identity() -> #{category => <<"pubsub">>, type => <<"pep">>}. --spec disco_sm_features(mongoose_disco:feature_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:feature_acc(). -disco_sm_features(Acc, From, To, Node, _Lang) -> +-spec disco_sm_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). +disco_sm_features(Acc = #{from_jid := From, to_jid := To, node := Node}) -> Features = disco_features(jid:to_lower(jid:to_bare(To)), Node, From), mongoose_disco:add_features(Features, Acc). @@ -716,10 +709,8 @@ disco_features(Host, Node, From) -> _ -> [] end. --spec disco_sm_items(mongoose_disco:item_acc(), jid:jid(), jid:jid(), binary(), - ejabberd:lang()) -> - mongoose_disco:item_acc(). -disco_sm_items(Acc, From, To, Node, _Lang) -> +-spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). +disco_sm_items(Acc = #{from_jid := From, to_jid := To, node := Node}) -> Items = disco_items(jid:to_lower(jid:to_bare(To)), Node, From), mongoose_disco:add_items(Items, Acc). @@ -1010,15 +1001,14 @@ do_route(ServerHost, Access, Plugins, Host, From, #iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ -> #xmlel{attrs = QAttrs} = SubEl, Node = xml:get_attr_s(<<"node">>, QAttrs), - Info = mongoose_hooks:disco_info(ServerHost, - ?MODULE, <<>>, <<>>), + InfoXML = mongoose_disco:get_info(ServerHost, ?MODULE, <<>>, <<>>), Res = case iq_disco_info(Host, Node, From, Lang) of {result, IQRes} -> jlib:iq_to_xml(IQ#iq{type = result, sub_el = [#xmlel{name = <<"query">>, attrs = QAttrs, - children = IQRes ++ Info}]}); + children = IQRes ++ InfoXML}]}); {error, Error} -> make_error_reply(Packet, Error) end, diff --git a/test/config_parser_SUITE.erl b/test/config_parser_SUITE.erl index b0319880931..9582875f8e3 100644 --- a/test/config_parser_SUITE.erl +++ b/test/config_parser_SUITE.erl @@ -1563,14 +1563,17 @@ mod_disco(_Config) -> T(<<"extra_domains">>, [])), Info = #{<<"name">> => <<"abuse-address">>, <<"urls">> => [<<"admin@example.com">>]}, - ?eqf(modopts(mod_disco, [{server_info, [{all, "abuse-address", ["admin@example.com"]}, - {[mod_muc, mod_disco], "friendly-spirits", - ["spirit1@localhost", "spirit2@localhost"]}]} + SpiritUrls = [<<"spirit1@localhost">>, <<"spirit2@localhost">>], + ?eqf(modopts(mod_disco, [{server_info, [[{name, <<"abuse-address">>}, + {urls, [<<"admin@example.com">>]}], + [{modules, [mod_muc, mod_disco]}, + {name, <<"friendly-spirits">>}, + {urls, SpiritUrls}] + ]} ]), T(<<"server_info">>, [Info, #{<<"modules">> => [<<"mod_muc">>, <<"mod_disco">>], <<"name">> => <<"friendly-spirits">>, - <<"urls">> => [<<"spirit1@localhost">>, - <<"spirit2@localhost">>]} + <<"urls">> => SpiritUrls} ])), ?errf(T(<<"users_can_see_hidden_services">>, 1)), ?errf(T(<<"users_can_see_hidden_services">>, <<"true">>)), diff --git a/test/config_parser_SUITE_data/modules.options b/test/config_parser_SUITE_data/modules.options index f7508013d9d..10deb0e3035 100644 --- a/test/config_parser_SUITE_data/modules.options +++ b/test/config_parser_SUITE_data/modules.options @@ -73,10 +73,10 @@ [{extra_domains,[<<"some_domain">>,<<"another_domain">>]}, {iqdisc,one_queue}, {server_info, - [{all,"abuse-address",["admin@example.com"]}, - {[mod_muc,mod_disco], - "friendly-spirits", - ["spirit1@localhost","spirit2@localhost"]}]}, + [[{name,<<"abuse-address">>},{urls,[<<"admin@example.com">>]}], + [{modules,[mod_muc,mod_disco]}, + {name,<<"friendly-spirits">>}, + {urls,[<<"spirit1@localhost">>,<<"spirit2@localhost">>]}]]}, {users_can_see_hidden_services,true}]}, {mod_event_pusher_http, [{configs, @@ -337,10 +337,10 @@ [{extra_domains,[<<"some_domain">>,<<"another_domain">>]}, {iqdisc,one_queue}, {server_info, - [{all,"abuse-address",["admin@example.com"]}, - {[mod_muc,mod_disco], - "friendly-spirits", - ["spirit1@localhost","spirit2@localhost"]}]}, + [[{name,<<"abuse-address">>},{urls,[<<"admin@example.com">>]}], + [{modules,[mod_muc,mod_disco]}, + {name,<<"friendly-spirits">>}, + {urls,[<<"spirit1@localhost">>,<<"spirit2@localhost">>]}]]}, {users_can_see_hidden_services,true}]}, {mod_event_pusher_http, [{configs, diff --git a/test/mongoose_cleanup_SUITE.erl b/test/mongoose_cleanup_SUITE.erl index 380509b60db..a957047d41c 100644 --- a/test/mongoose_cleanup_SUITE.erl +++ b/test/mongoose_cleanup_SUITE.erl @@ -173,6 +173,8 @@ setup_meck([ejabberd_local | R]) -> setup_meck(R); setup_meck([ejabberd_config | R]) -> meck:new(ejabberd_config), + meck:expect(ejabberd_config, get_global_option_or_default, + fun(_, Default) -> Default end), meck:expect(ejabberd_config, get_global_option, fun (hosts) -> [];