diff options
author | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
---|---|---|
committer | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
commit | 9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch) | |
tree | 7e1066c130250627ee0abab44a135f583a28d07f /src/mod_pubsub | |
parent | list_to_integer/2 only works in OTP R14 and newer (diff) |
Accumulated patch to binarize and indent code
Diffstat (limited to 'src/mod_pubsub')
28 files changed, 13196 insertions, 8818 deletions
diff --git a/src/mod_pubsub/Makefile.in b/src/mod_pubsub/Makefile.in index 1ea6a1625..88bf2ba0c 100644 --- a/src/mod_pubsub/Makefile.in +++ b/src/mod_pubsub/Makefile.in @@ -14,7 +14,7 @@ EFLAGS += -pz .. # make debug=true to compile Erlang module with debug informations. ifdef debug - EFLAGS+=+debug_info +export_all + EFLAGS+=+debug_info endif OUTDIR = .. diff --git a/src/mod_pubsub/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl index 57ebb57a6..0cf1fd2ff 100644 --- a/src/mod_pubsub/gen_pubsub_node.erl +++ b/src/mod_pubsub/gen_pubsub_node.erl @@ -4,12 +4,12 @@ %%% compliance with the License. You should have received a copy of the %%% 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.'' @@ -30,47 +30,238 @@ -module(gen_pubsub_node). --export([behaviour_info/1]). - -%% @spec (Query::atom()) -> Callbacks | atom() -%% Callbacks = [{Function,Arity}] -%% Function = atom() -%% Arity = integer() -%% @doc Behaviour definition -behaviour_info(callbacks) -> - [{init, 3}, - {terminate, 2}, - {options, 0}, - {features, 0}, - {create_node_permission, 6}, - {create_node, 2}, - {delete_node, 1}, - {purge_node, 2}, - {subscribe_node, 8}, - {unsubscribe_node, 4}, - {publish_item, 6}, - {delete_item, 4}, - {remove_extra_items, 3}, - {get_node_affiliations, 1}, - {get_entity_affiliations, 2}, - {get_affiliation, 2}, - {set_affiliation, 3}, - {get_node_subscriptions, 1}, - {get_entity_subscriptions, 2}, - {get_subscriptions, 2}, - {set_subscriptions, 4}, - {get_pending_nodes, 2}, - {get_states, 1}, - {get_state, 2}, - {set_state, 1}, - {get_items, 6}, - {get_items, 2}, - {get_item, 7}, - {get_item, 2}, - {set_item, 1}, - {get_item_name, 3}, - {node_to_path, 1}, - {path_to_node, 1} - ]; -behaviour_info(_Other) -> - undefined. +-include("jlib.hrl"). + +-type(host() :: mod_pubsub:host() + | mod_pubsub_odbc:host() +). + +-type(nodeId() :: mod_pubsub:nodeId() + | mod_pubsub_odbc:nodeId() +). + +-type(nodeIdx() :: mod_pubsub:nodeIdx() + | mod_pubsub_odbc:nodeIdx() +). + +-type(itemId() :: mod_pubsub:itemId() + | mod_pubsub_odbc:itemId() +). + +-type(pubsubNode() :: mod_pubsub:pubsubNode() + | mod_pubsub_odbc:pubsubNode() +). + +-type(pubsubState() :: mod_pubsub:pubsubState() + | mod_pubsub_odbc:pubsubState() +). + +-type(pubsubItem() :: mod_pubsub:pubsubItem() + | mod_pubsub_odbc:pubsubItem() +). + +-type(nodeOptions() :: mod_pubsub:nodeOptions() + | mod_pubsub_odbc:nodeOptions() +). + +-type(subOptions() :: mod_pubsub:subOptions() + | mod_pubsub_odbc:subOptions() +). + +-type(affiliation() :: mod_pubsub:affiliation() + | mod_pubsub_odbc:affiliation() +). + +-type(subscription() :: mod_pubsub:subscription() + | mod_pubsub_odbc:subscription() +). + +-type(subId() :: mod_pubsub:subId() + | mod_pubsub_odbc:subId() +). + +-type(accessModel() :: mod_pubsub:accessModel() + | mod_pubsub_odbc:accessModel() +). + +-type(publishModel() :: mod_pubsub:publishModel() + | mod_pubsub_odbc:publishModel() +). + +-type(payload() :: mod_pubsub:payload() + | mod_pubsub_odbc:payload() +). + +-callback init(Host :: binary(), + ServerHost :: binary(), + Opts :: [any()]) -> atom(). + +-callback terminate(Host :: host(), + ServerHost :: binary()) -> atom(). + +-callback options() -> [{atom(), any()}]. + +-callback features() -> [binary()]. + +-callback create_node_permission(Host :: host(), + ServerHost :: binary(), + Node :: nodeId(), + ParentNode :: nodeId(), + Owner :: jid(), Access :: atom()) -> + {result, boolean()}. + +-callback create_node(NodeIdx :: nodeIdx(), + Owner :: jid()) -> + {result, {default, broadcast}}. + +-callback delete_node(Nodes :: [pubsubNode(),...]) -> + {result, + {default, broadcast, + [{pubsubNode(), + [{ljid(), [{subscription(), subId()}]},...]},...] + } + } + | + {result, + {[], + [{pubsubNode(), + [{ljid(), [{subscription(), subId()}]},...]},...] + } + }. + +-callback purge_node(NodeIdx :: nodeIdx(), + Owner :: jid()) -> + {result, {default, broadcast}} | + {error, xmlel()}. + +-callback subscribe_node(NodeIdx :: nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + AccessModel :: accessModel(), + SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + Options :: subOptions()) -> + {result, {default, subscribed, subId()}} | + {result, {default, subscribed, subId(), send_last}} | + {result, {default, pending, subId()}} | + {error, xmlel()}. + +-callback unsubscribe_node(NodeIdx :: nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + SubId :: subId()) -> + {result, default} | + {error, xmlel()}. + +-callback publish_item(NodeId :: nodeIdx(), + Publisher :: jid(), + PublishModel :: publishModel(), + Max_Items :: non_neg_integer(), + ItemId :: <<>> | itemId(), + Payload :: payload()) -> + {result, {default, broadcast, [itemId()]}} | + {error, xmlel()}. + +-callback delete_item(NodeIdx :: nodeIdx(), + Publisher :: jid(), + PublishModel :: publishModel(), + ItemId :: <<>> | itemId()) -> + {result, {default, broadcast}} | + {error, xmlel()}. + +-callback remove_extra_items(NodeIdx :: nodeIdx(), + Max_Items :: unlimited | non_neg_integer(), + ItemIds :: [itemId()]) -> + {result, {[itemId()], [itemId()]} + }. + +-callback get_node_affiliations(NodeIdx :: nodeIdx()) -> + {result, [{ljid(), affiliation()}]}. + +-callback get_entity_affiliations(Host :: host(), + Owner :: jid()) -> + {result, [{pubsubNode(), affiliation()}]}. + +-callback get_affiliation(NodeIdx :: nodeIdx(), + Owner :: jid()) -> + {result, affiliation()}. + +-callback set_affiliation(NodeIdx :: nodeIdx(), + Owner :: ljid(), + Affiliation :: affiliation()) -> + ok | + {error, xmlel()}. + +-callback get_node_subscriptions(NodeIdx :: nodeIdx()) -> + {result, + [{ljid(), subscription(), subId()}] | + [{ljid(), none},...] + }. + +-callback get_entity_subscriptions(Host :: host(), + Owner :: jid()) -> + {result, [{pubsubNode(), subscription(), subId(), ljid()}] + }. + +-callback get_subscriptions(NodeIdx :: nodeIdx(), + Owner :: ljid()) -> + {result, [{subscription(), subId()}]}. + +-callback get_pending_nodes(Host :: host(), + Owner :: jid()) -> + {result, [nodeId()]}. + +-callback get_states(NodeIdx::nodeIdx()) -> + {result, [pubsubState()]}. + +-callback get_state(NodeIdx :: nodeIdx(), + JID :: ljid()) -> + pubsubState(). + +-callback set_state(State::pubsubState()) -> + ok | + {error, xmlel()}. + +-callback get_items(NodeIdx :: nodeIdx(), + JID :: jid(), + AccessModel :: accessModel(), + Presence_Subscription :: boolean(), + RosterGroup :: boolean(), + SubId :: subId()) -> + {result, [pubsubItem()]} | + {error, xmlel()}. + +-callback get_items(NodeIdx :: nodeIdx(), + From :: jid()) -> + {result, [pubsubItem()]}. + +-callback get_item(NodeIdx :: nodeIdx(), + ItemId :: itemId(), + JID :: jid(), + AccessModel :: accessModel(), + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + SubId :: subId()) -> + {result, pubsubItem()} | + {error, xmlel()}. + +-callback get_item(NodeIdx :: nodeIdx(), + ItemId :: itemId()) -> + {result, pubsubItem()} | + {error, xmlel()}. + +-callback set_item(Item :: pubsubItem()) -> + ok. +% | {error, _}. + +-callback get_item_name(Host :: host(), + ServerHost :: binary(), + Node :: nodeId()) -> + itemId(). + +-callback node_to_path(Node :: nodeId()) -> + [nodeId()]. + +-callback path_to_node(Node :: [nodeId()]) -> + nodeId(). diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl index 81daeb08e..8acba659c 100644 --- a/src/mod_pubsub/gen_pubsub_nodetree.erl +++ b/src/mod_pubsub/gen_pubsub_nodetree.erl @@ -4,12 +4,12 @@ %%% compliance with the License. You should have received a copy of the %%% 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.'' @@ -25,34 +25,100 @@ %%% @private %%% @doc <p>The module <strong>{@module}</strong> defines the PubSub node -%%% tree plugin behaviour. This behaviour is used to check that a PubSub +%%% tree plugin behaviour. This behaviour is used to check that a PubSub %%% node tree plugin respects the current ejabberd PubSub plugin API.</p> -module(gen_pubsub_nodetree). --export([behaviour_info/1]). - -%% @spec (Query::atom()) -> Callbacks | atom() -%% Callbacks = [{Function,Arity}] -%% Function = atom() -%% Arity = integer() -%% @doc Behaviour definition -behaviour_info(callbacks) -> - [{init, 3}, - {terminate, 2}, - {options, 0}, - {set_node, 1}, - {get_node, 3}, - {get_node, 2}, - {get_node, 1}, - {get_nodes, 2}, - {get_nodes, 1}, - {get_parentnodes, 3}, - {get_parentnodes_tree, 3}, - {get_subnodes, 3}, - {get_subnodes_tree, 3}, - {create_node, 6}, - {delete_node, 2} - ]; -behaviour_info(_Other) -> - undefined. +-include("jlib.hrl"). + +-type(host() :: mod_pubsub:host() + | mod_pubsub_odbc:host() +). + +-type(nodeId() :: mod_pubsub:nodeId() + | mod_pubsub_odbc:nodeId() +). + +-type(nodeIdx() :: mod_pubsub:nodeIdx() + | mod_pubsub_odbc:nodeIdx() +). + +-type(itemId() :: mod_pubsub:itemId() + | mod_pubsub_odbc:itemId() +). + +-type(pubsubNode() :: mod_pubsub:pubsubNode() + | mod_pubsub_odbc:pubsubNode() +). + +-type(nodeOptions() :: mod_pubsub:nodeOptions() + | mod_pubsub_odbc:nodeOptions() +). + +-callback init(Host :: host(), + ServerHost :: binary(), + Opts :: [any()]) -> atom(). + +-callback terminate(Host :: host(), ServerHost :: binary()) -> atom(). + +-callback options() -> nodeOptions(). + +-callback set_node(PubsubNode :: pubsubNode()) -> + ok | {result, NodeIdx::mod_pubsub_odbc:nodeIdx()} | {error, xmlel()}. + +-callback get_node(Host :: host(), + NodeId :: nodeId(), + From :: jid()) -> + pubsubNode() | + {error, xmlel()}. + +-callback get_node(Host :: host(), + NodeId :: nodeId()) -> + pubsubNode() | + {error, xmlel()}. + +-callback get_node(NodeIdx :: nodeIdx()) -> + pubsubNode() | + {error, xmlel()}. + +-callback get_nodes(Host :: host(), + From :: jid())-> + [pubsubNode()]. + +-callback get_nodes(Host :: host())-> + [pubsubNode()]. + +-callback get_parentnodes(Host :: host(), + NodeId :: nodeId(), + From :: jid()) -> + [pubsubNode()] | + {error, xmlel()}. + +-callback get_parentnodes_tree(Host :: host(), + NodeId :: nodeId(), + From :: jid()) -> + [{0, [pubsubNode(),...]}]. + +-callback get_subnodes(Host :: host(), + NodeId :: nodeId(), + From :: ljid()) -> + [pubsubNode()]. + +-callback get_subnodes_tree(Host :: host(), + NodeId :: nodeId(), + From :: ljid()) -> + [pubsubNode()]. + +-callback create_node(Host :: host(), + NodeId :: nodeId(), + Type :: binary(), + Owner :: jid(), + Options :: nodeOptions(), + Parents :: [nodeId()]) -> + {ok, NodeIdx::nodeIdx()} | + {error, xmlel()}. + +-callback delete_node(Host :: host(), + NodeId :: nodeId()) -> + [pubsubNode()]. 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. diff --git a/src/mod_pubsub/mod_pubsub_odbc.erl b/src/mod_pubsub/mod_pubsub_odbc.erl index 0be08b51f..3396570a4 100644 --- a/src/mod_pubsub/mod_pubsub_odbc.erl +++ b/src/mod_pubsub/mod_pubsub_odbc.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_odbc). + -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_odbc"). --define(STDNODE, "flat_odbc"). --define(PEPNODE, "pep_odbc"). +-define(STDTREE, <<"tree_odbc">>). + +-define(STDNODE, <<"flat_odbc">>). + +-define(PEPNODE, <<"pep_odbc">>). %% 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, - escape/1 - ]). +-export([subscription_to_string/1, affiliation_to_string/1, + string_to_subscription/1, string_to_affiliation/1, + extended_error/2, extended_error/3, + escape/1]). %% 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_odbc). + -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,63 +261,102 @@ 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), - 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}. @@ -251,285 +380,431 @@ 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_odbc", Plugins) of - true -> - create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree_odbc"), - create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree_odbc"); - false -> - ok + case lists:member(<<"hometree_odbc">>, Plugins) of + true -> + create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree_odbc">>), + create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), + <<"hometree_odbc">>); + false -> ok end. 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) -> - Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of - {result, S} -> S; - _ -> [] - end, - lists:foreach( - fun({Node, subscribed, _, SubJID}) -> - if (SubJID == LJID) or (SubJID == BJID) -> - #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node, - send_items(H, N, NodeId, Type, LJID, last); - 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} = case catch node_action(Host, + PType, + get_entity_subscriptions_for_send_last, + [Host, JID]) of + {result, S} -> S; + _ -> [] + end, + lists:foreach(fun ({Node, subscribed, _, + SubJID}) -> + if (SubJID == LJID) or + (SubJID == BJID) -> + #pubsub_node{nodeid + = + {H, + N}, + type = + Type, + id = + NodeId} = + Node, + send_items(H, + N, + NodeId, + Type, + LJID, + last); + 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 = node_owners_call(Type, Idx), - 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 = node_owners_call(Type, Idx), + 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 = node_owners_call(Type, Idx), - 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 = node_owners_call(Type, Idx), + 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}, Acc) -> - Owners = node_owners_call(Type, Idx), - 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}, + Acc) -> + Owners = node_owners_call(Type, Idx), + 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_on_nodes(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 = node_owners_call(Type, Idx), - 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 = node_owners_call(Type, Idx), + 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. %% ------- @@ -540,36 +815,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. %% ------- @@ -577,46 +856,70 @@ 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, id = NodeId}, subscribed, _, JID}) -> - case get_option(Options, access_model) of - presence -> - case lists:member(BJID, node_owners(Host, PType, NodeId)) 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, + id = + NodeId}, + subscribed, _, + JID}) -> + case + get_option(Options, + access_model) + of + presence -> + case + lists:member(BJID, + node_owners(Host, PType, NodeId)) + 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 @@ -625,25 +928,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} | @@ -655,6 +941,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) -> @@ -673,9 +1019,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} | @@ -683,18 +1026,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 @@ -703,529 +1054,762 @@ 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), - Rsm = jlib:rsm_decode(IQ), - Res = case iq_disco_items(Host, Node, From, Rsm) 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), + Rsm = jlib:rsm_decode(IQ), + Res = case iq_disco_items(Host, Node, From, Rsm) 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, none]) 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 - ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []}; - (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, none]) 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 +% (<<"rsm">>)-> +% #xmlel{name = <<"feature">>, +% attrs = [{<<"var">>, ?NS_RSM}]}; +% (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, none]) + 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 + (<<"rsm">>)-> + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_RSM}]}; + (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}], []}] ++ + <<>> -> + {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 - ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []}; - (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []} - 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, _RSM) -> - 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; + (<<"rsm">>)-> + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_RSM}]}; + (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/4 :: +( + Host :: mod_pubsub:host(), + NodeId :: <<>> | mod_pubsub:nodeId(), + From :: jid(), + Rsm :: any()) + -> {result, [xmlel()]} +). +iq_disco_items(Host, <<>>, From, _RSM) -> + {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, _RSM) -> - %% 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, _RSM) -> - CommandItems = [], - {result, CommandItems}; + CommandItems = [], {result, CommandItems}; iq_disco_items(Host, Item, From, RSM) -> - 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 = node_owners_call(Type, Idx), - {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of - {result, R} -> R; - _ -> {[], none} - 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 ++ jlib:rsm_encode(RsmOut)} - 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 = node_owners_call(Type, Idx), + {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of + {result, R} -> R; + _ -> {[], none} + 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 ++ jlib:rsm_encode(RsmOut)} + 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)), - RSM = jlib:rsm_decode(SubEl), - get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM); - {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)), + RSM = jlib:rsm_decode(SubEl), + get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM); + {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, - Action = lists:filter(fun({xmlelement, "set", _, _}) -> false; - (_) -> true - end, xml:remove_cdata(SubEls)), + #xmlel{children = SubEls} = SubEl, + Action = lists:filter(fun(#xmlel{name = <<"set">>, _ = '_'}) -> false; + (_) -> true + end, 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}. @@ -1233,270 +1817,341 @@ 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, + Tr = fun (Type) -> + case node_call(Type, get_pending_nodes, [Host, Owner]) + of + {result, Nodes} -> Nodes; + _ -> [] + end + end, case transaction(Host, - fun () -> {result, lists:flatmap(Tr, Plugins)} end, - sync_dirty) of - {result, Res} -> Res; - Err -> Err + 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{nodeid = {Host, Node}, type = Type, id = NodeId}, 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, node_owners(Host, Type, NodeId)). +send_authorization_request(#pubsub_node{nodeid = {Host, Node}, + type = Type, id = NodeId}, + 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, + node_owners(Host, Type, NodeId)). 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, id = NodeId}) -> - IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)), - {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, + id = NodeId}) -> + IsApprover = + lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), + node_owners_call(Type, NodeId)), + {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, []} @@ -1517,54 +2172,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 @@ -1593,9 +2298,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(Host, CreateNode, transaction) of {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), @@ -1626,6 +2333,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> @@ -1634,7 +2350,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}) -> @@ -1651,9 +2366,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}) -> @@ -1697,6 +2412,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> @@ -1713,86 +2439,99 @@ delete_node(Host, Node, Owner) -> %%<li>The node does not exist.</li> %%</ul> subscribe_node(Host, Node, From, JID, Configuration) -> - SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, + SubOpts = case + pubsub_subscription_odbc: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, 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, []), - Owners = node_owners_call(Type, NodeId), - {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, + 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, []), + Owners = node_owners_call(Type, NodeId), + {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, []} @@ -1811,23 +2550,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()) -> @@ -1844,49 +2597,65 @@ 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), - MaxItems = max_items(Host, Options), - DeliverPayloads = get_option(Options, deliver_payloads), - PersistItems = get_option(Options, persist_items), - 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), + MaxItems = max_items(Host, Options), + DeliverPayloads = get_option(Options, deliver_payloads), + PersistItems = get_option(Options, persist_items), + 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, @@ -1936,8 +2705,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); _ -> @@ -1953,6 +2726,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> @@ -1966,50 +2749,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) -> @@ -2026,44 +2813,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> @@ -2071,78 +2867,101 @@ 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/7 :: +( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid(), + SubId :: mod_pubsub:subId(), + SMaxItems :: binary(), + ItemIDs :: [mod_pubsub:itemId()], + Rsm :: any()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} +). get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) -> - 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}) -> - 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, []), - Owners = node_owners_call(Type, NodeId), - {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, RSM]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, {Items, RSMOut}}} -> - 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))} - | jlib:rsm_encode(RSMOut)]}]}; - Error -> - Error - end + {error, Error} -> {error, Error}; + _ -> + Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> + 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, []), + Owners = node_owners_call(Type, NodeId), + {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, RSM]) + end + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, {Items, RSMOut}}} -> + 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))} + | jlib:rsm_encode(RSMOut)]}]}; + 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) -> case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, none) of {result, {I, _}} -> {result, I}; @@ -2151,9 +2970,11 @@ get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, RSM) -> 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, RSM]). - + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, From, Owners, AccessModel, + AllowedGroups), + node_call(Type, get_items, + [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]). %% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() %% Host = pubsubHost() @@ -2172,43 +2993,51 @@ send_items(Host, Node, NodeId, Type, LJID, last) -> {result, [LastItem]} -> {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, event_stanza_with_delay( - [{xmlelement, "items", nodeAttr(Node), - itemsEls([LastItem])}], ModifNow, ModifUSR); + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = itemsEls([LastItem])}], ModifNow, ModifUSR); _ -> event_stanza( - [{xmlelement, "items", nodeAttr(Node), - itemsEls([])}]) + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = itemsEls([])}]) end; - LastItem -> - {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, - event_stanza_with_delay( - [{xmlelement, "items", nodeAttr(Node), - itemsEls([LastItem])}], ModifNow, ModifUSR) + LastItem -> + {ModifNow, ModifUSR} = + LastItem#pubsub_item.modification, + event_stanza_with_delay([#xmlel{name = + <<"items">>, + attrs = nodeAttr(Node), + children = + itemsEls([LastItem])}], + ModifNow, ModifUSR) end, ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza); -send_items(Host, Node, NodeId, Type, 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, ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza). %% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} @@ -2218,259 +3047,340 @@ send_items(Host, Node, NodeId, Type, 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), +-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{type = Type, id = NodeId}) -> - Owners = node_owners_call(Type, NodeId), - 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]) - 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{type = Type, + id = NodeId} = + N) -> + Owners = node_owners_call(Type, NodeId), + 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]) + 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_odbc: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_odbc: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_odbc: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_odbc: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. @@ -2487,11 +3397,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]), @@ -2499,256 +3409,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{type = Type, id = NodeId}) -> - case lists:member(Owner, node_owners_call(Type, NodeId)) 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{type = Type, + id = NodeId}) -> + case lists:member(Owner, node_owners_call(Type, NodeId)) 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 @@ -2757,14 +3748,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; @@ -2778,6 +3776,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 @@ -2794,6 +3798,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' @@ -2805,7 +3815,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, @@ -2817,8 +3827,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); @@ -2827,8 +3843,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 @@ -2841,26 +3857,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 @@ -2872,8 +3899,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 @@ -2883,8 +3911,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); @@ -2907,8 +3935,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}; @@ -2925,8 +3953,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}; @@ -2945,8 +3972,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} @@ -2958,7 +3984,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}. @@ -2969,13 +3995,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}; @@ -3144,81 +4170,83 @@ 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 - end. - %% @spec (Host, Type, NodeId) -> [ljid()] %% NodeId = pubsubNodeId() %% @doc <p>Return list of node owners.</p> -node_owners(Host, Type, NodeId) -> - case node_action(Host, Type, get_node_affiliations, [NodeId]) of - {result, Affiliations} -> - lists:foldl( - fun({LJID, owner}, Acc) -> [LJID|Acc]; - (_, Acc) -> Acc - end, [], Affiliations); - _ -> - [] - end. -node_owners_call(Type, NodeId) -> - case node_call(Type, get_node_affiliations, [NodeId]) of - {result, Affiliations} -> - lists:foldl( - fun({LJID, owner}, Acc) -> [LJID|Acc]; - (_, Acc) -> Acc - end, [], Affiliations); - _ -> - [] + {'EXIT', {undef, _}} -> + DefaultModule = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + (?STDNODE)/binary>>), + DefaultModule:options(); + Result -> Result end. %% @spec (Host, Options) -> MaxItems @@ -3234,80 +4262,116 @@ node_owners_call(Type, NodeId) -> %% 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> @@ -3319,52 +4383,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) -> @@ -3373,100 +4445,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 @@ -3474,149 +4590,196 @@ 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. + +%% @spec (Host, Type, NodeId) -> [ljid()] +%% NodeId = pubsubNodeId() +%% @doc <p>Return list of node owners.</p> +node_owners(Host, Type, NodeId) -> + case node_action(Host, Type, get_node_affiliations, [NodeId]) of + {result, Affiliations} -> + lists:foldl( + fun({LJID, owner}, Acc) -> [LJID|Acc]; + (_, Acc) -> Acc + end, [], Affiliations); + _ -> + [] + end. +node_owners_call(Type, NodeId) -> + case node_call(Type, get_node_affiliations, [NodeId]) of + {result, Affiliations} -> + lists:foldl( + fun({LJID, owner}, Acc) -> [LJID|Acc]; + (_, Acc) -> Acc + end, [], Affiliations); + _ -> + [] 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, case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of {atomic, Result} -> Result; @@ -3627,44 +4790,53 @@ tree_action(Host, Function, Args) -> %% @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(Host, fun() -> - node_call(Type, Function, Args) - end, sync_dirty). + ?DEBUG("node_action ~p ~p ~p ~p", + [Host, Type, Function, Args]), + transaction(Host, fun () -> node_call(Type, Function, Args) end, + sync_dirty). %% @doc <p>plugin transaction handling.</p> transaction(Host, Node, Action, Trans) -> - transaction(Host, fun() -> + transaction(Host, 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_on_nodes(Host, Action, Trans) -> - transaction(Host, fun() -> - {result, lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))} - end, Trans). + transaction(Host, fun () -> + {result, + lists:foldl(Action, [], + tree_call(Host, get_nodes, [Host]))} + end, + Trans). transaction(Host, Fun, Trans) -> transaction_retry(Host, Fun, Trans, 2). @@ -3674,14 +4846,15 @@ transaction_retry(Host, Fun, Trans, Count) -> _ -> sql_bloc end, case catch ejabberd_odbc:SqlFun(odbc_conn(Host), 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', {timeout, _} = Reason} -> + {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', {timeout, _} = Reason} -> case Count of 0 -> ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), @@ -3690,12 +4863,14 @@ transaction_retry(Host, Fun, Trans, Count) -> erlang:yield(), transaction_retry(Host, Fun, Trans, N-1) end; - {'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} + {'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. odbc_conn({_U, Host, _R})-> @@ -3714,40 +4889,42 @@ escape(Value)-> %% 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 @@ -3756,14 +4933,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. @@ -3779,18 +4961,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), @@ -3802,57 +4990,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. diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl index d3abc6386..23269b1eb 100644 --- a/src/mod_pubsub/node_buddy.erl +++ b/src/mod_pubsub/node_buddy.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.'' @@ -24,9 +26,11 @@ %%% ==================================================================== -module(node_buddy). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). @@ -40,39 +44,18 @@ %% (this makes code cleaner, but execution a little bit longer) %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). - +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -81,16 +64,11 @@ terminate(Host, ServerHost) -> node_hometree:terminate(Host, ServerHost). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, presence}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -99,25 +77,18 @@ options() -> {presence_based_delivery, false}]. features() -> - ["create-nodes", - "delete-nodes", - "delete-items", - "instant-nodes", - "item-ids", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications" - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + [<<"create-nodes">>, <<"delete-nodes">>, + <<"delete-items">>, <<"instant-nodes">>, <<"item-ids">>, + <<"outcast-affiliation">>, <<"persistent-items">>, + <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, <<"retrieve-items">>, + <<"retrieve-subscriptions">>, <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access) -> + node_hometree:create_node_permission(Host, ServerHost, + Node, ParentNode, Owner, Access). create_node(NodeId, Owner) -> node_hometree:create_node(NodeId, Owner). @@ -125,20 +96,28 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree:delete_node(Removed). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options). +subscribe_node(NodeId, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_hometree:subscribe_node(NodeId, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_hometree:unsubscribe_node(NodeId, Sender, + Subscriber, SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_hometree:publish_item(NodeId, Publisher, Model, + MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). + node_hometree:remove_extra_items(NodeId, MaxItems, + ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_hometree:delete_item(NodeId, Publisher, + PublishModel, ItemId). purge_node(NodeId, Owner) -> node_hometree:purge_node(NodeId, Owner). @@ -153,7 +132,8 @@ get_affiliation(NodeId, Owner) -> node_hometree:get_affiliation(NodeId, Owner). set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). + node_hometree:set_affiliation(NodeId, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> node_hometree:get_entity_subscriptions(Host, Owner). @@ -165,41 +145,40 @@ get_subscriptions(NodeId, Owner) -> node_hometree:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). +get_states(NodeId) -> node_hometree:get_states(NodeId). get_state(NodeId, JID) -> node_hometree:get_state(NodeId, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeId, From) -> node_hometree:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_hometree:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). +node_to_path(Node) -> node_flat:node_to_path(Node). +path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl index 9ef26c611..10849b36d 100644 --- a/src/mod_pubsub/node_club.erl +++ b/src/mod_pubsub/node_club.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.'' @@ -24,9 +26,11 @@ %%% ==================================================================== -module(node_club). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). @@ -40,39 +44,18 @@ %% (this makes code cleaner, but execution a little bit longer) %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). - +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -81,16 +64,11 @@ terminate(Host, ServerHost) -> node_hometree:terminate(Host, ServerHost). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, authorize}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, authorize}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -99,24 +77,18 @@ options() -> {presence_based_delivery, false}]. features() -> - ["create-nodes", - "delete-nodes", - "delete-items", - "instant-nodes", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications" - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + [<<"create-nodes">>, <<"delete-nodes">>, + <<"delete-items">>, <<"instant-nodes">>, + <<"outcast-affiliation">>, <<"persistent-items">>, + <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, <<"retrieve-items">>, + <<"retrieve-subscriptions">>, <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access) -> + node_hometree:create_node_permission(Host, ServerHost, + Node, ParentNode, Owner, Access). create_node(NodeId, Owner) -> node_hometree:create_node(NodeId, Owner). @@ -124,20 +96,28 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree:delete_node(Removed). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options). +subscribe_node(NodeId, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_hometree:subscribe_node(NodeId, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_hometree:unsubscribe_node(NodeId, Sender, + Subscriber, SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_hometree:publish_item(NodeId, Publisher, Model, + MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). + node_hometree:remove_extra_items(NodeId, MaxItems, + ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_hometree:delete_item(NodeId, Publisher, + PublishModel, ItemId). purge_node(NodeId, Owner) -> node_hometree:purge_node(NodeId, Owner). @@ -152,7 +132,8 @@ get_affiliation(NodeId, Owner) -> node_hometree:get_affiliation(NodeId, Owner). set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). + node_hometree:set_affiliation(NodeId, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> node_hometree:get_entity_subscriptions(Host, Owner). @@ -164,41 +145,40 @@ get_subscriptions(NodeId, Owner) -> node_hometree:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). +get_states(NodeId) -> node_hometree:get_states(NodeId). get_state(NodeId, JID) -> node_hometree:get_state(NodeId, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeId, From) -> node_hometree:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_hometree:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). +node_to_path(Node) -> node_flat:node_to_path(Node). +path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_dag.erl b/src/mod_pubsub/node_dag.erl index b70169460..9a36a4c4a 100644 --- a/src/mod_pubsub/node_dag.erl +++ b/src/mod_pubsub/node_dag.erl @@ -16,47 +16,29 @@ %%% ==================================================================== -module(node_dag). + -author('bjc@kublai.com'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, path_to_node/1]). - init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -67,10 +49,10 @@ options() -> [{node_type, leaf} | node_hometree:options()]. features() -> - ["multi-collection" | node_hometree:features()]. + [<<"multi-collection">> | node_hometree:features()]. -create_node_permission(_Host, _ServerHost, _Node, _ParentNode, - _Owner, _Access) -> +create_node_permission(_Host, _ServerHost, _Node, + _ParentNode, _Owner, _Access) -> {result, true}. create_node(NodeID, Owner) -> @@ -81,39 +63,40 @@ delete_node(Removed) -> subscribe_node(NodeID, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeID, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, - Options). + node_hometree:subscribe_node(NodeID, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeID, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeID, Sender, Subscriber, SubID). + node_hometree:unsubscribe_node(NodeID, Sender, + Subscriber, SubID). -publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) -> - %% TODO: should look up the NodeTree plugin here. There's no - %% access to the Host of the request at this level, so for now we - %% just use nodetree_dag. +publish_item(NodeID, Publisher, Model, MaxItems, ItemID, + Payload) -> case nodetree_dag:get_node(NodeID) of - #pubsub_node{options = Options} -> - case find_opt(node_type, Options) of - collection -> - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "publish")}; - _ -> - node_hometree:publish_item(NodeID, Publisher, Model, - MaxItems, ItemID, Payload) - end; - Err -> - Err + #pubsub_node{options = Options} -> + case find_opt(node_type, Options) of + collection -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"publish">>)}; + _ -> + node_hometree:publish_item(NodeID, Publisher, Model, + MaxItems, ItemID, Payload) + end; + Err -> Err end. -find_opt(_, []) -> false; +find_opt(_, []) -> false; find_opt(Option, [{Option, Value} | _]) -> Value; -find_opt(Option, [_ | T]) -> find_opt(Option, T). +find_opt(Option, [_ | T]) -> find_opt(Option, T). remove_extra_items(NodeID, MaxItems, ItemIDs) -> - node_hometree:remove_extra_items(NodeID, MaxItems, ItemIDs). + node_hometree:remove_extra_items(NodeID, MaxItems, + ItemIDs). delete_item(NodeID, Publisher, PublishModel, ItemID) -> - node_hometree:delete_item(NodeID, Publisher, PublishModel, ItemID). + node_hometree:delete_item(NodeID, Publisher, + PublishModel, ItemID). purge_node(NodeID, Owner) -> node_hometree:purge_node(NodeID, Owner). @@ -128,7 +111,8 @@ get_affiliation(NodeID, Owner) -> node_hometree:get_affiliation(NodeID, Owner). set_affiliation(NodeID, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeID, Owner, Affiliation). + node_hometree:set_affiliation(NodeID, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> node_hometree:get_entity_subscriptions(Host, Owner). @@ -140,45 +124,40 @@ get_subscriptions(NodeID, Owner) -> node_hometree:get_subscriptions(NodeID, Owner). set_subscriptions(NodeID, Owner, Subscription, SubID) -> - node_hometree:set_subscriptions(NodeID, Owner, Subscription, SubID). + node_hometree:set_subscriptions(NodeID, Owner, + Subscription, SubID). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeID) -> - node_hometree:get_states(NodeID). +get_states(NodeID) -> node_hometree:get_states(NodeID). get_state(NodeID, JID) -> node_hometree:get_state(NodeID, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeID, From) -> node_hometree:get_items(NodeID, From). -get_items(NodeID, JID, AccessModel, PresenceSubscription, - RosterGroup, SubID) -> - node_hometree:get_items(NodeID, JID, AccessModel, PresenceSubscription, - RosterGroup, SubID). +get_items(NodeID, JID, AccessModel, + PresenceSubscription, RosterGroup, SubID) -> + node_hometree:get_items(NodeID, JID, AccessModel, + PresenceSubscription, RosterGroup, SubID). get_item(NodeID, ItemID) -> node_hometree:get_item(NodeID, ItemID). -get_item(NodeID, ItemID, JID, AccessModel, PresenceSubscription, - RosterGroup, SubID) -> +get_item(NodeID, ItemID, JID, AccessModel, + PresenceSubscription, RosterGroup, SubID) -> node_hometree:get_item(NodeID, ItemID, JID, AccessModel, - PresenceSubscription, RosterGroup, SubID). + PresenceSubscription, RosterGroup, SubID). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, ID) -> node_hometree:get_item_name(Host, Node, ID). -node_to_path(Node) -> - node_hometree:node_to_path(Node). - -path_to_node(Path) -> - node_hometree:path_to_node(Path). +node_to_path(Node) -> node_hometree:node_to_path(Node). +path_to_node(Path) -> node_hometree:path_to_node(Path). diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl index c03ebda2d..9b72af7e7 100644 --- a/src/mod_pubsub/node_dispatch.erl +++ b/src/mod_pubsub/node_dispatch.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.'' @@ -24,9 +26,11 @@ %%% ==================================================================== -module(node_dispatch). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). @@ -38,39 +42,18 @@ %%% This module can not work with virtual nodetree %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). - +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -79,16 +62,11 @@ terminate(Host, ServerHost) -> node_hometree:terminate(Host, ServerHost). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, open}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -97,23 +75,15 @@ options() -> {presence_based_delivery, false}]. features() -> - ["create-nodes", - "delete-nodes", - "instant-nodes", - "outcast-affiliation", - "persistent-items", - "publish", - %%"purge-nodes", - %%"retract-items", - %%"retrieve-affiliations", - "retrieve-items" - %%"retrieve-subscriptions", - %%"subscribe", - %%"subscription-notifications", - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + [<<"create-nodes">>, <<"delete-nodes">>, + <<"instant-nodes">>, <<"outcast-affiliation">>, + <<"persistent-items">>, <<"publish">>, + <<"retrieve-items">>]. + +create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access) -> + node_hometree:create_node_permission(Host, ServerHost, + Node, ParentNode, Owner, Access). create_node(NodeId, Owner) -> node_hometree:create_node(NodeId, Owner). @@ -121,88 +91,86 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree:delete_node(Removed). -subscribe_node(_NodeId, _Sender, _Subscriber, _AccessModel, - _SendLast, _PresenceSubscription, _RosterGroup, _Options) -> +subscribe_node(_NodeId, _Sender, _Subscriber, + _AccessModel, _SendLast, _PresenceSubscription, + _RosterGroup, _Options) -> {error, ?ERR_FORBIDDEN}. -unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubID) -> +unsubscribe_node(_NodeId, _Sender, _Subscriber, + _SubID) -> {error, ?ERR_FORBIDDEN}. -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - lists:foreach(fun(SubNode) -> - node_hometree:publish_item( - SubNode#pubsub_node.id, Publisher, Model, - MaxItems, ItemId, Payload) - end, nodetree_tree:get_subnodes(NodeId, Publisher, Publisher)). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + lists:foreach(fun (SubNode) -> + node_hometree:publish_item(SubNode#pubsub_node.id, + Publisher, Model, MaxItems, + ItemId, Payload) + end, + nodetree_tree:get_subnodes(NodeId, Publisher, + Publisher)). remove_extra_items(_NodeId, _MaxItems, ItemIds) -> {result, {ItemIds, []}}. -delete_item(_NodeId, _Publisher, _PublishModel, _ItemId) -> +delete_item(_NodeId, _Publisher, _PublishModel, + _ItemId) -> {error, ?ERR_ITEM_NOT_FOUND}. -purge_node(_NodeId, _Owner) -> - {error, ?ERR_FORBIDDEN}. +purge_node(_NodeId, _Owner) -> {error, ?ERR_FORBIDDEN}. -get_entity_affiliations(_Host, _Owner) -> - {result, []}. +get_entity_affiliations(_Host, _Owner) -> {result, []}. -get_node_affiliations(_NodeId) -> - {result, []}. +get_node_affiliations(_NodeId) -> {result, []}. -get_affiliation(_NodeId, _Owner) -> - {result, []}. +get_affiliation(_NodeId, _Owner) -> {result, []}. set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). + node_hometree:set_affiliation(NodeId, Owner, + Affiliation). -get_entity_subscriptions(_Host, _Owner) -> - {result, []}. +get_entity_subscriptions(_Host, _Owner) -> {result, []}. get_node_subscriptions(NodeId) -> - %% note: get_node_subscriptions is used for broadcasting - %% DO NOT REMOVE node_hometree:get_node_subscriptions(NodeId). -get_subscriptions(_NodeId, _Owner) -> - {result, []}. +get_subscriptions(_NodeId, _Owner) -> {result, []}. set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). +get_states(NodeId) -> node_hometree:get_states(NodeId). get_state(NodeId, JID) -> node_hometree:get_state(NodeId, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeId, From) -> node_hometree:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_hometree:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). +node_to_path(Node) -> node_flat:node_to_path(Node). +path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl index fc361e8a0..836858520 100644 --- a/src/mod_pubsub/node_flat.erl +++ b/src/mod_pubsub/node_flat.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.'' @@ -23,47 +25,28 @@ %%% ==================================================================== -module(node_flat). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). - +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -72,16 +55,11 @@ terminate(Host, ServerHost) -> node_hometree:terminate(Host, ServerHost). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, open}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -89,20 +67,20 @@ options() -> {deliver_notifications, true}, {presence_based_delivery, false}]. -features() -> - node_hometree:features(). +features() -> node_hometree:features(). %% use same code as node_hometree, but do not limite node to %% the home/localhost/user/... hierarchy %% any node is allowed -create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> +create_node_permission(Host, ServerHost, _Node, + _ParentNode, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), Allowed = case LOwner of - {"", Host, ""} -> - true; % pubsub service always allowed - _ -> - acl:match_rule(ServerHost, Access, LOwner) =:= allow - end, + {<<"">>, Host, <<"">>} -> + true; % pubsub service always allowed + _ -> + acl:match_rule(ServerHost, Access, LOwner) =:= allow + end, {result, Allowed}. create_node(NodeId, Owner) -> @@ -111,20 +89,28 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree:delete_node(Removed). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options). +subscribe_node(NodeId, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_hometree:subscribe_node(NodeId, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_hometree:unsubscribe_node(NodeId, Sender, + Subscriber, SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_hometree:publish_item(NodeId, Publisher, Model, + MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). + node_hometree:remove_extra_items(NodeId, MaxItems, + ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_hometree:delete_item(NodeId, Publisher, + PublishModel, ItemId). purge_node(NodeId, Owner) -> node_hometree:purge_node(NodeId, Owner). @@ -139,7 +125,8 @@ get_affiliation(NodeId, Owner) -> node_hometree:get_affiliation(NodeId, Owner). set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). + node_hometree:set_affiliation(NodeId, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> node_hometree:get_entity_subscriptions(Host, Owner). @@ -151,47 +138,49 @@ get_subscriptions(NodeId, Owner) -> node_hometree:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). +get_states(NodeId) -> node_hometree:get_states(NodeId). get_state(NodeId, JID) -> node_hometree:get_state(NodeId, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeId, From) -> node_hometree:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_hometree:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - [binary_to_list(Node)]. +node_to_path(Node) -> [(Node)]. path_to_node(Path) -> case Path of - % default slot - [Node] -> list_to_binary(Node); - % handle old possible entries, used when migrating database content to new format - [Node|_] when is_list(Node) -> list_to_binary(string:join([""|Path], "/")); - % default case (used by PEP for example) - _ -> list_to_binary(Path) + % default slot + [Node] -> iolist_to_binary(Node); + % handle old possible entries, used when migrating database content to new format + [Node | _] when is_binary(Node) -> + iolist_to_binary(str:join([<<"">> | Path], <<"/">>)); + % default case (used by PEP for example) + _ -> iolist_to_binary(Path) end. diff --git a/src/mod_pubsub/node_flat_odbc.erl b/src/mod_pubsub/node_flat_odbc.erl index 56cc867b7..4b686f65e 100644 --- a/src/mod_pubsub/node_flat_odbc.erl +++ b/src/mod_pubsub/node_flat_odbc.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.'' @@ -23,51 +25,30 @@ %%% ==================================================================== -module(node_flat_odbc). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_entity_subscriptions_for_send_last/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/7, - get_items/6, - get_items/3, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - get_last_items/3, - node_to_path/1, - path_to_node/1 - ]). - + get_node_subscriptions/1, get_subscriptions/2, + set_subscriptions/4, get_pending_nodes/2, get_states/1, + get_state/2, set_state/1, get_items/7, get_items/6, + get_items/3, get_items/2, get_item/7, get_item/2, + set_item/1, get_item_name/3, get_last_items/3, + node_to_path/1, path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree_odbc:init(Host, ServerHost, Opts). @@ -92,24 +73,23 @@ options() -> {max_payload_size, ?MAX_PAYLOAD_SIZE}, {send_last_published_item, on_sub_and_presence}, {deliver_notifications, true}, - {presence_based_delivery, false}, - {odbc, true}, + {presence_based_delivery, false}, {odbc, true}, {rsm, true}]. -features() -> - node_hometree_odbc:features(). +features() -> node_hometree_odbc:features(). %% use same code as node_hometree_odbc, but do not limite node to %% the home/localhost/user/... hierarchy %% any node is allowed -create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> +create_node_permission(Host, ServerHost, _Node, + _ParentNode, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), Allowed = case LOwner of - {"", Host, ""} -> - true; % pubsub service always allowed - _ -> - acl:match_rule(ServerHost, Access, LOwner) =:= allow - end, + {<<"">>, Host, <<"">>} -> + true; % pubsub service always allowed + _ -> + acl:match_rule(ServerHost, Access, LOwner) =:= allow + end, {result, Allowed}. create_node(NodeId, Owner) -> @@ -118,20 +98,29 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree_odbc:delete_node(Removed). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree_odbc:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options). +subscribe_node(NodeId, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_hometree_odbc:subscribe_node(NodeId, Sender, + Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, + Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_hometree_odbc:unsubscribe_node(NodeId, Sender, + Subscriber, SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_hometree_odbc:publish_item(NodeId, Publisher, + Model, MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds). + node_hometree_odbc:remove_extra_items(NodeId, MaxItems, + ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_hometree_odbc:delete_item(NodeId, Publisher, + PublishModel, ItemId). purge_node(NodeId, Owner) -> node_hometree_odbc:purge_node(NodeId, Owner). @@ -146,13 +135,16 @@ get_affiliation(NodeId, Owner) -> node_hometree_odbc:get_affiliation(NodeId, Owner). set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation). + node_hometree_odbc:set_affiliation(NodeId, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> - node_hometree_odbc:get_entity_subscriptions(Host, Owner). + node_hometree_odbc:get_entity_subscriptions(Host, + Owner). get_entity_subscriptions_for_send_last(Host, Owner) -> - node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner). + node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, + Owner). get_node_subscriptions(NodeId) -> node_hometree_odbc:get_node_subscriptions(NodeId). @@ -161,7 +153,8 @@ get_subscriptions(NodeId, Owner) -> node_hometree_odbc:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree_odbc:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree_odbc:get_pending_nodes(Host, Owner). @@ -172,26 +165,34 @@ get_states(NodeId) -> get_state(NodeId, JID) -> node_hometree_odbc:get_state(NodeId, JID). -set_state(State) -> - node_hometree_odbc:set_state(State). +set_state(State) -> node_hometree_odbc:set_state(State). get_items(NodeId, From) -> node_hometree_odbc:get_items(NodeId, From). + get_items(NodeId, From, RSM) -> node_hometree_odbc:get_items(NodeId, From, RSM). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM). + +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, none). + +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM) -> + node_hometree_odbc:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). get_item(NodeId, ItemId) -> node_hometree_odbc:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree_odbc:get_item(NodeId, ItemId, JID, + AccessModel, PresenceSubscription, RosterGroup, + SubId). -set_item(Item) -> - node_hometree_odbc:set_item(Item). +set_item(Item) -> node_hometree_odbc:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree_odbc:get_item_name(Host, Node, Id). @@ -199,16 +200,15 @@ get_item_name(Host, Node, Id) -> get_last_items(NodeId, From, Count) -> node_hometree_odbc:get_last_items(NodeId, From, Count). -node_to_path(Node) -> - [binary_to_list(Node)]. +node_to_path(Node) -> [(Node)]. path_to_node(Path) -> - case Path of - % default slot - [Node] -> list_to_binary(Node); - % handle old possible entries, used when migrating database content to new format - [Node|_] when is_list(Node) -> list_to_binary(string:join([""|Path], "/")); - % default case (used by PEP for example) - _ -> list_to_binary(Path) + case Path of + % default slot + [Node] -> iolist_to_binary(Node); + % handle old possible entries, used when migrating database content to new format + [Node | _] when is_binary(Node) -> + iolist_to_binary(str:join([<<"">> | Path], <<"/">>)); + % default case (used by PEP for example) + _ -> iolist_to_binary(Path) end. - diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl index 522677a15..57507e67b 100644 --- a/src/mod_pubsub/node_hometree.erl +++ b/src/mod_pubsub/node_hometree.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.'' @@ -39,46 +41,28 @@ %%% improvements.</p> -module(node_hometree). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). %% ================ %% API definition @@ -103,9 +87,9 @@ init(_Host, _ServerHost, _Options) -> {attributes, record_info(fields, pubsub_item)}]), ItemsFields = record_info(fields, pubsub_item), case mnesia:table_info(pubsub_item, attributes) of - ItemsFields -> ok; - _ -> - mnesia:transform_table(pubsub_item, ignore, ItemsFields) + ItemsFields -> ok; + _ -> + mnesia:transform_table(pubsub_item, ignore, ItemsFields) end, ok. @@ -114,8 +98,9 @@ init(_Host, _ServerHost, _Options) -> %% ServerHost = string() %% @doc <p>Called during pubsub modules termination. Any pubsub plugin must %% implement this function. It can return anything.</p> -terminate(_Host, _ServerHost) -> - ok. +terminate(_Host, _ServerHost) -> ok. + +-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). %% @spec () -> Options %% Options = [mod_pubsub:nodeOption()] @@ -135,16 +120,11 @@ terminate(_Host, _ServerHost) -> %% {send_last_published_item, never}, %% {presence_based_delivery, false}]''' options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, open}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -155,30 +135,8 @@ options() -> %% @spec () -> Features %% Features = [string()] %% @doc Returns the node features +-spec(features/0 :: () -> Features::[binary(),...]). features() -> - ["create-nodes", - "auto-create", - "access-authorize", - "delete-nodes", - "delete-items", - "get-pending", - "instant-nodes", - "manage-subscriptions", - "modify-affiliations", - "multi-subscribe", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications", - "subscription-options" - ]. - %% @spec (Host, ServerHost, NodeId, ParentNodeId, Owner, Access) -> {result, Allowed} %% Host = mod_pubsub:hostPubsub() %% ServerHost = string() @@ -199,29 +157,58 @@ features() -> %% module by implementing this function like this: %% ```check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access) -> %% node_default:check_create_user_permission(Host, ServerHost, NodeId, ParentNodeId, Owner, Access).'''</p> + [<<"create-nodes">>, <<"auto-create">>, + <<"access-authorize">>, <<"delete-nodes">>, + <<"delete-items">>, <<"get-pending">>, + <<"instant-nodes">>, <<"manage-subscriptions">>, + <<"modify-affiliations">>, <<"multi-subscribe">>, + <<"outcast-affiliation">>, <<"persistent-items">>, + <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, <<"retrieve-items">>, + <<"retrieve-subscriptions">>, <<"subscribe">>, + <<"subscription-notifications">>, + <<"subscription-options">>]. + +-spec(create_node_permission/6 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + NodeId :: mod_pubsub:nodeId(), + _ParentNodeId :: mod_pubsub:nodeId(), + Owner :: jid(), + Access :: atom()) + -> {result, boolean()} +). + create_node_permission(Host, ServerHost, NodeId, _ParentNodeId, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), {User, Server, _Resource} = LOwner, Allowed = case LOwner of - {"", Host, ""} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case node_to_path(NodeId) of - ["home", Server, User | _] -> true; - _ -> false - end; + {<<"">>, Host, <<"">>} -> + true; % pubsub service always allowed _ -> - false - end - end, + case acl:match_rule(ServerHost, Access, LOwner) of + allow -> + case node_to_path(NodeId) of + [<<"home">>, Server, User | _] -> true; + _ -> false + end; + _ -> false + end + end, {result, Allowed}. %% @spec (NodeIdx, Owner) -> {result, {default, broadcast}} %% NodeIdx = mod_pubsub:nodeIdx() %% Owner = mod_pubsub:jid() %% @doc <p></p> +-spec(create_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} +). + create_node(NodeIdx, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), set_state(#pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}), @@ -232,21 +219,27 @@ create_node(NodeIdx, Owner) -> %% Reply = [{mod_pubsub:pubsubNode(), %% [{mod_pubsub:ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}] %% @doc <p>purge items of deleted nodes after effective deletion.</p> +-spec(delete_node/1 :: +( + Nodes :: [mod_pubsub:pubsubNode(),...]) + -> {result, + {default, broadcast, + [{mod_pubsub:pubsubNode(), + [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]},...]},...] + } + } +). delete_node(Nodes) -> - Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Ss}) -> - lists:map(fun(S) -> - {J, S} - end, Ss) - end, - Reply = lists:map( - fun(#pubsub_node{id = NodeId} = PubsubNode) -> - {result, States} = get_states(NodeId), - lists:foreach( - fun(#pubsub_state{stateid = {LJID, _}, items = Items}) -> - del_items(NodeId, Items), - del_state(NodeId, LJID) - end, States), - {PubsubNode, lists:flatmap(Tr, States)} + Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) -> + lists:map(fun (S) -> {J, S} end, Ss) + end, + Reply = lists:map(fun (#pubsub_node{id = NodeIdx} = PubsubNode) -> + {result, States} = get_states(NodeIdx), + lists:foreach(fun (#pubsub_state{stateid = {LJID, _}, items = Items}) -> + del_items(NodeIdx, Items), + del_state(NodeIdx, LJID) + end, States), + {PubsubNode, lists:flatmap(Tr, States)} end, Nodes), {result, {default, broadcast, Reply}}. @@ -295,66 +288,84 @@ delete_node(Nodes) -> %% to completly disable persistance.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> +-spec(subscribe_node/8 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + AccessModel :: mod_pubsub:accessModel(), + SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + Options :: mod_pubsub:subOptions()) + -> {result, {default, subscribed, mod_pubsub:subId()}} + | {result, {default, subscribed, mod_pubsub:subId(), send_last}} + | {result, {default, pending, mod_pubsub:subId()}} + %%% + | {error, xmlel()} +). subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> SubKey = jlib:jid_tolower(Subscriber), GenKey = jlib:jid_remove_resource(SubKey), - Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), + Authorized = + jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == + GenKey, GenState = get_state(NodeIdx, GenKey), SubState = case SubKey of - GenKey -> GenState; - _ -> get_state(NodeIdx, SubKey) - end, + GenKey -> GenState; + _ -> get_state(NodeIdx, SubKey) + end, Affiliation = GenState#pubsub_state.affiliation, Subscriptions = SubState#pubsub_state.subscriptions, - Whitelisted = lists:member(Affiliation, [member, publisher, owner]), - PendingSubscription = lists:any(fun({pending, _}) -> true; - (_) -> false - end, Subscriptions), - if - not Authorized -> - %% JIDs do not match - {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")}; - Affiliation == outcast -> - %% Requesting entity is blocked - {error, ?ERR_FORBIDDEN}; - PendingSubscription -> - %% Requesting entity has pending subscription - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")}; - (AccessModel == presence) and (not PresenceSubscription) -> - %% Entity is not authorized to create a subscription (presence subscription required) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; - (AccessModel == roster) and (not RosterGroup) -> - %% Entity is not authorized to create a subscription (not in roster group) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; - (AccessModel == whitelist) and (not Whitelisted) -> - %% Node has whitelist access model and entity lacks required affiliation - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - %%ForbiddenAnonymous -> - %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; - true -> - case pubsub_subscription:add_subscription(Subscriber, NodeIdx, Options) of - SubId when is_list(SubId) -> - NewSub = case AccessModel of - authorize -> pending; - _ -> subscribed - end, - set_state(SubState#pubsub_state{subscriptions = [{NewSub, SubId} | Subscriptions]}), + Whitelisted = lists:member(Affiliation, + [member, publisher, owner]), + PendingSubscription = lists:any(fun ({pending, _}) -> + true; + (_) -> false + end, + Subscriptions), + if not Authorized -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; + Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; + PendingSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"pending-subscription">>)}; + (AccessModel == presence) and + not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + %%ForbiddenAnonymous -> + %% % Requesting entity is anonymous + %% {error, ?ERR_FORBIDDEN}; + true -> + SubId = pubsub_subscription:add_subscription(Subscriber, NodeIdx, Options), + NewSub = case AccessModel of + authorize -> pending; + _ -> subscribed + end, + set_state(SubState#pubsub_state{subscriptions = + [{NewSub, SubId} | Subscriptions]}), case {NewSub, SendLast} of - {subscribed, never} -> - {result, {default, subscribed, SubId}}; - {subscribed, _} -> - {result, {default, subscribed, SubId, send_last}}; - {_, _} -> - {result, {default, pending, SubId}} - end; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end + {subscribed, never} -> + {result, {default, subscribed, SubId}}; + {subscribed, _} -> + {result, {default, subscribed, SubId, send_last}}; + {_, _} -> {result, {default, pending, SubId}} + end end. %% @spec (NodeIdx, Sender, Subscriber, SubId) -> {error, Reason} | {result, default} @@ -364,76 +375,100 @@ subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, %% SubId = mod_pubsub:subId() %% Reason = mod_pubsub:stanzaError() %% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p> +-spec(unsubscribe_node/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + SubId :: subId()) + -> {result, default} + % + | {error, xmlel()} +). + unsubscribe_node(NodeIdx, Sender, Subscriber, SubId) -> SubKey = jlib:jid_tolower(Subscriber), GenKey = jlib:jid_remove_resource(SubKey), - Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), + Authorized = + jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == + GenKey, GenState = get_state(NodeIdx, GenKey), SubState = case SubKey of - GenKey -> GenState; - _ -> get_state(NodeIdx, SubKey) - end, - Subscriptions = lists:filter(fun({_Sub, _SubId}) -> true; - (_SubId) -> false - end, SubState#pubsub_state.subscriptions), + GenKey -> GenState; + _ -> get_state(NodeIdx, SubKey) + end, + Subscriptions = lists:filter(fun ({_Sub, _SubId}) -> + true; + (_SubId) -> false + end, + SubState#pubsub_state.subscriptions), SubIdExists = case SubId of - [] -> false; - List when is_list(List) -> true; - _ -> false + <<>> -> false; + Binary when is_binary(Binary) -> true; + _ -> false end, if - %% Requesting entity is prohibited from unsubscribing entity - not Authorized -> - {error, ?ERR_FORBIDDEN}; - %% Entity did not specify SubId - %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %% Invalid subscription identifier - %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - %% Requesting entity is not a subscriber - Subscriptions == [] -> - {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}; - %% Subid supplied, so use that. - SubIdExists -> - Sub = first_in_list(fun(S) -> - case S of - {_Sub, SubId} -> true; - _ -> false - end - end, SubState#pubsub_state.subscriptions), - case Sub of - {value, S} -> - delete_subscriptions(SubKey, NodeIdx, [S], SubState), - {result, default}; - false -> - {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")} - end; - %% Asking to remove all subscriptions to the given node - SubId == all -> - delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState), - {result, default}; - %% No subid supplied, but there's only one matching subscription - length(Subscriptions) == 1 -> - delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState), - {result, default}; - %% No subid and more than one possible subscription match. - true -> - {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")} + %% Requesting entity is prohibited from unsubscribing entity + not Authorized -> {error, ?ERR_FORBIDDEN}; + %% Entity did not specify SubId + %%SubId == "", ?? -> + %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %% Invalid subscription identifier + %%InvalidSubId -> + %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %% Requesting entity is not a subscriber + Subscriptions == [] -> + {error, + ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), + <<"not-subscribed">>)}; + %% Subid supplied, so use that. + SubIdExists -> + Sub = first_in_list(fun (S) -> + case S of + {_Sub, SubId} -> true; + _ -> false + end + end, + SubState#pubsub_state.subscriptions), + case Sub of + {value, S} -> + delete_subscriptions(SubKey, NodeIdx, [S], SubState), + {result, default}; + false -> + {error, + ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), + <<"not-subscribed">>)} + end; + %% Asking to remove all subscriptions to the given node + SubId == all -> + delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState), + {result, default}; + %% No subid supplied, but there's only one matching subscription + length(Subscriptions) == 1 -> + delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState), + {result, default}; + %% No subid and more than one possible subscription match. + true -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} end. +-spec(delete_subscriptions/4 :: +( + SubKey :: ljid(), + NodeIdx :: mod_pubsub:nodeIdx(), + Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}], + SubState :: mod_pubsub:pubsubState()) + -> ok +). delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState) -> - NewSubs = lists:foldl(fun({Subscription, SubId}, Acc) -> - pubsub_subscription:delete_subscription(SubKey, NodeIdx, SubId), - Acc -- [{Subscription, SubId}] + NewSubs = lists:foldl(fun ({Subscription, SubId}, Acc) -> + pubsub_subscription:delete_subscription(SubKey, NodeIdx, SubId), + Acc -- [{Subscription, SubId}] end, SubState#pubsub_state.subscriptions, Subscriptions), case {SubState#pubsub_state.affiliation, NewSubs} of - {none, []} -> - % Just a regular subscriber, and this is final item, so - % delete the state. - del_state(NodeIdx, SubKey); - _ -> - set_state(SubState#pubsub_state{subscriptions = NewSubs}) + {none, []} -> del_state(NodeIdx, SubKey); + _ -> set_state(SubState#pubsub_state{subscriptions = NewSubs}) end. %% @spec (NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> @@ -476,49 +511,62 @@ delete_subscriptions(SubKey, NodeIdx, Subscriptions, SubState) -> %% to completly disable persistance.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> +-spec(publish_item/6 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + Max_Items :: non_neg_integer(), + ItemId :: <<>> | mod_pubsub:itemId(), + Payload :: mod_pubsub:payload()) + -> {result, {default, broadcast, [mod_pubsub:itemId()]}} + %%% + | {error, xmlel()} +). + publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> SubKey = jlib:jid_tolower(Publisher), GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeIdx, GenKey), SubState = case SubKey of - GenKey -> GenState; - _ -> get_state(NodeIdx, SubKey) - end, + GenKey -> GenState; + _ -> get_state(NodeIdx, SubKey) + end, Affiliation = GenState#pubsub_state.affiliation, Subscribed = case PublishModel of - subscribers -> is_subscribed(SubState#pubsub_state.subscriptions); - _ -> undefined - end, - if - not ((PublishModel == open) - or ((PublishModel == publishers) - and ((Affiliation == owner) or (Affiliation == publisher))) - or (Subscribed == true)) -> - %% Entity does not have sufficient privileges to publish to node - {error, ?ERR_FORBIDDEN}; - true -> - %% TODO: check creation, presence, roster - if MaxItems > 0 -> - Now = now(), - PubId = {Now, SubKey}, - Item = case get_item(NodeIdx, ItemId) of - {result, OldItem} -> - OldItem#pubsub_item{modification = PubId, - payload = Payload}; - _ -> - #pubsub_item{itemid = {ItemId, NodeIdx}, - creation = {Now, GenKey}, - modification = PubId, - payload = Payload} - end, - Items = [ItemId | GenState#pubsub_state.items--[ItemId]], - {result, {NI, OI}} = remove_extra_items(NodeIdx, MaxItems, Items), - set_item(Item), - set_state(GenState#pubsub_state{items = NI}), - {result, {default, broadcast, OI}}; - true -> - {result, {default, broadcast, []}} - end + subscribers -> + is_subscribed(SubState#pubsub_state.subscriptions); + _ -> undefined + end, + if not + ((PublishModel == open) or + (PublishModel == publishers) and + ((Affiliation == owner) or (Affiliation == publisher)) + or (Subscribed == true)) -> + {error, ?ERR_FORBIDDEN}; + true -> + if MaxItems > 0 -> + Now = now(), + PubId = {Now, SubKey}, + Item = case get_item(NodeIdx, ItemId) of + {result, OldItem} -> + OldItem#pubsub_item{modification = PubId, + payload = Payload}; + _ -> + #pubsub_item{itemid = {ItemId, NodeIdx}, + creation = {Now, GenKey}, + modification = PubId, + payload = Payload} + end, + Items = [ItemId | GenState#pubsub_state.items -- + [ItemId]], + {result, {NI, OI}} = remove_extra_items(NodeIdx, + MaxItems, Items), + set_item(Item), + set_state(GenState#pubsub_state{items = NI}), + {result, {default, broadcast, OI}}; + true -> {result, {default, broadcast, []}} + end end. %% @spec (NodeIdx, MaxItems, ItemIds) -> {result, {NewItemIds,OldItemIds}} @@ -537,14 +585,22 @@ publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> %% plugin is using the default pubsub storage), it can implements this function like this: %% ```remove_extra_items(NodeIdx, MaxItems, ItemIds) -> %% node_default:remove_extra_items(NodeIdx, MaxItems, ItemIds).'''</p> +-spec(remove_extra_items/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Max_Items :: unlimited | non_neg_integer(), + ItemIds :: [mod_pubsub:itemId()]) + -> {result, + {NewItems::[mod_pubsub:itemId()], + OldItems::[mod_pubsub:itemId()]} + } +). remove_extra_items(_NodeIdx, unlimited, ItemIds) -> {result, {ItemIds, []}}; remove_extra_items(NodeIdx, MaxItems, ItemIds) -> NewItems = lists:sublist(ItemIds, MaxItems), OldItems = lists:nthtail(length(NewItems), ItemIds), - %% Remove extra items: del_items(NodeIdx, OldItems), - %% Return the new items list: {result, {NewItems, OldItems}}. %% @spec (NodeIdx, Publisher, PublishModel, ItemId) -> @@ -557,74 +613,83 @@ remove_extra_items(NodeIdx, MaxItems, ItemIds) -> %% @doc <p>Triggers item deletion.</p> %% <p>Default plugin: The user performing the deletion must be the node owner %% or a publisher, or PublishModel being open.</p> +-spec(delete_item/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + ItemId :: <<>> | mod_pubsub:itemId()) + -> {result, {default, broadcast}} + %%% + | {error, xmlel()} +). delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> SubKey = jlib:jid_tolower(Publisher), GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeIdx, GenKey), #pubsub_state{affiliation = Affiliation, items = Items} = GenState, - Allowed = (Affiliation == publisher) orelse (Affiliation == owner) - orelse (PublishModel == open) - orelse case get_item(NodeIdx, ItemId) of - {result, #pubsub_item{creation = {_, GenKey}}} -> true; - _ -> false - end, - if - not Allowed -> - %% Requesting entity does not have sufficient privileges - {error, ?ERR_FORBIDDEN}; - true -> - case lists:member(ItemId, Items) of - true -> - del_item(NodeIdx, ItemId), - set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}), - {result, {default, broadcast}}; - false -> - case Affiliation of - owner -> - %% Owner can delete any items from its own node - {result, States} = get_states(NodeIdx), - lists:foldl( - fun(#pubsub_state{items = PI} = S, Res) -> - case lists:member(ItemId, PI) of - true -> - del_item(NodeIdx, ItemId), - set_state(S#pubsub_state{items = lists:delete(ItemId, PI)}), - {result, {default, broadcast}}; - false -> - Res - end; - (_, Res) -> - Res - end, {error, ?ERR_ITEM_NOT_FOUND}, States); - _ -> - %% Non-existent node or item - {error, ?ERR_ITEM_NOT_FOUND} - end - end + Allowed = Affiliation == publisher orelse + Affiliation == owner orelse + PublishModel == open orelse + case get_item(NodeIdx, ItemId) of + {result, #pubsub_item{creation = {_, GenKey}}} -> true; + _ -> false + end, + if not Allowed -> {error, ?ERR_FORBIDDEN}; + true -> + case lists:member(ItemId, Items) of + true -> + del_item(NodeIdx, ItemId), + set_state(GenState#pubsub_state{items = lists:delete(ItemId, Items)}), + {result, {default, broadcast}}; + false -> + case Affiliation of + owner -> + {result, States} = get_states(NodeIdx), + lists:foldl(fun (#pubsub_state{items = PI} = S, Res) -> + case lists:member(ItemId, PI) of + true -> + del_item(NodeIdx, ItemId), + set_state(S#pubsub_state{items + = lists:delete(ItemId, PI)}), + {result, {default, broadcast}}; + false -> Res + end; + (_, Res) -> Res + end, + {error, ?ERR_ITEM_NOT_FOUND}, States); + _ -> {error, ?ERR_ITEM_NOT_FOUND} + end + end end. %% @spec (NodeIdx, Owner) -> {error, Reason} | {result, {default, broadcast}} %% NodeIdx = mod_pubsub:nodeIdx() %% Owner = mod_pubsub:jid() %% Reason = mod_pubsub:stanzaError() +-spec(purge_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} + | {error, xmlel()} +). + purge_node(NodeIdx, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeIdx, GenKey), case GenState of - #pubsub_state{affiliation = owner} -> - {result, States} = get_states(NodeIdx), - lists:foreach( - fun(#pubsub_state{items = []}) -> - ok; - (#pubsub_state{items = Items} = S) -> - del_items(NodeIdx, Items), - set_state(S#pubsub_state{items = []}) - end, States), - {result, {default, broadcast}}; - _ -> - %% Entity is not owner - {error, ?ERR_FORBIDDEN} + #pubsub_state{affiliation = owner} -> + {result, States} = get_states(NodeIdx), + lists:foreach(fun (#pubsub_state{items = []}) -> ok; + (#pubsub_state{items = Items} = S) -> + del_items(NodeIdx, Items), + set_state(S#pubsub_state{items = []}) + end, + States), + {result, {default, broadcast}}; + _ -> {error, ?ERR_FORBIDDEN} end. %% @spec (Host, Owner) -> {result, Reply} @@ -638,44 +703,76 @@ purge_node(NodeIdx, Owner) -> %% the default PubSub module. Otherwise, it should return its own affiliation, %% that will be added to the affiliation stored in the main %% <tt>pubsub_state</tt> table.</p> +-spec(get_entity_affiliations/2 :: +( + Host :: mod_pubsub:host(), + Owner :: jid()) + -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} +). + get_entity_affiliations(Host, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}), - NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A}|Acc]; - _ -> Acc - end - end, [], States), + NodeTree = case catch + ets:lookup(gen_mod:get_module_proc(Host, config), + nodetree) + of + [{nodetree, N}] -> N; + _ -> nodetree_tree + end, + Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, + Acc) -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {Host, _}} = Node -> + [{Node, A} | Acc]; + _ -> Acc + end + end, + [], States), {result, Reply}. -get_node_affiliations(NodeId) -> - {result, States} = get_states(NodeId), - Tr = fun(#pubsub_state{stateid = {J, _}, affiliation = A}) -> +-spec(get_node_affiliations/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [{ljid(), mod_pubsub:affiliation()}]} +). + +get_node_affiliations(NodeIdx) -> + {result, States} = get_states(NodeIdx), + Tr = fun (#pubsub_state{stateid = {J, _}, + affiliation = A}) -> {J, A} end, {result, lists:map(Tr, States)}. -get_affiliation(NodeId, Owner) -> +-spec(get_affiliation/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, mod_pubsub:affiliation()} +). + +get_affiliation(NodeIdx, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeId, GenKey), - {result, GenState#pubsub_state.affiliation}. + #pubsub_state{affiliation = Affiliation} = get_state(NodeIdx, GenKey), + {result, Affiliation}. -set_affiliation(NodeId, Owner, Affiliation) -> +-spec(set_affiliation/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid(), + Affiliation :: mod_pubsub:affiliation()) + -> ok +). +set_affiliation(NodeIdx, Owner, Affiliation) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeId, GenKey), + GenState = get_state(NodeIdx, GenKey), case {Affiliation, GenState#pubsub_state.subscriptions} of - {none, none} -> - del_state(NodeId, GenKey); - _ -> - set_state(GenState#pubsub_state{affiliation = Affiliation}) + {none, []} -> del_state(NodeIdx, GenKey); + _ -> set_state(GenState#pubsub_state{affiliation = Affiliation}) end. %% @spec (Host, Owner) -> @@ -695,107 +792,165 @@ set_affiliation(NodeId, Owner, Affiliation) -> %% the default PubSub module. Otherwise, it should return its own affiliation, %% that will be added to the affiliation stored in the main %% <tt>pubsub_state</tt> table.</p> +-spec(get_entity_subscriptions/2 :: +( + Host :: mod_pubsub:host(), + Owner :: jid()) + -> {result, + [{mod_pubsub:pubsubNode(), + mod_pubsub:subscription(), + mod_pubsub:subId(), + ljid()}] + } +). + get_entity_subscriptions(Host, Owner) -> {U, D, _} = SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), States = case SubKey of - GenKey -> mnesia:match_object( - #pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'}); - _ -> mnesia:match_object( - #pubsub_state{stateid = {GenKey, '_'}, _ = '_'}) - ++ mnesia:match_object( - #pubsub_state{stateid = {SubKey, '_'}, _ = '_'}) - end, - NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {Host, _}} = Node -> - lists:foldl(fun({Sub, SubId}, Acc2) -> - [{Node, Sub, SubId, J} | Acc2]; - (S, Acc2) -> - [{Node, S, J} | Acc2] - end, Acc, Ss); - _ -> Acc - end - end, [], States), + GenKey -> + mnesia:match_object(#pubsub_state{stateid = + {{U, D, '_'}, '_'}, + _ = '_'}); + _ -> + mnesia:match_object(#pubsub_state{stateid = + {GenKey, '_'}, + _ = '_'}) + ++ + mnesia:match_object(#pubsub_state{stateid = + {SubKey, '_'}, + _ = '_'}) + end, + NodeTree = case catch + ets:lookup(gen_mod:get_module_proc(Host, config), + nodetree) + of + [{nodetree, N}] -> N; + _ -> nodetree_tree + end, + Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, subscriptions = Ss}, + Acc) -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {Host, _}} = Node -> + lists:foldl(fun ({Sub, SubId}, Acc2) -> + [{Node, Sub, SubId, J} | Acc2] + end, + Acc, Ss); + _ -> Acc + end + end, + [], States), {result, Reply}. -get_node_subscriptions(NodeId) -> - {result, States} = get_states(NodeId), - Tr = fun(#pubsub_state{stateid = {J, _}, subscriptions = Subscriptions}) -> - %% TODO: get rid of cases to handle non-list subscriptions +-spec(get_node_subscriptions/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, + [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}] | + [{ljid(), none},...] + } +). +get_node_subscriptions(NodeIdx) -> + {result, States} = get_states(NodeIdx), + Tr = fun (#pubsub_state{stateid = {J, _}, + subscriptions = Subscriptions}) -> case Subscriptions of - [_|_] -> - lists:foldl(fun({S, SubId}, Acc) -> - [{J, S, SubId} | Acc]; - (S, Acc) -> - [{J, S} | Acc] - end, [], Subscriptions); - [] -> - []; - _ -> - [{J, none}] + [_ | _] -> + lists:foldl(fun ({S, SubId}, Acc) -> + [{J, S, SubId} | Acc] + end, + [], Subscriptions); + [] -> []; + _ -> [{J, none}] end end, {result, lists:flatmap(Tr, States)}. -get_subscriptions(NodeId, Owner) -> +-spec(get_subscriptions/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid()) + -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]} +). +get_subscriptions(NodeIdx, Owner) -> SubKey = jlib:jid_tolower(Owner), - SubState = get_state(NodeId, SubKey), + SubState = get_state(NodeIdx, SubKey), {result, SubState#pubsub_state.subscriptions}. -set_subscriptions(NodeId, Owner, Subscription, SubId) -> +-spec(set_subscriptions/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid(), + Subscription :: mod_pubsub:subscription(), + SubId :: mod_pubsub:subId()) + -> ok + %%% + | {error, xmlel()} +). + +set_subscriptions(NodeIdx, Owner, Subscription, SubId) -> SubKey = jlib:jid_tolower(Owner), - SubState = get_state(NodeId, SubKey), + SubState = get_state(NodeIdx, SubKey), case {SubId, SubState#pubsub_state.subscriptions} of - {_, []} -> - case Subscription of - none -> {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "not-subscribed")}; - _ -> new_subscription(NodeId, Owner, Subscription, SubState) - end; - {"", [{_, SID}]} -> - case Subscription of - none -> unsub_with_subid(NodeId, SID, SubState); - _ -> replace_subscription({Subscription, SID}, SubState) - end; - {"", [_|_]} -> - {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - _ -> - case Subscription of - none -> unsub_with_subid(NodeId, SubId, SubState); - _ -> replace_subscription({Subscription, SubId}, SubState) - end + {_, []} -> + case Subscription of + none -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), + <<"not-subscribed">>)}; + _ -> + new_subscription(NodeIdx, Owner, Subscription, SubState) + end; + {<<>>, [{_, SID}]} -> + case Subscription of + none -> unsub_with_subid(NodeIdx, SID, SubState); + _ -> replace_subscription({Subscription, SID}, SubState) + end; + {<<>>, [_ | _]} -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), + <<"subid-required">>)}; + _ -> + case Subscription of + none -> unsub_with_subid(NodeIdx, SubId, SubState); + _ -> + replace_subscription({Subscription, SubId}, SubState) + end end. replace_subscription(NewSub, SubState) -> - NewSubs = replace_subscription(NewSub, - SubState#pubsub_state.subscriptions, []), + NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []), set_state(SubState#pubsub_state{subscriptions = NewSubs}). -replace_subscription(_, [], Acc) -> - Acc; +replace_subscription(_, [], Acc) -> Acc; replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) -> replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]). new_subscription(NodeId, Owner, Subscription, SubState) -> SubId = pubsub_subscription:add_subscription(Owner, NodeId, []), Subscriptions = SubState#pubsub_state.subscriptions, - set_state(SubState#pubsub_state{subscriptions = [{Subscription, SubId} | Subscriptions]}), + set_state(SubState#pubsub_state{subscriptions = + [{Subscription, SubId} | Subscriptions]}), {Subscription, SubId}. -unsub_with_subid(NodeId, SubId, SubState) -> +-spec(unsub_with_subid/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + SubId :: mod_pubsub:subId(), + SubState :: mod_pubsub:pubsubState()) + -> ok +). +unsub_with_subid(NodeIdx, SubId, #pubsub_state{stateid = {Entity, _}} = SubState) -> pubsub_subscription:delete_subscription(SubState#pubsub_state.stateid, - NodeId, SubId), - NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end, + NodeIdx, SubId), + NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID + end, SubState#pubsub_state.subscriptions), case {NewSubs, SubState#pubsub_state.affiliation} of - {[], none} -> - del_state(NodeId, element(1, SubState#pubsub_state.stateid)); - _ -> - set_state(SubState#pubsub_state{subscriptions = NewSubs}) + {[], none} -> + del_state(NodeIdx, Entity); + _ -> + set_state(SubState#pubsub_state{subscriptions = NewSubs}) end. %% TODO : doc @@ -805,45 +960,61 @@ unsub_with_subid(NodeId, SubId, SubState) -> %% Reply = [] | [mod_pubsub:nodeId()] %% @doc <p>Returns a list of Owner's nodes on Host with pending %% subscriptions.</p> +-spec(get_pending_nodes/2 :: +( + Host :: mod_pubsub:host(), + Owner :: jid()) + -> {result, [mod_pubsub:nodeId()]} +). + get_pending_nodes(Host, Owner) -> GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)), - States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, - affiliation = owner, - _ = '_'}), - NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States], - NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of - [{nodetree, N}] -> N; - _ -> nodetree_tree + States = mnesia:match_object(#pubsub_state{stateid = + {GenKey, '_'}, + affiliation = owner, _ = '_'}), + NodeIDs = [ID + || #pubsub_state{stateid = {_, ID}} <- States], + NodeTree = case catch + ets:lookup(gen_mod:get_module_proc(Host, config), + nodetree) + of + [{nodetree, N}] -> N; + _ -> nodetree_tree end, - Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) -> - case lists:member(NID, NodeIDs) of - true -> - case get_nodes_helper(NodeTree, S) of - {value, Node} -> [Node | Acc]; - false -> Acc - end; - false -> - Acc - end - end, [], pubsub_state), + Reply = mnesia:foldl(fun (#pubsub_state{stateid = {_, NID}} = S, + Acc) -> + case lists:member(NID, NodeIDs) of + true -> + case get_nodes_helper(NodeTree, S) of + {value, Node} -> [Node | Acc]; + false -> Acc + end; + false -> Acc + end + end, + [], pubsub_state), {result, Reply}. -get_nodes_helper(NodeTree, - #pubsub_state{stateid = {_, N}, subscriptions = Subs}) -> +-spec(get_nodes_helper/2 :: +( + NodeTree :: module(), + Pubsub_State :: mod_pubsub:pubsubState()) + -> {value, NodeId::mod_pubsub:nodeId()} + | false + +). +get_nodes_helper(NodeTree, #pubsub_state{stateid = {_, N}, subscriptions = Subs}) -> HasPending = fun ({pending, _}) -> true; - (pending) -> true; - (_) -> false + (pending) -> true; + (_) -> false end, case lists:any(HasPending, Subs) of - true -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {_, Node}} -> - {value, Node}; - _ -> - false - end; - false -> - false + true -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {_, Node}} -> {value, Node}; + _ -> false + end; + false -> false end. %% @spec (NodeIdx) -> {result, States} @@ -859,6 +1030,12 @@ get_nodes_helper(NodeTree, %% they can implement this function like this: %% ```get_states(NodeIdx) -> %% node_default:get_states(NodeIdx).'''</p> +-spec(get_states/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [mod_pubsub:pubsubState()]} +). + get_states(NodeIdx) -> States = case catch mnesia:match_object( #pubsub_state{stateid = {'_', NodeIdx}, _ = '_'}) of @@ -872,6 +1049,13 @@ get_states(NodeIdx) -> %% JID = mod_pubsub:jid() %% State = mod_pubsub:pubsubState() %% @doc <p>Returns a state (one state list), given its reference.</p> +-spec(get_state/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: ljid()) + -> mod_pubsub:pubsubState() +). + get_state(NodeIdx, JID) -> StateId = {JID, NodeIdx}, case catch mnesia:read({pubsub_state, StateId}) of @@ -883,16 +1067,26 @@ get_state(NodeIdx, JID) -> %% State = mod_pubsub:pubsubState() %% Reason = mod_pubsub:stanzaError() %% @doc <p>Write a state into database.</p> +-spec(set_state/1 :: +( + State::mod_pubsub:pubsubState()) + -> ok +). set_state(State) when is_record(State, pubsub_state) -> - mnesia:write(State); -set_state(_) -> - {error, ?ERR_INTERNAL_SERVER_ERROR}. - %% @spec (NodeIdx, JID) -> ok | {error, Reason} %% NodeIdx = mod_pubsub:nodeIdx() %% JID = mod_pubsub:jid() %% Reason = mod_pubsub:stanzaError() %% @doc <p>Delete a state from database.</p> + mnesia:write(State). +%set_state(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. + +-spec(del_state/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: ljid()) + -> ok +). del_state(NodeIdx, JID) -> mnesia:delete({pubsub_state, {JID, NodeIdx}}). @@ -910,10 +1104,30 @@ del_state(NodeIdx, JID) -> %% they can implement this function like this: %% ```get_items(NodeIdx, From) -> %% node_default:get_items(NodeIdx, From).'''</p> +-spec(get_items/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + _From :: jid()) + -> {result, [mod_pubsub:pubsubItem()]} +). + get_items(NodeIdx, _From) -> Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeIdx}, _ = '_'}), {result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}. +-spec(get_items/6 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: jid(), + AccessModel :: mod_pubsub:accessModel(), + Presence_Subscription :: boolean(), + RosterGroup :: boolean(), + _SubId :: mod_pubsub:subId()) + -> {result, [mod_pubsub:pubsubItem()]} + %%% + | {error, xmlel()} +). + get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> SubKey = jlib:jid_tolower(JID), GenKey = jlib:jid_remove_resource(SubKey), @@ -922,33 +1136,32 @@ get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) Affiliation = GenState#pubsub_state.affiliation, Subscriptions = SubState#pubsub_state.subscriptions, Whitelisted = can_fetch_item(Affiliation, Subscriptions), - if - %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - GenState#pubsub_state.affiliation == outcast -> - %% Requesting entity is blocked - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and (not PresenceSubscription) -> - %% Entity is not authorized to create a subscription (presence subscription required) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; - (AccessModel == roster) and (not RosterGroup) -> - %% Entity is not authorized to create a subscription (not in roster group) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; - (AccessModel == whitelist) and (not Whitelisted) -> - %% Node has whitelist access model and entity lacks required affiliation - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; - (AccessModel == authorize) and (not Whitelisted) -> - %% Node has authorize access model - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> - get_items(NodeIdx, JID) + if %%SubId == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubId -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + GenState#pubsub_state.affiliation == outcast -> + {error, ?ERR_FORBIDDEN}; + (AccessModel == presence) and + not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + (AccessModel == authorize) and not Whitelisted -> + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> get_items(NodeIdx, JID) end. %% @spec (NodeIdx, ItemId) -> {result, Item} | {error, 'item-not-found'} @@ -956,12 +1169,18 @@ get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) %% ItemId = mod_pubsub:itemId() %% Item = mod_pubsub:pubsubItem() %% @doc <p>Returns an item (one item list), given its reference.</p> +-spec(get_item/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + ItemId :: mod_pubsub:itemId()) + -> {result, mod_pubsub:pubsubItem()} + | {error, xmlel()} +). + get_item(NodeIdx, ItemId) -> case mnesia:read({pubsub_item, {ItemId, NodeIdx}}) of - [Item] when is_record(Item, pubsub_item) -> - {result, Item}; - _ -> - {error, ?ERR_ITEM_NOT_FOUND} + [Item] when is_record(Item, pubsub_item) -> {result, Item}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} end. %% @spec (NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> {result, Item} | {error, Reason} @@ -974,101 +1193,137 @@ get_item(NodeIdx, ItemId) -> %% SubId = mod_pubsub:subId() %% Item = mod_pubsub:pubsubItem() %% Reason = mod_pubsub:stanzaError() | 'item-not-found' +-spec(get_item/7 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + ItemId :: mod_pubsub:itemId(), + JID :: jid(), + AccessModel :: mod_pubsub:accessModel(), + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + SubId :: mod_pubsub:subId()) + -> {result, mod_pubsub:pubsubItem()} + | {error, xmlel()} +). -get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> +get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, + _SubId) -> SubKey = jlib:jid_tolower(JID), GenKey = jlib:jid_remove_resource(SubKey), GenState = get_state(NodeIdx, GenKey), Affiliation = GenState#pubsub_state.affiliation, Subscriptions = GenState#pubsub_state.subscriptions, Whitelisted = can_fetch_item(Affiliation, Subscriptions), - if - %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - GenState#pubsub_state.affiliation == outcast -> - %% Requesting entity is blocked - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and (not PresenceSubscription) -> - %% Entity is not authorized to create a subscription (presence subscription required) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; - (AccessModel == roster) and (not RosterGroup) -> - %% Entity is not authorized to create a subscription (not in roster group) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; - (AccessModel == whitelist) and (not Whitelisted) -> - %% Node has whitelist access model and entity lacks required affiliation - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; - (AccessModel == authorize) and (not Whitelisted) -> - %% Node has authorize access model - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> - get_item(NodeIdx, ItemId) + if %%SubId == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubId -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + GenState#pubsub_state.affiliation == outcast -> + {error, ?ERR_FORBIDDEN}; + (AccessModel == presence) and + not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + (AccessModel == authorize) and not Whitelisted -> + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> get_item(NodeIdx, ItemId) end. %% @spec (Item) -> ok | {error, Reason} %% Item = mod_pubsub:pubsubItem() %% Reason = mod_pubsub:stanzaError() %% @doc <p>Write an item into database.</p> +-spec(set_item/1 :: +( + Item::mod_pubsub:pubsubItem()) + -> ok +). set_item(Item) when is_record(Item, pubsub_item) -> - mnesia:write(Item); -set_item(_) -> - {error, ?ERR_INTERNAL_SERVER_ERROR}. - %% @spec (NodeIdx, ItemId) -> ok | {error, Reason} %% NodeIdx = mod_pubsub:nodeIdx() %% ItemId = mod_pubsub:itemId() %% Reason = mod_pubsub:stanzaError() %% @doc <p>Delete an item from database.</p> + mnesia:write(Item). +%set_item(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. + +-spec(del_item/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + ItemId :: mod_pubsub:itemId()) + -> ok +). del_item(NodeIdx, ItemId) -> mnesia:delete({pubsub_item, {ItemId, NodeIdx}}). +-spec(del_items/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + ItemIds :: [mod_pubsub:pubsubItem(),...]) + -> ok +). + del_items(NodeIdx, ItemIds) -> - lists:foreach(fun(ItemId) -> - del_item(NodeIdx, ItemId) - end, ItemIds). + lists:foreach(fun (ItemId) -> del_item(NodeIdx, ItemId) + end, + ItemIds). + +get_item_name(_Host, _Node, Id) -> Id. %% @doc <p>Return the name of the node if known: Default is to return %% node id.</p> -get_item_name(_Host, _Node, Id) -> - Id. +-spec(node_to_path/1 :: +( + Node::binary()) + -> [binary()] +). +node_to_path(Node) -> str:tokens((Node), <<"/">>). -node_to_path(Node) -> - string:tokens(binary_to_list(Node), "/"). +-spec(path_to_node/1 :: +( + Path :: [binary()]) + -> binary() +). -path_to_node([]) -> - <<>>; +path_to_node([]) -> <<>>; path_to_node(Path) -> - list_to_binary(string:join([""|Path], "/")). - %% @spec (Affiliation, Subscription) -> true | false %% Affiliation = owner | member | publisher | outcast | none %% Subscription = subscribed | none %% @doc Determines if the combination of Affiliation and Subscribed %% are allowed to get items from a node. -can_fetch_item(owner, _) -> true; -can_fetch_item(member, _) -> true; -can_fetch_item(publisher, _) -> true; -can_fetch_item(outcast, _) -> false; -can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions); -can_fetch_item(_Affiliation, _Subscription) -> false. + iolist_to_binary(str:join([<<"">> | Path], <<"/">>)). + +can_fetch_item(owner, _) -> true; +can_fetch_item(member, _) -> true; +can_fetch_item(publisher, _) -> true; +can_fetch_item(outcast, _) -> false; +can_fetch_item(none, Subscriptions) -> + is_subscribed(Subscriptions). +%can_fetch_item(_Affiliation, _Subscription) -> false. is_subscribed(Subscriptions) -> lists:any(fun ({subscribed, _SubId}) -> true; - (_) -> false - end, Subscriptions). + (_) -> false + end, + Subscriptions). %% Returns the first item where Pred() is true in List -first_in_list(_Pred, []) -> - false; +first_in_list(_Pred, []) -> false; first_in_list(Pred, [H | T]) -> case Pred(H) of - true -> {value, H}; - _ -> first_in_list(Pred, T) + true -> {value, H}; + _ -> first_in_list(Pred, T) end. - diff --git a/src/mod_pubsub/node_hometree_odbc.erl b/src/mod_pubsub/node_hometree_odbc.erl index 0deb9d1a0..a5f8668ec 100644 --- a/src/mod_pubsub/node_hometree_odbc.erl +++ b/src/mod_pubsub/node_hometree_odbc.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.'' @@ -39,9 +41,11 @@ %%% improvements.</p> -module(node_hometree_odbc). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -define(PUBSUB, mod_pubsub_odbc). @@ -49,52 +53,25 @@ -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_entity_subscriptions_for_send_last/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/7, - get_items/6, - get_items/3, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - get_last_items/3, - path_to_node/1, - node_to_path/1 - ]). - --export([ - decode_jid/1, - decode_node/1, - decode_affiliation/1, - decode_subscriptions/1, - encode_jid/1, - encode_affiliation/1, - encode_subscriptions/1 - ]). + get_node_subscriptions/1, get_subscriptions/2, + set_subscriptions/4, get_pending_nodes/2, get_states/1, + get_state/2, set_state/1, get_items/7, get_items/6, + get_items/3, get_items/2, get_item/7, get_item/2, + set_item/1, get_item_name/3, get_last_items/3, + path_to_node/1, node_to_path/1]). + +-export([decode_jid/1, decode_node/1, + decode_affiliation/1, decode_subscriptions/1, + encode_jid/1, encode_affiliation/1, + encode_subscriptions/1]). %% ================ %% API definition @@ -110,16 +87,14 @@ %% plugin. It can be used for example by the developer to create the specific %% module database schema if it does not exists yet.</p> init(_Host, _ServerHost, _Opts) -> - pubsub_subscription_odbc:init(), - ok. + pubsub_subscription_odbc:init(), ok. %% @spec (Host, ServerHost) -> any() %% Host = mod_pubsub:host() %% ServerHost = host() %% @doc <p>Called during pubsub modules termination. Any pubsub plugin must %% implement this function. It can return anything.</p> -terminate(_Host, _ServerHost) -> - ok. +terminate(_Host, _ServerHost) -> ok. %% @spec () -> [Option] %% Option = mod_pubsub:nodeOption() @@ -138,53 +113,25 @@ terminate(_Host, _ServerHost) -> %% {max_payload_size, 100000}, %% {send_last_published_item, never}, %% {presence_based_delivery, false}]''' +-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, open}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, {send_last_published_item, on_sub_and_presence}, {deliver_notifications, true}, - {presence_based_delivery, false}, - {odbc, true}, + {presence_based_delivery, false}, {odbc, true}, {rsm, true}]. %% @spec () -> [] %% @doc Returns the node features +-spec(features/0 :: () -> Features::[Feature::binary(),...]). features() -> - ["create-nodes", - "auto-create", - "access-authorize", - "delete-nodes", - "delete-items", - "get-pending", - "instant-nodes", - "manage-subscriptions", - "modify-affiliations", - "multi-subscribe", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications", - "subscription-options", - "rsm" - ]. - %% @spec (Host, ServerHost, Node, ParentNode, Owner, Access) -> bool() %% Host = mod_pubsub:host() %% ServerHost = mod_pubsub:host() @@ -204,23 +151,44 @@ features() -> %% module by implementing this function like this: %% ```check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> %% node_default:check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access).'''</p> + [<<"create-nodes">>, <<"auto-create">>, + <<"access-authorize">>, <<"delete-nodes">>, + <<"delete-items">>, <<"get-pending">>, + <<"instant-nodes">>, <<"manage-subscriptions">>, + <<"modify-affiliations">>, <<"multi-subscribe">>, + <<"outcast-affiliation">>, <<"persistent-items">>, + <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, <<"retrieve-items">>, + <<"retrieve-subscriptions">>, <<"subscribe">>, + <<"subscription-notifications">>, + <<"subscription-options">>, <<"rsm">>]. + +-spec(create_node_permission/6 :: +( + Host :: mod_pubsub:host(), + ServerHost :: binary(), + Node :: mod_pubsub:nodeId(), + _ParentNode :: _, + Owner :: jid(), + Access :: atom()) + -> {result, boolean()} +). create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), {User, Server, _Resource} = LOwner, Allowed = case LOwner of - {"", Host, ""} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case node_to_path(Node) of - ["home", Server, User | _] -> true; - _ -> false - end; + {<<"">>, Host, <<"">>} -> + true; % pubsub service always allowed _ -> - false - end - end, + case acl:match_rule(ServerHost, Access, LOwner) of + allow -> + case node_to_path(Node) of + [<<"home">>, Server, User | _] -> true; + _ -> false + end; + _ -> false + end + end, {result, Allowed}. %% @spec (NodeId, Owner) -> @@ -228,36 +196,53 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> %% NodeId = mod_pubsub:pubsubNodeId() %% Owner = mod_pubsub:jid() %% @doc <p></p> -create_node(NodeId, Owner) -> +-spec(create_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} +). +create_node(NodeIdx, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - State = #pubsub_state{stateid = {OwnerKey, NodeId}, affiliation = owner}, - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values(", state_to_raw(NodeId, State), ");"]), + State = #pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}, + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " + "affiliation, subscriptions) values(">>, + state_to_raw(NodeIdx, State), <<");">>]), {result, {default, broadcast}}. %% @spec (Removed) -> ok %% Removed = [mod_pubsub:pubsubNode()] %% @doc <p>purge items of deleted nodes after effective deletion.</p> +-spec(delete_node/1 :: +( + Removed :: [mod_pubsub:pubsubNode(),...]) + -> {result, {default, broadcast, _}} +). delete_node(Removed) -> - Reply = lists:map( - fun(#pubsub_node{id = NodeId} = PubsubNode) -> - Subscriptions = case catch ejabberd_odbc:sql_query_t( - ["select jid, subscriptions " - "from pubsub_state " - "where nodeid='", NodeId, "';"]) of - {selected, ["jid", "subscriptions"], RItems} -> - lists:map(fun({SJID, Subscriptions}) -> - {decode_jid(SJID), decode_subscriptions(Subscriptions)} - end, RItems); - _ -> - [] - end, - %% state and item remove already done thanks to DELETE CASCADE - %% but here we get nothing in States, making notify_retract unavailable ! - %% TODO, remove DELETE CASCADE from schema - {PubsubNode, Subscriptions} - end, Removed), + Reply = lists:map(fun (#pubsub_node{id = NodeId} = + PubsubNode) -> + Subscriptions = case catch + ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state " + "where nodeid='">>, + NodeId, + <<"';">>]) + of + {selected, + [<<"jid">>, + <<"subscriptions">>], + RItems} -> + lists:map(fun ({SJID, + Subscriptions}) -> + {decode_jid(SJID), + decode_subscriptions(Subscriptions)} + end, + RItems); + _ -> [] + end, + {PubsubNode, Subscriptions} + end, + Removed), {result, {default, broadcast, Reply}}. %% @spec (NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> @@ -293,60 +278,81 @@ delete_node(Removed) -> %% to completly disable persistance.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> +-spec(subscribe_node/8 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + AccessModel :: mod_pubsub:accessModel(), + SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + Options :: mod_pubsub:subOptions()) + -> {result, {default, subscribed, mod_pubsub:subId()}} + | {result, {default, subscribed, mod_pubsub:subId(), send_last}} + | {result, {default, pending, mod_pubsub:subId()}} + %%% + | {error, _} + | {error, _, binary()} +). subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> SubKey = jlib:jid_tolower(Subscriber), GenKey = jlib:jid_remove_resource(SubKey), - Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey), - Whitelisted = lists:member(Affiliation, [member, publisher, owner]), - PendingSubscription = lists:any(fun({pending, _}) -> true; - (_) -> false - end, Subscriptions), - if - not Authorized -> - %% JIDs do not match - {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")}; - Affiliation == outcast -> - %% Requesting entity is blocked - {error, ?ERR_FORBIDDEN}; - PendingSubscription -> - %% Requesting entity has pending subscription - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")}; - (AccessModel == presence) and (not PresenceSubscription) -> - %% Entity is not authorized to create a subscription (presence subscription required) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; - (AccessModel == roster) and (not RosterGroup) -> - %% Entity is not authorized to create a subscription (not in roster group) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; - (AccessModel == whitelist) and (not Whitelisted) -> - %% Node has whitelist access model and entity lacks required affiliation - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - %%ForbiddenAnonymous -> - %% % Requesting entity is anonymous - %% {error, ?ERR_FORBIDDEN}; - true -> - case pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options) of - {result, SubId} -> - NewSub = case AccessModel of - authorize -> pending; - _ -> subscribed - end, - update_subscription(NodeId, SubKey, [{NewSub, SubId} | Subscriptions]), - case {NewSub, SendLast} of - {subscribed, never} -> - {result, {default, subscribed, SubId}}; - {subscribed, _} -> - {result, {default, subscribed, SubId, send_last}}; - {_, _} -> - {result, {default, pending, SubId}} - end; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end + Authorized = + jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == + GenKey, + {Affiliation, Subscriptions} = + select_affiliation_subscriptions(NodeId, GenKey, + SubKey), + Whitelisted = lists:member(Affiliation, + [member, publisher, owner]), + PendingSubscription = lists:any(fun ({pending, _}) -> + true; + (_) -> false + end, + Subscriptions), + if not Authorized -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"invalid-jid">>)}; + Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; + PendingSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"pending-subscription">>)}; + (AccessModel == presence) and + not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + %%ForbiddenAnonymous -> + %% % Requesting entity is anonymous + %% {error, ?ERR_FORBIDDEN}; + true -> + {result, SubId} = pubsub_subscription_odbc:subscribe_node(Subscriber, NodeId, Options), + NewSub = case AccessModel of + authorize -> pending; + _ -> subscribed + end, + update_subscription(NodeId, SubKey, + [{NewSub, SubId} | Subscriptions]), + case {NewSub, SendLast} of + {subscribed, never} -> + {result, {default, subscribed, SubId}}; + {subscribed, _} -> + {result, {default, subscribed, SubId, send_last}}; + {_, _} -> {result, {default, pending, SubId}} + end end. %% @spec (NodeId, Sender, Subscriber, SubId) -> @@ -357,68 +363,97 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel, %% SubId = mod_pubsub:subid() %% Reason = mod_pubsub:stanzaError() %% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p> +-spec(unsubscribe_node/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: jid(), + SubId :: subId()) + -> {result, default} + % + | {error, _} + | {error, _, binary()} +). unsubscribe_node(NodeId, Sender, Subscriber, SubId) -> SubKey = jlib:jid_tolower(Subscriber), GenKey = jlib:jid_remove_resource(SubKey), - Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, SubKey), + Authorized = + jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == + GenKey, + {Affiliation, Subscriptions} = + select_affiliation_subscriptions(NodeId, SubKey), SubIdExists = case SubId of - [] -> false; - List when is_list(List) -> true; - _ -> false + [] -> false; + List when is_binary(List) -> true; + _ -> false end, if - %% Requesting entity is prohibited from unsubscribing entity - not Authorized -> - {error, ?ERR_FORBIDDEN}; - %% Entity did not specify SubId - %%SubId == "", ?? -> - %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %% Invalid subscription identifier - %%InvalidSubId -> - %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - %% Requesting entity is not a subscriber - Subscriptions == [] -> - {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}; - %% Subid supplied, so use that. - SubIdExists -> - Sub = first_in_list(fun(S) -> - case S of - {_Sub, SubId} -> true; - _ -> false - end - end, Subscriptions), - case Sub of - {value, S} -> - delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions), - {result, default}; - false -> - {error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")} - end; - %% Asking to remove all subscriptions to the given node - SubId == all -> - [delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions) || S <- Subscriptions], - {result, default}; - %% No subid supplied, but there's only one matching - %% subscription, so use that. - length(Subscriptions) == 1 -> - delete_subscription(SubKey, NodeId, hd(Subscriptions), Affiliation, Subscriptions), - {result, default}; - %% No subid and more than one possible subscription match. - true -> - {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")} + %% Requesting entity is prohibited from unsubscribing entity + not Authorized -> {error, ?ERR_FORBIDDEN}; + %% Entity did not specify SubId + %%SubId == "", ?? -> + %% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %% Invalid subscription identifier + %%InvalidSubId -> + %% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + %% Requesting entity is not a subscriber + Subscriptions == [] -> + {error, + ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), + <<"not-subscribed">>)}; + %% Subid supplied, so use that. + SubIdExists -> + Sub = first_in_list(fun (S) -> + case S of + {_Sub, SubId} -> true; + _ -> false + end + end, + Subscriptions), + case Sub of + {value, S} -> + delete_subscription(SubKey, NodeId, S, Affiliation, + Subscriptions), + {result, default}; + false -> + {error, + ?ERR_EXTENDED((?ERR_UNEXPECTED_REQUEST_CANCEL), + <<"not-subscribed">>)} + end; + %% Asking to remove all subscriptions to the given node + SubId == all -> + [delete_subscription(SubKey, NodeId, S, Affiliation, + Subscriptions) + || S <- Subscriptions], + {result, default}; + %% No subid supplied, but there's only one matching + %% subscription, so use that. + length(Subscriptions) == 1 -> + delete_subscription(SubKey, NodeId, hd(Subscriptions), + Affiliation, Subscriptions), + {result, default}; + %% No subid and more than one possible subscription match. + true -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), <<"subid-required">>)} end. -delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscriptions) -> +%-spec(delete_subscriptions/5 :: +%( +% SubKey :: ljid(), +% NodeIdx :: mod_pubsub:nodeIdx(), +% _ :: {mod_pubsub:subscription(), mod_pubsub:subId()}, +% SubState :: mod_pubsub:pubsubState(), +% Subscriptions :: [{mod_pubsub:subscription(), mod_pubsub:subId()}]) +% -> ok +%). +delete_subscription(SubKey, NodeIdx, + {Subscription, SubId}, Affiliation, Subscriptions) -> NewSubs = Subscriptions -- [{Subscription, SubId}], - pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeId, SubId), + pubsub_subscription_odbc:unsubscribe_node(SubKey, NodeIdx, SubId), case {Affiliation, NewSubs} of - {none, []} -> - % Just a regular subscriber, and this is final item, so - % delete the state. - del_state(NodeId, SubKey); - _ -> - update_subscription(NodeId, SubKey, NewSubs) + {none, []} -> del_state(NodeIdx, SubKey); + _ -> update_subscription(NodeIdx, SubKey, NewSubs) end. %% @spec (NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> @@ -459,38 +494,46 @@ delete_subscription(SubKey, NodeId, {Subscription, SubId}, Affiliation, Subscrip %% to completly disable persistance.</li></ul> %% </p> %% <p>In the default plugin module, the record is unchanged.</p> -publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) -> + +-spec(publish_item/6 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + Max_Items :: non_neg_integer(), + ItemId :: <<>> | mod_pubsub:itemId(), + Payload :: mod_pubsub:payload()) + -> {result, {default, broadcast, [mod_pubsub:itemId()]}} + %%% + | {error, _} +). +publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> SubKey = jlib:jid_tolower(Publisher), GenKey = jlib:jid_remove_resource(SubKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey), + {Affiliation, Subscriptions} = + select_affiliation_subscriptions(NodeIdx, GenKey, SubKey), Subscribed = case PublishModel of - subscribers -> is_subscribed(Subscriptions); - _ -> undefined - end, - if - not ((PublishModel == open) - or ((PublishModel == publishers) - and ((Affiliation == owner) or (Affiliation == publisher))) - or (Subscribed == true)) -> - %% Entity does not have sufficient privileges to publish to node - {error, ?ERR_FORBIDDEN}; - true -> - %% TODO: check creation, presence, roster - if MaxItems > 0 -> - %% Note: this works cause set_item tries an update before - %% the insert, and the update just ignore creation field. - PubId = {now(), SubKey}, - set_item(#pubsub_item{itemid = {ItemId, NodeId}, + subscribers -> is_subscribed(Subscriptions); + _ -> undefined + end, + if not + ((PublishModel == open) or + (PublishModel == publishers) and + ((Affiliation == owner) or (Affiliation == publisher)) + or (Subscribed == true)) -> + {error, ?ERR_FORBIDDEN}; + true -> + if MaxItems > 0 -> + PubId = {now(), SubKey}, + set_item(#pubsub_item{itemid = {ItemId, NodeIdx}, creation = {now(), GenKey}, modification = PubId, payload = Payload}), - Items = [ItemId | itemids(NodeId, GenKey)--[ItemId]], - {result, {_, OI}} = remove_extra_items(NodeId, MaxItems, Items), - %% set new item list use useless - {result, {default, broadcast, OI}}; - true -> - {result, {default, broadcast, []}} - end + Items = [ItemId | itemids(NodeIdx, GenKey) -- [ItemId]], + {result, {_, OI}} = remove_extra_items(NodeIdx, MaxItems, Items), + {result, {default, broadcast, OI}}; + true -> {result, {default, broadcast, []}} + end end. %% @spec (NodeId, MaxItems, ItemIds) -> {NewItemIds,OldItemIds} @@ -513,9 +556,7 @@ remove_extra_items(_NodeId, unlimited, ItemIds) -> remove_extra_items(NodeId, MaxItems, ItemIds) -> NewItems = lists:sublist(ItemIds, MaxItems), OldItems = lists:nthtail(length(NewItems), ItemIds), - %% Remove extra items: del_items(NodeId, OldItems), - %% Return the new items list: {result, {NewItems, OldItems}}. %% @spec (NodeId, Publisher, PublishModel, ItemId) -> @@ -528,29 +569,33 @@ remove_extra_items(NodeId, MaxItems, ItemIds) -> %% @doc <p>Triggers item deletion.</p> %% <p>Default plugin: The user performing the deletion must be the node owner %% or a publisher.</p> -delete_item(NodeId, Publisher, PublishModel, ItemId) -> +-spec(delete_item/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + ItemId :: <<>> | mod_pubsub:itemId()) + -> {result, {default, broadcast}} + %%% + | {error, _} +). +delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> SubKey = jlib:jid_tolower(Publisher), GenKey = jlib:jid_remove_resource(SubKey), - {result, Affiliation} = get_affiliation(NodeId, GenKey), - Allowed = (Affiliation == publisher) orelse (Affiliation == owner) - orelse (PublishModel == open) - orelse case get_item(NodeId, ItemId) of - {result, #pubsub_item{creation = {_, GenKey}}} -> true; - _ -> false - end, - if - not Allowed -> - %% Requesting entity does not have sufficient privileges - {error, ?ERR_FORBIDDEN}; - true -> - case del_item(NodeId, ItemId) of - {updated, 1} -> - %% set new item list use useless - {result, {default, broadcast}}; - _ -> - %% Non-existent node or item - {error, ?ERR_ITEM_NOT_FOUND} - end + {result, Affiliation} = get_affiliation(NodeIdx, GenKey), + Allowed = Affiliation == publisher orelse + Affiliation == owner orelse + PublishModel == open orelse + case get_item(NodeIdx, ItemId) of + {result, #pubsub_item{creation = {_, GenKey}}} -> true; + _ -> false + end, + if not Allowed -> {error, ?ERR_FORBIDDEN}; + true -> + case del_item(NodeIdx, ItemId) of + {updated, 1} -> {result, {default, broadcast}}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} + end end. %% @spec (NodeId, Owner) -> @@ -558,21 +603,27 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) -> %% {result, {default, broadcast}} %% NodeId = mod_pubsub:pubsubNodeId() %% Owner = mod_pubsub:jid() -purge_node(NodeId, Owner) -> +-spec(purge_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} + | {error, _} +). +purge_node(NodeIdx, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - GenState = get_state(NodeId, GenKey), + GenState = get_state(NodeIdx, GenKey), case GenState of - #pubsub_state{affiliation = owner} -> - {result, States} = get_states(NodeId), - lists:foreach( - fun(#pubsub_state{items = []}) -> ok; - (#pubsub_state{items = Items}) -> del_items(NodeId, Items) - end, States), - {result, {default, broadcast}}; - _ -> - %% Entity is not owner - {error, ?ERR_FORBIDDEN} + #pubsub_state{affiliation = owner} -> + {result, States} = get_states(NodeIdx), + lists:foreach(fun (#pubsub_state{items = []}) -> ok; + (#pubsub_state{items = Items}) -> + del_items(NodeIdx, Items) + end, + States), + {result, {default, broadcast}}; + _ -> {error, ?ERR_FORBIDDEN} end. %% @spec (Host, JID) -> [{Node,Affiliation}] @@ -585,60 +636,98 @@ purge_node(NodeId, Owner) -> %% the default PubSub module. Otherwise, it should return its own affiliation, %% that will be added to the affiliation stored in the main %% <tt>pubsub_state</tt> table.</p> +-spec(get_entity_affiliations/2 :: +( + Host :: mod_pubsub:hostPubsub(), + Owner :: jid()) + -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} +). get_entity_affiliations(Host, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - H = ?PUBSUB:escape(Host), + H = (?PUBSUB):escape(Host), J = encode_jid(GenKey), - Reply = case catch ejabberd_odbc:sql_query_t( - ["select node, type, i.nodeid, affiliation " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid " - "and jid='", J, "' " - "and host='", H, "';"]) of - {selected, ["node", "type", "nodeid", "affiliation"], RItems} -> - lists:map(fun({N, T, I, A}) -> - Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}), - {Node, decode_affiliation(A)} - end, RItems); - _ -> - [] - end, + Reply = case catch + ejabberd_odbc:sql_query_t([<<"select node, type, i.nodeid, affiliation " + "from pubsub_state i, pubsub_node n where " + "i.nodeid = n.nodeid and jid='">>, + J, <<"' and host='">>, H, + <<"';">>]) + of + {selected, + [<<"node">>, <<"type">>, <<"nodeid">>, + <<"affiliation">>], + RItems} -> + lists:map(fun ({N, T, I, A}) -> + Node = nodetree_tree_odbc:raw_to_node(Host, + {N, + <<"">>, + T, + I}), + {Node, decode_affiliation(A)} + end, + RItems); + _ -> [] + end, {result, Reply}. -get_node_affiliations(NodeId) -> - Reply = case catch ejabberd_odbc:sql_query_t( - ["select jid, affiliation " - "from pubsub_state " - "where nodeid='", NodeId, "';"]) of - {selected, ["jid", "affiliation"], RItems} -> - lists:map(fun({J, A}) -> {decode_jid(J), decode_affiliation(A)} end, RItems); - _ -> - [] - end, +-spec(get_node_affiliations/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [{ljid(), mod_pubsub:affiliation()}]} +). +get_node_affiliations(NodeIdx) -> + Reply = case catch + ejabberd_odbc:sql_query_t([<<"select jid, affiliation from pubsub_state " + "where nodeid='">>, + NodeIdx, <<"';">>]) + of + {selected, [<<"jid">>, <<"affiliation">>], RItems} -> + lists:map(fun ({J, A}) -> + {decode_jid(J), decode_affiliation(A)} + end, + RItems); + _ -> [] + end, {result, Reply}. -get_affiliation(NodeId, Owner) -> +-spec(get_affiliation/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid()) + -> {result, mod_pubsub:affiliation()} +). + +get_affiliation(NodeIdx, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), J = encode_jid(GenKey), - Reply = case catch ejabberd_odbc:sql_query_t( - ["select affiliation from pubsub_state " - "where nodeid='", NodeId, "' and jid='", J, "';"]) of - {selected, ["affiliation"], [{A}]} -> decode_affiliation(A); - _ -> none - end, + Reply = case catch + ejabberd_odbc:sql_query_t([<<"select affiliation from pubsub_state " + "where nodeid='">>, + NodeIdx, <<"' and jid='">>, J, + <<"';">>]) + of + {selected, [<<"affiliation">>], [{A}]} -> + decode_affiliation(A); + _ -> none + end, {result, Reply}. -set_affiliation(NodeId, Owner, Affiliation) -> +-spec(set_affiliation/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid(), + Affiliation :: mod_pubsub:affiliation()) + -> ok +). +set_affiliation(NodeIdx, Owner, Affiliation) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - {_, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey), + {_, Subscriptions} = select_affiliation_subscriptions(NodeIdx, GenKey), case {Affiliation, Subscriptions} of - {none, none} -> - del_state(NodeId, GenKey); - _ -> - update_affiliation(NodeId, GenKey, Affiliation) + {none, none} -> del_state(NodeIdx, GenKey); + _ -> update_affiliation(NodeIdx, GenKey, Affiliation) end. %% @spec (Host, Owner) -> [{Node,Subscription}] @@ -651,223 +740,341 @@ set_affiliation(NodeId, Owner, Affiliation) -> %% the default PubSub module. Otherwise, it should return its own affiliation, %% that will be added to the affiliation stored in the main %% <tt>pubsub_state</tt> table.</p> + +-spec(get_entity_subscriptions/2 :: +( + Host :: mod_pubsub:host(), + Owner :: jid()) + -> {result, + [{mod_pubsub:pubsubNode(), + mod_pubsub:subscription(), + mod_pubsub:subId(), + ljid()}] + } +). get_entity_subscriptions(Host, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - H = ?PUBSUB:escape(Host), + H = (?PUBSUB):escape(Host), SJ = encode_jid(SubKey), GJ = encode_jid(GenKey), Query = case SubKey of - GenKey -> - ["select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid " - "and jid like '", GJ, "%' " - "and host='", H, "';"]; - _ -> - ["select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid " - "and jid in ('", SJ, "', '", GJ, "') " - "and host='", H, "';"] - end, + GenKey -> + [<<"select node, type, i.nodeid, jid, subscriptio" + "ns from pubsub_state i, pubsub_node " + "n where i.nodeid = n.nodeid and jid " + "like '">>, + GJ, <<"%' and host='">>, H, <<"';">>]; + _ -> + [<<"select node, type, i.nodeid, jid, subscriptio" + "ns from pubsub_state i, pubsub_node " + "n where i.nodeid = n.nodeid and jid " + "in ('">>, + SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] + end, Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} -> - lists:foldl(fun({N, T, I, J, S}, Acc) -> - Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Node, none, Jid}|Acc]; - Subs -> - lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2]; - (Sub, Acc2) -> [{Node, Sub, Jid}|Acc2] - end, Acc, Subs) - end - end, [], RItems); - _ -> - [] - end, + {selected, + [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, + <<"subscriptions">>], + RItems} -> + lists:foldl(fun ({N, T, I, J, S}, Acc) -> + Node = + nodetree_tree_odbc:raw_to_node(Host, + {N, + <<"">>, + T, + I}), + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> [{Node, none, Jid} | Acc]; + Subs -> + lists:foldl(fun ({Sub, SubId}, + Acc2) -> + [{Node, Sub, + SubId, Jid} + | Acc2]; + (Sub, Acc2) -> + [{Node, Sub, + Jid} + | Acc2] + end, + Acc, Subs) + end + end, + [], RItems); + _ -> [] + end, {result, Reply}. %% do the same as get_entity_subscriptions but filter result only to %% nodes having send_last_published_item=on_sub_and_presence %% as this call avoid seeking node, it must return node and type as well +-spec(get_entity_subscriptions_for_send_last/2 :: +( + Host :: mod_pubsub:hostPubsub(), + Owner :: jid()) + -> {result, + [{mod_pubsub:pubsubNode(), + mod_pubsub:subscription(), + mod_pubsub:subId(), + ljid()}] + } +). + get_entity_subscriptions_for_send_last(Host, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - H = ?PUBSUB:escape(Host), + H = (?PUBSUB):escape(Host), SJ = encode_jid(SubKey), GJ = encode_jid(GenKey), Query = case SubKey of - GenKey -> - ["select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid " - "and name='send_last_published_item' and val='on_sub_and_presence' " - "and jid like '", GJ, "%' " - "and host='", H, "';"]; - _ -> - ["select node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid " - "and name='send_last_published_item' and val='on_sub_and_presence' " - "and jid in ('", SJ, "', '", GJ, "') " - "and host='", H, "';"] - end, + GenKey -> + [<<"select node, type, i.nodeid, jid, subscriptio" + "ns from pubsub_state i, pubsub_node " + "n, pubsub_node_option o where i.nodeid " + "= n.nodeid and n.nodeid = o.nodeid and " + "name='send_last_published_item' and " + "val='on_sub_and_presence' and jid like " + "'">>, + GJ, <<"%' and host='">>, H, <<"';">>]; + _ -> + [<<"select node, type, i.nodeid, jid, subscriptio" + "ns from pubsub_state i, pubsub_node " + "n, pubsub_node_option o where i.nodeid " + "= n.nodeid and n.nodeid = o.nodeid and " + "name='send_last_published_item' and " + "val='on_sub_and_presence' and jid in " + "('">>, + SJ, <<"', '">>, GJ, <<"') and host='">>, H, <<"';">>] + end, Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, ["node", "type", "nodeid", "jid", "subscriptions"], RItems} -> - lists:foldl(fun({N, T, I, J, S}, Acc) -> - Node = nodetree_tree_odbc:raw_to_node(Host, {N, "", T, I}), - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Node, none, Jid}|Acc]; - Subs -> - lists:foldl(fun({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, Jid}|Acc2]; - (Sub, Acc2) -> [{Node, Sub, Jid}|Acc2] - end, Acc, Subs) - end - end, [], RItems); - _ -> - [] - end, + {selected, + [<<"node">>, <<"type">>, <<"nodeid">>, <<"jid">>, + <<"subscriptions">>], + RItems} -> + lists:foldl(fun ({N, T, I, J, S}, Acc) -> + Node = + nodetree_tree_odbc:raw_to_node(Host, + {N, + <<"">>, + T, + I}), + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> [{Node, none, Jid} | Acc]; + Subs -> + lists:foldl(fun ({Sub, SubId}, Acc2) -> + [{Node, Sub, SubId, Jid}| Acc2] + end, + Acc, Subs) + end + end, + [], RItems); + _ -> [] + end, {result, Reply}. -get_node_subscriptions(NodeId) -> - Reply = case catch ejabberd_odbc:sql_query_t( - ["select jid, subscriptions " - "from pubsub_state " - "where nodeid='", NodeId, "';"]) of - {selected, ["jid", "subscriptions"], RItems} -> - lists:foldl(fun({J, S}, Acc) -> - Jid = decode_jid(J), - case decode_subscriptions(S) of - [] -> - [{Jid, none}|Acc]; - Subs -> - lists:foldl(fun({Sub, SubId}, Acc2) -> [{Jid, Sub, SubId}|Acc2]; - (Sub, Acc2) -> [{Jid, Sub}|Acc2] - end, Acc, Subs) - end - end, [], RItems); - _ -> - [] - end, +-spec(get_node_subscriptions/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}]} +). +get_node_subscriptions(NodeIdx) -> + Reply = case catch + ejabberd_odbc:sql_query_t([<<"select jid, subscriptions from pubsub_state " + "where nodeid='">>, + NodeIdx, <<"';">>]) + of + {selected, [<<"jid">>, <<"subscriptions">>], RItems} -> + lists:foldl(fun ({J, S}, Acc) -> + Jid = decode_jid(J), + case decode_subscriptions(S) of + [] -> [{Jid, none} | Acc]; + Subs -> + lists:foldl(fun ({Sub, SubId}, Acc2) -> + [{Jid, Sub, SubId} | Acc2] + end, + Acc, Subs) + end + end, + [], RItems); + _ -> [] + end, {result, Reply}. -get_subscriptions(NodeId, Owner) -> +-spec(get_subscriptions/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid()) + -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]} +). +get_subscriptions(NodeIdx, Owner) -> SubKey = jlib:jid_tolower(Owner), J = encode_jid(SubKey), - Reply = case catch ejabberd_odbc:sql_query_t( - ["select subscriptions from pubsub_state " - "where nodeid='", NodeId, "' and jid='", J, "';"]) of - {selected, ["subscriptions"], [{S}]} -> decode_subscriptions(S); - _ -> [] - end, + Reply = case catch + ejabberd_odbc:sql_query_t([<<"select subscriptions from pubsub_state " + "where nodeid='">>, + NodeIdx, <<"' and jid='">>, J, + <<"';">>]) + of + {selected, [<<"subscriptions">>], [{S}]} -> + decode_subscriptions(S); + _ -> [] + end, {result, Reply}. -set_subscriptions(NodeId, Owner, Subscription, SubId) -> +-spec(set_subscriptions/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid(), + Subscription :: mod_pubsub:subscription(), + SubId :: mod_pubsub:subId()) + -> _ + %%% + | {error, xmlel()} +). +set_subscriptions(NodeIdx, Owner, Subscription, SubId) -> SubKey = jlib:jid_tolower(Owner), - SubState = get_state_without_itemids(NodeId, SubKey), + SubState = get_state_without_itemids(NodeIdx, SubKey), case {SubId, SubState#pubsub_state.subscriptions} of - {_, []} -> - case Subscription of - none -> {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "not-subscribed")}; - _ -> new_subscription(NodeId, Owner, Subscription, SubState) - end; - {"", [{_, SID}]} -> - case Subscription of - none -> unsub_with_subid(NodeId, SID, SubState); - _ -> replace_subscription({Subscription, SID}, SubState) - end; - {"", [_|_]} -> - {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - _ -> - case Subscription of - none -> unsub_with_subid(NodeId, SubId, SubState); - _ -> replace_subscription({Subscription, SubId}, SubState) - end + {_, []} -> + case Subscription of + none -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), + <<"not-subscribed">>)}; + _ -> + new_subscription(NodeIdx, Owner, Subscription, SubState) + end; + {<<"">>, [{_, SID}]} -> + case Subscription of + none -> unsub_with_subid(NodeIdx, SID, SubState); + _ -> replace_subscription({Subscription, SID}, SubState) + end; + {<<"">>, [_ | _]} -> + {error, + ?ERR_EXTENDED((?ERR_BAD_REQUEST), + <<"subid-required">>)}; + _ -> + case Subscription of + none -> unsub_with_subid(NodeIdx, SubId, SubState); + _ -> + replace_subscription({Subscription, SubId}, SubState) + end end. +-spec(replace_subscription/2 :: +( + NewSub :: {mod_pubsub:subscription(), mod_pubsub:subId()}, + SubState :: mod_pubsub:pubsubState()) + -> {result, []} +). replace_subscription(NewSub, SubState) -> - NewSubs = replace_subscription(NewSub, - SubState#pubsub_state.subscriptions, []), + NewSubs = replace_subscription(NewSub, SubState#pubsub_state.subscriptions, []), set_state(SubState#pubsub_state{subscriptions = NewSubs}). -replace_subscription(_, [], Acc) -> - Acc; +replace_subscription(_, [], Acc) -> Acc; replace_subscription({Sub, SubId}, [{_, SubID} | T], Acc) -> replace_subscription({Sub, SubId}, T, [{Sub, SubID} | Acc]). -new_subscription(NodeId, Owner, Subscription, SubState) -> - case pubsub_subscription_odbc:subscribe_node(Owner, NodeId, []) of - {result, SubId} -> - Subscriptions = SubState#pubsub_state.subscriptions, - set_state(SubState#pubsub_state{subscriptions = [{Subscription, SubId} | Subscriptions]}), - {Subscription, SubId}; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} +-spec(new_subscription/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid(), + Subscription :: mod_pubsub:subscription(), + SubState :: mod_pubsub:pubsubState()) + -> {mod_pubsub:subscription(), mod_pubsub:subId()} + %%% + | {error, xmlel()} +). + +new_subscription(NodeIdx, Owner, Subscription, SubState) -> + case pubsub_subscription_odbc:subscribe_node(Owner, NodeIdx, []) of + {result, SubId} -> + Subscriptions = SubState#pubsub_state.subscriptions, + set_state(SubState#pubsub_state{subscriptions = + [{Subscription, SubId} | Subscriptions]}), + {Subscription, SubId}; + _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} end. -unsub_with_subid(NodeId, SubId, SubState) -> +-spec(unsub_with_subid/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + SubId :: mod_pubsub:subId(), + SubState :: mod_pubsub:pubsubState()) + -> ok +). +unsub_with_subid(NodeIdx, SubId, SubState) -> pubsub_subscription_odbc:unsubscribe_node(SubState#pubsub_state.stateid, - NodeId, SubId), - NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID end, + NodeIdx, SubId), + NewSubs = lists:filter(fun ({_, SID}) -> SubId =/= SID + end, SubState#pubsub_state.subscriptions), case {NewSubs, SubState#pubsub_state.affiliation} of - {[], none} -> - del_state(NodeId, element(1, SubState#pubsub_state.stateid)); - _ -> - set_state(SubState#pubsub_state{subscriptions = NewSubs}) + {[], none} -> + del_state(NodeIdx, + element(1, SubState#pubsub_state.stateid)); + _ -> + set_state(SubState#pubsub_state{subscriptions = NewSubs}) end. - %% @spec (Host, Owner) -> {result, [Node]} | {error, Reason} %% Host = host() %% Owner = jid() %% Node = pubsubNode() %% @doc <p>Returns a list of Owner's nodes on Host with pending %% subscriptions.</p> +-spec(get_pending_nodes/2 :: +( + Host :: mod_pubsub:hostPubsub(), + Owner :: jid()) + -> {result, [mod_pubsub:nodeId()]} +). get_pending_nodes(Host, Owner) -> GenKey = jlib:jid_remove_resource(jlib:jid_tolower(Owner)), - States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, - affiliation = owner, - _ = '_'}), - NodeIDs = [ID || #pubsub_state{stateid = {_, ID}} <- States], - NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of - [{nodetree, N}] -> N; - _ -> nodetree_tree_odbc + States = mnesia:match_object(#pubsub_state{stateid = + {GenKey, '_'}, + affiliation = owner, _ = '_'}), + NodeIDs = [ID + || #pubsub_state{stateid = {_, ID}} <- States], + NodeTree = case catch + ets:lookup(gen_mod:get_module_proc(Host, config), + nodetree) + of + [{nodetree, N}] -> N; + _ -> nodetree_tree_odbc end, - Reply = mnesia:foldl(fun(#pubsub_state{stateid = {_, NID}} = S, Acc) -> - case lists:member(NID, NodeIDs) of - true -> - case get_nodes_helper(NodeTree, S) of - {value, Node} -> [Node | Acc]; - false -> Acc - end; - false -> - Acc - end - end, [], pubsub_state), + Reply = mnesia:foldl(fun (#pubsub_state{stateid = + {_, NID}} = + S, + Acc) -> + case lists:member(NID, NodeIDs) of + true -> + case get_nodes_helper(NodeTree, S) of + {value, Node} -> [Node | Acc]; + false -> Acc + end; + false -> Acc + end + end, + [], pubsub_state), {result, Reply}. get_nodes_helper(NodeTree, - #pubsub_state{stateid = {_, N}, subscriptions = Subs}) -> + #pubsub_state{stateid = {_, N}, + subscriptions = Subs}) -> HasPending = fun ({pending, _}) -> true; - (pending) -> true; - (_) -> false + (pending) -> true; + (_) -> false end, case lists:any(HasPending, Subs) of - true -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {_, Node}} -> - {value, Node}; - _ -> - false - end; - false -> - false + true -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {_, Node}} -> {value, Node}; + _ -> false + end; + false -> false end. %% @spec (NodeId) -> [States] | [] @@ -882,20 +1089,29 @@ get_nodes_helper(NodeTree, %% they can implement this function like this: %% ```get_states(NodeId) -> %% node_default:get_states(NodeId).'''</p> -get_states(NodeId) -> - case catch ejabberd_odbc:sql_query_t( - ["select jid, affiliation, subscriptions " - "from pubsub_state " - "where nodeid='", NodeId, "';"]) of - {selected, ["jid", "affiliation", "subscriptions"], RItems} -> - {result, lists:map(fun({SJID, Affiliation, Subscriptions}) -> - #pubsub_state{stateid = {decode_jid(SJID), NodeId}, - items = itemids(NodeId, SJID), - affiliation = decode_affiliation(Affiliation), - subscriptions = decode_subscriptions(Subscriptions)} - end, RItems)}; - _ -> - {result, []} +-spec(get_states/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [mod_pubsub:pubsubState()]} +). +get_states(NodeIdx) -> + case catch + ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions " + "from pubsub_state where nodeid='">>, + NodeIdx, <<"';">>]) + of + {selected, + [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], + RItems} -> + {result, + lists:map(fun ({SJID, Affiliation, Subscriptions}) -> + #pubsub_state{stateid = {decode_jid(SJID), NodeIdx}, + items = itemids(NodeIdx, SJID), + affiliation = decode_affiliation(Affiliation), + subscriptions = decode_subscriptions(Subscriptions)} + end, + RItems)}; + _ -> {result, []} end. %% @spec (NodeId, JID) -> [State] | [] @@ -903,50 +1119,78 @@ get_states(NodeId) -> %% JID = mod_pubsub:jid() %% State = mod_pubsub:pubsubItems() %% @doc <p>Returns a state (one state list), given its reference.</p> -get_state(NodeId, JID) -> - State = get_state_without_itemids(NodeId, JID), + +-spec(get_state/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: ljid()) + -> mod_pubsub:pubsubState() +). +get_state(NodeIdx, JID) -> + State = get_state_without_itemids(NodeIdx, JID), {SJID, _} = State#pubsub_state.stateid, - State#pubsub_state{items = itemids(NodeId, SJID)}. -get_state_without_itemids(NodeId, JID) -> + State#pubsub_state{items = itemids(NodeIdx, SJID)}. + +-spec(get_state_without_itemids/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: jid()) + -> mod_pubsub:pubsubState() +). +get_state_without_itemids(NodeIdx, JID) -> J = encode_jid(JID), - case catch ejabberd_odbc:sql_query_t( - ["select jid, affiliation, subscriptions " - "from pubsub_state " - "where jid='", J, "' " - "and nodeid='", NodeId, "';"]) of - {selected, ["jid", "affiliation", "subscriptions"], [{SJID, Affiliation, Subscriptions}]} -> - #pubsub_state{stateid = {decode_jid(SJID), NodeId}, - affiliation = decode_affiliation(Affiliation), - subscriptions = decode_subscriptions(Subscriptions)}; - _ -> - #pubsub_state{stateid={JID, NodeId}} + case catch + ejabberd_odbc:sql_query_t([<<"select jid, affiliation, subscriptions " + "from pubsub_state where jid='">>, + J, <<"' and nodeid='">>, NodeIdx, + <<"';">>]) + of + {selected, + [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], + [{SJID, Affiliation, Subscriptions}]} -> + #pubsub_state{stateid = {decode_jid(SJID), NodeIdx}, + affiliation = decode_affiliation(Affiliation), + subscriptions = decode_subscriptions(Subscriptions)}; + _ -> #pubsub_state{stateid = {JID, NodeIdx}} end. %% @spec (State) -> ok | {error, Reason::stanzaError()} %% State = mod_pubsub:pubsubStates() %% @doc <p>Write a state into database.</p> + +-spec(set_state/1 :: +( + State :: mod_pubsub:pubsubState()) + -> {result, []} +). set_state(State) -> - {_, NodeId} = State#pubsub_state.stateid, - set_state(NodeId, State). -set_state(NodeId, State) -> - %% NOTE: in odbc version, as we do not handle item list, - %% we just need to update affiliation and subscription - %% cause {JID,NodeId} is the key. if it does not exists, then we insert it. - %% MySQL can be optimized using INSERT ... ON DUPLICATE KEY as well + {_, NodeIdx} = State#pubsub_state.stateid, + set_state(NodeIdx, State). + +-spec(set_state/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + State :: mod_pubsub:pubsubState()) + -> {result, []} +). +set_state(NodeIdx, State) -> {JID, _} = State#pubsub_state.stateid, J = encode_jid(JID), S = encode_subscriptions(State#pubsub_state.subscriptions), A = encode_affiliation(State#pubsub_state.affiliation), - case catch ejabberd_odbc:sql_query_t( - ["update pubsub_state " - "set subscriptions='", S, "', affiliation='", A, "' " - "where nodeid='", NodeId, "' and jid='", J, "';"]) of - {updated, 1} -> - ok; - _ -> - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('", NodeId, "', '", J, "', '", A, "', '", S, "');"]) + case catch + ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, + S, <<"', affiliation='">>, A, + <<"' where nodeid='">>, NodeIdx, + <<"' and jid='">>, J, <<"';">>]) + of + {updated, 1} -> ok; + _ -> + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " + "affiliation, subscriptions) values('">>, + NodeIdx, <<"', '">>, J, <<"', '">>, A, + <<"', '">>, S, <<"');">>]) end, {result, []}. @@ -956,10 +1200,9 @@ set_state(NodeId, State) -> %% @doc <p>Delete a state from database.</p> del_state(NodeId, JID) -> J = encode_jid(JID), - catch ejabberd_odbc:sql_query_t( - ["delete from pubsub_state " - "where jid='", J, "' " - "and nodeid='", NodeId, "';"]), + catch + ejabberd_odbc:sql_query_t([<<"delete from pubsub_state where jid='">>, + J, <<"' and nodeid='">>, NodeId, <<"';">>]), ok. %% @spec (NodeId, From) -> {[Items],RsmOut} | [] @@ -976,139 +1219,179 @@ del_state(NodeId, JID) -> %% ```get_items(NodeId, From) -> %% node_default:get_items(NodeId, From).'''</p> get_items(NodeId, _From) -> - case catch ejabberd_odbc:sql_query_t( - ["select itemid, publisher, creation, modification, payload " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "order by modification desc;"]) of - {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} -> - {result, lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems)}; - _ -> - {result, []} + case catch + ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " + "modification, payload from pubsub_item " + "where nodeid='">>, + NodeId, + <<"' order by modification desc;">>]) + of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, + <<"modification">>, <<"payload">>], + RItems} -> + {result, + lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end, + RItems)}; + _ -> {result, []} end. -get_items(NodeId, From, none) -> - MaxItems = case catch ejabberd_odbc:sql_query_t( - ["select val from pubsub_node_option " - "where nodeid='", NodeId, "' " - "and name='max_items';"]) of - {selected, ["val"], [{Value}]} -> - Tokens = element(2, erl_scan:string(Value++".")), - element(2, erl_parse:parse_term(Tokens)); - _ -> - ?MAXITEMS - end, - get_items(NodeId, From, #rsm_in{max=MaxItems}); -get_items(NodeId, _From, #rsm_in{max=M, direction=Direction, id=I, index=IncIndex})-> - Max = ?PUBSUB:escape(i2l(M)), - - {Way, Order} = case Direction of - aft -> {"<", "desc"}; - before when I == [] -> {"is not", "asc"}; - before -> {">", "asc"}; - _ when IncIndex =/= undefined -> {"<", "desc"}; % using index - _ -> {"is not", "desc"}% Can be better - end, - [AttrName, Id] = case I of - undefined when IncIndex =/= undefined -> - case catch ejabberd_odbc:sql_query_t( - ["select modification from pubsub_item pi " - "where exists ( " - "select count(*) as count1 " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "and modification > pi.modification " - "having count1 = ",?PUBSUB:escape(i2l(IncIndex))," );"]) of - {selected, [_], [{O}]} -> ["modification", "'"++O++"'"]; - _ -> ["modification", "null"] - end; - undefined -> ["modification", "null"]; - [] -> ["modification", "null"]; - I -> [A, B] = string:tokens(?PUBSUB:escape(i2l(I)), "@"), - [A, "'"++B++"'"] - end, - Count= case catch ejabberd_odbc:sql_query_t( - ["select count(*) " - "from pubsub_item " - "where nodeid='", NodeId, "';"]) of - {selected, [_], [{C}]} -> C; - _ -> "0" - end, - case catch ejabberd_odbc:sql_query_t( - ["select itemid, publisher, creation, modification, payload " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "and ", AttrName," ", Way, " ", Id, " " - "order by ", AttrName," ", Order," limit ", i2l(Max)," ;"]) of - {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} -> - case length(RItems) of - 0 -> {result, {[], #rsm_out{count=Count}}}; - _ -> - {_, _, _, F, _} = hd(RItems), - Index = case catch ejabberd_odbc:sql_query_t( - ["select count(*) " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "and ", AttrName," > '", F, "';"]) of - %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; - {selected, [_], [{In}]} -> In; - _ -> "0" - end, - %{F, _} = string:to_integer(FStr), - {_, _, _, L, _} = lists:last(RItems), - RsmOut = #rsm_out{count=Count, index=Index, first="modification@"++F, last="modification@"++i2l(L)}, - {result, {lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems), RsmOut}} - end; - _ -> - {result, {[], none}} +get_items(NodeId, From, none) -> + MaxItems = case catch + ejabberd_odbc:sql_query_t([<<"select val from pubsub_node_option where " + "nodeid='">>, + NodeId, + <<"' and name='max_items';">>]) + of + {selected, [<<"val">>], [{Value}]} -> + Tokens = element(2, + erl_scan:string(<<Value/binary, ".">>)), + element(2, erl_parse:parse_term(Tokens)); + _ -> ?MAXITEMS + end, + get_items(NodeId, From, #rsm_in{max = MaxItems}); +get_items(NodeId, _From, + #rsm_in{max = M, direction = Direction, id = I, + index = IncIndex}) -> + Max = (?PUBSUB):escape(i2l(M)), + {Way, Order} = case Direction of + aft -> {<<"<">>, <<"desc">>}; + before when I == [] -> {<<"is not">>, <<"asc">>}; + before -> {<<">">>, <<"asc">>}; + _ when IncIndex =/= undefined -> + {<<"<">>, <<"desc">>}; % using index + _ -> {<<"is not">>, <<"desc">>}% Can be better + end, + [AttrName, Id] = case I of + undefined when IncIndex =/= undefined -> + case catch + ejabberd_odbc:sql_query_t([<<"select modification from pubsub_item " + "pi where exists ( select count(*) as " + "count1 from pubsub_item where nodeid='">>, + NodeId, + <<"' and modification > pi.modification " + "having count1 = ">>, + (?PUBSUB):escape(i2l(IncIndex)), + <<" );">>]) + of + {selected, [_], [{O}]} -> + [<<"modification">>, <<"'", O/binary, "'">>]; + _ -> [<<"modification">>, <<"null">>] + end; + undefined -> [<<"modification">>, <<"null">>]; + [] -> [<<"modification">>, <<"null">>]; + I -> + [A, B] = str:tokens((?PUBSUB):escape(i2l(I)), + <<"@">>), + [A, <<"'", B/binary, "'">>] + end, + Count = case catch + ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where " + "nodeid='">>, + NodeId, <<"';">>]) + of + {selected, [_], [{C}]} -> C; + _ -> <<"0">> + end, + case catch + ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " + "modification, payload from pubsub_item " + "where nodeid='">>, + NodeId, <<"' and ">>, AttrName, <<" ">>, + Way, <<" ">>, Id, <<" order by ">>, + AttrName, <<" ">>, Order, <<" limit ">>, + i2l(Max), <<" ;">>]) + of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, + <<"modification">>, <<"payload">>], + RItems} -> + case str:len(RItems) of + 0 -> {result, {[], #rsm_out{count = Count}}}; + _ -> + {_, _, _, F, _} = hd(RItems), + Index = case catch + ejabberd_odbc:sql_query_t([<<"select count(*) from pubsub_item where " + "nodeid='">>, + NodeId, <<"' and ">>, + AttrName, <<" > '">>, + F, <<"';">>]) + of + %{selected, [_], [{C}, {In}]} -> [string:strip(C, both, $"), string:strip(In, both, $")]; + {selected, [_], [{In}]} -> In; + _ -> <<"0">> + end, + {_, _, _, L, _} = lists:last(RItems), + RsmOut = #rsm_out{count = Count, index = Index, + first = <<"modification@", F/binary>>, + last = <<"modification@", (i2l(L))/binary>>}, + {result, + {lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) + end, + RItems), + RsmOut}} + end; + _ -> {result, {[], none}} end. -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) -> +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, none). + +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, _SubId, RSM) -> SubKey = jlib:jid_tolower(JID), GenKey = jlib:jid_remove_resource(SubKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey), - Whitelisted = can_fetch_item(Affiliation, Subscriptions), - if - %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - Affiliation == outcast -> - %% Requesting entity is blocked - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and (not PresenceSubscription) -> - %% Entity is not authorized to create a subscription (presence subscription required) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; - (AccessModel == roster) and (not RosterGroup) -> - %% Entity is not authorized to create a subscription (not in roster group) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; - (AccessModel == whitelist) and (not Whitelisted) -> - %% Node has whitelist access model and entity lacks required affiliation - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; - (AccessModel == authorize) and (not Whitelisted) -> - %% Node has authorize access model - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> - get_items(NodeId, JID, RSM) + {Affiliation, Subscriptions} = + select_affiliation_subscriptions(NodeId, GenKey, + SubKey), + Whitelisted = can_fetch_item(Affiliation, + Subscriptions), + if %%SubId == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubId -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; + (AccessModel == presence) and + not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + (AccessModel == authorize) and not Whitelisted -> + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> get_items(NodeId, JID, RSM) end. get_last_items(NodeId, _From, Count) -> - case catch ejabberd_odbc:sql_query_t( - ["select itemid, publisher, creation, modification, payload " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "order by modification desc limit ", i2l(Count), ";"]) of - {selected, ["itemid", "publisher", "creation", "modification", "payload"], RItems} -> - {result, lists:map(fun(RItem) -> raw_to_item(NodeId, RItem) end, RItems)}; - _ -> - {result, []} + case catch + ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " + "modification, payload from pubsub_item " + "where nodeid='">>, + NodeId, + <<"' order by modification desc limit ">>, + i2l(Count), <<";">>]) + of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, + <<"modification">>, <<"payload">>], + RItems} -> + {result, + lists:map(fun (RItem) -> raw_to_item(NodeId, RItem) end, + RItems)}; + _ -> {result, []} end. %% @spec (NodeId, ItemId) -> [Item] | [] @@ -1117,49 +1400,56 @@ get_last_items(NodeId, _From, Count) -> %% Item = mod_pubsub:pubsubItems() %% @doc <p>Returns an item (one item list), given its reference.</p> get_item(NodeId, ItemId) -> - I = ?PUBSUB:escape(ItemId), - case catch ejabberd_odbc:sql_query_t( - ["select itemid, publisher, creation, modification, payload " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "and itemid='", I,"';"]) of - {selected, ["itemid", "publisher", "creation", "modification", "payload"], [RItem]} -> - {result, raw_to_item(NodeId, RItem)}; - _ -> - {error, ?ERR_ITEM_NOT_FOUND} + I = (?PUBSUB):escape(ItemId), + case catch + ejabberd_odbc:sql_query_t([<<"select itemid, publisher, creation, " + "modification, payload from pubsub_item " + "where nodeid='">>, + NodeId, <<"' and itemid='">>, I, + <<"';">>]) + of + {selected, + [<<"itemid">>, <<"publisher">>, <<"creation">>, + <<"modification">>, <<"payload">>], + [RItem]} -> + {result, raw_to_item(NodeId, RItem)}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} end. -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> + +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, _SubId) -> SubKey = jlib:jid_tolower(JID), GenKey = jlib:jid_remove_resource(SubKey), - {Affiliation, Subscriptions} = select_affiliation_subscriptions(NodeId, GenKey, SubKey), - Whitelisted = can_fetch_item(Affiliation, Subscriptions), - if - %%SubId == "", ?? -> - %% Entity has multiple subscriptions to the node but does not specify a subscription ID - %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; - %%InvalidSubId -> - %% Entity is subscribed but specifies an invalid subscription ID - %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - Affiliation == outcast -> - %% Requesting entity is blocked - {error, ?ERR_FORBIDDEN}; - (AccessModel == presence) and (not PresenceSubscription) -> - %% Entity is not authorized to create a subscription (presence subscription required) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}; - (AccessModel == roster) and (not RosterGroup) -> - %% Entity is not authorized to create a subscription (not in roster group) - {error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}; - (AccessModel == whitelist) and (not Whitelisted) -> - %% Node has whitelist access model and entity lacks required affiliation - {error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")}; - (AccessModel == authorize) and (not Whitelisted) -> - %% Node has authorize access model - {error, ?ERR_FORBIDDEN}; - %%MustPay -> - %% % Payment is required for a subscription - %% {error, ?ERR_PAYMENT_REQUIRED}; - true -> - get_item(NodeId, ItemId) + {Affiliation, Subscriptions} = + select_affiliation_subscriptions(NodeId, GenKey, + SubKey), + Whitelisted = can_fetch_item(Affiliation, + Subscriptions), + if %%SubId == "", ?? -> + %% Entity has multiple subscriptions to the node but does not specify a subscription ID + %{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}; + %%InvalidSubId -> + %% Entity is subscribed but specifies an invalid subscription ID + %{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + Affiliation == outcast -> {error, ?ERR_FORBIDDEN}; + (AccessModel == presence) and + not PresenceSubscription -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"presence-subscription-required">>)}; + (AccessModel == roster) and not RosterGroup -> + {error, + ?ERR_EXTENDED((?ERR_NOT_AUTHORIZED), + <<"not-in-roster-group">>)}; + (AccessModel == whitelist) and not Whitelisted -> + {error, + ?ERR_EXTENDED((?ERR_NOT_ALLOWED), <<"closed-node">>)}; + (AccessModel == authorize) and not Whitelisted -> + {error, ?ERR_FORBIDDEN}; + %%MustPay -> + %% % Payment is required for a subscription + %% {error, ?ERR_PAYMENT_REQUIRED}; + true -> get_item(NodeId, ItemId) end. %% @spec (Item) -> ok | {error, Reason::stanzaError()} @@ -1167,26 +1457,36 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S %% @doc <p>Write an item into database.</p> set_item(Item) -> {ItemId, NodeId} = Item#pubsub_item.itemid, - I = ?PUBSUB:escape(ItemId), + I = (?PUBSUB):escape(ItemId), {C, _} = Item#pubsub_item.creation, {M, JID} = Item#pubsub_item.modification, P = encode_jid(JID), Payload = Item#pubsub_item.payload, - XML = ?PUBSUB:escape(lists:flatten(lists:map(fun(X) -> xml:element_to_string(X) end, Payload))), - S = fun({T1, T2, T3}) -> - lists:flatten([i2l(T1, 6), ":", i2l(T2, 6), ":", i2l(T3, 6)]) + XML = (?PUBSUB):escape(lists:flatten(lists:map(fun + (X) -> + xml:element_to_binary(X) + end, + Payload))), + S = fun ({T1, T2, T3}) -> + lists:flatten([i2l(T1, 6), <<":">>, i2l(T2, 6), <<":">>, + i2l(T3, 6)]) end, - case catch ejabberd_odbc:sql_query_t( - ["update pubsub_item " - "set publisher='", P, "', modification='", S(M), "', payload='", XML, "' " - "where nodeid='", NodeId, "' and itemid='", I, "';"]) of - {updated, 1} -> - ok; - _ -> - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_item " - "(nodeid, itemid, publisher, creation, modification, payload) " - "values('", NodeId, "', '", I, "', '", P, "', '", S(C), "', '", S(M), "', '", XML, "');"]) + case catch + ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>, + P, <<"', modification='">>, S(M), + <<"', payload='">>, XML, + <<"' where nodeid='">>, NodeId, + <<"' and itemid='">>, I, <<"';">>]) + of + {updated, 1} -> ok; + _ -> + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_item (nodeid, itemid, " + "publisher, creation, modification, payload) " + "values('">>, + NodeId, <<"', '">>, I, <<"', '">>, P, + <<"', '">>, S(C), <<"', '">>, S(M), + <<"', '">>, XML, <<"');">>]) end, {result, []}. @@ -1195,161 +1495,181 @@ set_item(Item) -> %% ItemId = string() %% @doc <p>Delete an item from database.</p> del_item(NodeId, ItemId) -> - I = ?PUBSUB:escape(ItemId), - catch ejabberd_odbc:sql_query_t( - ["delete from pubsub_item " - "where itemid='", I, "' " - "and nodeid='", NodeId, "';"]). -del_items(_, []) -> - ok; -del_items(NodeId, [ItemId]) -> - del_item(NodeId, ItemId); -del_items(NodeId, ItemIds) -> - I = string:join([["'", ?PUBSUB:escape(X), "'"] || X <- ItemIds], ","), - catch ejabberd_odbc:sql_query_t( - ["delete from pubsub_item " - "where itemid in (", I, ") " - "and nodeid='", NodeId, "';"]). + I = (?PUBSUB):escape(ItemId), + catch + ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid='">>, + I, <<"' and nodeid='">>, NodeId, <<"';">>]). +del_items(_, []) -> ok; +del_items(NodeId, [ItemId]) -> del_item(NodeId, ItemId); +del_items(NodeId, ItemIds) -> %% @doc <p>Return the name of the node if known: Default is to return %% node id.</p> -get_item_name(_Host, _Node, Id) -> - Id. + I = str:join([[<<"'">>, (?PUBSUB):escape(X), <<"'">>] + || X <- ItemIds], + <<",">>), + catch + ejabberd_odbc:sql_query_t([<<"delete from pubsub_item where itemid " + "in (">>, + I, <<") and nodeid='">>, NodeId, <<"';">>]). -node_to_path(Node) -> - string:tokens(binary_to_list(Node), "/"). +get_item_name(_Host, _Node, Id) -> Id. -path_to_node([]) -> - <<>>; -path_to_node(Path) -> - list_to_binary(string:join([""|Path], "/")). +node_to_path(Node) -> str:tokens((Node), <<"/">>). +path_to_node([]) -> <<>>; +path_to_node(Path) -> %% @spec (Affiliation, Subscription) -> true | false %% Affiliation = owner | member | publisher | outcast | none %% Subscription = subscribed | none %% @doc Determines if the combination of Affiliation and Subscribed %% are allowed to get items from a node. -can_fetch_item(owner, _) -> true; -can_fetch_item(member, _) -> true; -can_fetch_item(publisher, _) -> true; -can_fetch_item(outcast, _) -> false; -can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions); + iolist_to_binary(str:join([<<"">> | Path], <<"/">>)). + +can_fetch_item(owner, _) -> true; +can_fetch_item(member, _) -> true; +can_fetch_item(publisher, _) -> true; +can_fetch_item(outcast, _) -> false; +can_fetch_item(none, Subscriptions) -> + is_subscribed(Subscriptions); can_fetch_item(_Affiliation, _Subscription) -> false. is_subscribed(Subscriptions) -> lists:any(fun ({subscribed, _SubId}) -> true; - (_) -> false - end, Subscriptions). + (_) -> false + end, + Subscriptions). %% Returns the first item where Pred() is true in List -first_in_list(_Pred, []) -> - false; +first_in_list(_Pred, []) -> false; first_in_list(Pred, [H | T]) -> case Pred(H) of - true -> {value, H}; - _ -> first_in_list(Pred, T) + true -> {value, H}; + _ -> first_in_list(Pred, T) end. itemids(NodeId, {U, S, R}) -> itemids(NodeId, encode_jid({U, S, R})); itemids(NodeId, SJID) -> - case catch ejabberd_odbc:sql_query_t( - ["select itemid " - "from pubsub_item " - "where nodeid='", NodeId, "' " - "and publisher like '", SJID, "%' " - "order by modification desc;"]) of - {selected, ["itemid"], RItems} -> - lists:map(fun({ItemId}) -> ItemId end, RItems); - _ -> - [] + case catch + ejabberd_odbc:sql_query_t([<<"select itemid from pubsub_item where " + "nodeid='">>, + NodeId, <<"' and publisher like '">>, + SJID, + <<"%' order by modification desc;">>]) + of + {selected, [<<"itemid">>], RItems} -> + lists:map(fun ({ItemId}) -> ItemId end, RItems); + _ -> [] end. select_affiliation_subscriptions(NodeId, JID) -> J = encode_jid(JID), - case catch ejabberd_odbc:sql_query_t( - ["select affiliation,subscriptions from pubsub_state " - "where nodeid='", NodeId, "' and jid='", J, "';"]) of - {selected, ["affiliation", "subscriptions"], [{A, S}]} -> - {decode_affiliation(A), decode_subscriptions(S)}; - _ -> - {none, []} + case catch + ejabberd_odbc:sql_query_t([<<"select affiliation,subscriptions from " + "pubsub_state where nodeid='">>, + NodeId, <<"' and jid='">>, J, <<"';">>]) + of + {selected, [<<"affiliation">>, <<"subscriptions">>], + [{A, S}]} -> + {decode_affiliation(A), decode_subscriptions(S)}; + _ -> {none, []} end. + select_affiliation_subscriptions(NodeId, JID, JID) -> select_affiliation_subscriptions(NodeId, JID); -select_affiliation_subscriptions(NodeId, GenKey, SubKey) -> +select_affiliation_subscriptions(NodeId, GenKey, + SubKey) -> {result, Affiliation} = get_affiliation(NodeId, GenKey), - {result, Subscriptions} = get_subscriptions(NodeId, SubKey), + {result, Subscriptions} = get_subscriptions(NodeId, + SubKey), {Affiliation, Subscriptions}. update_affiliation(NodeId, JID, Affiliation) -> J = encode_jid(JID), A = encode_affiliation(Affiliation), - case catch ejabberd_odbc:sql_query_t( - ["update pubsub_state " - "set affiliation='", A, "' " - "where nodeid='", NodeId, "' and jid='", J, "';"]) of - {updated, 1} -> - ok; - _ -> - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('", NodeId, "', '", J, "', '", A, "', '');"]) + case catch + ejabberd_odbc:sql_query_t([<<"update pubsub_state set affiliation='">>, + A, <<"' where nodeid='">>, NodeId, + <<"' and jid='">>, J, <<"';">>]) + of + {updated, 1} -> ok; + _ -> + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " + "affiliation, subscriptions) values('">>, + NodeId, <<"', '">>, J, <<"', '">>, A, + <<"', '');">>]) end. update_subscription(NodeId, JID, Subscription) -> J = encode_jid(JID), S = encode_subscriptions(Subscription), - case catch ejabberd_odbc:sql_query_t( - ["update pubsub_state " - "set subscriptions='", S, "' " - "where nodeid='", NodeId, "' and jid='", J, "';"]) of - {updated, 1} -> - ok; - _ -> - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_state(nodeid, jid, affiliation, subscriptions) " - "values('", NodeId, "', '", J, "', 'n', '", S, "');"]) + case catch + ejabberd_odbc:sql_query_t([<<"update pubsub_state set subscriptions='">>, + S, <<"' where nodeid='">>, NodeId, + <<"' and jid='">>, J, <<"';">>]) + of + {updated, 1} -> ok; + _ -> + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_state(nodeid, jid, " + "affiliation, subscriptions) values('">>, + NodeId, <<"', '">>, J, <<"', 'n', '">>, + S, <<"');">>]) end. -decode_jid(SJID) -> jlib:jid_tolower(jlib:string_to_jid(SJID)). +decode_jid(SJID) -> + jlib:jid_tolower(jlib:string_to_jid(SJID)). -decode_node(N) -> ?PUBSUB:string_to_node(N). +decode_node(N) -> (?PUBSUB):string_to_node(N). -decode_affiliation("o") -> owner; -decode_affiliation("p") -> publisher; -decode_affiliation("m") -> member; -decode_affiliation("c") -> outcast; +decode_affiliation(<<"o">>) -> owner; +decode_affiliation(<<"p">>) -> publisher; +decode_affiliation(<<"m">>) -> member; +decode_affiliation(<<"c">>) -> outcast; decode_affiliation(_) -> none. -decode_subscription("s") -> subscribed; -decode_subscription("p") -> pending; -decode_subscription("u") -> unconfigured; +decode_subscription(<<"s">>) -> subscribed; +decode_subscription(<<"p">>) -> pending; +decode_subscription(<<"u">>) -> unconfigured; decode_subscription(_) -> none. + decode_subscriptions(Subscriptions) -> - lists:foldl(fun(Subscription, Acc) -> - case string:tokens(Subscription, ":") of - [S, SubId] -> [{decode_subscription(S), SubId}|Acc]; - _ -> Acc - end - end, [], string:tokens(Subscriptions, ",")). - -encode_jid(JID) -> ?PUBSUB:escape(jlib:jid_to_string(JID)). - -encode_affiliation(owner) -> "o"; -encode_affiliation(publisher) -> "p"; -encode_affiliation(member) -> "m"; -encode_affiliation(outcast) -> "c"; -encode_affiliation(_) -> "n". - -encode_subscription(subscribed) -> "s"; -encode_subscription(pending) -> "p"; -encode_subscription(unconfigured) -> "u"; -encode_subscription(_) -> "n". + lists:foldl(fun (Subscription, Acc) -> + case str:tokens(Subscription, <<":">>) of + [S, SubId] -> [{decode_subscription(S), SubId} | Acc]; + _ -> Acc + end + end, + [], str:tokens(Subscriptions, <<",">>)). + +%-spec(encode_jid/1 :: +%( +% JID :: jid() | jid()) +% -> binary() +%). +encode_jid(JID) -> + (?PUBSUB):escape(jlib:jid_to_string(JID)). + +encode_affiliation(owner) -> <<"o">>; +encode_affiliation(publisher) -> <<"p">>; +encode_affiliation(member) -> <<"m">>; +encode_affiliation(outcast) -> <<"c">>; +encode_affiliation(_) -> <<"n">>. + +encode_subscription(subscribed) -> <<"s">>; +encode_subscription(pending) -> <<"p">>; +encode_subscription(unconfigured) -> <<"u">>; +encode_subscription(_) -> <<"n">>. + encode_subscriptions(Subscriptions) -> - string:join(lists:map(fun({S, SubId}) -> - encode_subscription(S)++":"++SubId - end, Subscriptions), ","). + str:join(lists:map(fun ({S, SubId}) -> + <<(encode_subscription(S))/binary, ":", + SubId/binary>> + end, + Subscriptions), + <<",">>). %%% record getter/setter @@ -1357,32 +1677,38 @@ state_to_raw(NodeId, State) -> {JID, _} = State#pubsub_state.stateid, J = encode_jid(JID), A = encode_affiliation(State#pubsub_state.affiliation), - S = encode_subscriptions(State#pubsub_state.subscriptions), - ["'", NodeId, "', '", J, "', '", A, "', '", S, "'"]. + S = + encode_subscriptions(State#pubsub_state.subscriptions), + [<<"'">>, NodeId, <<"', '">>, J, <<"', '">>, A, + <<"', '">>, S, <<"'">>]. -raw_to_item(NodeId, {ItemId, SJID, Creation, Modification, XML}) -> +raw_to_item(NodeId, + {ItemId, SJID, Creation, Modification, XML}) -> JID = decode_jid(SJID), - ToTime = fun(Str) -> - [T1,T2,T3] = string:tokens(Str, ":"), + ToTime = fun (Str) -> + [T1, T2, T3] = str:tokens(Str, <<":">>), {l2i(T1), l2i(T2), l2i(T3)} end, Payload = case xml_stream:parse_element(XML) of - {error, _Reason} -> []; - El -> [El] + {error, _Reason} -> []; + El -> [El] end, #pubsub_item{itemid = {ItemId, NodeId}, - creation={ToTime(Creation), JID}, - modification={ToTime(Modification), JID}, + creation = {ToTime(Creation), JID}, + modification = {ToTime(Modification), JID}, payload = Payload}. -l2i(L) when is_list(L) -> list_to_integer(L); +l2i(L) when is_binary(L) -> jlib:binary_to_integer(L); l2i(I) when is_integer(I) -> I. -i2l(I) when is_integer(I) -> integer_to_list(I); -i2l(L) when is_list(L) -> L. + +i2l(I) when is_integer(I) -> + iolist_to_binary(integer_to_list(I)); +i2l(L) when is_binary(L) -> L. + i2l(I, N) when is_integer(I) -> i2l(i2l(I), N); -i2l(L, N) when is_list(L) -> - case length(L) of - N -> L; - C when C > N -> L; - _ -> i2l([$0|L], N) +i2l(L, N) when is_binary(L) -> + case str:len(L) of + N -> L; + C when C > N -> L; + _ -> i2l(<<$0, L/binary>>, N) end. diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl index c83b445e2..b93a57eb5 100644 --- a/src/mod_pubsub/node_mb.erl +++ b/src/mod_pubsub/node_mb.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 pep microblog PubSub plugin. %%% <p> To be used, mod_pubsub must be configured : %%% {mod_pubsub, [ % requires mod_caps @@ -34,66 +35,43 @@ %%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> -module(node_mb). + -author('eric@ohmforce.com'). -include("ejabberd.hrl"). + -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_pep:init(Host, ServerHost, Opts). terminate(Host, ServerHost) -> - node_pep:terminate(Host, ServerHost), - ok. + node_pep:terminate(Host, ServerHost), ok. options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, false}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, false}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, presence}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -102,50 +80,51 @@ options() -> {presence_based_delivery, true}]. features() -> - ["create-nodes", %* - "auto-create", %* - "auto-subscribe", %* - "delete-nodes", %* - "delete-items", %* - "filtered-notifications", %* - "modify-affiliations", - "outcast-affiliation", - "persistent-items", - "publish", %* - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", %* - "retrieve-subscriptions", - "subscribe" %* - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_pep:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + [<<"create-nodes">>, %* + <<"auto-create">>, %* + <<"auto-subscribe">>, %* + <<"delete-nodes">>, %* + <<"delete-items">>, %* + <<"filtered-notifications">>, %* + <<"modify-affiliations">>, <<"outcast-affiliation">>, + <<"persistent-items">>, + <<"publish">>, %* + <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, + <<"retrieve-items">>, %* + <<"retrieve-subscriptions">>, <<"subscribe">>]. + +create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access) -> + node_pep:create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access). create_node(NodeId, Owner) -> node_pep:create_node(NodeId, Owner). -delete_node(Removed) -> - node_pep:delete_node(Removed). +delete_node(Removed) -> node_pep:delete_node(Removed). subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_pep:subscribe_node( - NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). + node_pep:subscribe_node(NodeId, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_pep:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_pep:unsubscribe_node(NodeId, Sender, Subscriber, + SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_pep:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_pep:publish_item(NodeId, Publisher, Model, + MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> node_pep:remove_extra_items(NodeId, MaxItems, ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_pep:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_pep:delete_item(NodeId, Publisher, PublishModel, + ItemId). purge_node(NodeId, Owner) -> node_pep:purge_node(NodeId, Owner). @@ -172,41 +151,40 @@ get_subscriptions(NodeId, Owner) -> node_pep:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_pep:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_pep:set_subscriptions(NodeId, Owner, Subscription, + SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_pep:get_states(NodeId). +get_states(NodeId) -> node_pep:get_states(NodeId). get_state(NodeId, JID) -> node_pep:get_state(NodeId, JID). -set_state(State) -> - node_pep:set_state(State). +set_state(State) -> node_pep:set_state(State). get_items(NodeId, From) -> node_pep:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_pep:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_pep:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_pep:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_pep:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_pep:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_pep:set_item(Item). +set_item(Item) -> node_pep:set_item(Item). get_item_name(Host, Node, Id) -> node_pep:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_pep:node_to_path(Node). - -path_to_node(Path) -> - node_pep:path_to_node(Path). +node_to_path(Node) -> node_pep:node_to_path(Node). +path_to_node(Path) -> node_pep:path_to_node(Path). diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl index f4c3f76b3..38a9bcec7 100644 --- a/src/mod_pubsub/node_pep.erl +++ b/src/mod_pubsub/node_pep.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.'' @@ -27,47 +29,30 @@ %%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> -module(node_pep). + -author('christophe.romain@process-one.net'). -include("ejabberd.hrl"). + -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts), @@ -75,20 +60,15 @@ init(Host, ServerHost, Opts) -> ok. terminate(Host, ServerHost) -> - node_hometree:terminate(Host, ServerHost), - ok. + node_hometree:terminate(Host, ServerHost), ok. +-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, false}, - {purge_offline, false}, - {persist_items, false}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, presence}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, false}, + {purge_offline, false}, {persist_items, false}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, presence}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -96,184 +76,411 @@ options() -> {deliver_notifications, true}, {presence_based_delivery, true}]. +-spec(features/0 :: () -> Features::[binary(),...]). features() -> - ["create-nodes", %* - "auto-create", %* - "auto-subscribe", %* - "delete-nodes", %* - "delete-items", %* - "filtered-notifications", %* - "modify-affiliations", - "outcast-affiliation", - "persistent-items", - "publish", %* - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", %* - "retrieve-subscriptions", - "subscribe" %* - ]. + [<<"create-nodes">>, %* + <<"auto-create">>, %* + <<"auto-subscribe">>, %* + <<"delete-nodes">>, %* + <<"delete-items">>, %* + <<"filtered-notifications">>, %* + <<"modify-affiliations">>, <<"outcast-affiliation">>, + <<"persistent-items">>, + <<"publish">>, %* + <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, + <<"retrieve-items">>, %* + <<"retrieve-subscriptions">>, <<"subscribe">>]. + + +-spec(create_node_permission/6 :: +( + Host :: mod_pubsub:hostPEP(), + ServerHost :: binary(), + NodeId :: mod_pubsub:nodeId(), + _ParentNodeId :: mod_pubsub:nodeId(), + Owner :: jid(), + Access :: atom()) + -> {result, boolean()} +). create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), {User, Server, _Resource} = LOwner, Allowed = case LOwner of - {"", Host, ""} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case Host of - {User, Server, _} -> true; - _ -> false - end; - E -> - ?DEBUG("Create not allowed : ~p~n", [E]), - false - end - end, + {<<"">>, Host, <<"">>} -> + true; % pubsub service always allowed + _ -> + case acl:match_rule(ServerHost, Access, LOwner) of + allow -> + case Host of + {User, Server, _} -> true; + _ -> false + end; + E -> ?DEBUG("Create not allowed : ~p~n", [E]), false + end + end, {result, Allowed}. -create_node(NodeId, Owner) -> - node_hometree:create_node(NodeId, Owner). +-spec(create_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} +). +create_node(NodeIdx, Owner) -> + node_hometree:create_node(NodeIdx, Owner). + +-spec(delete_node/1 :: +( + Nodes :: [mod_pubsub:pubsubNode(),...]) + -> {result, + {[], + [{mod_pubsub:pubsubNode(), + [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]},...]},...] + } + } +). delete_node(Removed) -> - case node_hometree:delete_node(Removed) of - {result, {_, _, Removed}} -> {result, {[], Removed}}; - Error -> Error + {result, {_, _, Removed}} = node_hometree:delete_node(Removed), + {result, {[], Removed}}. +% case node_hometree:delete_node(Removed) of +% {result, {_, _, Removed}} -> {result, {[], Removed}}; +% Error -> Error +% end. + +-spec(subscribe_node/8 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + AccessModel :: mod_pubsub:accessModel(), + SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + Options :: mod_pubsub:subOptions()) + -> {result, {default, subscribed, mod_pubsub:subId()}} + | {result, {default, subscribed, mod_pubsub:subId(), send_last}} + | {result, {default, pending, mod_pubsub:subId()}} + %%% + | {error, xmlel()} +). +subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, Options) -> + node_hometree:subscribe_node(NodeIdx, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options). + +-spec(unsubscribe_node/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + SubId :: subId()) + -> {result, default} + % + | {error, xmlel()} +). +unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) -> + case node_hometree:unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) of + {error, Error} -> {error, Error}; + {result, _} -> {result, []} end. -subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node( - NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - case node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of - {error, Error} -> {error, Error}; - {result, _} -> {result, []} - end. - -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). - +-spec(publish_item/6 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + Max_Items :: non_neg_integer(), + ItemId :: <<>> | mod_pubsub:itemId(), + Payload :: mod_pubsub:payload()) + -> {result, {default, broadcast, [mod_pubsub:itemId()]}} + %%% + | {error, xmlel()} +). +publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_hometree:publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload). + +-spec(remove_extra_items/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Max_Items :: unlimited | non_neg_integer(), + ItemIds :: [mod_pubsub:itemId()]) + -> {result, + {NewItems::[mod_pubsub:itemId()], + OldItems::[mod_pubsub:itemId()]} + } +). remove_extra_items(NodeId, MaxItems, ItemIds) -> node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). -delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree:purge_node(NodeId, Owner). +-spec(delete_item/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + ItemId :: <<>> | mod_pubsub:itemId()) + -> {result, {default, broadcast}} + %%% + | {error, xmlel()} +). +delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> + node_hometree:delete_item(NodeIdx, Publisher, PublishModel, ItemId). + +-spec(purge_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} + | {error, xmlel()} +). +purge_node(NodeIdx, Owner) -> + node_hometree:purge_node(NodeIdx, Owner). + +-spec(get_entity_affiliations/2 :: +( + Host :: mod_pubsub:hostPEP(), + Owner :: jid()) + -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} +). get_entity_affiliations(_Host, Owner) -> {_, D, _} = SubKey = jlib:jid_tolower(Owner), SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}), - NodeTree = case catch ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun(#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> [{Node, A}|Acc]; - _ -> Acc - end - end, [], States), + States = mnesia:match_object(#pubsub_state{stateid = + {GenKey, '_'}, + _ = '_'}), + NodeTree = case catch + ets:lookup(gen_mod:get_module_proc(D, config), nodetree) + of + [{nodetree, N}] -> N; + _ -> nodetree_tree + end, + Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, + affiliation = A}, + Acc) -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {{_, D, _}, _}} = + Node -> + [{Node, A} | Acc]; + _ -> Acc + end + end, + [], States), {result, Reply}. -get_node_affiliations(NodeId) -> - node_hometree:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree:get_affiliation(NodeId, Owner). - -set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). +-spec(get_node_affiliations/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [{ljid(), mod_pubsub:affiliation()}]} +). +get_node_affiliations(NodeIdx) -> + node_hometree:get_node_affiliations(NodeIdx). + +-spec(get_affiliation/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, mod_pubsub:affiliation()} +). +get_affiliation(NodeIdx, Owner) -> + node_hometree:get_affiliation(NodeIdx, Owner). + +-spec(set_affiliation/3 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid(), + Affiliation :: mod_pubsub:affiliation()) + -> ok +). +set_affiliation(NodeIdx, Owner, Affiliation) -> + node_hometree:set_affiliation(NodeIdx, Owner, Affiliation). + +-spec(get_entity_subscriptions/2 :: +( + Host :: mod_pubsub:hostPEP(), + Owner :: jid()) + -> {result, + [{mod_pubsub:pubsubNode(), + mod_pubsub:subscription(), + mod_pubsub:subId(), + ljid()}] + } +). get_entity_subscriptions(_Host, Owner) -> {U, D, _} = SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), States = case SubKey of - GenKey -> mnesia:match_object( - #pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'}); - _ -> mnesia:match_object( - #pubsub_state{stateid = {GenKey, '_'}, _ = '_'}) - ++ mnesia:match_object( - #pubsub_state{stateid = {SubKey, '_'}, _ = '_'}) - end, - NodeTree = case catch ets:lookup(gen_mod:get_module_proc(D, config), nodetree) of - [{nodetree, N}] -> N; - _ -> nodetree_tree - end, - Reply = lists:foldl(fun(#pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) -> - case NodeTree:get_node(N) of - #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> - lists:foldl(fun({subscribed, SubID}, Acc2) -> - [{Node, subscribed, SubID, J} | Acc2]; - ({pending, _SubID}, Acc2) -> - [{Node, pending, J} | Acc2]; - (S, Acc2) -> - [{Node, S, J} | Acc2] - end, Acc, Ss); - _ -> Acc - end - end, [], States), + GenKey -> + mnesia:match_object(#pubsub_state{stateid = + {{U, D, '_'}, '_'}, + _ = '_'}); + _ -> + mnesia:match_object(#pubsub_state{stateid = + {GenKey, '_'}, + _ = '_'}) + ++ + mnesia:match_object(#pubsub_state{stateid = + {SubKey, '_'}, + _ = '_'}) + end, + NodeTree = case catch + ets:lookup(gen_mod:get_module_proc(D, config), nodetree) + of + [{nodetree, N}] -> N; + _ -> nodetree_tree + end, + Reply = lists:foldl(fun (#pubsub_state{stateid = {J, N}, + subscriptions = Ss}, + Acc) -> + case NodeTree:get_node(N) of + #pubsub_node{nodeid = {{_, D, _}, _}} = Node -> + lists:foldl(fun + ({subscribed, SubID}, Acc2) -> + [{Node, subscribed, SubID, J} | Acc2]; + ({pending, _SubID}, Acc2) -> + [{Node, pending, J} | Acc2]; + (S, Acc2) -> + [{Node, S, J} | Acc2] + end, Acc, Ss); + _ -> Acc + end + end, + [], States), {result, Reply}. -get_node_subscriptions(NodeId) -> - %% note: get_node_subscriptions is used for broadcasting - %% there should not have any subscriptions - %% but that call returns also all subscription to none - %% and this is required for broadcast to occurs - %% DO NOT REMOVE - node_hometree:get_node_subscriptions(NodeId). - -get_subscriptions(NodeId, Owner) -> - node_hometree:get_subscriptions(NodeId, Owner). - -set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). - +-spec(get_node_subscriptions/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, + [{ljid(), mod_pubsub:subscription(), mod_pubsub:subId()}] | + [{ljid(), none},...] + } +). +get_node_subscriptions(NodeIdx) -> + node_hometree:get_node_subscriptions(NodeIdx). + +-spec(get_subscriptions/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid()) + -> {result, [{mod_pubsub:subscription(), mod_pubsub:subId()}]} +). +get_subscriptions(NodeIdx, Owner) -> + node_hometree:get_subscriptions(NodeIdx, Owner). + +-spec(set_subscriptions/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid(), + Subscription :: mod_pubsub:subscription(), + SubId :: mod_pubsub:subId()) + -> ok + %%% + | {error, xmlel()} +). +set_subscriptions(NodeIdx, Owner, Subscription, SubId) -> + node_hometree:set_subscriptions(NodeIdx, Owner, Subscription, SubId). + +-spec(get_pending_nodes/2 :: +( + Host :: mod_pubsub:hostPubsub(), + Owner :: jid()) + -> {result, [mod_pubsub:nodeId()]} +). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). - -get_state(NodeId, JID) -> - node_hometree:get_state(NodeId, JID). - -set_state(State) -> - node_hometree:set_state(State). - -get_items(NodeId, From) -> - node_hometree:get_items(NodeId, From). - -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). - -get_item(NodeId, ItemId) -> - node_hometree:get_item(NodeId, ItemId). - -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). - -set_item(Item) -> - node_hometree:set_item(Item). +-spec(get_states/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [mod_pubsub:pubsubState()]} +). +get_states(NodeIdx) -> node_hometree:get_states(NodeIdx). + +-spec(get_state/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: ljid()) + -> mod_pubsub:pubsubState() +). +get_state(NodeIdx, JID) -> + node_hometree:get_state(NodeIdx, JID). + +-spec(set_state/1 :: +( + State::mod_pubsub:pubsubState()) + -> ok +). +set_state(State) -> node_hometree:set_state(State). + +-spec(get_items/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + _From :: jid()) + -> {result, [mod_pubsub:pubsubItem()]} +). +get_items(NodeIdx, From) -> + node_hometree:get_items(NodeIdx, From). + +-spec(get_items/6 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + JID :: jid(), + AccessModel :: mod_pubsub:accessModel(), + Presence_Subscription :: boolean(), + RosterGroup :: boolean(), + _SubId :: mod_pubsub:subId()) + -> {result, [mod_pubsub:pubsubItem()]} + %%% + | {error, xmlel()} +). +get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). + +-spec(get_item/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + ItemId :: mod_pubsub:itemId()) + -> {result, mod_pubsub:pubsubItem()} + | {error, xmlel()} +). +get_item(NodeIdx, ItemId) -> + node_hometree:get_item(NodeIdx, ItemId). + +-spec(get_item/7 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + ItemId :: mod_pubsub:itemId(), + JID :: jid(), + AccessModel :: mod_pubsub:accessModel(), + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + SubId :: mod_pubsub:subId()) + -> {result, mod_pubsub:pubsubItem()} + | {error, xmlel()} +). +get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, + SubId) -> + node_hometree:get_item(NodeIdx, ItemId, JID, AccessModel, PresenceSubscription, + RosterGroup, SubId). + +-spec(set_item/1 :: +( + Item::mod_pubsub:pubsubItem()) + -> ok +). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). +node_to_path(Node) -> node_flat:node_to_path(Node). +path_to_node(Path) -> node_flat:path_to_node(Path). %%% %%% Internal @@ -284,13 +491,13 @@ path_to_node(Path) -> %% Check that the mod_caps module is enabled in that Jabber Host %% If not, show a warning message in the ejabberd log file. complain_if_modcaps_disabled(ServerHost) -> - Modules = ejabberd_config:get_local_option({modules, ServerHost}), + Modules = ejabberd_config:get_local_option({modules, ServerHost}, fun(Ms) when is_list(Ms) -> Ms end), ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules], case ModCaps of - [] -> - ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub of host ~p. " - "This plugin requires mod_caps to be enabled, " - "but it isn't.", [ServerHost]); - _ -> - ok + [] -> + ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " + "of host ~p. This plugin requires mod_caps " + "to be enabled, but it isn't.", + [ServerHost]); + _ -> ok end. diff --git a/src/mod_pubsub/node_pep_odbc.erl b/src/mod_pubsub/node_pep_odbc.erl index 9057e870c..8f4f36a45 100644 --- a/src/mod_pubsub/node_pep_odbc.erl +++ b/src/mod_pubsub/node_pep_odbc.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.'' @@ -27,10 +29,13 @@ %%% <p>PubSub plugin nodes are using the {@link gen_pubsub_node} behaviour.</p> -module(node_pep_odbc). + -author('christophe.romain@process-one.net'). -include("ejabberd.hrl"). + -include("pubsub.hrl"). + -include("jlib.hrl"). -define(PUBSUB, mod_pubsub_odbc). @@ -38,42 +43,20 @@ -behaviour(gen_pubsub_node). %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, get_entity_subscriptions/2, get_entity_subscriptions_for_send_last/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/7, - get_items/6, - get_items/3, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - get_last_items/3, - node_to_path/1, - path_to_node/1 - ]). + get_node_subscriptions/1, get_subscriptions/2, + set_subscriptions/4, get_pending_nodes/2, get_states/1, + get_state/2, set_state/1, get_items/7, get_items/6, + get_items/3, get_items/2, get_item/7, get_item/2, + set_item/1, get_item_name/3, get_last_items/3, + node_to_path/1, path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree_odbc:init(Host, ServerHost, Opts), @@ -81,9 +64,9 @@ init(Host, ServerHost, Opts) -> ok. terminate(Host, ServerHost) -> - node_hometree_odbc:terminate(Host, ServerHost), - ok. + node_hometree_odbc:terminate(Host, ServerHost), ok. +-spec(options/0 :: () -> NodeOptions::mod_pubsub:nodeOptions()). options() -> [{odbc, true}, {node_type, pep}, @@ -104,90 +87,191 @@ options() -> {deliver_notifications, true}, {presence_based_delivery, true}]. +-spec(features/0 :: () -> Features::[binary(),...]). features() -> - ["create-nodes", %* - "auto-create", %* - "auto-subscribe", %* - "delete-nodes", %* - "delete-items", %* - "filtered-notifications", %* - "modify-affiliations", - "outcast-affiliation", - "persistent-items", - "publish", %* - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", %* - "retrieve-subscriptions", - "subscribe" %* - ]. - -create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> + [<<"create-nodes">>, %* + <<"auto-create">>, %* + <<"auto-subscribe">>, %* + <<"delete-nodes">>, %* + <<"delete-items">>, %* + <<"filtered-notifications">>, %* + <<"modify-affiliations">>, <<"outcast-affiliation">>, + <<"persistent-items">>, + <<"publish">>, %* + <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, + <<"retrieve-items">>, %* + <<"retrieve-subscriptions">>, <<"subscribe">>]. + +-spec(create_node_permission/6 :: +( + Host :: mod_pubsub:hostPEP(), + ServerHost :: binary(), + NodeId :: mod_pubsub:nodeId(), + _ParentNodeId :: mod_pubsub:nodeId(), + Owner :: jid(), + Access :: atom()) + -> {result, boolean()} +). + +create_node_permission(Host, ServerHost, _NodeId, _ParentNode, Owner, Access) -> LOwner = jlib:jid_tolower(Owner), {User, Server, _Resource} = LOwner, Allowed = case LOwner of - {"", Host, ""} -> - true; % pubsub service always allowed - _ -> - case acl:match_rule(ServerHost, Access, LOwner) of - allow -> - case Host of - {User, Server, _} -> true; - _ -> false - end; - E -> - ?DEBUG("Create not allowed : ~p~n", [E]), - false - end - end, + {<<"">>, Host, <<"">>} -> + true; % pubsub service always allowed + _ -> + case acl:match_rule(ServerHost, Access, LOwner) of + allow -> + case Host of + {User, Server, _} -> true; + _ -> false + end; + E -> ?DEBUG("Create not allowed : ~p~n", [E]), false + end + end, {result, Allowed}. -create_node(NodeId, Owner) -> - case node_hometree_odbc:create_node(NodeId, Owner) of - {result, _} -> {result, []}; - Error -> Error +-spec(create_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, []} +). +create_node(NodeIdx, Owner) -> + case node_hometree_odbc:create_node(NodeIdx, Owner) of + {result, _} -> {result, []}; + Error -> Error end. +-spec(delete_node/1 :: +( + Removed :: [mod_pubsub:pubsubNode(),...]) + -> {result, + {[], + [{mod_pubsub:pubsubNode(), + [{ljid(), [{mod_pubsub:subscription(), mod_pubsub:subId()}]}]}] + } + } +). delete_node(Removed) -> case node_hometree_odbc:delete_node(Removed) of - {result, {_, _, Removed}} -> {result, {[], Removed}}; - Error -> Error + {result, {_, _, Removed}} -> {result, {[], Removed}}; + Error -> Error end. +-spec(subscribe_node/8 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: ljid(), + AccessModel :: mod_pubsub:accessModel(), + SendLast :: 'never' | 'on_sub' | 'on_sub_and_presence', + PresenceSubscription :: boolean(), + RosterGroup :: boolean(), + Options :: mod_pubsub:subOptions()) + -> {result, {default, subscribed, mod_pubsub:subId()}} + | {result, {default, subscribed, mod_pubsub:subId(), send_last}} + | {result, {default, pending, mod_pubsub:subId()}} + %%% + | {error, _} + | {error, _, binary()} +). subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree_odbc:subscribe_node( - NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options). - -unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - case node_hometree_odbc:unsubscribe_node(NodeId, Sender, Subscriber, SubID) of - {error, Error} -> {error, Error}; - {result, _} -> {result, []} + node_hometree_odbc:subscribe_node(NodeId, Sender, + Subscriber, AccessModel, SendLast, + PresenceSubscription, RosterGroup, + Options). + + +-spec(unsubscribe_node/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Sender :: jid(), + Subscriber :: jid(), + SubId :: subId()) + -> {result, []} + % + | {error, _} + | {error, _, binary()} +). +unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) -> + case node_hometree_odbc:unsubscribe_node(NodeIdx, Sender, Subscriber, SubID) of + {error, Error} -> {error, Error}; + {result, _} -> {result, []} end. -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree_odbc:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +-spec(publish_item/6 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + Max_Items :: non_neg_integer(), + ItemId :: <<>> | mod_pubsub:itemId(), + Payload :: mod_pubsub:payload()) + -> {result, {default, broadcast, [mod_pubsub:itemId()]}} + %%% + | {error, _} +). +publish_item(NodeIdx, Publisher, Model, MaxItems, ItemId, Payload) -> + node_hometree_odbc:publish_item(NodeIdx, Publisher, + Model, MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree_odbc:remove_extra_items(NodeId, MaxItems, ItemIds). - + node_hometree_odbc:remove_extra_items(NodeId, MaxItems, + ItemIds). + +-spec(delete_item/4 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Publisher :: jid(), + PublishModel :: mod_pubsub:publishModel(), + ItemId :: <<>> | mod_pubsub:itemId()) + -> {result, {default, broadcast}} + %%% + | {error, _} +). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree_odbc:delete_item(NodeId, Publisher, PublishModel, ItemId). - -purge_node(NodeId, Owner) -> - node_hometree_odbc:purge_node(NodeId, Owner). - + node_hometree_odbc:delete_item(NodeId, Publisher, + PublishModel, ItemId). + +-spec(purge_node/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: jid()) + -> {result, {default, broadcast}} + | {error, _} +). +purge_node(NodeIdx, Owner) -> + node_hometree_odbc:purge_node(NodeIdx, Owner). + +-spec(get_entity_affiliations/2 :: +( + Host :: mod_pubsub:hostPubsub(), + Owner :: jid()) + -> {result, [{mod_pubsub:pubsubNode(), mod_pubsub:affiliation()}]} +). get_entity_affiliations(_Host, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), node_hometree_odbc:get_entity_affiliations(OwnerKey, Owner). -get_node_affiliations(NodeId) -> - node_hometree_odbc:get_node_affiliations(NodeId). - -get_affiliation(NodeId, Owner) -> - node_hometree_odbc:get_affiliation(NodeId, Owner). +-spec(get_node_affiliations/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> {result, [{ljid(), mod_pubsub:affiliation()}]} +). +get_node_affiliations(NodeIdx) -> + node_hometree_odbc:get_node_affiliations(NodeIdx). + +-spec(get_affiliation/2 :: +( + NodeIdx :: mod_pubsub:nodeIdx(), + Owner :: ljid()) + -> {result, mod_pubsub:affiliation()} +). +get_affiliation(NodeIdx, Owner) -> + node_hometree_odbc:get_affiliation(NodeIdx, Owner). set_affiliation(NodeId, Owner, Affiliation) -> node_hometree_odbc:set_affiliation(NodeId, Owner, Affiliation). @@ -195,82 +279,102 @@ set_affiliation(NodeId, Owner, Affiliation) -> get_entity_subscriptions(_Host, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - Host = ?PUBSUB:escape(element(2, SubKey)), + Host = (?PUBSUB):escape(element(2, SubKey)), SJ = node_hometree_odbc:encode_jid(SubKey), GJ = node_hometree_odbc:encode_jid(GenKey), Query = case SubKey of - GenKey -> - ["select host, node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid " - "and jid like '", GJ, "%' " - "and host like '%@", Host, "';"]; - _ -> - ["select host, node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid " - "and jid in ('", SJ, "', '", GJ, "') " - "and host like '%@", Host, "';"] - end, + GenKey -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_nod" + "e n where i.nodeid = n.nodeid and jid " + "like '">>, + GJ, <<"%' and host like '%@">>, Host, <<"';">>]; + _ -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_nod" + "e n where i.nodeid = n.nodeid and jid " + "in ('">>, + SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, + <<"';">>] + end, Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, ["host", "node", "type", "nodeid", "jid", "subscriptions"], RItems} -> - lists:map(fun({H, N, T, I, J, S}) -> - O = node_hometree_odbc:decode_jid(H), - Node = nodetree_tree_odbc:raw_to_node(O, {N, "", T, I}), - {Node, node_hometree_odbc:decode_subscriptions(S), node_hometree_odbc:decode_jid(J)} - end, RItems); - _ -> - [] - end, + {selected, + [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, + <<"jid">>, <<"subscriptions">>], + RItems} -> + lists:map(fun ({H, N, T, I, J, S}) -> + O = node_hometree_odbc:decode_jid(H), + Node = nodetree_tree_odbc:raw_to_node(O, + {N, + <<"">>, + T, + I}), + {Node, + node_hometree_odbc:decode_subscriptions(S), + node_hometree_odbc:decode_jid(J)} + end, + RItems); + _ -> [] + end, {result, Reply}. get_entity_subscriptions_for_send_last(_Host, Owner) -> SubKey = jlib:jid_tolower(Owner), GenKey = jlib:jid_remove_resource(SubKey), - Host = ?PUBSUB:escape(element(2, SubKey)), + Host = (?PUBSUB):escape(element(2, SubKey)), SJ = node_hometree_odbc:encode_jid(SubKey), GJ = node_hometree_odbc:encode_jid(GenKey), Query = case SubKey of - GenKey -> - ["select host, node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid " - "and name='send_last_published_item' and val='on_sub_and_presence' " - "and jid like '", GJ, "%' " - "and host like '%@", Host, "';"]; - _ -> - ["select host, node, type, i.nodeid, jid, subscriptions " - "from pubsub_state i, pubsub_node n, pubsub_node_option o " - "where i.nodeid = n.nodeid and n.nodeid = o.nodeid " - "and name='send_last_published_item' and val='on_sub_and_presence' " - "and jid in ('", SJ, "', '", GJ, "') " - "and host like '%@", Host, "';"] - end, + GenKey -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_nod" + "e n, pubsub_node_option o where i.nodeid " + "= n.nodeid and n.nodeid = o.nodeid and " + "name='send_last_published_item' and " + "val='on_sub_and_presence' and jid like " + "'">>, + GJ, <<"%' and host like '%@">>, Host, <<"';">>]; + _ -> + [<<"select host, node, type, i.nodeid, jid, " + "subscriptions from pubsub_state i, pubsub_nod" + "e n, pubsub_node_option o where i.nodeid " + "= n.nodeid and n.nodeid = o.nodeid and " + "name='send_last_published_item' and " + "val='on_sub_and_presence' and jid in " + "('">>, + SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, + <<"';">>] + end, Reply = case catch ejabberd_odbc:sql_query_t(Query) of - {selected, ["host", "node", "type", "nodeid", "jid", "subscriptions"], RItems} -> - lists:map(fun({H, N, T, I, J, S}) -> - O = node_hometree_odbc:decode_jid(H), - Node = nodetree_tree_odbc:raw_to_node(O, {N, "", T, I}), - {Node, node_hometree_odbc:decode_subscriptions(S), node_hometree_odbc:decode_jid(J)} - end, RItems); - _ -> - [] - end, + {selected, + [<<"host">>, <<"node">>, <<"type">>, <<"nodeid">>, + <<"jid">>, <<"subscriptions">>], + RItems} -> + lists:map(fun ({H, N, T, I, J, S}) -> + O = node_hometree_odbc:decode_jid(H), + Node = nodetree_tree_odbc:raw_to_node(O, + {N, + <<"">>, + T, + I}), + {Node, + node_hometree_odbc:decode_subscriptions(S), + node_hometree_odbc:decode_jid(J)} + end, + RItems); + _ -> [] + end, {result, Reply}. get_node_subscriptions(NodeId) -> - %% note: get_node_subscriptions is used for broadcasting - %% there should not have any subscriptions - %% but that call returns also all subscription to none - %% and this is required for broadcast to occurs - %% DO NOT REMOVE node_hometree_odbc:get_node_subscriptions(NodeId). get_subscriptions(NodeId, Owner) -> node_hometree_odbc:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree_odbc:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree_odbc:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree_odbc:get_pending_nodes(Host, Owner). @@ -281,17 +385,23 @@ get_states(NodeId) -> get_state(NodeId, JID) -> node_hometree_odbc:get_state(NodeId, JID). -set_state(State) -> - node_hometree_odbc:set_state(State). +set_state(State) -> node_hometree_odbc:set_state(State). get_items(NodeId, From) -> node_hometree_odbc:get_items(NodeId, From). + get_items(NodeId, From, RSM) -> node_hometree_odbc:get_items(NodeId, From, RSM). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) -> - node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM). + +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, none). + +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM) -> + node_hometree_odbc:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId, RSM). get_last_items(NodeId, JID, Count) -> node_hometree_odbc:get_last_items(NodeId, JID, Count). @@ -299,20 +409,20 @@ get_last_items(NodeId, JID, Count) -> get_item(NodeId, ItemId) -> node_hometree_odbc:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree_odbc:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree_odbc:get_item(NodeId, ItemId, JID, + AccessModel, PresenceSubscription, RosterGroup, + SubId). -set_item(Item) -> - node_hometree_odbc:set_item(Item). +set_item(Item) -> node_hometree_odbc:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree_odbc:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat_odbc:node_to_path(Node). +node_to_path(Node) -> node_flat_odbc:node_to_path(Node). -path_to_node(Path) -> - node_flat_odbc:path_to_node(Path). +path_to_node(Path) -> node_flat_odbc:path_to_node(Path). %%% %%% Internal @@ -323,14 +433,16 @@ path_to_node(Path) -> %% Check that the mod_caps module is enabled in that Jabber Host %% If not, show a warning message in the ejabberd log file. complain_if_modcaps_disabled(ServerHost) -> - Modules = ejabberd_config:get_local_option({modules, ServerHost}), - ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules], + Modules = ejabberd_config:get_local_option({modules, + ServerHost}, + fun(Ms) when is_list(Ms) -> Ms end), + ModCaps = [mod_caps_enabled + || {mod_caps, _Opts} <- Modules], case ModCaps of - [] -> - ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub of host ~p. " - "This plugin requires mod_caps to be enabled, " - "but it isn't.", [ServerHost]); - _ -> - ok + [] -> + ?WARNING_MSG("The PEP plugin is enabled in mod_pubsub " + "of host ~p. This plugin requires mod_caps " + "to be enabled, but it isn't.", + [ServerHost]); + _ -> ok end. - diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl index 8a1cd3e45..27b7158a1 100644 --- a/src/mod_pubsub/node_private.erl +++ b/src/mod_pubsub/node_private.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.'' @@ -24,9 +26,11 @@ %%% ==================================================================== -module(node_private). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). @@ -40,39 +44,18 @@ %% (this makes code cleaner, but execution a little bit longer) %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). - +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -81,16 +64,11 @@ terminate(Host, ServerHost) -> node_hometree:terminate(Host, ServerHost). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, whitelist}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, whitelist}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -99,25 +77,18 @@ options() -> {presence_based_delivery, false}]. features() -> - ["create-nodes", - "delete-nodes", - "delete-items", - "instant-nodes", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications" - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, - Owner, Access). + [<<"create-nodes">>, <<"delete-nodes">>, + <<"delete-items">>, <<"instant-nodes">>, + <<"outcast-affiliation">>, <<"persistent-items">>, + <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, <<"retrieve-items">>, + <<"retrieve-subscriptions">>, <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access) -> + node_hometree:create_node_permission(Host, ServerHost, + Node, ParentNode, Owner, Access). create_node(NodeId, Owner) -> node_hometree:create_node(NodeId, Owner). @@ -125,23 +96,28 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree:delete_node(Removed). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, - PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, - SendLast, PresenceSubscription, RosterGroup, - Options). +subscribe_node(NodeId, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_hometree:subscribe_node(NodeId, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_hometree:unsubscribe_node(NodeId, Sender, + Subscriber, SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_hometree:publish_item(NodeId, Publisher, Model, + MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). + node_hometree:remove_extra_items(NodeId, MaxItems, + ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_hometree:delete_item(NodeId, Publisher, + PublishModel, ItemId). purge_node(NodeId, Owner) -> node_hometree:purge_node(NodeId, Owner). @@ -156,7 +132,8 @@ get_affiliation(NodeId, Owner) -> node_hometree:get_affiliation(NodeId, Owner). set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). + node_hometree:set_affiliation(NodeId, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> node_hometree:get_entity_subscriptions(Host, Owner). @@ -168,41 +145,40 @@ get_subscriptions(NodeId, Owner) -> node_hometree:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). +get_states(NodeId) -> node_hometree:get_states(NodeId). get_state(NodeId, JID) -> node_hometree:get_state(NodeId, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeId, From) -> node_hometree:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_hometree:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). +node_to_path(Node) -> node_flat:node_to_path(Node). +path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl index 8004696f0..3c391cfe8 100644 --- a/src/mod_pubsub/node_public.erl +++ b/src/mod_pubsub/node_public.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.'' @@ -24,9 +26,11 @@ %%% ==================================================================== -module(node_public). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_node). @@ -40,39 +44,18 @@ %% (this makes code cleaner, but execution a little bit longer) %% API definition --export([init/3, terminate/2, - options/0, features/0, - create_node_permission/6, - create_node/2, - delete_node/1, - purge_node/2, - subscribe_node/8, - unsubscribe_node/4, - publish_item/6, - delete_item/4, - remove_extra_items/3, - get_entity_affiliations/2, - get_node_affiliations/1, - get_affiliation/2, - set_affiliation/3, - get_entity_subscriptions/2, - get_node_subscriptions/1, - get_subscriptions/2, - set_subscriptions/4, - get_pending_nodes/2, - get_states/1, - get_state/2, - set_state/1, - get_items/6, - get_items/2, - get_item/7, - get_item/2, - set_item/1, - get_item_name/3, - node_to_path/1, - path_to_node/1 - ]). - +-export([init/3, terminate/2, options/0, features/0, + create_node_permission/6, create_node/2, delete_node/1, + purge_node/2, subscribe_node/8, unsubscribe_node/4, + publish_item/6, delete_item/4, remove_extra_items/3, + get_entity_affiliations/2, get_node_affiliations/1, + get_affiliation/2, set_affiliation/3, + get_entity_subscriptions/2, get_node_subscriptions/1, + get_subscriptions/2, set_subscriptions/4, + get_pending_nodes/2, get_states/1, get_state/2, + set_state/1, get_items/6, get_items/2, get_item/7, + get_item/2, set_item/1, get_item_name/3, node_to_path/1, + path_to_node/1]). init(Host, ServerHost, Opts) -> node_hometree:init(Host, ServerHost, Opts). @@ -81,16 +64,11 @@ terminate(Host, ServerHost) -> node_hometree:terminate(Host, ServerHost). options() -> - [{deliver_payloads, true}, - {notify_config, false}, - {notify_delete, false}, - {notify_retract, true}, - {purge_offline, false}, - {persist_items, true}, - {max_items, ?MAXITEMS}, - {subscribe, true}, - {access_model, open}, - {roster_groups_allowed, []}, + [{deliver_payloads, true}, {notify_config, false}, + {notify_delete, false}, {notify_retract, true}, + {purge_offline, false}, {persist_items, true}, + {max_items, ?MAXITEMS}, {subscribe, true}, + {access_model, open}, {roster_groups_allowed, []}, {publish_model, publishers}, {notification_type, headline}, {max_payload_size, ?MAX_PAYLOAD_SIZE}, @@ -99,24 +77,18 @@ options() -> {presence_based_delivery, false}]. features() -> - ["create-nodes", - "delete-nodes", - "delete-items", - "instant-nodes", - "outcast-affiliation", - "persistent-items", - "publish", - "purge-nodes", - "retract-items", - "retrieve-affiliations", - "retrieve-items", - "retrieve-subscriptions", - "subscribe", - "subscription-notifications" - ]. - -create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access) -> - node_hometree:create_node_permission(Host, ServerHost, Node, ParentNode, Owner, Access). + [<<"create-nodes">>, <<"delete-nodes">>, + <<"delete-items">>, <<"instant-nodes">>, + <<"outcast-affiliation">>, <<"persistent-items">>, + <<"publish">>, <<"purge-nodes">>, <<"retract-items">>, + <<"retrieve-affiliations">>, <<"retrieve-items">>, + <<"retrieve-subscriptions">>, <<"subscribe">>, + <<"subscription-notifications">>]. + +create_node_permission(Host, ServerHost, Node, + ParentNode, Owner, Access) -> + node_hometree:create_node_permission(Host, ServerHost, + Node, ParentNode, Owner, Access). create_node(NodeId, Owner) -> node_hometree:create_node(NodeId, Owner). @@ -124,20 +96,28 @@ create_node(NodeId, Owner) -> delete_node(Removed) -> node_hometree:delete_node(Removed). -subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) -> - node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options). +subscribe_node(NodeId, Sender, Subscriber, AccessModel, + SendLast, PresenceSubscription, RosterGroup, Options) -> + node_hometree:subscribe_node(NodeId, Sender, Subscriber, + AccessModel, SendLast, PresenceSubscription, + RosterGroup, Options). unsubscribe_node(NodeId, Sender, Subscriber, SubID) -> - node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID). + node_hometree:unsubscribe_node(NodeId, Sender, + Subscriber, SubID). -publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) -> - node_hometree:publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload). +publish_item(NodeId, Publisher, Model, MaxItems, ItemId, + Payload) -> + node_hometree:publish_item(NodeId, Publisher, Model, + MaxItems, ItemId, Payload). remove_extra_items(NodeId, MaxItems, ItemIds) -> - node_hometree:remove_extra_items(NodeId, MaxItems, ItemIds). + node_hometree:remove_extra_items(NodeId, MaxItems, + ItemIds). delete_item(NodeId, Publisher, PublishModel, ItemId) -> - node_hometree:delete_item(NodeId, Publisher, PublishModel, ItemId). + node_hometree:delete_item(NodeId, Publisher, + PublishModel, ItemId). purge_node(NodeId, Owner) -> node_hometree:purge_node(NodeId, Owner). @@ -152,7 +132,8 @@ get_affiliation(NodeId, Owner) -> node_hometree:get_affiliation(NodeId, Owner). set_affiliation(NodeId, Owner, Affiliation) -> - node_hometree:set_affiliation(NodeId, Owner, Affiliation). + node_hometree:set_affiliation(NodeId, Owner, + Affiliation). get_entity_subscriptions(Host, Owner) -> node_hometree:get_entity_subscriptions(Host, Owner). @@ -164,43 +145,42 @@ get_subscriptions(NodeId, Owner) -> node_hometree:get_subscriptions(NodeId, Owner). set_subscriptions(NodeId, Owner, Subscription, SubId) -> - node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId). + node_hometree:set_subscriptions(NodeId, Owner, + Subscription, SubId). get_pending_nodes(Host, Owner) -> node_hometree:get_pending_nodes(Host, Owner). -get_states(NodeId) -> - node_hometree:get_states(NodeId). +get_states(NodeId) -> node_hometree:get_states(NodeId). get_state(NodeId, JID) -> node_hometree:get_state(NodeId, JID). -set_state(State) -> - node_hometree:set_state(State). +set_state(State) -> node_hometree:set_state(State). get_items(NodeId, From) -> node_hometree:get_items(NodeId, From). -get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_items(NodeId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). get_item(NodeId, ItemId) -> node_hometree:get_item(NodeId, ItemId). -get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) -> - node_hometree:get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId). +get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId) -> + node_hometree:get_item(NodeId, ItemId, JID, AccessModel, + PresenceSubscription, RosterGroup, SubId). -set_item(Item) -> - node_hometree:set_item(Item). +set_item(Item) -> node_hometree:set_item(Item). %% @doc <p>Return the name of the node if known: Default is to return %% node id.</p> get_item_name(Host, Node, Id) -> node_hometree:get_item_name(Host, Node, Id). -node_to_path(Node) -> - node_flat:node_to_path(Node). - -path_to_node(Path) -> - node_flat:path_to_node(Path). +node_to_path(Node) -> node_flat:node_to_path(Node). +path_to_node(Path) -> node_flat:path_to_node(Path). diff --git a/src/mod_pubsub/nodetree_dag.erl b/src/mod_pubsub/nodetree_dag.erl index 7074cabc4..2be3e3522 100644 --- a/src/mod_pubsub/nodetree_dag.erl +++ b/src/mod_pubsub/nodetree_dag.erl @@ -16,35 +16,30 @@ %%% ==================================================================== -module(nodetree_dag). + -author('bjc@kublai.com'). %% API --export([init/3, - terminate/2, - options/0, - set_node/1, - get_node/3, - get_node/2, - get_node/1, - get_nodes/2, - get_nodes/1, - get_parentnodes/3, - get_parentnodes_tree/3, - get_subnodes/3, - get_subnodes_tree/3, - create_node/6, +-export([init/3, terminate/2, options/0, set_node/1, + get_node/3, get_node/2, get_node/1, get_nodes/2, + get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, + get_subnodes/3, get_subnodes_tree/3, create_node/6, delete_node/2]). -include_lib("stdlib/include/qlc.hrl"). -include("ejabberd.hrl"). + -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_nodetree). -define(DEFAULT_NODETYPE, leaf). + -define(DEFAULT_PARENTS, []). + -define(DEFAULT_CHILDREN, []). -compile(export_all). @@ -58,115 +53,172 @@ init(Host, ServerHost, Opts) -> terminate(Host, ServerHost) -> nodetree_tree:terminate(Host, ServerHost). +-spec(create_node/6 :: +( + Key :: mod_pubsub:hostPubsub(), + NodeID :: mod_pubsub:nodeId(), + Type :: binary(), + Owner :: jid(), + Options :: mod_pubsub:nodeOptions(), + Parents :: [mod_pubsub:nodeId()]) + -> {ok, NodeIdx::mod_pubsub:nodeIdx()} + | {error, xmlel()} +). create_node(Key, NodeID, Type, Owner, Options, Parents) -> OwnerJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), case find_node(Key, NodeID) of - false -> - ID = pubsub_index:new(node), - N = #pubsub_node{nodeid = oid(Key, NodeID), - id = ID, - type = Type, - parents = Parents, - owners = [OwnerJID], - options = Options}, - case set_node(N) of - ok -> {ok, ID}; - Other -> Other - end; - _ -> - {error, ?ERR_CONFLICT} + false -> + NodeIdx = pubsub_index:new(node), + N = #pubsub_node{nodeid = oid(Key, NodeID), id = NodeIdx, + type = Type, parents = Parents, owners = [OwnerJID], + options = Options}, + case set_node(N) of + ok -> {ok, NodeIdx}; + Other -> Other + end; + _ -> {error, ?ERR_CONFLICT} end. -set_node(#pubsub_node{nodeid = {Key, _}, - owners = Owners, - options = Options} = Node) -> - Parents = find_opt(collection, ?DEFAULT_PARENTS, Options), +-spec(set_node/1 :: +( + PubsubNode::mod_pubsub:pubsubNode()) + -> ok + %%% + | {error, xmlel()} +). +set_node(#pubsub_node{nodeid = {Key, _}, owners = Owners, options = Options} = + Node) -> + Parents = find_opt(collection, ?DEFAULT_PARENTS, Options), case validate_parentage(Key, Owners, Parents) of - true -> - %% Update parents whenever the config changes. - mnesia:write(Node#pubsub_node{parents = Parents}); - Other -> - Other + true -> + mnesia:write(Node#pubsub_node{parents = Parents}); + Other -> Other end. +-spec(delete_node/2 :: +( + Key :: mod_pubsub:hostPubsub(), + NodeID :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode(),...] + %%% + | {error, xmlel()} +). delete_node(Key, NodeID) -> case find_node(Key, NodeID) of - false -> - {error, ?ERR_ITEM_NOT_FOUND}; - Node -> - %% Find all of N's children, update their configs to - %% remove N from the collection setting. - lists:foreach(fun (#pubsub_node{options = Opts} = Child) -> - NewOpts = remove_config_parent(NodeID, Opts), - Parents = find_opt(collection, ?DEFAULT_PARENTS, NewOpts), - ok = mnesia:write(pubsub_node, - Child#pubsub_node{ - parents = Parents, - options = NewOpts}, - write) - end, get_subnodes(Key, NodeID)), - - %% Remove and return the requested node. - pubsub_index:free(node, Node#pubsub_node.id), - mnesia:delete_object(pubsub_node, Node, write), - [Node] + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> + lists:foreach(fun (#pubsub_node{options = Opts} = + Child) -> + NewOpts = remove_config_parent(NodeID, Opts), + Parents = find_opt(collection, ?DEFAULT_PARENTS, + NewOpts), + ok = mnesia:write(pubsub_node, + Child#pubsub_node{parents = + Parents, + options = + NewOpts}, + write) + end, + get_subnodes(Key, NodeID)), + pubsub_index:free(node, Node#pubsub_node.id), + mnesia:delete_object(pubsub_node, Node, write), + [Node] end. -options() -> - nodetree_tree:options(). +options() -> nodetree_tree:options(). -get_node(Host, NodeID, _From) -> - get_node(Host, NodeID). +get_node(Host, NodeID, _From) -> get_node(Host, NodeID). +-spec(get_node/2 :: +( + Host :: mod_pubsub:hostPubsub(), + NodeID :: mod_pubsub:nodeId()) + -> mod_pubsub:pubsubNode() + %%% + | {error, xmlel} +). get_node(Host, NodeID) -> case find_node(Host, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - Node -> Node + false -> {error, ?ERR_ITEM_NOT_FOUND}; + Node -> Node end. -get_node(NodeId) -> - nodetree_tree:get_node(NodeId). +-spec(get_node/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> mod_pubsub:pubsubNode() + | {error, xmlel()} +). +get_node(NodeId) -> nodetree_tree:get_node(NodeId). get_nodes(Key, From) -> nodetree_tree:get_nodes(Key, From). -get_nodes(Key) -> - nodetree_tree:get_nodes(Key). - +-spec(get_nodes/1 :: +( + Host::mod_pubsub:host()) + -> [mod_pubsub:pubsubNode()] +). +get_nodes(Key) -> nodetree_tree:get_nodes(Key). + +-spec(get_parentnodes/3 :: +( + Host :: mod_pubsub:hostPubsub(), + NodeID :: mod_pubsub:nodeId(), + _From :: _) + -> [mod_pubsub:pubsubNode()] + %%% + | {error, xmlel()} +). get_parentnodes(Host, NodeID, _From) -> case find_node(Host, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - #pubsub_node{parents = Parents} -> - Q = qlc:q([N || #pubsub_node{nodeid = {NHost, NNode}} = N <- mnesia:table(pubsub_node), - Parent <- Parents, - Host == NHost, - Parent == NNode]), - qlc:e(Q) + false -> {error, ?ERR_ITEM_NOT_FOUND}; + #pubsub_node{parents = Parents} -> + Q = qlc:q([N + || #pubsub_node{nodeid = {NHost, NNode}} = N + <- mnesia:table(pubsub_node), + Parent <- Parents, Host == NHost, Parent == NNode]), + qlc:e(Q) end. get_parentnodes_tree(Host, NodeID, _From) -> Pred = fun (NID, #pubsub_node{nodeid = {_, NNodeID}}) -> NID == NNodeID end, - Tr = fun (#pubsub_node{parents = Parents}) -> Parents end, + Tr = fun (#pubsub_node{parents = Parents}) -> Parents + end, traversal_helper(Pred, Tr, Host, [NodeID]). get_subnodes(Host, NodeID, _From) -> get_subnodes(Host, NodeID). +-spec(get_subnodes/2 :: +( + Host :: mod_pubsub:hostPubsub(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). get_subnodes(Host, <<>>) -> get_subnodes_helper(Host, <<>>); get_subnodes(Host, NodeID) -> case find_node(Host, NodeID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - _ -> get_subnodes_helper(Host, NodeID) + false -> {error, ?ERR_ITEM_NOT_FOUND}; + _ -> get_subnodes_helper(Host, NodeID) end. +-spec(get_subnodes_helper/2 :: +( + Host :: mod_pubsub:hostPubsub(), + NodeID :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). get_subnodes_helper(Host, NodeID) -> - Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = Node <- mnesia:table(pubsub_node), - Host == NHost, - lists:member(NodeID, Parents)]), + Q = qlc:q([Node + || #pubsub_node{nodeid = {NHost, _}, + parents = Parents} = + Node + <- mnesia:table(pubsub_node), + Host == NHost, lists:member(NodeID, Parents)]), qlc:e(Q). get_subnodes_tree(Host, NodeID, From) -> @@ -175,7 +227,7 @@ get_subnodes_tree(Host, NodeID, From) -> end, Tr = fun (#pubsub_node{nodeid = {_, N}}) -> [N] end, traversal_helper(Pred, Tr, 1, Host, [NodeID], - [{0, [get_node(Host, NodeID, From)]}]). + [{0, [get_node(Host, NodeID, From)]}]). %%==================================================================== %% Internal functions @@ -184,10 +236,16 @@ oid(Key, Name) -> {Key, Name}. %% Key = jlib:jid() | host() %% NodeID = string() +-spec(find_node/2 :: +( + Key :: mod_pubsub:hostPubsub(), + NodeID :: mod_pubsub:nodeId()) + -> mod_pubsub:pubsubNode() | false +). find_node(Key, NodeID) -> case mnesia:read(pubsub_node, oid(Key, NodeID), read) of - [] -> false; - [Node] -> Node + [] -> false; + [Node] -> Node end. %% Key = jlib:jid() | host() @@ -195,23 +253,33 @@ find_node(Key, NodeID) -> %% Options = [{Key = atom(), Value = term()}] find_opt(Key, Default, Options) -> case lists:keysearch(Key, 1, Options) of - {value, {Key, Val}} -> Val; - _ -> Default + {value, {Key, Val}} -> Val; + _ -> Default end. +-spec(traversal_helper/4 :: +( + Pred :: fun(), + Tr :: fun(), + Host :: mod_pubsub:hostPubsub(), + NodeId :: [mod_pubsub:pubsubNode(),...]) + -> [{Depth::non_neg_integer(), Nodes::[mod_pubsub:pubsubNode(),...]}] +). + traversal_helper(Pred, Tr, Host, NodeIDs) -> traversal_helper(Pred, Tr, 0, Host, NodeIDs, []). traversal_helper(_Pred, _Tr, _Depth, _Host, [], Acc) -> Acc; traversal_helper(Pred, Tr, Depth, Host, NodeIDs, Acc) -> - Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _}} = Node <- mnesia:table(pubsub_node), - NodeID <- NodeIDs, - Host == NHost, - Pred(NodeID, Node)]), + Q = qlc:q([Node + || #pubsub_node{nodeid = {NHost, _}} = Node + <- mnesia:table(pubsub_node), + NodeID <- NodeIDs, Host == NHost, Pred(NodeID, Node)]), Nodes = qlc:e(Q), IDs = lists:flatmap(Tr, Nodes), - traversal_helper(Pred, Tr, Depth + 1, Host, IDs, [{Depth, Nodes} | Acc]). + traversal_helper(Pred, Tr, Depth + 1, Host, IDs, + [{Depth, Nodes} | Acc]). remove_config_parent(NodeID, Options) -> remove_config_parent(NodeID, Options, []). @@ -220,28 +288,35 @@ remove_config_parent(_NodeID, [], Acc) -> lists:reverse(Acc); remove_config_parent(NodeID, [{collection, Parents} | T], Acc) -> remove_config_parent(NodeID, T, - [{collection, lists:delete(NodeID, Parents)} | Acc]); + [{collection, lists:delete(NodeID, Parents)} | Acc]); remove_config_parent(NodeID, [H | T], Acc) -> remove_config_parent(NodeID, T, [H | Acc]). -validate_parentage(_Key, _Owners, []) -> - true; +-spec(validate_parentage/3 :: +( + Key :: mod_pubsub:hostPubsub(), + Owners :: [ljid(),...], + Parent_NodeIds :: [mod_pubsub:nodeId()]) + -> true + %%% + | {error, xmlel()} +). +validate_parentage(_Key, _Owners, []) -> true; validate_parentage(Key, Owners, [[] | T]) -> validate_parentage(Key, Owners, T); validate_parentage(Key, Owners, [<<>> | T]) -> validate_parentage(Key, Owners, T); validate_parentage(Key, Owners, [ParentID | T]) -> case find_node(Key, ParentID) of - false -> {error, ?ERR_ITEM_NOT_FOUND}; - #pubsub_node{owners = POwners, options = POptions} -> - NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions), - MutualOwners = [O || O <- Owners, PO <- POwners, - O == PO], - case {MutualOwners, NodeType} of - {[], _} -> {error, ?ERR_FORBIDDEN}; - {_, collection} -> validate_parentage(Key, Owners, T); - {_, _} -> {error, ?ERR_NOT_ALLOWED} - end + false -> {error, ?ERR_ITEM_NOT_FOUND}; + #pubsub_node{owners = POwners, options = POptions} -> + NodeType = find_opt(node_type, ?DEFAULT_NODETYPE, POptions), + MutualOwners = [O || O <- Owners, PO <- POwners, O == PO], + case {MutualOwners, NodeType} of + {[], _} -> {error, ?ERR_FORBIDDEN}; + {_, collection} -> validate_parentage(Key, Owners, T); + {_, _} -> {error, ?ERR_NOT_ALLOWED} + end end. %% @spec (Host) -> jid() @@ -249,6 +324,6 @@ validate_parentage(Key, Owners, [ParentID | T]) -> %% @doc <p>Generate pubsub service JID.</p> service_jid(Host) -> case Host of - {U,S,_} -> {jid, U, S, "", U, S, ""}; - _ -> {jid, "", Host, "", "", Host, ""} + {U, S, _} -> jlib:make_jid(U, S, <<>>); + _ -> jlib:make_jid(<<>>, Host, <<>>) end. diff --git a/src/mod_pubsub/nodetree_tree.erl b/src/mod_pubsub/nodetree_tree.erl index 7a30039bd..23159d7a2 100644 --- a/src/mod_pubsub/nodetree_tree.erl +++ b/src/mod_pubsub/nodetree_tree.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.'' @@ -34,32 +36,22 @@ %%% improvements.</p> -module(nodetree_tree). + -author('christophe.romain@process-one.net'). -include_lib("stdlib/include/qlc.hrl"). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_nodetree). --export([init/3, - terminate/2, - options/0, - set_node/1, - get_node/3, - get_node/2, - get_node/1, - get_nodes/2, - get_nodes/1, - get_parentnodes/3, - get_parentnodes_tree/3, - get_subnodes/3, - get_subnodes_tree/3, - create_node/6, - delete_node/2 - ]). - +-export([init/3, terminate/2, options/0, set_node/1, + get_node/3, get_node/2, get_node/1, get_nodes/2, + get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, + get_subnodes/3, get_subnodes_tree/3, create_node/6, + delete_node/2]). %% ================ %% API definition @@ -81,61 +73,79 @@ init(_Host, _ServerHost, _Options) -> mnesia:add_table_index(pubsub_node, id), NodesFields = record_info(fields, pubsub_node), case mnesia:table_info(pubsub_node, attributes) of - NodesFields -> ok; - _ -> - ok - %% mnesia:transform_table(pubsub_state, ignore, StatesFields) + NodesFields -> ok; + _ -> ok end, + %% mnesia:transform_table(pubsub_state, ignore, StatesFields) ok. %% @spec (Host, ServerHost) -> ok %% Host = string() %% ServerHost = string() -terminate(_Host, _ServerHost) -> - ok. %% @spec () -> Options %% Options = [mod_pubsub:nodeOption()] %% @doc Returns the default pubsub node tree options. -options() -> - [{virtual_tree, false}]. +terminate(_Host, _ServerHost) -> ok. + +options() -> [{virtual_tree, false}]. %% @spec (Node) -> ok | {error, Reason} %% Node = mod_pubsub:pubsubNode() %% Reason = mod_pubsub:stanzaError() +-spec(set_node/1 :: +( + Node::mod_pubsub:pubsubNode()) + -> ok +). set_node(Node) when is_record(Node, pubsub_node) -> - mnesia:write(Node); -set_node(_) -> - {error, ?ERR_INTERNAL_SERVER_ERROR}. + mnesia:write(Node). +%set_node(_) -> {error, ?ERR_INTERNAL_SERVER_ERROR}. -get_node(Host, Node, _From) -> - get_node(Host, Node). +get_node(Host, Node, _From) -> get_node(Host, Node). %% @spec (Host, NodeId) -> Node | {error, Reason} %% Host = mod_pubsub:host() %% NodeId = mod_pubsub:nodeId() %% Node = mod_pubsub:pubsubNode() %% Reason = mod_pubsub:stanzaError() +-spec(get_node/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> mod_pubsub:pubsubNode() + | {error, xmlel()} +). get_node(Host, NodeId) -> case catch mnesia:read({pubsub_node, {Host, NodeId}}) of - [Record] when is_record(Record, pubsub_node) -> Record; - [] -> {error, ?ERR_ITEM_NOT_FOUND}; - Error -> Error + [Record] when is_record(Record, pubsub_node) -> Record; + [] -> {error, ?ERR_ITEM_NOT_FOUND} +% Error -> Error end. -get_node(NodeId) -> - case catch mnesia:index_read(pubsub_node, NodeId, #pubsub_node.id) of - [Record] when is_record(Record, pubsub_node) -> Record; - [] -> {error, ?ERR_ITEM_NOT_FOUND}; - Error -> Error +-spec(get_node/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> mod_pubsub:pubsubNode() + | {error, xmlel()} +). +get_node(NodeIdx) -> + case catch mnesia:index_read(pubsub_node, NodeIdx, #pubsub_node.id) of + [Record] when is_record(Record, pubsub_node) -> Record; + [] -> {error, ?ERR_ITEM_NOT_FOUND} +% Error -> Error end. -get_nodes(Host, _From) -> - get_nodes(Host). +get_nodes(Host, _From) -> get_nodes(Host). %% @spec (Host) -> Nodes | {error, Reason} %% Host = mod_pubsub:host() %% Nodes = [mod_pubsub:pubsubNode()] %% Reason = {aborted, atom()} +-spec(get_nodes/1 :: +( + Host::mod_pubsub:host()) + -> [mod_pubsub:pubsubNode()] +). get_nodes(Host) -> mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'}). @@ -144,8 +154,7 @@ get_nodes(Host) -> %% NodeId = mod_pubsub:nodeId() %% From = mod_pubsub:jid() %% @doc <p>Default node tree does not handle parents, return empty list.</p> -get_parentnodes(_Host, _NodeId, _From) -> - []. +get_parentnodes(_Host, _NodeId, _From) -> []. %% @spec (Host, NodeId, From) -> [{Depth, Node}] | [] %% Host = mod_pubsub:host() @@ -155,10 +164,17 @@ get_parentnodes(_Host, _NodeId, _From) -> %% Node = mod_pubsub:pubsubNode() %% @doc <p>Default node tree does not handle parents, return a list %% containing just this node.</p> +-spec(get_parentnodes_tree/3 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId(), + From :: jid()) + -> [{0, [mod_pubsub:pubsubNode(),...]}] +). get_parentnodes_tree(Host, NodeId, From) -> case get_node(Host, NodeId, From) of - Node when is_record(Node, pubsub_node) -> [{0, [Node]}]; - _Error -> [] + Node when is_record(Node, pubsub_node) -> [{0, [Node]}]; + _Error -> [] end. %% @spec (Host, NodeId, From) -> Nodes @@ -169,17 +185,27 @@ get_parentnodes_tree(Host, NodeId, From) -> get_subnodes(Host, NodeId, _From) -> get_subnodes(Host, NodeId). +-spec(get_subnodes/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). get_subnodes(Host, <<>>) -> - Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = N <- mnesia:table(pubsub_node), - Host == NHost, - Parents == []]), + Q = qlc:q([N + || #pubsub_node{nodeid = {NHost, _}, + parents = Parents} = + N + <- mnesia:table(pubsub_node), + Host == NHost, Parents == []]), qlc:e(Q); get_subnodes(Host, Node) -> - Q = qlc:q([N || #pubsub_node{nodeid = {NHost, _}, - parents = Parents} = N <- mnesia:table(pubsub_node), - Host == NHost, - lists:member(Node, Parents)]), + Q = qlc:q([N + || #pubsub_node{nodeid = {NHost, _}, + parents = Parents} = + N + <- mnesia:table(pubsub_node), + Host == NHost, lists:member(Node, Parents)]), qlc:e(Q). get_subnodes_tree(Host, Node, _From) -> @@ -189,21 +215,30 @@ get_subnodes_tree(Host, Node, _From) -> %% Host = mod_pubsub:host() %% NodeId = mod_pubsub:nodeId() %% Nodes = [] | [mod_pubsub:pubsubNode()] +-spec(get_subnodes_tree/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). get_subnodes_tree(Host, NodeId) -> case get_node(Host, NodeId) of - {error, _} -> - []; - Rec -> - BasePlugin = list_to_atom("node_"++Rec#pubsub_node.type), - BasePath = BasePlugin:node_to_path(NodeId), - mnesia:foldl(fun(#pubsub_node{nodeid = {H, N}} = R, Acc) -> - Plugin = list_to_atom("node_"++R#pubsub_node.type), - Path = Plugin:node_to_path(N), - case lists:prefix(BasePath, Path) and (H == Host) of - true -> [R | Acc]; - false -> Acc - end - end, [], pubsub_node) + {error, _} -> []; + Rec -> + BasePlugin = jlib:binary_to_atom(<<"node_", + (Rec#pubsub_node.type)/binary>>), + BasePath = BasePlugin:node_to_path(NodeId), + mnesia:foldl(fun (#pubsub_node{nodeid = {H, N}} = R, + Acc) -> + Plugin = jlib:binary_to_atom(<<"node_", + (R#pubsub_node.type)/binary>>), + Path = Plugin:node_to_path(N), + case lists:prefix(BasePath, Path) and (H == Host) of + true -> [R | Acc]; + false -> Acc + end + end, + [], pubsub_node) end. %% @spec (Host, NodeId, Type, Owner, Options, Parents) -> @@ -216,56 +251,72 @@ get_subnodes_tree(Host, NodeId) -> %% Parents = [] | [mod_pubsub:nodeId()] %% NodeIdx = mod_pubsub:nodeIdx() %% Reason = mod_pubsub:stanzaError() +-spec(create_node/6 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId(), + Type :: binary(), + Owner :: jid(), + Options :: mod_pubsub:nodeOptions(), + Parents :: [mod_pubsub:nodeId()]) + -> {ok, NodeIdx::mod_pubsub:nodeIdx()} + %%% + | {error, xmlel()} +). create_node(Host, NodeId, Type, Owner, Options, Parents) -> BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), case catch mnesia:read({pubsub_node, {Host, NodeId}}) of - [] -> - ParentExists = - case Host of - {_U, _S, _R} -> - %% This is special case for PEP handling - %% PEP does not uses hierarchy - true; - _ -> - case Parents of - [] -> true; - [Parent|_] -> - case catch mnesia:read({pubsub_node, {Host, Parent}}) of - [#pubsub_node{owners = [{[], Host, []}]}] -> true; - [#pubsub_node{owners = Owners}] -> lists:member(BJID, Owners); - _ -> false - end; - _ -> - false - end - end, - case ParentExists of - true -> - NodeIdx = pubsub_index:new(node), - mnesia:write(#pubsub_node{nodeid = {Host, NodeId}, - id = NodeIdx, - parents = Parents, - type = Type, - owners = [BJID], - options = Options}), - {ok, NodeIdx}; - false -> - %% Requesting entity is prohibited from creating nodes - {error, ?ERR_FORBIDDEN} - end; - _ -> - %% NodeID already exists - {error, ?ERR_CONFLICT} + [] -> + ParentExists = case Host of + {_U, _S, _R} -> + %% This is special case for PEP handling + %% PEP does not uses hierarchy + true; + _ -> + case Parents of + [] -> true; + [Parent | _] -> + case catch mnesia:read({pubsub_node, + {Host, Parent}}) + of + [#pubsub_node{owners = + [{[], Host, []}]}] -> + true; + [#pubsub_node{owners = Owners}] -> + lists:member(BJID, Owners); + _ -> false + end; + _ -> false + end + end, + case ParentExists of + true -> + NodeIdx = pubsub_index:new(node), + mnesia:write(#pubsub_node{nodeid = {Host, NodeId}, + id = NodeIdx, parents = Parents, + type = Type, owners = [BJID], + options = Options}), + {ok, NodeIdx}; + false -> {error, ?ERR_FORBIDDEN} + end; + _ -> {error, ?ERR_CONFLICT} end. %% @spec (Host, NodeId) -> Removed %% Host = mod_pubsub:host() %% NodeId = mod_pubsub:nodeId() %% Removed = [mod_pubsub:pubsubNode()] +-spec(delete_node/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode(),...] +). delete_node(Host, NodeId) -> Removed = get_subnodes_tree(Host, NodeId), - lists:foreach(fun(#pubsub_node{nodeid = {_, SubNodeId}, id = SubNodeIdx}) -> - pubsub_index:free(node, SubNodeIdx), - mnesia:delete({pubsub_node, {Host, SubNodeId}}) - end, Removed), + lists:foreach(fun (#pubsub_node{nodeid = {_, SubNodeId}, id = SubNodeIdx}) -> + pubsub_index:free(node, SubNodeIdx), + mnesia:delete({pubsub_node, {Host, SubNodeId}}) + end, + Removed), Removed. diff --git a/src/mod_pubsub/nodetree_tree_odbc.erl b/src/mod_pubsub/nodetree_tree_odbc.erl index f4b9737c9..9756b897b 100644 --- a/src/mod_pubsub/nodetree_tree_odbc.erl +++ b/src/mod_pubsub/nodetree_tree_odbc.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.'' @@ -34,32 +36,24 @@ %%% improvements.</p> -module(nodetree_tree_odbc). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -define(PUBSUB, mod_pubsub_odbc). --define(PLUGIN_PREFIX, "node_"). + +-define(PLUGIN_PREFIX, <<"node_">>). -behaviour(gen_pubsub_nodetree). --export([init/3, - terminate/2, - options/0, - set_node/1, - get_node/3, - get_node/2, - get_node/1, - get_nodes/2, - get_nodes/1, - get_parentnodes/3, - get_parentnodes_tree/3, - get_subnodes/3, - get_subnodes_tree/3, - create_node/6, - delete_node/2 - ]). +-export([init/3, terminate/2, options/0, set_node/1, + get_node/3, get_node/2, get_node/1, get_nodes/2, + get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, + get_subnodes/3, get_subnodes_tree/3, create_node/6, + delete_node/2]). -export([raw_to_node/2]). @@ -76,67 +70,87 @@ %% <p>This function is mainly used to trigger the setup task necessary for the %% plugin. It can be used for example by the developer to create the specific %% module database schema if it does not exists yet.</p> -init(_Host, _ServerHost, _Opts) -> - ok. -terminate(_Host, _ServerHost) -> - ok. - %% @spec () -> [Option] %% Option = mod_pubsub:nodetreeOption() %% @doc Returns the default pubsub node tree options. -options() -> - [{virtual_tree, false}, - {odbc, true}]. - %% @spec (Host, Node, From) -> pubsubNode() | {error, Reason} %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() -get_node(Host, Node, _From) -> - get_node(Host, Node). +init(_Host, _ServerHost, _Opts) -> ok. + +terminate(_Host, _ServerHost) -> ok. + +options() -> [{virtual_tree, false}, {odbc, true}]. + +get_node(Host, Node, _From) -> get_node(Host, Node). + +-spec(get_node/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> mod_pubsub:pubsubNode() + | {error, _} +). get_node(Host, Node) -> - H = ?PUBSUB:escape(Host), - N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)), - case catch ejabberd_odbc:sql_query_t( - ["select node, parent, type, nodeid " - "from pubsub_node " - "where host='", H, "' and node='", N, "';"]) + H = (?PUBSUB):escape(Host), + N = (?PUBSUB):escape(Node), + case catch + ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " + "pubsub_node where host='">>, + H, <<"' and node='">>, N, <<"';">>]) of - {selected, ["node", "parent", "type", "nodeid"], [RItem]} -> - raw_to_node(Host, RItem); - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - {error, ?ERR_ITEM_NOT_FOUND} + {selected, + [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], + [RItem]} -> + raw_to_node(Host, RItem); + {'EXIT', _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} end. -get_node(NodeId) -> - case catch ejabberd_odbc:sql_query_t( - ["select host, node, parent, type " - "from pubsub_node " - "where nodeid='", NodeId, "';"]) + +-spec(get_node/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> mod_pubsub:pubsubNode() + | {error, _} +). +get_node(NodeIdx) -> + case catch + ejabberd_odbc:sql_query_t([<<"select host, node, parent, type from " + "pubsub_node where nodeid='">>, + NodeIdx, <<"';">>]) of - {selected, ["host", "node", "parent", "type"], [{Host, Node, Parent, Type}]} -> - raw_to_node(Host, {Node, Parent, Type, NodeId}); - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - {error, ?ERR_ITEM_NOT_FOUND} + {selected, + [<<"host">>, <<"node">>, <<"parent">>, <<"type">>], + [{Host, Node, Parent, Type}]} -> + raw_to_node(Host, {Node, Parent, Type, NodeIdx}); + {'EXIT', _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} end. %% @spec (Host, From) -> [pubsubNode()] | {error, Reason} %% Host = mod_pubsub:host() | mod_pubsub:jid() -get_nodes(Host, _From) -> - get_nodes(Host). +get_nodes(Host, _From) -> get_nodes(Host). + +-spec(get_nodes/1 :: +( + Host::mod_pubsub:host()) + -> [mod_pubsub:pubsubNode()] +). get_nodes(Host) -> - H = ?PUBSUB:escape(Host), - case catch ejabberd_odbc:sql_query_t( - ["select node, parent, type, nodeid " - "from pubsub_node " - "where host='", H, "';"]) + H = (?PUBSUB):escape(Host), + case catch + ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " + "pubsub_node where host='">>, + H, <<"';">>]) of - {selected, ["node", "parent", "type", "nodeid"], RItems} -> - lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems); - _ -> - [] + {selected, + [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], + RItems} -> + lists:map(fun (Item) -> raw_to_node(Host, Item) end, + RItems); + _ -> [] end. %% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason} @@ -146,9 +160,6 @@ get_nodes(Host) -> %% Depth = integer() %% Record = pubsubNode() %% @doc <p>Default node tree does not handle parents, return empty list.</p> -get_parentnodes(_Host, _Node, _From) -> - []. - %% @spec (Host, Node, From) -> [{Depth, Record}] | {error, Reason} %% Host = mod_pubsub:host() | mod_pubsub:jid() %% Node = mod_pubsub:pubsubNode() @@ -157,10 +168,20 @@ get_parentnodes(_Host, _Node, _From) -> %% Record = pubsubNode() %% @doc <p>Default node tree does not handle parents, return a list %% containing just this node.</p> +get_parentnodes(_Host, _Node, _From) -> []. + +-spec(get_parentnodes_tree/3 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId(), + From :: jid()) + -> [{0, [mod_pubsub:pubsubNode(),...]}] +). + get_parentnodes_tree(Host, Node, From) -> case get_node(Host, Node, From) of - N when is_record(N, pubsub_node) -> [{0, [N]}]; - _Error -> [] + N when is_record(N, pubsub_node) -> [{0, [N]}]; + _Error -> [] end. get_subnodes(Host, Node, _From) -> @@ -169,18 +190,26 @@ get_subnodes(Host, Node, _From) -> %% @spec (Host, Index) -> [pubsubNode()] | {error, Reason} %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() +-spec(get_subnodes/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). get_subnodes(Host, Node) -> - H = ?PUBSUB:escape(Host), - N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)), - case catch ejabberd_odbc:sql_query_t( - ["select node, parent, type, nodeid " - "from pubsub_node " - "where host='", H, "' and parent='", N, "';"]) + H = (?PUBSUB):escape(Host), + N = (?PUBSUB):escape(Node), + case catch + ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " + "pubsub_node where host='">>, + H, <<"' and parent='">>, N, <<"';">>]) of - {selected, ["node", "parent", "type", "nodeid"], RItems} -> - lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems); - _ -> - [] + {selected, + [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], + RItems} -> + lists:map(fun (Item) -> raw_to_node(Host, Item) end, + RItems); + _ -> [] end. get_subnodes_tree(Host, Node, _From) -> @@ -189,18 +218,27 @@ get_subnodes_tree(Host, Node, _From) -> %% @spec (Host, Index) -> [pubsubNode()] | {error, Reason} %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() +-spec(get_subnodes_tree/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). + get_subnodes_tree(Host, Node) -> - H = ?PUBSUB:escape(Host), - N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)), - case catch ejabberd_odbc:sql_query_t( - ["select node, parent, type, nodeid " - "from pubsub_node " - "where host='", H, "' and node like '", N, "%';"]) + H = (?PUBSUB):escape(Host), + N = (?PUBSUB):escape(Node), + case catch + ejabberd_odbc:sql_query_t([<<"select node, parent, type, nodeid from " + "pubsub_node where host='">>, + H, <<"' and node like '">>, N, <<"%';">>]) of - {selected, ["node", "parent", "type", "nodeid"], RItems} -> - lists:map(fun(Item) -> raw_to_node(Host, Item) end, RItems); - _ -> - [] + {selected, + [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], + RItems} -> + lists:map(fun (Item) -> raw_to_node(Host, Item) end, + RItems); + _ -> [] end. %% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason} @@ -210,162 +248,219 @@ get_subnodes_tree(Host, Node) -> %% Owner = mod_pubsub:jid() %% Options = list() %% Parents = list() + +-spec(create_node/6 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId(), + Type :: binary(), + Owner :: jid(), + Options :: mod_pubsub:nodeOptions(), + Parents :: [mod_pubsub:nodeId()]) + -> {ok, NodeIdx::mod_pubsub:nodeIdx()} + %%% + | {error, _} +). + create_node(Host, Node, Type, Owner, Options, Parents) -> BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), case nodeid(Host, Node) of - {error, ?ERR_ITEM_NOT_FOUND} -> - ParentExists = - case Host of - {_U, _S, _R} -> - %% This is special case for PEP handling - %% PEP does not uses hierarchy - true; - _ -> - case Parents of - [] -> true; - [Parent|_] -> - case nodeid(Host, Parent) of - {result, PNodeId} -> - case nodeowners(PNodeId) of - [{[], Host, []}] -> true; - Owners -> lists:member(BJID, Owners) - end; - _ -> - false - end; - _ -> - false - end - end, - case ParentExists of - true -> - case set_node(#pubsub_node{ - nodeid={Host, Node}, - parents=Parents, - type=Type, - options=Options}) of - {result, NodeId} -> {ok, NodeId}; - Other -> Other - end; - false -> - %% Requesting entity is prohibited from creating nodes - {error, ?ERR_FORBIDDEN} - end; - {result, _} -> - %% NodeID already exists - {error, ?ERR_CONFLICT}; - Error -> - Error + {error, ?ERR_ITEM_NOT_FOUND} -> + ParentExists = case Host of + {_U, _S, _R} -> + %% This is special case for PEP handling + %% PEP does not uses hierarchy + true; + _ -> + case Parents of + [] -> true; + [Parent | _] -> + case nodeid(Host, Parent) of + {result, PNodeId} -> + case nodeowners(PNodeId) of + [{<<>>, Host, <<>>}] -> true; + Owners -> + lists:member(BJID, Owners) + end; + _ -> false + end; + _ -> false + end + end, + case ParentExists of + true -> + case set_node(#pubsub_node{nodeid = {Host, Node}, + parents = Parents, type = Type, + options = Options}) + of + {result, NodeId} -> {ok, NodeId}; + Other -> Other + end; + false -> {error, ?ERR_FORBIDDEN} + end; + {result, _} -> {error, ?ERR_CONFLICT}; + Error -> Error end. %% @spec (Host, Node) -> [mod_pubsub:node()] %% Host = mod_pubsub:host() | mod_pubsub:jid() %% Node = mod_pubsub:pubsubNode() +-spec(delete_node/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> [mod_pubsub:pubsubNode()] +). + delete_node(Host, Node) -> - H = ?PUBSUB:escape(Host), - N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)), + H = (?PUBSUB):escape(Host), + N = (?PUBSUB):escape(Node), Removed = get_subnodes_tree(Host, Node), - catch ejabberd_odbc:sql_query_t( - ["delete from pubsub_node " - "where host='", H, "' and node like '", N, "%';"]), + catch + ejabberd_odbc:sql_query_t([<<"delete from pubsub_node where host='">>, + H, <<"' and node like '">>, N, <<"%';">>]), Removed. %% helpers - -raw_to_node(Host, {Node, Parent, Type, NodeId}) -> - Options = case catch ejabberd_odbc:sql_query_t( - ["select name,val " - "from pubsub_node_option " - "where nodeid='", NodeId, "';"]) +-spec(raw_to_node/2 :: +( + Host :: mod_pubus:host(), + _ :: {NodeId::mod_pubsub:nodeId(), + Parent::mod_pubsub:nodeId(), + Type::binary(), + NodeIdx::mod_pubsub:nodeIdx()}) + -> mod_pubsub:pubsubNode() +). +raw_to_node(Host, {Node, Parent, Type, NodeIdx}) -> + Options = case catch + ejabberd_odbc:sql_query_t([<<"select name,val from pubsub_node_option " + "where nodeid='">>, + NodeIdx, <<"';">>]) of - {selected, ["name", "val"], ROptions} -> - DbOpts = lists:map(fun({Key, Value}) -> - RKey = list_to_atom(Key), - Tokens = element(2, erl_scan:string(Value++".")), - RValue = element(2, erl_parse:parse_term(Tokens)), - {RKey, RValue} - end, ROptions), - Module = list_to_atom(?PLUGIN_PREFIX++Type), - StdOpts = Module:options(), - lists:foldl(fun({Key, Value}, Acc)-> - lists:keyreplace(Key, 1, Acc, {Key, Value}) - end, StdOpts, DbOpts); - _ -> - [] + {selected, [<<"name">>, <<"val">>], ROptions} -> + DbOpts = lists:map(fun ({Key, Value}) -> + RKey = + jlib:binary_to_atom(Key), + Tokens = element(2, + erl_scan:string(<<Value/binary, + ".">>)), + RValue = element(2, + erl_parse:parse_term(Tokens)), + {RKey, RValue} + end, + ROptions), + Module = + jlib:binary_to_atom(<<(?PLUGIN_PREFIX)/binary, + Type/binary>>), + StdOpts = Module:options(), + lists:foldl(fun ({Key, Value}, Acc) -> + lists:keyreplace(Key, 1, Acc, + {Key, Value}) + end, + StdOpts, DbOpts); + _ -> [] end, - #pubsub_node{ - nodeid = {Host, ?PUBSUB:string_to_node(Node)}, - parents = [?PUBSUB:string_to_node(Parent)], - id = NodeId, - type = Type, - options = Options}. - %% @spec (NodeRecord) -> ok | {error, Reason} %% Record = mod_pubsub:pubsub_node() + #pubsub_node{nodeid = + {Host, Node}, + parents = [Parent], + id = NodeIdx, type = Type, options = Options}. + +-spec(set_node/1 :: +( + Record::mod_pubsub:pubsubNode()) + -> {result, NodeIdx::mod_pubsub:nodeIdx()} + %%% + | {error, _} +). set_node(Record) -> {Host, Node} = Record#pubsub_node.nodeid, Parent = case Record#pubsub_node.parents of - [] -> <<>>; - [First|_] -> First - end, + [] -> <<>>; + [First | _] -> First + end, Type = Record#pubsub_node.type, - H = ?PUBSUB:escape(Host), - N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)), - P = ?PUBSUB:escape(?PUBSUB:node_to_string(Parent)), - NodeId = case nodeid(Host, Node) of - {result, OldNodeId} -> - catch ejabberd_odbc:sql_query_t( - ["delete from pubsub_node_option " - "where nodeid='", OldNodeId, "';"]), - catch ejabberd_odbc:sql_query_t( - ["update pubsub_node " - "set host='", H, "' " - "node='", N, "' " - "parent='", P, "' " - "type='", Type, "' " - "where nodeid='", OldNodeId, "';"]), - OldNodeId; - _ -> - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_node(host, node, parent, type) " - "values('", H, "', '", N, "', '", P, "', '", Type, "');"]), - case nodeid(Host, Node) of - {result, NewNodeId} -> NewNodeId; - _ -> none % this should not happen - end + H = (?PUBSUB):escape(Host), + N = (?PUBSUB):escape(Node), + P = (?PUBSUB):escape(Parent), + NodeIdx = case nodeid(Host, Node) of + {result, OldNodeIdx} -> + catch + ejabberd_odbc:sql_query_t([<<"delete from pubsub_node_option where " + "nodeid='">>, + OldNodeIdx, <<"';">>]), + catch + ejabberd_odbc:sql_query_t([<<"update pubsub_node set host='">>, + H, <<"' node='">>, N, + <<"' parent='">>, P, + <<"' type='">>, Type, + <<"' where nodeid='">>, + OldNodeIdx, <<"';">>]), + OldNodeIdx; + _ -> + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_node(host, node, " + "parent, type) values('">>, + H, <<"', '">>, N, <<"', '">>, P, + <<"', '">>, Type, <<"');">>]), + case nodeid(Host, Node) of + {result, NewNodeIdx} -> NewNodeIdx; + _ -> none % this should not happen + end end, - case NodeId of - none -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - lists:foreach(fun({Key, Value}) -> - SKey = atom_to_list(Key), - SValue = ?PUBSUB:escape(lists:flatten(io_lib:fwrite("~p",[Value]))), - catch ejabberd_odbc:sql_query_t( - ["insert into pubsub_node_option(nodeid, name, val) " - "values('", NodeId, "', '", SKey, "', '", SValue, "');"]) - end, Record#pubsub_node.options), - {result, NodeId} + case NodeIdx of + none -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> + lists:foreach(fun ({Key, Value}) -> + SKey = iolist_to_binary(atom_to_list(Key)), + SValue = + (?PUBSUB):escape(lists:flatten(io_lib:fwrite("~p", + [Value]))), + catch + ejabberd_odbc:sql_query_t([<<"insert into pubsub_node_option(nodeid, " + "name, val) values('">>, + NodeIdx, <<"', '">>, + SKey, <<"', '">>, + SValue, <<"');">>]) + end, + Record#pubsub_node.options), + {result, NodeIdx} end. -nodeid(Host, Node) -> - H = ?PUBSUB:escape(Host), - N = ?PUBSUB:escape(?PUBSUB:node_to_string(Node)), - case catch ejabberd_odbc:sql_query_t( - ["select nodeid " - "from pubsub_node " - "where host='", H, "' and node='", N, "';"]) +-spec(nodeid/2 :: +( + Host :: mod_pubsub:host(), + NodeId :: mod_pubsub:nodeId()) + -> {result, NodeIdx::mod_pubsub:nodeIdx()} + %%% + | {error, _} +). + +nodeid(Host, NodeId) -> + H = (?PUBSUB):escape(Host), + N = (?PUBSUB):escape(NodeId), + case catch + ejabberd_odbc:sql_query_t([<<"select nodeid from pubsub_node where " + "host='">>, + H, <<"' and node='">>, N, <<"';">>]) of - {selected, ["nodeid"], [{NodeId}]} -> - {result, NodeId}; - {'EXIT', _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - _ -> - {error, ?ERR_ITEM_NOT_FOUND} + {selected, [<<"nodeid">>], [{NodeIdx}]} -> + {result, NodeIdx}; + {'EXIT', _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + _ -> {error, ?ERR_ITEM_NOT_FOUND} end. -nodeowners(NodeId) -> - {result, Res} = node_hometree_odbc:get_node_affiliations(NodeId), - lists:foldl(fun({LJID, owner}, Acc) -> [LJID|Acc]; - (_, Acc) -> Acc - end, [], Res). +-spec(nodeowners/1 :: +( + NodeIdx::mod_pubsub:nodeIdx()) + -> Node_Owners::[ljid()] +). + +nodeowners(NodeIdx) -> + {result, Res} = node_hometree_odbc:get_node_affiliations(NodeIdx), + lists:foldl(fun ({LJID, owner}, Acc) -> [LJID | Acc]; + (_, Acc) -> Acc + end, + [], Res). diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl index ff216c30d..2aa5d7405 100644 --- a/src/mod_pubsub/nodetree_virtual.erl +++ b/src/mod_pubsub/nodetree_virtual.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.'' @@ -31,29 +33,20 @@ %%% Please, send us comments, feedback and improvements.</p> -module(nodetree_virtual). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). + -include("jlib.hrl"). -behaviour(gen_pubsub_nodetree). --export([init/3, - terminate/2, - options/0, - set_node/1, - get_node/3, - get_node/2, - get_node/1, - get_nodes/2, - get_nodes/1, - get_parentnodes/3, - get_parentnodes_tree/3, - get_subnodes/3, - get_subnodes_tree/3, - create_node/6, - delete_node/2 - ]). +-export([init/3, terminate/2, options/0, set_node/1, + get_node/3, get_node/2, get_node/1, get_nodes/2, + get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3, + get_subnodes/3, get_subnodes_tree/3, create_node/6, + delete_node/2]). %% ================ %% API definition @@ -68,38 +61,36 @@ %% <p>This function is mainly used to trigger the setup task necessary for the %% plugin. It can be used for example by the developer to create the specific %% module database schema if it does not exists yet.</p> -init(_Host, _ServerHost, _Opts) -> - ok. -terminate(_Host, _ServerHost) -> - ok. - %% @spec () -> [Option] %% Option = mod_pubsub:nodetreeOption() %% @doc <p>Returns the default pubsub node tree options.</p> -options() -> - [{virtual_tree, true}]. - %% @spec (NodeRecord) -> ok | {error, Reason} %% NodeRecord = mod_pubsub:pubsub_node() %% @doc <p>No node record is stored on database. Just do nothing.</p> -set_node(_NodeRecord) -> - ok. - %% @spec (Host, Node, From) -> pubsubNode() %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% From = mod_pubsub:jid() %% @doc <p>Virtual node tree does not handle a node database. Any node is considered %% as existing. Node record contains default values.</p> -get_node(Host, Node, _From) -> - get_node(Host, Node). -get_node(Host, Node) -> - get_node({Host, Node}). +init(_Host, _ServerHost, _Opts) -> ok. + +terminate(_Host, _ServerHost) -> ok. + +options() -> [{virtual_tree, true}]. + +set_node(_NodeRecord) -> ok. + +get_node(Host, Node, _From) -> get_node(Host, Node). + +get_node(Host, Node) -> get_node({Host, Node}). + get_node({Host, _} = NodeId) -> Record = #pubsub_node{nodeid = NodeId, id = NodeId}, - Module = list_to_atom("node_" ++ Record#pubsub_node.type), + Module = jlib:binary_to_atom(<<"node_", + (Record#pubsub_node.type)/binary>>), Options = Module:options(), - Owners = [{"", Host, ""}], + Owners = [{<<"">>, Host, <<"">>}], Record#pubsub_node{owners = Owners, options = Options}. %% @spec (Host, From) -> [pubsubNode()] @@ -107,47 +98,41 @@ get_node({Host, _} = NodeId) -> %% From = mod_pubsub:jid() %% @doc <p>Virtual node tree does not handle a node database. Any node is considered %% as existing. Nodes list can not be determined.</p> -get_nodes(Host, _From) -> - get_nodes(Host). -get_nodes(_Host) -> - []. - %% @spec (Host, Node, From) -> [pubsubNode()] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% From = mod_pubsub:jid() %% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> -get_parentnodes(_Host, _Node, _From) -> - []. - %% @spec (Host, Node, From) -> [pubsubNode()] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% From = mod_pubsub:jid() %% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> -get_parentnodes_tree(_Host, _Node, _From) -> - []. - %% @spec (Host, Node, From) -> [pubsubNode()] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% From = mod_pubsub:jid() %% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> +get_nodes(Host, _From) -> get_nodes(Host). + +get_nodes(_Host) -> []. + +get_parentnodes(_Host, _Node, _From) -> []. + +get_parentnodes_tree(_Host, _Node, _From) -> []. + get_subnodes(Host, Node, _From) -> get_subnodes(Host, Node). -get_subnodes(_Host, _Node) -> - []. - %% @spec (Host, Node, From) -> [pubsubNode()] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% From = mod_pubsub:jid() %% @doc <p>Virtual node tree does not handle parent/child. Child list is empty.</p> + +get_subnodes(_Host, _Node) -> []. + get_subnodes_tree(Host, Node, _From) -> get_subnodes_tree(Host, Node). -get_subnodes_tree(_Host, _Node) -> - []. - %% @spec (Host, Node, Type, Owner, Options, Parents) -> ok %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() @@ -157,13 +142,16 @@ get_subnodes_tree(_Host, _Node) -> %% @doc <p>No node record is stored on database. Any valid node %% is considered as already created.</p> %% <p>default allowed nodes: /home/host/user/any/node/name</p> -create_node(Host, Node, _Type, _Owner, _Options, _Parents) -> - {error, {virtual, {Host, Node}}}. - %% @spec (Host, Node) -> [mod_pubsub:node()] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() %% @doc <p>Virtual node tree does not handle parent/child. %% node deletion just affects the corresponding node.</p> -delete_node(Host, Node) -> - [get_node(Host, Node)]. + +get_subnodes_tree(_Host, _Node) -> []. + +create_node(Host, Node, _Type, _Owner, _Options, + _Parents) -> + {error, {virtual, {Host, Node}}}. + +delete_node(Host, Node) -> [get_node(Host, Node)]. diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl index 5a943ee44..4f2cc9f38 100644 --- a/src/mod_pubsub/pubsub.hrl +++ b/src/mod_pubsub/pubsub.hrl @@ -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.'' @@ -23,7 +25,8 @@ %% ------------------------------- %% Pubsub constants --define(ERR_EXTENDED(E,C), mod_pubsub:extended_error(E,C)). +-define(ERR_EXTENDED(E, C), + mod_pubsub:extended_error(E, C)). %% The actual limit can be configured with mod_pubsub's option max_items_node -define(MAXITEMS, 10). @@ -36,9 +39,11 @@ %% Pubsub types %% @type hostPubsub() = string(). +-type(hostPubsub() :: binary()). %% <p><tt>hostPubsub</tt> is the name of the PubSub service. For example, it can be %% <tt>"pubsub.localhost"</tt>.</p> +-type(hostPEP() :: {binary(), binary(), <<>>}). %% @type hostPEP() = {User, Server, Resource} %% User = string() %% Server = string() @@ -46,23 +51,31 @@ %% <p>For example, it can be : %% ```{"bob", "example.org", []}'''.</p> +-type(host() :: hostPubsub() | hostPEP()). %% @type host() = hostPubsub() | hostPEP(). +-type(nodeId() :: binary()). %% @type nodeId() = binary(). %% <p>A node is defined by a list of its ancestors. The last element is the name %% of the current node. For example: +%% of the current node. For example: %% ```<<"/home/localhost/user">>'''</p> +-type(nodeIdx() :: pos_integer()). %% @type nodeIdx() = integer(). +-type(itemId() :: binary()). %% @type itemId() = string(). +-type(subId() :: binary()). %% @type subId() = string(). + %% @type payload() = [#xmlelement{} | #xmlcdata{}]. %% @type stanzaError() = #xmlelement{}. %% Example: +%% Example: %% ```{xmlelement, "error", %% [{"code", Code}, {"type", Type}], %% [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}''' @@ -73,12 +86,26 @@ %% [{xmlelement, "affiliations", [], %% []}]}''' +-type(nodeOption() :: + {Option::atom(), + Value::binary() | [binary()] | boolean() | non_neg_integer() +}). + +-type(nodeOptions() :: [NodeOption::mod_pubsub:nodeOption(),...]). + %% @type nodeOption() = {Option, Value} %% Option = atom() %% Value = term(). %% Example: %% ```{deliver_payloads, true}''' +-type(subOption() :: + {Option::atom(), + Value::binary() | [binary()] | boolean() +}). + +-type(subOptions() :: [SubOption::mod_pubsub:subOption(),...]). + %% @type nodeType() = string(). %% <p>The <tt>nodeType</tt> is a string containing the name of the PubSub %% plugin to use to manage a given node. For example, it can be @@ -92,15 +119,34 @@ %% LServer = string() %% LResource = string(). +%-type(ljid() :: {binary(), binary(), binary()}). %% @type ljid() = {User, Server, Resource} %% User = string() %% Server = string() %% Resource = string(). +-type(affiliation() :: 'none' + | 'owner' + | 'publisher' + %| 'publish-only' + | 'member' + | 'outcast' +). %% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'. +-type(subscription() :: 'none' + | 'pending' + | 'unconfigured' + | 'subscribed' +). %% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'. +-type(accessModel() :: 'open' + | 'presence' + | 'roster' + | 'authorize' + | 'whitelist' +). %% @type accessModel() = 'open' | 'presence' | 'roster' | 'authorize' | 'whitelist'. %% @type pubsubIndex() = {pubsub_index, Index, Last, Free} @@ -108,11 +154,17 @@ %% Last = integer() %% Free = [integer()]. %% internal pubsub index table +-type(publishModel() :: 'publishers' + | 'subscribers' + | 'open' +). + + -record(pubsub_index, { - index, - last, - free + index :: atom(), + last :: mod_pubsub:nodeIdx(), + free :: [mod_pubsub:nodeIdx()] }). %% @type pubsubNode() = {pubsub_node, NodeId, Id, Parents, Type, Owners, Options} @@ -128,12 +180,12 @@ %% <tt>id</tt> can be anything you want. -record(pubsub_node, { - nodeid, - id, - parents = [], - type = "flat", - owners = [], - options = [] + nodeid ,%:: {Host::mod_pubsub:host(), NodeId::mod_pubsub:nodeId()}, + id ,%:: mod_pubsub:nodeIdx(), + parents = [] ,%:: [Parent_NodeId::mod_pubsub:nodeId()], + type = <<"flat">> ,%:: binary(), + owners = [] ,%:: [Owner::ljid(),...], + options = [] %:: mod_pubsub:nodeOptions() }). %% @type pubsubState() = {pubsub_state, StateId, Items, Affiliation, Subscriptions} @@ -143,12 +195,16 @@ %% Subscriptions = [{subscription(), subId()}]. %% <p>This is the format of the <tt>affiliations</tt> table. The type of the %% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p> + +%-record(pubsub_state, +% {stateid, items = [], affiliation = none, +% subscriptions = []}). -record(pubsub_state, { - stateid, - items = [], - affiliation = 'none', - subscriptions = [] + stateid ,%:: {Entity::ljid(), NodeIdx::mod_pubsub:nodeIdx()}, + items = [] ,%:: [ItemId::mod_pubsub:itemId()], + affiliation = 'none' ,%:: mod_pubsub:affiliation(), + subscriptions = [] %:: [{mod_pubsub:subscription(), mod_pubsub:subId()}] }). %% @type pubsubItem() = {pubsub_item, ItemId, Creation, Modification, Payload} @@ -158,12 +214,16 @@ %% Payload = payload(). %% <p>This is the format of the <tt>published items</tt> table. The type of the %% table is: <tt>set</tt>,<tt>disc</tt>,<tt>fragmented</tt>.</p> +%-record(pubsub_item, +% {itemid, creation = {unknown, unknown}, +% modification = {unknown, unknown}, payload = []}). + -record(pubsub_item, { - itemid, - creation = {'unknown','unknown'}, - modification = {'unknown','unknown'}, - payload = [] + itemid ,%:: {mod_pubsub:itemId(), mod_pubsub:nodeIdx()}, + creation = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()}, + modification = {unknown, unknown} ,%:: {erlang:timestamp(), ljid()}, + payload = [] %:: mod_pubsub:payload() }). %% @type pubsubSubscription() = {pubsub_subscription, SubId, Options} @@ -171,10 +231,11 @@ %% Options = [nodeOption()]. %% <p>This is the format of the <tt>subscriptions</tt> table. The type of the %% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p> +%-record(pubsub_subscription, {subid, options}). -record(pubsub_subscription, { - subid, - options + subid ,%:: mod_pubsub:subId(), + options %:: [] | mod_pubsub:subOptions() }). %% @type pubsubLastItem() = {pubsub_last_item, NodeId, ItemId, Creation, Payload} @@ -184,10 +245,13 @@ %% Payload = payload(). %% <p>This is the format of the <tt>last items</tt> table. it stores last item payload %% for every node</p> +%-record(pubsub_last_item, +% {nodeid, itemid, creation, payload}). + -record(pubsub_last_item, { - nodeid, - itemid, - creation, - payload + nodeid ,%:: mod_pubsub:nodeIdx(), + itemid ,%:: mod_pubsub:itemId(), + creation ,%:: {erlang:timestamp(), ljid()}, + payload %:: mod_pubsub:payload() }). diff --git a/src/mod_pubsub/pubsub_db_odbc.erl b/src/mod_pubsub/pubsub_db_odbc.erl index 2cafc97c7..ca1318865 100644 --- a/src/mod_pubsub/pubsub_db_odbc.erl +++ b/src/mod_pubsub/pubsub_db_odbc.erl @@ -20,119 +20,132 @@ %%% @end %%% ==================================================================== -module(pubsub_db_odbc). + -author("pablo.polvorin@process-one.net"). -include("pubsub.hrl"). --export([add_subscription/1, - read_subscription/1, - delete_subscription/1, - update_subscription/1]). - +-export([add_subscription/1, read_subscription/1, + delete_subscription/1, update_subscription/1]). %% TODO: Those -spec lines produce errors in old Erlang versions. %% They can be enabled again in ejabberd 3.0 because it uses R12B or higher. %% -spec read_subscription(SubID :: string()) -> {ok, #pubsub_subscription{}} | notfound. read_subscription(SubID) -> - case ejabberd_odbc:sql_query_t( - ["select opt_name, opt_value " - "from pubsub_subscription_opt " - "where subid = '", ejabberd_odbc:escape(SubID), "'"]) of - {selected, ["opt_name", "opt_value"], []} -> - notfound; - - {selected, ["opt_name", "opt_value"], Options} -> - - {ok, #pubsub_subscription{subid = SubID, - options = lists:map(fun subscription_opt_from_odbc/1, Options)}} - end. - - + case + ejabberd_odbc:sql_query_t([<<"select opt_name, opt_value from pubsub_subscr" + "iption_opt where subid = '">>, + ejabberd_odbc:escape(SubID), <<"'">>]) + of + {selected, [<<"opt_name">>, <<"opt_value">>], []} -> + notfound; + {selected, [<<"opt_name">>, <<"opt_value">>], + Options} -> + {ok, + #pubsub_subscription{subid = SubID, + options = + lists:map(fun subscription_opt_from_odbc/1, + Options)}} + end. %% -spec delete_subscription(SubID :: string()) -> ok. delete_subscription(SubID) -> - ejabberd_odbc:sql_query_t(["delete from pubsub_subscription_opt " - "where subid = '", ejabberd_odbc:escape(SubID), "'"]), - ok. - - %% -spec update_subscription(#pubsub_subscription{}) -> ok . -update_subscription(#pubsub_subscription{subid = SubId} = Sub) -> - delete_subscription(SubId), - add_subscription(Sub). - %% -spec add_subscription(#pubsub_subscription{}) -> ok. -add_subscription(#pubsub_subscription{subid = SubId, options = Opts}) -> - EscapedSubId = ejabberd_odbc:escape(SubId), - lists:foreach(fun(Opt) -> - {OdbcOptName, OdbcOptValue} = subscription_opt_to_odbc(Opt), - ejabberd_odbc:sql_query_t( - ["insert into pubsub_subscription_opt(subid, opt_name, opt_value)" - "values ('", EscapedSubId, "','", OdbcOptName, "','", OdbcOptValue, "')"]) - end, Opts), - ok. - - - %% -------------- Internal utilities ----------------------- -subscription_opt_from_odbc({"DELIVER", Value}) -> - {deliver, odbc_to_boolean(Value)}; -subscription_opt_from_odbc({"DIGEST", Value}) -> - {digest, odbc_to_boolean(Value)}; -subscription_opt_from_odbc({"DIGEST_FREQUENCY", Value}) -> - {digest_frequency, odbc_to_integer(Value)}; -subscription_opt_from_odbc({"EXPIRE", Value}) -> - {expire, odbc_to_timestamp(Value)}; -subscription_opt_from_odbc({"INCLUDE_BODY", Value}) -> - {include_body, odbc_to_boolean(Value)}; - + ejabberd_odbc:sql_query_t([<<"delete from pubsub_subscription_opt " + "where subid = '">>, + ejabberd_odbc:escape(SubID), <<"'">>]), + ok. + +update_subscription(#pubsub_subscription{subid = + SubId} = + Sub) -> + delete_subscription(SubId), add_subscription(Sub). + +add_subscription(#pubsub_subscription{subid = SubId, + options = Opts}) -> + EscapedSubId = ejabberd_odbc:escape(SubId), + lists:foreach(fun (Opt) -> + {OdbcOptName, OdbcOptValue} = + subscription_opt_to_odbc(Opt), + ejabberd_odbc:sql_query_t([<<"insert into pubsub_subscription_opt(subid, " + "opt_name, opt_value)values ('">>, + EscapedSubId, <<"','">>, + OdbcOptName, <<"','">>, + OdbcOptValue, <<"')">>]) + end, + Opts), + ok. + +subscription_opt_from_odbc({<<"DELIVER">>, Value}) -> + {deliver, odbc_to_boolean(Value)}; +subscription_opt_from_odbc({<<"DIGEST">>, Value}) -> + {digest, odbc_to_boolean(Value)}; +subscription_opt_from_odbc({<<"DIGEST_FREQUENCY">>, + Value}) -> + {digest_frequency, odbc_to_integer(Value)}; +subscription_opt_from_odbc({<<"EXPIRE">>, Value}) -> + {expire, odbc_to_timestamp(Value)}; +subscription_opt_from_odbc({<<"INCLUDE_BODY">>, + Value}) -> + {include_body, odbc_to_boolean(Value)}; %%TODO: might be > than 1 show_values value??. %% need to use compact all in only 1 opt. -subscription_opt_from_odbc({"SHOW_VALUES", Value}) -> - {show_values, Value}; -subscription_opt_from_odbc({"SUBSCRIPTION_TYPE", Value}) -> - {subscription_type, case Value of - "items" -> items; - "nodes" -> nodes - end}; - -subscription_opt_from_odbc({"SUBSCRIPTION_DEPTH", Value}) -> - {subscription_depth, case Value of - "all" -> all; - N -> odbc_to_integer(N) - end}. +subscription_opt_from_odbc({<<"SHOW_VALUES">>, + Value}) -> + {show_values, Value}; +subscription_opt_from_odbc({<<"SUBSCRIPTION_TYPE">>, + Value}) -> + {subscription_type, + case Value of + <<"items">> -> items; + <<"nodes">> -> nodes + end}; +subscription_opt_from_odbc({<<"SUBSCRIPTION_DEPTH">>, + Value}) -> + {subscription_depth, + case Value of + <<"all">> -> all; + N -> odbc_to_integer(N) + end}. subscription_opt_to_odbc({deliver, Bool}) -> - {"DELIVER", boolean_to_odbc(Bool)}; + {<<"DELIVER">>, boolean_to_odbc(Bool)}; subscription_opt_to_odbc({digest, Bool}) -> - {"DIGEST", boolean_to_odbc(Bool)}; + {<<"DIGEST">>, boolean_to_odbc(Bool)}; subscription_opt_to_odbc({digest_frequency, Int}) -> - {"DIGEST_FREQUENCY", integer_to_odbc(Int)}; + {<<"DIGEST_FREQUENCY">>, integer_to_odbc(Int)}; subscription_opt_to_odbc({expire, Timestamp}) -> - {"EXPIRE", timestamp_to_odbc(Timestamp)}; + {<<"EXPIRE">>, timestamp_to_odbc(Timestamp)}; subscription_opt_to_odbc({include_body, Bool}) -> - {"INCLUDE_BODY", boolean_to_odbc(Bool)}; + {<<"INCLUDE_BODY">>, boolean_to_odbc(Bool)}; subscription_opt_to_odbc({show_values, Values}) -> - {"SHOW_VALUES", Values}; + {<<"SHOW_VALUES">>, Values}; subscription_opt_to_odbc({subscription_type, Type}) -> - {"SUBSCRIPTION_TYPE", case Type of - items -> "items"; - nodes -> "nodes" - end}; + {<<"SUBSCRIPTION_TYPE">>, + case Type of + items -> <<"items">>; + nodes -> <<"nodes">> + end}; subscription_opt_to_odbc({subscription_depth, Depth}) -> - {"SUBSCRIPTION_DEPTH", case Depth of - all -> "all"; - N -> integer_to_odbc(N) - end}. + {<<"SUBSCRIPTION_DEPTH">>, + case Depth of + all -> <<"all">>; + N -> integer_to_odbc(N) + end}. integer_to_odbc(N) -> - integer_to_list(N). + iolist_to_binary(integer_to_list(N)). + +boolean_to_odbc(true) -> <<"1">>; +boolean_to_odbc(false) -> <<"0">>. -boolean_to_odbc(true) -> "1"; -boolean_to_odbc(false) -> "0". timestamp_to_odbc(T) -> jlib:now_to_utc_string(T). +odbc_to_integer(N) -> jlib:binary_to_integer(N). + +odbc_to_boolean(B) -> B == <<"1">>. -odbc_to_integer(N) -> list_to_integer(N). -odbc_to_boolean(B) -> B == "1". -odbc_to_timestamp(T) -> jlib:datetime_string_to_timestamp(T). +odbc_to_timestamp(T) -> + jlib:datetime_string_to_timestamp(T). diff --git a/src/mod_pubsub/pubsub_index.erl b/src/mod_pubsub/pubsub_index.erl index 5041c28f7..1ff5a1e45 100644 --- a/src/mod_pubsub/pubsub_index.erl +++ b/src/mod_pubsub/pubsub_index.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.'' @@ -27,6 +29,7 @@ %% new/1 and free/2 MUST be called inside a transaction bloc -module(pubsub_index). + -author('christophe.romain@process-one.net'). -include("pubsub.hrl"). @@ -40,26 +43,25 @@ init(_Host, _ServerHost, _Opts) -> new(Index) -> case mnesia:read({pubsub_index, Index}) of - [I] -> - case I#pubsub_index.free of - [] -> - Id = I#pubsub_index.last + 1, - mnesia:write(I#pubsub_index{last = Id}), - Id; - [Id|Free] -> - mnesia:write(I#pubsub_index{free = Free}), - Id - end; - _ -> - mnesia:write(#pubsub_index{index = Index, last = 1, free = []}), - 1 + [I] -> + case I#pubsub_index.free of + [] -> + Id = I#pubsub_index.last + 1, + mnesia:write(I#pubsub_index{last = Id}), + Id; + [Id | Free] -> + mnesia:write(I#pubsub_index{free = Free}), Id + end; + _ -> + mnesia:write(#pubsub_index{index = Index, last = 1, + free = []}), + 1 end. free(Index, Id) -> case mnesia:read({pubsub_index, Index}) of - [I] -> - Free = I#pubsub_index.free, - mnesia:write(I#pubsub_index{free = [Id|Free]}); - _ -> - ok + [I] -> + Free = I#pubsub_index.free, + mnesia:write(I#pubsub_index{free = [Id | Free]}); + _ -> ok end. diff --git a/src/mod_pubsub/pubsub_odbc.patch b/src/mod_pubsub/pubsub_odbc.patch index b7c18bacc..986c6fe00 100644 --- a/src/mod_pubsub/pubsub_odbc.patch +++ b/src/mod_pubsub/pubsub_odbc.patch @@ -1,222 +1,376 @@ ---- mod_pubsub.erl 2012-04-11 16:47:33.620900390 +0200 -+++ mod_pubsub_odbc.erl 2012-04-11 16:47:53.390899087 +0200 -@@ -42,7 +42,7 @@ +--- mod_pubsub.erl 2013-03-03 23:32:53.669953265 +0100 ++++ mod_pubsub_odbc.erl 2013-03-03 23:37:10.128953065 +0100 +@@ -41,7 +41,7 @@ %%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see %%% XEP-0060 section 12.18. --module(mod_pubsub). +-module(mod_pubsub_odbc). + -author('christophe.romain@process-one.net'). - -version('1.13-0'). -@@ -54,9 +54,9 @@ - -include("jlib.hrl"). +@@ -59,11 +59,11 @@ + -include("pubsub.hrl"). ---define(STDTREE, "tree"). ---define(STDNODE, "flat"). ---define(PEPNODE, "pep"). -+-define(STDTREE, "tree_odbc"). -+-define(STDNODE, "flat_odbc"). -+-define(PEPNODE, "pep_odbc"). +--define(STDTREE, <<"tree">>). ++-define(STDTREE, <<"tree_odbc">>). + +--define(STDNODE, <<"flat">>). ++-define(STDNODE, <<"flat_odbc">>). + +--define(PEPNODE, <<"pep">>). ++-define(PEPNODE, <<"pep_odbc">>). %% exports for hooks - -export([presence_probe/3, -@@ -103,7 +103,7 @@ - string_to_affiliation/1, - extended_error/2, - extended_error/3, -- rename_default_nodeplugin/0 -+ escape/1 - ]). + -export([presence_probe/3, caps_update/3, +@@ -98,7 +98,7 @@ + -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]). ++ escape/1]). %% API and gen_server callbacks -@@ -122,7 +122,7 @@ - -export([send_loop/1 - ]). + -export([start_link/2, start/2, stop/1, init/1, +@@ -108,7 +108,7 @@ + %% calls for parallel sending of last items + -export([send_loop/1]). --define(PROCNAME, ejabberd_mod_pubsub). +-define(PROCNAME, ejabberd_mod_pubsub_odbc). + -define(LOOPNAME, ejabberd_mod_pubsub_loop). - -define(PLUGIN_PREFIX, "node_"). - -define(TREE_PREFIX, "nodetree_"). -@@ -217,8 +217,6 @@ - ok + +@@ -333,8 +333,6 @@ + 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, -@@ -283,207 +281,14 @@ + State = #state{host = Host, server_host = ServerHost, +@@ -394,359 +392,14 @@ + ok. init_nodes(Host, ServerHost, _NodeTree, Plugins) -> - %% TODO, this call should be done plugin side -- case lists:member("hometree", Plugins) of -+ case lists:member("hometree_odbc", 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"); -+ create_node(Host, ServerHost, string_to_node("/home"), service_jid(Host), "hometree_odbc"), -+ create_node(Host, ServerHost, string_to_node("/home/"++ServerHost), service_jid(Host), "hometree_odbc"); - false -> - ok +- case lists:member(<<"hometree">>, Plugins) of ++ case lists:member(<<"hometree_odbc">>, Plugins) of + true -> +- create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree">>), ++ create_node(Host, ServerHost, <<"/home">>, service_jid(Host), <<"hometree_odbc">>), + create_node(Host, ServerHost, <<"/home/", ServerHost/binary>>, service_jid(Host), +- <<"hometree">>); ++ <<"hometree_odbc">>); + 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 @@ -259,293 +413,367 @@ - send_loop(State) -> receive - {presence, JID, Pid} -> -@@ -494,17 +299,15 @@ - %% 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]), -+ Subscriptions = case catch node_action(Host, PType, get_entity_subscriptions_for_send_last, [Host, JID]) of -+ {result, S} -> S; -+ _ -> [] -+ end, - 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; -+ #pubsub_node{nodeid = {H, N}, type = Type, id = NodeId} = Node, -+ send_items(H, N, NodeId, Type, LJID, last); - true -> - % resource not concerned about that subscription - ok -@@ -623,7 +426,8 @@ - disco_identity(_Host, <<>>, _From) -> - [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}]; + {presence, JID, Pid} -> +@@ -755,11 +408,13 @@ + LJID = jlib:jid_tolower(JID), + BJID = jlib:jid_remove_resource(LJID), + lists:foreach(fun (PType) -> +- {result, Subscriptions} = node_action(Host, ++ {result, Subscriptions} = case catch node_action(Host, + PType, +- get_entity_subscriptions, +- [Host, +- JID]), ++ get_entity_subscriptions_for_send_last, ++ [Host, JID]) of ++ {result, S} -> S; ++ _ -> [] ++ end, + lists:foreach(fun ({Node, subscribed, _, + SubJID}) -> + if (SubJID == LJID) or +@@ -771,24 +426,14 @@ + type = + Type, + id = +- NodeId, +- options +- = +- Options} = ++ NodeId} = + Node, +- case +- get_option(Options, +- send_last_published_item) +- of +- on_sub_and_presence -> +- send_items(H, ++ send_items(H, + N, + NodeId, + Type, + LJID, + last); +- _ -> ok +- end; + true -> + % resource not concerned about that subscription + ok +@@ -979,7 +624,8 @@ + children = []}]; disco_identity(Host, Node, From) -> -- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) -> -+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, [{xmlelement, "identity", [{"category", "pubsub"}, {"type", "pep"}], []}, -@@ -658,7 +462,8 @@ - [?NS_PUBSUB - | [?NS_PUBSUB++"#"++Feature || Feature <- features("pep")]]; + Action = fun (#pubsub_node{id = Idx, type = Type, +- options = Options, owners = Owners}) -> ++ options = Options}) -> ++ Owners = node_owners_call(Type, Idx), + case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of + {result, _} -> + {result, +@@ -1031,7 +677,8 @@ + || Feature <- features(<<"pep">>)]]; disco_features(Host, Node, From) -> -- Action = fun(#pubsub_node{id = Idx, type = Type, options = Options, owners = Owners}) -> -+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - {result, [?NS_PUBSUB -@@ -683,7 +488,8 @@ - Acc. - + Action = fun (#pubsub_node{id = Idx, type = Type, +- options = Options, owners = Owners}) -> ++ options = Options}) -> ++ Owners = node_owners_call(Type, Idx), + case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of + {result, _} -> + {result, +@@ -1076,9 +723,9 @@ + ). disco_items(Host, <<>>, From) -> -- Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx, owners = Owners}, Acc) -> -+ Action = fun(#pubsub_node{nodeid ={_, NodeID}, options = Options, type = Type, id = Idx}, Acc) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, _} -> - [{xmlelement, "item", -@@ -701,13 +507,14 @@ - _ -> Acc - end - end, + Action = fun (#pubsub_node{nodeid = {_, NodeID}, +- options = Options, type = Type, id = Idx, +- owners = Owners}, ++ options = Options, type = Type, id = Idx}, + Acc) -> ++ Owners = node_owners_call(Type, Idx), + case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of + {result, _} -> + [#xmlel{name = <<"item">>, +@@ -1099,13 +746,14 @@ + _ -> Acc + end + end, - case transaction(Host, Action, sync_dirty) of + case transaction_on_nodes(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}) -> -+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) -> -+ Owners = node_owners_call(Type, Idx), - case get_allowed_items_call(Host, Idx, From, Type, Options, Owners) of - {result, Items} -> - {result, [{xmlelement, "item", -@@ -793,10 +600,10 @@ - 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}) -> -+ ({#pubsub_node{options = Options, id = NodeId}, subscribed, _, JID}) -> - case get_option(Options, access_model) of - presence -> -- case lists:member(BJID, Owners) of -+ case lists:member(BJID, node_owners(Host, PType, NodeId)) of - true -> - node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]); - false -> -@@ -964,7 +771,8 @@ - sub_el = SubEl} = IQ -> - {xmlelement, _, QAttrs, _} = SubEl, - Node = xml:get_attr_s("node", QAttrs), -- Res = case iq_disco_items(Host, Node, From) of -+ Rsm = jlib:rsm_decode(IQ), -+ Res = case iq_disco_items(Host, Node, From, Rsm) of - {result, IQRes} -> - jlib:iq_to_xml( - IQ#iq{type = result, -@@ -1077,7 +885,7 @@ - [] -> - ["leaf"]; %% No sub-nodes: it's a leaf node - _ -> -- case node_call(Type, get_items, [NodeId, From]) of -+ case node_call(Type, get_items, [NodeId, From, none]) of - {result, []} -> ["collection"]; - {result, _} -> ["leaf", "collection"]; - _ -> [] -@@ -1093,8 +901,9 @@ - []; - true -> - [{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} | -- lists:map(fun(T) -> -- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []} -+ lists:map(fun -+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []}; -+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []} - end, features(Type))] - end, - %% TODO: add meta-data info (spec section 5.4) -@@ -1123,8 +932,9 @@ - {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}], []} + Action = fun (#pubsub_node{id = Idx, type = Type, +- options = Options, owners = Owners}) -> ++ options = Options}) -> ++ Owners = node_owners_call(Type, Idx), + case get_allowed_items_call(Host, Idx, From, Type, + Options, Owners) + of +@@ -1209,9 +857,6 @@ + lists:foreach(fun ({#pubsub_node{options + = + Options, +- owners +- = +- Owners, + id = + NodeId}, + subscribed, _, +@@ -1223,7 +868,7 @@ + presence -> + case + lists:member(BJID, +- Owners) ++ node_owners(Host, PType, NodeId)) + of + true -> + node_action(Host, +@@ -1442,7 +1087,8 @@ + IQ -> + #xmlel{attrs = QAttrs} = SubEl, + Node = xml:get_attr_s(<<"node">>, QAttrs), +- Res = case iq_disco_items(Host, Node, From) of ++ Rsm = jlib:rsm_decode(IQ), ++ Res = case iq_disco_items(Host, Node, From, Rsm) of + {result, IQRes} -> + jlib:iq_to_xml(IQ#iq{type = result, + sub_el = +@@ -1569,7 +1215,7 @@ + % [] -> + % [<<"leaf">>]; %% No sub-nodes: it's a leaf node + % _ -> +-% case node_call(Type, get_items, [NodeId, From]) of ++% case node_call(Type, get_items, [NodeId, From, none]) of + % {result, []} -> [<<"collection">>]; + % {result, _} -> [<<"leaf">>, <<"collection">>]; + % _ -> [] +@@ -1591,7 +1237,11 @@ + % [#xmlel{name = <<"feature">>, + % attrs = [{<<"var">>, ?NS_PUBSUB}], + % children = []} +-% | lists:map(fun (T) -> ++% | lists:map(fun ++% (<<"rsm">>)-> ++% #xmlel{name = <<"feature">>, ++% attrs = [{<<"var">>, ?NS_RSM}]}; ++% (T) -> + % #xmlel{name = <<"feature">>, + % attrs = + % [{<<"var">>, +@@ -1616,7 +1266,7 @@ + [] -> [<<"leaf">>]; + _ -> + case node_call(Type, get_items, +- [NodeId, From]) ++ [NodeId, From, none]) + of + {result, []} -> + [<<"collection">>]; +@@ -1638,7 +1288,11 @@ + F = [#xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_PUBSUB}], + children = []} +- | lists:map(fun (T) -> ++ | lists:map(fun ++ (<<"rsm">>)-> ++ #xmlel{name = <<"feature">>, ++ attrs = [{<<"var">>, ?NS_RSM}]}; ++ (T) -> + #xmlel{name = <<"feature">>, + attrs = + [{<<"var">>, +@@ -1682,7 +1336,11 @@ + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_VCARD}], children = []}] + ++ +- lists:map(fun (Feature) -> + lists:map(fun -+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []}; -+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []} - end, features(Host, Node))}; - <<?NS_COMMANDS>> -> - command_disco_info(Host, Node, From); -@@ -1134,7 +944,7 @@ - node_disco_info(Host, Node, From) ++ (<<"rsm">>)-> ++ #xmlel{name = <<"feature">>, ++ attrs = [{<<"var">>, ?NS_RSM}]}; ++ (Feature) -> + #xmlel{name = <<"feature">>, + attrs = + [{<<"var">>, <<(?NS_PUBSUB)/binary, "#", Feature/binary>>}], +@@ -1695,14 +1353,15 @@ + _ -> node_disco_info(Host, Node, From) end. --iq_disco_items(Host, [], From) -> -+iq_disco_items(Host, [], From, _RSM) -> - case tree_action(Host, get_subnodes, [Host, <<>>, From]) of - Nodes when is_list(Nodes) -> - {result, lists:map( -@@ -1151,23 +961,24 @@ - Other -> - Other - end; +--spec(iq_disco_items/3 :: ++-spec(iq_disco_items/4 :: + ( + Host :: mod_pubsub:host(), + NodeId :: <<>> | mod_pubsub:nodeId(), +- From :: jid()) ++ From :: jid(), ++ Rsm :: any()) + -> {result, [xmlel()]} + ). +-iq_disco_items(Host, <<>>, From) -> ++iq_disco_items(Host, <<>>, From, _RSM) -> + {result, + lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, + options = Options}) -> +@@ -1739,7 +1398,7 @@ + % Nodes)}; + % Other -> Other + % end; -iq_disco_items(Host, ?NS_COMMANDS, _From) -> +iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) -> - %% 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}, +@@ -1747,22 +1406,19 @@ + {<<"name">>, <<"Get Pending">>}], + children = []}], {result, CommandItems}; -iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> +iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) -> - CommandItems = [], - {result, CommandItems}; + CommandItems = [], {result, CommandItems}; -iq_disco_items(Host, Item, From) -> +iq_disco_items(Host, Item, From, RSM) -> - 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 -+ Action = fun(#pubsub_node{id = Idx, type = Type, options = Options}) -> -+ Owners = node_owners_call(Type, Idx), -+ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of - {result, R} -> R; -- _ -> [] -+ _ -> {[], none} - end, - Nodes = lists:map( - fun(#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) -> -@@ -1185,7 +996,7 @@ - {result, Name} = node_call(Type, get_item_name, [Host, Node, RN]), - {xmlelement, "item", [{"jid", Host}, {"name", Name}], []} - end, NodeItems), -- {result, Nodes ++ Items} -+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)} - end, - case transaction(Host, Node, Action, sync_dirty) of - {result, {_, Result}} -> {result, Result}; -@@ -1296,7 +1107,8 @@ - (_, Acc) -> - Acc - end, [], xml:remove_cdata(Els)), -- get_items(Host, Node, From, SubId, MaxItems, ItemIDs); -+ RSM = jlib:rsm_decode(SubEl), -+ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM); - {get, "subscriptions"} -> - get_subscriptions(Host, Node, From, Plugins); - {get, "affiliations"} -> -@@ -1319,7 +1131,9 @@ - + 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 ++ options = Options}) -> ++ Owners = node_owners_call(Type, Idx), ++ {NodeItems, RsmOut} = case get_allowed_items_call(Host, Idx, From, Type, Options, Owners, RSM) of + {result, R} -> R; +- _ -> [] ++ _ -> {[], none} + end, + Nodes = lists:map(fun (#pubsub_node{nodeid = + {_, SubNode}, +@@ -1805,7 +1461,7 @@ + children = []} + end, + NodeItems), +- {result, Nodes ++ Items} ++ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)} + end, + case transaction(Host, Node, Action, sync_dirty) of + {result, {_, Result}} -> {result, Result}; +@@ -1956,7 +1612,8 @@ + (_, Acc) -> Acc + end, + [], xml:remove_cdata(Els)), +- get_items(Host, Node, From, SubId, MaxItems, ItemIDs); ++ RSM = jlib:rsm_decode(SubEl), ++ get_items(Host, Node, From, SubId, MaxItems, ItemIDs, RSM); + {get, <<"subscriptions">>} -> + get_subscriptions(Host, Node, From, Plugins); + {get, <<"affiliations">>} -> +@@ -1991,7 +1648,9 @@ + ). iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> - {xmlelement, _, _, SubEls} = SubEl, + #xmlel{children = SubEls} = SubEl, - Action = xml:remove_cdata(SubEls), -+ Action = lists:filter(fun({xmlelement, "set", _, _}) -> false; -+ (_) -> true -+ end, xml:remove_cdata(SubEls)), ++ Action = lists:filter(fun(#xmlel{name = <<"set">>, _ = '_'}) -> false; ++ (_) -> true ++ end, xml:remove_cdata(SubEls)), case Action of - [{xmlelement, Name, Attrs, Els}] -> - Node = string_to_node(xml:get_attr_s("node", Attrs)), -@@ -1449,7 +1263,8 @@ - _ -> [] - end - end, -- case transaction(fun () -> {result, lists:flatmap(Tr, Plugins)} end, + [#xmlel{name = Name, attrs = Attrs, children = Els}] -> + Node = xml:get_attr_s(<<"node">>, Attrs), +@@ -2121,7 +1780,8 @@ + _ -> [] + end + end, +- case transaction(fun () -> + case transaction(Host, -+ fun () -> {result, lists:flatmap(Tr, Plugins)} end, - sync_dirty) of - {result, Res} -> Res; - Err -> Err -@@ -1488,7 +1303,7 @@ ++ fun () -> + {result, lists:flatmap(Tr, Plugins)} + end, + sync_dirty) +@@ -2163,7 +1823,8 @@ %%% authorization handling --send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, Subscriber) -> -+send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id = NodeId}, Subscriber) -> - Lang = "en", %% TODO fix - Stanza = {xmlelement, "message", - [], -@@ -1517,7 +1332,7 @@ - [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]}, - lists:foreach(fun(Owner) -> - ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza) -- end, Owners). -+ end, node_owners(Host, Type, NodeId)). +-send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}}, ++send_authorization_request(#pubsub_node{nodeid = {Host, Node}, ++ type = Type, id = NodeId}, + Subscriber) -> + Lang = <<"en">>, + Stanza = #xmlel{name = <<"message">>, attrs = [], +@@ -2241,7 +1902,7 @@ + ejabberd_router:route(service_jid(Host), + jlib:make_jid(Owner), Stanza) + end, +- Owners). ++ node_owners(Host, Type, NodeId)). find_authorization_response(Packet) -> - {xmlelement, _Name, _Attrs, Els} = Packet, -@@ -1581,8 +1396,8 @@ - "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), -+ Action = fun(#pubsub_node{type = Type, id = NodeId}) -> -+ IsApprover = lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), node_owners_call(Type, NodeId)), - {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]), - if - not IsApprover -> -@@ -1781,7 +1596,7 @@ - Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "create", nodeAttr(Node), - []}]}], + #xmlel{children = Els} = Packet, +@@ -2300,11 +1961,11 @@ + <<"true">> -> true; + _ -> false + end, +- Action = fun (#pubsub_node{type = Type, owners = Owners, ++ Action = fun (#pubsub_node{type = Type, + id = NodeId}) -> + IsApprover = + lists:member(jlib:jid_tolower(jlib:jid_remove_resource(From)), +- Owners), ++ node_owners_call(Type, NodeId)), + {result, Subscriptions} = node_call(Type, + get_subscriptions, + [NodeId, +@@ -2539,7 +2200,7 @@ + children = [#xmlel{name = <<"create">>, + attrs = nodeAttr(Node), + children = []}]}], - case transaction(CreateNode, transaction) of + case transaction(Host, CreateNode, transaction) of {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]), -@@ -1898,7 +1713,7 @@ - %%<li>The node does not exist.</li> +@@ -2663,7 +2324,7 @@ %%</ul> subscribe_node(Host, Node, From, JID, Configuration) -> -- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of -+ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, -@@ -1906,7 +1721,7 @@ - error -> {"", "", ""}; - J -> jlib:jid_tolower(J) + SubOpts = case +- pubsub_subscription:parse_options_xform(Configuration) ++ pubsub_subscription_odbc:parse_options_xform(Configuration) + of + {result, GoodSubOpts} -> GoodSubOpts; + _ -> invalid +@@ -2677,7 +2338,7 @@ + end end, -- Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type, id = NodeId}) -> -+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> - Features = features(Type), - SubscribeFeature = lists:member("subscribe", Features), - OptionsFeature = lists:member("subscription-options", Features), -@@ -1915,6 +1730,7 @@ - AccessModel = get_option(Options, access_model), - SendLast = get_option(Options, send_last_published_item), - AllowedGroups = get_option(Options, roster_groups_allowed, []), -+ Owners = node_owners_call(Type, NodeId), - {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups), - if - not SubscribeFeature -> -@@ -2036,12 +1852,9 @@ - Features = features(Type), - PublishFeature = lists:member("publish", Features), - PublishModel = get_option(Options, publish_model), -+ MaxItems = max_items(Host, Options), - 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), -@@ -2092,7 +1905,7 @@ + Action = fun (#pubsub_node{options = Options, +- owners = Owners, type = Type, id = NodeId}) -> ++ type = Type, id = NodeId}) -> + Features = features(Type), + SubscribeFeature = lists:member(<<"subscribe">>, Features), + OptionsFeature = lists:member(<<"subscription-options">>, Features), +@@ -2686,6 +2347,7 @@ + AccessModel = get_option(Options, access_model), + SendLast = get_option(Options, send_last_published_item), + AllowedGroups = get_option(Options, roster_groups_allowed, []), ++ Owners = node_owners_call(Type, NodeId), + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, Subscriber, + Owners, AccessModel, AllowedGroups), +@@ -2808,12 +2470,9 @@ + Features = features(Type), + PublishFeature = lists:member(<<"publish">>, Features), + PublishModel = get_option(Options, publish_model), ++ MaxItems = max_items(Host, Options), + 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), +@@ -2869,7 +2528,7 @@ false -> ok end, @@ -554,57 +782,74 @@ case Result of default -> {result, Reply}; _ -> {result, Result} -@@ -2258,7 +2071,7 @@ - %% <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. +@@ -3040,19 +2699,20 @@ + Error -> Error + end. + +--spec(get_items/6 :: ++-spec(get_items/7 :: + ( + Host :: mod_pubsub:host(), + Node :: mod_pubsub:nodeId(), + From :: jid(), + SubId :: mod_pubsub:subId(), + SMaxItems :: binary(), +- ItemIDs :: [mod_pubsub:itemId()]) ++ ItemIDs :: [mod_pubsub:itemId()], ++ Rsm :: any()) + -> {result, [xmlel(),...]} + %%% + | {error, xmlel()} + ). -get_items(Host, Node, From, SubId, SMaxItems, ItemIDs) -> +get_items(Host, Node, From, SubId, SMaxItems, ItemIDs, RSM) -> - MaxItems = - if - SMaxItems == "" -> get_max_items_node(Host); -@@ -2272,12 +2085,13 @@ - {error, Error} -> - {error, Error}; - _ -> -- Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId, owners = Owners}) -> -+ Action = fun(#pubsub_node{options = Options, type = Type, id = NodeId}) -> - 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, []), -+ Owners = node_owners_call(Type, NodeId), - {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), - if - not RetreiveFeature -> -@@ -2290,11 +2104,11 @@ - node_call(Type, get_items, - [NodeId, From, - AccessModel, PresenceSubscription, RosterGroup, -- SubId]) -+ SubId, RSM]) - end - end, - case transaction(Host, Node, Action, sync_dirty) of -- {result, {_, Items}} -> -+ {result, {_, {Items, RSMOut}}} -> - SendItems = case ItemIDs of - [] -> - Items; -@@ -2307,7 +2121,8 @@ - %% number of items sent to MaxItems: - {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], - [{xmlelement, "items", nodeAttr(Node), -- itemsEls(lists:sublist(SendItems, MaxItems))}]}]}; -+ itemsEls(lists:sublist(SendItems, MaxItems))} -+ | jlib:rsm_encode(RSMOut)]}]}; - Error -> - Error - end -@@ -2329,10 +2144,15 @@ - Error -> Error + MaxItems = if SMaxItems == <<"">> -> + get_max_items_node(Host); + true -> +@@ -3064,13 +2724,13 @@ + case MaxItems of + {error, Error} -> {error, Error}; + _ -> +- Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId, +- owners = Owners}) -> ++ Action = fun (#pubsub_node{options = Options, type = Type, id = NodeId}) -> + 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, []), ++ Owners = node_owners_call(Type, NodeId), + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, From, Owners, + AccessModel, AllowedGroups), +@@ -3088,11 +2748,11 @@ + node_call(Type, get_items, + [NodeId, From, AccessModel, + PresenceSubscription, RosterGroup, +- SubId]) ++ SubId, RSM]) + end + end, + case transaction(Host, Node, Action, sync_dirty) of +- {result, {_, Items}} -> ++ {result, {_, {Items, RSMOut}}} -> + SendItems = case ItemIDs of + [] -> Items; + _ -> +@@ -3110,8 +2770,8 @@ + children = + [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), + children = +- itemsEls(lists:sublist(SendItems, +- MaxItems))}]}]}; ++ itemsEls(lists:sublist(SendItems, MaxItems))} ++ | jlib:rsm_encode(RSMOut)]}]}; + Error -> Error + end end. +@@ -3135,43 +2795,45 @@ + end. + get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> + case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, none) of + {result, {I, _}} -> {result, I}; @@ -613,165 +858,187 @@ +get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners, RSM) -> 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]). -+ node_call(Type, get_items, [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]). + {PresenceSubscription, RosterGroup} = + get_presence_and_roster_permissions(Host, From, Owners, AccessModel, + AllowedGroups), + node_call(Type, get_items, +- [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined]). ++ [NodeIdx, From, AccessModel, PresenceSubscription, RosterGroup, undefined, RSM]). - - %% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() -@@ -2344,31 +2164,29 @@ - %% 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); +send_items(Host, Node, NodeId, Type, LJID, last) -> + Stanza = case get_cached_item(Host, NodeId) of - undefined -> -- send_items(Host, Node, NodeId, Type, LJID, 1); ++ undefined -> + % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc + case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of + {result, [LastItem]} -> + {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, + event_stanza_with_delay( -+ [{xmlelement, "items", nodeAttr(Node), -+ itemsEls([LastItem])}], ModifNow, ModifUSR); ++ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), ++ children = itemsEls([LastItem])}], ModifNow, ModifUSR); + _ -> + event_stanza( -+ [{xmlelement, "items", nodeAttr(Node), -+ itemsEls([])}]) ++ [#xmlel{name = <<"items">>, attrs = nodeAttr(Node), ++ children = itemsEls([])}]) + end; - LastItem -> - {ModifNow, ModifUSR} = LastItem#pubsub_item.modification, -- Stanza = event_stanza_with_delay( -+ 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 + LastItem -> + {ModifNow, ModifUSR} = + LastItem#pubsub_item.modification, +- Stanza = event_stanza_with_delay([#xmlel{name = ++ 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) -> -+ itemsEls([LastItem])}], ModifNow, ModifUSR) ++ ModifNow, ModifUSR) + end, + ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza); -+send_items(Host, Node, NodeId, Type, LJID, Number) -> - ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of - {result, []} -> - []; -@@ -2391,20 +2209,7 @@ - [{xmlelement, "items", nodeAttr(Node), - itemsEls(ToSend)}]) - end, + send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, + Number) -> + ToSend = case node_action(Host, Type, get_items, +@@ -3199,20 +2861,7 @@ + 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 +- 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. + ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza). - %% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} - %% Host = host() -@@ -2540,7 +2345,8 @@ - error -> - {error, ?ERR_BAD_REQUEST}; - _ -> -- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) -> -+ Action = fun(#pubsub_node{type = Type, id = NodeId}) -> -+ Owners = node_owners_call(Type, NodeId), - case lists:member(Owner, Owners) of - true -> - OwnerJID = jlib:make_jid(Owner), -@@ -2550,24 +2356,7 @@ - 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 -+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation]) - end, FilteredEntities), - {result, []}; - _ -> -@@ -2620,11 +2409,11 @@ + -spec(get_affiliations/4 :: + ( +@@ -3400,9 +3049,10 @@ + case Entities of + error -> {error, ?ERR_BAD_REQUEST}; + _ -> +- Action = fun (#pubsub_node{owners = Owners, type = Type, ++ Action = fun (#pubsub_node{type = Type, + id = NodeId} = + N) -> ++ Owners = node_owners_call(Type, NodeId), + case lists:member(Owner, Owners) of + true -> + OwnerJID = jlib:make_jid(Owner), +@@ -3415,42 +3065,7 @@ + _ -> 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 ++ node_call(Type, set_affiliation, [NodeId, JID, Affiliation]) + end, + FilteredEntities), + {result, []}; +@@ -3509,11 +3124,11 @@ end. read_sub(Subscriber, Node, NodeID, SubID, Lang) -> - case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of + case pubsub_subscription_odbc: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), + {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options), - OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, - {"subid", SubID}|nodeAttr(Node)], - [XdataEl]}, -@@ -2650,7 +2439,7 @@ + OptionsEl = #xmlel{name = <<"options">>, + attrs = + [{<<"jid">>, jlib:jid_to_string(Subscriber)}, +@@ -3547,7 +3162,7 @@ end. set_options_helper(Configuration, JID, NodeID, SubID, Type) -> - SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of + SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of - {result, GoodSubOpts} -> GoodSubOpts; - _ -> invalid - end, -@@ -2679,7 +2468,7 @@ + {result, GoodSubOpts} -> GoodSubOpts; + _ -> invalid + end, +@@ -3579,7 +3194,7 @@ 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 + case pubsub_subscription_odbc: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, _} -> -@@ -2847,8 +2636,8 @@ - {"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 -+ Action = fun(#pubsub_node{type = Type, id = NodeId}) -> -+ case lists:member(Owner, node_owners_call(Type, NodeId)) of - true -> - Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) -> - -@@ -3203,7 +2992,7 @@ +@@ -3800,9 +3415,9 @@ + ejabberd_router:route(service_jid(Host), + jlib:make_jid(JID), Stanza) + end, +- Action = fun (#pubsub_node{owners = Owners, type = Type, ++ Action = fun (#pubsub_node{type = Type, + id = NodeId}) -> +- case lists:member(Owner, Owners) of ++ case lists:member(Owner, node_owners_call(Type, NodeId)) of + true -> + Result = lists:foldl(fun ({JID, Subscription, + SubId}, +@@ -4181,7 +3796,7 @@ {Depth, [{N, get_node_subs(N)} || N <- Nodes]} end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} end, @@ -780,7 +1047,7 @@ {result, CollSubs} -> CollSubs; _ -> [] end. -@@ -3217,9 +3006,9 @@ +@@ -4195,9 +3810,9 @@ get_options_for_subs(NodeID, Subs) -> lists:foldl(fun({JID, subscribed, SubID}, Acc) -> @@ -792,8 +1059,8 @@ _ -> Acc end; (_, Acc) -> -@@ -3408,6 +3197,30 @@ - Result +@@ -4870,6 +4485,30 @@ + _ -> features() end. +%% @spec (Host, Type, NodeId) -> [ljid()] @@ -820,13 +1087,13 @@ + [] + end. + - %% @spec (Host, Options) -> MaxItems - %% Host = host() - %% Options = [Option] -@@ -3804,7 +3617,13 @@ + tree_call({_User, Server, _Resource}, Function, Args) -> + tree_call(Server, Function, Args); + tree_call(Host, Function, Args) -> +@@ -4888,7 +4527,13 @@ 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). + case catch ejabberd_odbc:sql_bloc(odbc_conn(Host), Fun) of + {atomic, Result} -> @@ -836,34 +1103,36 @@ + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. - %% @doc <p>node plugin call.</p> node_call(Type, Function, Args) -> -@@ -3824,13 +3643,13 @@ - + ?DEBUG("node_call ~p ~p ~p", [Type, Function, Args]), +@@ -4912,12 +4557,11 @@ node_action(Host, Type, Function, Args) -> - ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]), -- transaction(fun() -> -+ transaction(Host, 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, ++ transaction(Host, fun () -> node_call(Type, Function, Args) end, + sync_dirty). - %% @doc <p>plugin transaction handling.</p> transaction(Host, Node, Action, Trans) -> -- transaction(fun() -> -+ transaction(Host, fun() -> +- transaction(fun () -> ++ transaction(Host, fun () -> case tree_call(Host, get_node, [Host, Node]) of - N when is_record(N, pubsub_node) -> - case Action(N) of -@@ -3842,13 +3661,19 @@ - Error - end - end, Trans). + N when is_record(N, pubsub_node) -> + case Action(N) of +@@ -4931,16 +4575,22 @@ + end, + Trans). + -transaction(Host, Action, Trans) -> -- transaction(fun() -> +- transaction(fun () -> +transaction_on_nodes(Host, Action, Trans) -> -+ transaction(Host, fun() -> - {result, lists:foldl(Action, [], tree_call(Host, get_nodes, [Host]))} - end, Trans). ++ transaction(Host, fun () -> + {result, + lists:foldl(Action, [], + tree_call(Host, get_nodes, [Host]))} + end, + Trans). -transaction(Fun, Trans) -> - case catch mnesia:Trans(Fun) of @@ -875,14 +1144,14 @@ + _ -> sql_bloc + end, + case catch ejabberd_odbc:SqlFun(odbc_conn(Host), Fun) of - {result, Result} -> {result, Result}; - {error, Error} -> {error, Error}; - {atomic, {result, Result}} -> {result, Result}; -@@ -3856,6 +3681,15 @@ - {aborted, Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; -+ {'EXIT', {timeout, _} = Reason} -> + {result, Result} -> {result, Result}; + {error, Error} -> {error, Error}; + {atomic, {result, Result}} -> {result, Result}; +@@ -4949,6 +4599,15 @@ + ?ERROR_MSG("transaction return internal error: ~p~n", + [{aborted, Reason}]), + {error, ?ERR_INTERNAL_SERVER_ERROR}; ++ {'EXIT', {timeout, _} = Reason} -> + case Count of + 0 -> + ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), @@ -891,11 +1160,11 @@ + erlang:yield(), + transaction_retry(Host, Fun, Trans, N-1) + end; - {'EXIT', Reason} -> - ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), - {error, ?ERR_INTERNAL_SERVER_ERROR}; -@@ -3864,6 +3698,17 @@ - {error, ?ERR_INTERNAL_SERVER_ERROR} + {'EXIT', Reason} -> + ?ERROR_MSG("transaction return internal error: ~p~n", + [{'EXIT', Reason}]), +@@ -4959,6 +4618,17 @@ + {error, ?ERR_INTERNAL_SERVER_ERROR} end. +odbc_conn({_U, Host, _R})-> @@ -911,4 +1180,4 @@ + %%%% helpers - %% Add pubsub-specific error element + extended_error(Error, Ext) -> diff --git a/src/mod_pubsub/pubsub_subscription.erl b/src/mod_pubsub/pubsub_subscription.erl index 600f1e119..bb09cdd60 100644 --- a/src/mod_pubsub/pubsub_subscription.erl +++ b/src/mod_pubsub/pubsub_subscription.erl @@ -21,130 +21,174 @@ %%% ==================================================================== -module(pubsub_subscription). + -author("bjc@kublai.com"). %% API --export([init/0, - subscribe_node/3, - unsubscribe_node/3, - get_subscription/3, - set_subscription/4, - get_options_xform/2, - parse_options_xform/1]). +-export([init/0, subscribe_node/3, unsubscribe_node/3, + get_subscription/3, set_subscription/4, + get_options_xform/2, parse_options_xform/1]). % Internal function also exported for use in transactional bloc from pubsub plugins --export([add_subscription/3, - delete_subscription/3, - read_subscription/3, - write_subscription/4]). +-export([add_subscription/3, delete_subscription/3, + read_subscription/3, write_subscription/4]). -include("pubsub.hrl"). + -include("jlib.hrl"). --define(PUBSUB_DELIVER, "pubsub#deliver"). --define(PUBSUB_DIGEST, "pubsub#digest"). --define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency"). --define(PUBSUB_EXPIRE, "pubsub#expire"). --define(PUBSUB_INCLUDE_BODY, "pubsub#include_body"). --define(PUBSUB_SHOW_VALUES, "pubsub#show-values"). --define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type"). --define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth"). +-define(PUBSUB_DELIVER, <<"pubsub#deliver">>). + +-define(PUBSUB_DIGEST, <<"pubsub#digest">>). + +-define(PUBSUB_DIGEST_FREQUENCY, + <<"pubsub#digest_frequency">>). + +-define(PUBSUB_EXPIRE, <<"pubsub#expire">>). + +-define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>). + +-define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>). + +-define(PUBSUB_SUBSCRIPTION_TYPE, + <<"pubsub#subscription_type">>). + +-define(PUBSUB_SUBSCRIPTION_DEPTH, + <<"pubsub#subscription_depth">>). -define(DELIVER_LABEL, - "Whether an entity wants to receive or disable notifications"). + <<"Whether an entity wants to receive or " + "disable notifications">>). + -define(DIGEST_LABEL, - "Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually"). + <<"Whether an entity wants to receive digests " + "(aggregations) of notifications or all " + "notifications individually">>). + -define(DIGEST_FREQUENCY_LABEL, - "The minimum number of milliseconds between sending any two notification digests"). + <<"The minimum number of milliseconds between " + "sending any two notification digests">>). + -define(EXPIRE_LABEL, - "The DateTime at which a leased subscription will end or has ended"). + <<"The DateTime at which a leased subscription " + "will end or has ended">>). + -define(INCLUDE_BODY_LABEL, - "Whether an entity wants to receive an XMPP message body in addition to the payload format"). + <<"Whether an entity wants to receive an " + "XMPP message body in addition to the " + "payload format">>). + -define(SHOW_VALUES_LABEL, - "The presence states for which an entity wants to receive notifications"). + <<"The presence states for which an entity " + "wants to receive notifications">>). + -define(SUBSCRIPTION_TYPE_LABEL, - "Type of notification to receive"). + <<"Type of notification to receive">>). + -define(SUBSCRIPTION_DEPTH_LABEL, - "Depth from subscription for which to receive notifications"). + <<"Depth from subscription for which to " + "receive notifications">>). + +-define(SHOW_VALUE_AWAY_LABEL, + <<"XMPP Show Value of Away">>). --define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away"). --define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat"). --define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)"). --define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)"). --define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)"). +-define(SHOW_VALUE_CHAT_LABEL, + <<"XMPP Show Value of Chat">>). + +-define(SHOW_VALUE_DND_LABEL, + <<"XMPP Show Value of DND (Do Not Disturb)">>). + +-define(SHOW_VALUE_ONLINE_LABEL, + <<"Mere Availability in XMPP (No Show Value)">>). + +-define(SHOW_VALUE_XA_LABEL, + <<"XMPP Show Value of XA (Extended Away)">>). -define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, - "Receive notification of new items only"). + <<"Receive notification of new items only">>). + -define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, - "Receive notification of new nodes only"). + <<"Receive notification of new nodes only">>). -define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, - "Receive notification from direct child nodes only"). + <<"Receive notification from direct child " + "nodes only">>). + -define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, - "Receive notification from all descendent nodes"). + <<"Receive notification from all descendent " + "nodes">>). %%==================================================================== %% API %%==================================================================== -init() -> - ok = create_table(). +init() -> ok = create_table(). subscribe_node(JID, NodeID, Options) -> case catch mnesia:sync_dirty(fun add_subscription/3, - [JID, NodeID, Options]) of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} + [JID, NodeID, Options]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} end. unsubscribe_node(JID, NodeID, SubID) -> case catch mnesia:sync_dirty(fun delete_subscription/3, - [JID, NodeID, SubID]) of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} + [JID, NodeID, SubID]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} end. get_subscription(JID, NodeID, SubID) -> case catch mnesia:sync_dirty(fun read_subscription/3, - [JID, NodeID, SubID]) of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} + [JID, NodeID, SubID]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} end. set_subscription(JID, NodeID, SubID, Options) -> case catch mnesia:sync_dirty(fun write_subscription/4, - [JID, NodeID, SubID, Options]) of - {'EXIT', {aborted, Error}} -> Error; - {error, Error} -> {error, Error}; - Result -> {result, Result} + [JID, NodeID, SubID, Options]) + of + {'EXIT', {aborted, Error}} -> Error; + {error, Error} -> {error, Error}; + Result -> {result, Result} end. -get_options_xform(Lang, Options) -> - Keys = [deliver, show_values, subscription_type, subscription_depth], - XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys], - {result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}], - [{xmlelement, "value", [], - [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}. +get_options_xform(Lang, Options) -> + Keys = [deliver, show_values, subscription_type, + subscription_depth], + XFields = [get_option_xfield(Lang, Key, Options) + || Key <- Keys], + {result, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] + ++ XFields}}. parse_options_xform(XFields) -> case xml:remove_cdata(XFields) of - [] -> {result, []}; - [{xmlelement, "x", _Attrs, _Els} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - XData when is_list(XData) -> - case set_xoption(XData, []) of - Opts when is_list(Opts) -> {result, Opts}; - Other -> Other - end; - Other -> - Other - end; - Other -> - Other + [#xmlel{name = <<"x">>} = XEl] -> + case jlib:parse_xdata_submit(XEl) of + XData when is_list(XData) -> + Opts = set_xoption(XData, []), + {result, Opts}; + Other -> Other + end; + _ -> {result, []} end. %%==================================================================== @@ -153,36 +197,73 @@ parse_options_xform(XFields) -> create_table() -> case mnesia:create_table(pubsub_subscription, [{disc_copies, [node()]}, - {attributes, record_info(fields, pubsub_subscription)}, - {type, set}]) of - {atomic, ok} -> ok; - {aborted, {already_exists, _}} -> ok; - Other -> Other + {attributes, + record_info(fields, pubsub_subscription)}, + {type, set}]) + of + {atomic, ok} -> ok; + {aborted, {already_exists, _}} -> ok; + Other -> Other end. +-spec(add_subscription/3 :: +( + _JID :: ljid(), + _NodeID :: mod_pubsub:nodeIdx(), + Options :: [] | mod_pubsub:subOptions()) + -> SubId :: mod_pubsub:subId() +). + +add_subscription(_JID, _NodeID, []) -> make_subid(); add_subscription(_JID, _NodeID, Options) -> SubID = make_subid(), - mnesia:write(#pubsub_subscription{subid = SubID, options = Options}), + mnesia:write(#pubsub_subscription{subid = SubID, + options = Options}), SubID. +-spec(delete_subscription/3 :: +( + _JID :: _, + _NodeID :: _, + SubId :: mod_pubsub:subId()) + -> ok +). + delete_subscription(_JID, _NodeID, SubID) -> mnesia:delete({pubsub_subscription, SubID}). +-spec(read_subscription/3 :: +( + _JID :: ljid(), + _NodeID :: _, + SubID :: mod_pubsub:subId()) + -> mod_pubsub:pubsubSubscription() + | {error, notfound} +). + read_subscription(_JID, _NodeID, SubID) -> case mnesia:read({pubsub_subscription, SubID}) of - [Sub] -> Sub; - _ -> {error, notfound} + [Sub] -> Sub; + _ -> {error, notfound} end. -write_subscription(JID, NodeID, SubID, Options) -> - case read_subscription(JID, NodeID, SubID) of - {error, notfound} -> {error, notfound}; - Sub -> mnesia:write(Sub#pubsub_subscription{options = Options}) - end. +-spec(write_subscription/4 :: +( + _JID :: ljid(), + _NodeID :: _, + SubID :: mod_pubsub:subId(), + Options :: mod_pubsub:subOptions()) + -> ok +). +write_subscription(_JID, _NodeID, SubID, Options) -> + mnesia:write(#pubsub_subscription{subid = SubID, + options = Options}). + +-spec(make_subid/0 :: () -> SubId::mod_pubsub:subId()). make_subid() -> {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])). %% %% Subscription XForm processing. @@ -190,133 +271,193 @@ make_subid() -> %% Return processed options, with types converted and so forth, using %% Opts as defaults. -set_xoption([], Opts) -> - Opts; +set_xoption([], Opts) -> Opts; set_xoption([{Var, Value} | T], Opts) -> NewOpts = case var_xfield(Var) of - {error, _} -> - Opts; - Key -> - Val = val_xfield(Key, Value), - lists:keystore(Key, 1, Opts, {Key, Val}) + {error, _} -> Opts; + Key -> + Val = val_xfield(Key, Value), + lists:keystore(Key, 1, Opts, {Key, Val}) end, set_xoption(T, NewOpts). %% Return the options list's key for an XForm var. -var_xfield(?PUBSUB_DELIVER) -> deliver; -var_xfield(?PUBSUB_DIGEST) -> digest; -var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency; -var_xfield(?PUBSUB_EXPIRE) -> expire; -var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; -var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; -var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type; -var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth; -var_xfield(_) -> {error, badarg}. - %% Convert Values for option list's Key. -val_xfield(deliver, [Val]) -> xopt_to_bool(Val); -val_xfield(digest, [Val]) -> xopt_to_bool(Val); -val_xfield(digest_frequency, [Val]) -> list_to_integer(Val); -val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val); -val_xfield(include_body, [Val]) -> xopt_to_bool(Val); -val_xfield(show_values, Vals) -> Vals; -val_xfield(subscription_type, ["items"]) -> items; -val_xfield(subscription_type, ["nodes"]) -> nodes; -val_xfield(subscription_depth, ["all"]) -> all; -val_xfield(subscription_depth, [Depth]) -> - case catch list_to_integer(Depth) of - N when is_integer(N) -> N; - _ -> {error, ?ERR_NOT_ACCEPTABLE} +var_xfield(?PUBSUB_DELIVER) -> deliver; +var_xfield(?PUBSUB_DIGEST) -> digest; +var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> + digest_frequency; +var_xfield(?PUBSUB_EXPIRE) -> expire; +var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; +var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; +var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> + subscription_type; +var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> + subscription_depth; +var_xfield(_) -> {error, badarg}. + +val_xfield(deliver, [Val]) -> xopt_to_bool(Val); +%val_xfield(digest, [Val]) -> xopt_to_bool(Val); +%val_xfield(digest_frequency, [Val]) -> +% jlib:binary_to_integer(Val); +%val_xfield(expire, [Val]) -> +% jlib:datetime_string_to_timestamp(Val); +%val_xfield(include_body, [Val]) -> xopt_to_bool(Val); +val_xfield(show_values, Vals) -> Vals; +val_xfield(subscription_type, [<<"items">>]) -> items; +val_xfield(subscription_type, [<<"nodes">>]) -> nodes; +val_xfield(subscription_depth, [<<"all">>]) -> all; +val_xfield(subscription_depth, [Depth]) -> + case catch jlib:binary_to_integer(Depth) of + N when is_integer(N) -> N; + _ -> {error, ?ERR_NOT_ACCEPTABLE} end. %% Convert XForm booleans to Erlang booleans. -xopt_to_bool("0") -> false; -xopt_to_bool("1") -> true; -xopt_to_bool("false") -> false; -xopt_to_bool("true") -> true; -xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. +xopt_to_bool(<<"0">>) -> false; +xopt_to_bool(<<"1">>) -> true; +xopt_to_bool(<<"false">>) -> false; +xopt_to_bool(<<"true">>) -> true; +xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. + +-spec(get_option_xfield/3 :: +( + Lang :: binary(), + Key :: atom(), + Options :: mod_pubsub:subOptions()) + -> xmlel() +). %% Return a field for an XForm for Key, with data filled in, if %% applicable, from Options. get_option_xfield(Lang, Key, Options) -> Var = xfield_var(Key), Label = xfield_label(Key), - {Type, OptEls} = type_and_options(xfield_type(Key), Lang), + {Type, OptEls} = type_and_options(xfield_type(Key), + Lang), Vals = case lists:keysearch(Key, 1, Options) of - {value, {_, Val}} -> - [tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)]; - false -> - [] + {value, {_, Val}} -> + [tr_xfield_values(Vals) + || Vals <- xfield_val(Key, Val)]; + false -> [] end, - {xmlelement, "field", - [{"var", Var}, {"type", Type}, - {"label", translate:translate(Lang, Label)}], - OptEls ++ Vals}. + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, Var}, {<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}], + children = OptEls ++ Vals}. type_and_options({Type, Options}, Lang) -> {Type, [tr_xfield_options(O, Lang) || O <- Options]}; -type_and_options(Type, _Lang) -> - {Type, []}. +type_and_options(Type, _Lang) -> {Type, []}. tr_xfield_options({Value, Label}, Lang) -> - {xmlelement, "option", - [{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [], - [{xmlcdata, Value}]}]}. + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, translate:translate(Lang, Label)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}]}. tr_xfield_values(Value) -> - {xmlelement, "value", [], [{xmlcdata, Value}]}. - %% Return the XForm variable name for a subscription option key. -xfield_var(deliver) -> ?PUBSUB_DELIVER; -xfield_var(digest) -> ?PUBSUB_DIGEST; -xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY; -xfield_var(expire) -> ?PUBSUB_EXPIRE; -xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; -xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; -xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE; -xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH. - %% Return the XForm variable type for a subscription option key. -xfield_type(deliver) -> "boolean"; -xfield_type(digest) -> "boolean"; -xfield_type(digest_frequency) -> "text-single"; -xfield_type(expire) -> "text-single"; -xfield_type(include_body) -> "boolean"; -xfield_type(show_values) -> - {"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL}, - {"chat", ?SHOW_VALUE_CHAT_LABEL}, - {"dnd", ?SHOW_VALUE_DND_LABEL}, - {"online", ?SHOW_VALUE_ONLINE_LABEL}, - {"xa", ?SHOW_VALUE_XA_LABEL}]}; -xfield_type(subscription_type) -> - {"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, - {"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}. + +-spec(xfield_var/1 :: +( + Var :: 'deliver' +% | 'digest' +% | 'digest_frequency' +% | 'expire' +% | 'include_body' + | 'show_values' + | 'subscription_type' + | 'subscription_depth') + -> binary() +). + +xfield_var(deliver) -> ?PUBSUB_DELIVER; +%xfield_var(digest) -> ?PUBSUB_DIGEST; +%xfield_var(digest_frequency) -> +% ?PUBSUB_DIGEST_FREQUENCY; +%xfield_var(expire) -> ?PUBSUB_EXPIRE; +%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; +xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; +xfield_var(subscription_type) -> + ?PUBSUB_SUBSCRIPTION_TYPE; +xfield_var(subscription_depth) -> + ?PUBSUB_SUBSCRIPTION_DEPTH. + +xfield_type(deliver) -> <<"boolean">>; +%xfield_type(digest) -> <<"boolean">>; +%xfield_type(digest_frequency) -> <<"text-single">>; +%xfield_type(expire) -> <<"text-single">>; +%xfield_type(include_body) -> <<"boolean">>; +xfield_type(show_values) -> + {<<"list-multi">>, + [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL}, + {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL}, + {<<"dnd">>, ?SHOW_VALUE_DND_LABEL}, + {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL}, + {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]}; +xfield_type(subscription_type) -> + {<<"list-single">>, + [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, + {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; xfield_type(subscription_depth) -> - {"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, - {"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. + {<<"list-single">>, + [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, + {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. %% Return the XForm variable label for a subscription option key. xfield_label(deliver) -> ?DELIVER_LABEL; -xfield_label(digest) -> ?DIGEST_LABEL; -xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL; -xfield_label(expire) -> ?EXPIRE_LABEL; -xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; +%xfield_label(digest) -> ?DIGEST_LABEL; +%xfield_label(digest_frequency) -> +% ?DIGEST_FREQUENCY_LABEL; +%xfield_label(expire) -> ?EXPIRE_LABEL; +%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; xfield_label(show_values) -> ?SHOW_VALUES_LABEL; -xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL; -xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL. - %% Return the XForm value for a subscription option key. -xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; -xfield_val(digest, Val) -> [bool_to_xopt(Val)]; -xfield_val(digest_frequency, Val) -> [integer_to_list(Val)]; -xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)]; -xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; -xfield_val(show_values, Val) -> Val; -xfield_val(subscription_type, items) -> ["items"]; -xfield_val(subscription_type, nodes) -> ["nodes"]; -xfield_val(subscription_depth, all) -> ["all"]; -xfield_val(subscription_depth, N) -> [integer_to_list(N)]. - %% Convert erlang booleans to XForms. -bool_to_xopt(false) -> "false"; -bool_to_xopt(true) -> "true". +xfield_label(subscription_type) -> + ?SUBSCRIPTION_TYPE_LABEL; +xfield_label(subscription_depth) -> + ?SUBSCRIPTION_DEPTH_LABEL. + +-spec(xfield_val/2 :: +( + Field :: 'deliver' +% | 'digest' +% | 'digest_frequency' +% | 'expire' +% | 'include_body' + | 'show_values' + | 'subscription_type' + | 'subscription_depth', + Val :: boolean() + | binary() + | integer() + | [binary()]) +% | erlang:timestamp()) + -> [binary()] +). + +xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest_frequency, Val) -> +% [iolist_to_binary(integer_to_list(Val))]; +%xfield_val(expire, Val) -> +% [jlib:now_to_utc_string(Val)]; +%%xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; +xfield_val(show_values, Val) -> Val; +xfield_val(subscription_type, items) -> [<<"items">>]; +xfield_val(subscription_type, nodes) -> [<<"nodes">>]; +xfield_val(subscription_depth, all) -> [<<"all">>]; +xfield_val(subscription_depth, N) -> + [iolist_to_binary(integer_to_list(N))]. + + +bool_to_xopt(true) -> <<"true">>; +bool_to_xopt(false) -> <<"false">>. diff --git a/src/mod_pubsub/pubsub_subscription_odbc.erl b/src/mod_pubsub/pubsub_subscription_odbc.erl index ea26f1ddd..55b337c80 100644 --- a/src/mod_pubsub/pubsub_subscription_odbc.erl +++ b/src/mod_pubsub/pubsub_subscription_odbc.erl @@ -22,139 +22,211 @@ %%% ==================================================================== -module(pubsub_subscription_odbc). + -author("pablo.polvorin@process-one.net"). %% API --export([init/0, - subscribe_node/3, - unsubscribe_node/3, - get_subscription/3, - set_subscription/4, - get_options_xform/2, - parse_options_xform/1]). +-export([init/0, subscribe_node/3, unsubscribe_node/3, + get_subscription/3, set_subscription/4, + get_options_xform/2, parse_options_xform/1]). -include("pubsub.hrl"). + -include("jlib.hrl"). --define(PUBSUB_DELIVER, "pubsub#deliver"). --define(PUBSUB_DIGEST, "pubsub#digest"). --define(PUBSUB_DIGEST_FREQUENCY, "pubsub#digest_frequency"). --define(PUBSUB_EXPIRE, "pubsub#expire"). --define(PUBSUB_INCLUDE_BODY, "pubsub#include_body"). --define(PUBSUB_SHOW_VALUES, "pubsub#show-values"). --define(PUBSUB_SUBSCRIPTION_TYPE, "pubsub#subscription_type"). --define(PUBSUB_SUBSCRIPTION_DEPTH, "pubsub#subscription_depth"). +-define(PUBSUB_DELIVER, <<"pubsub#deliver">>). + +-define(PUBSUB_DIGEST, <<"pubsub#digest">>). + +-define(PUBSUB_DIGEST_FREQUENCY, + <<"pubsub#digest_frequency">>). + +-define(PUBSUB_EXPIRE, <<"pubsub#expire">>). + +-define(PUBSUB_INCLUDE_BODY, <<"pubsub#include_body">>). + +-define(PUBSUB_SHOW_VALUES, <<"pubsub#show-values">>). + +-define(PUBSUB_SUBSCRIPTION_TYPE, + <<"pubsub#subscription_type">>). + +-define(PUBSUB_SUBSCRIPTION_DEPTH, + <<"pubsub#subscription_depth">>). -define(DELIVER_LABEL, - "Whether an entity wants to receive or disable notifications"). + <<"Whether an entity wants to receive or " + "disable notifications">>). + -define(DIGEST_LABEL, - "Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually"). + <<"Whether an entity wants to receive digests " + "(aggregations) of notifications or all " + "notifications individually">>). + -define(DIGEST_FREQUENCY_LABEL, - "The minimum number of milliseconds between sending any two notification digests"). + <<"The minimum number of milliseconds between " + "sending any two notification digests">>). + -define(EXPIRE_LABEL, - "The DateTime at which a leased subscription will end or has ended"). + <<"The DateTime at which a leased subscription " + "will end or has ended">>). + -define(INCLUDE_BODY_LABEL, - "Whether an entity wants to receive an XMPP message body in addition to the payload format"). + <<"Whether an entity wants to receive an " + "XMPP message body in addition to the " + "payload format">>). + -define(SHOW_VALUES_LABEL, - "The presence states for which an entity wants to receive notifications"). + <<"The presence states for which an entity " + "wants to receive notifications">>). + -define(SUBSCRIPTION_TYPE_LABEL, - "Type of notification to receive"). + <<"Type of notification to receive">>). + -define(SUBSCRIPTION_DEPTH_LABEL, - "Depth from subscription for which to receive notifications"). + <<"Depth from subscription for which to " + "receive notifications">>). + +-define(SHOW_VALUE_AWAY_LABEL, + <<"XMPP Show Value of Away">>). --define(SHOW_VALUE_AWAY_LABEL, "XMPP Show Value of Away"). --define(SHOW_VALUE_CHAT_LABEL, "XMPP Show Value of Chat"). --define(SHOW_VALUE_DND_LABEL, "XMPP Show Value of DND (Do Not Disturb)"). --define(SHOW_VALUE_ONLINE_LABEL, "Mere Availability in XMPP (No Show Value)"). --define(SHOW_VALUE_XA_LABEL, "XMPP Show Value of XA (Extended Away)"). +-define(SHOW_VALUE_CHAT_LABEL, + <<"XMPP Show Value of Chat">>). + +-define(SHOW_VALUE_DND_LABEL, + <<"XMPP Show Value of DND (Do Not Disturb)">>). + +-define(SHOW_VALUE_ONLINE_LABEL, + <<"Mere Availability in XMPP (No Show Value)">>). + +-define(SHOW_VALUE_XA_LABEL, + <<"XMPP Show Value of XA (Extended Away)">>). -define(SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL, - "Receive notification of new items only"). + <<"Receive notification of new items only">>). + -define(SUBSCRIPTION_TYPE_VALUE_NODES_LABEL, - "Receive notification of new nodes only"). + <<"Receive notification of new nodes only">>). -define(SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL, - "Receive notification from direct child nodes only"). --define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, - "Receive notification from all descendent nodes"). + <<"Receive notification from direct child " + "nodes only">>). +-define(SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL, + <<"Receive notification from all descendent " + "nodes">>). -define(DB_MOD, pubsub_db_odbc). %%==================================================================== %% API %%==================================================================== -init() -> - ok = create_table(). +init() -> ok = create_table(). + +-spec(subscribe_node/3 :: +( + _JID :: _, + _NodeID :: _, + Options :: mod_pubsub:subOptions()) + -> {result, mod_pubsub:subId()} +). subscribe_node(_JID, _NodeID, Options) -> SubID = make_subid(), - ?DB_MOD:add_subscription(#pubsub_subscription{subid = SubID, options = Options}), + (?DB_MOD):add_subscription(#pubsub_subscription{subid = + SubID, + options = Options}), {result, SubID}. - +-spec(unsubscribe_node/3 :: +( + _JID :: _, + _NodeID :: _, + SubID :: mod_pubsub:subId()) + -> {result, mod_pubsub:subscription()} + | {error, notfound} +). unsubscribe_node(_JID, _NodeID, SubID) -> - case ?DB_MOD:read_subscription(SubID) of - {ok, Sub} -> - ?DB_MOD:delete_subscription(SubID), - {result, Sub}; - notfound -> - {error, notfound} + case (?DB_MOD):read_subscription(SubID) of + {ok, Sub} -> + (?DB_MOD):delete_subscription(SubID), {result, Sub}; + notfound -> {error, notfound} end. +-spec(get_subscription/3 :: +( + _JID :: _, + _NodeID :: _, + SubId :: mod_pubsub:subId()) + -> {result, mod_pubsub:subscription()} + | {error, notfound} +). get_subscription(_JID, _NodeID, SubID) -> - case ?DB_MOD:read_subscription(SubID) of - {ok, Sub} -> {result, Sub}; - notfound -> {error, notfound} + case (?DB_MOD):read_subscription(SubID) of + {ok, Sub} -> {result, Sub}; + notfound -> {error, notfound} end. - +-spec(set_subscription/4 :: +( + _JID :: _, + _NodeID :: _, + SubId :: mod_pubsub:subId(), + Options :: mod_pubsub:subOptions()) + -> {result, ok} +). set_subscription(_JID, _NodeID, SubID, Options) -> - case ?DB_MOD:read_subscription(SubID) of - {ok, _} -> - ?DB_MOD:update_subscription(#pubsub_subscription{subid = SubID, options = Options}), - {result, ok}; - notfound -> - ?DB_MOD:add_subscription(#pubsub_subscription{subid = SubID, options = Options}), - {result, ok} + case (?DB_MOD):read_subscription(SubID) of + {ok, _} -> + (?DB_MOD):update_subscription(#pubsub_subscription{subid + = SubID, + options = + Options}), + {result, ok}; + notfound -> + (?DB_MOD):add_subscription(#pubsub_subscription{subid = + SubID, + options = Options}), + {result, ok} end. - get_options_xform(Lang, Options) -> Keys = [deliver, show_values, subscription_type, subscription_depth], - XFields = [get_option_xfield(Lang, Key, Options) || Key <- Keys], - - {result, {xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}], - [{xmlelement, "value", [], - [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] ++ XFields}}. + XFields = [get_option_xfield(Lang, Key, Options) + || Key <- Keys], + {result, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"FORM_TYPE">>}, + {<<"type">>, <<"hidden">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]}] + ++ XFields}}. parse_options_xform(XFields) -> case xml:remove_cdata(XFields) of - [] -> {result, []}; - [{xmlelement, "x", _Attrs, _Els} = XEl] -> - case jlib:parse_xdata_submit(XEl) of - XData when is_list(XData) -> - case set_xoption(XData, []) of - Opts when is_list(Opts) -> {result, Opts}; - Other -> Other - end; - Other -> - Other - end; - Other -> - Other + [#xmlel{name = <<"x">>} = XEl] -> + case jlib:parse_xdata_submit(XEl) of + XData when is_list(XData) -> + Opts = set_xoption(XData, []), + {result, Opts}; + Other -> Other + end; + _ -> {result, []} end. %%==================================================================== %% Internal functions %%==================================================================== -create_table() -> - ok. - +create_table() -> ok. +-spec(make_subid/0 :: () -> mod_pubsub:subId()). make_subid() -> {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])). %% %% Subscription XForm processing. @@ -162,133 +234,153 @@ make_subid() -> %% Return processed options, with types converted and so forth, using %% Opts as defaults. -set_xoption([], Opts) -> - Opts; +set_xoption([], Opts) -> Opts; set_xoption([{Var, Value} | T], Opts) -> NewOpts = case var_xfield(Var) of - {error, _} -> - Opts; - Key -> - Val = val_xfield(Key, Value), - lists:keystore(Key, 1, Opts, {Key, Val}) + {error, _} -> Opts; + Key -> + Val = val_xfield(Key, Value), + lists:keystore(Key, 1, Opts, {Key, Val}) end, set_xoption(T, NewOpts). %% Return the options list's key for an XForm var. -var_xfield(?PUBSUB_DELIVER) -> deliver; -var_xfield(?PUBSUB_DIGEST) -> digest; -var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> digest_frequency; -var_xfield(?PUBSUB_EXPIRE) -> expire; -var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; -var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; -var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> subscription_type; -var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> subscription_depth; -var_xfield(_) -> {error, badarg}. - %% Convert Values for option list's Key. -val_xfield(deliver, [Val]) -> xopt_to_bool(Val); -val_xfield(digest, [Val]) -> xopt_to_bool(Val); -val_xfield(digest_frequency, [Val]) -> list_to_integer(Val); -val_xfield(expire, [Val]) -> jlib:datetime_string_to_timestamp(Val); -val_xfield(include_body, [Val]) -> xopt_to_bool(Val); -val_xfield(show_values, Vals) -> Vals; -val_xfield(subscription_type, ["items"]) -> items; -val_xfield(subscription_type, ["nodes"]) -> nodes; -val_xfield(subscription_depth, ["all"]) -> all; -val_xfield(subscription_depth, [Depth]) -> - case catch list_to_integer(Depth) of - N when is_integer(N) -> N; - _ -> {error, ?ERR_NOT_ACCEPTABLE} +var_xfield(?PUBSUB_DELIVER) -> deliver; +%var_xfield(?PUBSUB_DIGEST) -> digest; +%var_xfield(?PUBSUB_DIGEST_FREQUENCY) -> +% digest_frequency; +%var_xfield(?PUBSUB_EXPIRE) -> expire; +%var_xfield(?PUBSUB_INCLUDE_BODY) -> include_body; +var_xfield(?PUBSUB_SHOW_VALUES) -> show_values; +var_xfield(?PUBSUB_SUBSCRIPTION_TYPE) -> + subscription_type; +var_xfield(?PUBSUB_SUBSCRIPTION_DEPTH) -> + subscription_depth; +var_xfield(_) -> {error, badarg}. + +val_xfield(deliver, [Val]) -> xopt_to_bool(Val); +%val_xfield(digest, [Val]) -> xopt_to_bool(Val); +%val_xfield(digest_frequency, [Val]) -> +% jlib:binary_to_integer(Val); +%val_xfield(expire, [Val]) -> +% jlib:datetime_string_to_timestamp(Val); +%val_xfield(include_body, [Val]) -> xopt_to_bool(Val); +val_xfield(show_values, Vals) -> Vals; +val_xfield(subscription_type, [<<"items">>]) -> items; +val_xfield(subscription_type, [<<"nodes">>]) -> nodes; +val_xfield(subscription_depth, [<<"all">>]) -> all; +val_xfield(subscription_depth, [Depth]) -> + case catch jlib:binary_to_integer(Depth) of + N when is_integer(N) -> N; + _ -> {error, ?ERR_NOT_ACCEPTABLE} end. %% Convert XForm booleans to Erlang booleans. -xopt_to_bool("0") -> false; -xopt_to_bool("1") -> true; -xopt_to_bool("false") -> false; -xopt_to_bool("true") -> true; -xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. +xopt_to_bool(<<"0">>) -> false; +xopt_to_bool(<<"1">>) -> true; +xopt_to_bool(<<"false">>) -> false; +xopt_to_bool(<<"true">>) -> true; +xopt_to_bool(_) -> {error, ?ERR_NOT_ACCEPTABLE}. %% Return a field for an XForm for Key, with data filled in, if %% applicable, from Options. get_option_xfield(Lang, Key, Options) -> Var = xfield_var(Key), Label = xfield_label(Key), - {Type, OptEls} = type_and_options(xfield_type(Key), Lang), + {Type, OptEls} = type_and_options(xfield_type(Key), + Lang), Vals = case lists:keysearch(Key, 1, Options) of - {value, {_, Val}} -> - [tr_xfield_values(Vals) || Vals <- xfield_val(Key, Val)]; - false -> - [] + {value, {_, Val}} -> + [tr_xfield_values(Vals) + || Vals <- xfield_val(Key, Val)]; + false -> [] end, - {xmlelement, "field", - [{"var", Var}, {"type", Type}, - {"label", translate:translate(Lang, Label)}], - OptEls ++ Vals}. + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, Var}, {<<"type">>, Type}, + {<<"label">>, translate:translate(Lang, Label)}], + children = OptEls ++ Vals}. type_and_options({Type, Options}, Lang) -> {Type, [tr_xfield_options(O, Lang) || O <- Options]}; -type_and_options(Type, _Lang) -> - {Type, []}. +type_and_options(Type, _Lang) -> {Type, []}. tr_xfield_options({Value, Label}, Lang) -> - {xmlelement, "option", - [{"label", translate:translate(Lang, Label)}], [{xmlelement, "value", [], - [{xmlcdata, Value}]}]}. + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, translate:translate(Lang, Label)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}]}. tr_xfield_values(Value) -> - {xmlelement, "value", [], [{xmlcdata, Value}]}. - %% Return the XForm variable name for a subscription option key. -xfield_var(deliver) -> ?PUBSUB_DELIVER; -xfield_var(digest) -> ?PUBSUB_DIGEST; -xfield_var(digest_frequency) -> ?PUBSUB_DIGEST_FREQUENCY; -xfield_var(expire) -> ?PUBSUB_EXPIRE; -xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; -xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; -xfield_var(subscription_type) -> ?PUBSUB_SUBSCRIPTION_TYPE; -xfield_var(subscription_depth) -> ?PUBSUB_SUBSCRIPTION_DEPTH. - %% Return the XForm variable type for a subscription option key. -xfield_type(deliver) -> "boolean"; -xfield_type(digest) -> "boolean"; -xfield_type(digest_frequency) -> "text-single"; -xfield_type(expire) -> "text-single"; -xfield_type(include_body) -> "boolean"; -xfield_type(show_values) -> - {"list-multi", [{"away", ?SHOW_VALUE_AWAY_LABEL}, - {"chat", ?SHOW_VALUE_CHAT_LABEL}, - {"dnd", ?SHOW_VALUE_DND_LABEL}, - {"online", ?SHOW_VALUE_ONLINE_LABEL}, - {"xa", ?SHOW_VALUE_XA_LABEL}]}; -xfield_type(subscription_type) -> - {"list-single", [{"items", ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, - {"nodes", ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Value}]}. + +xfield_var(deliver) -> ?PUBSUB_DELIVER; +%xfield_var(digest) -> ?PUBSUB_DIGEST; +%xfield_var(digest_frequency) -> +% ?PUBSUB_DIGEST_FREQUENCY; +%xfield_var(expire) -> ?PUBSUB_EXPIRE; +%xfield_var(include_body) -> ?PUBSUB_INCLUDE_BODY; +xfield_var(show_values) -> ?PUBSUB_SHOW_VALUES; +xfield_var(subscription_type) -> + ?PUBSUB_SUBSCRIPTION_TYPE; +xfield_var(subscription_depth) -> + ?PUBSUB_SUBSCRIPTION_DEPTH. + +xfield_type(deliver) -> <<"boolean">>; +%xfield_type(digest) -> <<"boolean">>; +%xfield_type(digest_frequency) -> <<"text-single">>; +%xfield_type(expire) -> <<"text-single">>; +%xfield_type(include_body) -> <<"boolean">>; +xfield_type(show_values) -> + {<<"list-multi">>, + [{<<"away">>, ?SHOW_VALUE_AWAY_LABEL}, + {<<"chat">>, ?SHOW_VALUE_CHAT_LABEL}, + {<<"dnd">>, ?SHOW_VALUE_DND_LABEL}, + {<<"online">>, ?SHOW_VALUE_ONLINE_LABEL}, + {<<"xa">>, ?SHOW_VALUE_XA_LABEL}]}; +xfield_type(subscription_type) -> + {<<"list-single">>, + [{<<"items">>, ?SUBSCRIPTION_TYPE_VALUE_ITEMS_LABEL}, + {<<"nodes">>, ?SUBSCRIPTION_TYPE_VALUE_NODES_LABEL}]}; xfield_type(subscription_depth) -> - {"list-single", [{"1", ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, - {"all", ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. + {<<"list-single">>, + [{<<"1">>, ?SUBSCRIPTION_DEPTH_VALUE_ONE_LABEL}, + {<<"all">>, ?SUBSCRIPTION_DEPTH_VALUE_ALL_LABEL}]}. %% Return the XForm variable label for a subscription option key. xfield_label(deliver) -> ?DELIVER_LABEL; -xfield_label(digest) -> ?DIGEST_LABEL; -xfield_label(digest_frequency) -> ?DIGEST_FREQUENCY_LABEL; -xfield_label(expire) -> ?EXPIRE_LABEL; -xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; +%xfield_label(digest) -> ?DIGEST_LABEL; +%xfield_label(digest_frequency) -> +% ?DIGEST_FREQUENCY_LABEL; +%xfield_label(expire) -> ?EXPIRE_LABEL; +%xfield_label(include_body) -> ?INCLUDE_BODY_LABEL; xfield_label(show_values) -> ?SHOW_VALUES_LABEL; -xfield_label(subscription_type) -> ?SUBSCRIPTION_TYPE_LABEL; -xfield_label(subscription_depth) -> ?SUBSCRIPTION_DEPTH_LABEL. - %% Return the XForm value for a subscription option key. -xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; -xfield_val(digest, Val) -> [bool_to_xopt(Val)]; -xfield_val(digest_frequency, Val) -> [integer_to_list(Val)]; -xfield_val(expire, Val) -> [jlib:now_to_utc_string(Val)]; -xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; -xfield_val(show_values, Val) -> Val; -xfield_val(subscription_type, items) -> ["items"]; -xfield_val(subscription_type, nodes) -> ["nodes"]; -xfield_val(subscription_depth, all) -> ["all"]; -xfield_val(subscription_depth, N) -> [integer_to_list(N)]. - %% Convert erlang booleans to XForms. -bool_to_xopt(false) -> "false"; -bool_to_xopt(true) -> "true". +xfield_label(subscription_type) -> + ?SUBSCRIPTION_TYPE_LABEL; +xfield_label(subscription_depth) -> + ?SUBSCRIPTION_DEPTH_LABEL. + +xfield_val(deliver, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest, Val) -> [bool_to_xopt(Val)]; +%xfield_val(digest_frequency, Val) -> +% [iolist_to_binary(integer_to_list(Val))]; +%xfield_val(expire, Val) -> +% [jlib:now_to_utc_string(Val)]; +%xfield_val(include_body, Val) -> [bool_to_xopt(Val)]; +xfield_val(show_values, Val) -> Val; +xfield_val(subscription_type, items) -> [<<"items">>]; +xfield_val(subscription_type, nodes) -> [<<"nodes">>]; +xfield_val(subscription_depth, all) -> [<<"all">>]; +xfield_val(subscription_depth, N) -> + [iolist_to_binary(integer_to_list(N))]. + +bool_to_xopt(false) -> <<"false">>; +bool_to_xopt(true) -> <<"true">>. |