From da310a517396bee93e96da9a3717230938064494 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 26 Jul 2016 09:52:29 +0300 Subject: [PATCH] Rewrite mod_adhoc and mod_announce to use XML generator --- include/xmpp_codec.hrl | 57 +++-- src/mod_adhoc.erl | 138 +++++------ src/mod_announce.erl | 355 ++++++++++----------------- src/xmpp_codec.erl | 544 ++++++++++++++++++++++++++++++++++++++++- src/xmpp_util.erl | 14 +- tools/xmpp_codec.spec | 56 +++++ 6 files changed, 840 insertions(+), 324 deletions(-) diff --git a/include/xmpp_codec.hrl b/include/xmpp_codec.hrl index 85601035d..b14c0d11f 100644 --- a/include/xmpp_codec.hrl +++ b/include/xmpp_codec.hrl @@ -18,6 +18,10 @@ -record(feature_register, {}). -type feature_register() :: #feature_register{}. +-record(adhoc_note, {type = info :: 'error' | 'info' | 'warn', + data = <<>> :: binary()}). +-type adhoc_note() :: #adhoc_note{}. + -record(address, {type :: 'bcc' | 'cc' | 'noreply' | 'ofrom' | 'replyroom' | 'replyto' | 'to', jid :: any(), desc :: binary(), @@ -419,6 +423,12 @@ sid :: binary()}). -type bytestreams() :: #bytestreams{}. +-record(adhoc_actions, {execute :: 'complete' | 'next' | 'prev', + prev = false :: boolean(), + next = false :: boolean(), + complete = false :: boolean()}). +-type adhoc_actions() :: #adhoc_actions{}. + -record(vcard_org, {name :: binary(), units = [] :: [binary()]}). -type vcard_org() :: #vcard_org{}. @@ -559,6 +569,16 @@ fields = [] :: [#xdata_field{}]}). -type xdata() :: #xdata{}. +-record(adhoc_command, {node :: binary(), + action = execute :: 'cancel' | 'complete' | 'execute' | 'next' | 'prev', + sid :: binary(), + status :: 'canceled' | 'completed' | 'executing', + lang :: binary(), + actions :: #adhoc_actions{}, + notes = [] :: [#adhoc_note{}], + xdata :: #xdata{}}). +-type adhoc_command() :: #adhoc_command{}. + -record(search, {instructions :: binary(), first :: binary(), last :: binary(), @@ -799,6 +819,7 @@ pubsub_options() | compress() | bytestreams() | + adhoc_actions() | muc_history() | identity() | feature_csi() | @@ -810,9 +831,7 @@ pubsub() | muc_owner() | muc_actor() | - carbons_private() | - mix_leave() | - muc_subscribe() | + adhoc_note() | rosterver_feature() | muc_invite() | vcard_xupdate() | @@ -820,28 +839,16 @@ bookmark_conference() | offline() | time() | - muc_unique() | - sasl_response() | - pubsub_subscribe() | - presence() | - message() | sm_enable() | starttls_failure() | sasl_challenge() | - gone() | x_conference() | private() | compress_failure() | sasl_failure() | bookmark_storage() | vcard_name() | - sm_resume() | - carbons_enable() | - expire() | - muc_unsubscribe() | - pubsub_unsubscribe() | muc_decline() | - chatstate() | sasl_auth() | p1_push() | legacy_auth() | @@ -866,10 +873,11 @@ rsm_first() | stat() | xdata_field() | + adhoc_command() | sm_failed() | ping() | - disco_item() | privacy_item() | + disco_item() | caps() | muc() | stream_features() | @@ -894,4 +902,19 @@ error() | stream_error() | muc_user() | - vcard_adr(). + vcard_adr() | + carbons_private() | + mix_leave() | + muc_subscribe() | + muc_unique() | + sasl_response() | + pubsub_subscribe() | + presence() | + message() | + gone() | + sm_resume() | + carbons_enable() | + expire() | + muc_unsubscribe() | + pubsub_unsubscribe() | + chatstate(). diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index e12e0de1e..9c8238900 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -31,18 +31,15 @@ -behaviour(gen_mod). --export([start/2, stop/1, process_local_iq/3, - process_sm_iq/3, get_local_commands/5, +-export([start/2, stop/1, process_local_iq/1, + process_sm_iq/1, get_local_commands/5, get_local_identity/5, get_local_features/5, get_sm_commands/5, get_sm_identity/5, get_sm_features/5, ping_item/4, ping_command/4, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). - --include("jlib.hrl"). - --include("adhoc.hrl"). +-include("xmpp.hrl"). start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -94,7 +91,7 @@ stop(Host) -> %------------------------------------------------------------------------- get_local_commands(Acc, _From, - #jid{server = Server, lserver = LServer} = _To, <<"">>, + #jid{server = Server, lserver = LServer} = _To, undefined, Lang) -> Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, @@ -107,12 +104,9 @@ get_local_commands(Acc, _From, {result, I} -> I; _ -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Server}, {<<"node">>, ?NS_COMMANDS}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []}], + Nodes = [#disco_item{jid = jid:make(Server), + node = ?NS_COMMANDS, + name = translate:translate(Lang, <<"Commands">>)}], {result, Items ++ Nodes} end; get_local_commands(_Acc, From, @@ -128,7 +122,7 @@ get_local_commands(Acc, _From, _To, _Node, _Lang) -> %------------------------------------------------------------------------- get_sm_commands(Acc, _From, - #jid{lserver = LServer} = To, <<"">>, Lang) -> + #jid{lserver = LServer} = To, undefined, Lang) -> Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, fun(B) when is_boolean(B) -> B end, @@ -140,13 +134,9 @@ get_sm_commands(Acc, _From, {result, I} -> I; _ -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, jid:to_string(To)}, - {<<"node">>, ?NS_COMMANDS}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []}], + Nodes = [#disco_item{jid = To, + node = ?NS_COMMANDS, + name = translate:translate(Lang, <<"Commands">>)}], {result, Items ++ Nodes} end; get_sm_commands(_Acc, From, @@ -160,21 +150,14 @@ get_sm_commands(Acc, _From, _To, _Node, _Lang) -> Acc. %% On disco info request to the ad-hoc node, return automation/command-list. get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []} + [#identity{category = <<"automation">>, + type = <<"command-list">>, + name = translate:translate(Lang, <<"Commands">>)} | Acc]; get_local_identity(Acc, _From, _To, <<"ping">>, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, translate:translate(Lang, <<"Ping">>)}], - children = []} + [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = translate:translate(Lang, <<"Ping">>)} | Acc]; get_local_identity(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -183,13 +166,9 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) -> %% On disco info request to the ad-hoc node, return automation/command-list. get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-list">>}, - {<<"name">>, - translate:translate(Lang, <<"Commands">>)}], - children = []} + [#identity{category = <<"automation">>, + type = <<"command-list">>, + name = translate:translate(Lang, <<"Commands">>)} | Acc]; get_sm_identity(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -225,34 +204,29 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. %------------------------------------------------------------------------- -process_local_iq(From, To, IQ) -> - process_adhoc_request(From, To, IQ, - adhoc_local_commands). +process_local_iq(IQ) -> + process_adhoc_request(IQ, adhoc_local_commands). -process_sm_iq(From, To, IQ) -> - process_adhoc_request(From, To, IQ, adhoc_sm_commands). +process_sm_iq(IQ) -> + process_adhoc_request(IQ, adhoc_sm_commands). -process_adhoc_request(From, To, - #iq{sub_el = SubEl, lang = Lang} = IQ, Hook) -> - ?DEBUG("About to parse ~p...", [IQ]), - case adhoc:parse_request(IQ) of - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]}; - #adhoc_request{} = AdhocRequest -> - Host = To#jid.lserver, - case ejabberd_hooks:run_fold(Hook, Host, empty, - [From, To, AdhocRequest]) - of - ignore -> ignore; - empty -> - Txt = <<"No hook has processed this command">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_ITEM_NOT_FOUND(Lang, Txt)]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]}; - Command -> IQ#iq{type = result, sub_el = [Command]} - end - end. +process_adhoc_request(#iq{from = From, to = To, + type = set, lang = Lang, + sub_els = [#adhoc_command{} = SubEl]} = IQ, Hook) -> + Host = To#jid.lserver, + case ejabberd_hooks:run_fold(Hook, Host, empty, [From, To, SubEl]) of + ignore -> + ignore; + empty -> + Txt = <<"No hook has processed this command">>, + xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); + {error, Error} -> + xmpp:make_error(IQ, Error); + Command -> + xmpp:make_iq_result(IQ, Command) + end; +process_adhoc_request(#iq{} = IQ, _Hooks) -> + xmpp:make_error(IQ, xmpp:err_bad_request()). ping_item(Acc, _From, #jid{server = Server} = _To, Lang) -> @@ -260,27 +234,25 @@ ping_item(Acc, _From, #jid{server = Server} = _To, {result, I} -> I; _ -> [] end, - Nodes = [#xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, Server}, {<<"node">>, <<"ping">>}, - {<<"name">>, translate:translate(Lang, <<"Ping">>)}], - children = []}], + Nodes = [#disco_item{jid = jid:make(Server), + node = <<"ping">>, + name = translate:translate(Lang, <<"Ping">>)}], {result, Items ++ Nodes}. ping_command(_Acc, _From, _To, - #adhoc_request{lang = Lang, node = <<"ping">>, - sessionid = _Sessionid, action = Action} = - Request) -> - if Action == <<"">>; Action == <<"execute">> -> - adhoc:produce_response(Request, - #adhoc_response{status = completed, - notes = - [{<<"info">>, - translate:translate(Lang, - <<"Pong">>)}]}); + #adhoc_command{lang = Lang, node = <<"ping">>, + action = Action} = Request) -> + if Action == execute -> + xmpp_util:make_adhoc_response( + Request, + #adhoc_command{ + status = completed, + notes = [#adhoc_note{ + type = info, + data = translate:translate(Lang, <<"Pong">>)}]}); true -> Txt = <<"Incorrect value of 'action' attribute">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + {error, xmpp:err_bad_request(Txt, Lang)} end; ping_command(Acc, _From, _To, _Request) -> Acc. diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 52ff2de92..4b7498447 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -39,8 +39,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). --include("adhoc.hrl"). +-include("xmpp.hrl"). -include("mod_announce.hrl"). -callback init(binary(), gen_mod:opts()) -> any(). @@ -57,6 +56,7 @@ -define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>, <<"admin">>, <>]). +tokenize(undefined) -> []; tokenize(Node) -> str:tokens(Node, <<"/#">>). start(Host, Opts) -> @@ -131,7 +131,7 @@ stop(Host) -> {wait, Proc}. %% Announcing via messages to a custom resource -announce(From, #jid{luser = <<>>} = To, #xmlel{name = <<"message">>} = Packet) -> +announce(From, #jid{luser = <<>>} = To, #message{} = Packet) -> Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME), case To#jid.lresource of <<"announce/all">> -> @@ -173,10 +173,9 @@ announce(_From, _To, _Packet) -> %%------------------------------------------------------------------------- %% Announcing via ad-hoc commands -define(INFO_COMMAND(Lang, Node), - [#xmlel{name = <<"identity">>, - attrs = [{<<"category">>, <<"automation">>}, - {<<"type">>, <<"command-node">>}, - {<<"name">>, get_title(Lang, Node)}]}]). + [#identity{category = <<"automation">>, + type = <<"command-node">>, + name = get_title(Lang, Node)}]). disco_identity(Acc, _From, _To, Node, Lang) -> LNode = tokenize(Node), @@ -210,7 +209,7 @@ disco_identity(Acc, _From, _To, Node, Lang) -> -define(INFO_RESULT(Allow, Feats, Lang), case Allow of deny -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> {result, Feats} end). @@ -226,7 +225,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) - acl:match_rule(global, Access2, From)} of {deny, deny} -> Txt = <<"Denied by ACL">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)}; + {error, xmpp:err_forbidden(Txt, Lang)}; _ -> {result, []} end @@ -269,26 +268,19 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) -> %%------------------------------------------------------------------------- -define(NODE_TO_ITEM(Lang, Server, Node), -( - #xmlel{ - name = <<"item">>, - attrs = [ - {<<"jid">>, Server}, - {<<"node">>, Node}, - {<<"name">>, get_title(Lang, Node)} - ] - } -)). + #disco_item{jid = jid:make(Server), + node = Node, + name = get_title(Lang, Node)}). -define(ITEMS_RESULT(Allow, Items, Lang), case Allow of deny -> - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> {result, Items} end). -disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<>>, Lang) -> +disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, undefined, Lang) -> case gen_mod:is_loaded(LServer, mod_adhoc) of false -> Acc; @@ -393,15 +385,15 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) commands_result(Allow, From, To, Request) -> case Allow of deny -> - Lang = Request#adhoc_request.lang, - {error, ?ERRT_FORBIDDEN(Lang, <<"Denied by ACL">>)}; + Lang = Request#adhoc_command.lang, + {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)}; allow -> announce_commands(From, To, Request) end. announce_commands(Acc, From, #jid{lserver = LServer} = To, - #adhoc_request{ node = Node} = Request) -> + #adhoc_command{node = Node} = Request) -> LNode = tokenize(Node), F = fun() -> Access = get_access(global), @@ -440,59 +432,35 @@ announce_commands(Acc, From, #jid{lserver = LServer} = To, %%------------------------------------------------------------------------- announce_commands(From, To, - #adhoc_request{lang = Lang, + #adhoc_command{lang = Lang, node = Node, - action = Action, - xdata = XData} = Request) -> - %% If the "action" attribute is not present, it is - %% understood as "execute". If there was no - %% element in the first response (which there isn't in our - %% case), "execute" and "complete" are equivalent. - ActionIsExecute = lists:member(Action, [<<>>, <<"execute">>, <<"complete">>]), - if Action == <<"cancel">> -> + sid = SID, + xdata = XData, + action = Action} = Request) -> + ActionIsExecute = Action == execute orelse Action == complete, + if Action == cancel -> %% User cancels request - adhoc:produce_response(Request, #adhoc_response{status = canceled}); - XData == false, ActionIsExecute -> + #adhoc_command{status = canceled, lang = Lang, node = Node, + sid = SID}; + XData == undefined, ActionIsExecute -> %% User requests form - Elements = generate_adhoc_form(Lang, Node, To#jid.lserver), - adhoc:produce_response(Request, - #adhoc_response{status = executing,elements = [Elements]}); - XData /= false, ActionIsExecute -> - %% User returns form. - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)}; - Fields -> - handle_adhoc_form(From, To, Request, Fields) - end; + Form = generate_adhoc_form(Lang, Node, To#jid.lserver), + #adhoc_command{status = executing, lang = Lang, node = Node, + sid = SID, xdata = Form}; + XData /= undefined, ActionIsExecute -> + handle_adhoc_form(From, To, Request); true -> - Txt = <<"Incorrect action or data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + Txt = <<"Unexpected action">>, + {error, xmpp:err_bad_request(Txt, Lang)} end. --define(VVALUE(Val), -( - #xmlel{ - name = <<"value">>, - children = [{xmlcdata, Val}] - } -)). - -define(TVFIELD(Type, Var, Val), -( - #xmlel{ - name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = vvaluel(Val) - } -)). - --define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, ?NS_ADMIN)). + #xdata_field{type = Type, var = Var, values = vvaluel(Val)}). vvaluel(Val) -> case Val of <<>> -> []; - _ -> [?VVALUE(Val)] + _ -> [Val] end. generate_adhoc_form(Lang, Node, ServerHost) -> @@ -503,49 +471,27 @@ generate_adhoc_form(Lang, Node, ServerHost) -> true -> {<<>>, <<>>} end, - #xmlel{ - name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = [ - ?HFIELD(), - #xmlel{name = <<"title">>, children = [{xmlcdata, get_title(Lang, Node)}]} - ] - ++ - if (LNode == ?NS_ADMINL("delete-motd")) - or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> - [#xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"confirm">>}, - {<<"type">>, <<"boolean">>}, - {<<"label">>, - translate:translate(Lang, <<"Really delete message of the day?">>)} - ], - children = [ - #xmlel{name = <<"value">>, children = [{xmlcdata, <<"true">>}]} - ] - } - ]; - true -> - [#xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"subject">>}, - {<<"type">>, <<"text-single">>}, - {<<"label">>, translate:translate(Lang, <<"Subject">>)}], - children = vvaluel(OldSubject) - }, - #xmlel{ - name = <<"field">>, - attrs = [ - {<<"var">>, <<"body">>}, - {<<"type">>, <<"text-multi">>}, - {<<"label">>, translate:translate(Lang, <<"Message body">>)}], - children = vvaluel(OldBody) - } - ] - - end}. + Fs = if (LNode == ?NS_ADMINL("delete-motd")) + or (LNode == ?NS_ADMINL("delete-motd-allhosts")) -> + [#xdata_field{type = boolean, + var = <<"confirm">>, + label = translate:translate( + Lang, <<"Really delete message of the day?">>), + values = [<<"true">>]}]; + true -> + [#xdata_field{type = 'text-single', + var = <<"subject">>, + label = translate:translate(Lang, <<"Subject">>), + values = vvaluel(OldSubject)}, + #xdata_field{type = 'text-multi', + var = <<"body">>, + label = translate:translate(Lang, <<"Message body">>), + values = vvaluel(OldBody)}] + end, + #xdata{type = form, + title = get_title(Lang, Node), + fields = [#xdata_field{type = hidden, var = <<"FORM_TYPE">>, + values = [?NS_ADMIN]}|Fs]}. join_lines([]) -> <<>>; @@ -558,103 +504,73 @@ join_lines([], Acc) -> iolist_to_binary(lists:reverse(tl(Acc))). handle_adhoc_form(From, #jid{lserver = LServer} = To, - #adhoc_request{lang = Lang, - node = Node, - sessionid = SessionID}, - Fields) -> - Confirm = case lists:keysearch(<<"confirm">>, 1, Fields) of - {value, {<<"confirm">>, [<<"true">>]}} -> - true; - {value, {<<"confirm">>, [<<"1">>]}} -> - true; - _ -> - false + #adhoc_command{lang = Lang, node = Node, + sid = SessionID, xdata = XData}) -> + Confirm = case xmpp_util:get_xdata_values(<<"confirm">>, XData) of + [<<"true">>] -> true; + [<<"1">>] -> true; + _ -> false end, - Subject = case lists:keysearch(<<"subject">>, 1, Fields) of - {value, {<<"subject">>, SubjectLines}} -> - %% There really shouldn't be more than one - %% subject line, but can we stop them? - join_lines(SubjectLines); - _ -> - <<>> - end, - Body = case lists:keysearch(<<"body">>, 1, Fields) of - {value, {<<"body">>, BodyLines}} -> - join_lines(BodyLines); - _ -> - <<>> - end, - Response = #adhoc_response{lang = Lang, - node = Node, - sessionid = SessionID, - status = completed}, - Packet = #xmlel{ - name = <<"message">>, - attrs = [{<<"type">>, <<"headline">>}], - children = if Subject /= <<>> -> - [#xmlel{name = <<"subject">>, children = [{xmlcdata, Subject}]}]; - true -> - [] - end - ++ - if Body /= <<>> -> - [#xmlel{name = <<"body">>, children = [{xmlcdata, Body}]}]; - true -> - [] - end - }, + Subject = join_lines(xmpp_util:get_xdata_values(<<"subject">>, XData)), + Body = join_lines(xmpp_util:get_xdata_values(<<"body">>, XData)), + Response = #adhoc_command{lang = Lang, node = Node, sid = SessionID, + status = completed}, + Packet = #message{type = headline, + body = xmpp:mk_text(Body), + subject = xmpp:mk_text(Subject)}, Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), case {Node, Body} of {?NS_ADMIN_DELETE_MOTD, _} -> if Confirm -> Proc ! {announce_motd_delete, From, To, Packet}, - adhoc:produce_response(Response); + Response; true -> - adhoc:produce_response(Response) + Response end; {?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} -> if Confirm -> Proc ! {announce_all_hosts_motd_delete, From, To, Packet}, - adhoc:produce_response(Response); + Response; true -> - adhoc:produce_response(Response) + Response end; {_, <<>>} -> %% An announce message with no body is definitely an operator error. %% Throw an error and give him/her a chance to send message again. - {error, ?ERRT_NOT_ACCEPTABLE(Lang, - <<"No body provided for announce message">>)}; + {error, xmpp:err_not_acceptable( + <<"No body provided for announce message">>, Lang)}; %% Now send the packet to ?PROCNAME. %% We don't use direct announce_* functions because it %% leads to large delay in response and queries processing {?NS_ADMIN_ANNOUNCE, _} -> Proc ! {announce_online, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} -> Proc ! {announce_all_hosts_online, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_ANNOUNCE_ALL, _} -> Proc ! {announce_all, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} -> Proc ! {announce_all_hosts_all, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_SET_MOTD, _} -> Proc ! {announce_motd, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} -> Proc ! {announce_all_hosts_motd, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_EDIT_MOTD, _} -> Proc ! {announce_motd_update, From, To, Packet}, - adhoc:produce_response(Response); + Response; {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} -> Proc ! {announce_all_hosts_motd_update, From, To, Packet}, - adhoc:produce_response(Response); - _ -> + Response; + Junk -> %% This can't happen, as we haven't registered any other %% command nodes. - {error, ?ERR_INTERNAL_SERVER_ERROR} + ?ERROR_MSG("got unexpected node/body = ~p", [Junk]), + {error, xmpp:err_internal_server_error()} end. get_title(Lang, <<"announce">>) -> @@ -687,15 +603,15 @@ announce_all(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> - Local = jid:make(<<>>, To#jid.server, <<>>), + Local = jid:make(To#jid.server), lists:foreach( fun({User, Server}) -> - Dest = jid:make(User, Server, <<>>), + Dest = jid:make(User, Server), ejabberd_router:route(Local, Dest, Packet) end, ejabberd_auth:get_vh_registered_users(Host)) end. @@ -704,15 +620,15 @@ announce_all_hosts_all(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> - Local = jid:make(<<>>, To#jid.server, <<>>), + Local = jid:make(To#jid.server), lists:foreach( fun({User, Server}) -> - Dest = jid:make(User, Server, <<>>), + Dest = jid:make(User, Server), ejabberd_router:route(Local, Dest, Packet) end, ejabberd_auth:dirty_get_registered_users()) end. @@ -722,9 +638,9 @@ announce_online(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> announce_online1(ejabberd_sm:get_vh_session_list(Host), @@ -736,9 +652,9 @@ announce_all_hosts_online(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> announce_online1(ejabberd_sm:dirty_get_sessions_list(), @@ -747,7 +663,7 @@ announce_all_hosts_online(From, To, Packet) -> end. announce_online1(Sessions, Server, Packet) -> - Local = jid:make(<<>>, Server, <<>>), + Local = jid:make(Server), lists:foreach( fun({U, S, R}) -> Dest = jid:make(U, S, R), @@ -759,9 +675,9 @@ announce_motd(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> announce_motd(Host, Packet) @@ -771,9 +687,9 @@ announce_all_hosts_motd(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> Hosts = ?MYHOSTS, @@ -793,9 +709,9 @@ announce_motd_update(From, To, Packet) -> Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> announce_motd_update(Host, Packet) @@ -805,9 +721,9 @@ announce_all_hosts_motd_update(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> Hosts = ?MYHOSTS, @@ -817,16 +733,16 @@ announce_all_hosts_motd_update(From, To, Packet) -> announce_motd_update(LServer, Packet) -> announce_motd_delete(LServer), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:set_motd(LServer, Packet). + Mod:set_motd(LServer, xmpp:encode(Packet)). announce_motd_delete(From, To, Packet) -> Host = To#jid.lserver, Access = get_access(Host), case acl:match_rule(Host, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> announce_motd_delete(Host) @@ -836,9 +752,9 @@ announce_all_hosts_motd_delete(From, To, Packet) -> Access = get_access(global), case acl:match_rule(global, Access, From) of deny -> - Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet), + Lang = xmpp:get_lang(Packet), Txt = <<"Denied by ACL">>, - Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, Txt)), + Err = xmpp:make_error(Packet, xmpp:err_forbidden(Txt, Lang)), ejabberd_router:route(To, From, Err); allow -> Hosts = ?MYHOSTS, @@ -853,13 +769,19 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:get_motd(LServer) of {ok, Packet} -> - case Mod:is_motd_user(LUser, LServer) of - false -> - Local = jid:make(<<>>, LServer, <<>>), - ejabberd_router:route(Local, JID, Packet), - Mod:set_motd_user(LUser, LServer); - true -> - ok + try xmpp:decode(Packet, [ignore_els]) of + Msg -> + case Mod:is_motd_user(LUser, LServer) of + false -> + Local = jid:make(LServer), + ejabberd_router:route(Local, JID, Msg), + Mod:set_motd_user(LUser, LServer); + true -> + ok + end + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode motd packet ~p: ~s", + [Packet, xmpp:format_error(Why)]) end; error -> ok @@ -871,31 +793,24 @@ get_stored_motd(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), case Mod:get_motd(LServer) of {ok, Packet} -> - {fxml:get_subtag_cdata(Packet, <<"subject">>), - fxml:get_subtag_cdata(Packet, <<"body">>)}; + try xmpp:decode(Packet, [ignore_els]) of + #message{body = Body, subject = Subject} -> + {xmpp:get_text(Subject), xmpp:get_text(Body)} + catch _:{xmpp_codec, Why} -> + ?ERROR_MSG("failed to decode motd packet ~p: ~s", + [Packet, xmpp:format_error(Why)]) + end; error -> {<<>>, <<>>} end. %% This function is similar to others, but doesn't perform any ACL verification send_announcement_to_all(Host, SubjectS, BodyS) -> - SubjectEls = if SubjectS /= <<>> -> - [#xmlel{name = <<"subject">>, children = [{xmlcdata, SubjectS}]}]; - true -> - [] - end, - BodyEls = if BodyS /= <<>> -> - [#xmlel{name = <<"body">>, children = [{xmlcdata, BodyS}]}]; - true -> - [] - end, - Packet = #xmlel{ - name = <<"message">>, - attrs = [{<<"type">>, <<"headline">>}], - children = SubjectEls ++ BodyEls - }, + Packet = #message{type = headline, + body = xmpp:mk_text(BodyS), + subject = xmpp:mk_text(SubjectS)}, Sessions = ejabberd_sm:dirty_get_sessions_list(), - Local = jid:make(<<>>, Host, <<>>), + Local = jid:make(Host), lists:foreach( fun({U, S, R}) -> Dest = jid:make(U, S, R), diff --git a/src/xmpp_codec.erl b/src/xmpp_codec.erl index c59c347f9..c8a4f002f 100644 --- a/src/xmpp_codec.erl +++ b/src/xmpp_codec.erl @@ -15,6 +15,30 @@ decode(_el) -> decode(_el, []). decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls = proplists:get_bool(ignore_els, Opts), case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"command">>, + <<"http://jabber.org/protocol/commands">>} -> + decode_adhoc_command(<<"http://jabber.org/protocol/commands">>, + IgnoreEls, _el); + {<<"note">>, + <<"http://jabber.org/protocol/commands">>} -> + decode_adhoc_command_notes(<<"http://jabber.org/protocol/commands">>, + IgnoreEls, _el); + {<<"actions">>, + <<"http://jabber.org/protocol/commands">>} -> + decode_adhoc_command_actions(<<"http://jabber.org/protocol/commands">>, + IgnoreEls, _el); + {<<"complete">>, + <<"http://jabber.org/protocol/commands">>} -> + decode_adhoc_command_complete(<<"http://jabber.org/protocol/commands">>, + IgnoreEls, _el); + {<<"next">>, + <<"http://jabber.org/protocol/commands">>} -> + decode_adhoc_command_next(<<"http://jabber.org/protocol/commands">>, + IgnoreEls, _el); + {<<"prev">>, + <<"http://jabber.org/protocol/commands">>} -> + decode_adhoc_command_prev(<<"http://jabber.org/protocol/commands">>, + IgnoreEls, _el); {<<"client-id">>, <<"urn:xmpp:sid:0">>} -> decode_client_id(<<"urn:xmpp:sid:0">>, IgnoreEls, _el); {<<"stanza-id">>, <<"urn:xmpp:sid:0">>} -> @@ -1254,6 +1278,24 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"command">>, + <<"http://jabber.org/protocol/commands">>} -> + true; + {<<"note">>, + <<"http://jabber.org/protocol/commands">>} -> + true; + {<<"actions">>, + <<"http://jabber.org/protocol/commands">>} -> + true; + {<<"complete">>, + <<"http://jabber.org/protocol/commands">>} -> + true; + {<<"next">>, + <<"http://jabber.org/protocol/commands">>} -> + true; + {<<"prev">>, + <<"http://jabber.org/protocol/commands">>} -> + true; {<<"client-id">>, <<"urn:xmpp:sid:0">>} -> true; {<<"stanza-id">>, <<"urn:xmpp:sid:0">>} -> true; {<<"addresses">>, @@ -2483,7 +2525,20 @@ encode({stanza_id, _, _} = Stanza_id) -> [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]); encode({client_id, _} = Client_id) -> encode_client_id(Client_id, - [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]). + [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]); +encode({adhoc_actions, _, _, _, _} = Actions) -> + encode_adhoc_command_actions(Actions, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/commands">>}]); +encode({adhoc_note, _, _} = Note) -> + encode_adhoc_command_notes(Note, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/commands">>}]); +encode({adhoc_command, _, _, _, _, _, _, _, _} = + Command) -> + encode_adhoc_command(Command, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/commands">>}]). get_name({last, _, _}) -> <<"query">>; get_name({version, _, _, _}) -> <<"query">>; @@ -2661,7 +2716,11 @@ get_name({nick, _}) -> <<"nick">>; get_name({address, _, _, _, _, _}) -> <<"address">>; get_name({addresses, _}) -> <<"addresses">>; get_name({stanza_id, _, _}) -> <<"stanza-id">>; -get_name({client_id, _}) -> <<"client-id">>. +get_name({client_id, _}) -> <<"client-id">>; +get_name({adhoc_actions, _, _, _, _}) -> <<"actions">>; +get_name({adhoc_note, _, _}) -> <<"note">>; +get_name({adhoc_command, _, _, _, _, _, _, _, _}) -> + <<"command">>. get_ns({last, _, _}) -> <<"jabber:iq:last">>; get_ns({version, _, _, _}) -> <<"jabber:iq:version">>; @@ -2909,7 +2968,13 @@ get_ns({address, _, _, _, _, _}) -> get_ns({addresses, _}) -> <<"http://jabber.org/protocol/address">>; get_ns({stanza_id, _, _}) -> <<"urn:xmpp:sid:0">>; -get_ns({client_id, _}) -> <<"urn:xmpp:sid:0">>. +get_ns({client_id, _}) -> <<"urn:xmpp:sid:0">>; +get_ns({adhoc_actions, _, _, _, _}) -> + <<"http://jabber.org/protocol/commands">>; +get_ns({adhoc_note, _, _}) -> + <<"http://jabber.org/protocol/commands">>; +get_ns({adhoc_command, _, _, _, _, _, _, _, _}) -> + <<"http://jabber.org/protocol/commands">>. dec_int(Val) -> dec_int(Val, infinity, infinity). @@ -3140,6 +3205,11 @@ pp(address, 5) -> [type, jid, desc, node, delivered]; pp(addresses, 1) -> [list]; pp(stanza_id, 2) -> [by, id]; pp(client_id, 1) -> [id]; +pp(adhoc_actions, 4) -> [execute, prev, next, complete]; +pp(adhoc_note, 2) -> [type, data]; +pp(adhoc_command, 8) -> + [node, action, sid, status, lang, actions, notes, + xdata]; pp(_, _) -> no. join([], _Sep) -> <<>>; @@ -3186,6 +3256,474 @@ dec_tzo(Val) -> M = jlib:binary_to_integer(M1), if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end. +decode_adhoc_command(__TopXMLNS, __IgnoreEls, + {xmlel, <<"command">>, _attrs, _els}) -> + {Xdata, Notes, Actions} = + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + undefined, [], undefined), + {Node, Lang, Sid, Status, Action} = + decode_adhoc_command_attrs(__TopXMLNS, _attrs, + undefined, undefined, undefined, undefined, + undefined), + {adhoc_command, Node, Action, Sid, Status, Lang, + Actions, Notes, Xdata}. + +decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, [], + Xdata, Notes, Actions) -> + {Xdata, lists:reverse(Notes), Actions}; +decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"actions">>, _attrs, _} = _el | _els], + Xdata, Notes, Actions) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Notes, + decode_adhoc_command_actions(__TopXMLNS, + __IgnoreEls, + _el)); + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Notes, + decode_adhoc_command_actions(<<"http://jabber.org/protocol/commands">>, + __IgnoreEls, + _el)); + _ -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Notes, Actions) + end; +decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata, + Notes, Actions) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"jabber:x:data">> -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + decode_xdata(<<"jabber:x:data">>, + __IgnoreEls, _el), + Notes, Actions); + _ -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Notes, Actions) + end; +decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"note">>, _attrs, _} = _el | _els], Xdata, + Notes, Actions) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, + [decode_adhoc_command_notes(__TopXMLNS, + __IgnoreEls, _el) + | Notes], + Actions); + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, + [decode_adhoc_command_notes(<<"http://jabber.org/protocol/commands">>, + __IgnoreEls, _el) + | Notes], + Actions); + _ -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Notes, Actions) + end; +decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Xdata, Notes, Actions) -> + decode_adhoc_command_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Notes, Actions). + +decode_adhoc_command_attrs(__TopXMLNS, + [{<<"node">>, _val} | _attrs], _Node, Lang, Sid, + Status, Action) -> + decode_adhoc_command_attrs(__TopXMLNS, _attrs, _val, + Lang, Sid, Status, Action); +decode_adhoc_command_attrs(__TopXMLNS, + [{<<"xml:lang">>, _val} | _attrs], Node, _Lang, Sid, + Status, Action) -> + decode_adhoc_command_attrs(__TopXMLNS, _attrs, Node, + _val, Sid, Status, Action); +decode_adhoc_command_attrs(__TopXMLNS, + [{<<"sessionid">>, _val} | _attrs], Node, Lang, _Sid, + Status, Action) -> + decode_adhoc_command_attrs(__TopXMLNS, _attrs, Node, + Lang, _val, Status, Action); +decode_adhoc_command_attrs(__TopXMLNS, + [{<<"status">>, _val} | _attrs], Node, Lang, Sid, + _Status, Action) -> + decode_adhoc_command_attrs(__TopXMLNS, _attrs, Node, + Lang, Sid, _val, Action); +decode_adhoc_command_attrs(__TopXMLNS, + [{<<"action">>, _val} | _attrs], Node, Lang, Sid, + Status, _Action) -> + decode_adhoc_command_attrs(__TopXMLNS, _attrs, Node, + Lang, Sid, Status, _val); +decode_adhoc_command_attrs(__TopXMLNS, [_ | _attrs], + Node, Lang, Sid, Status, Action) -> + decode_adhoc_command_attrs(__TopXMLNS, _attrs, Node, + Lang, Sid, Status, Action); +decode_adhoc_command_attrs(__TopXMLNS, [], Node, Lang, + Sid, Status, Action) -> + {decode_adhoc_command_attr_node(__TopXMLNS, Node), + 'decode_adhoc_command_attr_xml:lang'(__TopXMLNS, Lang), + decode_adhoc_command_attr_sessionid(__TopXMLNS, Sid), + decode_adhoc_command_attr_status(__TopXMLNS, Status), + decode_adhoc_command_attr_action(__TopXMLNS, Action)}. + +encode_adhoc_command({adhoc_command, Node, Action, Sid, + Status, Lang, Actions, Notes, Xdata}, + _xmlns_attrs) -> + _els = + lists:reverse('encode_adhoc_command_$xdata'(Xdata, + 'encode_adhoc_command_$notes'(Notes, + 'encode_adhoc_command_$actions'(Actions, + [])))), + _attrs = encode_adhoc_command_attr_action(Action, + encode_adhoc_command_attr_status(Status, + encode_adhoc_command_attr_sessionid(Sid, + 'encode_adhoc_command_attr_xml:lang'(Lang, + encode_adhoc_command_attr_node(Node, + _xmlns_attrs))))), + {xmlel, <<"command">>, _attrs, _els}. + +'encode_adhoc_command_$xdata'(undefined, _acc) -> _acc; +'encode_adhoc_command_$xdata'(Xdata, _acc) -> + [encode_xdata(Xdata, + [{<<"xmlns">>, <<"jabber:x:data">>}]) + | _acc]. + +'encode_adhoc_command_$notes'([], _acc) -> _acc; +'encode_adhoc_command_$notes'([Notes | _els], _acc) -> + 'encode_adhoc_command_$notes'(_els, + [encode_adhoc_command_notes(Notes, []) + | _acc]). + +'encode_adhoc_command_$actions'(undefined, _acc) -> + _acc; +'encode_adhoc_command_$actions'(Actions, _acc) -> + [encode_adhoc_command_actions(Actions, []) | _acc]. + +decode_adhoc_command_attr_node(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"node">>, <<"command">>, __TopXMLNS}}); +decode_adhoc_command_attr_node(__TopXMLNS, _val) -> + _val. + +encode_adhoc_command_attr_node(_val, _acc) -> + [{<<"node">>, _val} | _acc]. + +'decode_adhoc_command_attr_xml:lang'(__TopXMLNS, + undefined) -> + undefined; +'decode_adhoc_command_attr_xml:lang'(__TopXMLNS, + _val) -> + _val. + +'encode_adhoc_command_attr_xml:lang'(undefined, _acc) -> + _acc; +'encode_adhoc_command_attr_xml:lang'(_val, _acc) -> + [{<<"xml:lang">>, _val} | _acc]. + +decode_adhoc_command_attr_sessionid(__TopXMLNS, + undefined) -> + undefined; +decode_adhoc_command_attr_sessionid(__TopXMLNS, _val) -> + _val. + +encode_adhoc_command_attr_sessionid(undefined, _acc) -> + _acc; +encode_adhoc_command_attr_sessionid(_val, _acc) -> + [{<<"sessionid">>, _val} | _acc]. + +decode_adhoc_command_attr_status(__TopXMLNS, + undefined) -> + undefined; +decode_adhoc_command_attr_status(__TopXMLNS, _val) -> + case catch dec_enum(_val, + [canceled, completed, executing]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"status">>, <<"command">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_adhoc_command_attr_status(undefined, _acc) -> + _acc; +encode_adhoc_command_attr_status(_val, _acc) -> + [{<<"status">>, enc_enum(_val)} | _acc]. + +decode_adhoc_command_attr_action(__TopXMLNS, + undefined) -> + execute; +decode_adhoc_command_attr_action(__TopXMLNS, _val) -> + case catch dec_enum(_val, + [cancel, complete, execute, next, prev]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"action">>, <<"command">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_adhoc_command_attr_action(execute, _acc) -> _acc; +encode_adhoc_command_attr_action(_val, _acc) -> + [{<<"action">>, enc_enum(_val)} | _acc]. + +decode_adhoc_command_notes(__TopXMLNS, __IgnoreEls, + {xmlel, <<"note">>, _attrs, _els}) -> + Data = decode_adhoc_command_notes_els(__TopXMLNS, + __IgnoreEls, _els, <<>>), + Type = decode_adhoc_command_notes_attrs(__TopXMLNS, + _attrs, undefined), + {adhoc_note, Type, Data}. + +decode_adhoc_command_notes_els(__TopXMLNS, __IgnoreEls, + [], Data) -> + decode_adhoc_command_notes_cdata(__TopXMLNS, Data); +decode_adhoc_command_notes_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Data) -> + decode_adhoc_command_notes_els(__TopXMLNS, __IgnoreEls, + _els, <>); +decode_adhoc_command_notes_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Data) -> + decode_adhoc_command_notes_els(__TopXMLNS, __IgnoreEls, + _els, Data). + +decode_adhoc_command_notes_attrs(__TopXMLNS, + [{<<"type">>, _val} | _attrs], _Type) -> + decode_adhoc_command_notes_attrs(__TopXMLNS, _attrs, + _val); +decode_adhoc_command_notes_attrs(__TopXMLNS, + [_ | _attrs], Type) -> + decode_adhoc_command_notes_attrs(__TopXMLNS, _attrs, + Type); +decode_adhoc_command_notes_attrs(__TopXMLNS, [], + Type) -> + decode_adhoc_command_notes_attr_type(__TopXMLNS, Type). + +encode_adhoc_command_notes({adhoc_note, Type, Data}, + _xmlns_attrs) -> + _els = encode_adhoc_command_notes_cdata(Data, []), + _attrs = encode_adhoc_command_notes_attr_type(Type, + _xmlns_attrs), + {xmlel, <<"note">>, _attrs, _els}. + +decode_adhoc_command_notes_attr_type(__TopXMLNS, + undefined) -> + info; +decode_adhoc_command_notes_attr_type(__TopXMLNS, + _val) -> + case catch dec_enum(_val, [info, warn, error]) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"type">>, <<"note">>, __TopXMLNS}}); + _res -> _res + end. + +encode_adhoc_command_notes_attr_type(info, _acc) -> + _acc; +encode_adhoc_command_notes_attr_type(_val, _acc) -> + [{<<"type">>, enc_enum(_val)} | _acc]. + +decode_adhoc_command_notes_cdata(__TopXMLNS, <<>>) -> + <<>>; +decode_adhoc_command_notes_cdata(__TopXMLNS, _val) -> + _val. + +encode_adhoc_command_notes_cdata(<<>>, _acc) -> _acc; +encode_adhoc_command_notes_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_adhoc_command_actions(__TopXMLNS, __IgnoreEls, + {xmlel, <<"actions">>, _attrs, _els}) -> + {Next, Complete, Prev} = + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, false, false, + false), + Execute = decode_adhoc_command_actions_attrs(__TopXMLNS, + _attrs, undefined), + {adhoc_actions, Execute, Prev, Next, Complete}. + +decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, [], Next, Complete, Prev) -> + {Next, Complete, Prev}; +decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, + [{xmlel, <<"prev">>, _attrs, _} = _el | _els], + Next, Complete, Prev) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, Complete, + decode_adhoc_command_prev(__TopXMLNS, + __IgnoreEls, + _el)); + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, Complete, + decode_adhoc_command_prev(<<"http://jabber.org/protocol/commands">>, + __IgnoreEls, + _el)); + _ -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, Complete, + Prev) + end; +decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, + [{xmlel, <<"next">>, _attrs, _} = _el | _els], + Next, Complete, Prev) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, + decode_adhoc_command_next(__TopXMLNS, + __IgnoreEls, + _el), + Complete, Prev); + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, + decode_adhoc_command_next(<<"http://jabber.org/protocol/commands">>, + __IgnoreEls, + _el), + Complete, Prev); + _ -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, Complete, + Prev) + end; +decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, + [{xmlel, <<"complete">>, _attrs, _} = _el + | _els], + Next, Complete, Prev) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, + decode_adhoc_command_complete(__TopXMLNS, + __IgnoreEls, + _el), + Prev); + <<"http://jabber.org/protocol/commands">> -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, + decode_adhoc_command_complete(<<"http://jabber.org/protocol/commands">>, + __IgnoreEls, + _el), + Prev); + _ -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, Complete, + Prev) + end; +decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, [_ | _els], Next, Complete, + Prev) -> + decode_adhoc_command_actions_els(__TopXMLNS, + __IgnoreEls, _els, Next, Complete, Prev). + +decode_adhoc_command_actions_attrs(__TopXMLNS, + [{<<"execute">>, _val} | _attrs], + _Execute) -> + decode_adhoc_command_actions_attrs(__TopXMLNS, _attrs, + _val); +decode_adhoc_command_actions_attrs(__TopXMLNS, + [_ | _attrs], Execute) -> + decode_adhoc_command_actions_attrs(__TopXMLNS, _attrs, + Execute); +decode_adhoc_command_actions_attrs(__TopXMLNS, [], + Execute) -> + decode_adhoc_command_actions_attr_execute(__TopXMLNS, + Execute). + +encode_adhoc_command_actions({adhoc_actions, Execute, + Prev, Next, Complete}, + _xmlns_attrs) -> + _els = + lists:reverse('encode_adhoc_command_actions_$next'(Next, + 'encode_adhoc_command_actions_$complete'(Complete, + 'encode_adhoc_command_actions_$prev'(Prev, + [])))), + _attrs = + encode_adhoc_command_actions_attr_execute(Execute, + _xmlns_attrs), + {xmlel, <<"actions">>, _attrs, _els}. + +'encode_adhoc_command_actions_$next'(false, _acc) -> + _acc; +'encode_adhoc_command_actions_$next'(Next, _acc) -> + [encode_adhoc_command_next(Next, []) | _acc]. + +'encode_adhoc_command_actions_$complete'(false, _acc) -> + _acc; +'encode_adhoc_command_actions_$complete'(Complete, + _acc) -> + [encode_adhoc_command_complete(Complete, []) | _acc]. + +'encode_adhoc_command_actions_$prev'(false, _acc) -> + _acc; +'encode_adhoc_command_actions_$prev'(Prev, _acc) -> + [encode_adhoc_command_prev(Prev, []) | _acc]. + +decode_adhoc_command_actions_attr_execute(__TopXMLNS, + undefined) -> + undefined; +decode_adhoc_command_actions_attr_execute(__TopXMLNS, + _val) -> + case catch dec_enum(_val, [complete, next, prev]) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"execute">>, <<"actions">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_adhoc_command_actions_attr_execute(undefined, + _acc) -> + _acc; +encode_adhoc_command_actions_attr_execute(_val, _acc) -> + [{<<"execute">>, enc_enum(_val)} | _acc]. + +decode_adhoc_command_complete(__TopXMLNS, __IgnoreEls, + {xmlel, <<"complete">>, _attrs, _els}) -> + true. + +encode_adhoc_command_complete(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"complete">>, _attrs, _els}. + +decode_adhoc_command_next(__TopXMLNS, __IgnoreEls, + {xmlel, <<"next">>, _attrs, _els}) -> + true. + +encode_adhoc_command_next(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"next">>, _attrs, _els}. + +decode_adhoc_command_prev(__TopXMLNS, __IgnoreEls, + {xmlel, <<"prev">>, _attrs, _els}) -> + true. + +encode_adhoc_command_prev(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"prev">>, _attrs, _els}. + decode_client_id(__TopXMLNS, __IgnoreEls, {xmlel, <<"client-id">>, _attrs, _els}) -> Id = decode_client_id_attrs(__TopXMLNS, _attrs, diff --git a/src/xmpp_util.erl b/src/xmpp_util.erl index 83f1f4adc..a4f37c926 100644 --- a/src/xmpp_util.erl +++ b/src/xmpp_util.erl @@ -11,7 +11,7 @@ %% API -export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1, is_standalone_chat_state/1, get_xdata_values/2, - has_xdata_var/2]). + has_xdata_var/2, make_adhoc_response/1, make_adhoc_response/2]). -include("xmpp.hrl"). @@ -88,6 +88,18 @@ get_xdata_values(Var, #xdata{fields = Fields}) -> has_xdata_var(Var, #xdata{fields = Fields}) -> lists:keymember(Var, #xdata_field.var, Fields). +-spec make_adhoc_response(adhoc_command(), adhoc_command()) -> adhoc_command(). +make_adhoc_response(#adhoc_command{lang = Lang, node = Node, sid = SID}, + Command) -> + Command#adhoc_command{lang = Lang, node = Node, sid = SID}. + +-spec make_adhoc_response(adhoc_command()) -> adhoc_command(). +make_adhoc_response(#adhoc_command{sid = undefined} = Command) -> + SID = jlib:now_to_utc_string(p1_time_compat:timestamp()), + Command#adhoc_command{sid = SID}; +make_adhoc_response(Command) -> + Command. + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index fdf16ed37..0e0145f72 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2821,6 +2821,62 @@ result = {client_id, '$id'}, attrs = [#attr{name = <<"id">>, required = true}]}). +-xml(adhoc_command_prev, + #elem{name = <<"prev">>, + xmlns = <<"http://jabber.org/protocol/commands">>, + result = true}). +-xml(adhoc_command_next, + #elem{name = <<"next">>, + xmlns = <<"http://jabber.org/protocol/commands">>, + result = true}). +-xml(adhoc_command_complete, + #elem{name = <<"complete">>, + xmlns = <<"http://jabber.org/protocol/commands">>, + result = true}). + +-xml(adhoc_command_actions, + #elem{name = <<"actions">>, + xmlns = <<"http://jabber.org/protocol/commands">>, + result = {adhoc_actions, '$execute', '$prev', '$next', '$complete'}, + attrs = [#attr{name = <<"execute">>, + dec = {dec_enum, [[complete, next, prev]]}, + enc = {enc_enum, []}}], + refs = [#ref{name = adhoc_command_prev, min = 0, max = 1, + default = false, label = '$prev'}, + #ref{name = adhoc_command_next, min = 0, max = 1, + default = false, label = '$next'}, + #ref{name = adhoc_command_complete, min = 0, max = 1, + default = false, label = '$complete'}]}). + +-xml(adhoc_command_notes, + #elem{name = <<"note">>, + xmlns = <<"http://jabber.org/protocol/commands">>, + result = {adhoc_note, '$type', '$data'}, + attrs = [#attr{name = <<"type">>, default = info, + dec = {dec_enum, [[info, warn, error]]}, + enc = {enc_enum, []}}], + cdata = #cdata{default = <<"">>, label = '$data'}}). + +-xml(adhoc_command, + #elem{name = <<"command">>, + xmlns = <<"http://jabber.org/protocol/commands">>, + result = {adhoc_command, '$node', '$action', '$sid', + '$status', '$lang', '$actions', '$notes', '$xdata'}, + attrs = [#attr{name = <<"node">>, required = true}, + #attr{name = <<"xml:lang">>, label = '$lang'}, + #attr{name = <<"sessionid">>, label = '$sid'}, + #attr{name = <<"status">>, + dec = {dec_enum, [[canceled, completed, executing]]}, + enc = {enc_enum, []}}, + #attr{name = <<"action">>, default = execute, + dec = {dec_enum, [[cancel, complete, + execute, next, prev]]}, + enc = {enc_enum, []}}], + refs = [#ref{name = adhoc_command_actions, min = 0, max = 1, + label = '$actions'}, + #ref{name = xdata, min = 0, max = 1}, + #ref{name = adhoc_command_notes, label = '$notes'}]}). + dec_tzo(Val) -> [H1, M1] = str:tokens(Val, <<":">>), H = jlib:binary_to_integer(H1),