diff options
author | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-10-17 13:37:23 +0300 |
---|---|---|
committer | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-10-17 13:37:23 +0300 |
commit | 67720c77137da2e80907f67dccc96fcbdb44c3bf (patch) | |
tree | d3b44d577312ee0f94c46d3c03691279d329f16d /src/mod_muc_room.erl | |
parent | Update riakc to support r19 (diff) |
Add more MUC tests
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r-- | src/mod_muc_room.erl | 556 |
1 files changed, 261 insertions, 295 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 52401f83..ce6851bc 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -76,11 +76,6 @@ -type fsm_stop() :: {stop, normal, state()}. -type fsm_next() :: {next_state, normal_state, state()}. -type fsm_transition() :: fsm_stop() | fsm_next(). --type history_element() :: {binary(), %% nick - message(), %% message itself - boolean(), %% have subject - erlang:timestamp(), - non_neg_integer()}. -export_type([state/0]). @@ -252,7 +247,8 @@ normal_state({route, From, <<"">>, ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), {next_state, normal_state, StateData}; false when Type /= error -> - handle_roommessage_from_nonparticipant(Packet, StateData, From); + handle_roommessage_from_nonparticipant(Packet, StateData, From), + {next_state, normal_state, StateData}; false -> {next_state, normal_state, StateData} end; @@ -279,9 +275,6 @@ normal_state({route, From, <<"">>, process_iq_owner(From, IQ, StateData); ?NS_DISCO_INFO when SubEl#disco_info.node == <<>> -> process_iq_disco_info(From, IQ, StateData); - ?NS_DISCO_INFO -> - Txt = <<"Disco info is not available for this node">>, - {error, xmpp:err_service_unavailable(Txt, Lang)}; ?NS_DISCO_ITEMS -> process_iq_disco_items(From, IQ, StateData); ?NS_VCARD -> @@ -291,7 +284,9 @@ normal_state({route, From, <<"">>, ?NS_CAPTCHA -> process_iq_captcha(From, IQ, StateData); _ -> - {error, xmpp:err_feature_not_implemented()} + Txt = <<"The feature requested is not " + "supported by the conference">>, + {error, xmpp:err_service_unavailable(Txt, Lang)} end, {IQRes, NewStateData} = case Res1 of @@ -312,8 +307,12 @@ normal_state({route, From, <<"">>, ok end, case NewStateData of - stop -> {stop, normal, StateData}; - _ -> {next_state, normal_state, NewStateData} + stop -> + {stop, normal, StateData}; + _ when NewStateData#state.just_created -> + close_room_if_temporary_and_empty(NewStateData); + _ -> + {next_state, normal_state, NewStateData} end end catch _:{xmpp_codec, Why} -> @@ -324,7 +323,10 @@ normal_state({route, From, <<"">>, normal_state({route, From, <<"">>, #iq{} = IQ}, StateData) -> Err = xmpp:err_bad_request(), ejabberd_router:route_error(StateData#state.jid, From, IQ, Err), - {next_state, normal_state, StateData}; + case StateData#state.just_created of + true -> {stop, normal, StateData}; + false -> {next_state, normal_state, StateData} + end; normal_state({route, From, Nick, #presence{} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = p1_time_compat:system_time(micro_seconds), @@ -530,8 +532,14 @@ handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) -> {reply, {ok, NewStateData}, StateName, NewStateData}; handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) -> - NSD = process_item_change(Item, StateData, UJID), - {reply, {ok, NSD}, StateName, NSD}; + case process_item_change(Item, StateData, UJID) of + {error, _} = Err -> + {reply, Err, StateName, StateData}; + NSD -> + {reply, {ok, NSD}, StateName, NSD} + end; +handle_sync_event({is_subscriber, From}, _From, StateName, StateData) -> + {reply, is_subscriber(From, StateData), StateName, StateData}; handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. @@ -763,7 +771,7 @@ process_normal_message(From, #message{lang = Lang} = Pkt, StateData) -> (#muc_user{invites = [I]}, _) -> {ok, I}; (#muc_user{invites = [_|_]}, _) -> - Txt = <<"Multiple <invite/> elements are not allowed">>, + Txt = <<"Multiple invitations are not allowed">>, {error, xmpp:err_resource_constraint(Txt, Lang)}; (#xdata{type = submit, fields = Fs}, _) -> try {ok, muc_request:decode(Fs)} @@ -835,7 +843,7 @@ process_voice_request(From, Pkt, StateData) -> {ok, _, _} -> ErrText = <<"Please, wait for a while before sending " "new voice request">>, - Err = xmpp:err_not_acceptable(ErrText, Lang), + Err = xmpp:err_resource_constraint(ErrText, Lang), ejabberd_router:route_error( StateData#state.jid, From, Pkt, Err), StateData#state{last_voice_request_time = Times} @@ -1147,10 +1155,9 @@ decide_fate_message(#message{type = error} = Msg, PD = case check_error_kick(Err) of %% If this is an error stanza and its condition matches a criteria true -> - Reason = - io_lib:format("This participant is considered a ghost " - "and is expulsed: ~s", - [jid:to_string(From)]), + Reason = str:format("This participant is considered a ghost " + "and is expulsed: ~s", + [jid:to_string(From)]), {expulse_sender, Reason}; false -> continue_delivery end, @@ -1198,7 +1205,7 @@ get_error_condition(undefined) -> make_reason(Packet, From, StateData, Reason1) -> {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users), Condition = get_error_condition(xmpp:get_error(Packet)), - iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])). + str:format(Reason1, [FromNick, Condition]). -spec expulse_participant(stanza(), jid(), state(), binary()) -> state(). @@ -1816,11 +1823,9 @@ add_new_user(From, Nick, Packet, StateData) -> Nodes, StateData)), send_existing_presences(From, NewState), send_initial_presence(From, NewState, StateData), - Shift = count_stanza_shift(Nick, Packet, NewState), - case send_history(From, Shift, NewState) of - true -> ok; - _ -> send_subject(From, StateData) - end, + History = get_history(Nick, Packet, NewState), + send_history(From, History, NewState), + send_subject(From, StateData), NewState; true -> add_online_user(From, Nick, none, @@ -1963,84 +1968,43 @@ extract_password(Packet) -> false end. --spec count_stanza_shift(binary(), stanza(), state()) -> non_neg_integer(). -count_stanza_shift(Nick, Packet, StateData) -> - case xmpp:get_subtag(Packet, #muc_history{}) of - #muc_history{since = Since, - seconds = Seconds, - maxstanzas = MaxStanzas, - maxchars = MaxChars} -> - HL = lqueue_to_list(StateData#state.history), - Shift0 = case Since of - undefined -> 0; - _ -> - Sin = calendar:datetime_to_gregorian_seconds( - calendar:now_to_datetime(Since)), - count_seconds_shift(Sin, HL) - end, - Shift1 = case Seconds of - undefined -> 0; - _ -> - Sec = calendar:datetime_to_gregorian_seconds( - calendar:universal_time()) - Seconds, - count_seconds_shift(Sec, HL) - end, - Shift2 = case MaxStanzas of - undefined -> 0; - _ -> count_maxstanzas_shift(MaxStanzas, HL) - end, - Shift3 = case MaxChars of - undefined -> 0; - _ -> count_maxchars_shift(Nick, MaxChars, HL) - end, - lists:max([Shift0, Shift1, Shift2, Shift3]); - false -> - 0 - end. - --spec count_seconds_shift(non_neg_integer(), - [history_element()]) -> non_neg_integer(). -count_seconds_shift(Seconds, HistoryList) -> - lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject, - TimeStamp, _Size}) -> - T = - calendar:datetime_to_gregorian_seconds(TimeStamp), - if T < Seconds -> 1; - true -> 0 - end - end, - HistoryList)). - --spec count_maxstanzas_shift(non_neg_integer(), - [history_element()]) -> non_neg_integer(). -count_maxstanzas_shift(MaxStanzas, HistoryList) -> - S = length(HistoryList) - MaxStanzas, - if S =< 0 -> 0; - true -> S - end. - --spec count_maxchars_shift(binary(), non_neg_integer(), - [history_element()]) -> integer(). -count_maxchars_shift(Nick, MaxSize, HistoryList) -> - NLen = byte_size(Nick) + 1, - Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject, - _TimeStamp, Size}) -> - Size + NLen - end, - HistoryList), - calc_shift(MaxSize, Sizes). - --spec calc_shift(non_neg_integer(), [non_neg_integer()]) -> integer(). -calc_shift(MaxSize, Sizes) -> - Total = lists:sum(Sizes), - calc_shift(MaxSize, Total, 0, Sizes). - --spec calc_shift(non_neg_integer(), integer(), integer(), - [non_neg_integer()]) -> integer(). -calc_shift(_MaxSize, _Size, Shift, []) -> Shift; -calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> - if MaxSize >= Size -> Shift; - true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes) +-spec get_history(binary(), stanza(), state()) -> lqueue(). +get_history(Nick, Packet, #state{history = History}) -> + case xmpp:get_subtag(Packet, #muc{}) of + #muc{history = #muc_history{} = MUCHistory} -> + Now = p1_time_compat:timestamp(), + Q = History#lqueue.queue, + {NewQ, Len} = filter_history(Q, MUCHistory, Now, Nick, queue:new(), 0, 0), + History#lqueue{queue = NewQ, len = Len}; + _ -> + History + end. + +-spec filter_history(?TQUEUE, muc_history(), erlang:timestamp(), binary(), + ?TQUEUE, non_neg_integer(), non_neg_integer()) -> + {?TQUEUE, non_neg_integer()}. +filter_history(Queue, #muc_history{since = Since, + seconds = Seconds, + maxstanzas = MaxStanzas, + maxchars = MaxChars} = MUC, + Now, Nick, AccQueue, NumStanzas, NumChars) -> + case queue:out_r(Queue) of + {{value, {_, _, _, TimeStamp, Size} = Elem}, NewQueue} -> + NowDiff = timer:now_diff(Now, TimeStamp) div 1000000, + Chars = Size + byte_size(Nick) + 1, + if (NumStanzas < MaxStanzas) andalso + (TimeStamp > Since) andalso + (NowDiff =< Seconds) andalso + (NumChars + Chars =< MaxChars) -> + filter_history(NewQueue, MUC, Now, Nick, + queue:in_r(Elem, AccQueue), + NumStanzas + 1, + NumChars + Chars); + true -> + {AccQueue, NumStanzas} + end; + {empty, _} -> + {AccQueue, NumStanzas} end. -spec is_room_overcrowded(state()) -> boolean(). @@ -2166,19 +2130,20 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> end, lists:foreach( fun({LUJID, Info}) -> - {Role, Presence} = if LNJID == LUJID -> {Role0, Presence0}; + IsSelfPresence = LNJID == LUJID, + {Role, Presence} = if IsSelfPresence -> {Role0, Presence0}; true -> {Role1, Presence1} end, Item0 = #muc_item{affiliation = Affiliation, role = Role}, Item1 = case Info#user.role == moderator orelse (StateData#state.config)#config.anonymous - == false of + == false orelse IsSelfPresence of true -> Item0#muc_item{jid = RealJID}; false -> Item0 end, Item = Item1#muc_item{reason = Reason}, - StatusCodes = status_codes(IsInitialPresence, NJID, Info, + StatusCodes = status_codes(IsInitialPresence, IsSelfPresence, StateData), Pres = if Presence == undefined -> #presence{}; true -> Presence @@ -2303,30 +2268,26 @@ send_nick_changing(JID, OldNick, StateData, StateData#state.users), Affiliation = get_affiliation(JID, StateData), lists:foreach( - fun({_LJID, Info}) when Presence /= undefined -> + fun({LJID, Info}) when Presence /= undefined -> + IsSelfPresence = LJID == jid:tolower(JID), Item0 = #muc_item{affiliation = Affiliation, role = Role}, - Item1 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false of - true -> Item0#muc_item{jid = RealJID, nick = Nick}; - false -> Item0#muc_item{nick = Nick} - end, - Item2 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false of - true -> Item0#muc_item{jid = RealJID}; - false -> Item0 - end, - Status110 = case JID == Info#user.jid of + Item = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false orelse IsSelfPresence of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Status110 = case IsSelfPresence of true -> [110]; false -> [] end, - Packet1 = #presence{type = unavailable, - sub_els = [#muc_user{ - items = [Item1], - status_codes = [303|Status110]}]}, + Packet1 = #presence{ + type = unavailable, + sub_els = [#muc_user{ + items = [Item#muc_item{nick = Nick}], + status_codes = [303|Status110]}]}, Packet2 = xmpp:set_subtag(Presence, - #muc_user{items = [Item2], + #muc_user{items = [Item], status_codes = Status110}), if SendOldUnavailable -> send_wrapped( @@ -2364,12 +2325,12 @@ maybe_send_affiliation(JID, Affiliation, StateData) -> true -> ok; % The new affiliation is published via presence. false -> - send_affiliation(LJID, Affiliation, StateData) + send_affiliation(JID, Affiliation, StateData) end. --spec send_affiliation(ljid(), affiliation(), state()) -> ok. -send_affiliation(LJID, Affiliation, StateData) -> - Item = #muc_item{jid = jid:make(LJID), +-spec send_affiliation(jid(), affiliation(), state()) -> ok. +send_affiliation(JID, Affiliation, StateData) -> + Item = #muc_item{jid = JID, affiliation = Affiliation, role = none}, Message = #message{id = randoms:get_string(), @@ -2388,8 +2349,8 @@ send_affiliation(LJID, Affiliation, StateData) -> StateData#state.server_host, Recipients, Message). --spec status_codes(boolean(), jid(), #user{}, state()) -> [pos_integer()]. -status_codes(IsInitialPresence, JID, #user{jid = JID}, StateData) -> +-spec status_codes(boolean(), boolean(), state()) -> [pos_integer()]. +status_codes(IsInitialPresence, _IsSelfPresence = true, StateData) -> S0 = [110], case IsInitialPresence of true -> @@ -2408,7 +2369,7 @@ status_codes(IsInitialPresence, JID, #user{jid = JID}, StateData) -> S3; false -> S0 end; -status_codes(_IsInitialPresence, _JID, _Info, _StateData) -> []. +status_codes(_IsInitialPresence, _IsSelfPresence = false, _StateData) -> []. -spec lqueue_new(non_neg_integer()) -> lqueue(). lqueue_new(Max) -> @@ -2438,54 +2399,58 @@ lqueue_to_list(#lqueue{queue = Q1}) -> -spec add_message_to_history(binary(), jid(), message(), state()) -> state(). add_message_to_history(FromNick, FromJID, Packet, StateData) -> - HaveSubject = Packet#message.subject /= [], - TimeStamp = p1_time_compat:timestamp(), - AddrPacket = case (StateData#state.config)#config.anonymous of - true -> Packet; - false -> - Addresses = #addresses{ - list = [#address{type = ofrom, - jid = FromJID}]}, - xmpp:set_subtag(Packet, Addresses) - end, - TSPacket = xmpp_util:add_delay_info( - AddrPacket, StateData#state.jid, TimeStamp), - SPacket = xmpp:set_from_to( - TSPacket, - jid:replace_resource(StateData#state.jid, FromNick), - StateData#state.jid), - Size = element_size(SPacket), - Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, - calendar:now_to_universal_time(TimeStamp), Size}, - StateData#state.history), add_to_log(text, {FromNick, Packet}, StateData), - StateData#state{history = Q1}. - --spec send_history(jid(), integer(), state()) -> boolean(). -send_history(JID, Shift, StateData) -> - lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp, - _Size}, - B) -> - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), - JID, Packet), - B or HaveSubject - end, - false, - lists:nthtail(Shift, - lqueue_to_list(StateData#state.history))). + case check_subject(Packet) of + false -> + TimeStamp = p1_time_compat:timestamp(), + AddrPacket = case (StateData#state.config)#config.anonymous of + true -> Packet; + false -> + Addresses = #addresses{ + list = [#address{type = ofrom, + jid = FromJID}]}, + xmpp:set_subtag(Packet, Addresses) + end, + TSPacket = xmpp_util:add_delay_info( + AddrPacket, StateData#state.jid, TimeStamp), + SPacket = xmpp:set_from_to( + TSPacket, + jid:replace_resource(StateData#state.jid, FromNick), + StateData#state.jid), + Size = element_size(SPacket), + Q1 = lqueue_in({FromNick, TSPacket, false, + TimeStamp, Size}, + StateData#state.history), + StateData#state{history = Q1}; + _ -> + StateData + end. + +-spec send_history(jid(), lqueue(), state()) -> boolean(). +send_history(JID, History, StateData) -> + lists:foreach( + fun({Nick, Packet, _HaveSubject, _TimeStamp, _Size}) -> + ejabberd_router:route( + jid:replace_resource(StateData#state.jid, Nick), + JID, Packet) + end, lqueue_to_list(History)). -spec send_subject(jid(), state()) -> ok. -send_subject(_JID, #state{subject_author = <<"">>}) -> ok; send_subject(JID, #state{subject_author = Nick} = StateData) -> - Subject = StateData#state.subject, - Packet = #message{type = groupchat, subject = xmpp:mk_text(Subject)}, + Subject = case StateData#state.subject of + <<"">> -> [#text{}]; + Subj -> xmpp:mk_text(Subj) + end, + Packet = #message{type = groupchat, subject = Subject}, ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID, Packet). -spec check_subject(message()) -> false | binary(). -check_subject(#message{subject = []}) -> false; -check_subject(#message{subject = Subj}) -> xmpp:get_text(Subj). +check_subject(#message{subject = [_|_] = Subj, body = [], + thread = undefined}) -> + xmpp:get_text(Subj); +check_subject(_) -> + false. -spec can_change_subject(role(), state()) -> boolean(). can_change_subject(Role, StateData) -> @@ -2552,10 +2517,10 @@ items_with_role(SRole, StateData) -> items_with_affiliation(SAffiliation, StateData) -> lists:map( fun({JID, {Affiliation, Reason}}) -> - #muc_item{affiliation = Affiliation, jid = JID, + #muc_item{affiliation = Affiliation, jid = jid:make(JID), reason = Reason}; ({JID, Affiliation}) -> - #muc_item{affiliation = Affiliation, jid = JID} + #muc_item{affiliation = Affiliation, jid = jid:make(JID)} end, search_affiliation(SAffiliation, StateData)). @@ -2600,23 +2565,29 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> "room ~s:~n ~p", [jid:to_string(UJID), jid:to_string(StateData#state.jid), Res]), - NSD = lists:foldl(process_item_change(UJID), - StateData, lists:flatten(Res)), - store_room(NSD), - {result, undefined, NSD}; - {error, Err} -> {error, Err} + case lists:foldl(process_item_change(UJID), + StateData, lists:flatten(Res)) of + {error, _} = Err -> + Err; + NSD -> + store_room(NSD), + {result, undefined, NSD} + end; + {error, Err} -> {error, Err} end. -spec process_item_change(jid()) -> function(). process_item_change(UJID) -> - fun(E, SD) -> - process_item_change(E, SD, UJID) + fun(_, {error, _} = Err) -> + Err; + (Item, SD) -> + process_item_change(Item, SD, UJID) end. -type admin_action() :: {jid(), affiliation | role, affiliation() | role(), binary()}. --spec process_item_change(admin_action(), state(), jid()) -> state(). +-spec process_item_change(admin_action(), state(), jid()) -> state() | {error, stanza_error()}. process_item_change(Item, SD, UJID) -> try case Item of {JID, affiliation, owner, _} when JID#jid.luser == <<"">> -> @@ -2624,23 +2595,23 @@ process_item_change(Item, SD, UJID) -> %% forget the affiliation completely SD; {JID, role, none, Reason} -> - catch send_kickban_presence(UJID, JID, Reason, 307, SD), + send_kickban_presence(UJID, JID, Reason, 307, SD), set_role(JID, none, SD); {JID, affiliation, none, Reason} -> case (SD#state.config)#config.members_only of true -> - catch send_kickban_presence(UJID, JID, Reason, 321, none, SD), + send_kickban_presence(UJID, JID, Reason, 321, none, SD), maybe_send_affiliation(JID, none, SD), SD1 = set_affiliation(JID, none, SD), set_role(JID, none, SD1); _ -> SD1 = set_affiliation(JID, none, SD), - send_update_presence(JID, SD1, SD), + send_update_presence(JID, Reason, SD1, SD), maybe_send_affiliation(JID, none, SD1), SD1 end; {JID, affiliation, outcast, Reason} -> - catch send_kickban_presence(UJID, JID, Reason, 301, outcast, SD), + send_kickban_presence(UJID, JID, Reason, 301, outcast, SD), maybe_send_affiliation(JID, outcast, SD), set_affiliation(JID, outcast, set_role(JID, none, SD), Reason); {JID, affiliation, A, Reason} when (A == admin) or (A == owner) -> @@ -2657,7 +2628,7 @@ process_item_change(Item, SD, UJID) -> SD2; {JID, role, Role, Reason} -> SD1 = set_role(JID, Role, SD), - catch send_new_presence(JID, Reason, SD1, SD), + send_new_presence(JID, Reason, SD1, SD), SD1; {JID, affiliation, A, _Reason} -> SD1 = set_affiliation(JID, A, SD), @@ -2669,7 +2640,7 @@ process_item_change(Item, SD, UJID) -> ?ERROR_MSG("failed to set item ~p from ~s: ~p", [Item, jid:to_string(UJID), {E, {R, erlang:get_stacktrace()}}]), - SD + {error, xmpp:err_internal_server_error()} end. -spec find_changed_items(jid(), affiliation(), role(), @@ -2698,12 +2669,11 @@ find_changed_items(UJID, UAffiliation, URole, Nick /= <<"">> -> case find_jids_by_nick(Nick, StateData) of [] -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Nickname ~s does not exist in the room">>), - [Nick])), + ErrText = str:format( + translate:translate( + Lang, + <<"Nickname ~s does not exist in the room">>), + [Nick]), throw({error, xmpp:err_not_acceptable(ErrText, Lang)}); JIDList -> JIDList @@ -2740,9 +2710,15 @@ find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res); true -> - MoreRes = [{jid:remove_resource(Jidx), - RoleOrAff, RoleOrAffValue, Reason} - || Jidx <- JIDs], + MoreRes = case RoleOrAff of + affiliation -> + [{jid:remove_resource(Jidx), + RoleOrAff, RoleOrAffValue, Reason} + || Jidx <- JIDs]; + role -> + [{Jidx, RoleOrAff, RoleOrAffValue, Reason} + || Jidx <- JIDs] + end, find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, [MoreRes | Res]); @@ -2925,12 +2901,13 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, StateData#state.users), ActorNick = get_actor_nick(MJID, StateData), lists:foreach( - fun({_LJID, Info}) -> + fun({LJID, Info}) -> + IsSelfPresence = jid:tolower(UJID) == LJID, Item0 = #muc_item{affiliation = Affiliation, role = none}, Item1 = case Info#user.role == moderator orelse (StateData#state.config)#config.anonymous - == false of + == false orelse IsSelfPresence of true -> Item0#muc_item{jid = RealJID}; false -> Item0 end, @@ -2939,9 +2916,12 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, <<"">> -> Item2; _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}} end, + Codes = if IsSelfPresence -> [110, Code]; + true -> [Code] + end, Packet = #presence{type = unavailable, sub_els = [#muc_user{items = [Item], - status_codes = [Code]}]}, + status_codes = Codes}]}, RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick), send_wrapped(RoomJIDNick, Info#user.jid, Packet, ?NS_MUCSUB_NODES_AFFILIATIONS, StateData), @@ -2989,13 +2969,21 @@ process_iq_owner(From, #iq{type = set, lang = Lang, case Config of #xdata{type = cancel} -> {result, undefined}; - #xdata{type = submit} -> - case is_allowed_log_change(Config, StateData, From) andalso - is_allowed_persistent_change(Config, StateData, From) andalso - is_allowed_room_name_desc_limits(Config, StateData) andalso - is_password_settings_correct(Config, StateData) of - true -> set_config(Config, StateData, Lang); - false -> {error, xmpp:err_not_acceptable()} + #xdata{type = submit, fields = Fs} -> + try muc_roomconfig:decode(Fs) of + Options -> + case is_allowed_log_change(Options, StateData, From) andalso + is_allowed_persistent_change(Options, StateData, From) andalso + is_allowed_room_name_desc_limits(Options, StateData) andalso + is_password_settings_correct(Options, StateData) of + true -> + set_config(Options, StateData, Lang); + false -> + {error, xmpp:err_not_acceptable()} + end + catch _:{muc_roomconfig, Why} -> + Txt = muc_roomconfig:format_error(Why), + {error, xmpp:err_bad_request(Txt, Lang)} end; _ -> Txt = <<"Incorrect data form">>, @@ -3034,9 +3022,9 @@ process_iq_owner(From, #iq{type = get, lang = Lang, {error, xmpp:err_bad_request()} end. --spec is_allowed_log_change(xdata(), state(), jid()) -> boolean(). -is_allowed_log_change(X, StateData, From) -> - case xmpp_util:has_xdata_var(<<"muc#roomconfig_enablelogging">>, X) of +-spec is_allowed_log_change(muc_roomconfig:result(), state(), jid()) -> boolean(). +is_allowed_log_change(Options, StateData, From) -> + case proplists:is_defined(enablelogging, Options) of false -> true; true -> allow == @@ -3044,9 +3032,9 @@ is_allowed_log_change(X, StateData, From) -> From) end. --spec is_allowed_persistent_change(xdata(), state(), jid()) -> boolean(). -is_allowed_persistent_change(X, StateData, From) -> - case xmpp_util:has_xdata_var(<<"muc#roomconfig_persistentroom">>, X) of +-spec is_allowed_persistent_change(muc_roomconfig:result(), state(), jid()) -> boolean(). +is_allowed_persistent_change(Options, StateData, From) -> + case proplists:is_defined(persistentroom, Options) of false -> true; true -> {_AccessRoute, _AccessCreate, _AccessAdmin, @@ -3059,66 +3047,43 @@ is_allowed_persistent_change(X, StateData, From) -> %% Check if the Room Name and Room Description defined in the Data Form %% are conformant to the configured limits --spec is_allowed_room_name_desc_limits(xdata(), state()) -> boolean(). -is_allowed_room_name_desc_limits(XData, StateData) -> - IsNameAccepted = case xmpp_util:get_xdata_values( - <<"muc#roomconfig_roomname">>, XData) of - [N] -> - byte_size(N) =< - gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_room_name, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> I - end, infinity); - _ -> - true - end, - IsDescAccepted = case xmpp_util:get_xdata_values( - <<"muc#roomconfig_roomdesc">>, XData) of - [D] -> - byte_size(D) =< - gen_mod:get_module_opt( - StateData#state.server_host, - mod_muc, max_room_desc, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> - I - end, infinity); - _ -> true - end, - IsNameAccepted and IsDescAccepted. +-spec is_allowed_room_name_desc_limits(muc_roomconfig:result(), state()) -> boolean(). +is_allowed_room_name_desc_limits(Options, StateData) -> + RoomName = proplists:get_value(roomname, Options, <<"">>), + RoomDesc = proplists:get_value(roomdesc, Options, <<"">>), + MaxRoomName = gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, max_room_name, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> I + end, infinity), + MaxRoomDesc = gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, max_room_desc, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> + I + end, infinity), + (byte_size(RoomName) =< MaxRoomName) + andalso (byte_size(RoomDesc) =< MaxRoomDesc). %% Return false if: %% "the password for a password-protected room is blank" --spec is_password_settings_correct(xdata(), state()) -> boolean(). -is_password_settings_correct(XData, StateData) -> +-spec is_password_settings_correct(muc_roomconfig:result(), state()) -> boolean(). +is_password_settings_correct(Options, StateData) -> Config = StateData#state.config, OldProtected = Config#config.password_protected, OldPassword = Config#config.password, - NewProtected = case xmpp_util:get_xdata_values( - <<"muc#roomconfig_passwordprotectedroom">>, XData) of - [<<"1">>] -> true; - [<<"true">>] -> true; - [<<"0">>] -> false; - [<<"false">>] -> false; - _ -> undefined - end, - NewPassword = case xmpp_util:get_xdata_values( - <<"muc#roomconfig_roomsecret">>, XData) of - [P] -> P; - _ -> undefined - end, - case {OldProtected, NewProtected, OldPassword, - NewPassword} - of - {true, undefined, <<"">>, undefined} -> false; - {true, undefined, _, <<"">>} -> false; - {_, true, <<"">>, undefined} -> false; - {_, true, _, <<"">>} -> false; - _ -> true + NewProtected = proplists:get_value(passwordprotectedroom, Options), + NewPassword = proplists:get_value(roomsecret, Options), + case {OldProtected, NewProtected, OldPassword, NewPassword} of + {true, undefined, <<"">>, undefined} -> false; + {true, undefined, _, <<"">>} -> false; + {_, true, <<"">>, undefined} -> false; + {_, true, _, <<"">>} -> false; + _ -> true end. -spec get_default_room_maxusers(state()) -> non_neg_integer(). @@ -3144,10 +3109,9 @@ get_config(Lang, StateData, From) -> {N, N}; _ -> {0, none} end, - Title = iolist_to_binary( - io_lib:format( - translate:translate(Lang, <<"Configuration of room ~s">>), - [jid:to_string(StateData#state.jid)])), + Title = str:format( + translate:translate(Lang, <<"Configuration of room ~s">>), + [jid:to_string(StateData#state.jid)]), Fs = [{roomname, Config#config.title}, {roomdesc, Config#config.description}] ++ case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of @@ -3208,11 +3172,10 @@ get_config(Lang, StateData, From) -> fields = muc_roomconfig:encode( Fields, fun(T) -> translate:translate(Lang, T) end)}. --spec set_config(xdata(), state(), binary()) -> {error, stanza_error()} | - {result, undefined, state()}. -set_config(#xdata{fields = Fields}, StateData, Lang) -> +-spec set_config(muc_roomconfig:result(), state(), binary()) -> + {error, stanza_error()} | {result, undefined, state()}. +set_config(Options, StateData, Lang) -> try - Options = muc_roomconfig:decode(Fields), #config{} = Config = set_config(Options, StateData#state.config, StateData#state.server_host, Lang), {result, _, NSD} = Res = change_config(Config, StateData), @@ -3227,10 +3190,7 @@ set_config(#xdata{fields = Fields}, StateData, Lang) -> || {_, U} <- (?DICT):to_list(StateData#state.users)], add_to_log(Type, Users, NSD), Res - catch _:{muc_roomconfig, Why} -> - Txt = muc_roomconfig:format_error(Why), - {error, xmpp:err_bad_request(Txt, Lang)}; - _:{badmatch, {error, #stanza_error{}} = Err} -> + catch _:{badmatch, {error, #stanza_error{}} = Err} -> Err end. @@ -3331,18 +3291,23 @@ send_config_change_info(New, #state{config = Old} = StateData) -> end ++ case Old#config{anonymous = New#config.anonymous, + vcard = New#config.vcard, logging = New#config.logging} of New -> []; _ -> [104] end, - Message = #message{type = groupchat, - id = randoms:get_string(), - sub_els = [#muc_user{status_codes = Codes}]}, - send_wrapped_multiple(StateData#state.jid, - StateData#state.users, - Message, - ?NS_MUCSUB_NODES_CONFIG, - StateData). + if Codes /= [] -> + Message = #message{type = groupchat, + id = randoms:get_string(), + sub_els = [#muc_user{status_codes = Codes}]}, + send_wrapped_multiple(StateData#state.jid, + StateData#state.users, + Message, + ?NS_MUCSUB_NODES_CONFIG, + StateData); + true -> + ok + end. -spec remove_nonmembers(state()) -> state(). remove_nonmembers(StateData) -> @@ -3620,7 +3585,7 @@ iq_disco_info_extras(Lang, StateData) -> process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, {error, xmpp:err_not_allowed(Txt, Lang)}; -process_iq_disco_items(From, #iq{type = get, lang = Lang}, StateData) -> +process_iq_disco_items(From, #iq{type = get}, StateData) -> case (StateData#state.config)#config.public_list of true -> {result, get_mucroom_disco_items(StateData)}; @@ -3629,8 +3594,10 @@ process_iq_disco_items(From, #iq{type = get, lang = Lang}, StateData) -> true -> {result, get_mucroom_disco_items(StateData)}; _ -> - Txt = <<"Only occupants or administrators can perform this query">>, - {error, xmpp:err_forbidden(Txt, Lang)} + %% If the list of occupants is private, + %% the room MUST return an empty <query/> element + %% (http://xmpp.org/extensions/xep-0045.html#disco-roomitems) + {result, #disco_items{}} end end. @@ -3661,7 +3628,7 @@ process_iq_vcard(_From, #iq{type = get}, StateData) -> #xmlel{} = VCard -> {result, VCard}; {error, _} -> - {result, #vcard_temp{}} + {error, xmpp:err_item_not_found()} end; process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, StateData) -> @@ -3801,8 +3768,7 @@ get_roomdesc_tail(StateData, Lang) -> _ -> translate:translate(Lang, <<"private, ">>) end, Len = (?DICT):size(StateData#state.users), - <<" (", Desc/binary, - (iolist_to_binary(integer_to_list(Len)))/binary, ")">>. + <<" (", Desc/binary, (integer_to_binary(Len))/binary, ")">>. -spec get_mucroom_disco_items(state()) -> disco_items(). get_mucroom_disco_items(StateData) -> |