]> granicus.if.org Git - ejabberd/commitdiff
Rewrite mod_adhoc and mod_announce to use XML generator
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Tue, 26 Jul 2016 06:52:29 +0000 (09:52 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Tue, 26 Jul 2016 06:52:29 +0000 (09:52 +0300)
include/xmpp_codec.hrl
src/mod_adhoc.erl
src/mod_announce.erl
src/xmpp_codec.erl
src/xmpp_util.erl
tools/xmpp_codec.spec

index 85601035d02ce93ef589ba196410282266c23eb5..b14c0d11fbe9022915be1be9a3d8e0a5dbec2e38 100644 (file)
 -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(),
                       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{}.
                 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(),
                         pubsub_options() |
                         compress() |
                         bytestreams() |
+                        adhoc_actions() |
                         muc_history() |
                         identity() |
                         feature_csi() |
                         pubsub() |
                         muc_owner() |
                         muc_actor() |
-                        carbons_private() |
-                        mix_leave() |
-                        muc_subscribe() |
+                        adhoc_note() |
                         rosterver_feature() |
                         muc_invite() |
                         vcard_xupdate() |
                         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() |
                         rsm_first() |
                         stat() |
                         xdata_field() |
+                        adhoc_command() |
                         sm_failed() |
                         ping() |
-                        disco_item() |
                         privacy_item() |
+                        disco_item() |
                         caps() |
                         muc() |
                         stream_features() |
                         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().
index e12e0de1e6bd7f3922da9d26d932dbbe159b67eb..9c8238900386c74f82eb111f71581418485d0aef 100644 (file)
 
 -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.
 
index 52ff2de92f6c21e1756c3cbb135121806a17a08e..4b7498447e133f1829a7c1cdedc15096c0376174 100644 (file)
@@ -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">>, <<Sub>>]).
 
+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 <actions/>
-    %% 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 <iq/> 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),
index c59c347f9763f4f8aabb1973eac5be82b9c25c8a..c8a4f002f17b8ea8e58536ca309e31606dd62c79 100644 (file)
@@ -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, <<Data/binary, _data/binary>>);
+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,
index 83f1f4adc42a8a8ebce3829a95b2903c71400032..a4f37c92606d7d13d3f085a7f75806462a8e7537 100644 (file)
@@ -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
 %%%===================================================================
index fdf16ed37e49a426b5aa884adb15f3f69d4bd98e..0e0145f729fc74fe4f08287157e80c73d4ea6bff 100644 (file)
           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),