1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : adhoc.erl |
3 |
|
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se> |
4 |
|
%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050) |
5 |
|
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(adhoc). |
27 |
|
-author('henoch@dtek.chalmers.se'). |
28 |
|
-xep([{xep, 50}, {version, "1.3.0"}]). |
29 |
|
-export([parse_request/1, |
30 |
|
produce_response/2, |
31 |
|
produce_response/4, |
32 |
|
produce_response/1]). |
33 |
|
|
34 |
|
-include("mongoose.hrl"). |
35 |
|
-include("jlib.hrl"). |
36 |
|
-include("adhoc.hrl"). |
37 |
|
|
38 |
|
-type request() :: #adhoc_request{}. |
39 |
|
-type response() :: #adhoc_response{}. |
40 |
|
|
41 |
|
-export_type([request/0, response/0]). |
42 |
|
|
43 |
|
%% @doc Parse an ad-hoc request. Return either an adhoc_request record or |
44 |
|
%% an {error, ErrorType} tuple. |
45 |
|
-spec parse_request(jlib:iq()) -> request() | {error, exml:element()}. |
46 |
|
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) -> |
47 |
:-( |
?LOG_DEBUG(#{what => adhoc_parse_request, |
48 |
|
text => <<"entering parse_request...">>, |
49 |
:-( |
sub_el => SubEl}), |
50 |
:-( |
Node = xml:get_tag_attr_s(<<"node">>, SubEl), |
51 |
:-( |
SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl), |
52 |
:-( |
Action = xml:get_tag_attr_s(<<"action">>, SubEl), |
53 |
:-( |
XData = mongoose_data_forms:find_form(SubEl, false), |
54 |
:-( |
#xmlel{children = AllEls} = SubEl, |
55 |
:-( |
Others = case XData of |
56 |
|
false -> |
57 |
:-( |
AllEls; |
58 |
|
_ -> |
59 |
:-( |
lists:delete(XData, AllEls) |
60 |
|
end, |
61 |
|
|
62 |
:-( |
#adhoc_request{lang = Lang, |
63 |
|
node = Node, |
64 |
|
session_id = SessionID, |
65 |
|
action = Action, |
66 |
|
xdata = XData, |
67 |
|
others = Others}; |
68 |
|
parse_request(_) -> |
69 |
:-( |
{error, mongoose_xmpp_errors:bad_request()}. |
70 |
|
|
71 |
|
%% @doc Produce a <command/> node to use as response from an adhoc_response |
72 |
|
%% record, filling in values for language, node and session id from |
73 |
|
%% the request. |
74 |
|
-spec produce_response(request(), response() | atom()) -> #xmlel{}. |
75 |
|
produce_response(Request, Status) when is_atom(Status) -> |
76 |
:-( |
produce_response(Request, Status, <<>>, []); |
77 |
|
produce_response(#adhoc_request{lang = Lang, |
78 |
|
node = Node, |
79 |
|
session_id = SessionID}, |
80 |
|
Response) -> |
81 |
:-( |
produce_response(Response#adhoc_response{lang = Lang, |
82 |
|
node = Node, |
83 |
|
session_id = SessionID}). |
84 |
|
|
85 |
|
%% @doc Produce a <command/> node to use as response from an adhoc_response |
86 |
|
%% record, filling in values for language, node and session id from |
87 |
|
%% the request. |
88 |
|
-spec produce_response(request(), Status :: atom(), DefaultAction :: binary(), |
89 |
|
Elements :: [exml:element()]) -> exml:element(). |
90 |
|
produce_response(Request, Status, DefaultAction, Elements) -> |
91 |
:-( |
#adhoc_request{lang = Lang, node = Node, session_id = SessionID} = Request, |
92 |
:-( |
produce_response(#adhoc_response{lang = Lang, node = Node, session_id = SessionID, |
93 |
|
status = Status, default_action = DefaultAction, |
94 |
|
elements = Elements}). |
95 |
|
|
96 |
|
|
97 |
|
%% @doc Produce a <command/> node to use as response from an adhoc_response |
98 |
|
%% record. |
99 |
|
-spec produce_response(response()) -> exml:element(). |
100 |
|
produce_response(#adhoc_response{lang = _Lang, |
101 |
|
node = Node, |
102 |
|
session_id = ProvidedSessionID, |
103 |
|
status = Status, |
104 |
|
default_action = DefaultAction, |
105 |
|
actions = Actions, |
106 |
|
notes = Notes, |
107 |
|
elements = Elements}) -> |
108 |
:-( |
SessionID = ensure_correct_session_id(ProvidedSessionID), |
109 |
:-( |
ActionsEls = maybe_actions_element(Actions, DefaultAction), |
110 |
:-( |
NotesEls = lists:map(fun note_to_xmlel/1, Notes), |
111 |
:-( |
#xmlel{name = <<"command">>, |
112 |
|
attrs = [{<<"xmlns">>, ?NS_COMMANDS}, |
113 |
|
{<<"sessionid">>, SessionID}, |
114 |
|
{<<"node">>, Node}, |
115 |
|
{<<"status">>, list_to_binary(atom_to_list(Status))}], |
116 |
|
children = ActionsEls ++ NotesEls ++ Elements}. |
117 |
|
|
118 |
|
-spec ensure_correct_session_id(binary()) -> binary(). |
119 |
|
ensure_correct_session_id(SessionID) when is_binary(SessionID), SessionID /= <<>> -> |
120 |
:-( |
SessionID; |
121 |
|
ensure_correct_session_id(_) -> |
122 |
:-( |
USec = os:system_time(microsecond), |
123 |
:-( |
TS = calendar:system_time_to_rfc3339(USec, [{offset, "Z"}, {unit, microsecond}]), |
124 |
:-( |
list_to_binary(TS). |
125 |
|
|
126 |
|
-spec maybe_actions_element([binary()], binary()) -> [exml:element()]. |
127 |
|
maybe_actions_element([], _DefaultAction) -> |
128 |
:-( |
[]; |
129 |
|
maybe_actions_element(Actions, <<>>) -> |
130 |
|
% If the "execute" attribute is absent, it defaults to "next". |
131 |
:-( |
AllActions = ensure_default_action_present(Actions, <<"next">>), |
132 |
:-( |
[#xmlel{ |
133 |
|
name = <<"actions">>, |
134 |
:-( |
children = [#xmlel{name = Action} || Action <- AllActions] |
135 |
|
}]; |
136 |
|
maybe_actions_element(Actions, DefaultAction) -> |
137 |
|
% A form which has an <actions/> element and an "execute" attribute |
138 |
|
% which evaluates to an action which is not allowed is invalid. |
139 |
:-( |
AllActions = ensure_default_action_present(Actions, DefaultAction), |
140 |
:-( |
[#xmlel{ |
141 |
|
name = <<"actions">>, |
142 |
|
attrs = [{<<"execute">>, DefaultAction}], |
143 |
:-( |
children = [#xmlel{name = Action} || Action <- AllActions] |
144 |
|
}]. |
145 |
|
|
146 |
|
-spec ensure_default_action_present([binary()], binary()) -> [binary()]. |
147 |
|
ensure_default_action_present(Actions, DefaultAction) -> |
148 |
:-( |
case lists:member(DefaultAction, Actions) of |
149 |
:-( |
true -> Actions; |
150 |
:-( |
false -> [DefaultAction | Actions] |
151 |
|
end. |
152 |
|
|
153 |
|
-spec note_to_xmlel({binary(), iodata()}) -> exml:element(). |
154 |
|
note_to_xmlel({Type, Text}) -> |
155 |
:-( |
#xmlel{ |
156 |
|
name = <<"note">>, |
157 |
|
attrs = [{<<"type">>, Type}], |
158 |
|
children = [#xmlcdata{content = Text}] |
159 |
|
}. |