From e032a8c54f0b28af78c05bb3ae22106cec40bc83 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Sat, 3 Jan 2009 00:29:36 +0000 Subject: [PATCH] PubSub cleanup, EJAB-827 fix, EJAB-701 partial fix SVN Revision: 1767 --- ChangeLog | 18 ++ src/mod_pubsub/mod_pubsub.erl | 406 +++++++++++++------------------- src/mod_pubsub/node_default.erl | 208 +++++++--------- src/mod_pubsub/node_pep.erl | 12 +- src/mod_pubsub/pubsub.hrl | 10 +- 5 files changed, 278 insertions(+), 376 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2ef1596c7..cbba3fa0b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2009-01-03 Christophe Romain + + * src/mod_pubsub/mod_pubsub.erl: deliver notification depending on + presence-based-delivery configuration (EJAB-827). notification code + rewrite. + + * src/mod_pubsub/mod_pubsub.erl: code cleanning, minor bugfixes + * src/mod_pubsub/node_default.erl: Likewise + * src/mod_pubsub/node_pep.erl: Likewise + * src/mod_pubsub/pubsub.hrl: Likewise + + * src/mod_pubsub/mod_pubsub.erl: prevent subscribing with full jid, + waiting for full jid support (EJAB-701) + + * src/mod_pubsub/mod_pubsub.erl: use of delete-any feature instead of + delete-nodes for delete item use case (fix from erroneous definition + in XEP-0060) + 2008-12-23 Christophe Romain * src/mod_pubsub/mod_pubsub.erl: Improve handling of PEP sent to diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index da1620e58..19cca3886 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -30,7 +30,7 @@ %%% %%% @reference See XEP-0060: Pubsub for %%% the latest version of the PubSub specification. -%%% This module uses version 1.11 of the specification as a base. +%%% This module uses version 1.12 of the specification as a base. %%% Most of the specification is implemented. %%% Functions concerning configuration should be rewritten. %%% Code is derivated from the original pubsub v1.7, by Alexey Shchepin @@ -40,7 +40,7 @@ -module(mod_pubsub). -author('christophe.romain@process-one.net'). --version('1.11-01'). +-version('1.12-01'). -behaviour(gen_server). -behaviour(gen_mod). @@ -92,9 +92,7 @@ string_to_subscription/1, string_to_affiliation/1, extended_error/2, - extended_error/3, - make_stanza/3, - route_stanza/3 + extended_error/3 ]). %% API and gen_server callbacks @@ -1076,12 +1074,11 @@ find_authorization_response(Packet) -> %% Plugins = [Plugin::string()] %% @doc Send a message to JID with the supplied Subscription send_authorization_approval(Host, JID, Node, Subscription) -> - Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = + Stanza = event_stanza( [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'subscription', attrs = [#xmlattr{name = 'node', value = Node}, #xmlattr{name = 'jid', value = exmpp_jid:jid_to_list(JID)}, - #xmlattr{name = 'subscription', value = subscription_to_string(Subscription)}]}]}]}, + #xmlattr{name = 'subscription', value = subscription_to_string(Subscription)}]}]), ejabberd_router ! {route, service_jid(Host), JID, Stanza}. handle_authorization_response(Host, From, To, Packet, XFields) -> @@ -1271,7 +1268,6 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> %% [{xmlelement, "x", [{"xmlns", ?NS_DATA_FORMS}, {"type", "result"}], %% [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NMI), %% ?XFIELD("jid-single", "Node Creator", "creator", jlib:jid_to_string(OwnerKey))]}]), - %% todo publish_item(Host, ServerHost, ["pubsub", "nodes"], node_to_string(Node)), case Result of default -> {result, Reply}; _ -> {result, Result} @@ -1317,14 +1313,17 @@ delete_node(Host, Node, Owner) -> {error, Error} -> {error, Error}; {result, {Result, broadcast, Removed}} -> - broadcast_removed_node(Host, Removed), - %%broadcast_retract_item(Host, ["pubsub", "nodes"], node_to_string(Node)), + lists:foreach(fun(RNode) -> + broadcast_removed_node(Host, RNode) + end, Removed), case Result of default -> {result, Reply}; _ -> {result, Result} end; {result, {Result, Removed}} -> - broadcast_removed_node(Host, Removed), + lists:foreach(fun(RNode) -> + broadcast_removed_node(Host, RNode) + end, Removed), case Result of default -> {result, Reply}; _ -> {result, Result} @@ -1536,23 +1535,17 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> {error, Reason} -> {error, Reason}; {result, {Result, broadcast, Removed}} -> - lists:foreach(fun(OldItem) -> - broadcast_retract_item(Host, Node, OldItem) - end, Removed), + broadcast_retract_items(Host, Node, Removed), broadcast_publish_item(Host, Node, ItemId, jlib:short_prepd_jid(Publisher), Payload), case Result of default -> {result, Reply}; _ -> {result, Result} end; {result, default, Removed} -> - lists:foreach(fun(OldItem) -> - broadcast_retract_item(Host, Node, OldItem) - end, Removed), + broadcast_retract_items(Host, Node, Removed), {result, Reply}; {result, Result, Removed} -> - lists:foreach(fun(OldItem) -> - broadcast_retract_item(Host, Node, OldItem) - end, Removed), + broadcast_retract_items(Host, Node, Removed), {result, Result}; {result, default} -> {result, Reply}; @@ -1583,7 +1576,7 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> Action = fun(#pubsub_node{type = Type}) -> Features = features(Type), PersistentFeature = lists:member("persistent-items", Features), - DeleteFeature = lists:member("delete-nodes", Features), + DeleteFeature = lists:member("delete-any", Features), if %%-> iq_pubsub just does that matchs %% %% Request does not specify an item @@ -1593,7 +1586,7 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> {error, extended_error('feature-not-implemented', unsupported, "persistent-items")}; not DeleteFeature -> %% Service does not support item deletion - {error, extended_error('feature-not-implemented', unsupported, "delete-nodes")}; + {error, extended_error('feature-not-implemented', unsupported, "delete-any")}; true -> node_call(Type, delete_item, [Host, Node, Publisher, ItemId]) end @@ -1603,7 +1596,7 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> {error, Reason} -> {error, Reason}; {result, {Result, broadcast}} -> - broadcast_retract_item(Host, Node, ItemId, ForceNotify), + broadcast_retract_items(Host, Node, [ItemId], ForceNotify), case Result of default -> {result, Reply}; _ -> {result, Result} @@ -1771,12 +1764,10 @@ send_items(Host, Node, {LU, LS, LR} = LJID, Number) -> [First|Tail] = Items, [lists:foldl( fun(CurItem, LastItem) -> - {_, {LMS, LS, LmS}} = LastItem#pubsub_item.creation, - {_, {CMS, CS, CmS}} = CurItem#pubsub_item.creation, - LTimestamp = LMS * 1000000 + LS * 1000 + LmS, - CTimestamp = CMS * 1000000 + CS * 1000 + CmS, + {_, LTimeStamp} = LastItem#pubsub_item.creation, + {_, CTimeStamp} = CurItem#pubsub_item.creation, if - CTimestamp > LTimestamp -> CurItem; + CTimeStamp > LTimeStamp -> CurItem; true -> LastItem end end, First, Tail)]; @@ -1795,10 +1786,9 @@ send_items(Host, Node, {LU, LS, LR} = LJID, Number) -> end, #xmlel{ns = ?NS_PUBSUB_EVENT, name = 'item', attrs = ItemAttrs, children = Payload} end, ToSend), - Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = + Stanza = event_stanza( [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], children = - ItemsEls}]}]}, + ItemsEls}]), ejabberd_router ! {route, service_jid(Host), exmpp_jid:make_jid(LU, LS, LR), Stanza}. %% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response} @@ -2132,10 +2122,10 @@ service_jid(Host) -> %% Subscription = atom() %% PresenceDelivery = boolean() %% @doc

Check if a notification must be delivered or not.

-is_to_delivered(_, none, _) -> false; -is_to_delivered(_, pending, _) -> false; -is_to_delivered(_, _, false) -> true; -is_to_delivered({User, Server, _}, _, true) -> +is_to_deliver(_, none, _) -> false; +is_to_deliver(_, pending, _) -> false; +is_to_deliver(_, _, false) -> true; +is_to_deliver({User, Server, _}, _, true) -> case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of [] -> false; Ss -> @@ -2153,228 +2143,172 @@ payload_xmlelements([], Count) -> 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

Build pubsub event stanza +event_stanza(Els) -> + #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = Els}]}. + %%%%%% broadcast functions broadcast_publish_item(Host, Node, ItemId, _From, Payload) -> Action = fun(#pubsub_node{options = Options, type = Type}) -> - case node_call(Type, get_states, [Host, Node]) of - {error, _} -> {result, false}; - {result, []} -> {result, false}; - {result, States} -> - PresenceDelivery = get_option(Options, presence_based_delivery), - BroadcastAll = get_option(Options, broadcast_all_resources), - Content = case get_option(Options, deliver_payloads) of - true -> Payload; - false -> [] - end, - ItemAttrs = case ItemId of - "" -> []; - _ -> [#xmlattr{name = 'id', value = ItemId}] - end, - Stanza = make_stanza(Node, ItemAttrs, Content), - lists:foreach( - fun(#pubsub_state{stateid = {LJID, _}, - subscription = Subscription}) -> - case is_to_delivered(LJID, Subscription, PresenceDelivery) of - true -> - DestJIDs = case BroadcastAll of - true -> ejabberd_sm:get_user_resources(element(1, LJID), element(2, LJID)); - false -> [LJID] - end, - route_stanza(Host, DestJIDs, Stanza); - false -> - ok - end - end, States), - broadcast_by_caps(Host, Node, Type, Stanza), - {result, true} - end + case node_call(Type, get_states, [Host, Node]) of + {result, []} -> + {result, false}; + {result, States} -> + Content = case get_option(Options, deliver_payloads) of + true -> Payload; + false -> [] + end, + ItemAttrs = case ItemId of + "" -> []; + _ -> [#xmlattr{name = 'id', value = ItemId}] + end, + Stanza = event_stanza( + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], children = + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'item', attrs = ItemAttrs, children = Content}]}]), + broadcast_stanza(Host, Options, States, Stanza), + broadcast_by_caps(Host, Node, Type, Stanza), + {result, true}; + _ -> + {result, false} + end end, transaction(Host, Node, Action, sync_dirty). -%% ItemAttrs is a list of tuples: -%% For example: [{"id", ItemId}] -make_stanza(Node, ItemAttrs, Payload) -> - #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'item', attrs = ItemAttrs, children = Payload}]}]}]}. - -%% DestJIDs = [{LUser, LServer, LResource}] -route_stanza(Host, DestJIDs, Stanza) -> - lists:foreach( - fun({DU, DS, DR}) -> - ejabberd_router ! {route, service_jid(Host), exmpp_jid:make_jid(DU, DS, DR), Stanza} - end, DestJIDs). - -broadcast_retract_item(Host, Node, ItemId) -> - broadcast_retract_item(Host, Node, ItemId, false). -broadcast_retract_item(Host, Node, ItemId, ForceNotify) -> +broadcast_retract_items(Host, Node, ItemIds) -> + broadcast_retract_items(Host, Node, ItemIds, false). +broadcast_retract_items(Host, Node, ItemIds, ForceNotify) -> Action = fun(#pubsub_node{options = Options, type = Type}) -> - case node_call(Type, get_states, [Host, Node]) of - {error, _} -> {result, false}; - {result, []} -> {result, false}; - {result, States} -> - Notify = case ForceNotify of - true -> true; - _ -> get_option(Options, notify_retract) - end, - ItemAttrs = case ItemId of - "" -> []; - _ -> [#xmlattr{name = 'id', value = ItemId}] - end, - Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'retract', attrs = ItemAttrs}]}]}]}, - case Notify of - true -> - lists:foreach( - fun(#pubsub_state{stateid = {{U, S, R}, _}, - subscription = Subscription}) -> - if (Subscription /= none) and - (Subscription /= pending) -> - ejabberd_router ! {route, service_jid(Host), exmpp_jid:make_jid(U, S, R), Stanza}; - true -> - ok - end - end, States), - broadcast_by_caps(Host, Node, Type, Stanza), - {result, true}; - false -> - {result, false} - end - end + case (get_option(Options, notify_retract) or ForceNotify) of + true -> + case node_call(Type, get_states, [Host, Node]) of + {result, []} -> + {result, false}; + {result, States} -> + RetractEls = lists:map( + fun(ItemId) -> + ItemAttrs = case ItemId of + "" -> []; + _ -> [#xmlattr{name = 'id', value = ItemId}] + end, + #xmlel{ns = ?NS_PUBSUB_EVENT, name = 'retract', attrs = ItemAttrs} + end, ItemIds), + Stanza = event_stanza( + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], + children = RetractEls}]), + broadcast_stanza(Host, Options, States, Stanza), + broadcast_by_caps(Host, Node, Type, Stanza), + {result, true}; + _ -> + {result, false} + end; + _ -> + {result, false} + end end, transaction(Host, Node, Action, sync_dirty). broadcast_purge_node(Host, Node) -> Action = fun(#pubsub_node{options = Options, type = Type}) -> - case node_call(Type, get_states, [Host, Node]) of - {error, _} -> {result, false}; - {result, []} -> {result, false}; - {result, States} -> - Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'purge', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}]}]}]}, - case get_option(Options, notify_retract) of - true -> - lists:foreach( - fun(#pubsub_state{stateid = {{U, S, R},_}, - subscription = Subscription}) -> - if (Subscription /= none) and - (Subscription /= pending) -> - ejabberd_router ! {route, service_jid(Host), exmpp_jid:make_jid(U, S, R), Stanza}; - true -> - ok - end - end, States), - broadcast_by_caps(Host, Node, Type, Stanza), - {result, true}; - false -> - {result, false} - end - end + case get_option(Options, notify_retract) of + true -> + case node_call(Type, get_states, [Host, Node]) of + {result, []} -> + {result, false}; + {result, States} -> + Stanza = event_stanza( + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'purge', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}]}]), + broadcast_stanza(Host, Options, States, Stanza), + broadcast_by_caps(Host, Node, Type, Stanza), + {result, true}; + _ -> + {result, false} + end; + _ -> + {result, false} + end end, transaction(Host, Node, Action, sync_dirty). -broadcast_removed_node(Host, Removed) -> - lists:foreach( - fun(Node) -> - Action = - fun(#pubsub_node{options = Options, type = Type}) -> - Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'delete', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}]}]}]}, - case get_option(Options, notify_delete) of - true -> - case node_call(Type, get_states, [Host, Node]) of - {result, States} -> - lists:foreach( - fun(#pubsub_state{stateid = {{U, S, R}, _}, - subscription = Subscription}) -> - if (Subscription /= none) and - (Subscription /= pending) -> - ejabberd_router ! {route, service_jid(Host), jlib:make_jid(U, S, R), Stanza}; - true -> - ok - end - end, States), - broadcast_by_caps(Host, Node, Type, Stanza), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end - end, - transaction(Host, Node, Action, sync_dirty) - end, Removed). +broadcast_removed_node(Host, Node) -> + Action = + fun(#pubsub_node{options = Options, type = Type}) -> + case get_option(Options, notify_delete) of + true -> + case node_call(Type, get_states, [Host, Node]) of + {result, []} -> + {result, false}; + {result, States} -> + Stanza = event_stanza( + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'delete', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}]}]), + broadcast_stanza(Host, Options, States, Stanza), + broadcast_by_caps(Host, Node, Type, Stanza), + {result, true}; + _ -> + {result, false} + end; + _ -> + {result, false} + end + end, + transaction(Host, Node, Action, sync_dirty). broadcast_config_notification(Host, Node, Lang) -> Action = fun(#pubsub_node{options = Options, owners = Owners, type = Type}) -> - case node_call(Type, get_states, [Host, Node]) of - {error, _} -> {result, false}; - {result, []} -> {result, false}; - {result, States} -> - case get_option(Options, notify_config) of - true -> - PresenceDelivery = get_option(Options, presence_based_delivery), - Content = case get_option(Options, deliver_payloads) of - true -> - [#xmlel{ns = ?NS_DATA_FORMS, name = 'x', attrs = [#xmlattr{name = 'type', value = "form"}], children = - get_configure_xfields(Type, Options, Lang, Owners)}]; - false -> - [] - end, - Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'event', children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], children = - [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'item', attrs = [#xmlattr{name = 'id', value = "configuration"}], children = - Content}]}]}]}, - lists:foreach( - fun(#pubsub_state{stateid = {{U, S, R} = LJID, _}, - subscription = Subscription}) -> - case is_to_delivered(LJID, Subscription, PresenceDelivery) of - true -> - ejabberd_router ! {route, service_jid(Host), exmpp_jid:make_jid(U, S, R), Stanza}; - false -> - ok - end - end, States), - broadcast_by_caps(Host, Node, Type, Stanza), - {result, true}; - _ -> - {result, false} - end - end + case get_option(Options, notify_config) of + true -> + case node_call(Type, get_states, [Host, Node]) of + {result, []} -> + {result, false}; + {result, States} -> + Content = case get_option(Options, deliver_payloads) of + true -> + [#xmlel{ns = ?NS_DATA_FORMS, name = 'x', attrs = [#xmlattr{name = 'type', value = "form"}], children = + get_configure_xfields(Type, Options, Lang, Owners)}]; + false -> + [] + end, + Stanza = event_stanza( + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = [#xmlattr{name = 'node', value = node_to_string(Node)}], children = + [#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'item', attrs = [#xmlattr{name = 'id', value = "configuration"}], children = + Content}]}]), + broadcast_stanza(Host, Options, States, Stanza), + broadcast_by_caps(Host, Node, Type, Stanza), + {result, true}; + _ -> + {result, false} + end; + _ -> + {result, false} + end end, transaction(Host, Node, Action, sync_dirty). -%TODO: simplify broadcast_* using a generic function like that: -%broadcast(Host, Node, Fun) -> -% transaction(fun() -> -% case tree_call(Host, get_node, [Host, Node]) of -% #pubsub_node{options = Options, owners = Owners, type = Type} -> -% case node_call(Type, get_states, [Host, Node]) of -% {error, _} -> {result, false}; -% {result, []} -> {result, false}; -% {result, States} -> -% lists:foreach(fun(#pubsub_state{stateid = {JID,_}, subscription = Subscription}) -> -% Fun(Host, Node, Options, Owners, JID, Subscription) -% end, States), -% {result, true} -% end; -% Other -> -% Other -% end -% end, sync_dirty). - +broadcast_stanza(Host, NodeOpts, States, Stanza) -> + PresenceDelivery = get_option(NodeOpts, presence_based_delivery), + BroadcastAll = get_option(NodeOpts, broadcast_all_resources), + From = service_jid(Host), + lists:foreach(fun(#pubsub_state{stateid = {LJID, _}, subscription = Subs}) -> + case is_to_deliver(LJID, Subs, PresenceDelivery) of + true -> + JIDs = case BroadcastAll of + true -> ejabberd_sm:get_user_resources(element(1, LJID), element(2, LJID)); + false -> [LJID] + end, + lists:foreach(fun({U, S, R}) -> + ejabberd_router ! {route, From, exmpp_jlib:make_jid(U, S, R), Stanza} + end, JIDs); + false -> + ok + end + end, States). %% broadcast Stanza to all contacts of the user that are advertising %% interest in this kind of Node. @@ -2724,18 +2658,18 @@ select_type(ServerHost, Host, Node) -> features() -> [ - %"access-authorize", % OPTIONAL + %TODO "access-authorize", % OPTIONAL "access-open", % OPTIONAL this relates to access_model option in node_default "access-presence", % OPTIONAL this relates to access_model option in node_pep - %"access-roster", % OPTIONAL - %"access-whitelist", % OPTIONAL + %TODO "access-roster", % OPTIONAL + %TODO "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 - %TODO "delete-any", % OPTIONAL + % see plugin "delete-any", % RECOMMENDED % see plugin "delete-nodes", % RECOMMENDED % see plugin "filtered-notifications", % RECOMMENDED %TODO "get-pending", % OPTIONAL diff --git a/src/mod_pubsub/node_default.erl b/src/mod_pubsub/node_default.erl index 8757ac4a4..2deb66907 100644 --- a/src/mod_pubsub/node_default.erl +++ b/src/mod_pubsub/node_default.erl @@ -160,6 +160,7 @@ features() -> ["create-nodes", "auto-create", "delete-nodes", + "delete-any", "instant-nodes", "manage-subscriptions", "modify-affiliations", @@ -222,8 +223,7 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) -> %% @doc

create_node(Host, Node, Owner) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), - mnesia:write(#pubsub_state{stateid = {OwnerKey, {Host, Node}}, - affiliation = owner, subscription = none}), + set_state(#pubsub_state{stateid = {OwnerKey, {Host, Node}}, affiliation = owner}), {result, {default, broadcast}}. @@ -236,12 +236,8 @@ delete_node(Host, Removed) -> fun(Node) -> lists:foreach( fun(#pubsub_state{stateid = StateId, items = Items}) -> - lists:foreach( - fun(ItemId) -> - mnesia:delete( - {pubsub_item, {ItemId, {Host, Node}}}) - end, Items), - mnesia:delete({pubsub_state, StateId}) + del_items(Host, Node, Items), + del_state(StateId) end, mnesia:match_object( #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'})) @@ -283,13 +279,9 @@ delete_node(Host, Removed) -> %%

In the default plugin module, the record is unchanged.

subscribe_node(Host, Node, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) -> - Authorized = (jlib:short_prepd_bare_jid(Sender) == jlib:short_bare_jid(Subscriber)), - % TODO add some acl check for Authorized ? - State = case get_state(Host, Node, Subscriber) of - {error, 'item-not-found'} -> - #pubsub_state{stateid = {Subscriber, {Host, Node}}}; % TODO: bug on Key ? - {result, S} -> S - end, + SubscriberKey = jlib:short_prepd_bare_jid(Subscriber), + Authorized = (jlib:short_prepd_bare_jid(Sender) == SubscriberKey), + State = get_state(Host, Node, SubscriberKey), #pubsub_state{affiliation = Affiliation, subscription = Subscription} = State, if @@ -325,7 +317,6 @@ subscribe_node(Host, Node, Sender, Subscriber, AccessModel, if AccessModel == authorize -> pending; - %%TODO Affiliation == none -> ? %%NeedConfiguration -> %% unconfigured true -> @@ -353,43 +344,29 @@ subscribe_node(Host, Node, Sender, Subscriber, AccessModel, %% Reason = mod_pubsub:stanzaError() %% @doc

Unsubscribe the Subscriber from the Node.

unsubscribe_node(Host, Node, Sender, Subscriber, _SubId) -> - SenderKey = jlib:short_prepd_jid(Sender), - Match = jlib:short_prepd_bare_jid(Sender) == jlib:short_bare_jid(Subscriber), - Authorized = case Match of - true -> - true; - false -> - case get_state(Host, Node, SenderKey) of % TODO: bug on Key ? - {result, #pubsub_state{affiliation=owner}} -> true; - _ -> false - end - end, - case get_state(Host, Node, Subscriber) of - {error, 'item-not-found'} -> - %% Requesting entity is not a subscriber + SubscriberKey = jlib:short_prepd_bare_jid(Subscriber), + Authorized = (jlib:short_prepd_bare_jid(Sender) == SubscriberKey), + State = get_state(Host, Node, SubscriberKey), + if + %% Entity did not specify SubID + %%SubID == "", ?? -> + %% {error, ?ERR_EXTENDED('bad-request', "subid-required")}; + %% Invalid subscription identifier + %%InvalidSubID -> + %% {error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")}; + %% Requesting entity is not a subscriber + State#pubsub_state.subscription == none -> {error, ?ERR_EXTENDED('unexpected-request', "not-subscribed")}; - {result, State} -> - if - %% Entity did not specify SubID - %%SubID == "", ?? -> - %% {error, ?ERR_EXTENDED('bad-request', "subid-required")}; - %% Invalid subscription identifier - %%InvalidSubID -> - %% {error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")}; - %% Requesting entity is not a subscriber - State#pubsub_state.subscription == none -> - {error, ?ERR_EXTENDED('unexpected-request', "not-subscribed")}; - %% Requesting entity is prohibited from unsubscribing entity - not Authorized -> - {error, 'forbidden'}; - %% Was just subscriber, remove the record - State#pubsub_state.affiliation == none -> - mnesia:delete({pubsub_state, State#pubsub_state.stateid}), - {result, default}; - true -> - set_state(State#pubsub_state{subscription = none}), - {result, default} - end + %% Requesting entity is prohibited from unsubscribing entity + (not Authorized) and (State#pubsub_state.affiliation =/= owner) -> + {error, 'forbidden'}; + %% Was just subscriber, remove the record + State#pubsub_state.affiliation == none -> + mnesia:delete({pubsub_state, State#pubsub_state.stateid}), + {result, default}; + true -> + set_state(State#pubsub_state{subscription = none}), + {result, default} end. %% @spec (Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) -> @@ -433,10 +410,7 @@ unsubscribe_node(Host, Node, Sender, Subscriber, _SubId) -> %%

In the default plugin module, the record is unchanged.

publish_item(Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) -> PublisherKey = jlib:short_prepd_bare_jid(Publisher), - State = case get_state(Host, Node, PublisherKey) of - {error, 'item-not-found'} -> #pubsub_state{stateid={PublisherKey, {Host, Node}}}; - {result, S} -> S - end, + State = get_state(Host, Node, PublisherKey), #pubsub_state{affiliation = Affiliation, subscription = Subscription} = State, if @@ -448,17 +422,17 @@ publish_item(Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload) -> %% Entity does not have sufficient privileges to publish to node {error, 'forbidden'}; true -> - PubId = {PublisherKey, now()}, + PubId = {PublisherKey, now()}, %% TODO, uses {now(),PublisherKey} for sorting (EJAB-824) %% TODO: check creation, presence, roster (EJAB-663) Item = case get_item(Host, Node, ItemId) of - {error, 'item-not-found'} -> + {result, OldItem} -> + OldItem#pubsub_item{modification = PubId, + payload = Payload}; + _ -> #pubsub_item{itemid = {ItemId, {Host, Node}}, creation = PubId, modification = PubId, - payload = Payload}; - {result, OldItem} -> - OldItem#pubsub_item{modification = PubId, - payload = Payload} + payload = Payload} end, Items = [ItemId | State#pubsub_state.items--[ItemId]], {result, {NI, OI}} = remove_extra_items( @@ -492,9 +466,7 @@ remove_extra_items(Host, Node, MaxItems, ItemIds) -> NewItems = lists:sublist(ItemIds, MaxItems), OldItems = lists:nthtail(length(NewItems), ItemIds), %% Remove extra items: - lists:foreach(fun(ItemId) -> - mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}) - end, OldItems), + del_items(Host, Node, OldItems), %% Return the new items list: {result, {NewItems, OldItems}}. @@ -510,12 +482,7 @@ remove_extra_items(Host, Node, MaxItems, ItemIds) -> %% or a publisher.

delete_item(Host, Node, Publisher, ItemId) -> PublisherKey = jlib:short_prepd_bare_jid(Publisher), - State = case get_state(Host, Node, PublisherKey) of - {error, 'item-not-found'} -> - #pubsub_state{stateid = {PublisherKey, {Host, Node}}}; - {result, S} -> - S - end, + State = get_state(Host, Node, PublisherKey), #pubsub_state{affiliation = Affiliation, items = Items} = State, Allowed = (Affiliation == publisher) orelse (Affiliation == owner) orelse case get_item(Host, Node, ItemId) of @@ -529,7 +496,7 @@ delete_item(Host, Node, Publisher, ItemId) -> true -> case get_item(Host, Node, ItemId) of {result, _} -> - mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}), + del_item(Host, Node, ItemId), NewItems = lists:delete(ItemId, Items), set_state(State#pubsub_state{items = NewItems}), {result, {default, broadcast}}; @@ -548,16 +515,14 @@ delete_item(Host, Node, Publisher, ItemId) -> purge_node(Host, Node, Owner) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), case get_state(Host, Node, OwnerKey) of - {result, #pubsub_state{items = Items, affiliation = owner}} -> + #pubsub_state{items = Items, affiliation = owner} -> lists:foreach(fun(ItemId) -> mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}) end, Items), {result, {default, broadcast}}; - {result, _} -> - %% Entity is not owner - {error, 'forbidden'}; _ -> - {error, 'item-not-found'} + %% Entity is not owner + {error, 'forbidden'} end. %% @spec (Host, JID) -> [{Node,Affiliation}] @@ -573,8 +538,7 @@ purge_node(Host, Node, Owner) -> get_entity_affiliations(Host, Owner) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), States = mnesia:match_object( - #pubsub_state{stateid = {OwnerKey, {Host, '_'}}, - _ = '_'}), + #pubsub_state{stateid = {OwnerKey, {Host, '_'}}, _ = '_'}), Tr = fun(#pubsub_state{stateid = {_, {_, N}}, affiliation = A}) -> {N, A} end, @@ -582,8 +546,7 @@ get_entity_affiliations(Host, Owner) -> get_node_affiliations(Host, Node) -> States = mnesia:match_object( - #pubsub_state{stateid = {'_', {Host, Node}}, - _ = '_'}), + #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}), Tr = fun(#pubsub_state{stateid = {J, {_, _}}, affiliation = A}) -> {J, A} end, @@ -591,22 +554,13 @@ get_node_affiliations(Host, Node) -> get_affiliation(Host, Node, Owner) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), - Affiliation = case get_state(Host, Node, OwnerKey) of - {result, #pubsub_state{affiliation = A}} -> A; - _ -> none - end, - {result, Affiliation}. + State = get_state(Host, Node, OwnerKey), + {result, State#pubsub_state.affiliation}. set_affiliation(Host, Node, Owner, Affiliation) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), - Record = case get_state(Host, Node, OwnerKey) of - {error, 'item-not-found'} -> - #pubsub_state{stateid = {OwnerKey, {Host, Node}}, - affiliation = Affiliation}; - {result, State} -> - State#pubsub_state{affiliation = Affiliation} - end, - set_state(Record), + State = get_state(Host, Node, OwnerKey), + set_state(State#pubsub_state{affiliation = Affiliation}), ok. %% @spec (Host, Owner) -> [{Node,Subscription}] @@ -623,8 +577,7 @@ set_affiliation(Host, Node, Owner, Affiliation) -> get_entity_subscriptions(Host, Owner) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), States = mnesia:match_object( - #pubsub_state{stateid = {OwnerKey, {Host, '_'}}, - _ = '_'}), + #pubsub_state{stateid = {OwnerKey, {Host, '_'}}, _ = '_'}), Tr = fun(#pubsub_state{stateid = {_, {_, N}}, subscription = S}) -> {N, S} end, @@ -632,8 +585,7 @@ get_entity_subscriptions(Host, Owner) -> get_node_subscriptions(Host, Node) -> States = mnesia:match_object( - #pubsub_state{stateid = {'_', {Host, Node}}, - _ = '_'}), + #pubsub_state{stateid = {'_', {Host, Node}}, _ = '_'}), Tr = fun(#pubsub_state{stateid = {J, {_, _}}, subscription = S}) -> {J, S} end, @@ -641,22 +593,13 @@ get_node_subscriptions(Host, Node) -> get_subscription(Host, Node, Owner) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), - Subscription = case get_state(Host, Node, OwnerKey) of - {result, #pubsub_state{subscription = S}} -> S; - _ -> none - end, - {result, Subscription}. + State = get_state(Host, Node, OwnerKey), + {result, State#pubsub_state.subscription}. set_subscription(Host, Node, Owner, Subscription) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), - Record = case get_state(Host, Node, OwnerKey) of - {error, 'item-not-found'} -> - #pubsub_state{stateid = {OwnerKey, {Host, Node}}, - subscription = Subscription}; - {result, State} -> - State#pubsub_state{subscription = Subscription} - end, - set_state(Record), + State = get_state(Host, Node, OwnerKey), + set_state(State#pubsub_state{subscription = Subscription}), ok. %% @spec (Host, Node) -> [States] | [] @@ -685,11 +628,10 @@ get_states(Host, Node) -> %% State = mod_pubsub:pubsubItems() %% @doc

Returns a state (one state list), given its reference.

get_state(Host, Node, JID) -> - case mnesia:read({pubsub_state, {JID, {Host, Node}}}) of - [State] when is_record(State, pubsub_state) -> - {result, State}; - _ -> - {error, 'item-not-found'} + StateId = {JID, {Host, Node}}, + case mnesia:read({pubsub_state, StateId}) of + [State] when is_record(State, pubsub_state) -> State + _ -> #pubsub_state{stateid=StateId} end. %% @spec (State) -> ok | {error, Reason::stanzaError()} @@ -700,6 +642,12 @@ set_state(State) when is_record(State, pubsub_state) -> set_state(_) -> {error, 'internal-server-error'}. +%% @spec (StateId) -> ok | {error, Reason::stanzaError()} +%% StateId = mod_pubsub:pubsubStateId() +%% @doc

Delete a state from database.

+del_state(StateId) -> + mnesia:delete({pubsub_state, StateId}). + %% @spec (Host, Node) -> [Items] | [] %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() @@ -719,11 +667,9 @@ get_items(Host, Node, _From) -> #pubsub_item{itemid = {'_', {Host, Node}}, _ = '_'}), {result, Items}. get_items(Host, Node, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> - {Affiliation, Subscription} = - case get_state(Host, Node, jlib:short_prepd_bare_jid(JID)) of - {result, #pubsub_state{affiliation = A, subscription = S}} -> {A, S}; - _ -> {none, none} - end, + State = get_state(Host, Node, jlib:short_prepd_bare_jid(JID)), + #pubsub_state{affiliation = Affiliation, + subscription = Subscription} = State, Subscribed = not ((Subscription == none) or (Subscription == pending)), if %%SubID == "", ?? -> @@ -771,11 +717,9 @@ get_item(Host, Node, ItemId) -> {error, 'item-not-found'} end. get_item(Host, Node, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> - {Affiliation, Subscription} = - case get_state(Host, Node, jlib:short_prepd_bare_jid(JID)) of - {result, #pubsub_state{affiliation = A, subscription = S}} -> {A, S}; - _ -> {none, none} - end, + State = get_state(Host, Node, jlib:short_prepd_bare_jid(JID)), + #pubsub_state{affiliation = Affiliation, + subscription = Subscription} = State, Subscribed = not ((Subscription == none) or (Subscription == pending)), if %%SubID == "", ?? -> @@ -817,6 +761,18 @@ set_item(Item) when is_record(Item, pubsub_item) -> set_item(_) -> {error, 'internal-server-error'}. +%% @spec (ItemId) -> ok | {error, Reason::stanzaError()} +%% Host = mod_pubsub:host() +%% Node = mod_pubsub:pubsubNode() +%% ItemId = string() +%% @doc

Delete an item from database.

+del_item(Host, Node, ItemId) -> + mnesia:delete({pubsub_item, {ItemId, {Host, Node}}}). +del_items(Host, Node, ItemIds) -> + lists:foreach(fun(ItemId) -> + del_item(Host, Node, ItemId) + end, ItemIds). + %% @doc

Return the name of the node if known: Default is to return %% node id.

get_item_name(_Host, _Node, Id) -> diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl index ec617abda..cbbc16ce6 100644 --- a/src/mod_pubsub/node_pep.erl +++ b/src/mod_pubsub/node_pep.erl @@ -182,15 +182,9 @@ get_affiliation(_Host, Node, Owner) -> set_affiliation(_Host, Node, Owner, Affiliation) -> OwnerKey = jlib:short_prepd_bare_jid(Owner), - Record = case get_state(OwnerKey, Node, OwnerKey) of - {error, 'item-not-found'} -> - #pubsub_state{stateid = {OwnerKey, {OwnerKey, Node}}, - affiliation = Affiliation}; - {result, State} -> - State#pubsub_state{affiliation = Affiliation} - end, - set_state(Record), - ok. + State = get_state(OwnerKey, Node, OwnerKey), + set_state(State#pubsub_state{affiliation = Affiliation}), + ok. get_entity_subscriptions(_Host, _Owner) -> {result, []}. diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl index b4ceed4fa..61ed88dbe 100644 --- a/src/mod_pubsub/pubsub.hrl +++ b/src/mod_pubsub/pubsub.hrl @@ -72,7 +72,7 @@ %%% lserver = string(), %%% lresource = string()}. -%%% @type usr() = {User::string(), Server::string(), Resource::string()}. +%%% @type ljid() = {User::string(), Server::string(), Resource::string()}. %%% @type affiliation() = none | owner | publisher | outcast. %%% @type subscription() = none | pending | unconfigured | subscribed. @@ -81,7 +81,7 @@ %%% nodeid = {Host::host(), Node::pubsubNode()}, %%% parentid = {Host::host(), Node::pubsubNode()}, %%% type = nodeType(), -%%% owners = [usr()], +%%% owners = [ljid()], %%% options = [nodeOption()]}. %%%

This is the format of the nodes table. The type of the table %%% is: set,ram/disc.

@@ -94,7 +94,7 @@ }). %%% @type pubsubState() = #pubsub_state{ -%%% stateid = {jid(), {Host::host(), Node::pubsubNode()}}, +%%% stateid = {ljid(), {Host::host(), Node::pubsubNode()}}, %%% items = [ItemId::string()], %%% affiliation = affiliation(), %%% subscription = subscription()}. @@ -108,8 +108,8 @@ %% @type pubsubItem() = #pubsub_item{ %% itemid = {ItemId::string(), {Host::host(),Node::pubsubNode()}}, -%% creation = {JID::jid(), now()}, -%% modification = {JID::jid(), now()}, +%% creation = {ljid(), now()}, +%% modification = {ljid(), now()}, %% payload = XMLContent::string()}. %%%

This is the format of the published items table. The type of the %%% table is: set,disc,fragmented.