25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-12 15:57:31 +01:00
xmpp.chapril.org-ejabberd/src/mod_pubsub/mod_pubsub.erl

1245 lines
32 KiB
Erlang
Raw Normal View History

%%%----------------------------------------------------------------------
%%% File : mod_pubsub.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Pub/sub support (JEP-0060)
%%% Created : 4 Jul 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(mod_pubsub).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_mod).
-export([start/1,
init/3,
loop/2,
stop/0,
system_continue/3,
system_terminate/4,
system_code_change/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(DICT, dict).
-define(MAXITEMS, 10).
-record(pubsub_node, {host_node, host_parent, info}).
-record(nodeinfo, {items = [],
options = [],
entities = ?DICT:new()
}).
-record(entity, {affiliation = none,
subscription = none}).
-record(item, {id, publisher, payload}).
start(Opts) ->
mnesia:create_table(pubsub_node,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, pubsub_node)}]),
Hosts = gen_mod:get_hosts(Opts, "pubsub."),
Host = hd(Hosts),
update_table(Host),
mnesia:add_table_index(pubsub_node, host_parent),
ServedHosts = gen_mod:get_opt(served_hosts, Opts, []),
register(ejabberd_mod_pubsub,
proc_lib:spawn_link(?MODULE, init, [Hosts, ServedHosts, self()])).
-define(MYJID, #jid{user = "", server = Host, resource = "",
luser = "", lserver = Host, lresource = ""}).
init(Hosts, ServedHosts, Parent) ->
ejabberd_router:register_routes(Hosts),
lists:foreach(
fun(Host) ->
create_new_node(Host, ["pubsub"], ?MYJID),
create_new_node(Host, ["pubsub", "nodes"], ?MYJID),
create_new_node(Host, ["home"], ?MYJID),
create_new_node(Host, ["home", find_my_host(Host)], ?MYJID),
lists:foreach(fun(H) ->
create_new_node(Host, ["home", H], ?MYJID)
end, ServedHosts)
end, Hosts),
loop(Hosts, Parent).
loop(Hosts, Parent) ->
receive
{route, From, To, Packet} ->
case catch do_route(To#jid.lserver, From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end,
loop(Hosts, Parent);
{room_destroyed, Room} ->
ets:delete(muc_online_room, Room),
loop(Hosts, Parent);
stop ->
ejabberd_router:unregister_global_routes(Hosts),
ok;
reload ->
?MODULE:loop(Hosts, Parent);
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], Hosts);
_ ->
loop(Hosts, Parent)
end.
do_route(Host, From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = 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 = XMLNS,
sub_el = SubEl} = IQ ->
{xmlelement, _, QAttrs, _} = SubEl,
Node = xml:get_attr_s("node", QAttrs),
Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query",
QAttrs,
iq_disco_info(Node)}]},
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res));
#iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS,
sub_el = SubEl} = IQ ->
{xmlelement, _, QAttrs, _} = SubEl,
Node = xml:get_attr_s("node", QAttrs),
Res =
case iq_disco_items(Host, From, Node) 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 = get, xmlns = ?NS_REGISTER = XMLNS,
% lang = Lang, sub_el = SubEl} = IQ ->
% Res = IQ#iq{type = result,
% sub_el = [{xmlelement, "query",
% [{"xmlns", XMLNS}],
% iq_get_register_info(
% From, Lang)}]},
% ejabberd_router:route(To,
% From,
% jlib:iq_to_xml(Res));
%#iq{type = set, xmlns = ?NS_REGISTER = XMLNS,
% sub_el = SubEl} = IQ ->
% case process_iq_register_set(From, SubEl) of
% {result, IQRes} ->
% Res = IQ#iq{type = result,
% sub_el = [{xmlelement, "query",
% [{"xmlns", XMLNS}],
% IQRes}]},
% ejabberd_router:route(
% To, From, jlib:iq_to_xml(Res));
% {error, Error} ->
% Err = jlib:make_error_reply(
% Packet, Error),
% ejabberd_router:route(
% To, From, Err)
% end;
#iq{type = Type, xmlns = ?NS_PUBSUB = XMLNS,
sub_el = SubEl} = IQ ->
Res =
case iq_pubsub(Host, From, Type, SubEl) 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{} ->
Err = jlib:make_error_reply(
Packet,
?ERR_FEATURE_NOT_IMPLEMENTED),
ejabberd_router:route(To, From, Err);
_ ->
ok
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.
stop() ->
ejabberd_mod_pubsub ! stop,
ok.
node_to_string(Node) ->
string:strip(lists:flatten(lists:map(fun(S) -> [S, "/"] end, Node)),
right, $/).
iq_disco_info(SNode) ->
Node = string:tokens(SNode, "/"),
case Node of
[] ->
[{xmlelement, "identity",
[{"category", "pubsub"},
{"type", "generic"},
{"name", "ejabberd/mod_pubsub"}], []},
%{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB_EVENT}], []},
{xmlelement, "feature", [{"var", ?NS_PUBSUB_OWNER}], []},
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}];
_ ->
% TODO
[]
end.
iq_disco_items(Host, From, SNode) ->
Node = string:tokens(SNode, "/"),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info}] ->
SubNodes = mnesia:index_read(pubsub_node,
{Host, Node},
#pubsub_node.host_parent),
SubItems =
lists:map(fun(#pubsub_node{host_node = {_, N}}) ->
SN = node_to_string(N),
{xmlelement, "item",
[{"jid", Host},
{"node", SN},
{"name", lists:last(N)}], []}
end, SubNodes),
SN = node_to_string(Node),
Items =
lists:map(fun(#item{id = Name}) ->
{xmlelement, "item",
[{"jid", Host},
{"node", SN ++ "!" ++ Name},
{"name", Name}], []}
end, Info#nodeinfo.items),
SubItems ++ Items;
[] ->
case Node of
[] ->
SubNodes = mnesia:index_read(
pubsub_node,
{Host, Node},
#pubsub_node.host_parent),
lists:map(
fun(#pubsub_node{host_node = {_, N}}) ->
SN = node_to_string(N),
{xmlelement, "item",
[{"jid", Host},
{"node", SN},
{"name", lists:last(N)}],
[]}
end, SubNodes) ;
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, Res} ->
{result, Res};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
% TODO
%-define(XFIELD(Type, Label, Var, Val),
% {xmlelement, "field", [{"type", Type},
% {"label", translate:translate(Lang, Label)},
% {"var", Var}],
% [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
%
%iq_get_register_info(From, Lang) ->
% {LUser, LServer, _} = jlib:jid_tolower(From),
% LUS = {LUser, LServer},
% Nick = case catch mnesia:dirty_read(muc_registered, LUS) of
% {'EXIT', Reason} ->
% "";
% [] ->
% "";
% [#muc_registered{nick = N}] ->
% N
% end,
% [{xmlelement, "instructions", [],
% [{xmlcdata, translate:translate(
% Lang, "You need a x:data capable client to register.")}]},
% {xmlelement, "x",
% [{"xmlns", ?NS_XDATA}],
% [{xmlelement, "title", [],
% [{xmlcdata,
% translate:translate(
% Lang, "Nick Registration")}]},
% {xmlelement, "instructions", [],
% [{xmlcdata,
% translate:translate(
% Lang, "Enter nick you want to register.")}]},
% ?XFIELD("text-single", "Nick", "nick", Nick)]}].
%
%iq_set_register_info(From, XData) ->
% {LUser, LServer, _} = jlib:jid_tolower(From),
% LUS = {LUser, LServer},
% case lists:keysearch("nick", 1, XData) of
% false ->
% {error, ?ERR_BAD_REQUEST};
% {value, {_, [Nick]}} ->
% F = fun() ->
% case Nick of
% "" ->
% mnesia:delete({muc_registered, LUS}),
% ok;
% _ ->
% Allow = case mnesia:index_read(
% muc_registered,
% Nick,
% #muc_registered.nick) of
% [] ->
% true;
% [#muc_registered{user = U}] ->
% U == LUS
% end,
% if
% Allow ->
% mnesia:write(
% #muc_registered{user = LUS,
% nick = Nick}),
% ok;
% true ->
% false
% end
% end
% end,
% case mnesia:transaction(F) of
% {atomic, ok} ->
% {result, []};
% {atomic, false} ->
% {error, ?ERR_NOT_ALLOWED};
% _ ->
% {error, ?ERR_INTERNAL_SERVER_ERROR}
% end
% end.
%
%process_iq_register_set(From, SubEl) ->
% {xmlelement, Name, Attrs, Els} = SubEl,
% 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"} ->
% XData = jlib:parse_xdata_submit(XEl),
% case XData of
% invalid ->
% {error, ?ERR_BAD_REQUEST};
% _ ->
% iq_set_register_info(From, XData)
% end;
% _ ->
% {error, ?ERR_BAD_REQUEST}
% end;
% _ ->
% {error, ?ERR_BAD_REQUEST}
% end.
iq_get_vcard(Lang) ->
[{xmlelement, "FN", [],
[{xmlcdata, "ejabberd/mod_pubsub"}]},
{xmlelement, "URL", [],
[{xmlcdata,
"http://ejabberd.jabberstudio.org/"}]},
{xmlelement, "DESC", [],
[{xmlcdata, translate:translate(
Lang,
"ejabberd pub/sub module\n"
"Copyright (c) 2003-2005 Alexey Shchepin")}]}].
iq_pubsub(Host, From, Type, SubEl) ->
{xmlelement, _, _, SubEls} = SubEl,
case xml:remove_cdata(SubEls) of
[{xmlelement, Name, Attrs, Els}] ->
SNode = xml:get_attr_s("node", Attrs),
Node = string:tokens(SNode, "/"),
case {Type, Name} of
{set, "create"} ->
create_new_node(Host, Node, From);
{set, "publish"} ->
case xml:remove_cdata(Els) of
[{xmlelement, "item", ItemAttrs, Payload}] ->
ItemID = xml:get_attr_s("id", ItemAttrs),
publish_item(Host, From, Node, ItemID, Payload);
_ ->
{error, ?ERR_BAD_REQUEST}
end;
{set, "retract"} ->
case xml:remove_cdata(Els) of
[{xmlelement, "item", ItemAttrs, _}] ->
ItemID = xml:get_attr_s("id", ItemAttrs),
delete_item(Host, From, Node, ItemID);
_ ->
{error, ?ERR_BAD_REQUEST}
end;
{set, "subscribe"} ->
JID = xml:get_attr_s("jid", Attrs),
subscribe_node(Host, From, JID, Node);
{set, "unsubscribe"} ->
JID = xml:get_attr_s("jid", Attrs),
unsubscribe_node(Host, From, JID, Node);
{get, "items"} ->
MaxItems = xml:get_attr_s("max_items", Attrs),
get_items(Host, From, Node, MaxItems);
{set, "delete"} ->
delete_node(Host, From, Node);
{set, "purge"} ->
purge_node(Host, From, Node);
{get, "entities"} ->
get_entities(Host, From, Node);
{set, "entities"} ->
set_entities(Host, From, Node, xml:remove_cdata(Els));
%{get, "configure"} ->
% get_node_config(From, Node);
_ ->
{error, ?ERR_FEATURE_NOT_IMPLEMENTED}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
end.
-define(XFIELD(Type, Label, Var, Val),
{xmlelement, "field", [{"type", Type},
{"label", translate:translate(Lang, Label)},
{"var", Var}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
%% Create new pubsub nodes
%% This function is used during init to create the first bootstrap nodes
create_new_node(Host, Node, Owner) ->
case Node of
[] ->
{LOU, LOS, _} = jlib:jid_tolower(Owner),
HomeNode = ["home", LOS, LOU],
create_new_node(Host, HomeNode, Owner),
NewNode = ["home", LOS, LOU, randoms:get_string()],
create_new_node(Host, NewNode, Owner);
_ ->
LOwner = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
Parent = lists:sublist(Node, length(Node) - 1),
F = fun() ->
ParentExists = (Parent == []) orelse
case mnesia:read({pubsub_node, {Host, Parent}}) of
[_] ->
true;
[] ->
false
end,
case ParentExists of
false ->
{error, ?ERR_CONFLICT};
_ ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[_] ->
{error, ?ERR_CONFLICT};
[] ->
Entities =
?DICT:store(
LOwner,
#entity{affiliation = owner,
subscription = none},
?DICT:new()),
mnesia:write(
#pubsub_node{host_node = {Host, Node},
host_parent = {Host, Parent},
info = #nodeinfo{
entities = Entities}}),
ok
end
end
end,
case check_create_permission(Host, Node, Owner) of
true ->
case mnesia:transaction(F) of
{atomic, ok} ->
Lang = "",
broadcast_publish_item(
Host, ["pubsub", "nodes"], node_to_string(Node),
[{xmlelement, "x",
[{"xmlns", ?NS_XDATA},
{"type", "result"}],
[?XFIELD("hidden", "", "FORM_TYPE",
?NS_PUBSUB_NMI),
?XFIELD("jid-single", "Node Creator",
"creator",
jlib:jid_to_string(LOwner))]}]),
{result,
[{xmlelement, "pubsub",
[{"xmlns", ?NS_PUBSUB}],
[{xmlelement, "create",
[{"node", node_to_string(Node)}], []}]}]};
{atomic, {error, _} = Error} ->
Error;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
_ ->
{error, ?ERR_NOT_ALLOWED}
end
end.
publish_item(Host, JID, Node, ItemID, Payload) ->
Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
Affiliation = get_affiliation(Info, Publisher),
if
(Affiliation == owner) or
(Affiliation == publisher) ->
NewInfo =
insert_item(Info, ItemID,
Publisher, Payload),
mnesia:write(
N#pubsub_node{info = NewInfo}),
{result, []};
true ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, Res}} ->
broadcast_publish_item(Host, Node, ItemID, Payload),
{result, Res};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
delete_item(Host, JID, Node, ItemID) ->
Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
case check_item_publisher(Info, ItemID, Publisher)
orelse
(get_affiliation(Info, Publisher) == owner) of
true ->
NewInfo =
remove_item(Info, ItemID),
mnesia:write(
N#pubsub_node{info = NewInfo}),
{result, []};
_ ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, Res}} ->
broadcast_retract_item(Host, Node, ItemID),
{result, Res};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
subscribe_node(Host, From, JID, Node) ->
Sender = jlib:jid_tolower(jlib:jid_remove_resource(From)),
SubscriberJID =
case jlib:string_to_jid(JID) of
error ->
{"", "", ""};
J ->
J
end,
Subscriber = jlib:jid_tolower(SubscriberJID),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
Affiliation = get_affiliation(Info, Subscriber),
if
Affiliation /= outcast ->
NewInfo =
add_subscriber(Info, Subscriber),
mnesia:write(
N#pubsub_node{info = NewInfo}),
{result, []};
true ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
if
Sender == Subscriber ->
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, Res}} ->
{result, Res};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
true ->
{error, ?ERR_NOT_ALLOWED}
end.
unsubscribe_node(Host, From, JID, Node) ->
Sender = jlib:jid_tolower(jlib:jid_remove_resource(From)),
SubscriberJID =
case jlib:string_to_jid(JID) of
error ->
{"", "", ""};
J ->
J
end,
Subscriber = jlib:jid_tolower(SubscriberJID),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
Subscription = get_subscription(Info, Subscriber),
if
Subscription /= none ->
NewInfo =
remove_subscriber(Info, Subscriber),
mnesia:write(
N#pubsub_node{info = NewInfo}),
{result, []};
true ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
if
Sender == Subscriber ->
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, Res}} ->
{result, Res};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
true ->
{error, ?ERR_NOT_ALLOWED}
end.
get_items(Host, JID, Node, SMaxItems) ->
MaxItems =
if
SMaxItems == "" ->
?MAXITEMS;
true ->
case catch list_to_integer(SMaxItems) of
{'EXIT', _} ->
{error, ?ERR_BAD_REQUEST};
Val ->
Val
end
end,
case MaxItems of
{error, _} = Error ->
Error;
_ ->
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
Items = lists:sublist(Info#nodeinfo.items, MaxItems),
ItemsEls =
lists:map(
fun(#item{id = ItemID,
payload = Payload}) ->
ItemAttrs = case ItemID of
"" -> [];
_ -> [{"id", ItemID}]
end,
{xmlelement, "item", ItemAttrs, Payload}
end, Items),
{result, [{xmlelement, "pubsub",
[{"xmlns", ?NS_PUBSUB_EVENT}],
[{xmlelement, "items",
[{"node", node_to_string(Node)}],
ItemsEls}]}]};
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end.
delete_node(Host, JID, Node) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info}] ->
case get_affiliation(Info, Owner) of
owner ->
% TODO: don't iterate over entire table
Removed =
mnesia:foldl(
fun(#pubsub_node{host_node = {_, N},
info = #nodeinfo{
entities = Entities
}}, Acc) ->
case lists:prefix(Node, N) of
true ->
[{N, Entities} | Acc];
_ ->
Acc
end
end, [], pubsub_node),
lists:foreach(
fun({N, _}) ->
mnesia:delete({pubsub_node, {Host, N}})
end, Removed),
{removed, Removed};
_ ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {removed, Removed}} ->
broadcast_removed_node(Host, Removed),
Lang = "",
broadcast_retract_item(
Host, ["pubsub", "nodes"], node_to_string(Node)),
{result, []};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
purge_node(Host, JID, Node) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
case get_affiliation(Info, Owner) of
owner ->
NewInfo = Info#nodeinfo{items = []},
mnesia:write(
N#pubsub_node{info = NewInfo}),
{result, Info#nodeinfo.items, []};
_ ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, Items, Res}} ->
lists:foreach(
fun(#item{id = ItemID}) ->
broadcast_retract_item(Host, Node, ItemID)
end, Items),
{result, Res};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end.
get_entities(Host, OJID, Node) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
case get_affiliation(Info, Owner) of
owner ->
Entities = Info#nodeinfo.entities,
EntitiesEls =
?DICT:fold(
fun(JID,
#entity{affiliation = Affiliation,
subscription = Subscription},
Acc) ->
[{xmlelement, "entity",
[{"jid", jlib:jid_to_string(JID)},
{"affiliation",
affiliation_to_string(Affiliation)},
{"subscription",
subscription_to_string(Subscription)}],
[]} | Acc]
end, [], Entities),
{result, [{xmlelement, "pubsub",
[{"xmlns", ?NS_PUBSUB_EVENT}],
[{xmlelement, "entities",
[{"node", node_to_string(Node)}],
EntitiesEls}]}]};
_ ->
{error, ?ERR_NOT_ALLOWED}
end;
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
end.
set_entities(Host, OJID, Node, EntitiesEls) ->
Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
Entities =
lists:foldl(
fun(El, Acc) ->
case Acc of
error ->
error;
_ ->
case El of
{xmlelement, "entity", Attrs, _} ->
JID = jlib:string_to_jid(
xml:get_attr_s("jid", Attrs)),
Affiliation =
case xml:get_attr_s("affiliation",
Attrs) of
"owner" -> owner;
"publisher" -> publisher;
"outcast" -> outcast;
"none" -> none;
_ -> false
end,
Subscription =
case xml:get_attr_s("subscription",
Attrs) of
"subscribed" -> subscribed;
"pending" -> pending;
"unconfigured" -> unconfigured;
"none" -> none;
_ -> false
end,
if
(JID == error) or
(Affiliation == false) or
(Subscription == false) ->
error;
true ->
[{jlib:jid_tolower(JID),
#entity{
affiliation = Affiliation,
subscription = Subscription}} |
Acc]
end
end
end
end, [], EntitiesEls),
case Entities of
error ->
{error, ?ERR_BAD_REQUEST};
_ ->
F = fun() ->
case mnesia:read({pubsub_node, {Host, Node}}) of
[#pubsub_node{info = Info} = N] ->
case get_affiliation(Info, Owner) of
owner ->
NewInfo =
set_info_entities(Info, Entities),
mnesia:write(
N#pubsub_node{info = NewInfo}),
{result, []};
_ ->
{error, ?ERR_NOT_ALLOWED}
end;
[] ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _}} ->
{result, []};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end.
%get_node_config(OJID, Node) ->
% Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
% case catch mnesia:dirty_read(pubsub_node, Node) of
% [#pubsub_node{info = Info}] ->
% case get_affiliation(Info, Owner) of
% owner ->
% Entities = Info#nodeinfo.entities,
% EntitiesEls =
% ?DICT:fold(
% fun(JID,
% #entity{affiliation = Affiliation,
% subscription = Subscription},
% Acc) ->
% [{xmlelement, "entity",
% [{"jid", jlib:jid_to_string(JID)},
% {"affiliation",
% affiliation_to_string(Affiliation)},
% {"subscription",
% subscription_to_string(Subscription)}],
% []} | Acc]
% end, [], Entities),
% {result, [{xmlelement, "pubsub",
% [{"xmlns", ?NS_PUBSUB_EVENT}],
% [{xmlelement, "entities",
% [{"node", node_to_string(Node)}],
% EntitiesEls}]}]};
% _ ->
% {error, ?ERR_NOT_ALLOWED}
% end;
% _ ->
% {error, ?ERR_ITEM_NOT_FOUND}
% end.
get_affiliation(#nodeinfo{entities = Entities}, JID) ->
LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
case ?DICT:find(LJID, Entities) of
{ok, #entity{affiliation = Affiliation}} ->
Affiliation;
_ ->
none
end.
get_subscription(#nodeinfo{entities = Entities}, JID) ->
LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
case ?DICT:find(LJID, Entities) of
{ok, #entity{subscription = Subscription}} ->
Subscription;
_ ->
none
end.
affiliation_to_string(Affiliation) ->
case Affiliation of
owner -> "owner";
publisher -> "publisher";
outcast -> "outcast";
_ -> "none"
end.
subscription_to_string(Subscription) ->
case Subscription of
subscribed -> "subscribed";
pending -> "pending";
unconfigured -> "unconfigured";
_ -> "none"
end.
check_create_permission(Host, Node, Owner) ->
if
Owner#jid.lserver == Host ->
true;
true ->
#jid{luser = User, lserver = Server} = Owner,
case Node of
["home", Server, User | _] ->
true;
_ ->
false
end
end.
insert_item(Info, ItemID, Publisher, Payload) ->
Items = Info#nodeinfo.items,
Items1 = lists:filter(fun(I) ->
I#item.id /= ItemID
end, Items),
Items2 = [#item{id = ItemID, publisher = Publisher, payload = Payload} |
Items1],
Items3 = lists:sublist(Items2, ?MAXITEMS),
Info#nodeinfo{items = Items3}.
remove_item(Info, ItemID) ->
Items = Info#nodeinfo.items,
Items1 = lists:filter(fun(I) ->
I#item.id /= ItemID
end, Items),
Info#nodeinfo{items = Items1}.
check_item_publisher(Info, ItemID, Publisher) ->
Items = Info#nodeinfo.items,
case lists:keysearch(ItemID, #item.id, Items) of
{value, #item{publisher = Publisher}} ->
true;
_ ->
false
end.
add_subscriber(Info, Subscriber) ->
Entities = Info#nodeinfo.entities,
case ?DICT:find(Subscriber, Entities) of
{ok, Entity} ->
Info#nodeinfo{
entities = ?DICT:store(Subscriber,
Entity#entity{subscription = subscribed},
Entities)};
_ ->
Info#nodeinfo{
entities = ?DICT:store(Subscriber,
#entity{subscription = subscribed},
Entities)}
end.
remove_subscriber(Info, Subscriber) ->
Entities = Info#nodeinfo.entities,
case ?DICT:find(Subscriber, Entities) of
{ok, #entity{affiliation = none}} ->
Info#nodeinfo{
entities = ?DICT:erase(Subscriber, Entities)};
{ok, Entity} ->
Info#nodeinfo{
entities = ?DICT:store(Subscriber,
Entity#entity{subscription = none},
Entities)};
_ ->
Info
end.
set_info_entities(Info, Entities) ->
NewEntities =
lists:foldl(
fun({JID, Ent}, Es) ->
case Ent of
#entity{affiliation = none, subscription = none} ->
?DICT:erase(JID, Es);
_ ->
?DICT:store(JID, Ent, Es)
end
end, Info#nodeinfo.entities, Entities),
Info#nodeinfo{entities = NewEntities}.
broadcast_publish_item(Host, Node, ItemID, Payload) ->
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
?DICT:fold(
fun(JID, #entity{subscription = Subscription}, _) ->
if
(Subscription /= none) and
(Subscription /= pending) ->
ItemAttrs = case ItemID of
"" -> [];
_ -> [{"id", ItemID}]
end,
Stanza =
{xmlelement, "message", [],
[{xmlelement, "x",
[{"xmlns", ?NS_PUBSUB_EVENT}],
[{xmlelement, "items",
[{"node", node_to_string(Node)}],
[{xmlelement, "item",
ItemAttrs,
Payload}]}]}]},
ejabberd_router:route(
?MYJID, jlib:make_jid(JID), Stanza);
true ->
ok
end
end, ok, Info#nodeinfo.entities);
_ ->
false
end.
broadcast_retract_item(Host, Node, ItemID) ->
case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
[#pubsub_node{info = Info}] ->
?DICT:fold(
fun(JID, #entity{subscription = Subscription}, _) ->
if
(Subscription /= none) and
(Subscription /= pending) ->
ItemAttrs = case ItemID of
"" -> [];
_ -> [{"id", ItemID}]
end,
Stanza =
{xmlelement, "message", [],
[{xmlelement, "x",
[{"xmlns", ?NS_PUBSUB_EVENT}],
[{xmlelement, "items",
[{"node", node_to_string(Node)}],
[{xmlelement, "retract",
ItemAttrs, []}]}]}]},
ejabberd_router:route(
?MYJID, jlib:make_jid(JID), Stanza);
true ->
ok
end
end, ok, Info#nodeinfo.entities);
_ ->
false
end.
broadcast_removed_node(Host, Removed) ->
lists:foreach(
fun({Node, Entities}) ->
?DICT:fold(
fun(JID, #entity{subscription = Subscription}, _) ->
if
(Subscription /= none) and
(Subscription /= pending) ->
Stanza =
{xmlelement, "message", [],
[{xmlelement, "x",
[{"xmlns", ?NS_PUBSUB_EVENT}],
[{xmlelement, "delete",
[{"node", node_to_string(Node)}],
[]}]}]},
ejabberd_router:route(
?MYJID, jlib:make_jid(JID), Stanza);
true ->
ok
end
end, ok, Entities)
end, Removed).
system_continue(Parent, _, State) ->
loop(State, Parent).
system_terminate(Reason, Parent, _, State) ->
exit(Reason).
system_code_change(State, _Mod, Ver, _Extra) ->
{ok, State}.
find_my_host(LServer) ->
Parts = string:tokens(LServer, "."),
find_my_host(Parts, ?MYHOSTS).
find_my_host([], _Hosts) ->
?MYNAME;
find_my_host([_ | Tail] = Parts, Hosts) ->
Domain = parts_to_string(Parts),
case lists:member(Domain, Hosts) of
true ->
Domain;
false ->
find_my_host(Tail, Hosts)
end.
parts_to_string(Parts) ->
string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
right, $.).
update_table(Host) ->
Fields = record_info(fields, pubsub_node),
case mnesia:table_info(pubsub_node, attributes) of
Fields ->
ok;
[node, parent, info] ->
?INFO_MSG("Converting pubsub_node table from "
"{node, parent, info} format", []),
{atomic, ok} = mnesia:create_table(
mod_pubsub_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, pubsub_node},
{attributes, record_info(fields, pubsub_node)}]),
mnesia:del_table_index(pubsub_node, parent),
mnesia:transform_table(pubsub_node, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_pubsub_tmp_table),
mnesia:foldl(
fun(#pubsub_node{host_node = N,
host_parent = P} = R, _) ->
mnesia:dirty_write(
mod_pubsub_tmp_table,
R#pubsub_node{host_node = {Host, N},
host_parent = {Host, P}})
end, ok, pubsub_node)
end,
mnesia:transaction(F1),
mnesia:clear_table(pubsub_node),
F2 = fun() ->
mnesia:write_lock_table(pubsub_node),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_pubsub_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_pubsub_tmp_table);
_ ->
?INFO_MSG("Recreating pubsub_node table", []),
mnesia:transform_table(pubsub_node, ignore, Fields)
end.