./ct_report/coverage/mongoose_stanza_api.COVER.html

1 -module(mongoose_stanza_api).
2
3 %% API
4 -export([send_chat_message/4, send_headline_message/5, send_stanza/2, lookup_recent_messages/5]).
5
6 %% Event API
7 -export([open_session/2, close_session/1]).
8
9 -include("jlib.hrl").
10 -include("mongoose_rsm.hrl").
11 -include("mongoose_logger.hrl").
12
13 -type stream() :: #{jid := jid:jid(),
14 sid := ejabberd_sm:sid(),
15 host_type := mongooseim:host_type()}.
16
17 %% API
18
19 -spec send_chat_message(User :: jid:jid() | undefined, From :: jid:jid() | undefined,
20 To :: jid:jid(), Body :: binary()) ->
21 {unknown_user | invalid_sender, iodata()} | {ok, map()}.
22 send_chat_message(User, From, To, Body) ->
23
:-(
M = #{user => User, from => From, to => To, body => Body},
24
:-(
fold(M, [fun check_sender/1, fun get_host_type/1, fun check_user/1,
25 fun prepare_chat_message/1, fun send/1]).
26
27 -spec send_headline_message(User :: jid:jid() | undefined, From :: jid:jid() | undefined,
28 To :: jid:jid(), Body :: binary() | undefined,
29 Subject :: binary() | undefined) ->
30 {unknown_user | invalid_sender, iodata()} | {ok, map()}.
31 send_headline_message(User, From, To, Body, Subject) ->
32
:-(
M = #{user => User, from => From, to => To, body => Body, subject => Subject},
33
:-(
fold(M, [fun check_sender/1, fun get_host_type/1, fun check_user/1,
34 fun prepare_headline_message/1, fun send/1]).
35
36 -spec send_stanza(User :: jid:jid() | undefined, exml:element()) ->
37 {unknown_user | invalid_sender | no_sender |
38 invalid_recipient | no_recipient, iodata()} | {ok, map()}.
39 send_stanza(User, Stanza) ->
40
:-(
M = #{user => User, stanza => Stanza},
41
:-(
fold(M, [fun extract_from_jid/1, fun extract_to_jid/1, fun check_sender/1,
42 fun get_host_type/1, fun check_user/1, fun prepare_stanza/1, fun send/1]).
43
44 -spec lookup_recent_messages(User :: jid:jid(), With :: jid:jid() | undefined,
45 Before :: mod_mam:unix_timestamp() | undefined, % microseconds
46 Limit :: non_neg_integer() | undefined, CheckUser :: boolean()) ->
47 {unknown_user, iodata()} | {ok, {[mod_mam:message_row()], non_neg_integer()}}.
48 lookup_recent_messages(User, With, Before, Limit, CheckUser) ->
49
:-(
M = #{user => User, with => With, before => Before, limit => Limit, check_user => CheckUser},
50
:-(
fold(M, [fun get_host_type/1, fun check_user/1, fun lookup_messages/1]).
51
52 %% Event API
53
54 -spec open_session(jid:jid(), boolean()) -> {unknown_user, iodata()} | {ok, stream()}.
55 open_session(User, CheckUser) ->
56
:-(
M = #{user => User, check_user => CheckUser},
57
:-(
fold(M, [fun get_host_type/1, fun check_user/1, fun do_open_session/1]).
58
59 -spec close_session(stream()) -> {ok, closed}.
60 close_session(#{jid := Jid = #jid{lserver = S}, sid := Sid, host_type := HostType}) ->
61
:-(
Acc = mongoose_acc:new(
62 #{location => ?LOCATION,
63 lserver => S,
64 host_type => HostType,
65 element => undefined}),
66
:-(
ejabberd_sm:close_session(Acc, Sid, Jid, normal, #{}),
67
:-(
{ok, closed}.
68
69 %% Steps
70
71 %% @doc Determine the user's bare JID and the 'from' JID, and check if they match
72 check_sender(M = #{user := User = #jid{}, from := From = #jid{}}) ->
73
:-(
case jid:are_bare_equal(User, From) of
74 true ->
75
:-(
M#{check_user => false};
76 false ->
77
:-(
{invalid_sender, <<"Sender's JID is different from the user's JID">>}
78 end;
79 check_sender(M = #{from := From = #jid{}}) ->
80
:-(
M#{user => jid:to_bare(From), check_user => true};
81 check_sender(M = #{user := User = #jid{}}) ->
82
:-(
M#{from => User, check_user => false};
83 check_sender(#{}) ->
84
:-(
{no_sender, <<"Missing sender JID">>}.
85
86 extract_from_jid(M = #{stanza := Stanza}) ->
87
:-(
case exml_query:attr(Stanza, <<"from">>) of
88 undefined ->
89
:-(
M;
90 JidBin ->
91
:-(
case jid:from_binary(JidBin) of
92
:-(
error -> {invalid_sender, <<"Invalid sender JID">>};
93
:-(
Jid -> M#{from => Jid}
94 end
95 end.
96
97 extract_to_jid(M = #{stanza := Stanza}) ->
98
:-(
case exml_query:attr(Stanza, <<"to">>) of
99 undefined ->
100
:-(
{no_recipient, <<"Missing recipient JID">>};
101 JidBin ->
102
:-(
case jid:from_binary(JidBin) of
103
:-(
error -> {invalid_recipient, <<"Invalid recipient JID">>};
104
:-(
Jid -> M#{to => Jid}
105 end
106 end.
107
108 prepare_chat_message(M = #{from := From, to := To, body := Body}) ->
109
:-(
FromBin = jid:to_binary(From),
110
:-(
ToBin = jid:to_binary(To),
111
:-(
M#{stanza => build_chat_message(FromBin, ToBin, Body)}.
112
113 prepare_headline_message(M = #{from := From, to := To, body := Body, subject := Subject}) ->
114
:-(
FromBin = jid:to_binary(From),
115
:-(
ToBin = jid:to_binary(To),
116
:-(
M#{stanza => build_headline_message(FromBin, ToBin, Body, Subject)}.
117
118 prepare_stanza(M = #{stanza := Stanza}) ->
119
:-(
M#{stanza := ensure_id(Stanza)}.
120
121 get_host_type(M = #{user := #jid{lserver = LServer}}) ->
122
:-(
case mongoose_domain_api:get_domain_host_type(LServer) of
123 {ok, HostType} ->
124
:-(
M#{host_type => HostType};
125 {error, not_found} ->
126
:-(
{unknown_user, <<"User's domain does not exist">>}
127 end.
128
129 check_user(M = #{check_user := false}) ->
130
:-(
M;
131 check_user(M = #{check_user := true, user := UserJid, host_type := HostType}) ->
132
:-(
case ejabberd_auth:does_user_exist(HostType, UserJid, stored) of
133 true ->
134
:-(
M;
135 false ->
136
:-(
{unknown_user, <<"User does not exist">>}
137 end.
138
139 send(#{host_type := HostType, from := From, to := To, stanza := Stanza}) ->
140
:-(
Acc = mongoose_acc:new(#{from_jid => From,
141 to_jid => To,
142 location => ?LOCATION,
143 host_type => HostType,
144 lserver => From#jid.lserver,
145 element => Stanza}),
146
:-(
C2SData = mongoose_c2s:create_data(#{host_type => HostType, jid => From}),
147
:-(
Params = mongoose_c2s:hook_arg(C2SData, session_established, internal, Stanza, undefined),
148
:-(
case mongoose_c2s_hooks:user_send_packet(HostType, Acc, Params) of
149 {ok, Acc1} ->
150
:-(
{_, Acc2} = handle_message(HostType, Acc1, Params),
151
:-(
ejabberd_router:route(From, To, Acc2);
152 {stop, Acc1} ->
153
:-(
Acc1
154 end,
155
:-(
{ok, #{<<"id">> => get_id(Stanza)}}.
156
157 lookup_messages(#{user := UserJid, with := WithJid, before := Before, limit := Limit,
158 host_type := HostType}) ->
159
:-(
#jid{luser = LUser, lserver = LServer} = UserJid,
160
:-(
MaxResultLimit = mod_mam_params:max_result_limit(mod_mam_pm, HostType),
161
:-(
Limit2 = min(Limit, MaxResultLimit),
162
:-(
Params = #{archive_id => mod_mam_pm:archive_id(LServer, LUser),
163 owner_jid => UserJid,
164 borders => undefined,
165 rsm => #rsm_in{direction = before, id = undefined}, % last msgs
166 start_ts => undefined,
167 end_ts => Before,
168 now => os:system_time(microsecond),
169 with_jid => WithJid,
170 search_text => undefined,
171 page_size => Limit2,
172 limit_passed => false,
173 max_result_limit => MaxResultLimit,
174 is_simple => true},
175
:-(
{ok, {_, _, Rows}} = mod_mam_pm:lookup_messages(HostType, Params),
176
:-(
{ok, {Rows, Limit2}}.
177
178 do_open_session(#{host_type := HostType, user := JID}) ->
179
:-(
SID = ejabberd_sm:make_new_sid(),
180
:-(
UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
181
:-(
Resource = <<"sse-", UUID/binary>>,
182
:-(
NewJid = jid:replace_resource(JID, Resource),
183
:-(
ejabberd_sm:open_session(HostType, SID, NewJid, 1, #{}),
184
:-(
{ok, #{sid => SID, jid => NewJid, host_type => HostType}}.
185
186 %% Helpers
187
188 -spec build_chat_message(jid:literal_jid(), jid:literal_jid(), binary()) -> exml:element().
189 build_chat_message(From, To, Body) ->
190
:-(
#xmlel{name = <<"message">>,
191 attrs = add_id([{<<"type">>, <<"chat">>}, {<<"from">>, From}, {<<"to">>, To}]),
192 children = [#xmlel{name = <<"body">>,
193 children = [#xmlcdata{content = Body}]}]
194 }.
195
196 -spec build_headline_message(jid:literal_jid(), jid:literal_jid(),
197 binary() | undefined, binary() | undefined) -> exml:element().
198 build_headline_message(From, To, Body, Subject) ->
199
:-(
Children = maybe_cdata_elem(<<"subject">>, Subject) ++
200 maybe_cdata_elem(<<"body">>, Body),
201
:-(
Attrs = add_id([{<<"type">>, <<"headline">>}, {<<"from">>, From}, {<<"to">>, To}]),
202
:-(
#xmlel{name = <<"message">>, attrs = Attrs, children = Children}.
203
204 -spec ensure_id(exml:element()) -> exml:element().
205 ensure_id(Stanza) ->
206
:-(
case get_id(Stanza) of
207
:-(
undefined -> Stanza#xmlel{attrs = add_id(Stanza#xmlel.attrs)};
208
:-(
_ -> Stanza
209 end.
210
211
:-(
maybe_cdata_elem(_, undefined) -> [];
212 maybe_cdata_elem(Name, Text) when is_binary(Text) ->
213
:-(
[cdata_elem(Name, Text)].
214
215 cdata_elem(Name, Text) when is_binary(Name), is_binary(Text) ->
216
:-(
#xmlel{name = Name, children = [#xmlcdata{content = Text}]}.
217
218 add_id(Attrs) ->
219
:-(
[{<<"id">>, mongoose_bin:gen_from_crypto()} | Attrs].
220
221 -spec get_id(exml:element()) -> binary() | undefined.
222 get_id(Stanza) ->
223
:-(
exml_query:attr(Stanza, <<"id">>, undefined).
224
225 fold({_, _} = Result, _) ->
226
:-(
Result;
227 fold(M, [Step | Rest]) when is_map(M) ->
228
:-(
fold(Step(M), Rest).
229
230 handle_message(HostType, Acc, Params) ->
231
:-(
case mongoose_acc:stanza_name(Acc) of
232 <<"message">> ->
233
:-(
mongoose_c2s_hooks:user_send_message(HostType, Acc, Params);
234 _ ->
235
:-(
{ok, Acc}
236 end.
Line Hits Source