./ct_report/coverage/mod_muc_light_codec_legacy.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc_light_codec_legacy.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : MUC Light codec for XEP-0045 compatibility
5 %%% Created : 27 Oct 2015 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6 %%%----------------------------------------------------------------------
7
8 -module(mod_muc_light_codec_legacy).
9 -author('piotr.nosek@erlang-solutions.com').
10
11 -behaviour(mod_muc_light_codec_backend).
12
13 %% API
14 -export([decode/4, encode/5, encode_error/5]).
15
16 -include("mongoose.hrl").
17 -include("jlib.hrl").
18 -include("mod_muc_light.hrl").
19
20 %%====================================================================
21 %% API
22 %%====================================================================
23
24 -spec decode(From :: jid:jid(), To :: jid:jid(),
25 Stanza :: jlib:iq() | exml:element(),
26 Acc :: mongoose_acc:t()) -> mod_muc_light_codec_backend:decode_result().
27 decode(_From, #jid{ luser = ToU } = _To, #xmlel{ name = <<"presence">> } = Stanza, _Acc)
28 when ToU =/= <<>> ->
29 5 case {exml_query:path(Stanza, [{element, <<"x">>}, {attr, <<"xmlns">>}]),
30 exml_query:attr(Stanza, <<"type">>)} of
31 {?NS_MUC, Available} when Available =:= undefined orelse
32 3 Available =:= <<"available">> -> {ok, {set, #create{}}};
33 2 _ -> ignore
34 end;
35 decode(_From, #jid{ lresource = Resource }, _Stanza, _Acc) when Resource =/= <<>> ->
36
:-(
{error, bad_request};
37 decode(_From, _To, #xmlel{ name = <<"message">> } = Stanza, _Acc) ->
38 7 decode_message(Stanza);
39 decode(From, _To, #xmlel{ name = <<"iq">> } = Stanza, _Acc) ->
40 43 decode_iq(From, jlib:iq_query_info(Stanza));
41 decode(From, _To, #iq{} = IQ, _Acc) ->
42 9 decode_iq(From, IQ);
43 decode(_, _, _, _Acc) ->
44
:-(
{error, bad_request}.
45
46 -spec encode(Request :: muc_light_encode_request(),
47 OriginalSender :: jid:jid(), RoomUS :: jid:jid(),
48 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler(),
49 Acc :: mongoose_acc:t()) -> mongoose_acc:t().
50 encode({#msg{} = Msg, AffUsers}, Sender, RoomBareJid, HandleFun, Acc) ->
51 3 US = jid:to_lus(Sender),
52 3 Aff = get_sender_aff(AffUsers, US),
53 3 FromNick = jid:to_binary(jid:to_lus(Sender)),
54 3 {RoomJID, RoomBin} = jids_from_room_with_resource(RoomBareJid, FromNick),
55 3 Attrs = [
56 {<<"id">>, Msg#msg.id},
57 {<<"type">>, <<"groupchat">>},
58 {<<"from">>, RoomBin}
59 ],
60 3 MsgForArch = #xmlel{ name = <<"message">>, attrs = Attrs, children = Msg#msg.children },
61 3 TS = mongoose_acc:timestamp(Acc),
62 3 EventData = #{from_nick => FromNick,
63 from_jid => Sender,
64 room_jid => RoomBareJid,
65 affiliation => Aff,
66 role => mod_muc_light_utils:light_aff_to_muc_role(Aff),
67 timestamp => TS},
68 3 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
69 3 Packet1 = #xmlel{ children = Children }
70 = mongoose_hooks:filter_room_packet(HostType, MsgForArch, EventData),
71 3 lists:foreach(
72 fun({{U, S}, _}) ->
73 9 send_to_aff_user(RoomJID, U, S, <<"message">>, Attrs, Children, HandleFun)
74 end, AffUsers),
75 3 mongoose_acc:update_stanza(#{from_jid => RoomJID,
76 to_jid => RoomBareJid,
77 element => Packet1}, Acc);
78 encode(OtherCase, Sender, RoomBareJid, HandleFun, Acc) ->
79 50 {RoomJID, RoomBin} = jids_from_room_with_resource(RoomBareJid, <<>>),
80 50 case encode_meta(OtherCase, RoomJID, Sender, HandleFun, Acc) of
81 {iq_reply, ID} ->
82 23 IQRes = make_iq_result(RoomBin, jid:to_binary(Sender), ID, <<>>, undefined),
83 23 HandleFun(RoomJID, Sender, IQRes);
84 {iq_reply, XMLNS, Els, ID} ->
85 22 IQRes = make_iq_result(RoomBin, jid:to_binary(Sender), ID, XMLNS, Els),
86 22 HandleFun(RoomJID, Sender, IQRes);
87 noreply ->
88 5 Acc
89 end.
90
91 -spec encode_error(
92 ErrMsg :: tuple(), OrigFrom :: jid:jid(), OrigTo :: jid:jid(),
93 OrigPacket :: exml:element(), Acc :: mongoose_acc:t()) ->
94 mongoose_acc:t().
95 encode_error(_, OrigFrom, OrigTo, #xmlel{ name = <<"presence">> } = OrigPacket, Acc) ->
96 %% The only error case for valid presence is registration-required for room creation
97 1 X = #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}] },
98 1 mod_muc_light_codec_backend:encode_error({error, registration_required}, [X], OrigFrom, OrigTo,
99 OrigPacket, Acc);
100 encode_error(ErrMsg, OrigFrom, OrigTo, OrigPacket, Acc) ->
101 6 mod_muc_light_codec_backend:encode_error(ErrMsg, [], OrigFrom, OrigTo, OrigPacket, Acc).
102
103 %%====================================================================
104 %% Message decoding
105 %%====================================================================
106
107 -spec decode_message(Packet :: exml:element()) ->
108 {ok, muc_light_packet()} | {error, bad_request} | ignore.
109 decode_message(#xmlel{ attrs = Attrs, children = Children }) ->
110 7 decode_message_by_type(lists:keyfind(<<"type">>, 1, Attrs),
111 lists:keyfind(<<"id">>, 1, Attrs), Children).
112
113 -spec decode_message_by_type(Type :: {binary(), binary()} | false,
114 Id :: {binary(), binary()} | false,
115 Children :: [jlib:xmlch()]) ->
116 {ok, msg() | {set, mod_muc_light_room_config:kv()}} | {error, bad_request} | ignore.
117 decode_message_by_type({_, <<"groupchat">>}, _, [#xmlel{ name = <<"subject">> } = SubjectEl]) ->
118 3 {ok, {set, #config{ raw_config = [{<<"subject">>, exml_query:cdata(SubjectEl)}] }}};
119 decode_message_by_type({_, <<"groupchat">>}, Id, Children) ->
120 4 {ok, #msg{ children = Children, id = ensure_id(Id) }};
121 decode_message_by_type({_, <<"error">>}, _, _) ->
122
:-(
ignore;
123 decode_message_by_type(_, _, _) ->
124
:-(
{error, bad_request}.
125
126 -spec ensure_id(Id :: {binary(), binary()} | false) -> binary().
127 1 ensure_id(false) -> mongoose_bin:gen_from_timestamp();
128 3 ensure_id({_, Id}) -> Id.
129
130 %%====================================================================
131 %% IQ decoding
132 %%====================================================================
133
134 -spec decode_iq(From :: jid:jid(), IQ :: jlib:iq()) ->
135 {ok, muc_light_packet() | muc_light_disco() | jlib:iq()} | {error, bad_request} | ignore.
136 decode_iq(_From, #iq{ xmlns = ?NS_MUC_OWNER, type = get, sub_el = _QueryEl, id = ID }) ->
137 9 {ok, {get, #config{ id = ID }}};
138 decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_OWNER, type = set, sub_el = QueryEl, id = ID }) ->
139 7 case exml_query:subelement(QueryEl, <<"destroy">>) of
140 undefined ->
141 6 try parse_config(exml_query:paths(QueryEl, [{element, <<"x">>},
142 {element, <<"field">>}])) of
143 {ok, RawConfig} ->
144 6 {ok, {set, #config{ id = ID, raw_config = RawConfig }}}
145 catch Class:Reason:Stacktrace ->
146
:-(
?LOG_WARNING(#{what => muc_parse_config_failed,
147 from_jid => jid:to_binary(From), iq => IQ,
148
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
149
:-(
{error, bad_request}
150 end;
151 _ ->
152 1 {ok, {set, #destroy{ id = ID }}}
153 end;
154 decode_iq(_From, #iq{ xmlns = ?NS_MUC_ADMIN, type = get, sub_el = _QueryEl, id = ID }) ->
155 3 {ok, {get, #affiliations{ id = ID }}};
156 decode_iq(From, IQ = #iq{ xmlns = ?NS_MUC_ADMIN, type = set, sub_el = QueryEl, id = ID }) ->
157 16 try parse_aff_users(exml_query:subelements(QueryEl, <<"item">>)) of
158 {ok, AffUsers} ->
159 16 {ok, {set, #affiliations{ id = ID, aff_users = AffUsers }}}
160 catch Class:Reason:Stacktrace ->
161
:-(
?LOG_WARNING(#{what => muc_parse_aff_users_failed,
162 from_jid => jid:to_binary(From), iq => IQ,
163
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
164
:-(
{error, bad_request}
165 end;
166 decode_iq(_From, #iq{ xmlns = ?NS_PRIVACY, type = get, id = ID }) ->
167 4 {ok, {get, #blocking{ id = ID }}};
168 decode_iq(From, IQ = #iq{ xmlns = ?NS_PRIVACY, type = set,
169 sub_el = #xmlel{ children = Lists }, id = ID }) ->
170 5 case lists:keyfind([{<<"name">>, ?NS_MUC_LIGHT}], #xmlel.attrs, Lists) of
171 false ->
172
:-(
ignore;
173 List ->
174 5 try parse_blocking_list(exml_query:subelements(List, <<"item">>)) of
175 {ok, BlockingList} ->
176 5 {ok, {set, #blocking{ id = ID, items = BlockingList }}}
177 catch Class:Reason:Stacktrace ->
178
:-(
?LOG_WARNING(#{what => muc_parse_blocking_list_failed,
179 from_jid => jid:to_binary(From), iq => IQ,
180
:-(
class => Class, reason => Reason, stacktrace => Stacktrace})
181 end
182 end;
183 decode_iq(_From, #iq{ xmlns = ?NS_DISCO_ITEMS, type = get, id = ID} = IQ) ->
184 4 {ok, {get, #disco_items{ id = ID, rsm = jlib:rsm_decode(IQ) }}};
185 decode_iq(_From, #iq{ xmlns = ?NS_DISCO_INFO, type = get, id = ID}) ->
186 4 {ok, {get, #disco_info{ id = ID }}};
187 decode_iq(_From, #iq{ type = error }) ->
188
:-(
ignore;
189 decode_iq(_From, #iq{} = IQ) ->
190
:-(
{ok, IQ}.
191
192 %% ------------------ Parsers ------------------
193
194 -spec parse_config(Els :: [jlib:xmlch()]) -> {ok, mod_muc_light_room_config:binary_kv()}.
195 parse_config(Els) ->
196 6 parse_config(Els, []).
197
198 -spec parse_config(Els :: [jlib:xmlch()], ConfigAcc :: mod_muc_light_room_config:binary_kv()) ->
199 {ok, mod_muc_light_room_config:binary_kv()}.
200 parse_config([], ConfigAcc) ->
201 6 {ok, ConfigAcc};
202 parse_config([Field | REls], ConfigAcc) ->
203 14 case {exml_query:attr(Field, <<"var">>),
204 exml_query:path(Field, [{element, <<"value">>}, cdata])} of
205 6 {<<"FORM_TYPE">>, _} -> parse_config(REls, ConfigAcc);
206 8 ConfigKV -> parse_config(REls, [ConfigKV | ConfigAcc])
207 end.
208
209 -spec parse_aff_users(Els :: [jlib:xmlch()]) -> {ok, aff_users()}.
210 parse_aff_users(Els) ->
211 16 parse_aff_users(Els, []).
212
213 -spec parse_aff_users(Els :: [jlib:xmlch()], AffUsersAcc :: aff_users()) -> {ok, aff_users()}.
214 parse_aff_users([], AffUsersAcc) ->
215 16 {ok, AffUsersAcc};
216 parse_aff_users([Item | RItemsEls], AffUsersAcc) ->
217 24 AffBin = exml_query:attr(Item, <<"affiliation">>),
218 24 JIDBin = exml_query:attr(Item, <<"jid">>),
219 24 #jid{} = JID = jid:from_binary(JIDBin),
220 24 Aff = mod_muc_light_utils:b2aff(AffBin),
221 24 parse_aff_users(RItemsEls, [{jid:to_lus(JID), Aff} | AffUsersAcc]).
222
223 -spec parse_blocking_list(Els :: [jlib:xmlch()]) -> {ok, [blocking_item()]}.
224 parse_blocking_list(ItemsEls) ->
225 5 parse_blocking_list(ItemsEls, []).
226
227 -spec parse_blocking_list(Els :: [jlib:xmlch()], ItemsAcc :: [blocking_item()]) ->
228 {ok, [blocking_item()]}.
229 parse_blocking_list([], ItemsAcc) ->
230 5 {ok, ItemsAcc};
231 parse_blocking_list([Item | RItemsEls], ItemsAcc) ->
232 8 JIDBin = exml_query:attr(Item, <<"value">>),
233 8 ActionBin = exml_query:attr(Item, <<"action">>),
234 8 #jid{} = JID = jid:from_binary(JIDBin),
235 8 Action = b2action(ActionBin),
236 8 {What, Who} = case {JID#jid.luser =:= <<>>, JID#jid.lresource =:= <<>>} of
237 {false, true} ->
238 4 {room, JID};
239 {true, false} ->
240 4 {user, jid:from_binary(JID#jid.lresource)}
241 end,
242 8 parse_blocking_list(RItemsEls, [{What, Action, jid:to_lus(Who)} | ItemsAcc]).
243
244 %%====================================================================
245 %% Encoding
246 %%====================================================================
247
248 -spec encode_meta(Request :: muc_light_encode_request(), RoomJID :: jid:jid(),
249 SenderJID :: jid:jid(),
250 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler(),
251 Acc :: mongoose_acc:t()) ->
252 {iq_reply, ID :: binary()} |
253 {iq_reply, XMLNS :: binary(), Els :: [jlib:xmlch()], ID :: binary()} |
254 noreply.
255 encode_meta({get, #disco_info{ id = ID }}, RoomJID, SenderJID, _HandleFun, Acc) ->
256 4 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
257 4 IdentityXML = mongoose_disco:identities_to_xml([identity()]),
258 4 FeatureXML = mongoose_disco:get_muc_features(HostType, SenderJID, RoomJID, <<>>, <<>>,
259 [?NS_MUC]),
260 4 DiscoEls = IdentityXML ++ FeatureXML,
261 4 {iq_reply, ?NS_DISCO_INFO, DiscoEls, ID};
262 encode_meta({get, #disco_items{ rooms = Rooms, id = ID, rsm = RSMOut }},
263 _RoomJID, _SenderJID, _HandleFun, _Acc) ->
264 3 DiscoEls = [ #xmlel{ name = <<"item">>,
265 attrs = [{<<"jid">>, <<RoomU/binary, $@, RoomS/binary>>},
266 {<<"name">>, RoomName}] }
267 3 || {{RoomU, RoomS}, RoomName, _RoomVersion} <- Rooms ],
268 3 {iq_reply, ?NS_DISCO_ITEMS, jlib:rsm_encode(RSMOut) ++ DiscoEls, ID};
269 encode_meta({get, #config{} = Config}, _RoomJID, _SenderJID, _HandleFun, _Acc) ->
270 9 ConfigEls = [ jlib:form_field({K, <<"text-single">>, V, K})
271 9 || {K, V} <- Config#config.raw_config ],
272 9 XEl = #xmlel{ name = <<"x">>,
273 attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
274 children = [
275 kv_to_el(<<"title">>, <<"Configuration form for the room">>),
276 jlib:form_field({<<"FORM_TYPE">>, <<"hidden">>,
277 <<"http://jabber.org/protocol/muc#roomconfig">>})
278 | ConfigEls] },
279 9 {iq_reply, ?NS_MUC_OWNER, [XEl], Config#config.id};
280 encode_meta({get, #affiliations{} = Affs}, _RoomJID, _SenderJID, _HandleFun, _Acc) ->
281 3 AffEls = [ aff_user_to_item(AffUser) || AffUser <- Affs#affiliations.aff_users ],
282 3 {iq_reply, ?NS_MUC_ADMIN, AffEls, Affs#affiliations.id};
283 encode_meta({set, #affiliations{} = Affs, OldAffUsers, NewAffUsers},
284 RoomJID, SenderJID, HandleFun, _Acc) ->
285 13 bcast_aff_messages(RoomJID, OldAffUsers, NewAffUsers, SenderJID,
286 Affs#affiliations.aff_users, HandleFun),
287 13 {iq_reply, Affs#affiliations.id};
288 encode_meta({get, #blocking{} = Blocking}, SenderBareJID, _SenderJID, _HandleFun, Acc) ->
289 3 HostType = mod_muc_light_utils:acc_to_host_type(Acc),
290 3 ServerHost = SenderBareJID#jid.lserver,
291 3 MUCHost = mongoose_subdomain_utils:get_fqdn(mod_muc_light:subdomain_pattern(HostType), ServerHost),
292 3 BlockingEls = [ blocking_to_el(BlockingItem, MUCHost)
293 3 || BlockingItem <- Blocking#blocking.items ],
294 3 Blocklist = #xmlel{ name = <<"list">>, attrs = [{<<"name">>, ?NS_MUC_LIGHT}],
295 children = BlockingEls },
296 3 {iq_reply, ?NS_PRIVACY, [Blocklist], Blocking#blocking.id};
297 encode_meta({set, #blocking{ id = ID }}, _RoomJID, _SenderJID, _HandleFun, _Acc) ->
298 4 {iq_reply, ID};
299 encode_meta({set, #create{} = Create, _UniqueRequested}, RoomJID, _SenderJID, HandleFun, _Acc) ->
300 2 [{{ToU, ToS}, CreatorAff}] = Create#create.aff_users,
301 2 ToBin = jid:to_binary({ToU, ToS, <<>>}),
302 2 {From, FromBin} = jids_from_room_with_resource(RoomJID, ToBin),
303 2 Attrs = [{<<"from">>, FromBin}],
304 2 {AffBin, RoleBin} = case CreatorAff of
305 1 owner -> {<<"owner">>, <<"moderator">>};
306 1 member -> {<<"member">>, <<"participant">>}
307 end,
308 2 NotifEls = [ #xmlel{ name = <<"item">>,
309 attrs = [{<<"affiliation">>, AffBin}, {<<"role">>, RoleBin}]},
310 status(<<"110">>), status(<<"201">>) ],
311 2 Children = envelope(?NS_MUC_USER, NotifEls),
312
313 2 send_to_aff_user(From, ToU, ToS, <<"presence">>, Attrs, Children, HandleFun),
314 2 noreply;
315 encode_meta({set, #destroy{ id = ID }, AffUsers}, RoomJID, _SenderJID, HandleFun, _Acc) ->
316 1 lists:foreach(
317 fun({{U, S}, _}) ->
318 3 FromJID = jid:replace_resource(RoomJID, jid:to_binary({U, S, <<>>})),
319 3 Attrs = [{<<"from">>, jid:to_binary(FromJID)},
320 {<<"type">>, <<"unavailable">>}],
321 3 Children = [ #xmlel{ name = <<"item">>,
322 attrs = [{<<"affiliation">>, <<"none">>},
323 {<<"role">>, <<"none">>}] },
324 #xmlel{ name = <<"destroy">> } ],
325 3 send_to_aff_user(FromJID, U, S, <<"presence">>, Attrs,
326 envelope(?NS_MUC_USER, Children), HandleFun)
327 end, AffUsers),
328
329 1 {iq_reply, ID};
330 encode_meta({set, #config{ raw_config = [{<<"subject">>, Subject}], id = ID }, AffUsers},
331 RoomJID, _SenderJID, HandleFun, _Acc) ->
332 3 Attrs = [
333 {<<"id">>, ID},
334 {<<"type">>, <<"groupchat">>},
335 {<<"from">>, jid:to_binary(RoomJID)}
336 ],
337 3 SubjectEl = #xmlel{ name = <<"subject">>, children = [ #xmlcdata{ content = Subject } ] },
338 3 lists:foreach(
339 fun({{U, S}, _}) ->
340 9 send_to_aff_user(RoomJID, U, S, <<"message">>, Attrs, [SubjectEl], HandleFun)
341 end, AffUsers),
342 3 noreply;
343 encode_meta({set, #config{} = Config, AffUsers}, RoomJID, _SenderJID, HandleFun, _Acc) ->
344 5 Attrs = [{<<"id">>, Config#config.id},
345 {<<"from">>, jid:to_binary(RoomJID)},
346 {<<"type">>, <<"groupchat">>}],
347 5 ConfigNotif = envelope(?NS_MUC_USER, [status(<<"104">>)]),
348 5 lists:foreach(
349 fun({{U, S}, _}) ->
350 15 send_to_aff_user(RoomJID, U, S, <<"message">>, Attrs, ConfigNotif, HandleFun)
351 end, AffUsers),
352
353 5 {iq_reply, Config#config.id}.
354
355 %% --------------------------- Helpers ---------------------------
356
357 -spec identity() -> mongoose_disco:identity().
358 identity() ->
359 4 #{category => <<"conference">>, type => <<"text">>, name => <<"MUC Light (legacy)">>}.
360
361 -spec aff_user_to_item(aff_user()) -> exml:element().
362 aff_user_to_item({User, Aff}) ->
363 44 UserBin = jid:to_binary(User),
364 44 {RoleBin, NickEl} = case Aff of
365 6 owner -> {<<"moderator">>, [{<<"nick">>, UserBin}]};
366 14 member -> {<<"participant">>, [{<<"nick">>, UserBin}]};
367 24 none -> {<<"none">>, []}
368 end,
369 44 #xmlel{ name = <<"item">>,
370 attrs = [{<<"affiliation">>, mod_muc_light_utils:aff2b(Aff)},
371 {<<"jid">>, UserBin},
372 {<<"role">>, RoleBin} | NickEl] }.
373
374 -spec blocking_to_el(BlockingItem :: blocking_item(), Service :: binary()) -> exml:element().
375 blocking_to_el({What, Action, {WhoU, WhoS}}, Service) ->
376 2 WhoBin = jid:to_binary({WhoU, WhoS, <<>>}),
377 2 Value = case What of
378 1 room -> WhoBin;
379 1 user -> <<Service/binary, $/, WhoBin/binary>>
380 end,
381 2 #xmlel{ name = <<"item">>,
382 attrs = [
383 {<<"type">>, <<"jid">>},
384 {<<"value">>, Value},
385 {<<"action">>, action2b(Action)},
386 {<<"order">>, <<"1">>}
387 ] }.
388
389 -spec kv_to_el(binary(), binary()) -> exml:element().
390 kv_to_el(Key, Value) ->
391 9 #xmlel{ name = Key, children = [#xmlcdata{ content = Value }] }.
392
393 -spec envelope(XMLNS :: binary(), Children :: [jlib:xmlch()]) -> [jlib:xmlch()].
394 envelope(XMLNS, Children) ->
395 49 [ #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, XMLNS}], children = Children } ].
396
397 -spec bcast_aff_messages(Room :: jid:jid(), OldAffUsers :: aff_users(),
398 NewAffUsers :: aff_users(), SenderJID :: jid:jid(),
399 ChangedAffUsers :: aff_users(),
400 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok.
401 bcast_aff_messages(_, [], [], _, _, _) ->
402 13 ok;
403 bcast_aff_messages(Room, [{User, _} | ROldAffUsers], [], SenderJID, ChangedAffUsers, HandleFun) ->
404 7 msg_to_leaving_user(Room, User, HandleFun),
405 7 bcast_aff_messages(Room, ROldAffUsers, [], SenderJID, ChangedAffUsers, HandleFun);
406 bcast_aff_messages(Room, [{{ToU, ToS} = User, _} | ROldAffUsers], [{User, _} | RNewAffUsers],
407 SenderJID, ChangedAffUsers, HandleFun) ->
408 15 lists:foreach(
409 fun({{ChangedU, ChangedS}, NewAff} = ChangedAffUser) ->
410 24 ChangedUserBin = jid:to_binary({ChangedU, ChangedS, <<>>}),
411 24 {From, FromBin} = jids_from_room_with_resource(Room,
412 ChangedUserBin),
413 24 Attrs0 = [{<<"from">>, FromBin}],
414 24 ElToEnvelope0 = aff_user_to_item(ChangedAffUser),
415 24 {Attrs, ElsToEnvelope} = case NewAff of
416 13 none -> {[{<<"type">>, <<"unavailable">>} | Attrs0],
417 [ElToEnvelope0, status(<<"321">>)]};
418 11 _ -> {Attrs0, [ElToEnvelope0]}
419 end,
420 24 Children = envelope(?NS_MUC_USER, ElsToEnvelope),
421 24 send_to_aff_user(From, ToU, ToS, <<"presence">>, Attrs, Children, HandleFun)
422 end, ChangedAffUsers),
423 15 bcast_aff_messages(Room, ROldAffUsers, RNewAffUsers, SenderJID, ChangedAffUsers, HandleFun);
424 bcast_aff_messages(Room, [{User1, _} | ROldAffUsers], [{User2, _} | _] = NewAffUsers,
425 SenderJID, ChangedAffUsers, HandleFun) when User1 < User2 ->
426 4 msg_to_leaving_user(Room, User1, HandleFun),
427 4 bcast_aff_messages(Room, ROldAffUsers, NewAffUsers, SenderJID, ChangedAffUsers, HandleFun);
428 bcast_aff_messages(Room, OldAffUsers, [{{ToU, ToS}, _} | RNewAffUsers],
429 SenderJID, ChangedAffUsers, HandleFun) ->
430 4 InviterBin = jid:to_binary({SenderJID#jid.luser, SenderJID#jid.lserver, <<>>}),
431 4 RoomBin = jid:to_binary(jid:to_lower(Room)),
432 4 InviteEl = #xmlel{ name = <<"invite">>,
433 attrs = [{<<"from">>, InviterBin}] },
434 4 NotifForNewcomer = envelope(?NS_MUC_USER, [InviteEl]),
435 4 send_to_aff_user(Room, ToU, ToS, <<"message">>, [{<<"from">>, RoomBin}],
436 NotifForNewcomer, HandleFun),
437 4 bcast_aff_messages(Room, OldAffUsers, RNewAffUsers, SenderJID, ChangedAffUsers, HandleFun).
438
439 -spec msg_to_leaving_user(Room :: jid:jid(), User :: jid:simple_bare_jid(),
440 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok.
441 msg_to_leaving_user(Room, {ToU, ToS} = User, HandleFun) ->
442 11 UserBin = jid:to_binary({ToU, ToS, <<>>}),
443 11 {From, FromBin} = jids_from_room_with_resource(Room, UserBin),
444 11 Attrs = [{<<"from">>, FromBin},
445 {<<"type">>, <<"unavailable">>}],
446 11 NotifForLeaving = envelope(?NS_MUC_USER, [ aff_user_to_item({User, none}), status(<<"321">>) ]),
447 11 send_to_aff_user(From, ToU, ToS, <<"presence">>, Attrs, NotifForLeaving, HandleFun).
448
449 -spec send_to_aff_user(From :: jid:jid(), ToU :: jid:luser(), ToS :: jid:lserver(),
450 Name :: binary(), Attrs :: [{binary(), binary()}],
451 Children :: [jlib:xmlch()],
452 HandleFun :: mod_muc_light_codec_backend:encoded_packet_handler()) -> ok.
453 send_to_aff_user(From, ToU, ToS, Name, Attrs, Children, HandleFun) ->
454 77 To = jid:make_noprep(ToU, ToS, <<>>),
455 77 ToBin = jid:to_binary({ToU, ToS, <<>>}),
456 77 Packet = #xmlel{ name = Name, attrs = [{<<"to">>, ToBin} | Attrs],
457 children = Children },
458 77 HandleFun(From, To, Packet).
459
460 -spec jids_from_room_with_resource(Room :: jid:jid(), binary()) ->
461 {jid:jid(), binary()}.
462 jids_from_room_with_resource(RoomJID, Resource) ->
463 90 From = jid:replace_resource(RoomJID, Resource),
464 90 FromBin = jid:to_binary(jid:to_lower(From)),
465 90 {From, FromBin}.
466
467 -spec make_iq_result(FromBin :: binary(), ToBin :: binary(), ID :: binary(),
468 XMLNS :: binary(), Els :: [jlib:xmlch()] | undefined) -> exml:element().
469 make_iq_result(FromBin, ToBin, ID, XMLNS, Els) ->
470 45 Attrs = [
471 {<<"from">>, FromBin},
472 {<<"to">>, ToBin},
473 {<<"id">>, ID},
474 {<<"type">>, <<"result">>}
475 ],
476 45 Query = make_query_el(XMLNS, Els),
477 45 #xmlel{ name = <<"iq">>, attrs = Attrs, children = Query }.
478
479 -spec make_query_el(binary(), [jlib:xmlch()] | undefined) -> [exml:element()].
480 make_query_el(_, undefined) ->
481 23 [];
482 make_query_el(XMLNS, Els) ->
483 22 [#xmlel{ name = <<"query">>, attrs = [{<<"xmlns">>, XMLNS}], children = Els }].
484
485 -spec status(Code :: binary()) -> exml:element().
486 33 status(Code) -> #xmlel{ name = <<"status">>, attrs = [{<<"code">>, Code}] }.
487
488 %%====================================================================
489 %% Common helpers and internal functions
490 %%====================================================================
491
492 -spec b2action(ActionBin :: binary()) -> atom().
493 2 b2action(<<"allow">>) -> allow;
494 6 b2action(<<"deny">>) -> deny.
495
496 -spec action2b(Action :: atom()) -> binary().
497
:-(
action2b(allow) -> <<"allow">>;
498 2 action2b(deny) -> <<"deny">>.
499
500 get_sender_aff(Users, US) ->
501 3 case lists:keyfind(US, 1, Users) of
502 3 {US, Aff} -> Aff;
503
:-(
_ -> undefined
504 end.
Line Hits Source