Skip to content

Commit

Permalink
Implement XEP-0004: Data Forms
Browse files Browse the repository at this point in the history
  • Loading branch information
chrzaszcz committed May 25, 2023
1 parent 86672e1 commit e37cc5a
Show file tree
Hide file tree
Showing 24 changed files with 620 additions and 934 deletions.
48 changes: 3 additions & 45 deletions include/mod_vcard.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -16,51 +16,9 @@
-export_type([vcard_search/0]).

-define(TLFIELD(Type, Label, Var),
#xmlel{name = <<"field">>,
attrs =
[{<<"type">>, Type},
{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}],
children = []}).

-define(LFIELD(Label, Var),
#xmlel{name = <<"field">>,
attrs = [{<<"label">>, translate:translate(Lang, Label)},
{<<"var">>, Var}]}).
mongoose_data_forms:form_field(#{var => Var, type => Type,
label => translate:translate(Lang, Label)})).

-define(FIELD(Var, Val),
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}],
children = [#xmlel{name = <<"value">>,
children = [#xmlcdata{content = Val}]}]}).

-define(FORM(JID, SearchFields,Lang),
[#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"You need an x:data capable client to "
"search">>)}]},
#xmlel{name = <<"x">>,
attrs =
[{<<"xmlns">>, ?NS_XDATA},
{<<"type">>, <<"form">>}],
children =
[#xmlel{name = <<"title">>, attrs = [],
children =
[{xmlcdata,
<<(translate:translate(Lang,
<<"Search users in ">>))/binary,
(jid:to_binary(JID))/binary>>}]},
#xmlel{name = <<"instructions">>, attrs = [],
children =
[{xmlcdata,
translate:translate(Lang,
<<"Fill in fields to search for any matching "
"Jabber User">>)}]}]
++
lists:map(fun ({X, Y}) ->
?TLFIELD(<<"text-single">>, X, Y)
end,
SearchFields)}]).

mongoose_data_forms:form_field(#{var => Var, values => [Val]})).

20 changes: 1 addition & 19 deletions src/adhoc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
Node = xml:get_tag_attr_s(<<"node">>, SubEl),
SessionID = xml:get_tag_attr_s(<<"sessionid">>, SubEl),
Action = xml:get_tag_attr_s(<<"action">>, SubEl),
XData = find_xdata_el(SubEl),
XData = mongoose_data_forms:find_form(SubEl, false),
#xmlel{children = AllEls} = SubEl,
Others = case XData of
false ->
Expand All @@ -68,24 +68,6 @@ parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}
parse_request(_) ->
{error, mongoose_xmpp_errors:bad_request()}.

%% @doc Borrowed from mod_vcard.erl
-spec find_xdata_el(exml:element()) -> false | exml:element().
find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).

%% @private
find_xdata_el1([]) ->
false;
find_xdata_el1([XE = #xmlel{attrs = Attrs} | Els]) ->
case xml:get_attr_s(<<"xmlns">>, Attrs) of
?NS_XDATA ->
XE;
_ ->
find_xdata_el1(Els)
end;
find_xdata_el1([_ | Els]) ->
find_xdata_el1(Els).

%% @doc Produce a <command/> node to use as response from an adhoc_response
%% record, filling in values for language, node and session id from
%% the request.
Expand Down
42 changes: 14 additions & 28 deletions src/event_pusher/mod_event_pusher_push.erl
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ do_push_event(Acc, Event, BareRecipient) ->
parse_request(#xmlel{name = <<"enable">>} = Request) ->
JID = jid:from_binary(exml_query:attr(Request, <<"jid">>, <<>>)),
Node = exml_query:attr(Request, <<"node">>, <<>>), %% Treat unset node as empty - both forbidden
Form = exml_query:subelement(Request, <<"x">>),
Form = mongoose_data_forms:find_form(Request),

case {JID, Node, parse_form(Form)} of
{_, _, invalid_form} -> bad_request;
Expand Down Expand Up @@ -232,38 +232,24 @@ parse_request(_) ->
parse_form(undefined) ->
[];
parse_form(Form) ->
case is_valid_form(Form) of
true ->
parse_form_fields(Form);
false ->
invalid_form
end.

-spec is_valid_form(exml:element()) -> boolean().
is_valid_form(Form) ->
IsForm = ?NS_XDATA == exml_query:attr(Form, <<"xmlns">>),
IsSubmit = <<"submit">> == exml_query:attr(Form, <<"type">>, <<"submit">>),
IsForm andalso IsSubmit.
parse_form_fields(Form).

-spec parse_form_fields(exml:element()) -> invalid_form | form().
parse_form_fields(Form) ->
FieldsXML = exml_query:subelements(Form, <<"field">>),
Fields = [{exml_query:attr(Field, <<"var">>),
exml_query:path(Field, [{element, <<"value">>}, cdata])} || Field <- FieldsXML],
case lists:keytake(<<"FORM_TYPE">>, 1, Fields) of
{value, {_, ?NS_PUBSUB_PUB_OPTIONS}, CustomFields} ->
case are_form_fields_valid(CustomFields) of
true ->
CustomFields;
false ->
invalid_form
end;
_ ->
invalid_form
case mongoose_data_forms:form_to_kvs(Form) of
invalid ->
invalid_form;
KVs ->
ParsedKVs = lists:flatmap(fun parse_kv/1, KVs),
case lists:keymember(invalid_field, 1, ParsedKVs) of
true -> ParsedKVs;
false -> invalid_form
end
end.

are_form_fields_valid(Fields) ->
lists:all(fun({Key, Value}) -> is_binary(Key) andalso is_binary(Value) end, Fields).
parse_kv({<<"FORM_TYPE">>, [?NS_PUBSUB_PUB_OPTIONS]}) -> [];
parse_kv({K, [V]}) -> [{K, V}];
parse_kv(Other) -> [{invalid_field, Other}].

-spec enable_node(mongooseim:host_type(), jid:jid(), jid:jid(), pubsub_node(), form()) ->
ok | {error, Reason :: term()}.
Expand Down
19 changes: 8 additions & 11 deletions src/event_pusher/mod_event_pusher_push_plugin_defaults.erl
Original file line number Diff line number Diff line change
Expand Up @@ -182,34 +182,31 @@ handle_publish_response(HostType, Recipient, PubsubJID, Node, #iq{type = error,
PushPayload :: mod_event_pusher_push_plugin:push_payload()) ->
jlib:iq().
push_notification_iq(Node, Form, PushPayload) ->
NotificationFields = [{<<"FORM_TYPE">>, ?PUSH_FORM_TYPE} | PushPayload ],

#iq{type = set, sub_el = [
#xmlel{name = <<"pubsub">>, attrs = [{<<"xmlns">>, ?NS_PUBSUB}], children = [
#xmlel{name = <<"publish">>, attrs = [{<<"node">>, Node}], children = [
#xmlel{name = <<"item">>, children = [
#xmlel{name = <<"notification">>,
attrs = [{<<"xmlns">>, ?NS_PUSH}],
children = [make_form(NotificationFields)]}
children = [make_form(?PUSH_FORM_TYPE, PushPayload)]}
]}
]}
] ++ maybe_publish_options(Form)}
]}.

-spec make_form(mod_event_pusher_push:form()) -> exml:element().
make_form(Fields) ->
#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"submit">>}],
children = [make_form_field(Field) || Field <- Fields]}.
-spec make_form(binary(), mod_event_pusher_push:form()) -> exml:element().
make_form(FormType, Fields) ->
Children = [mongoose_data_forms:form_type_field(FormType) |
[make_form_field(Field) || Field <- Fields]],
mongoose_data_forms:form(Children, <<"submit">>).

-spec make_form_field(mod_event_pusher_push:form_field()) -> exml:element().
make_form_field({Name, Value}) ->
#xmlel{name = <<"field">>,
attrs = [{<<"var">>, Name}],
children = [#xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]}]}.
mongoose_data_forms:form_field(#{var => Name, values => [Value]}).

-spec maybe_publish_options(mod_event_pusher_push:form()) -> [exml:element()].
maybe_publish_options([]) ->
[];
maybe_publish_options(FormFields) ->
Children = [make_form([{<<"FORM_TYPE">>, ?NS_PUBSUB_PUB_OPTIONS}] ++ FormFields)],
Children = [make_form(?NS_PUBSUB_PUB_OPTIONS, FormFields)],
[#xmlel{name = <<"publish-options">>, children = Children}].
8 changes: 4 additions & 4 deletions src/http_upload/mod_http_upload.erl
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,10 @@ parse_request(Request) ->

-spec get_disco_info_form(MaxFileSizeBin :: binary()) -> exml:element().
get_disco_info_form(MaxFileSizeBin) ->
#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
children = [jlib:form_field({<<"FORM_TYPE">>, <<"hidden">>, ?NS_HTTP_UPLOAD_030}),
jlib:form_field({<<"max-file-size">>, MaxFileSizeBin})]}.
Fields = [mongoose_data_forms:form_type_field(?NS_HTTP_UPLOAD_030),
mongoose_data_forms:form_field(#{var => <<"max-file-size">>,
values => [MaxFileSizeBin]})],
mongoose_data_forms:form(Fields, <<"result">>).


-spec header_to_xmlel({Key :: binary(), Value :: binary()}) -> exml:element().
Expand Down
41 changes: 14 additions & 27 deletions src/inbox/mod_inbox.erl
Original file line number Diff line number Diff line change
Expand Up @@ -431,38 +431,25 @@ result_set([#{remote_jid := FirstBinJid, timestamp := FirstTS} | _] = List) ->
-spec build_inbox_form(mongooseim:host_type()) -> exml:element().
build_inbox_form(HostType) ->
AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType),
OrderOptions = [
{<<"Ascending by timestamp">>, <<"asc">>},
{<<"Descending by timestamp">>, <<"desc">>}
],
FormFields = [
jlib:form_field({<<"FORM_TYPE">>, <<"hidden">>, ?NS_ESL_INBOX}),
text_single_form_field(<<"start">>),
text_single_form_field(<<"end">>),
text_single_form_field(<<"hidden_read">>, <<"false">>),
mod_inbox_utils:list_single_form_field(<<"order">>, <<"desc">>, OrderOptions),
mod_inbox_utils:list_single_form_field(<<"box">>, <<"all">>, AllBoxes),
jlib:form_field({<<"archive">>, <<"boolean">>, <<"false">>})
],
#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
children = FormFields}.

-spec text_single_form_field(Var :: binary()) -> exml:element().
text_single_form_field(Var) ->
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}, {<<"type">>, <<"text-single">>}]}.

-spec text_single_form_field(Var :: binary(), DefaultValue :: binary()) -> exml:element().
text_single_form_field(Var, DefaultValue) ->
#xmlel{name = <<"field">>,
attrs = [{<<"var">>, Var}, {<<"type">>, <<"text-single">>}, {<<"value">>, DefaultValue}]}.
OrderOptions = [{<<"Ascending by timestamp">>, <<"asc">>},
{<<"Descending by timestamp">>, <<"desc">>}],
FieldSpecs = [#{var => <<"start">>, type => <<"text-single">>},
#{var => <<"end">>, type => <<"text-single">>},
#{var => <<"hidden_read">>, type => <<"text-single">>, values => [<<"false">>]},
#{var => <<"order">>, type => <<"list-single">>, values => [<<"desc">>],
options => OrderOptions},
#{var => <<"box">>, type => <<"list-single">>, values => [<<"all">>],
options => AllBoxes},
#{var => <<"archive">>, type => <<"boolean">>, values => [<<"false">>]}],
Fields = [mongoose_data_forms:form_field(Spec) || Spec <- FieldSpecs],
mongoose_data_forms:form([mongoose_data_forms:form_type_field(?NS_ESL_INBOX) | Fields]).

%%%%%%%%%%%%%%%%%%%
%% iq-set
-spec query_to_params(mongooseim:host_type(), QueryEl :: exml:element()) ->
get_inbox_params() | {error, atom(), binary()}.
query_to_params(HostType, QueryEl) ->
Form = form_to_params(HostType, exml_query:subelement_with_ns(QueryEl, ?NS_XDATA)),
Form = form_to_params(HostType, mongoose_data_forms:find_form(QueryEl)),
Rsm = create_rsm(HostType, QueryEl),
build_params(Form, Rsm).

Expand Down Expand Up @@ -524,7 +511,7 @@ expand_limit(Max) ->
form_to_params(_, undefined) ->
#{ order => desc };
form_to_params(HostType, FormEl) ->
ParsedFields = jlib:parse_xdata_fields(exml_query:subelements(FormEl, <<"field">>)),
ParsedFields = mongoose_data_forms:form_to_kvs(FormEl),
?LOG_DEBUG(#{what => inbox_parsed_form_fields, parsed_fields => ParsedFields}),
fields_to_params(HostType, ParsedFields, #{ order => desc }).

Expand Down
16 changes: 8 additions & 8 deletions src/inbox/mod_inbox_entries.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ maybe_get_full_entry(SubEl) ->
-spec build_inbox_entry_form(mongooseim:host_type()) -> exml:element().
build_inbox_entry_form(HostType) ->
AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType),
#xmlel{name = <<"x">>,
attrs = [{<<"xmlns">>, ?NS_XDATA},
{<<"type">>, <<"form">>}],
children = [jlib:form_field({<<"FORM_TYPE">>, <<"hidden">>, ?NS_ESL_INBOX_CONVERSATION}),
mod_inbox_utils:list_single_form_field(<<"box">>, <<"all">>, AllBoxes),
jlib:form_field({<<"archive">>, <<"boolean">>, <<"false">>}),
jlib:form_field({<<"read">>, <<"boolean">>, <<"false">>}),
jlib:form_field({<<"mute">>, <<"text-single">>, <<"0">>})]}.
FieldSpecs =
[#{var => <<"box">>, type => <<"list-single">>, values => [<<"all">>], options => AllBoxes},
#{var => <<"archive">>, type => <<"boolean">>, values => [<<"false">>]},
#{var => <<"read">>, type => <<"boolean">>, values => [<<"false">>]},
#{var => <<"mute">>, type => <<"text-single">>, values => [<<"0">>]}],
Fields = lists:map(fun mongoose_data_forms:form_field/1, FieldSpecs),
FormType = mongoose_data_forms:form_type_field(?NS_ESL_INBOX_CONVERSATION),
mongoose_data_forms:form([FormType | Fields]).

fetch_right_query(HostType, InboxEntryKey, only_properties) ->
mod_inbox_backend:get_entry_properties(HostType, InboxEntryKey);
Expand Down
28 changes: 0 additions & 28 deletions src/inbox/mod_inbox_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
build_inbox_result_elements/2,
build_entry_result_elements/2,
all_valid_boxes_for_query/1,
list_single_form_field/3,
calculate_ts_from/2
]).

Expand Down Expand Up @@ -281,33 +280,6 @@ build_delay_el(Timestamp) ->
all_valid_boxes_for_query(HostType) ->
[<<"all">> | gen_mod:get_module_opt(HostType, mod_inbox, boxes)].

-spec list_single_form_field(Var :: binary(),
Default :: binary(),
Options :: [ Option | {Label, Value}]) -> exml:element() when
Option :: binary(), Label :: binary(), Value :: binary().
list_single_form_field(Var, Default, Options) ->
Value = form_field_value(Default),
#xmlel{
name = <<"field">>,
attrs = [{<<"var">>, Var}, {<<"type">>, <<"list-single">>}],
children = [Value | [ form_field_option(Option) || Option <- Options ]]
}.

-spec form_field_option(Option | {Label, Value}) -> exml:element() when
Option :: binary(), Label :: binary(), Value :: binary().
form_field_option({Label, Value}) ->
#xmlel{
name = <<"option">>,
attrs = [{<<"label">>, Label}],
children = [form_field_value(Value)]
};
form_field_option(Option) ->
form_field_option({Option, Option}).

-spec form_field_value(Value :: binary()) -> exml:element().
form_field_value(Value) ->
#xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]}.

-spec calculate_ts_from(integer(), non_neg_integer()) -> integer().
calculate_ts_from(Now, Days) ->
DaysInMicroSeconds = 86400000000 * Days, % 8.64e+10 microseconds in a day
Expand Down
Loading

0 comments on commit e37cc5a

Please sign in to comment.