./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 51 M = #{user => User, from => From, to => To, body => Body},
24 51 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 8 M = #{user => User, from => From, to => To, body => Body, subject => Subject},
33 8 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 16 M = #{user => User, stanza => Stanza},
41 16 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 48 M = #{user => User, with => With, before => Before, limit => Limit, check_user => CheckUser},
50 48 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 8 M = #{user => User, check_user => CheckUser},
57 8 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 3 Acc = mongoose_acc:new(
62 #{location => ?LOCATION,
63 lserver => S,
64 host_type => HostType,
65 element => undefined}),
66 3 ejabberd_sm:close_session(Acc, Sid, Jid, normal, #{}),
67 3 {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 7 case jid:are_bare_equal(User, From) of
74 true ->
75 4 M#{check_user => false};
76 false ->
77 3 {invalid_sender, <<"Sender's JID is different from the user's JID">>}
78 end;
79 check_sender(M = #{from := From = #jid{}}) ->
80 54 M#{user => jid:to_bare(From), check_user => true};
81 check_sender(M = #{user := User = #jid{}}) ->
82 10 M#{from => User, check_user => false};
83 check_sender(#{}) ->
84 1 {no_sender, <<"Missing sender JID">>}.
85
86 extract_from_jid(M = #{stanza := Stanza}) ->
87 16 case exml_query:attr(Stanza, <<"from">>) of
88 undefined ->
89 2 M;
90 JidBin ->
91 14 case jid:from_binary(JidBin) of
92 1 error -> {invalid_sender, <<"Invalid sender JID">>};
93 13 Jid -> M#{from => Jid}
94 end
95 end.
96
97 extract_to_jid(M = #{stanza := Stanza}) ->
98 15 case exml_query:attr(Stanza, <<"to">>) of
99 undefined ->
100 1 {no_recipient, <<"Missing recipient JID">>};
101 JidBin ->
102 14 case jid:from_binary(JidBin) of
103 1 error -> {invalid_recipient, <<"Invalid recipient JID">>};
104 13 Jid -> M#{to => Jid}
105 end
106 end.
107
108 prepare_chat_message(M = #{from := From, to := To, body := Body}) ->
109 48 FromBin = jid:to_binary(From),
110 48 ToBin = jid:to_binary(To),
111 48 M#{stanza => build_chat_message(FromBin, ToBin, Body)}.
112
113 prepare_headline_message(M = #{from := From, to := To, body := Body, subject := Subject}) ->
114 7 FromBin = jid:to_binary(From),
115 7 ToBin = jid:to_binary(To),
116 7 M#{stanza => build_headline_message(FromBin, ToBin, Body, Subject)}.
117
118 prepare_stanza(M = #{stanza := Stanza}) ->
119 5 M#{stanza := ensure_id(Stanza)}.
120
121 get_host_type(M = #{user := #jid{lserver = LServer}}) ->
122 124 case mongoose_domain_api:get_domain_host_type(LServer) of
123 {ok, HostType} ->
124 120 M#{host_type => HostType};
125 {error, not_found} ->
126 4 {unknown_user, <<"User's domain does not exist">>}
127 end.
128
129 check_user(M = #{check_user := false}) ->
130 33 M;
131 check_user(M = #{check_user := true, user := UserJid, host_type := HostType}) ->
132 87 case ejabberd_auth:does_user_exist(HostType, UserJid, stored) of
133 true ->
134 78 M;
135 false ->
136 9 {unknown_user, <<"User does not exist">>}
137 end.
138
139 send(#{host_type := HostType, from := From, to := To, stanza := Stanza}) ->
140 60 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 60 C2SData = mongoose_c2s:create_data(#{host_type => HostType, jid => From}),
147 60 Params = mongoose_c2s:hook_arg(C2SData, session_established, internal, Stanza, undefined),
148 60 case mongoose_c2s_hooks:user_send_packet(HostType, Acc, Params) of
149 {ok, Acc1} ->
150 60 {_, Acc2} = handle_message(HostType, Acc1, Params),
151 60 ejabberd_router:route(From, To, Acc2);
152 {stop, Acc1} ->
153
:-(
Acc1
154 end,
155 60 {ok, #{<<"id">> => get_id(Stanza)}}.
156
157 lookup_messages(#{user := UserJid, with := WithJid, before := Before, limit := Limit,
158 host_type := HostType}) ->
159 44 #jid{luser = LUser, lserver = LServer} = UserJid,
160 44 MaxResultLimit = mod_mam_params:max_result_limit(mod_mam_pm, HostType),
161 44 Limit2 = min(Limit, MaxResultLimit),
162 44 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 44 {ok, {_, _, Rows}} = mod_mam_pm:lookup_messages(HostType, Params),
176 44 {ok, {Rows, Limit2}}.
177
178 do_open_session(#{host_type := HostType, user := JID}) ->
179 7 SID = ejabberd_sm:make_new_sid(),
180 7 UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
181 7 Resource = <<"sse-", UUID/binary>>,
182 7 NewJid = jid:replace_resource(JID, Resource),
183 7 ejabberd_sm:open_session(HostType, SID, NewJid, 1, #{}),
184 7 {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 48 #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 7 Children = maybe_cdata_elem(<<"subject">>, Subject) ++
200 maybe_cdata_elem(<<"body">>, Body),
201 7 Attrs = add_id([{<<"type">>, <<"headline">>}, {<<"from">>, From}, {<<"to">>, To}]),
202 7 #xmlel{name = <<"message">>, attrs = Attrs, children = Children}.
203
204 -spec ensure_id(exml:element()) -> exml:element().
205 ensure_id(Stanza) ->
206 5 case get_id(Stanza) of
207 4 undefined -> Stanza#xmlel{attrs = add_id(Stanza#xmlel.attrs)};
208 1 _ -> Stanza
209 end.
210
211 6 maybe_cdata_elem(_, undefined) -> [];
212 maybe_cdata_elem(Name, Text) when is_binary(Text) ->
213 8 [cdata_elem(Name, Text)].
214
215 cdata_elem(Name, Text) when is_binary(Name), is_binary(Text) ->
216 8 #xmlel{name = Name, children = [#xmlcdata{content = Text}]}.
217
218 add_id(Attrs) ->
219 59 [{<<"id">>, mongoose_bin:gen_from_crypto()} | Attrs].
220
221 -spec get_id(exml:element()) -> binary() | undefined.
222 get_id(Stanza) ->
223 65 exml_query:attr(Stanza, <<"id">>, undefined).
224
225 fold({_, _} = Result, _) ->
226 131 Result;
227 fold(M, [Step | Rest]) when is_map(M) ->
228 518 fold(Step(M), Rest).
229
230 handle_message(HostType, Acc, Params) ->
231 60 case mongoose_acc:stanza_name(Acc) of
232 <<"message">> ->
233 60 mongoose_c2s_hooks:user_send_message(HostType, Acc, Params);
234 _ ->
235
:-(
{ok, Acc}
236 end.
Line Hits Source