diff options
Diffstat (limited to 'src/mod_pubsub/mod_pubsub.erl')
-rw-r--r-- | src/mod_pubsub/mod_pubsub.erl | 6199 |
1 files changed, 3793 insertions, 2406 deletions
diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index 0cda9de11..5f1d38e7d 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -5,11 +5,13 @@ %%% Erlang Public License along with this software. If not, it can be %%% retrieved via the world wide web at http://www.erlang.org/. %%% +%%% %%% Software distributed under the License is distributed on an "AS IS" %%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %%% the License for the specific language governing rights and limitations %%% under the License. %%% +%%% %%% The Initial Developer of the Original Code is ProcessOne. %%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne %%% All Rights Reserved.'' @@ -22,7 +24,6 @@ %%% @end %%% ==================================================================== - %%% @doc The module <strong>{@module}</strong> is the core of the PubSub %%% extension. It relies on PubSub plugins for a large part of its functions. %%% @@ -43,38 +44,39 @@ %%% XEP-0060 section 12.18. -module(mod_pubsub). + -author('christophe.romain@process-one.net'). + -version('1.13-0'). -behaviour(gen_server). + -behaviour(gen_mod). -include("ejabberd.hrl"). + -include("adhoc.hrl"). + -include("jlib.hrl"). + -include("pubsub.hrl"). --define(STDTREE, "tree"). --define(STDNODE, "flat"). --define(PEPNODE, "pep"). +-define(STDTREE, <<"tree">>). + +-define(STDNODE, <<"flat">>). + +-define(PEPNODE, <<"pep">>). %% exports for hooks --export([presence_probe/3, - caps_update/3, - in_subscription/6, - out_subscription/4, - on_user_offline/3, - remove_user/2, - disco_local_identity/5, - disco_local_features/5, - disco_local_items/5, - disco_sm_identity/5, - disco_sm_features/5, - disco_sm_items/5 - ]). +-export([presence_probe/3, caps_update/3, + in_subscription/6, out_subscription/4, + on_user_offline/3, remove_user/2, + disco_local_identity/5, disco_local_features/5, + disco_local_items/5, disco_sm_identity/5, + disco_sm_features/5, disco_sm_items/5]). + %% exported iq handlers --export([iq_sm/3 - ]). +-export([iq_sm/3]). %% exports for console debug manual use -export([create_node/5, @@ -95,47 +97,22 @@ ]). %% general helpers for plugins --export([node_to_string/1, - string_to_node/1, - subscription_to_string/1, - affiliation_to_string/1, - string_to_subscription/1, - string_to_affiliation/1, - extended_error/2, - extended_error/3, - rename_default_nodeplugin/0 - ]). +-export([subscription_to_string/1, affiliation_to_string/1, + string_to_subscription/1, string_to_affiliation/1, + extended_error/2, extended_error/3, + rename_default_nodeplugin/0]). %% API and gen_server callbacks --export([start_link/2, - start/2, - stop/1, - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2, - code_change/3 - ]). +-export([start_link/2, start/2, stop/1, init/1, + handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). %% calls for parallel sending of last items --export([send_loop/1 - ]). +-export([send_loop/1]). -define(PROCNAME, ejabberd_mod_pubsub). + -define(LOOPNAME, ejabberd_mod_pubsub_loop). --define(PLUGIN_PREFIX, "node_"). --define(TREE_PREFIX, "nodetree_"). - --record(state, {server_host, - host, - access, - pep_mapping = [], - ignore_pep_from_offline = true, - last_item_cache = false, - max_items_node = ?MAXITEMS, - nodetree = ?STDTREE, - plugins = [?STDNODE]}). %%==================================================================== %% API @@ -144,14 +121,127 @@ %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- +-define(PLUGIN_PREFIX, <<"node_">>). + +-define(TREE_PREFIX, <<"nodetree_">>). + +% +-export_type([ + host/0, + hostPubsub/0, + hostPEP/0, + %% + nodeIdx/0, + nodeId/0, + itemId/0, + subId/0, + payload/0, + %% + nodeOption/0, + nodeOptions/0, + subOption/0, + subOptions/0, + %% + affiliation/0, + subscription/0, + accessModel/0, + publishModel/0 +]). + +%% -type payload() defined here because the -type xmlel() is not accessible +%% from pubsub.hrl +-type(payload() :: [] | [xmlel(),...]). + +-export_type([ + pubsubNode/0, + pubsubState/0, + pubsubItem/0, + pubsubSubscription/0, + pubsubLastItem/0 +]). + +-type(pubsubNode() :: + #pubsub_node{ + nodeid :: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()}, + id :: mod_pubsub:nodeIdx(), + parents :: [Parent_NodeId::mod_pubsub:nodeId()], + type :: binary(), + owners :: [Owner::ljid(),...], + options :: mod_pubsub:nodeOptions() + } +). + +-type(pubsubState() :: + #pubsub_state{ + stateid :: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()}, + items :: [ItemId::mod_pubsub:itemId()], + affiliation :: mod_pubsub:affiliation(), + subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}] + } +). + +-type(pubsubItem() :: + #pubsub_item{ + itemid :: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()}, + creation :: {erlang:timestamp(), ljid()}, + modification :: {erlang:timestamp(), ljid()}, + payload :: mod_pubsub:payload() + } +). + +-type(pubsubSubscription() :: + #pubsub_subscription{ + subid :: mod_pubsub:subId(), + options :: [] | mod_pubsub:subOptions() + } +). + +-type(pubsubLastItem() :: + #pubsub_last_item{ + nodeid :: mod_pubsub:nodeIdx(), + itemid :: mod_pubsub:itemId(), + creation :: {erlang:timestamp(), ljid()}, + payload :: mod_pubsub:payload() + } +). + +-record(state, +{ + server_host, + host, + access, + pep_mapping = [], + ignore_pep_from_offline = true, + last_item_cache = false, + max_items_node = ?MAXITEMS, + nodetree = ?STDTREE, + plugins = [?STDNODE] +}). + +-type(state() :: + #state{ + server_host :: binary(), + host :: mod_pubsub:hostPubsub(), + access :: atom(), + pep_mapping :: [{binary(), binary()}], + ignore_pep_from_offline :: boolean(), + last_item_cache :: boolean(), + max_items_node :: non_neg_integer(), + nodetree :: binary(), + plugins :: [binary(),...] + } + +). + + start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + gen_server:start_link({local, Proc}, ?MODULE, + [Host, Opts], []). start(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = {Proc, - {?MODULE, start_link, [Host, Opts]}, + ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, transient, 1000, worker, [?MODULE]}, supervisor:start_child(ejabberd_sup, ChildSpec). @@ -171,65 +261,104 @@ stop(Host) -> %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- +-spec(init/1 :: +( + _:: _) + -> {ok, state()} +). + init([ServerHost, Opts]) -> - ?DEBUG("pubsub init ~p ~p",[ServerHost,Opts]), - Host = gen_mod:get_opt_host(ServerHost, Opts, "pubsub.@HOST@"), - Access = gen_mod:get_opt(access_createnode, Opts, all), - PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true), - IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false), - MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS), + ?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]), + Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>), + Access = gen_mod:get_opt(access_createnode, Opts, + fun(A) when is_atom(A) -> A end, all), + PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, + fun(A) when is_boolean(A) -> A end, true), + IQDisc = gen_mod:get_opt(iqdisc, Opts, + fun(A) when is_atom(A) -> A end, one_queue), + LastItemCache = gen_mod:get_opt(last_item_cache, Opts, + fun(A) when is_boolean(A) -> A end, false), + MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, + fun(A) when is_integer(A) andalso A >= 0 -> A end, ?MAXITEMS), pubsub_index:init(Host, ServerHost, Opts), - ets:new(gen_mod:get_module_proc(Host, config), [set, named_table]), - ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]), - {Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts), - mnesia:create_table(pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]), + ets:new(gen_mod:get_module_proc(Host, config), + [set, named_table]), + ets:new(gen_mod:get_module_proc(ServerHost, config), + [set, named_table]), + {Plugins, NodeTree, PepMapping} = init_plugins(Host, + ServerHost, Opts), + mnesia:create_table(pubsub_last_item, + [{ram_copies, [node()]}, + {attributes, record_info(fields, pubsub_last_item)}]), mod_disco:register_feature(ServerHost, ?NS_PUBSUB), - ets:insert(gen_mod:get_module_proc(Host, config), {nodetree, NodeTree}), - ets:insert(gen_mod:get_module_proc(Host, config), {plugins, Plugins}), - ets:insert(gen_mod:get_module_proc(Host, config), {last_item_cache, LastItemCache}), - ets:insert(gen_mod:get_module_proc(Host, config), {max_items_node, MaxItemsNode}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}), - ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}), - ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75), - ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), - ejabberd_hooks:add(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75), - ejabberd_hooks:add(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75), - ejabberd_hooks:add(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 80), - ejabberd_hooks:add(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50), - ejabberd_hooks:add(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50), - ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, remove_user, 50), - ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50), + ets:insert(gen_mod:get_module_proc(Host, config), + {nodetree, NodeTree}), + ets:insert(gen_mod:get_module_proc(Host, config), + {plugins, Plugins}), + ets:insert(gen_mod:get_module_proc(Host, config), + {last_item_cache, LastItemCache}), + ets:insert(gen_mod:get_module_proc(Host, config), + {max_items_node, MaxItemsNode}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {nodetree, NodeTree}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {plugins, Plugins}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {last_item_cache, LastItemCache}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {max_items_node, MaxItemsNode}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {pep_mapping, PepMapping}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {ignore_pep_from_offline, PepOffline}), + ets:insert(gen_mod:get_module_proc(ServerHost, config), + {host, Host}), + ejabberd_hooks:add(sm_remove_connection_hook, + ServerHost, ?MODULE, on_user_offline, 75), + ejabberd_hooks:add(disco_local_identity, ServerHost, + ?MODULE, disco_local_identity, 75), + ejabberd_hooks:add(disco_local_features, ServerHost, + ?MODULE, disco_local_features, 75), + ejabberd_hooks:add(disco_local_items, ServerHost, + ?MODULE, disco_local_items, 75), + ejabberd_hooks:add(presence_probe_hook, ServerHost, + ?MODULE, presence_probe, 80), + ejabberd_hooks:add(roster_in_subscription, ServerHost, + ?MODULE, in_subscription, 50), + ejabberd_hooks:add(roster_out_subscription, ServerHost, + ?MODULE, out_subscription, 50), + ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, + remove_user, 50), + ejabberd_hooks:add(anonymous_purge_hook, ServerHost, + ?MODULE, remove_user, 50), case lists:member(?PEPNODE, Plugins) of - true -> - ejabberd_hooks:add(caps_update, ServerHost, ?MODULE, caps_update, 80), - ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75), - ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75), - ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75), - gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, IQDisc); - false -> - ok + true -> + ejabberd_hooks:add(caps_update, ServerHost, ?MODULE, + caps_update, 80), + ejabberd_hooks:add(disco_sm_identity, ServerHost, + ?MODULE, disco_sm_identity, 75), + ejabberd_hooks:add(disco_sm_features, ServerHost, + ?MODULE, disco_sm_features, 75), + ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, + disco_sm_items, 75), + gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, + ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, + ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, + IQDisc); + false -> ok end, ejabberd_router:register_route(Host), update_node_database(Host, ServerHost), update_state_database(Host, ServerHost), - put(server_host, ServerHost), % not clean, but needed to plug hooks at any location + put(server_host, ServerHost), init_nodes(Host, ServerHost, NodeTree, Plugins), - State = #state{host = Host, - server_host = ServerHost, - access = Access, - pep_mapping = PepMapping, - ignore_pep_from_offline = PepOffline, - last_item_cache = LastItemCache, - max_items_node = MaxItemsNode, - nodetree = NodeTree, - plugins = Plugins}, + State = #state{host = Host, server_host = ServerHost, + access = Access, pep_mapping = PepMapping, + ignore_pep_from_offline = PepOffline, + last_item_cache = LastItemCache, + max_items_node = MaxItemsNode, nodetree = NodeTree, + plugins = Plugins}, init_send_loop(ServerHost, State), {ok, State}. @@ -253,197 +382,360 @@ init_send_loop(ServerHost, State) -> %% and sorted to ensure that each module is initialized only once.</p> %% <p>See {@link node_hometree:init/1} for an example implementation.</p> init_plugins(Host, ServerHost, Opts) -> - TreePlugin = list_to_atom(?TREE_PREFIX ++ - gen_mod:get_opt(nodetree, Opts, ?STDTREE)), - ?DEBUG("** tree plugin is ~p",[TreePlugin]), + TreePlugin = + jlib:binary_to_atom(<<(?TREE_PREFIX)/binary, + (gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end, + ?STDTREE))/binary>>), + ?DEBUG("** tree plugin is ~p", [TreePlugin]), TreePlugin:init(Host, ServerHost, Opts), - Plugins = gen_mod:get_opt(plugins, Opts, [?STDNODE]), - PepMapping = gen_mod:get_opt(pep_mapping, Opts, []), - ?DEBUG("** PEP Mapping : ~p~n",[PepMapping]), - PluginsOK = lists:foldl(fun(Name, Acc) -> - Plugin = list_to_atom(?PLUGIN_PREFIX ++ Name), - case catch apply(Plugin, init, [Host, ServerHost, Opts]) of - {'EXIT', _Error} -> - Acc; - _ -> - ?DEBUG("** init ~s plugin",[Name]), - [Name | Acc] - end - end, [], Plugins), + Plugins = gen_mod:get_opt(plugins, Opts, + fun(A) when is_list(A) -> A end, [?STDNODE]), + PepMapping = gen_mod:get_opt(pep_mapping, Opts, + fun(A) when is_list(A) -> A end, []), + ?DEBUG("** PEP Mapping : ~p~n", [PepMapping]), + PluginsOK = lists:foldl(fun (Name, Acc) -> + Plugin = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + Name/binary>>), + case catch apply(Plugin, init, + [Host, ServerHost, Opts]) + of + {'EXIT', _Error} -> Acc; + _ -> + ?DEBUG("** init ~s plugin", [Name]), + [Name | Acc] + end + end, + [], Plugins), {lists:reverse(PluginsOK), TreePlugin, PepMapping}. -terminate_plugins(Host, ServerHost, Plugins, TreePlugin) -> - lists:foreach(fun(Name) -> - ?DEBUG("** terminate ~s plugin",[Name]), - Plugin = list_to_atom(?PLUGIN_PREFIX++Name), +terminate_plugins(Host, ServerHost, Plugins, + TreePlugin) -> + lists:foreach(fun (Name) -> + ?DEBUG("** terminate ~s plugin", [Name]), + Plugin = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + Name/binary>>), Plugin:terminate(Host, ServerHost) - end, Plugins), + end, + Plugins), TreePlugin:terminate(Host, ServerHost), ok. init_nodes(Host, ServerHost, _NodeTree, Plugins) -> - %% TODO, this call should be done plugin side - case lists:member("hometree", Plugins) of - true -> - create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree"), - create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree"); - false -> - ok + case lists:member(<<"hometree">>, Plugins) of + true -> + create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>), + create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), + <<"hometree">>); + false -> ok end. update_node_database(Host, ServerHost) -> mnesia:del_table_index(pubsub_node, type), mnesia:del_table_index(pubsub_node, parentid), case catch mnesia:table_info(pubsub_node, attributes) of - [host_node, host_parent, info] -> - ?INFO_MSG("upgrade node pubsub tables",[]), - F = fun() -> - {Result, LastIdx} = lists:foldl( - fun({pubsub_node, NodeId, ParentId, {nodeinfo, Items, Options, Entities}}, {RecList, NodeIdx}) -> - ItemsList = - lists:foldl( - fun({item, IID, Publisher, Payload}, Acc) -> - C = {unknown, Publisher}, - M = {now(), Publisher}, - mnesia:write( - #pubsub_item{itemid = {IID, NodeIdx}, - creation = C, - modification = M, - payload = Payload}), - [{Publisher, IID} | Acc] - end, [], Items), - Owners = - dict:fold( - fun(JID, {entity, Aff, Sub}, Acc) -> - UsrItems = - lists:foldl( - fun({P, I}, IAcc) -> - case P of - JID -> [I | IAcc]; - _ -> IAcc - end - end, [], ItemsList), - mnesia:write({pubsub_state, - {JID, NodeIdx}, - UsrItems, - Aff, - Sub}), - case Aff of - owner -> [JID | Acc]; - _ -> Acc - end - end, [], Entities), - mnesia:delete({pubsub_node, NodeId}), - {[#pubsub_node{nodeid = NodeId, - id = NodeIdx, - parents = [element(2, ParentId)], - owners = Owners, - options = Options} | - RecList], NodeIdx + 1} - end, {[], 1}, - mnesia:match_object( - {pubsub_node, {Host, '_'}, '_', '_'})), - mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []}), - Result - end, - {atomic, NewRecords} = mnesia:transaction(F), - {atomic, ok} = mnesia:delete_table(pubsub_node), - {atomic, ok} = mnesia:create_table(pubsub_node, - [{disc_copies, [node()]}, - {attributes, record_info(fields, pubsub_node)}]), - FNew = fun() -> lists:foreach(fun(Record) -> - mnesia:write(Record) - end, NewRecords) - end, - case mnesia:transaction(FNew) of - {atomic, Result} -> - ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]); - {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason]) - end; - [nodeid, parentid, type, owners, options] -> - F = fun({pubsub_node, NodeId, {_, Parent}, Type, Owners, Options}) -> - #pubsub_node{ - nodeid = NodeId, - id = 0, - parents = [Parent], - type = Type, - owners = Owners, - options = Options} - end, - mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]), - FNew = fun() -> - LastIdx = lists:foldl(fun(#pubsub_node{nodeid = NodeId} = PubsubNode, NodeIdx) -> - mnesia:write(PubsubNode#pubsub_node{id = NodeIdx}), - lists:foreach(fun(#pubsub_state{stateid = StateId} = State) -> - {JID, _} = StateId, - mnesia:delete({pubsub_state, StateId}), - mnesia:write(State#pubsub_state{stateid = {JID, NodeIdx}}) - end, mnesia:match_object(#pubsub_state{stateid = {'_', NodeId}, _ = '_'})), - lists:foreach(fun(#pubsub_item{itemid = ItemId} = Item) -> - {IID, _} = ItemId, - {M1, M2} = Item#pubsub_item.modification, - {C1, C2} = Item#pubsub_item.creation, - mnesia:delete({pubsub_item, ItemId}), - mnesia:write(Item#pubsub_item{itemid = {IID, NodeIdx}, - modification = {M2, M1}, - creation = {C2, C1}}) - end, mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'})), - NodeIdx + 1 - end, 1, mnesia:match_object( - {pubsub_node, {Host, '_'}, '_', '_', '_', '_', '_'}) - ++ mnesia:match_object( - {pubsub_node, {{'_', ServerHost, '_'}, '_'}, '_', '_', '_', '_', '_'})), - mnesia:write(#pubsub_index{index = node, last = LastIdx, free = []}) - end, - case mnesia:transaction(FNew) of - {atomic, Result} -> - rename_default_nodeplugin(), - ?INFO_MSG("Pubsub node tables updated correctly: ~p", [Result]); - {aborted, Reason} -> - ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", [Reason]) - end; - [nodeid, id, parent, type, owners, options] -> - F = fun({pubsub_node, NodeId, Id, Parent, Type, Owners, Options}) -> - #pubsub_node{ - nodeid = NodeId, - id = Id, - parents = [Parent], - type = Type, - owners = Owners, - options = Options} - end, - mnesia:transform_table(pubsub_node, F, [nodeid, id, parents, type, owners, options]), - rename_default_nodeplugin(); - _ -> - ok + [host_node, host_parent, info] -> + ?INFO_MSG("upgrade node pubsub tables", []), + F = fun () -> + {Result, LastIdx} = lists:foldl(fun ({pubsub_node, + NodeId, ParentId, + {nodeinfo, Items, + Options, + Entities}}, + {RecList, + NodeIdx}) -> + ItemsList = + lists:foldl(fun + ({item, + IID, + Publisher, + Payload}, + Acc) -> + C = + {unknown, + Publisher}, + M = + {now(), + Publisher}, + mnesia:write(#pubsub_item{itemid + = + {IID, + NodeIdx}, + creation + = + C, + modification + = + M, + payload + = + Payload}), + [{Publisher, + IID} + | Acc] + end, + [], + Items), + Owners = + dict:fold(fun + (JID, + {entity, + Aff, + Sub}, + Acc) -> + UsrItems = + lists:foldl(fun + ({P, + I}, + IAcc) -> + case + P + of + JID -> + [I + | IAcc]; + _ -> + IAcc + end + end, + [], + ItemsList), + mnesia:write({pubsub_state, + {JID, + NodeIdx}, + UsrItems, + Aff, + Sub}), + case + Aff + of + owner -> + [JID + | Acc]; + _ -> + Acc + end + end, + [], + Entities), + mnesia:delete({pubsub_node, + NodeId}), + {[#pubsub_node{nodeid + = + NodeId, + id + = + NodeIdx, + parents + = + [element(2, + ParentId)], + owners + = + Owners, + options + = + Options} + | RecList], + NodeIdx + 1} + end, + {[], 1}, + mnesia:match_object({pubsub_node, + {Host, + '_'}, + '_', + '_'})), + mnesia:write(#pubsub_index{index = node, last = LastIdx, + free = []}), + Result + end, + {atomic, NewRecords} = mnesia:transaction(F), + {atomic, ok} = mnesia:delete_table(pubsub_node), + {atomic, ok} = mnesia:create_table(pubsub_node, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, + pubsub_node)}]), + FNew = fun () -> + lists:foreach(fun (Record) -> mnesia:write(Record) end, + NewRecords) + end, + case mnesia:transaction(FNew) of + {atomic, Result} -> + ?INFO_MSG("Pubsub node tables updated correctly: ~p", + [Result]); + {aborted, Reason} -> + ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", + [Reason]) + end; + [nodeid, parentid, type, owners, options] -> + F = fun ({pubsub_node, NodeId, {_, Parent}, Type, + Owners, Options}) -> + #pubsub_node{nodeid = NodeId, id = 0, + parents = [Parent], type = Type, + owners = Owners, options = Options} + end, + mnesia:transform_table(pubsub_node, F, + [nodeid, id, parents, type, owners, options]), + FNew = fun () -> + LastIdx = lists:foldl(fun (#pubsub_node{nodeid = + NodeId} = + PubsubNode, + NodeIdx) -> + mnesia:write(PubsubNode#pubsub_node{id + = + NodeIdx}), + lists:foreach(fun + (#pubsub_state{stateid + = + StateId} = + State) -> + {JID, + _} = + StateId, + mnesia:delete({pubsub_state, + StateId}), + mnesia:write(State#pubsub_state{stateid + = + {JID, + NodeIdx}}) + end, + mnesia:match_object(#pubsub_state{stateid + = + {'_', + NodeId}, + _ + = + '_'})), + lists:foreach(fun + (#pubsub_item{itemid + = + ItemId} = + Item) -> + {IID, + _} = + ItemId, + {M1, + M2} = + Item#pubsub_item.modification, + {C1, + C2} = + Item#pubsub_item.creation, + mnesia:delete({pubsub_item, + ItemId}), + mnesia:write(Item#pubsub_item{itemid + = + {IID, + NodeIdx}, + modification + = + {M2, + M1}, + creation + = + {C2, + C1}}) + end, + mnesia:match_object(#pubsub_item{itemid + = + {'_', + NodeId}, + _ + = + '_'})), + NodeIdx + 1 + end, + 1, + mnesia:match_object({pubsub_node, + {Host, '_'}, + '_', '_', + '_', '_', + '_'}) + ++ + mnesia:match_object({pubsub_node, + {{'_', + ServerHost, + '_'}, + '_'}, + '_', '_', + '_', '_', + '_'})), + mnesia:write(#pubsub_index{index = node, + last = LastIdx, free = []}) + end, + case mnesia:transaction(FNew) of + {atomic, Result} -> + rename_default_nodeplugin(), + ?INFO_MSG("Pubsub node tables updated correctly: ~p", + [Result]); + {aborted, Reason} -> + ?ERROR_MSG("Problem updating Pubsub node tables:~n~p", + [Reason]) + end; + [nodeid, id, parent, type, owners, options] -> + F = fun ({pubsub_node, NodeId, Id, Parent, Type, Owners, + Options}) -> + #pubsub_node{nodeid = NodeId, id = Id, + parents = [Parent], type = Type, + owners = Owners, options = Options} + end, + mnesia:transform_table(pubsub_node, F, + [nodeid, id, parents, type, owners, options]), + rename_default_nodeplugin(); + _ -> ok end, - mnesia:transaction(fun() -> - case catch mnesia:first(pubsub_node) of - {_, L} when is_list(L) -> - lists:foreach( - fun({H, N}) when is_list(N) -> - [Node] = mnesia:read({pubsub_node, {H, N}}), - Type = Node#pubsub_node.type, - BN = element(2, node_call(Type, path_to_node, [N])), - BP = case [element(2, node_call(Type, path_to_node, [P])) || P <- Node#pubsub_node.parents] of - [<<>>] -> []; - Parents -> Parents - end, - mnesia:write(Node#pubsub_node{nodeid={H, BN}, parents=BP}), - mnesia:delete({pubsub_node, {H, N}}); - (_) -> - ok - end, mnesia:all_keys(pubsub_node)); - _ -> - ok - end - end). + mnesia:transaction(fun () -> + case catch mnesia:first(pubsub_node) of + {_, L} when is_binary(L) -> + lists:foreach(fun ({H, N}) + when is_binary(N) -> + [Node] = + mnesia:read({pubsub_node, + {H, + N}}), + Type = + Node#pubsub_node.type, + BN = element(2, + node_call(Type, + path_to_node, + [N])), + BP = case [element(2, + node_call(Type, + path_to_node, + [P])) + || P + <- Node#pubsub_node.parents] + of + [<<>>] -> []; + Parents -> + Parents + end, + mnesia:write(Node#pubsub_node{nodeid + = + {H, + BN}, + parents + = + BP}), + mnesia:delete({pubsub_node, + {H, + N}}); + (_) -> ok + end, + mnesia:all_keys(pubsub_node)); + _ -> ok + end + end). rename_default_nodeplugin() -> - lists:foreach(fun(Node) -> - mnesia:dirty_write(Node#pubsub_node{type = "hometree"}) - end, mnesia:dirty_match_object(#pubsub_node{type = "default", _ = '_'})). + lists:foreach(fun (Node) -> + mnesia:dirty_write(Node#pubsub_node{type = + <<"hometree">>}) + end, + mnesia:dirty_match_object(#pubsub_node{type = + <<"default">>, + _ = '_'})). update_state_database(_Host, _ServerHost) -> case catch mnesia:table_info(pubsub_state, attributes) of @@ -486,243 +778,385 @@ update_state_database(_Host, _ServerHost) -> send_loop(State) -> receive - {presence, JID, Pid} -> - Host = State#state.host, - ServerHost = State#state.server_host, - LJID = jlib:jid_tolower(JID), - BJID = jlib:jid_remove_resource(LJID), - %% for each node From is subscribed to - %% and if the node is so configured, send the last published item to From - lists:foreach(fun(PType) -> - {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, JID]), - lists:foreach( - fun({Node, subscribed, _, SubJID}) -> - if (SubJID == LJID) or (SubJID == BJID) -> - #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId, options = Options} = Node, - case get_option(Options, send_last_published_item) of - on_sub_and_presence -> - send_items(H, N, NodeId, Type, LJID, last); - _ -> - ok - end; - true -> - % resource not concerned about that subscription - ok - end; - (_) -> - ok - end, Subscriptions) - end, State#state.plugins), - %% and force send the last PEP events published by its offline and local contacts - %% only if pubsub is explicitely configured for that. - %% this is a hack in a sense that PEP should only be based on presence - %% and is not able to "store" events of remote users (via s2s) - %% this makes that hack only work for local domain by now - if not State#state.ignore_pep_from_offline -> - {User, Server, Resource} = jlib:jid_tolower(JID), - case catch ejabberd_c2s:get_subscribed(Pid) of - Contacts when is_list(Contacts) -> - lists:foreach( - fun({U, S, R}) -> - case S of - ServerHost -> %% local contacts - case user_resources(U, S) of - [] -> %% offline - PeerJID = jlib:make_jid(U, S, R), - self() ! {presence, User, Server, [Resource], PeerJID}; - _ -> %% online - % this is already handled by presence probe - ok - end; - _ -> %% remote contacts - % we can not do anything in any cases - ok - end - end, Contacts); - _ -> - ok - end; - true -> - ok - end, - send_loop(State); - {presence, User, Server, Resources, JID} -> - %% get resources caps and check if processing is needed - spawn(fun() -> - Host = State#state.host, - Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)), - lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) -> - case get_option(Options, send_last_published_item) of - on_sub_and_presence -> - lists:foreach( - fun(Resource) -> - LJID = {User, Server, Resource}, - Subscribed = case get_option(Options, access_model) of - open -> true; - presence -> true; - whitelist -> false; % subscribers are added manually - authorize -> false; % likewise - roster -> - Grps = get_option(Options, roster_groups_allowed, []), - {OU, OS, _} = Owner, - element(2, get_roster_info(OU, OS, LJID, Grps)) - end, - if Subscribed -> - send_items(Owner, Node, NodeId, Type, LJID, last); - true -> - ok - end - end, Resources); - _ -> - ok - end - end, tree_action(Host, get_nodes, [Owner, JID])) + {presence, JID, Pid} -> + Host = State#state.host, + ServerHost = State#state.server_host, + LJID = jlib:jid_tolower(JID), + BJID = jlib:jid_remove_resource(LJID), + lists:foreach(fun (PType) -> + {result, Subscriptions} = node_action(Host, + PType, + get_entity_subscriptions, + [Host, + JID]), + lists:foreach(fun ({Node, subscribed, _, + SubJID}) -> + if (SubJID == LJID) or + (SubJID == BJID) -> + #pubsub_node{nodeid + = + {H, + N}, + type = + Type, + id = + NodeId, + options + = + Options} = + Node, + case + get_option(Options, + send_last_published_item) + of + on_sub_and_presence -> + send_items(H, + N, + NodeId, + Type, + LJID, + last); + _ -> ok + end; + true -> + % resource not concerned about that subscription + ok + end; + (_) -> ok + end, + Subscriptions) + end, + State#state.plugins), + if not State#state.ignore_pep_from_offline -> + {User, Server, Resource} = jlib:jid_tolower(JID), + case catch ejabberd_c2s:get_subscribed(Pid) of + Contacts when is_list(Contacts) -> + lists:foreach(fun ({U, S, R}) -> + case S of + ServerHost -> %% local contacts + case user_resources(U, S) of + [] -> %% offline + PeerJID = + jlib:make_jid(U, S, + R), + self() ! + {presence, User, + Server, [Resource], + PeerJID}; + _ -> %% online + % this is already handled by presence probe + ok + end; + _ -> %% remote contacts + % we can not do anything in any cases + ok + end + end, + Contacts); + _ -> ok + end; + true -> ok + end, + send_loop(State); + {presence, User, Server, Resources, JID} -> + spawn(fun () -> + Host = State#state.host, + Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)), + lists:foreach(fun (#pubsub_node{nodeid = {_, Node}, + type = Type, + id = NodeId, + options = Options}) -> + case get_option(Options, + send_last_published_item) + of + on_sub_and_presence -> + lists:foreach(fun + (Resource) -> + LJID = + {User, + Server, + Resource}, + Subscribed = + case + get_option(Options, + access_model) + of + open -> + true; + presence -> + true; + whitelist -> + false; % subscribers are added manually + authorize -> + false; % likewise + roster -> + Grps = + get_option(Options, + roster_groups_allowed, + []), + {OU, + OS, + _} = + Owner, + element(2, + get_roster_info(OU, + OS, + LJID, + Grps)) + end, + if + Subscribed -> + send_items(Owner, + Node, + NodeId, + Type, + LJID, + last); + true -> + ok + end + end, + Resources); + _ -> ok + end + end, + tree_action(Host, get_nodes, + [Owner, JID])) end), - send_loop(State); - stop -> - ok + send_loop(State); + stop -> ok end. %% ------- %% disco hooks handling functions %% -disco_local_identity(Acc, _From, To, [], _Lang) -> +-spec(disco_local_identity/5 :: +( + Acc :: [xmlel()], + _From :: jid(), + To :: jid(), + NodeId :: <<>> | mod_pubsub:nodeId(), + Lang :: binary()) + -> [xmlel()] +). +disco_local_identity(Acc, _From, To, <<>>, _Lang) -> case lists:member(?PEPNODE, plugins(To#jid.lserver)) of - true -> - [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []} | Acc]; - false -> Acc + true -> + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"pubsub">>}, + {<<"type">>, <<"pep">>}], + children = []} + | Acc]; + false -> Acc end; disco_local_identity(Acc, _From, _To, _Node, _Lang) -> Acc. -disco_local_features(Acc, _From, To, [], _Lang) -> +-spec(disco_local_features/5 :: +( + Acc :: [xmlel()], + _From :: jid(), + To :: jid(), + NodeId :: <<>> | mod_pubsub:nodeId(), + Lang :: binary()) + -> [binary(),...] +). +disco_local_features(Acc, _From, To, <<>>, _Lang) -> Host = To#jid.lserver, Feats = case Acc of - {result, I} -> I; - _ -> [] - end, - {result, Feats ++ lists:map(fun(Feature) -> - ?NS_PUBSUB++"#"++Feature - end, features(Host, <<>>))}; + {result, I} -> I; + _ -> [] + end, + {result, + Feats ++ + lists:map(fun (Feature) -> + <<(?NS_PUBSUB)/binary, "#", Feature/binary>> + end, + features(Host, <<>>))}; disco_local_features(Acc, _From, _To, _Node, _Lang) -> Acc. -disco_local_items(Acc, _From, _To, [], _Lang) -> - Acc; -disco_local_items(Acc, _From, _To, _Node, _Lang) -> - Acc. - -disco_sm_identity(Acc, From, To, Node, Lang) when is_list(Node) -> - disco_sm_identity(Acc, From, To, list_to_binary(Node), Lang); +disco_local_items(Acc, _From, _To, <<>>, _Lang) -> Acc; +disco_local_items(Acc, _From, _To, _Node, _Lang) -> Acc. + +%disco_sm_identity(Acc, From, To, Node, Lang) +% when is_binary(Node) -> +% disco_sm_identity(Acc, From, To, iolist_to_binary(Node), +% Lang); +-spec(disco_sm_identity/5 :: +( + Acc :: empty | [xmlel()], + From :: jid(), + To :: jid(), + Node :: mod_pubsub:nodeId(), + Lang :: binary()) + -> [xmlel()] +). disco_sm_identity(empty, From, To, Node, Lang) -> disco_sm_identity([], From, To, Node, Lang); disco_sm_identity(Acc, From, To, Node, _Lang) -> - disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From) ++ Acc. + disco_identity(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From) + ++ Acc. disco_identity(_Host, <<>>, _From) -> - [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}]; + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"pubsub">>}, + {<<"type">>, <<"pep">>}], + children = []}]; disco_identity(Host, Node, From) -> - Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}, - {xmlelement, "identity", - [{"category", "pubsub"}, - {"type", "leaf"} - | case get_option(Options, title) of - false -> []; - [Title] -> [{"name", Title}] - end], - []}]}; - _ -> {result, []} - end - end, + Action = fun (#pubsub_node{id = Idx, type = Type, + options = Options, owners = Owners}) -> + case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of + {result, _} -> + {result, + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"pubsub">>}, + {<<"type">>, <<"pep">>}], + children = []}, + #xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"pubsub">>}, + {<<"type">>, <<"leaf">>} + | case get_option(Options, title) of + false -> []; + [Title] -> [{<<"name">>, Title}] + end], + children = []}]}; + _ -> {result, []} + end + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] + {result, {_, Result}} -> Result; + _ -> [] end. -disco_sm_features(Acc, From, To, Node, Lang) when is_list(Node) -> - disco_sm_features(Acc, From, To, list_to_binary(Node), Lang); +-spec(disco_sm_features/5 :: +( + Acc :: empty | {result, Features::[Feature::binary()]}, + From :: jid(), + To :: jid(), + Node :: mod_pubsub:nodeId(), + Lang :: binary()) + -> {result, Features::[Feature::binary()]} +). +%disco_sm_features(Acc, From, To, Node, Lang) +% when is_binary(Node) -> +% disco_sm_features(Acc, From, To, iolist_to_binary(Node), +% Lang); disco_sm_features(empty, From, To, Node, Lang) -> disco_sm_features({result, []}, From, To, Node, Lang); disco_sm_features({result, OtherFeatures} = _Acc, From, To, Node, _Lang) -> {result, OtherFeatures ++ disco_features(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From)}; -disco_sm_features(Acc, _From, _To, _Node, _Lang) -> - Acc. +disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. disco_features(_Host, <<>>, _From) -> - [?NS_PUBSUB - | [?NS_PUBSUB++"#"++Feature || Feature <- features("pep")]]; + [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", Feature/binary>> + || Feature <- features(<<"pep">>)]]; disco_features(Host, Node, From) -> - Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, [?NS_PUBSUB - | [?NS_PUBSUB ++ "#" ++ Feature || Feature <- features("pep")]]}; - _ -> {result, []} - end - end, + Action = fun (#pubsub_node{id = Idx, type = Type, + options = Options, owners = Owners}) -> + case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of + {result, _} -> + {result, + [?NS_PUBSUB | [<<(?NS_PUBSUB)/binary, "#", + Feature/binary>> + || Feature <- features(<<"pep">>)]]}; + _ -> {result, []} + end + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] + {result, {_, Result}} -> Result; + _ -> [] end. -disco_sm_items(Acc, From, To, Node, Lang) when is_list(Node) -> - disco_sm_items(Acc, From, To, list_to_binary(Node), Lang); +-spec(disco_sm_items/5 :: +( + Acc :: empty | {result, [xmlel()]}, + From :: jid(), + To :: jid(), + Node :: mod_pubsub:nodeId(), + Lang :: binary()) + -> {result, [xmlel()]} +). +%disco_sm_items(Acc, From, To, Node, Lang) +% when is_binary(Node) -> +% disco_sm_items(Acc, From, To, iolist_to_binary(Node), +% Lang); disco_sm_items(empty, From, To, Node, Lang) -> disco_sm_items({result, []}, From, To, Node, Lang); disco_sm_items({result, OtherItems}, From, To, Node, _Lang) -> {result, lists:usort(OtherItems ++ - disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))}; -disco_sm_items(Acc, _From, _To, _Node, _Lang) -> - Acc. - + disco_items(jlib:jid_tolower(jlib:jid_remove_resource(To)), Node, From))}; +disco_sm_items(Acc, _From, _To, _Node, _Lang) -> Acc. + +-spec(disco_items/3 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid()) + -> [xmlel()] +). disco_items(Host, <<>>, From) -> - Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx, owners = Owners}, Acc) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - [{xmlelement, "item", - [{"node", binary_to_list(NodeID)}, - {"jid", case Host of - {_,_,_} -> jlib:jid_to_string(Host); - _Host -> Host - end} - | case get_option(Options, title) of - false -> []; - [Title] -> [{"name", Title}] - end], - []} + Action = fun (#pubsub_node{nodeid = {_, NodeID}, + options = Options, type = Type, id = Idx, + owners = Owners}, + Acc) -> + case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of + {result, _} -> + [#xmlel{name = <<"item">>, + attrs = + [{<<"node">>, (NodeID)}, + {<<"jid">>, + case Host of + {_, _, _} -> + jlib:jid_to_string(Host); + _Host -> Host + end} + | case get_option(Options, title) of + false -> []; + [Title] -> [{<<"name">>, Title}] + end], + children = []} | Acc]; - _ -> Acc - end - end, + _ -> Acc + end + end, case transaction(Host, Action, sync_dirty) of - {result, Items} -> Items; - _ -> [] + {result, Items} -> Items; + _ -> [] end; - disco_items(Host, Node, From) -> - Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) -> - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, Items} -> - {result, [{xmlelement, "item", - [{"jid", case Host of - {_,_,_} -> jlib:jid_to_string(Host); - _Host -> Host - end}, - {"name", ItemID}], []} - || #pubsub_item{itemid = {ItemID,_}} <- Items]}; - _ -> {result, []} - end - end, + Action = fun (#pubsub_node{id = Idx, type = Type, + options = Options, owners = Owners}) -> + case get_allowed_items_call(Host, Idx, From, Type, + Options, Owners) + of + {result, Items} -> + {result, + [#xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + case Host of + {_, _, _} -> + jlib:jid_to_string(Host); + _Host -> Host + end}, + {<<"name">>, ItemID}], + children = []} + || #pubsub_item{itemid = {ItemID, _}} <- Items]}; + _ -> {result, []} + end + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> Result; - _ -> [] + {result, {_, Result}} -> Result; + _ -> [] end. %% ------- @@ -733,36 +1167,40 @@ caps_update(#jid{luser = U, lserver = S, lresource = R} = From, To, _Features) - Pid = ejabberd_sm:get_session_pid(U, S, R), presence_probe(From, To, Pid). -presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, JID, Pid) -> - %%?DEBUG("presence probe self ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]), +presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, + JID, Pid) -> presence(Server, {presence, JID, Pid}), presence(Server, {presence, User, Server, [Resource], JID}); -presence_probe(#jid{luser = User, lserver = Server}, #jid{luser = User, lserver = Server}, _Pid) -> +presence_probe(#jid{luser = User, lserver = Server}, + #jid{luser = User, lserver = Server}, _Pid) -> %% ignore presence_probe from other ressources for the current user %% this way, we do not send duplicated last items if user already connected with other clients ok; -presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, #jid{lserver = Host} = JID, _Pid) -> - %%?DEBUG("presence probe peer ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]), +presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, + #jid{lserver = Host} = JID, _Pid) -> presence(Host, {presence, User, Server, [Resource], JID}). presence(ServerHost, Presence) -> - SendLoop = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of - undefined -> - % in case send_loop process died, we rebuild a minimal State record and respawn it - Host = host(ServerHost), - Plugins = plugins(Host), - PepOffline = case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), ignore_pep_from_offline) of - [{ignore_pep_from_offline, PO}] -> PO; - _ -> true - end, - State = #state{host = Host, - server_host = ServerHost, - ignore_pep_from_offline = PepOffline, - plugins = Plugins}, - init_send_loop(ServerHost, State); - Pid -> - Pid - end, + SendLoop = case + whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) + of + undefined -> + Host = host(ServerHost), + Plugins = plugins(Host), + PepOffline = case catch + ets:lookup(gen_mod:get_module_proc(ServerHost, + config), + ignore_pep_from_offline) + of + [{ignore_pep_from_offline, PO}] -> PO; + _ -> true + end, + State = #state{host = Host, server_host = ServerHost, + ignore_pep_from_offline = PepOffline, + plugins = Plugins}, + init_send_loop(ServerHost, State); + Pid -> Pid + end, SendLoop ! Presence. %% ------- @@ -770,46 +1208,73 @@ presence(ServerHost, Presence) -> %% out_subscription(User, Server, JID, subscribed) -> - Owner = jlib:make_jid(User, Server, ""), + Owner = jlib:make_jid(User, Server, <<"">>), {PUser, PServer, PResource} = jlib:jid_tolower(JID), PResources = case PResource of - [] -> user_resources(PUser, PServer); - _ -> [PResource] - end, - presence(Server, {presence, PUser, PServer, PResources, Owner}), + <<>> -> user_resources(PUser, PServer); + _ -> [PResource] + end, + presence(Server, + {presence, PUser, PServer, PResources, Owner}), true; -out_subscription(_,_,_,_) -> - true. -in_subscription(_, User, Server, Owner, unsubscribed, _) -> - unsubscribe_user(jlib:make_jid(User, Server, ""), Owner), +out_subscription(_, _, _, _) -> true. + +in_subscription(_, User, Server, Owner, unsubscribed, + _) -> + unsubscribe_user(jlib:make_jid(User, Server, <<"">>), + Owner), true; -in_subscription(_, _, _, _, _, _) -> - true. +in_subscription(_, _, _, _, _, _) -> true. unsubscribe_user(Entity, Owner) -> BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), Host = host(element(2, BJID)), - spawn(fun() -> - lists:foreach(fun(PType) -> - {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]), - lists:foreach(fun - ({#pubsub_node{options = Options, owners = Owners, id = NodeId}, subscribed, _, JID}) -> - case get_option(Options, access_model) of - presence -> - case lists:member(BJID, Owners) of - true -> - node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]); - false -> - {result, ok} - end; - _ -> - {result, ok} - end; - (_) -> - ok - end, Subscriptions) - end, plugins(Host)) - end). + spawn(fun () -> + lists:foreach(fun (PType) -> + {result, Subscriptions} = + node_action(Host, PType, + get_entity_subscriptions, + [Host, Entity]), + lists:foreach(fun ({#pubsub_node{options + = + Options, + owners + = + Owners, + id = + NodeId}, + subscribed, _, + JID}) -> + case + get_option(Options, + access_model) + of + presence -> + case + lists:member(BJID, + Owners) + of + true -> + node_action(Host, + PType, + unsubscribe_node, + [NodeId, + Entity, + JID, + all]); + false -> + {result, + ok} + end; + _ -> + {result, ok} + end; + (_) -> ok + end, + Subscriptions) + end, + plugins(Host)) + end). %% ------- %% user remove hook handling function @@ -818,25 +1283,8 @@ unsubscribe_user(Entity, Owner) -> remove_user(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), - Entity = jlib:make_jid(LUser, LServer, ""), + Entity = jlib:make_jid(LUser, LServer, <<"">>), Host = host(LServer), - HomeTreeBase = string_to_node("/home/"++LServer++"/"++LUser), - spawn(fun() -> - lists:foreach(fun(PType) -> - {result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]), - lists:foreach(fun - ({#pubsub_node{id = NodeId}, _, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]) - end, Subscriptions), - {result, Affiliations} = node_action(Host, PType, get_entity_affiliations, [Host, Entity]), - lists:foreach(fun - ({#pubsub_node{nodeid = {H, N}, parents = []}, owner}) -> delete_node(H, N, Entity); - ({#pubsub_node{nodeid = {H, N}, type = "hometree"}, owner}) when N == HomeTreeBase -> delete_node(H, N, Entity); - ({#pubsub_node{id = NodeId}, publisher}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none]); - (_) -> ok - end, Affiliations) - end, plugins(Host)) - end). - %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | @@ -848,6 +1296,66 @@ remove_user(User, Server) -> %% Description: Handling call messages %%-------------------------------------------------------------------- %% @private + HomeTreeBase = <<"/home/", LServer/binary, "/", LUser/binary>>, + spawn(fun () -> + lists:foreach(fun (PType) -> + {result, Subscriptions} = + node_action(Host, PType, + get_entity_subscriptions, + [Host, Entity]), + lists:foreach(fun ({#pubsub_node{id = + NodeId}, + _, _, JID}) -> + node_action(Host, + PType, + unsubscribe_node, + [NodeId, + Entity, + JID, + all]) + end, + Subscriptions), + {result, Affiliations} = + node_action(Host, PType, + get_entity_affiliations, + [Host, Entity]), + lists:foreach(fun ({#pubsub_node{nodeid + = + {H, + N}, + parents + = + []}, + owner}) -> + delete_node(H, N, + Entity); + ({#pubsub_node{nodeid + = + {H, + N}, + type = + <<"hometree">>}, + owner}) + when N == + HomeTreeBase -> + delete_node(H, N, + Entity); + ({#pubsub_node{id = + NodeId}, + publisher}) -> + node_action(Host, + PType, + set_affiliation, + [NodeId, + Entity, + none]); + (_) -> ok + end, + Affiliations) + end, + plugins(Host)) + end). + handle_call(server_host, _From, State) -> {reply, State#state.server_host, State}; handle_call(plugins, _From, State) -> @@ -866,9 +1374,6 @@ handle_call(stop, _From, State) -> %% Description: Handling cast messages %%-------------------------------------------------------------------- %% @private -handle_cast(_Msg, State) -> - {noreply, State}. - %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | @@ -876,18 +1381,26 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- %% @private +handle_cast(_Msg, State) -> {noreply, State}. + +-spec(handle_info/2 :: +( + _ :: {route, From::jid(), To::jid(), Packet::xmlel()}, + State :: state()) + -> {noreply, state()} +). + handle_info({route, From, To, Packet}, - #state{server_host = ServerHost, - access = Access, - plugins = Plugins} = State) -> - case catch do_route(ServerHost, Access, Plugins, To#jid.lserver, From, To, Packet) of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); - _ -> ok + #state{server_host = ServerHost, access = Access, + plugins = Plugins} = + State) -> + case catch do_route(ServerHost, Access, Plugins, + To#jid.lserver, From, To, Packet) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + _ -> ok end, {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. - %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to @@ -896,522 +1409,748 @@ handle_info(_Info, State) -> %% The return value is ignored. %%-------------------------------------------------------------------- %% @private -terminate(_Reason, #state{host = Host, - server_host = ServerHost, - nodetree = TreePlugin, - plugins = Plugins}) -> +handle_info(_Info, State) -> {noreply, State}. + +terminate(_Reason, + #state{host = Host, server_host = ServerHost, + nodetree = TreePlugin, plugins = Plugins}) -> ejabberd_router:unregister_route(Host), case lists:member(?PEPNODE, Plugins) of - true -> - ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE, caps_update, 80), - ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75), - ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75), - ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75), - gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB), - gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER); - false -> - ok + true -> + ejabberd_hooks:delete(caps_update, ServerHost, ?MODULE, + caps_update, 80), + ejabberd_hooks:delete(disco_sm_identity, ServerHost, + ?MODULE, disco_sm_identity, 75), + ejabberd_hooks:delete(disco_sm_features, ServerHost, + ?MODULE, disco_sm_features, 75), + ejabberd_hooks:delete(disco_sm_items, ServerHost, + ?MODULE, disco_sm_items, 75), + gen_iq_handler:remove_iq_handler(ejabberd_sm, + ServerHost, ?NS_PUBSUB), + gen_iq_handler:remove_iq_handler(ejabberd_sm, + ServerHost, ?NS_PUBSUB_OWNER); + false -> ok end, - ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75), - ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75), - ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75), - ejabberd_hooks:delete(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75), - ejabberd_hooks:delete(presence_probe_hook, ServerHost, ?MODULE, presence_probe, 80), - ejabberd_hooks:delete(roster_in_subscription, ServerHost, ?MODULE, in_subscription, 50), - ejabberd_hooks:delete(roster_out_subscription, ServerHost, ?MODULE, out_subscription, 50), - ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, remove_user, 50), - ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, remove_user, 50), + ejabberd_hooks:delete(sm_remove_connection_hook, + ServerHost, ?MODULE, on_user_offline, 75), + ejabberd_hooks:delete(disco_local_identity, ServerHost, + ?MODULE, disco_local_identity, 75), + ejabberd_hooks:delete(disco_local_features, ServerHost, + ?MODULE, disco_local_features, 75), + ejabberd_hooks:delete(disco_local_items, ServerHost, + ?MODULE, disco_local_items, 75), + ejabberd_hooks:delete(presence_probe_hook, ServerHost, + ?MODULE, presence_probe, 80), + ejabberd_hooks:delete(roster_in_subscription, + ServerHost, ?MODULE, in_subscription, 50), + ejabberd_hooks:delete(roster_out_subscription, + ServerHost, ?MODULE, out_subscription, 50), + ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, + remove_user, 50), + ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, + ?MODULE, remove_user, 50), mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB), gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! stop, - terminate_plugins(Host, ServerHost, Plugins, TreePlugin). - %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- %% @private -code_change(_OldVsn, State, _Extra) -> - {ok, State}. + terminate_plugins(Host, ServerHost, Plugins, + TreePlugin). + +code_change(_OldVsn, State, _Extra) -> {ok, State}. + +-spec(do_route/7 :: +( + ServerHost :: binary(), + Access :: atom(), + Plugins :: [binary(),...], + Host :: mod_pubsub:hostPubsub(), + From :: jid(), + To :: jid(), + Packet :: xmlel()) + -> ok +). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- do_route(ServerHost, Access, Plugins, Host, From, To, Packet) -> - {xmlelement, Name, Attrs, _Els} = Packet, + #xmlel{name = Name, attrs = Attrs} = Packet, case To of - #jid{luser = "", lresource = ""} -> - case Name of - "iq" -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO, - sub_el = SubEl, lang = Lang} = IQ -> - {xmlelement, _, QAttrs, _} = SubEl, - Node = xml:get_attr_s("node", QAttrs), - Info = ejabberd_hooks:run_fold( - disco_info, ServerHost, [], - [ServerHost, ?MODULE, "", ""]), - Res = case iq_disco_info(Host, Node, From, Lang) of - {result, IQRes} -> - jlib:iq_to_xml( - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - QAttrs, IQRes++Info}]}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = ?NS_DISCO_ITEMS, - sub_el = SubEl} = IQ -> - {xmlelement, _, QAttrs, _} = SubEl, - Node = xml:get_attr_s("node", QAttrs), - Res = case iq_disco_items(Host, Node, From) of - {result, IQRes} -> - jlib:iq_to_xml( - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - QAttrs, IQRes}]}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB, - lang = Lang, sub_el = SubEl} = IQ -> - Res = - case iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) of - {result, IQRes} -> - jlib:iq_to_xml( - IQ#iq{type = result, - sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, - lang = Lang, sub_el = SubEl} = IQ -> - Res = - case iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) of - {result, IQRes} -> - jlib:iq_to_xml( - IQ#iq{type = result, - sub_el = IQRes}); - {error, Error} -> - jlib:make_error_reply(Packet, Error) - end, - ejabberd_router:route(To, From, Res); - #iq{type = get, xmlns = ?NS_VCARD = XMLNS, - lang = Lang, sub_el = _SubEl} = IQ -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "vCard", [{"xmlns", XMLNS}], - iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = ?NS_COMMANDS} = IQ -> - Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of - {error, Error} -> - jlib:make_error_reply(Packet, Error); - {result, IQRes} -> - jlib:iq_to_xml(IQ#iq{type = result, - sub_el = IQRes}) - end, - ejabberd_router:route(To, From, Res); - #iq{} -> - Err = jlib:make_error_reply( - Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> - ok - end; - "message" -> - case xml:get_attr_s("type", Attrs) of - "error" -> - ok; - _ -> - case find_authorization_response(Packet) of - none -> - ok; - invalid -> - ejabberd_router:route(To, From, - jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST)); - XFields -> - handle_authorization_response(Host, From, To, Packet, XFields) - end - end; - _ -> - ok - end; - _ -> - case xml:get_attr_s("type", Attrs) of - "error" -> - ok; - "result" -> - ok; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end + #jid{luser = <<"">>, lresource = <<"">>} -> + case Name of + <<"iq">> -> + case jlib:iq_query_info(Packet) of + #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 = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], + [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}]}); + {error, Error} -> + jlib:make_error_reply(Packet, Error) + end, + ejabberd_router:route(To, From, Res); + #iq{type = get, xmlns = ?NS_DISCO_ITEMS, + sub_el = SubEl} = + IQ -> + #xmlel{attrs = QAttrs} = SubEl, + Node = xml:get_attr_s(<<"node">>, QAttrs), + Res = case iq_disco_items(Host, Node, From) of + {result, IQRes} -> + jlib:iq_to_xml(IQ#iq{type = result, + sub_el = + [#xmlel{name = + <<"query">>, + attrs = + QAttrs, + children = + IQRes}]}) +% {error, Error} -> +% jlib:make_error_reply(Packet, Error) + end, + ejabberd_router:route(To, From, Res); + #iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, + sub_el = SubEl} = + IQ -> + Res = case iq_pubsub(Host, ServerHost, From, IQType, + SubEl, Lang, Access, Plugins) + of + {result, IQRes} -> + jlib:iq_to_xml(IQ#iq{type = result, + sub_el = IQRes}); + {error, Error} -> + jlib:make_error_reply(Packet, Error) + end, + ejabberd_router:route(To, From, Res); + #iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, + lang = Lang, sub_el = SubEl} = + IQ -> + Res = case iq_pubsub_owner(Host, ServerHost, From, + IQType, SubEl, Lang) + of + {result, IQRes} -> + jlib:iq_to_xml(IQ#iq{type = result, + sub_el = IQRes}); + {error, Error} -> + jlib:make_error_reply(Packet, Error) + end, + ejabberd_router:route(To, From, Res); + #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, + lang = Lang, sub_el = _SubEl} = + IQ -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"vCard">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = iq_get_vcard(Lang)}]}, + ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); + #iq{type = set, xmlns = ?NS_COMMANDS} = IQ -> + Res = case iq_command(Host, ServerHost, From, IQ, + Access, Plugins) + of + {error, Error} -> + jlib:make_error_reply(Packet, Error); + {result, IQRes} -> + jlib:iq_to_xml(IQ#iq{type = result, + sub_el = IQRes}) + end, + ejabberd_router:route(To, From, Res); + #iq{} -> + Err = jlib:make_error_reply(Packet, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + _ -> ok + end; + <<"message">> -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; + _ -> + case find_authorization_response(Packet) of + none -> ok; + invalid -> + ejabberd_router:route(To, From, + jlib:make_error_reply(Packet, + ?ERR_BAD_REQUEST)); + XFields -> + handle_authorization_response(Host, From, To, + Packet, XFields) + end + end; + _ -> ok + end; + _ -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; + <<"result">> -> ok; + _ -> + Err = jlib:make_error_reply(Packet, + ?ERR_ITEM_NOT_FOUND), + ejabberd_router:route(To, From, Err) + end end. -command_disco_info(_Host, <<?NS_COMMANDS>>, _From) -> - IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-list"}], []}, +command_disco_info(_Host, ?NS_COMMANDS, _From) -> + IdentityEl = #xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"automation">>}, + {<<"type">>, <<"command-list">>}], + children = []}, {result, [IdentityEl]}; -command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) -> - IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-node"}], []}, - FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}, +command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, + _From) -> + IdentityEl = #xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"automation">>}, + {<<"type">>, <<"command-node">>}], + children = []}, + FeaturesEl = #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, {result, [IdentityEl, FeaturesEl]}. node_disco_info(Host, Node, From) -> node_disco_info(Host, Node, From, true, true). -%node_disco_identity(Host, Node, From) -> -% node_disco_info(Host, Node, From, true, false). -%node_disco_features(Host, Node, From) -> -% node_disco_info(Host, Node, From, false, true). + node_disco_info(Host, Node, From, Identity, Features) -> - Action = - fun(#pubsub_node{type = Type, id = NodeId}) -> - I = case Identity of - false -> - []; - true -> - Types = - case tree_call(Host, get_subnodes, [Host, Node, From]) of - [] -> - ["leaf"]; %% No sub-nodes: it's a leaf node - _ -> - case node_call(Type, get_items, [NodeId, From]) of - {result, []} -> ["collection"]; - {result, _} -> ["leaf", "collection"]; - _ -> [] - end - end, - lists:map(fun(T) -> - {xmlelement, "identity", [{"category", "pubsub"}, - {"type", T}], []} - end, Types) - end, - F = case Features of - false -> - []; - true -> - [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} | - lists:map(fun(T) -> - {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []} - end, features(Type))] - end, - %% TODO: add meta-data info (spec section 5.4) - {result, I ++ F} - end, +% Action = +% fun(#pubsub_node{type = Type, id = NodeId}) -> +% I = case Identity of +% false -> +% []; +% true -> +% Types = +% case tree_call(Host, get_subnodes, [Host, Node, From]) of +% [] -> +% [<<"leaf">>]; %% No sub-nodes: it's a leaf node +% _ -> +% case node_call(Type, get_items, [NodeId, From]) of +% {result, []} -> [<<"collection">>]; +% {result, _} -> [<<"leaf">>, <<"collection">>]; +% _ -> [] +% end +% end, +% lists:map(fun(T) -> +% #xmlel{name = <<"identity">>, +% attrs = +% [{<<"category">>, +% <<"pubsub">>}, +% {<<"type">>, T}], +% children = []} +% end, Types) +% end, +% F = case Features of +% false -> +% []; +% true -> +% [#xmlel{name = <<"feature">>, +% attrs = [{<<"var">>, ?NS_PUBSUB}], +% children = []} +% | lists:map(fun (T) -> +% #xmlel{name = <<"feature">>, +% attrs = +% [{<<"var">>, +% <<(?NS_PUBSUB)/binary, +% "#", +% T/binary>>}], +% children = []} +% end, +% features(Type))] +% end, +% %% TODO: add meta-data info (spec section 5.4) +% {result, I ++ F} +% end, +% case transaction(Host, Node, Action, sync_dirty) of +% {result, {_, Result}} -> {result, Result}; +% Other -> Other +% end. + Action = fun (#pubsub_node{type = Type, id = NodeId}) -> + I = Types = case tree_call(Host, get_subnodes, + [Host, Node, From]) + of + [] -> [<<"leaf">>]; + _ -> + case node_call(Type, get_items, + [NodeId, From]) + of + {result, []} -> + [<<"collection">>]; + {result, _} -> + [<<"leaf">>, + <<"collection">>]; + _ -> [] + end + end, + lists:map(fun (T) -> + #xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, + <<"pubsub">>}, + {<<"type">>, T}], + children = []} + end, + Types), + F = [#xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_PUBSUB}], + children = []} + | lists:map(fun (T) -> + #xmlel{name = <<"feature">>, + attrs = + [{<<"var">>, + <<(?NS_PUBSUB)/binary, + "#", + T/binary>>}], + children = []} + end, + features(Type))], + {result, I ++ F} + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other + {result, {_, Result}} -> {result, Result}; + Other -> Other end. iq_disco_info(Host, SNode, From, Lang) -> - [RealSNode|_] = case SNode of - [] -> [[]]; - _ -> string:tokens(SNode, "!") - end, - Node = string_to_node(RealSNode), + [Node | _] = case SNode of + <<>> -> [<<>>]; + _ -> str:tokens(SNode, <<"!">>) + end, + % Node = string_to_node(RealSNode), case Node of - <<>> -> - {result, - [{xmlelement, "identity", - [{"category", "pubsub"}, - {"type", "service"}, - {"name", translate:translate(Lang, "Publish-Subscribe")}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []}, - {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []}, - {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}, - {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++ - lists:map(fun(Feature) -> - {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []} - end, features(Host, Node))}; - <<?NS_COMMANDS>> -> - command_disco_info(Host, Node, From); - <<?NS_PUBSUB_GET_PENDING>> -> - command_disco_info(Host, Node, From); - _ -> - node_disco_info(Host, Node, From) - end. - -iq_disco_items(Host, [], From) -> - case tree_action(Host, get_subnodes, [Host, <<>>, From]) of - Nodes when is_list(Nodes) -> - {result, lists:map( - fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) -> - Attrs = - case get_option(Options, title) of - false -> - [{"jid", Host} |nodeAttr(SubNode)]; - Title -> - [{"jid", Host}, {"name", Title}|nodeAttr(SubNode)] - end, - {xmlelement, "item", Attrs, []} - end, Nodes)}; - Other -> - Other - end; + <<>> -> + {result, + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"pubsub">>}, + {<<"type">>, <<"service">>}, + {<<"name">>, + translate:translate(Lang, <<"Publish-Subscribe">>)}], + children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_PUBSUB}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_VCARD}], children = []}] + ++ + lists:map(fun (Feature) -> + #xmlel{name = <<"feature">>, + attrs = + [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}], + children = []} + end, + features(Host, Node))}; + ?NS_COMMANDS -> command_disco_info(Host, Node, From); + ?NS_PUBSUB_GET_PENDING -> + command_disco_info(Host, Node, From); + _ -> node_disco_info(Host, Node, From) + end. + +-spec(iq_disco_items/3 :: +( + Host :: mod_pubsub:host(), + NodeId :: <<>> | mod_pubsub:nodeId(), + From :: jid()) + -> {result, [xmlel()]} +). +iq_disco_items(Host, <<>>, From) -> + {result, + lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, + options = Options}) -> + Attrs = case get_option(Options, title) of + false -> + [{<<"jid">>, Host} + | nodeAttr(SubNode)]; + Title -> + [{<<"jid">>, Host}, + {<<"name">>, Title} + | nodeAttr(SubNode)] + end, + #xmlel{name = <<"item">>, attrs = Attrs, + children = []} + end, + tree_action(Host, get_subnodes, [Host, <<>>, From]))}; +% case tree_action(Host, get_subnodes, [Host, <<>>, From]) of +% Nodes when is_list(Nodes) -> +% {result, +% lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, +% options = Options}) -> +% Attrs = case get_option(Options, title) of +% false -> +% [{<<"jid">>, Host} +% | nodeAttr(SubNode)]; +% Title -> +% [{<<"jid">>, Host}, +% {<<"name">>, Title} +% | nodeAttr(SubNode)] +% end, +% #xmlel{name = <<"item">>, attrs = Attrs, +% children = []} +% end, +% Nodes)}; +% Other -> Other +% end; iq_disco_items(Host, ?NS_COMMANDS, _From) -> - %% TODO: support localization of this string - CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}], + CommandItems = [#xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Host}, + {<<"node">>, ?NS_PUBSUB_GET_PENDING}, + {<<"name">>, <<"Get Pending">>}], + children = []}], {result, CommandItems}; iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> - CommandItems = [], - {result, CommandItems}; + CommandItems = [], {result, CommandItems}; iq_disco_items(Host, Item, From) -> - case string:tokens(Item, "!") of - [_SNode, _ItemID] -> - {result, []}; - [SNode] -> - Node = string_to_node(SNode), - Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) -> - NodeItems = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, R} -> R; - _ -> [] - end, - Nodes = lists:map( - fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) -> - Attrs = - case get_option(SubOptions, title) of - false -> - [{"jid", Host} |nodeAttr(SubNode)]; - Title -> - [{"jid", Host}, {"name", Title}|nodeAttr(SubNode)] - end, - {xmlelement, "item", Attrs, []} - end, tree_call(Host, get_subnodes, [Host, Node, From])), - Items = lists:map( - fun(#pubsub_item{itemid = {RN, _}}) -> - {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]), - {xmlelement, "item", [{"jid", Host}, {"name", Name}], []} - end, NodeItems), - {result, Nodes ++ Items} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end + case str:tokens(Item, <<"!">>) of + [_Node, _ItemID] -> {result, []}; + [Node] -> +% Node = string_to_node(SNode), + Action = fun (#pubsub_node{id = Idx, type = Type, + options = Options, owners = Owners}) -> + NodeItems = case get_allowed_items_call(Host, Idx, + From, Type, + Options, + Owners) + of + {result, R} -> R; + _ -> [] + end, + Nodes = lists:map(fun (#pubsub_node{nodeid = + {_, SubNode}, + options = + SubOptions}) -> + Attrs = case + get_option(SubOptions, + title) + of + false -> + [{<<"jid">>, + Host} + | nodeAttr(SubNode)]; + Title -> + [{<<"jid">>, + Host}, + {<<"name">>, + Title} + | nodeAttr(SubNode)] + end, + #xmlel{name = <<"item">>, + attrs = Attrs, + children = []} + end, + tree_call(Host, get_subnodes, + [Host, Node, From])), + Items = lists:map(fun (#pubsub_item{itemid = + {RN, _}}) -> + {result, Name} = + node_call(Type, + get_item_name, + [Host, Node, + RN]), + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + Host}, + {<<"name">>, + Name}], + children = []} + end, + NodeItems), + {result, Nodes ++ Items} + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Result}} -> {result, Result}; + Other -> Other + end end. +-spec(iq_sm/3 :: +( + From :: jid(), + To :: jid(), + IQ :: iq_request()) + -> iq_result() | iq_error() +). iq_sm(From, To, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) -> ServerHost = To#jid.lserver, LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)), Res = case XMLNS of - ?NS_PUBSUB -> iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang); - ?NS_PUBSUB_OWNER -> iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang) + ?NS_PUBSUB -> + iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang); + ?NS_PUBSUB_OWNER -> + iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, + Lang) end, case Res of - {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes}; - {error, Error} -> IQ#iq{type = error, sub_el = [Error, SubEl]} + {result, IQRes} -> IQ#iq{type = result, sub_el = IQRes}; + {error, Error} -> + IQ#iq{type = error, sub_el = [Error, SubEl]} end. iq_get_vcard(Lang) -> - [{xmlelement, "FN", [], [{xmlcdata, "ejabberd/mod_pubsub"}]}, - {xmlelement, "URL", [], [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, - translate:translate(Lang, - "ejabberd Publish-Subscribe module") ++ - "\nCopyright (c) 2004-2013 ProcessOne"}]}]. + [#xmlel{name = <<"FN">>, attrs = [], + children = [{xmlcdata, <<"ejabberd/mod_pubsub">>}]}, + #xmlel{name = <<"URL">>, attrs = [], + children = [{xmlcdata, ?EJABBERD_URI}]}, + #xmlel{name = <<"DESC">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"ejabberd Publish-Subscribe module">>))/binary, + "\nCopyright (c) 2004-2013 ProcessOne">>}]}]. + +-spec(iq_pubsub/6 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + From :: jid(), + IQType :: 'get' | 'set', + SubEl :: xmlel(), + Lang :: binary()) + -> {result, [xmlel()]} + %%% + | {error, xmlel()} +). iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). +-spec(iq_pubsub/8 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + From :: jid(), + IQType :: 'get' | 'set', + SubEl :: xmlel(), + Lang :: binary(), + Access :: atom(), + Plugins :: [binary(),...]) + -> {result, [xmlel()]} + %%% + | {error, xmlel()} +). + iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, Access, Plugins) -> - {xmlelement, _, _, SubEls} = SubEl, + #xmlel{children = SubEls} = SubEl, case xml:remove_cdata(SubEls) of - [{xmlelement, Name, Attrs, Els} | Rest] -> - Node = string_to_node(xml:get_attr_s("node", Attrs)), - case {IQType, Name} of - {set, "create"} -> - Config = case Rest of - [{xmlelement, "configure", _, C}] -> C; - _ -> [] - end, - %% Get the type of the node - Type = case xml:get_attr_s("type", Attrs) of - [] -> hd(Plugins); - T -> T - end, - %% we use Plugins list matching because we do not want to allocate - %% atoms for non existing type, this prevent atom allocation overflow - case lists:member(Type, Plugins) of - false -> - {error, extended_error( - ?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, "create-nodes")}; - true -> - create_node(Host, ServerHost, Node, From, - Type, Access, Config) - end; - {set, "publish"} -> - case xml:remove_cdata(Els) of - [{xmlelement, "item", ItemAttrs, Payload}] -> - ItemId = xml:get_attr_s("id", ItemAttrs), - publish_item(Host, ServerHost, Node, From, ItemId, Payload); - [] -> - %% Publisher attempts to publish to persistent node with no item - {error, extended_error(?ERR_BAD_REQUEST, - "item-required")}; - _ -> - %% Entity attempts to publish item with multiple payload elements or namespace does not match - {error, extended_error(?ERR_BAD_REQUEST, - "invalid-payload")} - end; - {set, "retract"} -> - ForceNotify = case xml:get_attr_s("notify", Attrs) of - "1" -> true; - "true" -> true; - _ -> false - end, - case xml:remove_cdata(Els) of - [{xmlelement, "item", ItemAttrs, _}] -> - ItemId = xml:get_attr_s("id", ItemAttrs), - delete_item(Host, Node, From, ItemId, ForceNotify); - _ -> - %% Request does not specify an item - {error, extended_error(?ERR_BAD_REQUEST, - "item-required")} - end; - {set, "subscribe"} -> - Config = case Rest of - [{xmlelement, "options", _, C}] -> C; - _ -> [] - end, - JID = xml:get_attr_s("jid", Attrs), - subscribe_node(Host, Node, From, JID, Config); - {set, "unsubscribe"} -> - JID = xml:get_attr_s("jid", Attrs), - SubId = xml:get_attr_s("subid", Attrs), - unsubscribe_node(Host, Node, From, JID, SubId); - {get, "items"} -> - MaxItems = xml:get_attr_s("max_items", Attrs), - SubId = xml:get_attr_s("subid", Attrs), - ItemIDs = lists:foldl(fun - ({xmlelement, "item", ItemAttrs, _}, Acc) -> - case xml:get_attr_s("id", ItemAttrs) of - "" -> Acc; - ItemID -> [ItemID|Acc] - end; - (_, Acc) -> - Acc - end, [], xml:remove_cdata(Els)), - get_items(Host, Node, From, SubId, MaxItems, ItemIDs); - {get, "subscriptions"} -> - get_subscriptions(Host, Node, From, Plugins); - {get, "affiliations"} -> - get_affiliations(Host, Node, From, Plugins); - {get, "options"} -> - SubID = xml:get_attr_s("subid", Attrs), - JID = xml:get_attr_s("jid", Attrs), - get_options(Host, Node, JID, SubID, Lang); - {set, "options"} -> - SubID = xml:get_attr_s("subid", Attrs), - JID = xml:get_attr_s("jid", Attrs), - set_options(Host, Node, JID, SubID, Els); - _ -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - Other -> - ?INFO_MSG("Too many actions: ~p", [Other]), - {error, ?ERR_BAD_REQUEST} + [#xmlel{name = Name, attrs = Attrs, children = Els} | Rest] -> + Node = xml:get_attr_s(<<"node">>, Attrs), + case {IQType, Name} of + {set, <<"create">>} -> + Config = case Rest of + [#xmlel{name = <<"configure">>, children = C}] -> C; + _ -> [] + end, + Type = case xml:get_attr_s(<<"type">>, Attrs) of + <<>> -> hd(Plugins); + T -> T + end, + case lists:member(Type, Plugins) of + false -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, <<"create-nodes">>)}; + true -> + create_node(Host, ServerHost, Node, From, Type, Access, Config) + end; + {set, <<"publish">>} -> + case xml:remove_cdata(Els) of + [#xmlel{name = <<"item">>, attrs = ItemAttrs, + children = Payload}] -> + ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), + publish_item(Host, ServerHost, Node, From, ItemId, Payload); + [] -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; + _ -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)} + end; + {set, <<"retract">>} -> + ForceNotify = case xml:get_attr_s(<<"notify">>, Attrs) + of + <<"1">> -> true; + <<"true">> -> true; + _ -> false + end, + case xml:remove_cdata(Els) of + [#xmlel{name = <<"item">>, attrs = ItemAttrs}] -> + ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), + delete_item(Host, Node, From, ItemId, ForceNotify); + _ -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"item-required">>)} + end; + {set, <<"subscribe">>} -> + Config = case Rest of + [#xmlel{name = <<"options">>, children = C}] -> C; + _ -> [] + end, + JID = xml:get_attr_s(<<"jid">>, Attrs), + subscribe_node(Host, Node, From, JID, Config); + {set, <<"unsubscribe">>} -> + JID = xml:get_attr_s(<<"jid">>, Attrs), + SubId = xml:get_attr_s(<<"subid">>, Attrs), + unsubscribe_node(Host, Node, From, JID, SubId); + {get, <<"items">>} -> + MaxItems = xml:get_attr_s(<<"max_items">>, Attrs), + SubId = xml:get_attr_s(<<"subid">>, Attrs), + ItemIDs = lists:foldl(fun (#xmlel{name = <<"item">>, + attrs = ItemAttrs}, + Acc) -> + case xml:get_attr_s(<<"id">>, + ItemAttrs) + of + <<"">> -> Acc; + ItemID -> [ItemID | Acc] + end; + (_, Acc) -> Acc + end, + [], xml:remove_cdata(Els)), + get_items(Host, Node, From, SubId, MaxItems, ItemIDs); + {get, <<"subscriptions">>} -> + get_subscriptions(Host, Node, From, Plugins); + {get, <<"affiliations">>} -> + get_affiliations(Host, Node, From, Plugins); + {get, <<"options">>} -> + SubID = xml:get_attr_s(<<"subid">>, Attrs), + JID = xml:get_attr_s(<<"jid">>, Attrs), + get_options(Host, Node, JID, SubID, Lang); + {set, <<"options">>} -> + SubID = xml:get_attr_s(<<"subid">>, Attrs), + JID = xml:get_attr_s(<<"jid">>, Attrs), + set_options(Host, Node, JID, SubID, Els); + _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + end; + Other -> + ?INFO_MSG("Too many actions: ~p", [Other]), + {error, ?ERR_BAD_REQUEST} end. + +-spec(iq_pubsub_owner/6 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + From :: jid(), + IQType :: 'get' | 'set', + SubEl :: xmlel(), + Lang :: binary()) + -> {result, [xmlel()]} + %%% + | {error, xmlel()} +). iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> - {xmlelement, _, _, SubEls} = SubEl, + #xmlel{children = SubEls} = SubEl, Action = xml:remove_cdata(SubEls), case Action of - [{xmlelement, Name, Attrs, Els}] -> - Node = string_to_node(xml:get_attr_s("node", Attrs)), - case {IQType, Name} of - {get, "configure"} -> - get_configure(Host, ServerHost, Node, From, Lang); - {set, "configure"} -> - set_configure(Host, Node, From, Els, Lang); - {get, "default"} -> - get_default(Host, Node, From, Lang); - {set, "delete"} -> - delete_node(Host, Node, From); - {set, "purge"} -> - purge_node(Host, Node, From); - {get, "subscriptions"} -> - get_subscriptions(Host, Node, From); - {set, "subscriptions"} -> - set_subscriptions(Host, Node, From, xml:remove_cdata(Els)); - {get, "affiliations"} -> - get_affiliations(Host, Node, From); - {set, "affiliations"} -> - set_affiliations(Host, Node, From, xml:remove_cdata(Els)); - _ -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - _ -> - ?INFO_MSG("Too many actions: ~p", [Action]), - {error, ?ERR_BAD_REQUEST} + [#xmlel{name = Name, attrs = Attrs, children = Els}] -> + Node = xml:get_attr_s(<<"node">>, Attrs), + case {IQType, Name} of + {get, <<"configure">>} -> + get_configure(Host, ServerHost, Node, From, Lang); + {set, <<"configure">>} -> + set_configure(Host, Node, From, Els, Lang); + {get, <<"default">>} -> + get_default(Host, Node, From, Lang); + {set, <<"delete">>} -> delete_node(Host, Node, From); + {set, <<"purge">>} -> purge_node(Host, Node, From); + {get, <<"subscriptions">>} -> + get_subscriptions(Host, Node, From); + {set, <<"subscriptions">>} -> + set_subscriptions(Host, Node, From, + xml:remove_cdata(Els)); + {get, <<"affiliations">>} -> + get_affiliations(Host, Node, From); + {set, <<"affiliations">>} -> + set_affiliations(Host, Node, From, xml:remove_cdata(Els)); + _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + end; + _ -> + ?INFO_MSG("Too many actions: ~p", [Action]), + {error, ?ERR_BAD_REQUEST} end. iq_command(Host, ServerHost, From, IQ, Access, Plugins) -> case adhoc:parse_request(IQ) of - Req when is_record(Req, adhoc_request) -> - case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of - Resp when is_record(Resp, adhoc_response) -> - {result, [adhoc:produce_response(Req, Resp)]}; - Error -> - Error - end; - Err -> - Err + Req when is_record(Req, adhoc_request) -> + case adhoc_request(Host, ServerHost, From, Req, Access, + Plugins) + of + Resp when is_record(Resp, adhoc_response) -> + {result, [adhoc:produce_response(Req, Resp)]}; + Error -> Error + end; + Err -> Err end. %% @doc <p>Processes an Ad Hoc Command.</p> adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - lang = Lang, - action = "execute", - xdata = false}, - _Access, Plugins) -> + #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, + lang = Lang, action = <<"execute">>, + xdata = false}, + _Access, Plugins) -> send_pending_node_form(Host, Owner, Lang, Plugins); adhoc_request(Host, _ServerHost, Owner, - #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, - action = "execute", - xdata = XData}, - _Access, _Plugins) -> + #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, + action = <<"execute">>, xdata = XData}, + _Access, _Plugins) -> ParseOptions = case XData of - {xmlelement, "x", _Attrs, _SubEls} = XEl -> - case jlib:parse_xdata_submit(XEl) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - XData2 -> - case set_xoption(Host, XData2, []) of - NewOpts when is_list(NewOpts) -> - {result, NewOpts}; - Err -> - Err - end - end; - _ -> - ?INFO_MSG("Bad XForm: ~p", [XData]), - {error, ?ERR_BAD_REQUEST} + #xmlel{name = <<"x">>} = XEl -> + case jlib:parse_xdata_submit(XEl) of + invalid -> {error, ?ERR_BAD_REQUEST}; + XData2 -> + case set_xoption(Host, XData2, []) of + NewOpts when is_list(NewOpts) -> + {result, NewOpts}; + Err -> Err + end + end; + _ -> + ?INFO_MSG("Bad XForm: ~p", [XData]), + {error, ?ERR_BAD_REQUEST} end, case ParseOptions of - {result, XForm} -> - case lists:keysearch(node, 1, XForm) of - {value, {_, Node}} -> - send_pending_auth_events(Host, Node, Owner); - false -> - {error, extended_error(?ERR_BAD_REQUEST, "bad-payload")} - end; - Error -> - Error + {result, XForm} -> + case lists:keysearch(node, 1, XForm) of + {value, {_, Node}} -> + send_pending_auth_events(Host, Node, Owner); + false -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"bad-payload">>)} + end; + Error -> Error end; -adhoc_request(_Host, _ServerHost, _Owner, #adhoc_request{action = "cancel"}, - _Access, _Plugins) -> +adhoc_request(_Host, _ServerHost, _Owner, + #adhoc_request{action = <<"cancel">>}, _Access, + _Plugins) -> #adhoc_response{status = canceled}; -adhoc_request(Host, ServerHost, Owner, #adhoc_request{action = []} = R, - Access, Plugins) -> - adhoc_request(Host, ServerHost, Owner, R#adhoc_request{action = "execute"}, - Access, Plugins); -adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) -> +adhoc_request(Host, ServerHost, Owner, + #adhoc_request{action = <<>>} = R, Access, Plugins) -> + adhoc_request(Host, ServerHost, Owner, + R#adhoc_request{action = <<"execute">>}, Access, + Plugins); +adhoc_request(_Host, _ServerHost, _Owner, Other, + _Access, _Plugins) -> ?DEBUG("Couldn't process ad hoc command:~n~p", [Other]), {error, ?ERR_ITEM_NOT_FOUND}. @@ -1419,269 +2158,339 @@ adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) -> %% @doc <p>Sends the process pending subscriptions XForm for Host to %% Owner.</p> send_pending_node_form(Host, Owner, _Lang, Plugins) -> - Filter = - fun (Plugin) -> - lists:member("get-pending", features(Plugin)) - end, + Filter = fun (Plugin) -> + lists:member(<<"get-pending">>, features(Plugin)) + end, case lists:filter(Filter, Plugins) of - [] -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; - Ps -> - XOpts = lists:map(fun (Node) -> - {xmlelement, "option", [], - [{xmlelement, "value", [], - [{xmlcdata, node_to_string(Node)}]}]} - end, get_pending_nodes(Host, Owner, Ps)), - XForm = {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}], - [{xmlelement, "field", - [{"type", "list-single"}, {"var", "pubsub#node"}], - lists:usort(XOpts)}]}, - #adhoc_response{status = executing, - defaultaction = "execute", - elements = [XForm]} + [] -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; + Ps -> + XOpts = lists:map(fun (Node) -> + #xmlel{name = <<"option">>, attrs = [], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, Node}]}]} + end, + get_pending_nodes(Host, Owner, Ps)), + XForm = #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-single">>}, + {<<"var">>, <<"pubsub#node">>}], + children = lists:usort(XOpts)}]}, + #adhoc_response{status = executing, + defaultaction = <<"execute">>, elements = [XForm]} end. get_pending_nodes(Host, Owner, Plugins) -> - Tr = - fun (Type) -> - case node_call(Type, get_pending_nodes, [Host, Owner]) of - {result, Nodes} -> Nodes; - _ -> [] - end - end, - case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end, - sync_dirty) of - {result, Res} -> Res; - Err -> Err + Tr = fun (Type) -> + case node_call(Type, get_pending_nodes, [Host, Owner]) + of + {result, Nodes} -> Nodes; + _ -> [] + end + end, + case transaction(fun () -> + {result, lists:flatmap(Tr, Plugins)} + end, + sync_dirty) + of + {result, Res} -> Res; + Err -> Err end. %% @spec (Host, Node, Owner) -> iqRes() %% @doc <p>Send a subscription approval form to Owner for all pending %% subscriptions on Host and Node.</p> send_pending_auth_events(Host, Node, Owner) -> - ?DEBUG("Sending pending auth events for ~s on ~s:~s", - [jlib:jid_to_string(Owner), Host, node_to_string(Node)]), - Action = - fun (#pubsub_node{id = NodeID, type = Type}) -> - case lists:member("get-pending", features(Type)) of - true -> - case node_call(Type, get_affiliation, [NodeID, Owner]) of - {result, owner} -> - node_call(Type, get_node_subscriptions, [NodeID]); - _ -> - {error, ?ERR_FORBIDDEN} - end; - false -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end - end, + ?DEBUG("Sending pending auth events for ~s on " + "~s:~s", + [jlib:jid_to_string(Owner), Host, Node]), + Action = fun (#pubsub_node{id = NodeID, type = Type}) -> + case lists:member(<<"get-pending">>, features(Type)) of + true -> + case node_call(Type, get_affiliation, + [NodeID, Owner]) + of + {result, owner} -> + node_call(Type, get_node_subscriptions, + [NodeID]); + _ -> {error, ?ERR_FORBIDDEN} + end; + false -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + end + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {N, Subscriptions}} -> - lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J)); - ({J, pending}) -> send_authorization_request(N, jlib:make_jid(J)); - (_) -> ok - end, Subscriptions), - #adhoc_response{}; - Err -> - Err + {result, {N, Subscriptions}} -> + lists:foreach(fun ({J, pending, _SubID}) -> + send_authorization_request(N, jlib:make_jid(J)); + ({J, pending}) -> + send_authorization_request(N, jlib:make_jid(J)); + (_) -> ok + end, + Subscriptions), + #adhoc_response{}; + Err -> Err end. %%% authorization handling -send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) -> - Lang = "en", %% TODO fix - Stanza = {xmlelement, "message", - [], - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}], - [{xmlelement, "title", [], - [{xmlcdata, translate:translate(Lang, "PubSub subscriber request")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, translate:translate(Lang, "Choose whether to approve this entity's subscription.")}]}, - {xmlelement, "field", - [{"var", "FORM_TYPE"}, {"type", "hidden"}], - [{xmlelement, "value", [], [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]}, - {xmlelement, "field", - [{"var", "pubsub#node"}, {"type", "text-single"}, - {"label", translate:translate(Lang, "Node ID")}], - [{xmlelement, "value", [], - [{xmlcdata, node_to_string(Node)}]}]}, - {xmlelement, "field", [{"var", "pubsub#subscriber_jid"}, - {"type", "jid-single"}, - {"label", translate:translate(Lang, "Subscriber Address")}], - [{xmlelement, "value", [], - [{xmlcdata, jlib:jid_to_string(Subscriber)}]}]}, - {xmlelement, "field", - [{"var", "pubsub#allow"}, - {"type", "boolean"}, - {"label", translate:translate(Lang, "Allow this Jabber ID to subscribe to this pubsub node?")}], - [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]}, - lists:foreach(fun(Owner) -> - ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza) - end, Owners). +send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, + Subscriber) -> + Lang = <<"en">>, + Stanza = #xmlel{name = <<"message">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"PubSub subscriber request">>)}]}, + #xmlel{name = <<"instructions">>, + attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Choose whether to approve this entity's " + "subscription.">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, + ?NS_PUBSUB_SUB_AUTH}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"pubsub#node">>}, + {<<"type">>, + <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"Node ID">>)}], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, Node}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, + <<"pubsub#subscriber_jid">>}, + {<<"type">>, <<"jid-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"Subscriber Address">>)}], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, + jlib:jid_to_string(Subscriber)}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, + <<"pubsub#allow">>}, + {<<"type">>, <<"boolean">>}, + {<<"label">>, + translate:translate(Lang, + <<"Allow this Jabber ID to subscribe to " + "this pubsub node?">>)}], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, + <<"false">>}]}]}]}]}, + lists:foreach(fun (Owner) -> + ejabberd_router:route(service_jid(Host), + jlib:make_jid(Owner), Stanza) + end, + Owners). find_authorization_response(Packet) -> - {xmlelement, _Name, _Attrs, Els} = Packet, - XData1 = lists:map(fun({xmlelement, "x", XAttrs, _} = XEl) -> - case xml:get_attr_s("xmlns", XAttrs) of - ?NS_XDATA -> - case xml:get_attr_s("type", XAttrs) of - "cancel" -> - none; - _ -> - jlib:parse_xdata_submit(XEl) - end; - _ -> - none + #xmlel{children = Els} = Packet, + XData1 = lists:map(fun (#xmlel{name = <<"x">>, + attrs = XAttrs} = + XEl) -> + case xml:get_attr_s(<<"xmlns">>, XAttrs) of + ?NS_XDATA -> + case xml:get_attr_s(<<"type">>, XAttrs) of + <<"cancel">> -> none; + _ -> jlib:parse_xdata_submit(XEl) + end; + _ -> none end; - (_) -> - none - end, xml:remove_cdata(Els)), - XData = lists:filter(fun(E) -> E /= none end, XData1), + (_) -> none + end, + xml:remove_cdata(Els)), + XData = lists:filter(fun (E) -> E /= none end, XData1), case XData of - [invalid] -> invalid; - [] -> none; - [XFields] when is_list(XFields) -> - ?DEBUG("XFields: ~p", [XFields]), - case lists:keysearch("FORM_TYPE", 1, XFields) of - {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> - XFields; - _ -> - invalid - end + [invalid] -> invalid; + [] -> none; + [XFields] when is_list(XFields) -> + ?DEBUG("XFields: ~p", [XFields]), + case lists:keysearch(<<"FORM_TYPE">>, 1, XFields) of + {value, {_, [?NS_PUBSUB_SUB_AUTH]}} -> XFields; + _ -> invalid + end end. - %% @spec (Host, JID, Node, Subscription) -> void %% Host = mod_pubsub:host() %% JID = jlib:jid() %% SNode = string() %% Subscription = atom() | {atom(), mod_pubsub:subid()} %% @doc Send a message to JID with the supplied Subscription +%% TODO : ask Christophe's opinion send_authorization_approval(Host, JID, SNode, Subscription) -> SubAttrs = case Subscription of - {S, SID} -> [{"subscription", subscription_to_string(S)}, - {"subid", SID}]; - S -> [{"subscription", subscription_to_string(S)}] +% {S, SID} -> +% [{<<"subscription">>, subscription_to_string(S)}, +% {<<"subid">>, SID}]; + S -> [{<<"subscription">>, subscription_to_string(S)}] end, - Stanza = event_stanza( - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(JID)}|nodeAttr(SNode)] ++ SubAttrs, - []}]), + Stanza = event_stanza([#xmlel{name = <<"subscription">>, + attrs = + [{<<"jid">>, jlib:jid_to_string(JID)} + | nodeAttr(SNode)] + ++ SubAttrs, + children = []}]), ejabberd_router:route(service_jid(Host), JID, Stanza). handle_authorization_response(Host, From, To, Packet, XFields) -> - case {lists:keysearch("pubsub#node", 1, XFields), - lists:keysearch("pubsub#subscriber_jid", 1, XFields), - lists:keysearch("pubsub#allow", 1, XFields)} of - {{value, {_, [SNode]}}, {value, {_, [SSubscriber]}}, - {value, {_, [SAllow]}}} -> - Node = string_to_node(SNode), - Subscriber = jlib:string_to_jid(SSubscriber), - Allow = case SAllow of - "1" -> true; - "true" -> true; - _ -> false - end, - Action = fun(#pubsub_node{type = Type, owners = Owners, id = NodeId}) -> - IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), Owners), - {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]), - if - not IsApprover -> - {error, ?ERR_FORBIDDEN}; - true -> - update_auth(Host, SNode, Type, NodeId, - Subscriber, Allow, - Subscriptions) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {error, Error} -> - ejabberd_router:route( - To, From, - jlib:make_error_reply(Packet, Error)); - {result, {_, _NewSubscription}} -> - %% XXX: notify about subscription state change, section 12.11 - ok; - _ -> - ejabberd_router:route( - To, From, - jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR)) - end; - _ -> - ejabberd_router:route( - To, From, - jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE)) + case {lists:keysearch(<<"pubsub#node">>, 1, XFields), + lists:keysearch(<<"pubsub#subscriber_jid">>, 1, XFields), + lists:keysearch(<<"pubsub#allow">>, 1, XFields)} + of + {{value, {_, [Node]}}, {value, {_, [SSubscriber]}}, + {value, {_, [SAllow]}}} -> +% Node = string_to_node(SNode), + Subscriber = jlib:string_to_jid(SSubscriber), + Allow = case SAllow of + <<"1">> -> true; + <<"true">> -> true; + _ -> false + end, + Action = fun (#pubsub_node{type = Type, owners = Owners, + id = NodeId}) -> + IsApprover = + lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), + Owners), + {result, Subscriptions} = node_call(Type, + get_subscriptions, + [NodeId, + Subscriber]), + if not IsApprover -> {error, ?ERR_FORBIDDEN}; + true -> + update_auth(Host, Node, Type, NodeId, + Subscriber, Allow, Subscriptions) + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {error, Error} -> + ejabberd_router:route(To, From, + jlib:make_error_reply(Packet, Error)); + {result, {_, _NewSubscription}} -> + %% XXX: notify about subscription state change, section 12.11 + ok; + _ -> + ejabberd_router:route(To, From, + jlib:make_error_reply(Packet, + ?ERR_INTERNAL_SERVER_ERROR)) + end; + _ -> + ejabberd_router:route(To, From, + jlib:make_error_reply(Packet, + ?ERR_NOT_ACCEPTABLE)) end. -update_auth(Host, Node, Type, NodeId, Subscriber, - Allow, Subscriptions) -> - Subscription = lists:filter(fun({pending, _}) -> true; - (_) -> false - end, Subscriptions), +update_auth(Host, Node, Type, NodeId, Subscriber, Allow, + Subscriptions) -> + Subscription = lists:filter(fun ({pending, _}) -> true; + (_) -> false + end, + Subscriptions), case Subscription of - [{pending, SubID}] -> %% TODO does not work if several pending - NewSubscription = case Allow of - true -> subscribed; - false -> none - end, - node_call(Type, set_subscriptions, - [NodeId, Subscriber, NewSubscription, SubID]), - send_authorization_approval(Host, Subscriber, Node, - NewSubscription), - {result, ok}; - _ -> - {error, ?ERR_UNEXPECTED_REQUEST} + [{pending, SubID}] -> + NewSubscription = case Allow of + true -> subscribed; + false -> none + end, + node_call(Type, set_subscriptions, + [NodeId, Subscriber, NewSubscription, SubID]), + send_authorization_approval(Host, Subscriber, Node, + NewSubscription), + {result, ok}; + _ -> {error, ?ERR_UNEXPECTED_REQUEST} end. -define(XFIELD(Type, Label, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). -define(BOOLXFIELD(Label, Var, Val), - ?XFIELD("boolean", Label, Var, + ?XFIELD(<<"boolean">>, Label, Var, case Val of - true -> "1"; - _ -> "0" + true -> <<"1">>; + _ -> <<"0">> end)). -define(STRINGXFIELD(Label, Var, Val), - ?XFIELD("text-single", Label, Var, Val)). + ?XFIELD(<<"text-single">>, Label, Var, Val)). -define(STRINGMXFIELD(Label, Var, Vals), - {xmlelement, "field", [{"type", "text-multi"}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, V}]} || V <- Vals]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-multi">>}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, V}]} + || V <- Vals]}). -define(XFIELDOPT(Type, Label, Var, Val, Opts), - {xmlelement, "field", [{"type", Type}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - lists:map(fun(Opt) -> - {xmlelement, "option", [], - [{xmlelement, "value", [], - [{xmlcdata, Opt}]}]} - end, Opts) ++ - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + lists:map(fun (Opt) -> + #xmlel{name = <<"option">>, attrs = [], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, Opt}]}]} + end, + Opts) + ++ + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]}]}). -define(LISTXFIELD(Label, Var, Val, Opts), - ?XFIELDOPT("list-single", Label, Var, Val, Opts)). + ?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)). -define(LISTMXFIELD(Label, Var, Vals, Opts), - {xmlelement, "field", [{"type", "list-multi"}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - lists:map(fun(Opt) -> - {xmlelement, "option", [], - [{xmlelement, "value", [], - [{xmlcdata, Opt}]}]} - end, Opts) ++ - lists:map(fun(Val) -> - {xmlelement, "value", [], - [{xmlcdata, Val}]} - end, Vals)}). - %% @spec (Host::host(), ServerHost::host(), Node::pubsubNode(), Owner::jid(), NodeType::nodeType()) -> %% {error, Reason::stanzaError()} | %% {result, []} @@ -1702,54 +2511,104 @@ update_auth(Host, Node, Type, NodeId, Subscriber, %%<li>nodetree create_node checks if nodeid already exists</li> %%<li>node plugin create_node just sets default affiliation/subscription</li> %%</ul> + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-multi">>}, + {<<"label">>, translate:translate(Lang, Label)}, + {<<"var">>, Var}], + children = + lists:map(fun (Opt) -> + #xmlel{name = <<"option">>, attrs = [], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, Opt}]}]} + end, + Opts) + ++ + lists:map(fun (Val) -> + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Val}]} + end, + Vals)}). + +-spec(create_node/5 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + Node :: <<>> | mod_pubsub:nodeId(), + Owner :: jid(), + Type :: binary()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). + create_node(Host, ServerHost, Node, Owner, Type) -> create_node(Host, ServerHost, Node, Owner, Type, all, []). + +-spec(create_node/7 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + Node :: <<>> | mod_pubsub:nodeId(), + Owner :: jid(), + Type :: binary(), + Access :: atom(), + Configuration :: [xmlel()]) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> - case lists:member("instant-nodes", features(Type)) of - true -> - NewNode = string_to_node(randoms:get_string()), - case create_node(Host, ServerHost, - NewNode, Owner, Type, Access, Configuration) of - {result, []} -> - {result, - [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "create", nodeAttr(NewNode), []}]}]}; - Error -> - Error - end; - false -> - %% Service does not support instant nodes - {error, extended_error(?ERR_NOT_ACCEPTABLE, "nodeid-required")} + case lists:member(<<"instant-nodes">>, features(Type)) of + true -> + NewNode = randoms:get_string(), + case create_node(Host, ServerHost, NewNode, Owner, Type, + Access, Configuration) + of + {result, _} -> + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"create">>, + attrs = nodeAttr(NewNode), + children = []}]}]}; + Error -> Error + end; + false -> + {error, + extended_error(?ERR_NOT_ACCEPTABLE, + <<"nodeid-required">>)} end; create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> Type = select_type(ServerHost, Host, Node, GivenType), - %% TODO, check/set node_type = Type ParseOptions = case xml:remove_cdata(Configuration) of - [] -> - {result, node_options(Type)}; - [{xmlelement, "x", _Attrs, _SubEls} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - XData -> - case set_xoption(Host, XData, node_options(Type)) of - NewOpts when is_list(NewOpts) -> - {result, NewOpts}; - Err -> - Err - end - end; - _ -> - ?INFO_MSG("Node ~p; bad configuration: ~p", [Node, Configuration]), - {error, ?ERR_BAD_REQUEST} + [] -> {result, node_options(Type)}; + [#xmlel{name = <<"x">>} = XEl] -> + case jlib:parse_xdata_submit(XEl) of + invalid -> {error, ?ERR_BAD_REQUEST}; + XData -> + case set_xoption(Host, XData, node_options(Type)) + of + NewOpts when is_list(NewOpts) -> + {result, NewOpts}; + Err -> Err + end + end; + _ -> + ?INFO_MSG("Node ~p; bad configuration: ~p", + [Node, Configuration]), + {error, ?ERR_BAD_REQUEST} end, case ParseOptions of {result, NodeOptions} -> CreateNode = fun() -> - SNode = node_to_string(Node), Parent = case node_call(Type, node_to_path, [Node]) of - {result, [SNode]} -> <<>>; + {result, [Node]} -> <<>>; {result, Path} -> element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)])) end, Parents = case Parent of @@ -1778,9 +2637,11 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> {error, ?ERR_FORBIDDEN} end end, - Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "create", nodeAttr(Node), - []}]}], + Reply = [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = [#xmlel{name = <<"create">>, + attrs = nodeAttr(Node), + children = []}]}], case transaction(CreateNode, transaction) of {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), @@ -1811,6 +2672,15 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> %% Node = pubsubNode() %% Owner = jid() %% Reason = stanzaError() +-spec(delete_node/3 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + Owner :: jid()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). %% @doc <p>Delete specified node and all childs.</p> %%<p>There are several reasons why the node deletion request might fail:</p> %%<ul> @@ -1819,7 +2689,6 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> %%<li>The specified node does not exist.</li> %%</ul> delete_node(_Host, <<>>, _Owner) -> - %% Node is the root {error, ?ERR_NOT_ALLOWED}; delete_node(Host, Node, Owner) -> Action = fun(#pubsub_node{type = Type, id = NodeId}) -> @@ -1836,9 +2705,9 @@ delete_node(Host, Node, Owner) -> %% Entity is not an owner {error, ?ERR_FORBIDDEN} end - end, + end, Reply = [], - ServerHost = get(server_host), % not clean, but prevent many API changes + ServerHost = get(server_host), case transaction(Host, Node, Action, transaction) of {result, {_TNode, {SubsByDepth, {Result, broadcast, Removed}}}} -> lists:foreach(fun({RNode, _RSubscriptions}) -> @@ -1882,6 +2751,17 @@ delete_node(Host, Node, Owner) -> %% Node = pubsubNode() %% From = jid() %% JID = jid() +-spec(subscribe_node/5 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid(), + JID :: binary(), + Configuration :: [xmlel()]) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). %% @see node_hometree:subscribe_node/5 %% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> %%<p>There are several reasons why the subscription request might fail:</p> @@ -1898,85 +2778,98 @@ delete_node(Host, Node, Owner) -> %%<li>The node does not exist.</li> %%</ul> subscribe_node(Host, Node, From, JID, Configuration) -> - SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, + SubOpts = case + pubsub_subscription:parse_options_xform(Configuration) + of + {result, GoodSubOpts} -> GoodSubOpts; + _ -> invalid + end, Subscriber = case jlib:string_to_jid(JID) of - error -> {"", "", ""}; - J -> jlib:jid_tolower(J) + error -> {<<"">>, <<"">>, <<"">>}; + J -> + case jlib:jid_tolower(J) of + error -> {<<"">>, <<"">>, <<"">>}; + J1 -> J1 + end end, - Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId}) -> - Features = features(Type), - SubscribeFeature = lists:member("subscribe", Features), - OptionsFeature = lists:member("subscription-options", Features), - HasOptions = not (SubOpts == []), - SubscribeConfig = get_option(Options, subscribe), - AccessModel = get_option(Options, access_model), - SendLast = get_option(Options, send_last_published_item), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups), - if - not SubscribeFeature -> - %% Node does not support subscriptions - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; + Action = fun (#pubsub_node{options = Options, + owners = Owners, type = Type, id = NodeId}) -> + Features = features(Type), + SubscribeFeature = lists:member(<<"subscribe">>, Features), + OptionsFeature = lists:member(<<"subscription-options">>, Features), + HasOptions = not (SubOpts == []), + SubscribeConfig = get_option(Options, subscribe), + AccessModel = get_option(Options, access_model), + SendLast = get_option(Options, send_last_published_item), + AllowedGroups = get_option(Options, roster_groups_allowed, []), + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, Subscriber, + Owners, AccessModel, AllowedGroups), + if not SubscribeFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, <<"subscribe">>)}; not SubscribeConfig -> - %% Node does not support subscriptions - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")}; + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, <<"subscribe">>)}; HasOptions andalso not OptionsFeature -> - %% Node does not support subscription options - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")}; + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"subscription-options">>)}; SubOpts == invalid -> - %% Passed invalit options submit form - {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")}; + {error, + extended_error(?ERR_BAD_REQUEST, + <<"invalid-options">>)}; true -> node_call(Type, subscribe_node, - [NodeId, From, Subscriber, - AccessModel, SendLast, - PresenceSubscription, RosterGroup, - SubOpts]) - end - end, - Reply = fun(Subscription) -> - %% TODO, this is subscription-notification, should depends on node features + [NodeId, From, Subscriber, AccessModel, + SendLast, PresenceSubscription, + RosterGroup, SubOpts]) + end + end, + Reply = fun (Subscription) -> SubAttrs = case Subscription of - {subscribed, SubId} -> - [{"subscription", subscription_to_string(subscribed)}, - {"subid", SubId}, - {"node",Node}]; - Other -> - [{"subscription", subscription_to_string(Other)}, - {"node", Node}] + {subscribed, SubId} -> + [{<<"subscription">>, + subscription_to_string(subscribed)}, + {<<"subid">>, SubId}, {<<"node">>, Node}]; + Other -> + [{<<"subscription">>, + subscription_to_string(Other)}, + {<<"node">>, Node}] end, - Fields = - [{"jid", jlib:jid_to_string(Subscriber)} | SubAttrs], - [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "subscription", Fields, []}]}] + Fields = [{<<"jid">>, jlib:jid_to_string(Subscriber)} + | SubAttrs], + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"subscription">>, + attrs = Fields, children = []}]}] end, case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, subscribed, SubId, send_last}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - send_items(Host, Node, NodeId, Type, Subscriber, last), - case Result of - default -> {result, Reply({subscribed, SubId})}; - _ -> {result, Result} - end; - {result, {_TNode, {default, subscribed, SubId}}} -> - {result, Reply({subscribed, SubId})}; - {result, {_TNode, {Result, subscribed, _SubId}}} -> - {result, Result}; - {result, {TNode, {default, pending, _SubId}}} -> - send_authorization_request(TNode, Subscriber), - {result, Reply(pending)}; - {result, {TNode, {Result, pending}}} -> - send_authorization_request(TNode, Subscriber), - {result, Result}; - {result, {_, Result}} -> - %% this case should never occure anyway - {result, Result}; - Error -> - Error + {result, + {TNode, {Result, subscribed, SubId, send_last}}} -> + NodeId = TNode#pubsub_node.id, + Type = TNode#pubsub_node.type, + send_items(Host, Node, NodeId, Type, Subscriber, last), + case Result of + default -> {result, Reply({subscribed, SubId})}; + _ -> {result, Result} + end; + {result, {_TNode, {default, subscribed, SubId}}} -> + {result, Reply({subscribed, SubId})}; + {result, {_TNode, {Result, subscribed, _SubId}}} -> + {result, Result}; + {result, {TNode, {default, pending, _SubId}}} -> + send_authorization_request(TNode, Subscriber), + {result, Reply(pending)}; + {result, {TNode, {Result, pending}}} -> + send_authorization_request(TNode, Subscriber), + {result, Result}; + {result, {_, Result}} -> {result, Result}; + Error -> Error end. %% @spec (Host, Noce, From, JID, SubId) -> {error, Reason} | {result, []} @@ -1995,23 +2888,37 @@ subscribe_node(Host, Node, From, JID, Configuration) -> %%<li>The node does not exist.</li> %%<li>The request specifies a subscription ID that is not valid or current.</li> %%</ul> -unsubscribe_node(Host, Node, From, JID, SubId) when is_list(JID) -> +-spec(unsubscribe_node/5 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid(), + JID :: binary() | ljid(), + SubId :: mod_pubsub:subId()) + -> {result, []} + %%% + | {error, xmlel()} +). +unsubscribe_node(Host, Node, From, JID, SubId) + when is_binary(JID) -> Subscriber = case jlib:string_to_jid(JID) of - error -> {"", "", ""}; - J -> jlib:jid_tolower(J) + error -> {<<"">>, <<"">>, <<"">>}; + J -> + case jlib:jid_tolower(J) of + error -> {<<"">>, <<"">>, <<"">>}; + J1 -> J1 + end end, unsubscribe_node(Host, Node, From, Subscriber, SubId); unsubscribe_node(Host, Node, From, Subscriber, SubId) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, unsubscribe_node, [NodeId, From, Subscriber, SubId]) - end, + Action = fun (#pubsub_node{type = Type, id = NodeId}) -> + node_call(Type, unsubscribe_node, + [NodeId, From, Subscriber, SubId]) + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, default}} -> - {result, []}; - {result, {_, Result}} -> - {result, Result}; - Error -> - Error + {result, {_, default}} -> {result, []}; +% {result, {_, Result}} -> {result, Result}; + Error -> Error end. %% @spec (Host::host(), ServerHost::host(), JID::jid(), Node::pubsubNode(), ItemId::string(), Payload::term()) -> @@ -2028,52 +2935,68 @@ unsubscribe_node(Host, Node, From, Subscriber, SubId) -> %%<li>The item contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node.</li> %%<li>The request does not match the node configuration.</li> %%</ul> -publish_item(Host, ServerHost, Node, Publisher, "", Payload) -> - %% if publisher does not specify an ItemId, the service MUST generate the ItemId +-spec(publish_item/6 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + Node :: mod_pubsub:nodeId(), + Publisher :: jid(), + ItemId :: <<>> | mod_pubsub:itemId(), + Payload :: mod_pubsub:payload()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). +publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload) -> publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload); publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> - Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PublishFeature = lists:member("publish", Features), - PublishModel = get_option(Options, publish_model), - DeliverPayloads = get_option(Options, deliver_payloads), - PersistItems = get_option(Options, persist_items), - MaxItems = case PersistItems of - false -> 0; - true -> max_items(Host, Options) - end, - PayloadCount = payload_xmlelements(Payload), - PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2 - PayloadMaxSize = get_option(Options, max_payload_size), - % pubsub#deliver_payloads true - % pubsub#persist_items true -> 1 item; false -> 0 item - if - not PublishFeature -> - %% Node does not support item publication - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "publish")}; + Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> + Features = features(Type), + PublishFeature = lists:member(<<"publish">>, Features), + PublishModel = get_option(Options, publish_model), + DeliverPayloads = get_option(Options, deliver_payloads), + PersistItems = get_option(Options, persist_items), + MaxItems = case PersistItems of + false -> 0; + true -> max_items(Host, Options) + end, + PayloadCount = payload_xmlelements(Payload), + PayloadSize = byte_size(term_to_binary(Payload)) - 2, + PayloadMaxSize = get_option(Options, max_payload_size), + if not PublishFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, <<"publish">>)}; PayloadSize > PayloadMaxSize -> - %% Entity attempts to publish very large payload - {error, extended_error(?ERR_NOT_ACCEPTABLE, "payload-too-big")}; + {error, + extended_error(?ERR_NOT_ACCEPTABLE, <<"payload-too-big">>)}; (PayloadCount == 0) and (Payload == []) -> - %% Publisher attempts to publish to payload node with no payload - {error, extended_error(?ERR_BAD_REQUEST, "payload-required")}; + {error, + extended_error(?ERR_BAD_REQUEST, <<"payload-required">>)}; (PayloadCount > 1) or (PayloadCount == 0) -> - %% Entity attempts to publish item with multiple payload elements - {error, extended_error(?ERR_BAD_REQUEST, "invalid-payload")}; - (DeliverPayloads == 0) and (PersistItems == 0) and (PayloadSize > 0) -> - %% Publisher attempts to publish to transient notification node with item - {error, extended_error(?ERR_BAD_REQUEST, "item-forbidden")}; - ((DeliverPayloads == 1) or (PersistItems == 1)) and (PayloadSize == 0) -> - %% Publisher attempts to publish to persistent node with no item - {error, extended_error(?ERR_BAD_REQUEST, "item-required")}; + {error, + extended_error(?ERR_BAD_REQUEST, <<"invalid-payload">>)}; + (DeliverPayloads == false) and (PersistItems == false) and + (PayloadSize > 0) -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"item-forbidden">>)}; + ((DeliverPayloads == true) or (PersistItems == true)) and + (PayloadSize == 0) -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"item-required">>)}; true -> node_call(Type, publish_item, [NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload]) end end, ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, Payload]), - Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "publish", nodeAttr(Node), - [{xmlelement, "item", itemAttr(ItemId), []}]}]}], + Reply = [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"publish">>, attrs = nodeAttr(Node), + children = + [#xmlel{name = <<"item">>, + attrs = itemAttr(ItemId), + children = []}]}]}], case transaction(Host, Node, Action, sync_dirty) of {result, {TNode, {Result, Broadcast, Removed}}} -> NodeId = TNode#pubsub_node.id, @@ -2123,8 +3046,12 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> case lists:member("auto-create", features(Type)) of true -> case create_node(Host, ServerHost, Node, Publisher, Type) of - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "create", [{"node", NewNode}], []}]}]} -> + {result, [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"create">>, + attrs = [{<<"node">>, NewNode}], + children = []}]}]} -> publish_item(Host, ServerHost, list_to_binary(NewNode), Publisher, ItemId, Payload); _ -> @@ -2140,6 +3067,16 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> %% @spec (Host::host(), JID::jid(), Node::pubsubNode(), ItemId::string()) -> %% {error, Reason::stanzaError()} | %% {result, []} +-spec(delete_item/4 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + Publisher :: jid(), + ItemId :: mod_pubsub:itemId()) + -> {result, []} + %%% + | {error, xmlel()} +). %% @doc <p>Delete item from a PubSub node.</p> %% <p>The permission to delete an item must be verified by the plugin implementation.</p> %%<p>There are several reasons why the item retraction request might fail:</p> @@ -2153,50 +3090,54 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> %%</ul> delete_item(Host, Node, Publisher, ItemId) -> delete_item(Host, Node, Publisher, ItemId, false). -delete_item(_, "", _, _, _) -> - %% Request does not specify a node - {error, extended_error(?ERR_BAD_REQUEST, "node-required")}; + + +delete_item(_, <<"">>, _, _, _) -> + {error, + extended_error(?ERR_BAD_REQUEST, <<"node-required">>)}; delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> - Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - PersistentFeature = lists:member("persistent-items", Features), - DeleteFeature = lists:member("delete-items", Features), - PublishModel = get_option(Options, publish_model), - if - %%-> iq_pubsub just does that matchs + Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> + Features = features(Type), + PersistentFeature = lists:member(<<"persistent-items">>, Features), + DeleteFeature = lists:member(<<"delete-items">>, Features), + PublishModel = get_option(Options, publish_model), + if %%-> iq_pubsub just does that matchs %% %% Request does not specify an item %% {error, extended_error(?ERR_BAD_REQUEST, "item-required")}; not PersistentFeature -> - %% Node does not support persistent items - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")}; + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"persistent-items">>)}; not DeleteFeature -> - %% Service does not support item deletion - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "delete-items")}; + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, <<"delete-items">>)}; true -> - node_call(Type, delete_item, [NodeId, Publisher, PublishModel, ItemId]) - end + node_call(Type, delete_item, + [NodeId, Publisher, PublishModel, ItemId]) + end end, Reply = [], case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, broadcast}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_retract_items(Host, Node, NodeId, Type, Options, [ItemId], ForceNotify), - case get_cached_item(Host, NodeId) of - #pubsub_item{itemid = {ItemId, NodeId}, _ = '_'} -> unset_cached_item(Host, NodeId); + {result, {TNode, {Result, broadcast}}} -> + NodeId = TNode#pubsub_node.id, + Type = TNode#pubsub_node.type, + Options = TNode#pubsub_node.options, + broadcast_retract_items(Host, Node, NodeId, Type, + Options, [ItemId], ForceNotify), + case get_cached_item(Host, NodeId) of + #pubsub_item{itemid = {ItemId, NodeId}} -> + unset_cached_item(Host, NodeId); _ -> ok - end, - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_, default}} -> - {result, Reply}; - {result, {_, Result}} -> - {result, Result}; - Error -> - Error + end, + case Result of + default -> {result, Reply}; + _ -> {result, Result} + end; + {result, {_, default}} -> {result, Reply}; + {result, {_, Result}} -> {result, Result}; + Error -> Error end. %% @spec (Host, JID, Node) -> @@ -2213,44 +3154,53 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> %%<li>The node is not configured to persist items.</li> %%<li>The specified node does not exist.</li> %%</ul> +-spec(purge_node/3 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + Owner :: jid()) + -> {result, []} + %%% + | {error, xmlel()} +). purge_node(Host, Node, Owner) -> - Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> + Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> Features = features(Type), - PurgeFeature = lists:member("purge-nodes", Features), - PersistentFeature = lists:member("persistent-items", Features), + PurgeFeature = lists:member(<<"purge-nodes">>, Features), + PersistentFeature = lists:member(<<"persistent-items">>, Features), PersistentConfig = get_option(Options, persist_items), - if - not PurgeFeature -> - %% Service does not support node purging - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "purge-nodes")}; - not PersistentFeature -> - %% Node does not support persistent items - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")}; - not PersistentConfig -> - %% Node is not configured for persistent items - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")}; - true -> - node_call(Type, purge_node, [NodeId, Owner]) + if not PurgeFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, <<"purge-nodes">>)}; + not PersistentFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"persistent-items">>)}; + not PersistentConfig -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"persistent-items">>)}; + true -> node_call(Type, purge_node, [NodeId, Owner]) end end, Reply = [], case transaction(Host, Node, Action, sync_dirty) of - {result, {TNode, {Result, broadcast}}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_purge_node(Host, Node, NodeId, Type, Options), - unset_cached_item(Host, NodeId), - case Result of - default -> {result, Reply}; - _ -> {result, Result} - end; - {result, {_, default}} -> - {result, Reply}; - {result, {_, Result}} -> - {result, Result}; - Error -> - Error + {result, {TNode, {Result, broadcast}}} -> + NodeId = TNode#pubsub_node.id, + Type = TNode#pubsub_node.type, + Options = TNode#pubsub_node.options, + broadcast_purge_node(Host, Node, NodeId, Type, Options), + unset_cached_item(Host, NodeId), + case Result of + default -> {result, Reply}; + _ -> {result, Result} + end; + {result, {_, default}} -> {result, Reply}; + {result, {_, Result}} -> {result, Result}; + Error -> Error end. %% @doc <p>Return the items of a given node.</p> @@ -2258,82 +3208,108 @@ purge_node(Host, Node, Owner) -> %% <p>The permission are not checked in this function.</p> %% @todo We probably need to check that the user doing the query has the right %% to read the items. +-spec(get_items/6 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid(), + SubId :: mod_pubsub:subId(), + SMaxItems :: binary(), + ItemIDs :: [mod_pubsub:itemId()]) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) -> - MaxItems = - if - SMaxItems == "" -> get_max_items_node(Host); - true -> - case catch list_to_integer(SMaxItems) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - Val -> Val - end - end, + MaxItems = if SMaxItems == <<"">> -> + get_max_items_node(Host); + true -> + case catch jlib:binary_to_integer(SMaxItems) of + {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; + Val -> Val + end + end, case MaxItems of - {error, Error} -> - {error, Error}; - _ -> - Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId, owners = Owners}) -> - Features = features(Type), - RetreiveFeature = lists:member("retrieve-items", Features), - PersistentFeature = lists:member("persistent-items", Features), - AccessModel = get_option(Options, access_model), - AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), - if - not RetreiveFeature -> - %% Item Retrieval Not Supported - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-items")}; - not PersistentFeature -> - %% Persistent Items Not Supported - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "persistent-items")}; - true -> - node_call(Type, get_items, - [NodeId, From, - AccessModel, PresenceSubscription, RosterGroup, - SubId]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> - SendItems = case ItemIDs of - [] -> - Items; - _ -> - lists:filter(fun(#pubsub_item{itemid = {ItemId, _}}) -> - lists:member(ItemId, ItemIDs) - end, Items) - end, - %% Generate the XML response (Item list), limiting the - %% number of items sent to MaxItems: - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "items", nodeAttr(Node), - itemsEls(lists:sublist(SendItems, MaxItems))}]}]}; - Error -> - Error - end + {error, Error} -> {error, Error}; + _ -> + Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId, + owners = Owners}) -> + Features = features(Type), + RetreiveFeature = lists:member(<<"retrieve-items">>, Features), + PersistentFeature = lists:member(<<"persistent-items">>, Features), + AccessModel = get_option(Options, access_model), + AllowedGroups = get_option(Options, roster_groups_allowed, []), + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, From, Owners, + AccessModel, AllowedGroups), + if not RetreiveFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"retrieve-items">>)}; + not PersistentFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"persistent-items">>)}; + true -> + node_call(Type, get_items, + [NodeId, From, AccessModel, + PresenceSubscription, RosterGroup, + SubId]) + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Items}} -> + SendItems = case ItemIDs of + [] -> Items; + _ -> + lists:filter(fun (#pubsub_item{itemid = + {ItemId, + _}}) -> + lists:member(ItemId, + ItemIDs) + end, + Items) + end, + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = + itemsEls(lists:sublist(SendItems, + MaxItems))}]}]}; + Error -> Error + end end. + get_items(Host, Node) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, get_items, [NodeId, service_jid(Host)]) - end, + Action = fun (#pubsub_node{type = Type, id = NodeId}) -> + node_call(Type, get_items, [NodeId, service_jid(Host)]) + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> Items; - Error -> Error + {result, {_, Items}} -> Items; + Error -> Error end. + get_item(Host, Node, ItemId) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> - node_call(Type, get_item, [NodeId, ItemId]) - end, + Action = fun (#pubsub_node{type = Type, id = NodeId}) -> + node_call(Type, get_item, [NodeId, ItemId]) + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Items}} -> Items; - Error -> Error + {result, {_, Items}} -> Items; + Error -> Error end. + get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> AccessModel = get_option(Options, access_model), AllowedGroups = get_option(Options, roster_groups_allowed, []), - {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), - node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]). - + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, From, Owners, AccessModel, + AllowedGroups), + node_call(Type, get_items, + [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]). %% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() %% Host = pubsubHost() @@ -2344,68 +3320,63 @@ get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> %% Number = last | integer() %% @doc <p>Resend the items of a node to the user.</p> %% @todo use cache-last-item feature -send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, last) -> +send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) -> case get_cached_item(Host, NodeId) of - undefined -> - send_items(Host, Node, NodeId, Type, LJID, 1); - LastItem -> - {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, - Stanza = event_stanza_with_delay( - [{xmlelement, "items", nodeAttr(Node), - itemsEls([LastItem])}], ModifNow, ModifUSR), - case is_tuple(Host) of - false -> - ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza); - true -> - case ejabberd_sm:get_session_pid(U,S,R) of - C2SPid when is_pid(C2SPid) -> - ejabberd_c2s:broadcast(C2SPid, - {pep_message, binary_to_list(Node)++"+notify"}, - _Sender = service_jid(Host), - Stanza); - _ -> - ok - end - end + undefined -> + send_items(Host, Node, NodeId, Type, LJID, 1); + LastItem -> + {ModifNow, ModifUSR} = + LastItem#pubsub_item.modification, + Stanza = event_stanza_with_delay([#xmlel{name = + <<"items">>, + attrs = nodeAttr(Node), + children = + itemsEls([LastItem])}], + ModifNow, ModifUSR), + case is_tuple(Host) of + false -> + ejabberd_router:route(service_jid(Host), + jlib:make_jid(LJID), Stanza); + true -> + case ejabberd_sm:get_session_pid(U, S, R) of + C2SPid when is_pid(C2SPid) -> + ejabberd_c2s:broadcast(C2SPid, + {pep_message, + <<((Node))/binary, "+notify">>}, + _Sender = service_jid(Host), + Stanza); + _ -> ok + end + end end; -send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, Number) -> - ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of - {result, []} -> - []; - {result, Items} -> - case Number of - N when N > 0 -> lists:sublist(Items, N); - _ -> Items - end; - _ -> - [] - end, +send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, + Number) -> + ToSend = case node_action(Host, Type, get_items, + [NodeId, LJID]) + of + {result, []} -> []; + {result, Items} -> + case Number of + N when N > 0 -> lists:sublist(Items, N); + _ -> Items + end; + _ -> [] + end, Stanza = case ToSend of - [LastItem] -> - {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, - event_stanza_with_delay( - [{xmlelement, "items", nodeAttr(Node), - itemsEls(ToSend)}], ModifNow, ModifUSR); - _ -> - event_stanza( - [{xmlelement, "items", nodeAttr(Node), - itemsEls(ToSend)}]) - end, + [LastItem] -> + {ModifNow, ModifUSR} = + LastItem#pubsub_item.modification, + event_stanza_with_delay([#xmlel{name = <<"items">>, + attrs = nodeAttr(Node), + children = + itemsEls(ToSend)}], + ModifNow, ModifUSR); + _ -> + event_stanza([#xmlel{name = <<"items">>, + attrs = nodeAttr(Node), + children = itemsEls(ToSend)}]) + end, case is_tuple(Host) of - false -> - ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza); - true -> - case ejabberd_sm:get_session_pid(U,S,R) of - C2SPid when is_pid(C2SPid) -> - ejabberd_c2s:broadcast(C2SPid, - {pep_message, binary_to_list(Node)++"+notify"}, - _Sender = service_jid(Host), - Stanza); - _ -> - ok - end - end. - %% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} %% Host = host() %% JID = jid() @@ -2413,275 +3384,388 @@ send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, Number) -> %% Reason = stanzaError() %% Response = [pubsubIQResponse()] %% @doc <p>Return the list of affiliations as an XMPP response.</p> -get_affiliations(Host, <<>>, JID, Plugins) when is_list(Plugins) -> - Result = lists:foldl( - fun(Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = lists:member("retrieve-affiliations", Features), - if - not RetrieveFeature -> - %% Service does not support retreive affiliatons - {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc}; - true -> - {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, JID]), - {Status, [Affiliations|Acc]} - end - end, {ok, []}, Plugins), + false -> + ejabberd_router:route(service_jid(Host), + jlib:make_jid(LJID), Stanza); + true -> + case ejabberd_sm:get_session_pid(U, S, R) of + C2SPid when is_pid(C2SPid) -> + ejabberd_c2s:broadcast(C2SPid, + {pep_message, + <<((Node))/binary, "+notify">>}, + _Sender = service_jid(Host), Stanza); + _ -> ok + end + end. + +-spec(get_affiliations/4 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + JID :: jid(), + Plugins :: [binary()]) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). +get_affiliations(Host, <<>>, JID, Plugins) + when is_list(Plugins) -> + Result = lists:foldl(fun (Type, {Status, Acc}) -> + Features = features(Type), + RetrieveFeature = + lists:member(<<"retrieve-affiliations">>, Features), + if not RetrieveFeature -> + {{error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"retrieve-affiliations">>)}, + Acc}; + true -> + {result, Affiliations} = + node_action(Host, Type, + get_entity_affiliations, + [Host, JID]), + {Status, [Affiliations | Acc]} + end + end, + {ok, []}, Plugins), case Result of - {ok, Affiliations} -> - Entities = lists:flatmap( - fun({_, none}) -> []; - ({#pubsub_node{nodeid = {_, Node}}, Affiliation}) -> - [{xmlelement, "affiliation", - [{"affiliation", affiliation_to_string(Affiliation)}|nodeAttr(Node)], - []}] - end, lists:usort(lists:flatten(Affiliations))), - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "affiliations", [], - Entities}]}]}; - {Error, _} -> - Error + {ok, Affiliations} -> + Entities = lists:flatmap(fun ({_, none}) -> []; + ({#pubsub_node{nodeid = {_, Node}}, + Affiliation}) -> + [#xmlel{name = <<"affiliation">>, + attrs = + [{<<"affiliation">>, + affiliation_to_string(Affiliation)} + | nodeAttr(Node)], + children = []}] + end, + lists:usort(lists:flatten(Affiliations))), + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"affiliations">>, attrs = [], + children = Entities}]}]}; + {Error, _} -> Error end; -get_affiliations(Host, NodeId, JID, Plugins) when is_list(Plugins) -> - Result = lists:foldl( - fun(Type, {Status, Acc}) -> - Features = features(Type), - RetrieveFeature = lists:member("retrieve-affiliations", Features), - if - not RetrieveFeature -> - %% Service does not support retreive affiliatons - {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-affiliations")}, Acc}; - true -> - {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, JID]), - {Status, [Affiliations|Acc]} - end - end, {ok, []}, Plugins), +get_affiliations(Host, NodeId, JID, Plugins) + when is_list(Plugins) -> + Result = lists:foldl(fun (Type, {Status, Acc}) -> + Features = features(Type), + RetrieveFeature = + lists:member(<<"retrieve-affiliations">>, + Features), + if not RetrieveFeature -> + {{error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"retrieve-affiliations">>)}, + Acc}; + true -> + {result, Affiliations} = + node_action(Host, Type, + get_entity_affiliations, + [Host, JID]), + {Status, [Affiliations | Acc]} + end + end, + {ok, []}, Plugins), case Result of - {ok, Affiliations} -> - Entities = lists:flatmap( - fun({_, none}) -> []; - ({#pubsub_node{nodeid = {_, Node}}, Affiliation}) - when NodeId == Node -> - [{xmlelement, "affiliation", - [{"affiliation", affiliation_to_string(Affiliation)}|nodeAttr(Node)], - []}]; - (_) -> - [] - end, lists:usort(lists:flatten(Affiliations))), - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "affiliations", [], - Entities}]}]}; - {Error, _} -> - Error + {ok, Affiliations} -> + Entities = lists:flatmap(fun ({_, none}) -> []; + ({#pubsub_node{nodeid = {_, Node}}, + Affiliation}) + when NodeId == Node -> + [#xmlel{name = <<"affiliation">>, + attrs = + [{<<"affiliation">>, + affiliation_to_string(Affiliation)} + | nodeAttr(Node)], + children = []}]; + (_) -> [] + end, + lists:usort(lists:flatten(Affiliations))), + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"affiliations">>, attrs = [], + children = Entities}]}]}; + {Error, _} -> Error end. - +-spec(get_affiliations/3 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + JID :: jid()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). get_affiliations(Host, Node, JID) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> + Action = fun (#pubsub_node{type = Type, id = NodeId}) -> Features = features(Type), - RetrieveFeature = lists:member("modify-affiliations", Features), - {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]), - if - not RetrieveFeature -> - %% Service does not support modify affiliations - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "modify-affiliations")}; - Affiliation /= owner -> - %% Entity is not an owner - {error, ?ERR_FORBIDDEN}; - true -> - node_call(Type, get_node_affiliations, [NodeId]) + RetrieveFeature = + lists:member(<<"modify-affiliations">>, Features), + {result, Affiliation} = node_call(Type, get_affiliation, + [NodeId, JID]), + if not RetrieveFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"modify-affiliations">>)}; + Affiliation /= owner -> {error, ?ERR_FORBIDDEN}; + true -> node_call(Type, get_node_affiliations, [NodeId]) end end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, []}} -> - {error, ?ERR_ITEM_NOT_FOUND}; - {result, {_, Affiliations}} -> - Entities = lists:flatmap( - fun({_, none}) -> []; - ({AJID, Affiliation}) -> - [{xmlelement, "affiliation", - [{"jid", jlib:jid_to_string(AJID)}, - {"affiliation", affiliation_to_string(Affiliation)}], - []}] - end, Affiliations), - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}], - [{xmlelement, "affiliations", nodeAttr(Node), - Entities}]}]}; - Error -> - Error + {result, {_, []}} -> {error, ?ERR_ITEM_NOT_FOUND}; + {result, {_, Affiliations}} -> + Entities = lists:flatmap(fun ({_, none}) -> []; + ({AJID, Affiliation}) -> + [#xmlel{name = <<"affiliation">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(AJID)}, + {<<"affiliation">>, + affiliation_to_string(Affiliation)}], + children = []}] + end, + Affiliations), + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], + children = + [#xmlel{name = <<"affiliations">>, + attrs = nodeAttr(Node), children = Entities}]}]}; + Error -> Error end. +-spec(set_affiliations/4 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid(), + EntitiesEls :: [xmlel()]) + -> {result, []} + %%% + | {error, xmlel()} +). set_affiliations(Host, Node, From, EntitiesEls) -> Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)), - Entities = - lists:foldl( - fun(El, Acc) -> - case Acc of - error -> - error; - _ -> - case El of - {xmlelement, "affiliation", Attrs, _} -> - JID = jlib:string_to_jid( - xml:get_attr_s("jid", Attrs)), - Affiliation = string_to_affiliation( - xml:get_attr_s("affiliation", Attrs)), - if - (JID == error) or - (Affiliation == false) -> - error; - true -> - [{jlib:jid_tolower(JID), Affiliation} | Acc] - end - end - end - end, [], EntitiesEls), + Entities = lists:foldl(fun (El, Acc) -> + case Acc of + error -> error; + _ -> + case El of + #xmlel{name = <<"affiliation">>, + attrs = Attrs} -> + JID = + jlib:string_to_jid(xml:get_attr_s(<<"jid">>, + Attrs)), + Affiliation = + string_to_affiliation(xml:get_attr_s(<<"affiliation">>, + Attrs)), + if (JID == error) or + (Affiliation == false) -> + error; + true -> + [{jlib:jid_tolower(JID), + Affiliation} + | Acc] + end + end + end + end, + [], EntitiesEls), case Entities of - error -> - {error, ?ERR_BAD_REQUEST}; - _ -> - Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) -> - case lists:member(Owner, Owners) of - true -> - OwnerJID = jlib:make_jid(Owner), - FilteredEntities = case Owners of - [Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID]; - _ -> Entities - end, - lists:foreach( - fun({JID, Affiliation}) -> - node_call(Type, set_affiliation, [NodeId, JID, Affiliation]), - case Affiliation of - owner -> - NewOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)), - NewOwners = [NewOwner|Owners], - tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]); - none -> - OldOwner = jlib:jid_tolower(jlib:jid_remove_resource(JID)), - case lists:member(OldOwner, Owners) of - true -> - NewOwners = Owners--[OldOwner], - tree_call(Host, set_node, [N#pubsub_node{owners = NewOwners}]); - _ -> - ok - end; - _ -> - ok - end - end, FilteredEntities), - {result, []}; - _ -> - {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end + error -> {error, ?ERR_BAD_REQUEST}; + _ -> + Action = fun (#pubsub_node{owners = Owners, type = Type, + id = NodeId} = + N) -> + case lists:member(Owner, Owners) of + true -> + OwnerJID = jlib:make_jid(Owner), + FilteredEntities = case Owners of + [Owner] -> + [E + || E <- Entities, + element(1, E) =/= + OwnerJID]; + _ -> Entities + end, + lists:foreach(fun ({JID, Affiliation}) -> + node_call(Type, + set_affiliation, + [NodeId, JID, + Affiliation]), + case Affiliation of + owner -> + NewOwner = + jlib:jid_tolower(jlib:jid_remove_resource(JID)), + NewOwners = + [NewOwner + | Owners], + tree_call(Host, + set_node, + [N#pubsub_node{owners + = + NewOwners}]); + none -> + OldOwner = + jlib:jid_tolower(jlib:jid_remove_resource(JID)), + case + lists:member(OldOwner, + Owners) + of + true -> + NewOwners = + Owners -- + [OldOwner], + tree_call(Host, + set_node, + [N#pubsub_node{owners + = + NewOwners}]); + _ -> ok + end; + _ -> ok + end + end, + FilteredEntities), + {result, []}; + _ -> {error, ?ERR_FORBIDDEN} + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Result}} -> {result, Result}; + Other -> Other + end end. get_options(Host, Node, JID, SubID, Lang) -> - Action = fun(#pubsub_node{type = Type, id = NodeID}) -> - case lists:member("subscription-options", features(Type)) of - true -> - get_options_helper(JID, Lang, Node, NodeID, SubID, Type); - false -> - {error, extended_error( - ?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, "subscription-options")} + Action = fun (#pubsub_node{type = Type, id = NodeID}) -> + case lists:member(<<"subscription-options">>, features(Type)) of + true -> + get_options_helper(JID, Lang, Node, NodeID, SubID, Type); + false -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"subscription-options">>)} end end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, XForm}} -> {result, [XForm]}; - Error -> Error + {result, {_Node, XForm}} -> {result, [XForm]}; + Error -> Error end. get_options_helper(JID, Lang, Node, NodeID, SubID, Type) -> Subscriber = case jlib:string_to_jid(JID) of - error -> {"", "", ""}; - J -> jlib:jid_tolower(J) + error -> {<<"">>, <<"">>, <<"">>}; + J -> case jlib:jid_tolower(J) of + error -> {<<"">>, <<"">>, <<"">>}; + J1 -> J1 + end end, {result, Subs} = node_call(Type, get_subscriptions, [NodeID, Subscriber]), - SubIDs = lists:foldl(fun({subscribed, SID}, Acc) -> + SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) -> [SID | Acc]; - (_, Acc) -> - Acc - end, [], Subs), + (_, Acc) -> Acc + end, + [], Subs), case {SubID, SubIDs} of - {_, []} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "not-subscribed")}; - {[], [SID]} -> - read_sub(Subscriber, Node, NodeID, SID, Lang); - {[], _} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "subid-required")}; - {_, _} -> - read_sub(Subscriber, Node, NodeID, SubID, Lang) + {_, []} -> + {error, + extended_error(?ERR_NOT_ACCEPTABLE, <<"not-subscribed">>)}; + {<<>>, [SID]} -> + read_sub(Subscriber, Node, NodeID, SID, Lang); + {<<>>, _} -> + {error, + extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)}; + {_, _} -> + read_sub(Subscriber, Node, NodeID, SubID, Lang) end. read_sub(Subscriber, Node, NodeID, SubID, Lang) -> case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; {result, #pubsub_subscription{options = Options}} -> {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options), - OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, - {"subid", SubID}|nodeAttr(Node)], - [XdataEl]}, - PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]}, + OptionsEl = #xmlel{name = <<"options">>, + attrs = + [{<<"jid">>, jlib:jid_to_string(Subscriber)}, + {<<"subid">>, SubID} + | nodeAttr(Node)], + children = [XdataEl]}, + PubsubEl = #xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = [OptionsEl]}, {result, PubsubEl} end. set_options(Host, Node, JID, SubID, Configuration) -> - Action = fun(#pubsub_node{type = Type, id = NodeID}) -> - case lists:member("subscription-options", features(Type)) of - true -> - set_options_helper(Configuration, JID, NodeID, - SubID, Type); - false -> - {error, extended_error( - ?ERR_FEATURE_NOT_IMPLEMENTED, - unsupported, "subscription-options")} + Action = fun (#pubsub_node{type = Type, id = NodeID}) -> + case lists:member(<<"subscription-options">>, + features(Type)) + of + true -> + set_options_helper(Configuration, JID, NodeID, SubID, + Type); + false -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"subscription-options">>)} end end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_Node, Result}} -> {result, Result}; - Error -> Error + {result, {_Node, Result}} -> {result, Result}; + Error -> Error end. set_options_helper(Configuration, JID, NodeID, SubID, Type) -> SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, + {result, GoodSubOpts} -> GoodSubOpts; + _ -> invalid + end, Subscriber = case jlib:string_to_jid(JID) of - error -> {"", "", ""}; - J -> jlib:jid_tolower(J) + error -> {<<"">>, <<"">>, <<"">>}; + J -> jlib:jid_tolower(J) end, {result, Subs} = node_call(Type, get_subscriptions, [NodeID, Subscriber]), - SubIDs = lists:foldl(fun({subscribed, SID}, Acc) -> + SubIDs = lists:foldl(fun ({subscribed, SID}, Acc) -> [SID | Acc]; - (_, Acc) -> - Acc - end, [], Subs), + (_, Acc) -> Acc + end, + [], Subs), case {SubID, SubIDs} of - {_, []} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "not-subscribed")}; - {[], [SID]} -> - write_sub(Subscriber, NodeID, SID, SubOpts); - {[], _} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "subid-required")}; - {_, _} -> - write_sub(Subscriber, NodeID, SubID, SubOpts) + {_, []} -> + {error, + extended_error(?ERR_NOT_ACCEPTABLE, + <<"not-subscribed">>)}; + {<<>>, [SID]} -> + write_sub(Subscriber, NodeID, SID, SubOpts); + {<<>>, _} -> + {error, + extended_error(?ERR_NOT_ACCEPTABLE, + <<"subid-required">>)}; + {_, _} -> write_sub(Subscriber, NodeID, SubID, SubOpts) end. write_sub(_Subscriber, _NodeID, _SubID, invalid) -> - {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")}; + {error, extended_error(?ERR_BAD_REQUEST, <<"invalid-options">>)}; write_sub(Subscriber, NodeID, SubID, Options) -> case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + {error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}; {result, _} -> {result, []} end. @@ -2698,11 +3782,11 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> Result = lists:foldl( fun(Type, {Status, Acc}) -> Features = features(Type), - RetrieveFeature = lists:member("retrieve-subscriptions", Features), + RetrieveFeature = lists:member(<<"retrieve-subscriptions">>, Features), if not RetrieveFeature -> %% Service does not support retreive subscriptions - {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-subscriptions")}, Acc}; + {{error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, <<"retrieve-subscriptions">>)}, Acc}; true -> Subscriber = jlib:jid_remove_resource(JID), {result, Subscriptions} = node_action(Host, Type, get_entity_subscriptions, [Host, Subscriber]), @@ -2710,256 +3794,337 @@ get_subscriptions(Host, Node, JID, Plugins) when is_list(Plugins) -> end end, {ok, []}, Plugins), case Result of - {ok, Subscriptions} -> - Entities = lists:flatmap( - fun({_, none}) -> - []; - ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription}) -> - case Node of - <<>> -> - [{xmlelement, "subscription", - [{"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)], - []}]; - SubsNode -> - [{xmlelement, "subscription", - [{"subscription", subscription_to_string(Subscription)}], - []}]; - _ -> - [] - end; - ({_, none, _}) -> - []; - ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubID, SubJID}) -> - case Node of - <<>> -> - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(SubJID)}, - {"subid", SubID}, - {"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)], - []}]; - SubsNode -> - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(SubJID)}, - {"subid", SubID}, - {"subscription", subscription_to_string(Subscription)}], - []}]; - _ -> - [] - end; - ({#pubsub_node{nodeid = {_, SubsNode}}, Subscription, SubJID}) -> - case Node of - <<>> -> - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(SubJID)}, - {"subscription", subscription_to_string(Subscription)}|nodeAttr(SubsNode)], - []}]; - SubsNode -> - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(SubJID)}, - {"subscription", subscription_to_string(Subscription)}], - []}]; - _ -> - [] - end - end, lists:usort(lists:flatten(Subscriptions))), - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "subscriptions", [], - Entities}]}]}; - {Error, _} -> - Error + {ok, Subscriptions} -> + Entities = lists:flatmap(fun ({_, none}) -> []; + ({#pubsub_node{nodeid = {_, SubsNode}}, + Subscription}) -> + case Node of + <<>> -> + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"subscription">>, + subscription_to_string(Subscription)} + | nodeAttr(SubsNode)], + children = []}]; + SubsNode -> + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"subscription">>, + subscription_to_string(Subscription)}], + children = []}]; + _ -> [] + end; + ({_, none, _}) -> []; + ({#pubsub_node{nodeid = {_, SubsNode}}, + Subscription, SubID, SubJID}) -> + case Node of + <<>> -> + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(SubJID)}, + {<<"subid">>, + SubID}, + {<<"subscription">>, + subscription_to_string(Subscription)} + | nodeAttr(SubsNode)], + children = []}]; + SubsNode -> + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(SubJID)}, + {<<"subid">>, + SubID}, + {<<"subscription">>, + subscription_to_string(Subscription)}], + children = []}]; + _ -> [] + end; + ({#pubsub_node{nodeid = {_, SubsNode}}, + Subscription, SubJID}) -> + case Node of + <<>> -> + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(SubJID)}, + {<<"subscription">>, + subscription_to_string(Subscription)} + | nodeAttr(SubsNode)], + children = []}]; + SubsNode -> + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(SubJID)}, + {<<"subscription">>, + subscription_to_string(Subscription)}], + children = []}]; + _ -> [] + end + end, + lists:usort(lists:flatten(Subscriptions))), + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB}], + children = + [#xmlel{name = <<"subscriptions">>, attrs = [], + children = Entities}]}]}; + {Error, _} -> Error end. + get_subscriptions(Host, Node, JID) -> - Action = fun(#pubsub_node{type = Type, id = NodeId}) -> + Action = fun (#pubsub_node{type = Type, id = NodeId}) -> Features = features(Type), - RetrieveFeature = lists:member("manage-subscriptions", Features), - {result, Affiliation} = node_call(Type, get_affiliation, [NodeId, JID]), - if - not RetrieveFeature -> - %% Service does not support manage subscriptions - {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "manage-subscriptions")}; - Affiliation /= owner -> - %% Entity is not an owner - {error, ?ERR_FORBIDDEN}; - true -> - node_call(Type, get_node_subscriptions, [NodeId]) + RetrieveFeature = + lists:member(<<"manage-subscriptions">>, Features), + {result, Affiliation} = node_call(Type, get_affiliation, + [NodeId, JID]), + if not RetrieveFeature -> + {error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"manage-subscriptions">>)}; + Affiliation /= owner -> {error, ?ERR_FORBIDDEN}; + true -> + node_call(Type, get_node_subscriptions, [NodeId]) end end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Subscriptions}} -> - Entities = lists:flatmap( - fun({_, none}) -> []; - ({_, pending, _}) -> []; - ({AJID, Subscription}) -> - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(AJID)}, - {"subscription", subscription_to_string(Subscription)}], - []}]; - ({AJID, Subscription, SubId}) -> - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(AJID)}, - {"subscription", subscription_to_string(Subscription)}, - {"subid", SubId}], - []}] - end, Subscriptions), - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}], - [{xmlelement, "subscriptions", nodeAttr(Node), - Entities}]}]}; - Error -> - Error + {result, {_, Subscriptions}} -> + Entities = lists:flatmap(fun ({_, none}) -> []; + ({_, pending, _}) -> []; + ({AJID, Subscription}) -> + [#xmlel{name = <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(AJID)}, + {<<"subscription">>, + subscription_to_string(Subscription)}], + children = []}]; + ({AJID, Subscription, SubId}) -> + [#xmlel{name = <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(AJID)}, + {<<"subscription">>, + subscription_to_string(Subscription)}, + {<<"subid">>, SubId}], + children = []}] + end, + Subscriptions), + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], + children = + [#xmlel{name = <<"subscriptions">>, + attrs = nodeAttr(Node), children = Entities}]}]}; + Error -> Error end. set_subscriptions(Host, Node, From, EntitiesEls) -> - Owner = jlib:jid_tolower(jlib:jid_remove_resource(From)), - Entities = - lists:foldl( - fun(El, Acc) -> - case Acc of - error -> - error; - _ -> - case El of - {xmlelement, "subscription", Attrs, _} -> - JID = jlib:string_to_jid( - xml:get_attr_s("jid", Attrs)), - Subscription = string_to_subscription( - xml:get_attr_s("subscription", Attrs)), - SubId = xml:get_attr_s("subid", Attrs), - if - (JID == error) or - (Subscription == false) -> - error; - true -> - [{jlib:jid_tolower(JID), Subscription, SubId} | Acc] - end - end - end - end, [], EntitiesEls), + Owner = + jlib:jid_tolower(jlib:jid_remove_resource(From)), + Entities = lists:foldl(fun (El, Acc) -> + case Acc of + error -> error; + _ -> + case El of + #xmlel{name = <<"subscription">>, + attrs = Attrs} -> + JID = + jlib:string_to_jid(xml:get_attr_s(<<"jid">>, + Attrs)), + Subscription = + string_to_subscription(xml:get_attr_s(<<"subscription">>, + Attrs)), + SubId = + xml:get_attr_s(<<"subid">>, + Attrs), + if (JID == error) or + (Subscription == false) -> + error; + true -> + [{jlib:jid_tolower(JID), + Subscription, SubId} + | Acc] + end + end + end + end, + [], EntitiesEls), case Entities of - error -> - {error, ?ERR_BAD_REQUEST}; - _ -> - Notify = fun(JID, Sub, _SubId) -> - Stanza = {xmlelement, "message", [], - [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "subscription", - [{"jid", jlib:jid_to_string(JID)}, - %{"subid", SubId}, - {"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]}, - ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza) - end, - Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) -> - case lists:member(Owner, Owners) of - true -> - Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) -> - - case node_call(Type, set_subscriptions, [NodeId, JID, Subscription, SubId]) of - {error, Err} -> [{error, Err} | Acc]; - _ -> Notify(JID, Subscription, SubId), Acc - end - end, [], Entities), - case Result of - [] -> {result, []}; - _ -> {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other - end + error -> {error, ?ERR_BAD_REQUEST}; + _ -> + Notify = fun (JID, Sub, _SubId) -> + Stanza = #xmlel{name = <<"message">>, attrs = [], + children = + [#xmlel{name = <<"pubsub">>, + attrs = + [{<<"xmlns">>, + ?NS_PUBSUB}], + children = + [#xmlel{name = + <<"subscription">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(JID)}, + {<<"subscription">>, + subscription_to_string(Sub)} + | nodeAttr(Node)], + children = + []}]}]}, + ejabberd_router:route(service_jid(Host), + jlib:make_jid(JID), Stanza) + end, + Action = fun (#pubsub_node{owners = Owners, type = Type, + id = NodeId}) -> + case lists:member(Owner, Owners) of + true -> + Result = lists:foldl(fun ({JID, Subscription, + SubId}, + Acc) -> + case + node_call(Type, + set_subscriptions, + [NodeId, + JID, + Subscription, + SubId]) + of + {error, Err} -> + [{error, + Err} + | Acc]; + _ -> + Notify(JID, + Subscription, + SubId), + Acc + end + end, + [], Entities), + case Result of + [] -> {result, []}; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_FORBIDDEN} + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Result}} -> {result, Result}; + Other -> Other + end end. +-spec(get_presence_and_roster_permissions/5 :: +( + Host :: mod_pubsub:host(), + From :: ljid(), + Owners :: [ljid(),...], + AccessModel :: mod_pubsub:accessModel(), + AllowedGroups :: [binary()]) + -> {PresenceSubscription::boolean(), RosterGroup::boolean()} +). get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) -> if (AccessModel == presence) or (AccessModel == roster) -> - case Host of - {User, Server, _} -> - get_roster_info(User, Server, From, AllowedGroups); - _ -> - [{OUser, OServer, _}|_] = Owners, - get_roster_info(OUser, OServer, From, AllowedGroups) - end; - true -> - {true, true} + case Host of + {User, Server, _} -> + get_roster_info(User, Server, From, AllowedGroups); + _ -> + [{OUser, OServer, _} | _] = Owners, + get_roster_info(OUser, OServer, From, AllowedGroups) + end; + true -> {true, true} end. %% @spec (OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, SubscriberResource}, AllowedGroups) %% -> {PresenceSubscription, RosterGroup} -get_roster_info(_, _, {"", "", _}, _) -> +get_roster_info(_, _, {<<"">>, <<"">>, _}, _) -> {false, false}; -get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) -> +get_roster_info(OwnerUser, OwnerServer, + {SubscriberUser, SubscriberServer, _}, AllowedGroups) -> {Subscription, Groups} = - ejabberd_hooks:run_fold( - roster_get_jid_info, OwnerServer, - {none, []}, - [OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, ""}]), - PresenceSubscription = (Subscription == both) orelse (Subscription == from) - orelse ({OwnerUser, OwnerServer} == {SubscriberUser, SubscriberServer}), - RosterGroup = lists:any(fun(Group) -> + ejabberd_hooks:run_fold(roster_get_jid_info, + OwnerServer, {none, []}, + [OwnerUser, OwnerServer, + {SubscriberUser, SubscriberServer, <<"">>}]), + PresenceSubscription = Subscription == both orelse + Subscription == from orelse + {OwnerUser, OwnerServer} == + {SubscriberUser, SubscriberServer}, + RosterGroup = lists:any(fun (Group) -> lists:member(Group, AllowedGroups) - end, Groups), + end, + Groups), {PresenceSubscription, RosterGroup}; -get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) -> - get_roster_info(OwnerUser, OwnerServer, jlib:jid_tolower(JID), AllowedGroups). - %% @spec (AffiliationStr) -> Affiliation %% AffiliationStr = string() %% Affiliation = atom() %% @doc <p>Convert an affiliation type from string to atom.</p> -string_to_affiliation("owner") -> owner; -string_to_affiliation("publisher") -> publisher; -string_to_affiliation("member") -> member; -string_to_affiliation("outcast") -> outcast; -string_to_affiliation("none") -> none; +get_roster_info(OwnerUser, OwnerServer, JID, + AllowedGroups) -> + get_roster_info(OwnerUser, OwnerServer, + jlib:jid_tolower(JID), AllowedGroups). + +string_to_affiliation(<<"owner">>) -> owner; +string_to_affiliation(<<"publisher">>) -> publisher; +string_to_affiliation(<<"member">>) -> member; +string_to_affiliation(<<"outcast">>) -> outcast; +string_to_affiliation(<<"none">>) -> none; string_to_affiliation(_) -> false. %% @spec (SubscriptionStr) -> Subscription %% SubscriptionStr = string() %% Subscription = atom() %% @doc <p>Convert a subscription type from string to atom.</p> -string_to_subscription("subscribed") -> subscribed; -string_to_subscription("pending") -> pending; -string_to_subscription("unconfigured") -> unconfigured; -string_to_subscription("none") -> none; +string_to_subscription(<<"subscribed">>) -> subscribed; +string_to_subscription(<<"pending">>) -> pending; +string_to_subscription(<<"unconfigured">>) -> + unconfigured; +string_to_subscription(<<"none">>) -> none; string_to_subscription(_) -> false. %% @spec (Affiliation) -> AffiliationStr %% Affiliation = atom() %% AffiliationStr = string() %% @doc <p>Convert an affiliation type from atom to string.</p> -affiliation_to_string(owner) -> "owner"; -affiliation_to_string(publisher) -> "publisher"; -affiliation_to_string(member) -> "member"; -affiliation_to_string(outcast) -> "outcast"; -affiliation_to_string(_) -> "none". - %% @spec (Subscription) -> SubscriptionStr %% Subscription = atom() %% SubscriptionStr = string() %% @doc <p>Convert a subscription type from atom to string.</p> -subscription_to_string(subscribed) -> "subscribed"; -subscription_to_string(pending) -> "pending"; -subscription_to_string(unconfigured) -> "unconfigured"; -subscription_to_string(_) -> "none". - %% @spec (Node) -> NodeStr %% Node = pubsubNode() %% NodeStr = string() %% @doc <p>Convert a node type from pubsubNode to string.</p> -node_to_string(Node) -> binary_to_list(Node). -string_to_node(SNode) -> list_to_binary(SNode). - %% @spec (Host) -> jid() %% Host = host() %% @doc <p>Generate pubsub service JID.</p> +affiliation_to_string(owner) -> <<"owner">>; +affiliation_to_string(publisher) -> <<"publisher">>; +affiliation_to_string(member) -> <<"member">>; +affiliation_to_string(outcast) -> <<"outcast">>; +affiliation_to_string(_) -> <<"none">>. + +subscription_to_string(subscribed) -> <<"subscribed">>; +subscription_to_string(pending) -> <<"pending">>; +subscription_to_string(unconfigured) -> <<"unconfigured">>; +subscription_to_string(_) -> <<"none">>. + +-spec(service_jid/1 :: +( + Host :: mod_pubsub:host()) + -> jid() +). service_jid(Host) -> - case Host of - {U,S,_} -> {jid, U, S, "", U, S, ""}; - _ -> {jid, "", Host, "", "", Host, ""} - end. - %% @spec (LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean() %% LJID = jid() %% NotifyType = items | nodes @@ -2968,14 +4133,21 @@ service_jid(Host) -> %% SubOptions = [{atom(), term()}] %% @doc <p>Check if a notification must be delivered or not based on %% node and subscription options.</p> -is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) -> + case Host of + {U, S, _} -> {jid, U, S, <<"">>, U, S, <<"">>}; + _ -> {jid, <<"">>, Host, <<"">>, <<"">>, Host, <<"">>} + end. + +is_to_deliver(LJID, NotifyType, Depth, NodeOptions, + SubOptions) -> sub_to_deliver(LJID, NotifyType, Depth, SubOptions) - andalso node_to_deliver(LJID, NodeOptions). + andalso node_to_deliver(LJID, NodeOptions). sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) -> lists:all(fun (Option) -> sub_option_can_deliver(NotifyType, Depth, Option) - end, SubOptions). + end, + SubOptions). sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false; sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false; @@ -2989,6 +4161,12 @@ node_to_deliver(LJID, NodeOptions) -> PresenceDelivery = get_option(NodeOptions, presence_based_delivery), presence_can_deliver(LJID, PresenceDelivery). +-spec(presence_can_deliver/2 :: +( + Entity :: ljid(), + _ :: boolean()) + -> boolean() +). presence_can_deliver(_, false) -> true; presence_can_deliver({User, Server, Resource}, true) -> case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of @@ -3005,6 +4183,12 @@ presence_can_deliver({User, Server, Resource}, true) -> end, false, Ss) end. +-spec(state_can_deliver/2 :: +( + Entity::ljid(), + SubOptions :: mod_pubsub:subOptions() | []) + -> [ljid()] +). state_can_deliver({U, S, R}, []) -> [{U, S, R}]; state_can_deliver({U, S, R}, SubOptions) -> %% Check SubOptions for 'show_values' @@ -3016,7 +4200,7 @@ state_can_deliver({U, S, R}, SubOptions) -> %% Get subscriber resources Resources = case R of %% If the subscriber JID is a bare one, get all its resources - [] -> user_resources(U, S); + <<>> -> user_resources(U, S); %% If the subscriber JID is a full one, use its resource R -> [R] end, @@ -3028,8 +4212,14 @@ state_can_deliver({U, S, R}, SubOptions) -> end, [], Resources) end. +-spec(get_resource_state/3 :: +( + Entity :: ljid(), + ShowValues :: [binary()], + JIDs :: [ljid()]) + -> [ljid()] +). get_resource_state({U, S, R}, ShowValues, JIDs) -> - %% Get user session PID case ejabberd_sm:get_session_pid(U, S, R) of %% If no PID, item can be delivered none -> lists:append([{U, S, R}], JIDs); @@ -3038,8 +4228,8 @@ get_resource_state({U, S, R}, ShowValues, JIDs) -> %% Get user resource state %% TODO : add a catch clause Show = case ejabberd_c2s:get_presence(Pid) of - {_, _, "available", _} -> "online"; - {_, _, State, _} -> State + {_, _, <<"available">>, _} -> <<"online">>; + {_, _, State, _} -> State end, %% Is current resource state listed in 'show-values' suboption ? case lists:member(Show, ShowValues) of %andalso Show =/= "online" of @@ -3052,26 +4242,37 @@ get_resource_state({U, S, R}, ShowValues, JIDs) -> %% @spec (Payload) -> int() %% Payload = term() +-spec(payload_xmlelements/1 :: +( + Payload :: mod_pubsub:payload()) + -> Count :: non_neg_integer() +). %% @doc <p>Count occurence of XML elements in payload.</p> payload_xmlelements(Payload) -> payload_xmlelements(Payload, 0). payload_xmlelements([], Count) -> Count; -payload_xmlelements([{xmlelement, _, _, _}|Tail], Count) -> payload_xmlelements(Tail, Count+1); -payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count). +payload_xmlelements([#xmlel{} | Tail], Count) -> + payload_xmlelements(Tail, Count + 1); +payload_xmlelements([_ | Tail], Count) -> + payload_xmlelements(Tail, Count). %% @spec (Els) -> stanza() %% Els = [xmlelement()] %% @doc <p>Build pubsub event stanza</p> -event_stanza(Els) -> - event_stanza_withmoreels(Els, []). +event_stanza(Els) -> event_stanza_withmoreels(Els, []). event_stanza_with_delay(Els, ModifNow, ModifUSR) -> DateTime = calendar:now_to_datetime(ModifNow), - MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifUSR, "")], + MoreEls = [jlib:timestamp_to_xml(DateTime, utc, + ModifUSR, <<"">>)], event_stanza_withmoreels(Els, MoreEls). event_stanza_withmoreels(Els, MoreEls) -> - {xmlelement, "message", [], - [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], Els} | MoreEls]}. + #xmlel{name = <<"message">>, attrs = [], + children = + [#xmlel{name = <<"event">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], + children = Els} + | MoreEls]}. %%%%%% broadcast functions @@ -3083,8 +4284,9 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F false -> [] end, Stanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "item", itemAttr(ItemId), Content}]}]), + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = [#xmlel{name = <<"item">>, attrs = itemAttr(ItemId), + children = Content}]}]), broadcast_stanza(Host, From, Node, NodeId, Type, NodeOptions, SubsByDepth, items, Stanza, true), case Removed of @@ -3094,8 +4296,8 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F case get_option(NodeOptions, notify_retract) of true -> RetractStanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]), + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = [#xmlel{name = <<"retract">>, attrs = itemAttr(RId)} || RId <- Removed]}]), broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, items, RetractStanza, true); @@ -3118,8 +4320,8 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot case get_collection_subscriptions(Host, Node) of SubsByDepth when is_list(SubsByDepth) -> Stanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]), + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = [#xmlel{name = <<"retract">>, attrs = itemAttr(ItemId)} || ItemId <- ItemIds]}]), broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, items, Stanza, true), {result, true}; @@ -3136,8 +4338,7 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) -> case get_collection_subscriptions(Host, Node) of SubsByDepth when is_list(SubsByDepth) -> Stanza = event_stanza( - [{xmlelement, "purge", nodeAttr(Node), - []}]), + [#xmlel{name = <<"purge">>, attrs = nodeAttr(Node)}]), broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, false), {result, true}; @@ -3156,8 +4357,7 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> {result, false}; _ -> Stanza = event_stanza( - [{xmlelement, "delete", nodeAttr(Node), - []}]), + [#xmlel{name = <<"delete">>, attrs = nodeAttr(Node)}]), broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, false), {result, true} @@ -3169,7 +4369,7 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> broadcast_created_node(_, _, _, _, _, []) -> {result, false}; broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]), + Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = nodeAttr(Node)}]), broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true), {result, true}. @@ -3180,13 +4380,13 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) -> SubsByDepth when is_list(SubsByDepth) -> Content = case get_option(NodeOptions, deliver_payloads) of true -> - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], - get_configure_xfields(Type, NodeOptions, Lang, [])}]; + [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], + children = get_configure_xfields(Type, NodeOptions, Lang, [])}]; false -> [] end, Stanza = event_stanza( - [{xmlelement, "configuration", nodeAttr(Node), Content}]), + [#xmlel{name = <<"configuration">>, attrs = nodeAttr(Node), children = Content}]), broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, false), {result, true}; @@ -3355,57 +4555,80 @@ user_resources(User, Server) -> %%<li>The service does not support retrieval of default node configuration.</li> %%</ul> get_configure(Host, ServerHost, Node, From, Lang) -> - Action = - fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> - case node_call(Type, get_affiliation, [NodeId, From]) of - {result, owner} -> - Groups = ejabberd_hooks:run_fold(roster_groups, ServerHost, [], [ServerHost]), - {result, - [{xmlelement, "pubsub", - [{"xmlns", ?NS_PUBSUB_OWNER}], - [{xmlelement, "configure", nodeAttr(Node), - [{xmlelement, "x", - [{"xmlns", ?NS_XDATA}, {"type", "form"}], - get_configure_xfields(Type, Options, Lang, Groups) - }]}]}]}; - _ -> - {error, ?ERR_FORBIDDEN} - end - end, + Action = fun (#pubsub_node{options = Options, + type = Type, id = NodeId}) -> + case node_call(Type, get_affiliation, [NodeId, From]) of + {result, owner} -> + Groups = ejabberd_hooks:run_fold(roster_groups, + ServerHost, [], + [ServerHost]), + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], + children = + [#xmlel{name = <<"configure">>, + attrs = nodeAttr(Node), + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_XDATA}, + {<<"type">>, + <<"form">>}], + children = + get_configure_xfields(Type, + Options, + Lang, + Groups)}]}]}]}; + _ -> {error, ?ERR_FORBIDDEN} + end + end, case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; - Other -> Other + {result, {_, Result}} -> {result, Result}; + Other -> Other end. get_default(Host, Node, _From, Lang) -> Type = select_type(Host, Host, Node), Options = node_options(Type), - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB_OWNER}], - [{xmlelement, "default", [], - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}], - get_configure_xfields(Type, Options, Lang, []) - }]}]}]}. - %% Get node option %% The result depend of the node type plugin system. + {result, + [#xmlel{name = <<"pubsub">>, + attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], + children = + [#xmlel{name = <<"default">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"form">>}], + children = + get_configure_xfields(Type, Options, + Lang, [])}]}]}]}. + get_option([], _) -> false; get_option(Options, Var) -> get_option(Options, Var, false). + get_option(Options, Var, Def) -> case lists:keysearch(Var, 1, Options) of - {value, {_Val, Ret}} -> Ret; - _ -> Def + {value, {_Val, Ret}} -> Ret; + _ -> Def end. %% Get default options from the module plugin. node_options(Type) -> - Module = list_to_atom(?PLUGIN_PREFIX ++ Type), + Module = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + Type/binary>>), case catch Module:options() of - {'EXIT',{undef,_}} -> - DefaultModule = list_to_atom(?PLUGIN_PREFIX++?STDNODE), - DefaultModule:options(); - Result -> - Result + {'EXIT', {undef, _}} -> + DefaultModule = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + (?STDNODE)/binary>>), + DefaultModule:options(); + Result -> Result end. %% @spec (Host, Options) -> MaxItems @@ -3421,80 +4644,116 @@ node_options(Type) -> %% version. max_items(Host, Options) -> case get_option(Options, persist_items) of - true -> - case get_option(Options, max_items) of - false -> unlimited; - Result when (Result < 0) -> 0; - Result -> Result - end; - false -> - case get_option(Options, send_last_published_item) of - never -> - 0; - _ -> - case is_last_item_cache_enabled(Host) of - true -> 0; - false -> 1 - end - end + true -> + case get_option(Options, max_items) of + false -> unlimited; + Result when Result < 0 -> 0; + Result -> Result + end; + false -> + case get_option(Options, send_last_published_item) of + never -> 0; + _ -> + case is_last_item_cache_enabled(Host) of + true -> 0; + false -> 1 + end + end end. -define(BOOL_CONFIG_FIELD(Label, Var), - ?BOOLXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - get_option(Options, Var))). + ?BOOLXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + (get_option(Options, Var)))). -define(STRING_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - get_option(Options, Var, ""))). + ?STRINGXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + (get_option(Options, Var, <<"">>)))). -define(INTEGER_CONFIG_FIELD(Label, Var), - ?STRINGXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - integer_to_list(get_option(Options, Var)))). + ?STRINGXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + (iolist_to_binary(integer_to_list(get_option(Options, + Var)))))). -define(JLIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - jlib:jid_to_string(get_option(Options, Var)), + ?LISTXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + (jlib:jid_to_string(get_option(Options, Var))), [jlib:jid_to_string(O) || O <- Opts])). -define(ALIST_CONFIG_FIELD(Label, Var, Opts), - ?LISTXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - atom_to_list(get_option(Options, Var)), - [atom_to_list(O) || O <- Opts])). + ?LISTXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + (iolist_to_binary(atom_to_list(get_option(Options, + Var)))), + [iolist_to_binary(atom_to_list(O)) || O <- Opts])). -define(LISTM_CONFIG_FIELD(Label, Var, Opts), - ?LISTMXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - get_option(Options, Var), Opts)). + ?LISTMXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + (get_option(Options, Var)), Opts)). -define(NLIST_CONFIG_FIELD(Label, Var), - ?STRINGMXFIELD(Label, "pubsub#" ++ atom_to_list(Var), - [node_to_string(N) || N <- get_option(Options, Var, [])])). + ?STRINGMXFIELD(Label, + <<"pubsub#", + (iolist_to_binary(atom_to_list(Var)))/binary>>, + get_option(Options, Var, []))). get_configure_xfields(_Type, Options, Lang, Groups) -> - [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG), - ?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads), - ?BOOL_CONFIG_FIELD("Deliver event notifications", deliver_notifications), - ?BOOL_CONFIG_FIELD("Notify subscribers when the node configuration changes", notify_config), - ?BOOL_CONFIG_FIELD("Notify subscribers when the node is deleted", notify_delete), - ?BOOL_CONFIG_FIELD("Notify subscribers when items are removed from the node", notify_retract), - ?BOOL_CONFIG_FIELD("Persist items to storage", persist_items), - ?STRING_CONFIG_FIELD("A friendly name for the node", title), - ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items), - ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe), - ?ALIST_CONFIG_FIELD("Specify the access model", access_model, + [?XFIELD(<<"hidden">>, <<"">>, <<"FORM_TYPE">>, + (?NS_PUBSUB_NODE_CONFIG)), + ?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>, + deliver_payloads), + ?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>, + deliver_notifications), + ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuratio" + "n changes">>, + notify_config), + ?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is " + "deleted">>, + notify_delete), + ?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed " + "from the node">>, + notify_retract), + ?BOOL_CONFIG_FIELD(<<"Persist items to storage">>, + persist_items), + ?STRING_CONFIG_FIELD(<<"A friendly name for the node">>, + title), + ?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>, + max_items), + ?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>, + subscribe), + ?ALIST_CONFIG_FIELD(<<"Specify the access model">>, + access_model, [open, authorize, presence, roster, whitelist]), - %% XXX: change to list-multi, include current roster groups as options - ?LISTM_CONFIG_FIELD("Roster groups allowed to subscribe", roster_groups_allowed, Groups), - ?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model, - [publishers, subscribers, open]), - ?BOOL_CONFIG_FIELD("Purge all items when the relevant publisher goes offline", purge_offline), - ?ALIST_CONFIG_FIELD("Specify the event message type", notification_type, - [headline, normal]), - ?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size), - ?ALIST_CONFIG_FIELD("When to send the last published item", send_last_published_item, + ?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>, + roster_groups_allowed, Groups), + ?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>, + publish_model, [publishers, subscribers, open]), + ?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher " + "goes offline">>, + purge_offline), + ?ALIST_CONFIG_FIELD(<<"Specify the event message type">>, + notification_type, [headline, normal]), + ?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>, + max_payload_size), + ?ALIST_CONFIG_FIELD(<<"When to send the last published item">>, + send_last_published_item, [never, on_sub, on_sub_and_presence]), - ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery), - ?NLIST_CONFIG_FIELD("The collections with which a node is affiliated", collection) - ]. + ?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available " + "users">>, + presence_based_delivery), + ?NLIST_CONFIG_FIELD(<<"The collections with which a node is " + "affiliated">>, + collection)]. %%<p>There are several reasons why the node configuration request might fail:</p> %%<ul> @@ -3506,52 +4765,60 @@ get_configure_xfields(_Type, Options, Lang, Groups) -> %%</ul> set_configure(Host, Node, From, Els, Lang) -> case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> - case {xml:get_tag_attr_s("xmlns", XEl), xml:get_tag_attr_s("type", XEl)} of - {?NS_XDATA, "cancel"} -> - {result, []}; - {?NS_XDATA, "submit"} -> - Action = - fun(#pubsub_node{options = Options, type = Type, id = NodeId} = N) -> - case node_call(Type, get_affiliation, [NodeId, From]) of - {result, owner} -> - case jlib:parse_xdata_submit(XEl) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - XData -> - OldOpts = case Options of - [] -> node_options(Type); - _ -> Options - end, - case set_xoption(Host, XData, OldOpts) of - NewOpts when is_list(NewOpts) -> - case tree_call(Host, set_node, [N#pubsub_node{options = NewOpts}]) of - ok -> {result, ok}; - Err -> Err - end; - Err -> - Err - end - end; - _ -> - {error, ?ERR_FORBIDDEN} - end - end, - case transaction(Host, Node, Action, transaction) of - {result, {TNode, ok}} -> - NodeId = TNode#pubsub_node.id, - Type = TNode#pubsub_node.type, - Options = TNode#pubsub_node.options, - broadcast_config_notification(Host, Node, NodeId, Type, Options, Lang), - {result, []}; - Other -> - Other - end; - _ -> - {error, ?ERR_BAD_REQUEST} - end; - _ -> - {error, ?ERR_BAD_REQUEST} + [#xmlel{name = <<"x">>} = XEl] -> + case {xml:get_tag_attr_s(<<"xmlns">>, XEl), + xml:get_tag_attr_s(<<"type">>, XEl)} + of + {?NS_XDATA, <<"cancel">>} -> {result, []}; + {?NS_XDATA, <<"submit">>} -> + Action = fun (#pubsub_node{options = Options, + type = Type, id = NodeId} = + N) -> + case node_call(Type, get_affiliation, + [NodeId, From]) + of + {result, owner} -> + case jlib:parse_xdata_submit(XEl) of + invalid -> {error, ?ERR_BAD_REQUEST}; + XData -> + OldOpts = case Options of + [] -> + node_options(Type); + _ -> Options + end, + case set_xoption(Host, XData, + OldOpts) + of + NewOpts + when is_list(NewOpts) -> + case tree_call(Host, + set_node, + [N#pubsub_node{options + = + NewOpts}]) + of + ok -> {result, ok}; + Err -> Err + end; + Err -> Err + end + end; + _ -> {error, ?ERR_FORBIDDEN} + end + end, + case transaction(Host, Node, Action, transaction) of + {result, {TNode, ok}} -> + NodeId = TNode#pubsub_node.id, + Type = TNode#pubsub_node.type, + Options = TNode#pubsub_node.options, + broadcast_config_notification(Host, Node, NodeId, Type, + Options, Lang), + {result, []}; + Other -> Other + end; + _ -> {error, ?ERR_BAD_REQUEST} + end; + _ -> {error, ?ERR_BAD_REQUEST} end. add_opt(Key, Value, Opts) -> @@ -3560,100 +4827,144 @@ add_opt(Key, Value, Opts) -> -define(SET_BOOL_XOPT(Opt, Val), BoolVal = case Val of - "0" -> false; - "1" -> true; - "false" -> false; - "true" -> true; - _ -> error + <<"0">> -> false; + <<"1">> -> true; + <<"false">> -> false; + <<"true">> -> true; + _ -> error end, case BoolVal of - error -> {error, ?ERR_NOT_ACCEPTABLE}; - _ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts)) + error -> {error, ?ERR_NOT_ACCEPTABLE}; + _ -> + set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts)) end). -define(SET_STRING_XOPT(Opt, Val), set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). -define(SET_INTEGER_XOPT(Opt, Val, Min, Max), - case catch list_to_integer(Val) of - IVal when is_integer(IVal), - IVal >= Min, - IVal =< Max -> - set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts)); - _ -> - {error, ?ERR_NOT_ACCEPTABLE} + case catch jlib:binary_to_integer(Val) of + IVal when is_integer(IVal), IVal >= Min, IVal =< Max -> + set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts)); + _ -> {error, ?ERR_NOT_ACCEPTABLE} end). -define(SET_ALIST_XOPT(Opt, Val, Vals), - case lists:member(Val, [atom_to_list(V) || V <- Vals]) of - true -> set_xoption(Host, Opts, add_opt(Opt, list_to_atom(Val), NewOpts)); - false -> {error, ?ERR_NOT_ACCEPTABLE} + case lists:member(Val, + [iolist_to_binary(atom_to_list(V)) || V <- Vals]) + of + true -> + set_xoption(Host, Opts, + add_opt(Opt, jlib:binary_to_atom(Val), NewOpts)); + false -> {error, ?ERR_NOT_ACCEPTABLE} end). -define(SET_LIST_XOPT(Opt, Val), set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). -set_xoption(_Host, [], NewOpts) -> - NewOpts; -set_xoption(Host, [{"FORM_TYPE", _} | Opts], NewOpts) -> +set_xoption(_Host, [], NewOpts) -> NewOpts; +set_xoption(Host, [{<<"FORM_TYPE">>, _} | Opts], + NewOpts) -> set_xoption(Host, Opts, NewOpts); -set_xoption(Host, [{"pubsub#roster_groups_allowed", Value} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], + NewOpts) -> ?SET_LIST_XOPT(roster_groups_allowed, Value); -set_xoption(Host, [{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(deliver_payloads, Val); -set_xoption(Host, [{"pubsub#deliver_notifications", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(deliver_notifications, Val); -set_xoption(Host, [{"pubsub#notify_config", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#notify_config">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(notify_config, Val); -set_xoption(Host, [{"pubsub#notify_delete", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#notify_delete">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(notify_delete, Val); -set_xoption(Host, [{"pubsub#notify_retract", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#notify_retract">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(notify_retract, Val); -set_xoption(Host, [{"pubsub#persist_items", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#persist_items">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(persist_items, Val); -set_xoption(Host, [{"pubsub#max_items", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) -> MaxItems = get_max_items_node(Host), ?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems); -set_xoption(Host, [{"pubsub#subscribe", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) -> ?SET_BOOL_XOPT(subscribe, Val); -set_xoption(Host, [{"pubsub#access_model", [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]); -set_xoption(Host, [{"pubsub#publish_model", [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]); -set_xoption(Host, [{"pubsub#notification_type", [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(notification_type, Val, [headline, normal]); -set_xoption(Host, [{"pubsub#node_type", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) -> + ?SET_ALIST_XOPT(access_model, Val, + [open, authorize, presence, roster, whitelist]); +set_xoption(Host, + [{<<"pubsub#publish_model">>, [Val]} | Opts], + NewOpts) -> + ?SET_ALIST_XOPT(publish_model, Val, + [publishers, subscribers, open]); +set_xoption(Host, + [{<<"pubsub#notification_type">>, [Val]} | Opts], + NewOpts) -> + ?SET_ALIST_XOPT(notification_type, Val, + [headline, normal]); +set_xoption(Host, + [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) -> ?SET_ALIST_XOPT(node_type, Val, [leaf, collection]); -set_xoption(Host, [{"pubsub#max_payload_size", [Val]} | Opts], NewOpts) -> - ?SET_INTEGER_XOPT(max_payload_size, Val, 0, ?MAX_PAYLOAD_SIZE); -set_xoption(Host, [{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts) -> - ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]); -set_xoption(Host, [{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#max_payload_size">>, [Val]} | Opts], + NewOpts) -> + ?SET_INTEGER_XOPT(max_payload_size, Val, 0, + (?MAX_PAYLOAD_SIZE)); +set_xoption(Host, + [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], + NewOpts) -> + ?SET_ALIST_XOPT(send_last_published_item, Val, + [never, on_sub, on_sub_and_presence]); +set_xoption(Host, + [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(presence_based_delivery, Val); -set_xoption(Host, [{"pubsub#purge_offline", [Val]} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#purge_offline">>, [Val]} | Opts], + NewOpts) -> ?SET_BOOL_XOPT(purge_offline, Val); -set_xoption(Host, [{"pubsub#title", Value} | Opts], NewOpts) -> +set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], + NewOpts) -> ?SET_STRING_XOPT(title, Value); -set_xoption(Host, [{"pubsub#type", Value} | Opts], NewOpts) -> +set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], + NewOpts) -> ?SET_STRING_XOPT(type, Value); -set_xoption(Host, [{"pubsub#body_xslt", Value} | Opts], NewOpts) -> +set_xoption(Host, + [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) -> ?SET_STRING_XOPT(body_xslt, Value); -set_xoption(Host, [{"pubsub#collection", Value} | Opts], NewOpts) -> - NewValue = [string_to_node(V) || V <- Value], - ?SET_LIST_XOPT(collection, NewValue); -set_xoption(Host, [{"pubsub#node", [Value]} | Opts], NewOpts) -> - NewValue = string_to_node(Value), - ?SET_LIST_XOPT(node, NewValue); +set_xoption(Host, + [{<<"pubsub#collection">>, Value} | Opts], NewOpts) -> +% NewValue = [string_to_node(V) || V <- Value], + ?SET_LIST_XOPT(collection, Value); +set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], + NewOpts) -> +% NewValue = string_to_node(Value), + ?SET_LIST_XOPT(node, Value); set_xoption(Host, [_ | Opts], NewOpts) -> - % skip unknown field set_xoption(Host, Opts, NewOpts). get_max_items_node({_, ServerHost, _}) -> get_max_items_node(ServerHost); get_max_items_node(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, config), max_items_node) of - [{max_items_node, Integer}] -> Integer; - _ -> ?MAXITEMS + case catch ets:lookup(gen_mod:get_module_proc(Host, + config), + max_items_node) + of + [{max_items_node, Integer}] -> Integer; + _ -> ?MAXITEMS end. %%%% last item cache handling @@ -3661,207 +4972,243 @@ get_max_items_node(Host) -> is_last_item_cache_enabled({_, ServerHost, _}) -> is_last_item_cache_enabled(ServerHost); is_last_item_cache_enabled(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, config), last_item_cache) of - [{last_item_cache, true}] -> true; - _ -> false + case catch ets:lookup(gen_mod:get_module_proc(Host, + config), + last_item_cache) + of + [{last_item_cache, true}] -> true; + _ -> false end. -set_cached_item({_, ServerHost, _}, NodeId, ItemId, Publisher, Payload) -> - set_cached_item(ServerHost, NodeId, ItemId, Publisher, Payload); -set_cached_item(Host, NodeId, ItemId, Publisher, Payload) -> +set_cached_item({_, ServerHost, _}, NodeId, ItemId, + Publisher, Payload) -> + set_cached_item(ServerHost, NodeId, ItemId, Publisher, + Payload); +set_cached_item(Host, NodeId, ItemId, Publisher, + Payload) -> case is_last_item_cache_enabled(Host) of - true -> mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, {now(), jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, Payload}); - _ -> ok + true -> + mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, + {now(), + jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, + Payload}); + _ -> ok end. + unset_cached_item({_, ServerHost, _}, NodeId) -> unset_cached_item(ServerHost, NodeId); unset_cached_item(Host, NodeId) -> case is_last_item_cache_enabled(Host) of - true -> mnesia:dirty_delete({pubsub_last_item, NodeId}); - _ -> ok + true -> mnesia:dirty_delete({pubsub_last_item, NodeId}); + _ -> ok end. + +-spec(get_cached_item/2 :: +( + Host :: mod_pubsub:host(), + NodeIdx :: mod_pubsub:nodeIdx()) + -> undefined | mod_pubsub:pubsubItem() +). get_cached_item({_, ServerHost, _}, NodeId) -> get_cached_item(ServerHost, NodeId); -get_cached_item(Host, NodeId) -> +get_cached_item(Host, NodeIdx) -> case is_last_item_cache_enabled(Host) of - true -> - case mnesia:dirty_read({pubsub_last_item, NodeId}) of - [{pubsub_last_item, NodeId, ItemId, Creation, Payload}] -> - #pubsub_item{itemid = {ItemId, NodeId}, payload = Payload, - creation = Creation, modification = Creation}; - _ -> - undefined - end; - _ -> - undefined + true -> + case mnesia:dirty_read({pubsub_last_item, NodeIdx}) of + [#pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}] -> +% [{pubsub_last_item, NodeId, ItemId, Creation, +% Payload}] -> + #pubsub_item{itemid = {ItemId, NodeIdx}, + payload = Payload, creation = Creation, + modification = Creation}; + _ -> undefined + end; + _ -> undefined end. %%%% plugin handling host(ServerHost) -> - case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), host) of - [{host, Host}] -> Host; - _ -> "pubsub."++ServerHost + case catch + ets:lookup(gen_mod:get_module_proc(ServerHost, config), + host) + of + [{host, Host}] -> Host; + _ -> <<"pubsub.", ServerHost/binary>> end. plugins(Host) -> - case catch ets:lookup(gen_mod:get_module_proc(Host, config), plugins) of - [{plugins, []}] -> [?STDNODE]; - [{plugins, PL}] -> PL; - _ -> [?STDNODE] + case catch ets:lookup(gen_mod:get_module_proc(Host, + config), + plugins) + of + [{plugins, []}] -> [?STDNODE]; + [{plugins, PL}] -> PL; + _ -> [?STDNODE] end. -select_type(ServerHost, Host, Node, Type)-> + +select_type(ServerHost, Host, Node, Type) -> SelectedType = case Host of - {_User, _Server, _Resource} -> - case catch ets:lookup(gen_mod:get_module_proc(ServerHost, config), pep_mapping) of - [{pep_mapping, PM}] -> proplists:get_value(node_to_string(Node), PM, ?PEPNODE); - _ -> ?PEPNODE - end; - _ -> - Type - end, + {_User, _Server, _Resource} -> + case catch + ets:lookup(gen_mod:get_module_proc(ServerHost, + config), + pep_mapping) + of + [{pep_mapping, PM}] -> + proplists:get_value(Node, PM, ?PEPNODE); + _ -> ?PEPNODE + end; + _ -> Type + end, ConfiguredTypes = plugins(ServerHost), case lists:member(SelectedType, ConfiguredTypes) of - true -> SelectedType; - false -> hd(ConfiguredTypes) + true -> SelectedType; + false -> hd(ConfiguredTypes) end. -select_type(ServerHost, Host, Node) -> - select_type(ServerHost, Host, Node, hd(plugins(ServerHost))). + +select_type(ServerHost, Host, Node) -> + select_type(ServerHost, Host, Node, + hd(plugins(ServerHost))). features() -> - [ - % see plugin "access-authorize", % OPTIONAL - "access-open", % OPTIONAL this relates to access_model option in node_hometree - "access-presence", % OPTIONAL this relates to access_model option in node_pep - %TODO "access-roster", % OPTIONAL - "access-whitelist", % OPTIONAL - % see plugin "auto-create", % OPTIONAL - % see plugin "auto-subscribe", % RECOMMENDED - "collections", % RECOMMENDED - "config-node", % RECOMMENDED - "create-and-configure", % RECOMMENDED - % see plugin "create-nodes", % RECOMMENDED - % see plugin "delete-items", % RECOMMENDED - % see plugin "delete-nodes", % RECOMMENDED - % see plugin "filtered-notifications", % RECOMMENDED - % see plugin "get-pending", % OPTIONAL - % see plugin "instant-nodes", % RECOMMENDED - "item-ids", % RECOMMENDED - "last-published", % RECOMMENDED - %TODO "cache-last-item", - %TODO "leased-subscription", % OPTIONAL - % see plugin "manage-subscriptions", % OPTIONAL - "member-affiliation", % RECOMMENDED - %TODO "meta-data", % RECOMMENDED - % see plugin "modify-affiliations", % OPTIONAL - % see plugin "multi-collection", % OPTIONAL - % see plugin "multi-subscribe", % OPTIONAL - % see plugin "outcast-affiliation", % RECOMMENDED - % see plugin "persistent-items", % RECOMMENDED - "presence-notifications", % OPTIONAL - "presence-subscribe", % RECOMMENDED - % see plugin "publish", % REQUIRED - %TODO "publish-options", % OPTIONAL - "publisher-affiliation", % RECOMMENDED - % see plugin "purge-nodes", % OPTIONAL - % see plugin "retract-items", % OPTIONAL - % see plugin "retrieve-affiliations", % RECOMMENDED - "retrieve-default" % RECOMMENDED - % see plugin "retrieve-items", % RECOMMENDED + [% see plugin "access-authorize", % OPTIONAL + <<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree + <<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep + <<"access-whitelist">>, % OPTIONAL + <<"collections">>, % RECOMMENDED + <<"config-node">>, % RECOMMENDED + <<"create-and-configure">>, % RECOMMENDED + <<"item-ids">>, % RECOMMENDED + <<"last-published">>, % RECOMMENDED + <<"member-affiliation">>, % RECOMMENDED + <<"presence-notifications">>, % OPTIONAL + <<"presence-subscribe">>, % RECOMMENDED + <<"publisher-affiliation">>, % RECOMMENDED + <<"retrieve-default">>]. + + % see plugin "retrieve-items", % RECOMMENDED % see plugin "retrieve-subscriptions", % RECOMMENDED %TODO "shim", % OPTIONAL % see plugin "subscribe", % REQUIRED % see plugin "subscription-options", % OPTIONAL % see plugin "subscription-notifications" % OPTIONAL - ]. + features(Type) -> - Module = list_to_atom(?PLUGIN_PREFIX++Type), - features() ++ case catch Module:features() of - {'EXIT', {undef, _}} -> []; - Result -> Result - end. + Module = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + Type/binary>>), + features() ++ + case catch Module:features() of + {'EXIT', {undef, _}} -> []; + Result -> Result + end. + features(Host, <<>>) -> - lists:usort(lists:foldl(fun(Plugin, Acc) -> - Acc ++ features(Plugin) - end, [], plugins(Host))); + lists:usort(lists:foldl(fun (Plugin, Acc) -> + Acc ++ features(Plugin) + end, + [], plugins(Host))); features(Host, Node) -> - Action = fun(#pubsub_node{type = Type}) -> {result, features(Type)} end, + Action = fun (#pubsub_node{type = Type}) -> + {result, features(Type)} + end, case transaction(Host, Node, Action, sync_dirty) of - {result, Features} -> lists:usort(features() ++ Features); - _ -> features() + {result, Features} -> + lists:usort(features() ++ Features); + _ -> features() end. %% @doc <p>node tree plugin call.</p> tree_call({_User, Server, _Resource}, Function, Args) -> tree_call(Server, Function, Args); tree_call(Host, Function, Args) -> - ?DEBUG("tree_call ~p ~p ~p",[Host, Function, Args]), - Module = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of - [{nodetree, N}] -> N; - _ -> list_to_atom(?TREE_PREFIX ++ ?STDTREE) - end, + ?DEBUG("tree_call ~p ~p ~p", [Host, Function, Args]), + Module = case catch + ets:lookup(gen_mod:get_module_proc(Host, config), + nodetree) + of + [{nodetree, N}] -> N; + _ -> + jlib:binary_to_atom(<<(?TREE_PREFIX)/binary, + (?STDTREE)/binary>>) + end, catch apply(Module, Function, Args). + tree_action(Host, Function, Args) -> - ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]), - Fun = fun() -> tree_call(Host, Function, Args) end, + ?DEBUG("tree_action ~p ~p ~p", [Host, Function, Args]), + Fun = fun () -> tree_call(Host, Function, Args) end, catch mnesia:sync_dirty(Fun). %% @doc <p>node plugin call.</p> node_call(Type, Function, Args) -> - ?DEBUG("node_call ~p ~p ~p",[Type, Function, Args]), - Module = list_to_atom(?PLUGIN_PREFIX++Type), + ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]), + Module = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + Type/binary>>), case apply(Module, Function, Args) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {'EXIT', {undef, Undefined}} -> - case Type of - ?STDNODE -> {error, {undef, Undefined}}; - _ -> node_call(?STDNODE, Function, Args) - end; - {'EXIT', Reason} -> {error, Reason}; - Result -> {result, Result} %% any other return value is forced as result + {result, Result} -> {result, Result}; + {error, Error} -> {error, Error}; + {'EXIT', {undef, Undefined}} -> + case Type of + ?STDNODE -> {error, {undef, Undefined}}; + _ -> node_call(?STDNODE, Function, Args) + end; + {'EXIT', Reason} -> {error, Reason}; + Result -> + {result, + Result} %% any other return value is forced as result end. node_action(Host, Type, Function, Args) -> - ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]), - transaction(fun() -> - node_call(Type, Function, Args) - end, sync_dirty). + ?DEBUG("node_action ~p ~p ~p ~p", + [Host, Type, Function, Args]), + transaction(fun () -> node_call(Type, Function, Args) + end, + sync_dirty). %% @doc <p>plugin transaction handling.</p> transaction(Host, Node, Action, Trans) -> - transaction(fun() -> + transaction(fun () -> case tree_call(Host, get_node, [Host, Node]) of - N when is_record(N, pubsub_node) -> - case Action(N) of - {result, Result} -> {result, {N, Result}}; - {atomic, {result, Result}} -> {result, {N, Result}}; - Other -> Other - end; - Error -> - Error + N when is_record(N, pubsub_node) -> + case Action(N) of + {result, Result} -> {result, {N, Result}}; + {atomic, {result, Result}} -> + {result, {N, Result}}; + Other -> Other + end; + Error -> Error end - end, Trans). + end, + Trans). + transaction(Host, Action, Trans) -> - transaction(fun() -> - {result, lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))} - end, Trans). + transaction(fun () -> + {result, + lists:foldl(Action, [], + tree_call(Host, get_nodes, [Host]))} + end, + Trans). transaction(Fun, Trans) -> case catch mnesia:Trans(Fun) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {atomic, {result, Result}} -> {result, Result}; - {atomic, {error, Error}} -> {error, Error}; - {aborted, Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - {'EXIT', Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; - Other -> - ?ERROR_MSG("transaction return internal error: ~p~n", [Other]), - {error, ?ERR_INTERNAL_SERVER_ERROR} + {result, Result} -> {result, Result}; + {error, Error} -> {error, Error}; + {atomic, {result, Result}} -> {result, Result}; + {atomic, {error, Error}} -> {error, Error}; + {aborted, Reason} -> + ?ERROR_MSG("transaction return internal error: ~p~n", + [{aborted, Reason}]), + {error, ?ERR_INTERNAL_SERVER_ERROR}; + {'EXIT', Reason} -> + ?ERROR_MSG("transaction return internal error: ~p~n", + [{'EXIT', Reason}]), + {error, ?ERR_INTERNAL_SERVER_ERROR}; + Other -> + ?ERROR_MSG("transaction return internal error: ~p~n", + [Other]), + {error, ?ERR_INTERNAL_SERVER_ERROR} end. %%%% helpers @@ -3869,40 +5216,42 @@ transaction(Fun, Trans) -> %% Add pubsub-specific error element extended_error(Error, Ext) -> extended_error(Error, Ext, - [{"xmlns", ?NS_PUBSUB_ERRORS}]). -extended_error(Error, unsupported, Feature) -> - extended_error(Error, "unsupported", - [{"xmlns", ?NS_PUBSUB_ERRORS}, - {"feature", Feature}]); -extended_error({xmlelement, Error, Attrs, SubEls}, Ext, ExtAttrs) -> - {xmlelement, Error, Attrs, - lists:reverse([{xmlelement, Ext, ExtAttrs, []} | SubEls])}. + [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]). +extended_error(Error, unsupported, Feature) -> %% Give a uniq identifier + extended_error(Error, <<"unsupported">>, + [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}, + {<<"feature">>, Feature}]); +extended_error(#xmlel{name = Error, attrs = Attrs, + children = SubEls}, + Ext, ExtAttrs) -> + #xmlel{name = Error, attrs = Attrs, + children = + lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs, + children = []} + | SubEls])}. + +-spec(uniqid/0 :: () -> mod_pubsub:itemId()). uniqid() -> {T1, T2, T3} = now(), - lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). + iolist_to_binary(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])). -% node attributes -nodeAttr(Node) when is_list(Node) -> - [{"node", Node}]; -nodeAttr(Node) -> - [{"node", node_to_string(Node)}]. +nodeAttr(Node) -> [{<<"node">>, Node}]. -% item attributes itemAttr([]) -> []; -itemAttr(ItemId) -> [{"id", ItemId}]. +itemAttr(ItemId) -> [{<<"id">>, ItemId}]. -% build item elements from item list itemsEls(Items) -> - lists:map(fun(#pubsub_item{itemid = {ItemId, _}, payload = Payload}) -> - {xmlelement, "item", itemAttr(ItemId), Payload} - end, Items). + lists:map(fun (#pubsub_item{itemid = {ItemId, _}, payload = Payload}) -> + #xmlel{name = <<"item">>, attrs = itemAttr(ItemId), children = Payload} + end, Items). -add_message_type({xmlelement, "message", Attrs, Els}, Type) -> - {xmlelement, "message", [{"type", Type}|Attrs], Els}; -add_message_type(XmlEl, _Type) -> - XmlEl. +add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, + Type) -> + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, Type} | Attrs], children = Els}; +add_message_type(XmlEl, _Type) -> XmlEl. %% Place of <headers/> changed at the bottom of the stanza %% cf. http://xmpp.org/extensions/xep-0060.html#publisher-publish-success-subid @@ -3911,14 +5260,19 @@ add_message_type(XmlEl, _Type) -> %% (i.e., as the last child of the <message/> stanza)". add_shim_headers(Stanza, HeaderEls) -> - add_headers(Stanza, "headers", ?NS_SHIM, HeaderEls). + add_headers(Stanza, <<"headers">>, ?NS_SHIM, HeaderEls). add_extended_headers(Stanza, HeaderEls) -> - add_headers(Stanza, "addresses", ?NS_ADDRESS, HeaderEls). + add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, + HeaderEls). -add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) -> - HeaderEl = {xmlelement, HeaderName, [{"xmlns", HeaderNS}], HeaderEls}, - {xmlelement, Name, Attrs, lists:append(Els, [HeaderEl])}. +add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, + HeaderName, HeaderNS, HeaderEls) -> + HeaderEl = #xmlel{name = HeaderName, + attrs = [{<<"xmlns">>, HeaderNS}], + children = HeaderEls}, + #xmlel{name = Name, attrs = Attrs, + children = lists:append(Els, [HeaderEl])}. %% Removed multiple <header name=Collection>Foo</header/> elements %% Didn't seem compliant, but not sure. Confirmation required. @@ -3934,18 +5288,24 @@ add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) -> %% identifier of the collection". collection_shim(Node) -> - [{xmlelement, "header", [{"name", "Collection"}], - [{xmlcdata, node_to_string(Node)}]}]. + [#xmlel{name = <<"header">>, + attrs = [{<<"name">>, <<"Collection">>}], + children = [{xmlcdata, Node}]}]. subid_shim(SubIDs) -> - [{xmlelement, "header", [{"name", "SubID"}], - [{xmlcdata, SubID}]} || SubID <- SubIDs]. + [#xmlel{name = <<"header">>, + attrs = [{<<"name">>, <<"SubID">>}], + children = [{xmlcdata, SubID}]} + || SubID <- SubIDs]. %% The argument is a list of Jids because this function could be used %% with the 'pubsub#replyto' (type=jid-multi) node configuration. extended_headers(Jids) -> - [{xmlelement, "address", [{"type", "replyto"}, {"jid", Jid}], []} || Jid <- Jids]. + [#xmlel{name = <<"address">>, + attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}], + children = []} + || Jid <- Jids]. on_user_offline(_, JID, _) -> {User, Server, Resource} = jlib:jid_tolower(JID), @@ -3957,57 +5317,84 @@ on_user_offline(_, JID, _) -> purge_offline({User, Server, _} = LJID) -> Host = host(element(2, LJID)), Plugins = plugins(Host), - Result = lists:foldl( - fun(Type, {Status, Acc}) -> - case lists:member("retrieve-affiliations", features(Type)) of - false -> - {{error, extended_error('feature-not-implemented', unsupported, "retrieve-affiliations")}, Acc}; - true -> - {result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, LJID]), - {Status, [Affiliations|Acc]} - end - end, {ok, []}, Plugins), + Result = lists:foldl(fun (Type, {Status, Acc}) -> + case lists:member(<<"retrieve-affiliations">>, + features(Type)) + of + false -> + {{error, + extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, + unsupported, + <<"retrieve-affiliations">>)}, + Acc}; + true -> + {result, Affiliations} = + node_action(Host, Type, + get_entity_affiliations, + [Host, LJID]), + {Status, [Affiliations | Acc]} + end + end, + {ok, []}, Plugins), case Result of - {ok, Affiliations} -> - lists:foreach( - fun({#pubsub_node{nodeid = {_, NodeId}, options = Options, type = Type}, Affiliation}) - when Affiliation == 'owner' orelse Affiliation == 'publisher' -> - Action = fun(#pubsub_node{type = NType, id = NodeIdx}) -> - node_call(NType, get_items, [NodeIdx, service_jid(Host)]) - end, - case transaction(Host, NodeId, Action, sync_dirty) of - {result, {_, []}} -> - true; - {result, {_, Items}} -> - Features = features(Type), - case - {lists:member("retract-items", Features), - lists:member("persistent-items", Features), - get_option(Options, persist_items), - get_option(Options, purge_offline)} - of - {true, true, true, true} -> - ForceNotify = get_option(Options, notify_retract), - lists:foreach( - fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, Modification}}) -> - case Modification of - {User, Server, _} -> - delete_item(Host, NodeId, LJID, ItemId, ForceNotify); - _ -> - true - end; - (_) -> - true - end, Items); - _ -> - true + {ok, Affiliations} -> + lists:foreach(fun ({#pubsub_node{nodeid = {_, NodeId}, + options = Options, type = Type}, + Affiliation}) + when Affiliation == owner orelse + Affiliation == publisher -> + Action = fun (#pubsub_node{type = NType, + id = NodeIdx}) -> + node_call(NType, get_items, + [NodeIdx, + service_jid(Host)]) + end, + case transaction(Host, NodeId, Action, + sync_dirty) + of + {result, {_, []}} -> true; + {result, {_, Items}} -> + Features = features(Type), + case {lists:member(<<"retract-items">>, + Features), + lists:member(<<"persistent-items">>, + Features), + get_option(Options, persist_items), + get_option(Options, purge_offline)} + of + {true, true, true, true} -> + ForceNotify = get_option(Options, + notify_retract), + lists:foreach(fun + (#pubsub_item{itemid + = + {ItemId, + _}, + modification + = + {_, + Modification}}) -> + case + Modification + of + {User, Server, + _} -> + delete_item(Host, + NodeId, + LJID, + ItemId, + ForceNotify); + _ -> true + end; + (_) -> true + end, + Items); + _ -> true + end; + Error -> Error end; - Error -> - Error - end; - (_) -> - true - end, lists:usort(lists:flatten(Affiliations))); - {Error, _} -> - ?DEBUG("on_user_offline ~p", [Error]) + (_) -> true + end, + lists:usort(lists:flatten(Affiliations))); + {Error, _} -> ?DEBUG("on_user_offline ~p", [Error]) end. |