Current section

Files

Jump to
escalus src escalus_pubsub_stanza.erl
Raw

src/escalus_pubsub_stanza.erl

%%%===================================================================
%%% @copyright (C) 2016, Erlang Solutions Ltd.
%%% @doc Stanzas for testing XEP-0060 PubSub
%%% @end
%%%===================================================================
-module(escalus_pubsub_stanza).
-include("escalus_xmlns.hrl").
-include_lib("exml/include/exml.hrl").
-export([
discover_nodes/3,
create_node/3, create_node/4, delete_node/3,
get_configuration/3, set_configuration/4,
get_default_configuration/3,
get_affiliations/3, set_affiliations/4,
subscribe/3, subscribe/4, unsubscribe/3,
submit_subscription_response/4,
get_pending_subscriptions/3,
get_user_subscriptions/3,
get_subscription_options/3,
set_subscription_options/4,
get_node_subscriptions/3,
set_subscriptions/4,
pubsub_owner_iq/5,
pubsub_iq/5,
iq/5,
publish/3, publish/4, publish/5,
publish_with_options/4, publish_with_options/5, publish_with_options/6,
retract/4,
get_items/4,
get_all_items/3,
get_item/4,
purge_all_items/3
]).
-type pubsub_node_id() :: {pep | binary(), binary()}.
-export_type([pubsub_node_id/0]).
-type publish_options() :: [{binary(), binary()}].
-type form_field() :: {Var :: binary(), Value :: binary()}
| {Var :: binary(), Type :: binary(), Value :: binary()}.
-type form() :: [form_field()].
%%-----------------------------------------------------------------------------
%% Request construction
%%-----------------------------------------------------------------------------
%% ---------------- disco ----------------
-spec discover_nodes(escalus_utils:jid_spec(), binary(), binary() | pubsub_node_id()) ->
exml:element().
discover_nodes(User, Id, {NodeAddr, NodeName}) ->
QueryElement = escalus_stanza:query_el(?NS_DISCO_ITEMS, [{<<"node">>, NodeName}], []),
iq(<<"get">>, User, Id, NodeAddr, [QueryElement]);
discover_nodes(User, Id, NodeAddr) ->
QueryElement = escalus_stanza:query_el(?NS_DISCO_ITEMS, [], []),
iq(<<"get">>, User, Id, NodeAddr, [QueryElement]).
%% ---------------- create & delete ----------------
-spec create_node(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
create_node(User, Id, Node) ->
create_node(User, Id, Node, []).
-spec create_node(escalus_utils:jid_spec(), binary(), pubsub_node_id(), [{binary(), binary()}]) ->
exml:element().
create_node(User, Id, {NodeAddr, NodeName}, ConfigFields) ->
Elements = [create_node_element(NodeName) | configure_node_form(ConfigFields, undefined)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec delete_node(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
delete_node(User, Id, {NodeAddr, NodeName}) ->
Elements = [delete_element(NodeName)],
pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, Elements).
%% ---------------- configuration ----------------
-spec get_configuration(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
get_configuration(User, Id, {NodeAddr, NodeName}) ->
Elements = [#xmlel{ name = <<"configure">>,
attrs = [{<<"node">>, NodeName}] }],
pubsub_owner_iq(<<"get">>, User, Id, NodeAddr, Elements).
-spec set_configuration(escalus_utils:jid_spec(), binary(),
pubsub_node_id(), [{binary(), binary()}]) ->
exml:element().
set_configuration(User, Id, {NodeAddr, NodeName}, ConfigFields) ->
Elements = configure_node_form(ConfigFields, NodeName),
pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec get_default_configuration(escalus_utils:jid_spec(), binary(),
escalus_utils:jid_spec()) -> exml:element().
get_default_configuration(User, Id, NodeAddr) ->
Elements = [default_element()],
pubsub_owner_iq(<<"get">>, User, Id, NodeAddr, Elements).
%% ---------------- affiliations ----------------
-spec get_affiliations(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
get_affiliations(User, Id, {NodeAddr, NodeName}) ->
Elements = [#xmlel{ name = <<"affiliations">>,
attrs = [{<<"node">>, NodeName}] }],
pubsub_owner_iq(<<"get">>, User, Id, NodeAddr, Elements).
-spec set_affiliations(escalus_utils:jid_spec(), binary(), pubsub_node_id(),
[{escalus_utils:jid_spec(), binary()}]) ->
exml:element().
set_affiliations(User, Id, {NodeAddr, NodeName}, AffChange) ->
AffList = [ #xmlel{ name = <<"affiliation">>,
attrs = [{<<"jid">>, escalus_utils:get_short_jid(U)},
{<<"affiliation">>, A}] }
|| {U, A} <- AffChange ],
Affiliations = #xmlel{ name = <<"affiliations">>, attrs = [{<<"node">>, NodeName}],
children = AffList },
pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, [Affiliations]).
%% ---------------- subscriptions ----------------
-spec subscribe(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
subscribe(User, Id, Node) ->
subscribe(User, Id, Node, []).
-spec subscribe(escalus_utils:jid_spec(), binary(), pubsub_node_id(), [{binary(), binary()}]) ->
exml:element().
subscribe(User, Id, {NodeAddr, NodeName}, ConfigFields) ->
Elements = [subscribe_element(NodeName, User) | subscribe_options_form(ConfigFields)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec unsubscribe(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
unsubscribe(User, Id, {NodeAddr, NodeName}) ->
Elements = [unsubscribe_element(NodeName, User)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec submit_subscription_response(escalus_utils:jid_spec(), binary(), pubsub_node_id(), form()) ->
exml:element().
submit_subscription_response(User, Id, {NodeAddr, _NodeName}, Form) ->
Fields = [ encode_form_field(F) || F <- Form ],
XEl = escalus_stanza:x_data_form(<<"submit">>, Fields),
Msg = #xmlel{ name = <<"message">>,
attrs = [{<<"to">>, NodeAddr}, {<<"id">>, Id}],
children = [XEl] },
escalus_stanza:from(Msg, escalus_utils:get_jid(User)).
-spec get_pending_subscriptions(escalus_utils:jid_spec(), binary(),
pubsub_node_id() | binary()) -> exml:element().
get_pending_subscriptions(User, Id, {NodeAddr, NodeName}) ->
Fields = [ encode_form_field({<<"pubsub#node">>, NodeName}) ],
Payload = [ escalus_stanza:x_data_form(<<"submit">>, Fields) ],
Node = <<"http://jabber.org/protocol/pubsub#get-pending">>,
CommandIQ = escalus_stanza:adhoc_request(Node, Payload),
escalus_stanza:from(escalus_stanza:set_id(escalus_stanza:to(CommandIQ, NodeAddr), Id), User);
get_pending_subscriptions(User, Id, NodesAddr) ->
Node = <<"http://jabber.org/protocol/pubsub#get-pending">>,
CommandIQ = escalus_stanza:adhoc_request(Node, []),
escalus_stanza:from(escalus_stanza:set_id(escalus_stanza:to(CommandIQ, NodesAddr), Id), User).
-spec get_user_subscriptions(escalus_utils:jid_spec(), binary(),
pubsub_node_id() | binary()) -> exml:element().
get_user_subscriptions(User, Id, Node) ->
{Element, NodeAddr}
= case Node of
{NodeAddr0, NodeName} -> {subscriptions_element(NodeName, []), NodeAddr0};
NodeAddr0 -> {subscriptions_element(), NodeAddr0}
end,
pubsub_iq(<<"get">>, User, Id, NodeAddr, [Element]).
get_subscription_options(User, Id, {NodeAddr, NodeName}) ->
Element = subscription_options(NodeName, User),
pubsub_iq(<<"get">>, User, Id, NodeAddr, [Element]).
set_subscription_options(User, Id, {NodeAddr, NodeName}, Options) ->
FormType = form_type_field_element(<<"subscribe_options">>),
EncodedOptions = [FormType | lists:map(fun encode_form_field/1, Options)],
Children = [set_subscription_options_form(NodeName, User, EncodedOptions)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Children).
-spec get_node_subscriptions(escalus_utils:jid_spec(), binary(), pubsub_node_id()) ->
exml:element().
get_node_subscriptions(User, Id, {NodeAddr, NodeName}) ->
Elements = [subscriptions_element(NodeName, [])],
pubsub_owner_iq(<<"get">>, User, Id, NodeAddr, Elements).
-spec set_subscriptions(escalus_utils:jid_spec(), binary(),
[{escalus_utils:jid_spec(), binary()}], pubsub_node_id()) ->
exml:element().
set_subscriptions(User, Id, Subscriptions, {NodeAddr, NodeName}) ->
SubElements = [subscription_element(Jid, SubState) || {Jid, SubState} <- Subscriptions],
Elements = [subscriptions_element(NodeName, SubElements)],
pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, Elements).
%% ---------------- publish & items management ----------------
-spec publish(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
publish(User, Id, {NodeAddr, NodeName}) ->
Elements = [publish_element(NodeName, undefined)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec publish(escalus_utils:jid_spec(), exml:element(), binary(), pubsub_node_id()) ->
exml:element().
publish(User, ContentElement, Id, {NodeAddr, NodeName}) ->
ItemElement = item_element(ContentElement),
Elements = [publish_element(NodeName, ItemElement)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec publish(escalus_utils:jid_spec(), binary(), exml:element(), binary(), pubsub_node_id()) ->
exml:element().
publish(User, ItemId, ContentElement, Id, {NodeAddr, NodeName}) ->
ItemElement = item_element(ItemId, ContentElement),
Elements = [publish_element(NodeName, ItemElement)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec publish_with_options(escalus_utils:jid_spec(),
binary(),
pubsub_node_id(),
publish_options()) -> exml:element().
publish_with_options(User, Id, {NodeAddr, NodeName}, PublishOptions) ->
PublishOptionsElement = publish_options_element(PublishOptions),
Elements = [publish_element(NodeName, undefined), PublishOptionsElement],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec publish_with_options(escalus_utils:jid_spec(),
exml:element(),
binary(),
pubsub_node_id(),
publish_options()) -> exml:element().
publish_with_options(User, ContentElement, Id, {NodeAddr, NodeName}, PublishOptions) ->
ItemElement = item_element(ContentElement),
PublishOptionsElement = publish_options_element(PublishOptions),
Elements = [publish_element(NodeName, ItemElement), PublishOptionsElement],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec publish_with_options(escalus_utils:jid_spec(),
binary(),
exml:element(),
binary(),
pubsub_node_id(),
publish_options()) -> exml:element().
publish_with_options(User, ItemId, ContentElement, Id, {NodeAddr, NodeName}, PublishOptions) ->
ItemElement = item_element(ItemId, ContentElement),
PublishOptionsElement = publish_options_element(PublishOptions),
Elements = [publish_element(NodeName, ItemElement), PublishOptionsElement],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec retract(escalus_utils:jid_spec(), binary(), pubsub_node_id(), binary()) -> exml:element().
retract(User, Id, {NodeAddr, NodeName}, ItemId) ->
Elements = [retract_item(NodeName, ItemId)],
pubsub_iq(<<"set">>, User, Id, NodeAddr, Elements).
-spec get_items(escalus_utils:jid_spec(), binary(), pubsub_node_id(), pos_integer()) -> exml:element().
get_items(User, Id, {NodeAddr, NodeName}, MaxItems) when is_integer(MaxItems), MaxItems > 0 ->
Elements = [items_element(NodeName, MaxItems)],
pubsub_iq(<<"get">>, User, Id, NodeAddr, Elements).
-spec get_all_items(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
get_all_items(User, Id, {NodeAddr, NodeName}) ->
Elements = [items_element(NodeName)],
pubsub_iq(<<"get">>, User, Id, NodeAddr, Elements).
-spec get_item(escalus_utils:jid_spec(), binary(), binary(), pubsub_node_id()) -> exml:element().
get_item(User, Id, ItemId, {NodeAddr, NodeName}) ->
BareItemsEl = items_element(NodeName),
Item = item_element(ItemId, undefined),
Elements = [BareItemsEl#xmlel{children = [Item]}],
pubsub_iq(<<"get">>, User, Id, NodeAddr, Elements).
-spec purge_all_items(escalus_utils:jid_spec(), binary(), pubsub_node_id()) -> exml:element().
purge_all_items(User, Id, {NodeAddr, NodeName}) ->
Elements = [purge_element(NodeName)],
pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, Elements).
%%-----------------------------------------------------------------------------
%% XML element construction
%%-----------------------------------------------------------------------------
%% Whole stanzas
iq(Type, From, Id, pep, Elements) ->
Stanza = escalus_stanza:iq(Type, Elements),
StanzaWithId = escalus_stanza:set_id(Stanza, Id),
escalus_stanza:from(StanzaWithId, escalus_utils:get_jid(From));
iq(Type, From, Id, To, Elements) ->
Stanza = escalus_stanza:iq(To, Type, Elements),
StanzaWithId = escalus_stanza:set_id(Stanza, Id),
escalus_stanza:from(StanzaWithId, escalus_utils:get_jid(From)).
pubsub_iq(Type, User, Id, NodeAddr, Elements) ->
pubsub_iq(Type, User, Id, NodeAddr, Elements, ?NS_PUBSUB).
pubsub_iq(Type, User, Id, NodeAddr, Elements, NS) ->
PubSubElement = pubsub_element(Elements, NS),
iq(Type, User, Id, NodeAddr, [PubSubElement]).
pubsub_owner_iq(Type, User, Id, NodeAddr, Elements) ->
pubsub_iq(Type, User, Id, NodeAddr, Elements, ?NS_PUBSUB_OWNER).
%% Form utils
configure_node_form(Fields, NodeName) ->
optional_form(<<"configure">>, NodeName, <<"node_config">>, Fields).
subscribe_options_form(Fields) ->
optional_form(<<"options">>, undefined, <<"subscribe_options">>, Fields).
optional_form(_FormName, _NodeName, _Type, []) -> [];
optional_form(FormName, NodeName, Type, Fields) ->
FormTypeField = form_type_field_element(Type),
FormFields = [encode_form_field(F) || F <- Fields],
[form_element(FormName, NodeName, [FormTypeField | FormFields])].
%% Elements
create_node_element(NodeName) ->
#xmlel{name = <<"create">>, attrs = [{<<"node">>, NodeName}]}.
pubsub_element(Children, NS) ->
#xmlel{name = <<"pubsub">>,
attrs = [{<<"xmlns">>, NS}],
children = Children}.
delete_element(NodeName) ->
#xmlel{name = <<"delete">>,
attrs = [{<<"node">>, NodeName}]}.
subscribe_element(NodeName, User) ->
#xmlel{name = <<"subscribe">>,
attrs = [{<<"node">>, NodeName},
{<<"jid">>, escalus_utils:get_jid(User)}]}.
unsubscribe_element(NodeName, User) ->
#xmlel{name = <<"unsubscribe">>,
attrs = [{<<"node">>, NodeName},
{<<"jid">>, escalus_utils:get_jid(User)}]}.
publish_element(NodeName, Item) ->
#xmlel{name = <<"publish">>,
attrs = [{<<"node">>, NodeName}],
children = skip_undefined([Item])}.
publish_options_element(Fields) ->
#xmlel{name = <<"publish-options">>,
children = x_data_form(<<"submit">>, form_type_field() ++ x_data_fields(Fields))}.
x_data_form(Type, Children) ->
[escalus_stanza:x_data_form(Type, Children)].
x_data_fields(Fields) ->
escalus_stanza:search_fields(Fields).
form_type_field() ->
[#xmlel{name = <<"field">>,
attrs = [{<<"var">>, <<"FORM_TYPE">>}, {<<"type">>, <<"hidden">>}],
children = [#xmlel{name = <<"value">>,
children = [{xmlcdata, ?NS_PUBSUB_PUBLISH_OPTIONS}]}]}].
items_element(NodeName) ->
#xmlel{name = <<"items">>,
attrs = [{<<"node">>, NodeName}]}.
items_element(NodeName, MaxItems) ->
#xmlel{name = <<"items">>,
attrs = [{<<"node">>, NodeName},
{<<"max_items">>, integer_to_binary(MaxItems)}]}.
item_element(ContentElement) ->
#xmlel{name = <<"item">>,
children = skip_undefined([ContentElement])}.
item_element(ItemId, ContentElement) ->
#xmlel{name = <<"item">>,
attrs = [{<<"id">>, ItemId}],
children = skip_undefined([ContentElement])}.
retract_item(NodeName, ItemId) ->
#xmlel{name = <<"retract">>,
attrs = [{<<"node">>, NodeName}],
children = [#xmlel{ name = <<"item">>,
attrs = [{<<"id">>, ItemId}] }]}.
purge_element(NodeName) ->
#xmlel{name = <<"purge">>,
attrs = [{<<"node">>, NodeName}]}.
subscriptions_element() ->
#xmlel{name = <<"subscriptions">>}.
subscriptions_element(NodeName, Children) ->
#xmlel{name = <<"subscriptions">>,
attrs = [{<<"node">>, NodeName}],
children = Children}.
subscription_element(User, SubscriptionState) ->
#xmlel{name = <<"subscription">>,
attrs = [{<<"jid">>, escalus_utils:get_jid(User)},
{<<"subscription">>, SubscriptionState}]}.
subscription_options(NodeName, User) ->
#xmlel{name = <<"options">>,
attrs = [{<<"node">>, NodeName},
{<<"jid">>, escalus_utils:get_jid(User)}]}.
set_subscription_options_form(NodeName, User, Children) ->
#xmlel{name = <<"options">>,
attrs = [{<<"node">>, NodeName},
{<<"jid">>, escalus_utils:get_jid(User)}],
children = [#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, <<"jabber:x:data">>},
{<<"type">>, <<"submit">>}],
children = Children}]}.
default_element() ->
#xmlel{name = <<"default">>}.
form_element(FormName, NodeName, FieldElements) ->
#xmlel{name = FormName,
attrs = skip_undefined([{<<"node">>, NodeName}]),
children = [#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, <<"jabber:x:data">>},
{<<"type">>, <<"submit">>}],
children = FieldElements}
]}.
form_type_field_element(FormType) ->
Content = << <<"http://jabber.org/protocol/pubsub#">>/binary, FormType/binary >>,
#xmlel{name = <<"field">>,
attrs = [{<<"var">>, <<"FORM_TYPE">>},
{<<"type">>, <<"hidden">>}],
children = [#xmlel{name = <<"value">>,
children = [#xmlcdata{content = Content}]}]}.
encode_form_field({Var, Value}) ->
encode_form_field(Var, [Value]);
encode_form_field({Var, <<"text-multi">>, Value}) ->
encode_form_field(Var, Value);
encode_form_field({Var, _Type, Value}) ->
encode_form_field(Var, [Value]).
encode_form_field(Var, Values) ->
Children = [ #xmlel{name = <<"value">>, children = [#xmlcdata{content = Content}]}
|| Content <- Values ],
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}], children = Children}.
%% Helpers
skip_undefined(L) ->
lists:filter(fun(undefined) -> false;
({_, undefined}) -> false;
(_) -> true
end, L).