From 9f2fda60601cfa956c865f482d05f82fc6e863e3 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 9 Dec 2008 00:32:36 +0000 Subject: [PATCH] merge to lattest trunk r1716 SVN Revision: 1720 --- src/mod_pubsub/gen_pubsub_nodetree.erl | 2 + src/mod_pubsub/mod_pubsub.erl | 245 ++++++++++-------- src/mod_pubsub/node_default.erl | 4 + .../{node_zoo.erl => node_flat.erl} | 11 +- src/mod_pubsub/nodetree_default.erl | 8 + src/mod_pubsub/nodetree_virtual.erl | 8 + src/mod_pubsub/pubsub.hrl | 10 - 7 files changed, 161 insertions(+), 127 deletions(-) rename src/mod_pubsub/{node_zoo.erl => node_flat.erl} (97%) diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl index ebdeca450..c249ff43d 100644 --- a/src/mod_pubsub/gen_pubsub_nodetree.erl +++ b/src/mod_pubsub/gen_pubsub_nodetree.erl @@ -42,7 +42,9 @@ behaviour_info(callbacks) -> {terminate, 2}, {options, 0}, {set_node, 1}, + {get_node, 3}, {get_node, 2}, + {get_nodes, 2}, {get_nodes, 1}, {get_subnodes, 3}, {get_subnodes_tree, 2}, diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index fed1c3aff..244268aa0 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -356,11 +356,11 @@ disco_sm_features(Acc, From, To, Node, _Lang) -> Acc end. -disco_sm_items(Acc, _From, To, [], _Lang) -> +disco_sm_items(Acc, From, To, [], _Lang) -> %% TODO, use iq_disco_items(Host, [], From) Host = To#jid.ldomain, LJID = jlib:short_prepd_bare_jid(To), - case tree_action(Host, get_nodes, [Host]) of + case tree_action(Host, get_nodes, [Host, From]) of [] -> Acc; Nodes -> @@ -460,7 +460,7 @@ handle_cast({presence, JID, Pid}, State) -> {result, Subscriptions} = node_action(Type, get_entity_subscriptions, [Host, JID]), lists:foreach( fun({Node, subscribed}) -> - case tree_action(Host, get_node, [Host, Node]) of + case tree_action(Host, get_node, [Host, Node, JID]) of #pubsub_node{options = Options} -> case get_option(Options, send_last_published_item) of on_sub_and_presence -> @@ -477,17 +477,14 @@ handle_cast({presence, JID, Pid}, State) -> end, State#state.plugins), %% and send to From last PEP events published by its contacts case catch ejabberd_c2s:get_subscribed(Pid) of - ContactsWithCaps when is_list(ContactsWithCaps) -> - Caps = proplists:get_value(LJID, ContactsWithCaps), - ContactsUsers = lists:usort(lists:map( - fun({{User, Server, _}, _}) -> {User, Server} end, ContactsWithCaps)), + Contacts when is_list(Contacts) -> lists:foreach( - fun({User, Server}) -> - PepKey = {User, Server, undefined}, + fun({User, Server, _}) -> + Owner = {User, Server, undefined}, lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, options = Options}) -> case get_option(Options, send_last_published_item) of on_sub_and_presence -> - case is_caps_notify(ServerHost, Node, Caps) of + case is_caps_notify(ServerHost, Node, LJID) of true -> Subscribed = case get_option(Options, access_model) of open -> true; @@ -499,8 +496,7 @@ handle_cast({presence, JID, Pid}, State) -> element(2, get_roster_info(User, Server, LJID, Grps)) end, if Subscribed -> - ?DEBUG("send ~s's ~s event to ~s",[exmpp_jid:jid_to_list(User, Server),Node,exmpp_jid:jid_to_list(JID)]), - send_last_item(PepKey, Node, LJID); + send_last_item(Owner, Node, LJID); true -> ok end; @@ -510,8 +506,8 @@ handle_cast({presence, JID, Pid}, State) -> _ -> ok end - end, tree_action(Host, get_nodes, [PepKey])) - end, ContactsUsers); + end, tree_action(Host, get_nodes, [Owner, JID])) + end, Contacts); _ -> ok end, @@ -786,7 +782,7 @@ iq_disco_items(Host, Item, From) -> Node = string_to_node(SNode), %% Note: Multiple Node Discovery not supported (mask on pubsub#type) %% TODO this code is also back-compatible with pubsub v1.8 (for client issue) - %% TODO make it pubsub v1.10 compliant (this breaks client compatibility) + %% TODO make it pubsub v1.12 compliant (breaks client compatibility ?) %% TODO That is, remove name attribute Action = fun(#pubsub_node{type = Type}) -> @@ -951,6 +947,10 @@ iq_pubsub(Host, ServerHost, From, IQType, SubEl, _Lang, Access, Plugins) -> get_subscriptions(Host, From, Plugins); {get, 'affiliations'} -> get_affiliations(Host, From, Plugins); + {get, "options"} -> + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")}; + {set, "options"} -> + {error, extended_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscription-options")}; _ -> {error, 'feature-not-implemented'} end; @@ -1024,7 +1024,7 @@ send_authorization_request(Host, Node, Subscriber) -> #xmlattr{name = 'type', value = "boolean"}, #xmlattr{name = 'label', value = translate:translate(Lang, "Allow this JID to subscribe to this pubsub node?")}], children = [#xmlel{ns = ?NS_DATA_FORMS, name = 'value', children = [#xmlcdata{cdata = <<"false">>}]}]}]}]}, - case tree_action(Host, get_node, [Host, Node]) of + case tree_action(Host, get_node, [Host, Node, Subscriber]) of #pubsub_node{owners = Owners} -> lists:foreach( fun({U1, S1, R1}) -> @@ -1053,19 +1053,37 @@ find_authorization_response(Packet) -> [] -> none; [XFields] when is_list(XFields) -> case lists:keysearch("FORM_TYPE", 1, XFields) of - {value, {_, ?NS_PUBSUB_SUBSCRIBE_AUTH_s}} -> + {value, {_, [?NS_PUBSUB_SUBSCRIBE_AUTH_s]}} -> XFields; _ -> invalid end end. +%% @spec (Host, JID, Node, Subscription) -> void +%% Host = mod_pubsub:host() +%% JID = jlib:jid() +%% Node = string() +%% Subscription = atom() +%% Plugins = [Plugin::string()] +%% @doc Send a message to JID with the supplied Subscription +send_authorization_approval(Host, JID, Node, Subscription) -> + Stanza = {xmlelement, "message", + [], + [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], + [{xmlelement, "subscription", + [{"node", Node}, + {"jid", jlib:jid_to_string(JID)}, + {"subscription", subscription_to_string(Subscription)}], + []}]}]}, + 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}}} -> + {{value, {_, [SNode]}}, {value, {_, [SSubscriber]}}, + {value, {_, [SAllow]}}} -> Node = case Host of {_, _, _} -> [SNode]; _ -> string:tokens(SNode, "/") @@ -1080,7 +1098,7 @@ handle_authorization_response(Host, From, To, Packet, XFields) -> %%options = Options, owners = Owners}) -> IsApprover = lists:member(jlib:short_prepd_bare_jid(From), Owners), - Subscription = node_call(Type, get_subscription, [Host, Node, Subscriber]), + {result, Subscription} = node_call(Type, get_subscription, [Host, Node, Subscriber]), if not IsApprover -> {error, 'forbidden'}; @@ -1091,6 +1109,7 @@ handle_authorization_response(Host, From, To, Packet, XFields) -> true -> subscribed; false -> none end, + send_authorization_approval(Host, Subscriber, SNode, NewSubscription), node_call(Type, set_subscription, [Host, Node, Subscriber, NewSubscription]) end end, @@ -1457,34 +1476,38 @@ 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}) -> - Features = features(Type), - PublishFeature = lists:member("publish", Features), - PublishModel = get_option(Options, publish_model), - MaxItems = max_items(Options), - PayloadSize = size(term_to_binary(Payload)), - PayloadMaxSize = get_option(Options, max_payload_size), - if - not PublishFeature -> - %% Node does not support item publication - {error, extended_error('feature-not-implemented', unsupported, "publish")}; - PayloadSize > PayloadMaxSize -> - %% Entity attempts to publish very large payload - {error, extended_error('not-acceptable', "payload-too-big")}; - %%?? -> iq_pubsub just does that matchs - %% % Entity attempts to publish item with multiple payload elements or namespace does not match - %% {error, extended_error('bad-request', "invalid-payload")}; - %% % Publisher attempts to publish to persistent node with no item - %% {error, extended_error('bad-request', "item-required")}; - Payload == "" -> - %% Publisher attempts to publish to payload node with no payload - {error, extended_error('bad-request', "payload-required")}; - %%?? -> - %% % Publisher attempts to publish to transient notification node with item - %% {error, extended_error('bad-request', "item-forbidden")}; - true -> - node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload]) - end - end, + Features = features(Type), + PublishFeature = lists:member("publish", Features), + PublishModel = get_option(Options, publish_model), + MaxItems = max_items(Options), + PayloadCount = payload_elements(xmlelement, Payload), + PayloadSize = size(term_to_binary(Payload)), + 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('feature-not-implemented', unsupported, "publish")}; + PayloadSize > PayloadMaxSize -> + %% Entity attempts to publish very large payload + {error, extended_error('not-acceptable', "payload-too-big")}; + PayloadCount > 1 -> + %% Entity attempts to publish item with multiple payload elements + {error, extended_error('bad-request', "invalid-payload")}; + Payload == "" -> + %% Publisher attempts to publish to payload node with no payload + {error, extended_error('bad-request', "payload-required")}; + (MaxItems == 0) and (PayloadSize > 0) -> + % Publisher attempts to publish to transient notification node with item + {error, extended_error('bad-request', "item-forbidden")}; + (MaxItems > 0) and (PayloadSize == 0) -> + % Publisher attempts to publish to persistent node with no item + {error, extended_error('bad-request', "item-required")}; + true -> + node_call(Type, publish_item, [Host, Node, Publisher, PublishModel, MaxItems, ItemId, Payload]) + end + end, ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, Payload]), Reply = [], case transaction(Host, Node, Action, sync_dirty) of @@ -1556,7 +1579,7 @@ delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> PersistentFeature = lists:member("persistent-items", Features), DeleteFeature = lists:member("delete-nodes", Features), if - %%?? -> iq_pubsub just does that matchs + %%-> iq_pubsub just does that matchs %% %% Request does not specify an item %% {error, extended_error('bad-request', "item-required")}; not PersistentFeature -> @@ -1735,7 +1758,7 @@ send_items(Host, Node, {LU, LS, LR} = LJID, Number) -> []; Items -> case Number of - last -> lists:sublist(lists:reverse(Items), 1); + last -> lists:last(Items); all -> Items; N when N > 0 -> lists:nthtail(length(Items)-N, Items); _ -> Items @@ -2098,6 +2121,15 @@ is_to_delivered({User, Server, _}, _, true) -> end, false, Ss) end. +%% @spec (Elem, Payload) -> int() +%% Elem = atom() +%% Payload = term() +%% @doc

Count occurence of given element in payload.

+payload_elements(Elem, Payload) -> payload_elements(Elem, Payload, 0). +payload_elements(_, [], Count) -> Count; +payload_elements(Elem, [Elem|Tail], Count) -> payload_elements(Elem, Tail, Count+1); +payload_elements(Elem, [_|Tail], Count) -> payload_elements(Elem, Tail, Count). + %%%%%% broadcast functions broadcast_publish_item(Host, Node, ItemId, _From, Payload) -> @@ -2324,58 +2356,52 @@ broadcast_config_notification(Host, Node, Lang) -> %% broadcast Stanza to all contacts of the user that are advertising %% interest in this kind of Node. broadcast_by_caps({LUser, LServer, LResource}, Node, _Type, Stanza) -> - ?DEBUG("looking for pid of ~p@~p/~p", [LUser, LServer, LResource]), - %% We need to know the resource, so we can ask for presence data. - SenderResource = case LResource of - undefined -> - %% If we don't know the resource, just pick one. - case ejabberd_sm:get_user_resources(LUser, LServer) of - [R|_] -> - R; - [] -> - undefined - end; - _ -> - LResource - end, - case SenderResource of - undefined -> - ?DEBUG("~p@~p is offline; can't deliver ~p to contacts", [LUser, LServer, Stanza]), - ok; - _ -> - case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of - C2SPid when is_pid(C2SPid) -> - %% set the from address on the notification to the bare JID of the account owner - %% Also, add "replyto" if entity has presence subscription to the account owner - %% See XEP-0163 1.1 section 4.3.1 - Sender = exmpp_jid:make_jid(LUser, LServer), - %%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used - case catch ejabberd_c2s:get_subscribed_and_online(C2SPid) of - ContactsWithCaps when is_list(ContactsWithCaps) -> - ?DEBUG("found contacts with caps: ~p", [ContactsWithCaps]), - lists:foreach( - fun({{U1, S1, R1}, Caps}) -> - case is_caps_notify(LServer, Node, Caps) of - true -> - To = exmpp_jid:make_jid(U1, S1, R1), - ejabberd_router ! {route, Sender, To, Stanza}; - false -> - ok + SenderResource = user_resource(LUser, LServer, LResource), + case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of + C2SPid when is_pid(C2SPid) -> + %% set the from address on the notification to the bare JID of the account owner + %% Also, add "replyto" if entity has presence subscription to the account owner + %% See XEP-0163 1.1 section 4.3.1 + Sender = exmpp_jid:make_jid(LUser, LServer), + %%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used + case catch ejabberd_c2s:get_subscribed(C2SPid) of + Contacts when is_list(Contacts) -> + Online = lists:foldl(fun({U, S, R}, Acc) -> + case user_resource(U, S, R) of + [] -> Acc; + OR -> [{U, S, OR}|Acc] end - end, ContactsWithCaps); - _ -> - ok - end, - ok; - _ -> - ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]), - ok - end + end, [], Contacts), + lists:foreach(fun({U, S, R}) -> + case is_caps_notify(LServer, Node, {U, S, R}) of + true -> + ejabberd_router ! {route, Sender, exmpp_jlib:make_jid(U, S, R), Stanza}; + false -> + ok + end + end, Online); + _ -> + ok + end, + ok; + _ -> + ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]), + ok end; broadcast_by_caps(_, _, _, _) -> ok. + +user_resource(LUser, LServer, []) -> + %% If we don't know the resource, just pick first if any + case ejabberd_sm:get_user_resources(LUser, LServer) of + [R|_] -> R; + [] -> [] + end; +user_resource(_, _, LResource) -> + LResource. -is_caps_notify(Host, Node, Caps) -> +is_caps_notify(Host, Node, LJID) -> + Caps = mod_caps:get_caps(LJID), case catch mod_caps:get_features(Host, Caps) of Features when is_list(Features) -> lists:member(Node ++ "+notify", Features); _ -> false @@ -2408,7 +2434,7 @@ get_configure(Host, Node, From, Lang) -> transaction(Host, Node, Action, sync_dirty). get_default(Host, Node, _From, Lang) -> - Type=select_type(Host, Host, Node), + Type = select_type(Host, Host, Node), Options = node_options(Type), {result, [#xmlel{ns = ?NS_PUBSUB_OWNER, name = 'pubsub', children = [#xmlel{ns = ?NS_PUBSUB_OWNER, name = 'default', children = @@ -2647,7 +2673,8 @@ set_xoption([{"pubsub#type", Value} | Opts], NewOpts) -> set_xoption([{"pubsub#body_xslt", Value} | Opts], NewOpts) -> ?SET_STRING_XOPT(body_xslt, Value); set_xoption([_ | _Opts], _NewOpts) -> - {error, 'not-acceptable'}. + % skip unknown field + set_xoption(Opts, NewOpts). %%%% plugin handling @@ -2657,18 +2684,18 @@ plugins(Host) -> _ -> [?STDNODE] end. select_type(ServerHost, Host, Node, Type)-> - ?DEBUG("SELECT_TYPE : ~p~n", [Node]), - case Host of - {_User, _Server, _Resource} -> - case ets:lookup(gen_mod:get_module_proc(ServerHost, pubsub_state), pep_mapping) of - [{pep_mapping, PM}] -> ?DEBUG("SELECT_TYPE : ~p~n", [PM]), proplists:get_value(Node, PM,?PEPNODE); - R -> ?DEBUG("SELECT_TYPE why ?: ~p~n", [R]), ?PEPNODE - end; - _ -> - Type + ?DEBUG("SELECT_TYPE : ~p~n", [[ServerHost, Host, Node, Type]]), + case Host of + {_User, _Server, _Resource} -> + case ets:lookup(gen_mod:get_module_proc(ServerHost, pubsub_state), pep_mapping) of + [{pep_mapping, PM}] -> ?DEBUG("SELECT_TYPE : ~p~n", [PM]), proplists:get_value(Node, PM, ?PEPNODE); + R -> ?DEBUG("SELECT_TYPE why ?: ~p~n", [R]), ?PEPNODE + end; + _ -> + Type end. select_type(ServerHost, Host, Node) -> - select_type(ServerHost, Host, Node,hd(plugins(ServerHost))). + select_type(ServerHost, Host, Node, hd(plugins(ServerHost))). features() -> [ diff --git a/src/mod_pubsub/node_default.erl b/src/mod_pubsub/node_default.erl index 15d871300..8757ac4a4 100644 --- a/src/mod_pubsub/node_default.erl +++ b/src/mod_pubsub/node_default.erl @@ -382,6 +382,10 @@ unsubscribe_node(Host, Node, Sender, Subscriber, _SubId) -> %% 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} diff --git a/src/mod_pubsub/node_zoo.erl b/src/mod_pubsub/node_flat.erl similarity index 97% rename from src/mod_pubsub/node_zoo.erl rename to src/mod_pubsub/node_flat.erl index 0fc6fb797..6996912b0 100644 --- a/src/mod_pubsub/node_zoo.erl +++ b/src/mod_pubsub/node_flat.erl @@ -22,7 +22,7 @@ %%% @end %%% ==================================================================== --module(node_zoo). +-module(node_flat). -author('christophe.romain@process-one.net'). -include_lib("exmpp/include/exmpp.hrl"). @@ -70,7 +70,7 @@ terminate(Host, ServerHost) -> node_default:terminate(Host, ServerHost). options() -> - [{node_type, zoo}, + [{node_type, flat}, {deliver_payloads, true}, {notify_config, false}, {notify_delete, false}, @@ -99,12 +99,7 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> true; % pubsub service always allowed _ -> {LU, LS, LR} = LOwner, - case acl:match_rule(ServerHost, Access, exmpp_jid:make_jid(LU, LS, LR)) of - allow -> - true; - _ -> - false - end + acl:match_rule(ServerHost, Access, exmpp_jid:make_jid(LU, LS, LR)) =:= allow end, {result, Allowed}. diff --git a/src/mod_pubsub/nodetree_default.erl b/src/mod_pubsub/nodetree_default.erl index 0c3ce3082..126ea7f75 100644 --- a/src/mod_pubsub/nodetree_default.erl +++ b/src/mod_pubsub/nodetree_default.erl @@ -46,7 +46,9 @@ terminate/2, options/0, set_node/1, + get_node/3, get_node/2, + get_nodes/2, get_nodes/1, get_subnodes/3, get_subnodes_tree/2, @@ -98,6 +100,9 @@ set_node(_) -> %% @spec (Host, Node) -> pubsubNode() | {error, Reason} %% Host = mod_pubsub:host() %% Node = mod_pubsub:pubsubNode() +get_node(Host, Node, _From) -> + get_node(Host, Node). + get_node(Host, Node) -> case catch mnesia:read({pubsub_node, {Host, Node}}) of [Record] when is_record(Record, pubsub_node) -> Record; @@ -107,6 +112,9 @@ get_node(Host, Node) -> %% @spec (Key) -> [pubsubNode()] | {error, Reason} %% Key = mod_pubsub:host() | mod_pubsub:jid() +get_nodes(Key, _From) -> + get_nodes(Key). + get_nodes(Key) -> mnesia:match_object(#pubsub_node{nodeid = {Key, '_'}, _ = '_'}). diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl index 80a6e745e..2df61e70a 100644 --- a/src/mod_pubsub/nodetree_virtual.erl +++ b/src/mod_pubsub/nodetree_virtual.erl @@ -44,7 +44,9 @@ terminate/2, options/0, set_node/1, + get_node/3, get_node/2, + get_nodes/2, get_nodes/1, get_subnodes/3, get_subnodes_tree/2, @@ -87,6 +89,9 @@ set_node(_NodeRecord) -> %% Node = mod_pubsub:pubsubNode() %% @doc

Virtual node tree does not handle a node database. Any node is considered %% as existing. Node record contains default values.

+get_node(Host, Node, _From) -> + get_node(Host, Node). + get_node(Host, Node) -> #pubsub_node{nodeid = {Host, Node}}. @@ -94,6 +99,9 @@ get_node(Host, Node) -> %% Host = mod_pubsub:host() | mod_pubsub:jid() %% @doc

Virtual node tree does not handle a node database. Any node is considered %% as existing. Nodes list can not be determined.

+get_nodes(Key, _From) -> + get_nodes(Key). + get_nodes(_Key) -> []. diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl index f763909a7..b4ceed4fa 100644 --- a/src/mod_pubsub/pubsub.hrl +++ b/src/mod_pubsub/pubsub.hrl @@ -119,13 +119,3 @@ payload = [] }). - -%% @type pubsubPresence() = #pubsub_presence{ -%% key = {Host::host(), User::string(), Server::string()}, -%% presence = list()}. -%%%

This is the format of the published presence table. The type of the -%%% table is: set,ram.

--record(pubsub_presence, {key, - resource - }). -