diff options
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r-- | src/mod_muc_room.erl | 1250 |
1 files changed, 709 insertions, 541 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 8d0b36b6c..06fdf325f 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -5,7 +5,7 @@ %%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2015 ProcessOne +%%% ejabberd, Copyright (C) 2002-2016 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -34,6 +34,9 @@ start_link/7, start/9, start/7, + get_role/2, + get_affiliation/2, + is_occupant_or_admin/2, route/4]). %% gen_fsm callbacks @@ -55,6 +58,8 @@ -define(MAX_USERS_DEFAULT_LIST, [5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]). +-define(DEFAULT_MAX_USERS_PRESENCE,1000). + %-define(DBGFSM, true). -ifdef(DBGFSM). @@ -67,32 +72,19 @@ -endif. -%% Module start with or without supervisor: --ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, - gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Creator, Nick, DefRoomOpts], - ?FSMOPTS)). --else. --define(SUPERVISOR_START, - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), - supervisor:start_child( - Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts])). --endif. - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts) -> - ?SUPERVISOR_START. + gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + RoomShaper, Creator, Nick, DefRoomOpts], + ?FSMOPTS). start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), - supervisor:start_child( - Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Opts]). + gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + RoomShaper, Opts], + ?FSMOPTS). start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts) -> @@ -109,23 +101,17 @@ start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- -init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, DefRoomOpts]) -> +init([Host, ServerHost, Access, Room, HistorySize, + RoomShaper, Creator, _Nick, DefRoomOpts]) -> process_flag(trap_exit, true), Shaper = shaper:new(RoomShaper), State = set_affiliation(Creator, owner, - #state{host = Host, server_host = ServerHost, - access = Access, room = Room, - history = lqueue_new(HistorySize), - jid = jlib:make_jid(Room, Host, <<"">>), - just_created = true, - room_shaper = Shaper}), + #state{host = Host, server_host = ServerHost, + access = Access, room = Room, + history = lqueue_new(HistorySize), + jid = jid:make(Room, Host, <<"">>), + just_created = true, + room_shaper = Shaper}), State1 = set_opts(DefRoomOpts, State), if (State1#state.config)#config.persistent -> mod_muc:store_room(State1#state.server_host, @@ -134,8 +120,8 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, D make_opts(State1)); true -> ok end, - ?INFO_MSG("Created MUC room ~s@~s by ~s", - [Room, Host, jlib:jid_to_string(Creator)]), + ?INFO_MSG("Created MUC room ~s@~s by ~s", + [Room, Host, jid:to_string(Creator)]), add_to_log(room_existence, created, State1), add_to_log(room_existence, started, State1), {ok, normal_state, State1}; @@ -147,31 +133,25 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) -> access = Access, room = Room, history = lqueue_new(HistorySize), - jid = jlib:make_jid(Room, Host, <<"">>), + jid = jid:make(Room, Host, <<"">>), room_shaper = Shaper}), add_to_log(room_existence, started, State), {ok, normal_state, State}. -%%---------------------------------------------------------------------- -%% Func: StateName/2 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- normal_state({route, From, <<"">>, #xmlel{name = <<"message">>, attrs = Attrs, children = Els} = Packet}, StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), case is_user_online(From, StateData) orelse is_user_allowed_message_nonparticipant(From, StateData) of true -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"groupchat">> -> Activity = get_user_activity(From, StateData), - Now = now_to_usec(now()), + Now = p1_time_compat:system_time(micro_seconds), MinMessageInterval = trunc(gen_mod:get_module_opt(StateData#state.server_host, mod_muc, min_message_interval, fun(MMI) when is_number(MMI) -> MMI end, 0) @@ -247,8 +227,9 @@ normal_state({route, From, <<"">>, <<"error">> -> case is_user_online(From, StateData) of true -> - ErrorText = <<"This participant is kicked from the " - "room because he sent an error message">>, + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, NewState = expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)), @@ -309,8 +290,8 @@ normal_state({route, From, <<"">>, MinInterval = (StateData#state.config)#config.voice_request_min_interval, BareFrom = - jlib:jid_remove_resource(jlib:jid_tolower(From)), - NowPriority = -now_to_usec(now()), + jid:remove_resource(jid:tolower(From)), + NowPriority = -p1_time_compat:system_time(micro_seconds), CleanPriority = NowPriority + MinInterval * 1000000, @@ -385,7 +366,8 @@ normal_state({route, From, <<"">>, catch send_new_presence(TargetJid, Reason, - NSD), + NSD, + StateData), NSD; _ -> StateData end @@ -412,7 +394,7 @@ normal_state({route, From, <<"">>, {next_state, normal_state, StateData} end; _ -> - case xml:get_attr_s(<<"type">>, Attrs) of + case fxml:get_attr_s(<<"type">>, Attrs) of <<"error">> -> ok; _ -> handle_roommessage_from_nonparticipant(Packet, Lang, @@ -424,62 +406,76 @@ normal_state({route, From, <<"">>, #xmlel{name = <<"iq">>} = Packet}, StateData) -> case jlib:iq_query_info(Packet) of - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = #xmlel{name = SubElName} = SubEl} = - IQ - when (XMLNS == (?NS_MUC_ADMIN)) or - (XMLNS == (?NS_MUC_OWNER)) - or (XMLNS == (?NS_DISCO_INFO)) - or (XMLNS == (?NS_DISCO_ITEMS)) - or (XMLNS == (?NS_VCARD)) - or (XMLNS == (?NS_CAPTCHA)) -> - Res1 = case XMLNS of - ?NS_MUC_ADMIN -> - process_iq_admin(From, Type, Lang, SubEl, StateData); - ?NS_MUC_OWNER -> - process_iq_owner(From, Type, Lang, SubEl, StateData); - ?NS_DISCO_INFO -> - process_iq_disco_info(From, Type, Lang, StateData); - ?NS_DISCO_ITEMS -> - process_iq_disco_items(From, Type, Lang, StateData); - ?NS_VCARD -> - process_iq_vcard(From, Type, Lang, SubEl, StateData); - ?NS_CAPTCHA -> - process_iq_captcha(From, Type, Lang, SubEl, StateData) - end, - {IQRes, NewStateData} = case Res1 of - {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = - [#xmlel{name = SubElName, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = Res}]}, - SD}; - {error, Error} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - StateData} - end, - ejabberd_router:route(StateData#state.jid, From, - jlib:iq_to_xml(IQRes)), - case NewStateData of - stop -> {stop, normal, StateData}; - _ -> {next_state, normal_state, NewStateData} - end; - reply -> {next_state, normal_state, StateData}; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} + reply -> + {next_state, normal_state, StateData}; + IQ0 -> + case ejabberd_hooks:run_fold( + muc_process_iq, + StateData#state.server_host, + IQ0, [StateData, From, StateData#state.jid]) of + ignore -> + {next_state, normal_state, StateData}; + #iq{type = T} = IQRes when T == error; T == result -> + ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)), + {next_state, normal_state, StateData}; + #iq{type = Type, xmlns = XMLNS, lang = Lang, + sub_el = #xmlel{name = SubElName, attrs = Attrs} = SubEl} = IQ + when (XMLNS == (?NS_MUC_ADMIN)) or + (XMLNS == (?NS_MUC_OWNER)) + or (XMLNS == (?NS_DISCO_INFO)) + or (XMLNS == (?NS_DISCO_ITEMS)) + or (XMLNS == (?NS_VCARD)) + or (XMLNS == (?NS_CAPTCHA)) -> + Res1 = case XMLNS of + ?NS_MUC_ADMIN -> + process_iq_admin(From, Type, Lang, SubEl, StateData); + ?NS_MUC_OWNER -> + process_iq_owner(From, Type, Lang, SubEl, StateData); + ?NS_DISCO_INFO -> + case fxml:get_attr(<<"node">>, Attrs) of + false -> process_iq_disco_info(From, Type, Lang, StateData); + {value, _} -> {error, ?ERR_SERVICE_UNAVAILABLE} + end; + ?NS_DISCO_ITEMS -> + process_iq_disco_items(From, Type, Lang, StateData); + ?NS_VCARD -> + process_iq_vcard(From, Type, Lang, SubEl, StateData); + ?NS_CAPTCHA -> + process_iq_captcha(From, Type, Lang, SubEl, StateData) + end, + {IQRes, NewStateData} = + case Res1 of + {result, Res, SD} -> + {IQ#iq{type = result, + sub_el = + [#xmlel{name = SubElName, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = Res}]}, + SD}; + {error, Error} -> + {IQ#iq{type = error, + sub_el = [SubEl, Error]}, + StateData} + end, + ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)), + case NewStateData of + stop -> {stop, normal, StateData}; + _ -> {next_state, normal_state, NewStateData} + end; + _ -> + Err = jlib:make_error_reply(Packet, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(StateData#state.jid, From, Err), + {next_state, normal_state, StateData} + end end; normal_state({route, From, Nick, #xmlel{name = <<"presence">>} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), - Now = now_to_usec(now()), + Now = p1_time_compat:system_time(micro_seconds), MinPresenceInterval = trunc(gen_mod:get_module_opt(StateData#state.server_host, mod_muc, min_presence_interval, @@ -513,15 +509,15 @@ normal_state({route, From, Nick, normal_state({route, From, ToNick, #xmlel{name = <<"message">>, attrs = Attrs} = Packet}, StateData) -> - Type = xml:get_attr_s(<<"type">>, Attrs), - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Type = fxml:get_attr_s(<<"type">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), case decide_fate_message(Type, Packet, From, StateData) of {expulse_sender, Reason} -> ?DEBUG(Reason, []), - ErrorText = <<"This participant is kicked from the " - "room because he sent an error message " - "to another participant">>, + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, NewState = expulse_participant(Packet, From, StateData, translate:translate(Lang, ErrorText)), {next_state, normal_state, NewState}; @@ -540,7 +536,7 @@ normal_state({route, From, ToNick, Err = jlib:make_error_reply(Packet, ?ERRT_BAD_REQUEST(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err); _ -> @@ -551,7 +547,7 @@ normal_state({route, From, ToNick, Err = jlib:make_error_reply(Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err); ToJIDs -> @@ -565,14 +561,14 @@ normal_state({route, From, ToNick, (PmFromVisitors == moderators) and DstIsModerator -> {ok, #user{nick = FromNick}} = - (?DICT):find(jlib:jid_tolower(From), + (?DICT):find(jid:tolower(From), StateData#state.users), FromNickJID = - jlib:jid_replace_resource(StateData#state.jid, + jid:replace_resource(StateData#state.jid, FromNick), X = #xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC_USER}]}, - PrivMsg = xml:append_subtags(Packet, [X]), + PrivMsg = fxml:append_subtags(Packet, [X]), [ejabberd_router:route(FromNickJID, ToJID, PrivMsg) || ToJID <- ToJIDs]; true -> @@ -581,7 +577,7 @@ normal_state({route, From, ToNick, Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err) end @@ -594,7 +590,7 @@ normal_state({route, From, ToNick, Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err); {false, _} -> @@ -602,7 +598,7 @@ normal_state({route, From, ToNick, <<"It is not allowed to send private messages">>, Err = jlib:make_error_reply(Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err) end, @@ -611,8 +607,8 @@ normal_state({route, From, ToNick, normal_state({route, From, ToNick, #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - StanzaId = xml:get_attr_s(<<"id">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + StanzaId = fxml:get_attr_s(<<"id">>, Attrs), case {(StateData#state.config)#config.allow_query_users, is_user_online_iq(StanzaId, From, StateData)} of @@ -626,17 +622,17 @@ normal_state({route, From, ToNick, Err = jlib:make_error_reply(Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err) end; ToJID -> {ok, #user{nick = FromNick}} = - (?DICT):find(jlib:jid_tolower(FromFull), + (?DICT):find(jid:tolower(FromFull), StateData#state.users), {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, FromNick), ToJID2, Packet2) end; @@ -650,7 +646,7 @@ normal_state({route, From, ToNick, Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err) end; @@ -662,7 +658,7 @@ normal_state({route, From, ToNick, "not allowed in this room">>, Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ALLOWED(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, ToNick), From, Err) end @@ -671,12 +667,6 @@ normal_state({route, From, ToNick, normal_state(_Event, StateData) -> {next_state, normal_state, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_event({service_message, Msg}, _StateName, StateData) -> MessagePkt = #xmlel{name = <<"message">>, @@ -711,12 +701,12 @@ handle_event({destroy, Reason}, _StateName, end}, StateData), ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", - [jlib:jid_to_string(StateData#state.jid), Reason]), + [jid:to_string(StateData#state.jid), Reason]), add_to_log(room_existence, destroyed, StateData), {stop, shutdown, StateData}; handle_event(destroy, StateName, StateData) -> ?INFO_MSG("Destroyed MUC room ~s", - [jlib:jid_to_string(StateData#state.jid)]), + [jid:to_string(StateData#state.jid)]), handle_event({destroy, none}, StateName, StateData); handle_event({set_affiliations, Affiliations}, StateName, StateData) -> @@ -725,19 +715,20 @@ handle_event({set_affiliations, Affiliations}, handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) -> - Reply = get_roomdesc_reply(JID, StateData, - get_roomdesc_tail(StateData, Lang)), +handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) -> + Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0, + StateData#state.users), + Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of + true -> + get_roomdesc_reply(JID, StateData, + get_roomdesc_tail(StateData, Lang)); + false -> + false + end, {reply, Reply, StateName, StateData}; +%% This clause is only for backwards compatibility +handle_sync_event({get_disco_item, JID, Lang}, From, StateName, StateData) -> + handle_sync_event({get_disco_item, any, JID, Lang}, From, StateName, StateData); handle_sync_event(get_config, _From, StateName, StateData) -> {reply, {ok, StateData#state.config}, StateName, @@ -762,12 +753,6 @@ handle_sync_event(_Event, _From, StateName, code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_info({process_user_presence, From}, normal_state = _StateName, StateData) -> RoomQueueEmpty = queue:is_empty(StateData#state.room_queue), RoomQueue = queue:in({presence, From}, StateData#state.room_queue), @@ -835,7 +820,7 @@ handle_info({captcha_failed, From}, normal_state, Err = jlib:make_error_reply(Packet, ?ERR_NOT_AUTHORIZED), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData#state{robots = Robots}; @@ -847,11 +832,6 @@ handle_info(shutdown, _StateName, StateData) -> handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate(Reason, _StateName, StateData) -> ?INFO_MSG("Stopping MUC room ~s@~s", [StateData#state.room, StateData#state.host]), @@ -881,7 +861,7 @@ terminate(Reason, _StateName, StateData) -> Nick = Info#user.nick, case Reason of shutdown -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet); _ -> ok @@ -904,7 +884,7 @@ route(Pid, From, ToNick, Packet) -> process_groupchat_message(From, #xmlel{name = <<"message">>, attrs = Attrs} = Packet, StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), case is_user_online(From, StateData) orelse is_user_allowed_message_nonparticipant(From, StateData) of @@ -945,20 +925,33 @@ process_groupchat_message(From, end, case IsAllowed of true -> - send_multiple( - jlib:jid_replace_resource(StateData#state.jid, FromNick), - StateData#state.server_host, - StateData#state.users, - Packet), - NewStateData2 = case has_body_or_subject(Packet) of - true -> - add_message_to_history(FromNick, From, - Packet, - NewStateData1); - false -> - NewStateData1 - end, - {next_state, normal_state, NewStateData2}; + case + ejabberd_hooks:run_fold(muc_filter_message, + StateData#state.server_host, + Packet, + [StateData, + StateData#state.jid, + From, FromNick]) + of + drop -> + {next_state, normal_state, StateData}; + NewPacket1 -> + NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}), + send_multiple(jid:replace_resource(StateData#state.jid, + FromNick), + StateData#state.server_host, + StateData#state.users, + NewPacket), + NewStateData2 = case has_body_or_subject(NewPacket) of + true -> + add_message_to_history(FromNick, From, + NewPacket, + NewStateData1); + false -> + NewStateData1 + end, + {next_state, normal_state, NewStateData2} + end; _ -> Err = case (StateData#state.config)#config.allow_change_subj @@ -1012,7 +1005,7 @@ is_user_allowed_message_nonparticipant(JID, %% @doc Get information of this participant, or default values. %% If the JID is not a participant, return values for a service message. get_participant_data(From, StateData) -> - case (?DICT):find(jlib:jid_tolower(From), + case (?DICT):find(jid:tolower(From), StateData#state.users) of {ok, #user{nick = FromNick, role = Role}} -> @@ -1021,117 +1014,126 @@ get_participant_data(From, StateData) -> end. process_presence(From, Nick, - #xmlel{name = <<"presence">>, attrs = Attrs} = Packet, + #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0, StateData) -> - Type = xml:get_attr_s(<<"type">>, Attrs), - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), - StateData1 = case Type of - <<"unavailable">> -> - case is_user_online(From, StateData) of - true -> - NewPacket = case - {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} - of - {false, true} -> - strip_status(Packet); - _ -> Packet - end, - NewState = add_user_presence_un(From, NewPacket, - StateData), - case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [_, _ | _]} -> ok; - _ -> send_new_presence(From, NewState) - end, - Reason = case xml:get_subtag(NewPacket, - <<"status">>) - of - false -> <<"">>; - Status_el -> - xml:get_tag_cdata(Status_el) - end, - remove_online_user(From, NewState, Reason); - _ -> StateData - end; - <<"error">> -> - case is_user_online(From, StateData) of - true -> - ErrorText = - <<"This participant is kicked from the " - "room because he sent an error presence">>, - expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)); - _ -> StateData - end; - <<"">> -> - case is_user_online(From, StateData) of - true -> - case is_nick_change(From, Nick, StateData) of - true -> - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, - From, Nick), - {(StateData#state.config)#config.allow_visitor_nickchange, - is_visitor(From, StateData)}} - of - {_, _, {false, true}} -> - ErrText = - <<"Visitors are not allowed to change their " - "nicknames in this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - {true, _, _} -> - Lang = xml:get_attr_s(<<"xml:lang">>, - Attrs), - ErrText = - <<"That nickname is already in use by another " - "occupant">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), % TODO: s/Nick/""/ - From, Err), - StateData; - {_, false, _} -> - ErrText = - <<"That nickname is registered by another " - "person">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - _ -> change_nick(From, Nick, StateData) - end; - _NotNickChange -> - Stanza = case - {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} - of - {false, true} -> - strip_status(Packet); - _Allowed -> Packet - end, - NewState = add_user_presence(From, Stanza, - StateData), - send_new_presence(From, NewState), - NewState - end; - _ -> add_new_user(From, Nick, Packet, StateData) - end; - _ -> StateData - end, - close_room_if_temporary_and_empty(StateData1). + Type0 = fxml:get_attr_s(<<"type">>, Attrs0), + IsOnline = is_user_online(From, StateData), + if Type0 == <<"">>; + IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) -> + case ejabberd_hooks:run_fold(muc_filter_presence, + StateData#state.server_host, + Packet0, + [StateData, + StateData#state.jid, + From, Nick]) of + drop -> + {next_state, normal_state, StateData}; + #xmlel{attrs = Attrs} = Packet -> + Type = fxml:get_attr_s(<<"type">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + StateData1 = case Type of + <<"unavailable">> -> + NewPacket = case + {(StateData#state.config)#config.allow_visitor_status, + is_visitor(From, StateData)} + of + {false, true} -> + strip_status(Packet); + _ -> Packet + end, + NewState = add_user_presence_un(From, NewPacket, + StateData), + case (?DICT):find(Nick, StateData#state.nicks) of + {ok, [_, _ | _]} -> ok; + _ -> send_new_presence(From, NewState, StateData) + end, + Reason = case fxml:get_subtag(NewPacket, + <<"status">>) + of + false -> <<"">>; + Status_el -> + fxml:get_tag_cdata(Status_el) + end, + remove_online_user(From, NewState, Reason); + <<"error">> -> + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + expulse_participant(Packet, From, StateData, + translate:translate(Lang, + ErrorText)); + <<"">> -> + if not IsOnline -> + add_new_user(From, Nick, Packet, StateData); + true -> + case is_nick_change(From, Nick, StateData) of + true -> + case {nick_collision(From, Nick, StateData), + mod_muc:can_use_nick(StateData#state.server_host, + StateData#state.host, + From, Nick), + {(StateData#state.config)#config.allow_visitor_nickchange, + is_visitor(From, StateData)}} + of + {_, _, {false, true}} -> + ErrText = + <<"Visitors are not allowed to change their " + "nicknames in this room">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_NOT_ALLOWED(Lang, + ErrText)), + ejabberd_router:route(jid:replace_resource(StateData#state.jid, + Nick), + From, Err), + StateData; + {true, _, _} -> + Lang = fxml:get_attr_s(<<"xml:lang">>, + Attrs), + ErrText = + <<"That nickname is already in use by another " + "occupant">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_CONFLICT(Lang, + ErrText)), + ejabberd_router:route(jid:replace_resource(StateData#state.jid, + Nick), % TODO: s/Nick/""/ + From, Err), + StateData; + {_, false, _} -> + ErrText = + <<"That nickname is registered by another " + "person">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_CONFLICT(Lang, + ErrText)), + ejabberd_router:route(jid:replace_resource(StateData#state.jid, + Nick), + From, Err), + StateData; + _ -> change_nick(From, Nick, StateData) + end; + _NotNickChange -> + Stanza = case + {(StateData#state.config)#config.allow_visitor_status, + is_visitor(From, StateData)} + of + {false, true} -> + strip_status(Packet); + _Allowed -> Packet + end, + NewState = add_user_presence(From, Stanza, + StateData), + send_new_presence( + From, NewState, StateData), + NewState + end + end + end, + close_room_if_temporary_and_empty(StateData1) + end; + true -> + {next_state, normal_state, StateData} + end. close_room_if_temporary_and_empty(StateData1) -> case not (StateData1#state.config)#config.persistent @@ -1140,14 +1142,14 @@ close_room_if_temporary_and_empty(StateData1) -> true -> ?INFO_MSG("Destroyed MUC room ~s because it's temporary " "and empty", - [jlib:jid_to_string(StateData1#state.jid)]), + [jid:to_string(StateData1#state.jid)]), add_to_log(room_existence, destroyed, StateData1), {stop, normal, StateData1}; _ -> {next_state, normal_state, StateData1} end. is_user_online(JID, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), (?DICT):is_key(LJID, StateData#state.users). %% Check if the user is occupant of the room, or at least is an admin or owner. @@ -1172,7 +1174,7 @@ is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == <<"">> -> try stanzaid_unpack(StanzaId) of {OriginalId, Resource} -> - JIDWithResource = jlib:jid_replace_resource(JID, + JIDWithResource = jid:replace_resource(JID, Resource), {is_user_online(JIDWithResource, StateData), OriginalId, JIDWithResource} @@ -1182,7 +1184,7 @@ is_user_online_iq(StanzaId, JID, StateData) handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) -> - ToBareJID = jlib:jid_remove_resource(ToJID), + ToBareJID = jid:remove_resource(ToJID), IQ = jlib:iq_query_info(Packet), handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, NewId, IQ, Packet). @@ -1264,7 +1266,7 @@ decide_fate_message(<<"error">>, Packet, From, Reason = io_lib:format("This participant is considered a ghost " "and is expulsed: ~s", - [jlib:jid_to_string(From)]), + [jid:to_string(From)]), {expulse_sender, Reason}; false -> continue_delivery end, @@ -1302,7 +1304,7 @@ get_error_condition(Packet) -> end. get_error_condition2(Packet) -> - #xmlel{children = EEls} = xml:get_subtag(Packet, + #xmlel{children = EEls} = fxml:get_subtag(Packet, <<"error">>), [Condition] = [Name || #xmlel{name = Name, @@ -1311,11 +1313,13 @@ get_error_condition2(Packet) -> <- EEls], {condition, Condition}. +make_reason(Packet, From, StateData, Reason1) -> + {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users), + Condition = get_error_condition(Packet), + iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])). + expulse_participant(Packet, From, StateData, Reason1) -> - ErrorCondition = get_error_condition(Packet), - Reason2 = iolist_to_binary( - io_lib:format(binary_to_list(Reason1) ++ ": " ++ "~s", - [ErrorCondition])), + Reason2 = make_reason(Packet, From, StateData, Reason1), NewState = add_user_presence_un(From, #xmlel{name = <<"presence">>, attrs = @@ -1328,14 +1332,14 @@ expulse_participant(Packet, From, StateData, Reason1) -> [{xmlcdata, Reason2}]}]}, StateData), - send_new_presence(From, NewState), + send_new_presence(From, NewState, StateData), remove_online_user(From, NewState). set_affiliation(JID, Affiliation, StateData) -> set_affiliation(JID, Affiliation, StateData, <<"">>). set_affiliation(JID, Affiliation, StateData, Reason) -> - LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)), + LJID = jid:remove_resource(jid:tolower(JID)), Affiliations = case Affiliation of none -> (?DICT):erase(LJID, StateData#state.affiliations); @@ -1354,11 +1358,11 @@ get_affiliation(JID, StateData) -> of allow -> owner; _ -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), case (?DICT):find(LJID, StateData#state.affiliations) of {ok, Affiliation} -> Affiliation; _ -> - LJID1 = jlib:jid_remove_resource(LJID), + LJID1 = jid:remove_resource(LJID), case (?DICT):find(LJID1, StateData#state.affiliations) of {ok, Affiliation} -> Affiliation; @@ -1369,7 +1373,7 @@ get_affiliation(JID, StateData) -> of {ok, Affiliation} -> Affiliation; _ -> - LJID3 = jlib:jid_remove_resource(LJID2), + LJID3 = jid:remove_resource(LJID2), case (?DICT):find(LJID3, StateData#state.affiliations) of @@ -1397,7 +1401,7 @@ get_service_affiliation(JID, StateData) -> end. set_role(JID, Role, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), LJIDs = case LJID of {U, S, <<"">>} -> (?DICT):fold(fun (J, _, Js) -> @@ -1444,7 +1448,7 @@ set_role(JID, Role, StateData) -> StateData#state{users = Users, nicks = Nicks}. get_role(JID, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), case (?DICT):find(LJID, StateData#state.users) of {ok, #user{role = Role}} -> Role; _ -> none @@ -1494,7 +1498,7 @@ get_max_users_admin_threshold(StateData) -> 5). get_user_activity(JID, StateData) -> - case treap:lookup(jlib:jid_tolower(JID), + case treap:lookup(jid:tolower(JID), StateData#state.activity) of {ok, _P, A} -> A; @@ -1526,8 +1530,8 @@ store_user_activity(JID, UserActivity, StateData) -> fun(I) when is_number(I), I>=0 -> I end, 0) * 1000), - Key = jlib:jid_tolower(JID), - Now = now_to_usec(now()), + Key = jid:tolower(JID), + Now = p1_time_compat:system_time(micro_seconds), Activity1 = clean_treap(StateData#state.activity, {1, -Now}), Activity = case treap:lookup(Key, Activity1) of @@ -1609,7 +1613,7 @@ prepare_room_queue(StateData) -> end. add_online_user(JID, Nick, Role, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), Users = (?DICT):store(LJID, #user{jid = JID, nick = Nick, role = Role}, StateData#state.users), @@ -1629,7 +1633,7 @@ remove_online_user(JID, StateData) -> remove_online_user(JID, StateData, <<"">>). remove_online_user(JID, StateData, Reason) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users), add_to_log(leave, {Nick, Reason}, StateData), @@ -1651,7 +1655,7 @@ filter_presence(#xmlel{name = <<"presence">>, case El of {xmlcdata, _} -> false; #xmlel{attrs = Attrs1} -> - XMLNS = xml:get_attr_s(<<"xmlns">>, + XMLNS = fxml:get_attr_s(<<"xmlns">>, Attrs1), NS_MUC = ?NS_MUC, Size = byte_size(NS_MUC), @@ -1678,7 +1682,7 @@ strip_status(#xmlel{name = <<"presence">>, children = FEls}. add_user_presence(JID, Presence, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), FPresence = filter_presence(Presence), Users = (?DICT):update(LJID, fun (#user{} = User) -> @@ -1688,7 +1692,7 @@ add_user_presence(JID, Presence, StateData) -> StateData#state{users = Users}. add_user_presence_un(JID, Presence, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), FPresence = filter_presence(Presence), Users = (?DICT):update(LJID, fun (#user{} = User) -> @@ -1702,8 +1706,8 @@ add_user_presence_un(JID, Presence, StateData) -> %% Return jid record. find_jids_by_nick(Nick, StateData) -> case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [User]} -> [jlib:make_jid(User)]; - {ok, Users} -> [jlib:make_jid(LJID) || LJID <- Users]; + {ok, [User]} -> [jid:make(User)]; + {ok, Users} -> [jid:make(LJID) || LJID <- Users]; error -> false end. @@ -1711,7 +1715,7 @@ find_jids_by_nick(Nick, StateData) -> %% highest-priority presence. Return jid record. find_jid_by_nick(Nick, StateData) -> case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [User]} -> jlib:make_jid(User); + {ok, [User]} -> jid:make(User); {ok, [FirstUser | Users]} -> #user{last_presence = FirstPresence} = (?DICT):fetch(FirstUser, StateData#state.users), @@ -1729,7 +1733,7 @@ find_jid_by_nick(Nick, StateData) -> end end, {FirstUser, FirstPresence}, Users), - jlib:make_jid(LJID); + jid:make(LJID); error -> false end. @@ -1739,11 +1743,11 @@ higher_presence(Pres1, Pres2) -> Pri1 > Pri2. get_priority_from_presence(PresencePacket) -> - case xml:get_subtag(PresencePacket, <<"priority">>) of + case fxml:get_subtag(PresencePacket, <<"priority">>) of false -> 0; SubEl -> case catch - jlib:binary_to_integer(xml:get_tag_cdata(SubEl)) + jlib:binary_to_integer(fxml:get_tag_cdata(SubEl)) of P when is_integer(P) -> P; _ -> 0 @@ -1759,7 +1763,7 @@ find_nick_by_jid(Jid, StateData) -> Nick. is_nick_change(JID, Nick, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), case Nick of <<"">> -> false; _ -> @@ -1770,14 +1774,14 @@ is_nick_change(JID, Nick, StateData) -> nick_collision(User, Nick, StateData) -> UserOfNick = find_jid_by_nick(Nick, StateData), - UserOfNick /= false andalso - jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick)) - /= jlib:jid_remove_resource(jlib:jid_tolower(User)). + (UserOfNick /= false andalso + jid:remove_resource(jid:tolower(UserOfNick)) + /= jid:remove_resource(jid:tolower(User))). add_new_user(From, Nick, #xmlel{attrs = Attrs, children = Els} = Packet, StateData) -> - Lang = xml:get_attr_s(<<"xml:lang">>, Attrs), + Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), MaxUsers = get_max_users(StateData), MaxAdminUsers = MaxUsers + get_max_users_admin_threshold(StateData), @@ -1807,7 +1811,7 @@ add_new_user(From, Nick, Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, Nick), + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData; {_, _, _, none} -> @@ -1824,14 +1828,14 @@ add_new_user(From, Nick, ErrText) end), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, Nick), + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData; {_, true, _, _} -> ErrText = <<"That nickname is already in use by another occupant">>, Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData; @@ -1839,7 +1843,7 @@ add_new_user(From, Nick, ErrText = <<"That nickname is registered by another person">>, Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData; @@ -1851,37 +1855,12 @@ add_new_user(From, Nick, NewState = add_user_presence(From, Packet, add_online_user(From, Nick, Role, StateData)), - if not (NewState#state.config)#config.anonymous -> - WPacket = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, - attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"This room is not anonymous">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - <<"100">>}], - children = - []}]}]}, - ejabberd_router:route(StateData#state.jid, From, WPacket); - true -> ok - end, send_existing_presences(From, NewState), - send_new_presence(From, NewState), + send_initial_presence(From, NewState, StateData), Shift = count_stanza_shift(Nick, Els, NewState), case send_history(From, Shift, NewState) of true -> ok; - _ -> send_subject(From, Lang, StateData) + _ -> send_subject(From, StateData) end, case NewState#state.just_created of true -> NewState#state{just_created = false}; @@ -1895,14 +1874,14 @@ add_new_user(From, Nick, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData; captcha_required -> - SID = xml:get_attr_s(<<"id">>, Attrs), + SID = fxml:get_attr_s(<<"id">>, Attrs), RoomJID = StateData#state.jid, - To = jlib:jid_replace_resource(RoomJID, Nick), + To = jid:replace_resource(RoomJID, Nick), Limiter = {From#jid.luser, From#jid.lserver}, case ejabberd_captcha:create_captcha(SID, RoomJID, To, Lang, Limiter, From) @@ -1921,7 +1900,7 @@ add_new_user(From, Nick, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData; @@ -1931,7 +1910,7 @@ add_new_user(From, Nick, ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData @@ -1942,7 +1921,7 @@ add_new_user(From, Nick, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)), ejabberd_router:route % TODO: s/Nick/""/ - (jlib:jid_replace_resource(StateData#state.jid, + (jid:replace_resource(StateData#state.jid, Nick), From, Err), StateData @@ -2000,11 +1979,11 @@ check_captcha(Affiliation, From, StateData) -> extract_password([]) -> false; extract_password([#xmlel{attrs = Attrs} = El | Els]) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC -> - case xml:get_subtag(El, <<"password">>) of + case fxml:get_subtag(El, <<"password">>) of false -> false; - SubEl -> xml:get_tag_cdata(SubEl) + SubEl -> fxml:get_tag_cdata(SubEl) end; _ -> extract_password(Els) end; @@ -2023,9 +2002,8 @@ count_stanza_shift(Nick, Els, StateData) -> Shift1 = case Seconds of false -> 0; _ -> - Sec = - calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now())) - - Seconds, + Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time()) + - Seconds, count_seconds_shift(Sec, HL) end, MaxStanzas = extract_history(Els, <<"maxstanzas">>), @@ -2079,9 +2057,9 @@ calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> extract_history([], _Type) -> false; extract_history([#xmlel{attrs = Attrs} = El | Els], Type) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC -> - AttrVal = xml:get_path_s(El, + AttrVal = fxml:get_path_s(El, [{elem, <<"history">>}, {attr, Type}]), case Type of <<"since">> -> @@ -2101,11 +2079,31 @@ extract_history([#xmlel{attrs = Attrs} = El | Els], extract_history([_ | Els], Type) -> extract_history(Els, Type). -send_update_presence(JID, StateData) -> - send_update_presence(JID, <<"">>, StateData). +is_room_overcrowded(StateData) -> + MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host, + mod_muc, max_users_presence, + fun(MUP) when is_integer(MUP) -> MUP end, + ?DEFAULT_MAX_USERS_PRESENCE), + (?DICT):size(StateData#state.users) > MaxUsersPresence. + +presence_broadcast_allowed(JID, StateData) -> + Role = get_role(JID, StateData), + lists:member(Role, (StateData#state.config)#config.presence_broadcast). + +send_initial_presence(NJID, StateData, OldStateData) -> + send_new_presence1(NJID, <<"">>, true, StateData, OldStateData). -send_update_presence(JID, Reason, StateData) -> - LJID = jlib:jid_tolower(JID), +send_update_presence(JID, StateData, OldStateData) -> + send_update_presence(JID, <<"">>, StateData, OldStateData). + +send_update_presence(JID, Reason, StateData, OldStateData) -> + case is_room_overcrowded(StateData) of + true -> ok; + false -> send_update_presence1(JID, Reason, StateData, OldStateData) + end. + +send_update_presence1(JID, Reason, StateData, OldStateData) -> + LJID = jid:tolower(JID), LJIDs = case LJID of {U, S, <<"">>} -> (?DICT):fold(fun (J, _, Js) -> @@ -2122,34 +2120,67 @@ send_update_presence(JID, Reason, StateData) -> end end, lists:foreach(fun (J) -> - send_new_presence(J, Reason, StateData) + send_new_presence1(J, Reason, false, StateData, + OldStateData) end, LJIDs). -send_new_presence(NJID, StateData) -> - send_new_presence(NJID, <<"">>, StateData). +send_new_presence(NJID, StateData, OldStateData) -> + send_new_presence(NJID, <<"">>, false, StateData, OldStateData). -send_new_presence(NJID, Reason, StateData) -> - #user{nick = Nick} = - (?DICT):fetch(jlib:jid_tolower(NJID), - StateData#state.users), +send_new_presence(NJID, Reason, StateData, OldStateData) -> + send_new_presence(NJID, Reason, false, StateData, OldStateData). + +send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> + case is_room_overcrowded(StateData) of + true -> ok; + false -> send_new_presence1(NJID, Reason, IsInitialPresence, StateData, + OldStateData) + end. + +send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> + LNJID = jid:tolower(NJID), + #user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users), LJID = find_jid_by_nick(Nick, StateData), {ok, - #user{jid = RealJID, role = Role, - last_presence = Presence}} = - (?DICT):find(jlib:jid_tolower(LJID), + #user{jid = RealJID, role = Role0, + last_presence = Presence0} = UserInfo} = + (?DICT):find(jid:tolower(LJID), StateData#state.users), + {Role1, Presence1} = + case presence_broadcast_allowed(NJID, StateData) of + true -> {Role0, Presence0}; + false -> + {none, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = []} + } + end, Affiliation = get_affiliation(LJID, StateData), SAffiliation = affiliation_to_list(Affiliation), - SRole = role_to_list(Role), - lists:foreach(fun ({_LJID, Info}) -> + UserList = + case not (presence_broadcast_allowed(NJID, StateData) orelse + presence_broadcast_allowed(NJID, OldStateData)) of + true -> + [{LNJID, UserInfo}]; + false -> + (?DICT):to_list(StateData#state.users) + end, + lists:foreach(fun ({LUJID, Info}) -> + {Role, Presence} = + if + LNJID == LUJID -> {Role0, Presence0}; + true -> {Role1, Presence1} + end, + SRole = role_to_list(Role), ItemAttrs = case Info#user.role == moderator orelse (StateData#state.config)#config.anonymous == false of true -> [{<<"jid">>, - jlib:jid_to_string(RealJID)}, + jid:to_string(RealJID)}, {<<"affiliation">>, SAffiliation}, {<<"role">>, SRole}]; _ -> @@ -2164,37 +2195,9 @@ send_new_presence(NJID, Reason, StateData) -> children = [{xmlcdata, Reason}]}] end, - Status = case StateData#state.just_created of - true -> - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, <<"201">>}], - children = []}]; - false -> [] - end, - Status2 = case - (StateData#state.config)#config.anonymous - == false - andalso NJID == Info#user.jid - of - true -> - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, <<"100">>}], - children = []} - | Status]; - false -> Status - end, - Status3 = case NJID == Info#user.jid of - true -> - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, <<"110">>}], - children = []} - | Status2]; - false -> Status2 - end, - Packet = xml:append_subtags(Presence, + StatusEls = status_els(IsInitialPresence, NJID, Info, + StateData), + Packet = fxml:append_subtags(Presence, [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, @@ -2208,25 +2211,35 @@ send_new_presence(NJID, Reason, StateData) -> children = ItemEls} - | Status3]}]), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + | StatusEls]}]), + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet) end, - (?DICT):to_list(StateData#state.users)). + UserList). send_existing_presences(ToJID, StateData) -> - LToJID = jlib:jid_tolower(ToJID), + case is_room_overcrowded(StateData) of + true -> ok; + false -> send_existing_presences1(ToJID, StateData) + end. + +send_existing_presences1(ToJID, StateData) -> + LToJID = jid:tolower(ToJID), {ok, #user{jid = RealToJID, role = Role}} = (?DICT):find(LToJID, StateData#state.users), lists:foreach(fun ({FromNick, _Users}) -> LJID = find_jid_by_nick(FromNick, StateData), #user{jid = FromJID, role = FromRole, last_presence = Presence} = - (?DICT):fetch(jlib:jid_tolower(LJID), + (?DICT):fetch(jid:tolower(LJID), StateData#state.users), - case RealToJID of - FromJID -> ok; + PresenceBroadcast = + lists:member( + FromRole, (StateData#state.config)#config.presence_broadcast), + case {RealToJID, PresenceBroadcast} of + {FromJID, _} -> ok; + {_, false} -> ok; _ -> FromAffiliation = get_affiliation(LJID, StateData), @@ -2236,7 +2249,7 @@ send_existing_presences(ToJID, StateData) -> of true -> [{<<"jid">>, - jlib:jid_to_string(FromJID)}, + jid:to_string(FromJID)}, {<<"affiliation">>, affiliation_to_list(FromAffiliation)}, {<<"role">>, @@ -2247,7 +2260,7 @@ send_existing_presences(ToJID, StateData) -> {<<"role">>, role_to_list(FromRole)}] end, - Packet = xml:append_subtags(Presence, + Packet = fxml:append_subtags(Presence, [#xmlel{name = <<"x">>, attrs = @@ -2263,18 +2276,15 @@ send_existing_presences(ToJID, StateData) -> children = []}]}]), - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, FromNick), RealToJID, Packet) end end, (?DICT):to_list(StateData#state.nicks)). -now_to_usec({MSec, Sec, USec}) -> - (MSec * 1000000 + Sec) * 1000000 + USec. - change_nick(JID, Nick, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), {ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users), Users = (?DICT):update(LJID, @@ -2302,8 +2312,12 @@ change_nick(JID, Nick, StateData) -> end, NewStateData = StateData#state{users = Users, nicks = Nicks}, - send_nick_changing(JID, OldNick, NewStateData, - SendOldUnavailable, SendNewAvailable), + case presence_broadcast_allowed(JID, NewStateData) of + true -> + send_nick_changing(JID, OldNick, NewStateData, + SendOldUnavailable, SendNewAvailable); + false -> ok + end, add_to_log(nickchange, {OldNick, Nick}, StateData), NewStateData. @@ -2312,7 +2326,7 @@ send_nick_changing(JID, OldNick, StateData, {ok, #user{jid = RealJID, nick = Nick, role = Role, last_presence = Presence}} = - (?DICT):find(jlib:jid_tolower(JID), + (?DICT):find(jid:tolower(JID), StateData#state.users), Affiliation = get_affiliation(JID, StateData), SAffiliation = affiliation_to_list(Affiliation), @@ -2324,7 +2338,7 @@ send_nick_changing(JID, OldNick, StateData, of true -> [{<<"jid">>, - jlib:jid_to_string(RealJID)}, + jid:to_string(RealJID)}, {<<"affiliation">>, SAffiliation}, {<<"role">>, SRole}, {<<"nick">>, Nick}]; @@ -2339,7 +2353,7 @@ send_nick_changing(JID, OldNick, StateData, of true -> [{<<"jid">>, - jlib:jid_to_string(RealJID)}, + jid:to_string(RealJID)}, {<<"affiliation">>, SAffiliation}, {<<"role">>, SRole}]; _ -> @@ -2377,7 +2391,7 @@ send_nick_changing(JID, OldNick, StateData, <<"303">>}], children = []}|Status110]}]}, - Packet2 = xml:append_subtags(Presence, + Packet2 = fxml:append_subtags(Presence, [#xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, @@ -2393,13 +2407,13 @@ send_nick_changing(JID, OldNick, StateData, = []}|Status110]}]), if SendOldUnavailable -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, OldNick), Info#user.jid, Packet1); true -> ok end, if SendNewAvailable -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet2); true -> ok @@ -2407,6 +2421,40 @@ send_nick_changing(JID, OldNick, StateData, end, (?DICT):to_list(StateData#state.users)). +status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) -> + Status = case IsInitialPresence of + true -> + S1 = case StateData#state.just_created of + true -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"201">>}], + children = []}]; + false -> [] + end, + S2 = case (StateData#state.config)#config.anonymous of + true -> S1; + false -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"100">>}], + children = []} | S1] + end, + S3 = case (StateData#state.config)#config.logging of + true -> + [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"170">>}], + children = []} | S2]; + false -> S2 + end, + S3; + false -> [] + end, + [#xmlel{name = <<"status">>, + attrs = + [{<<"code">>, + <<"110">>}], + children = []} | Status]; +status_els(_IsInitialPresence, _JID, _Info, _StateData) -> []. + lqueue_new(Max) -> #lqueue{queue = queue:new(), len = 0, max = Max}. @@ -2431,28 +2479,28 @@ lqueue_to_list(#lqueue{queue = Q1}) -> add_message_to_history(FromNick, FromJID, Packet, StateData) -> - HaveSubject = case xml:get_subtag(Packet, <<"subject">>) + HaveSubject = case fxml:get_subtag(Packet, <<"subject">>) of false -> false; _ -> true end, - TimeStamp = now(), + TimeStamp = p1_time_compat:timestamp(), AddrPacket = case (StateData#state.config)#config.anonymous of true -> Packet; false -> Address = #xmlel{name = <<"address">>, attrs = [{<<"type">>, <<"ofrom">>}, {<<"jid">>, - jlib:jid_to_string(FromJID)}], + jid:to_string(FromJID)}], children = []}, Addresses = #xmlel{name = <<"addresses">>, attrs = [{<<"xmlns">>, ?NS_ADDRESS}], children = [Address]}, - xml:append_subtags(Packet, [Addresses]) + fxml:append_subtags(Packet, [Addresses]) end, TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp), SPacket = - jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid, + jlib:replace_from_to(jid:replace_resource(StateData#state.jid, FromNick), StateData#state.jid, TSPacket), Size = element_size(SPacket), @@ -2466,7 +2514,7 @@ send_history(JID, Shift, StateData) -> lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) -> - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID, Packet), B or HaveSubject @@ -2475,30 +2523,21 @@ send_history(JID, Shift, StateData) -> lists:nthtail(Shift, lqueue_to_list(StateData#state.history))). -send_subject(JID, Lang, StateData) -> - case StateData#state.subject_author of - <<"">> -> ok; - Nick -> - Subject = StateData#state.subject, - Packet = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Subject}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - <<Nick/binary, - (translate:translate(Lang, - <<" has set the subject to: ">>))/binary, - Subject/binary>>}]}]}, - ejabberd_router:route(StateData#state.jid, JID, Packet) - end. +send_subject(_JID, #state{subject_author = <<"">>}) -> ok; +send_subject(JID, #state{subject_author = Nick} = StateData) -> + Subject = StateData#state.subject, + Packet = #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"subject">>, attrs = [], + children = [{xmlcdata, Subject}]}]}, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID, + Packet). check_subject(Packet) -> - case xml:get_subtag(Packet, <<"subject">>) of + case fxml:get_subtag(Packet, <<"subject">>) of false -> false; - SubjEl -> xml:get_tag_cdata(SubjEl) + SubjEl -> fxml:get_tag_cdata(SubjEl) end. can_change_subject(Role, StateData) -> @@ -2515,14 +2554,14 @@ process_iq_admin(From, set, Lang, SubEl, StateData) -> #xmlel{children = Items} = SubEl, process_admin_items_set(From, Items, Lang, StateData); process_iq_admin(From, get, Lang, SubEl, StateData) -> - case xml:get_subtag(SubEl, <<"item">>) of + case fxml:get_subtag(SubEl, <<"item">>) of false -> {error, ?ERR_BAD_REQUEST}; Item -> FAffiliation = get_affiliation(From, StateData), FRole = get_role(From, StateData), - case xml:get_tag_attr(<<"role">>, Item) of + case fxml:get_tag_attr(<<"role">>, Item) of false -> - case xml:get_tag_attr(<<"affiliation">>, Item) of + case fxml:get_tag_attr(<<"affiliation">>, Item) of false -> {error, ?ERR_BAD_REQUEST}; {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of @@ -2530,7 +2569,8 @@ process_iq_admin(From, get, Lang, SubEl, StateData) -> SAffiliation -> if (FAffiliation == owner) or (FAffiliation == admin) or - ((FAffiliation == member) and (SAffiliation == member)) -> + ((FAffiliation == member) and not + (StateData#state.config)#config.anonymous) -> Items = items_with_affiliation(SAffiliation, StateData), {result, Items, StateData}; @@ -2567,7 +2607,7 @@ items_with_affiliation(SAffiliation, StateData) -> attrs = [{<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"jid">>, jlib:jid_to_string(JID)}], + {<<"jid">>, jid:to_string(JID)}], children = [#xmlel{name = <<"reason">>, attrs = [], children = [{xmlcdata, Reason}]}]}; @@ -2576,7 +2616,7 @@ items_with_affiliation(SAffiliation, StateData) -> attrs = [{<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"jid">>, jlib:jid_to_string(JID)}], + {<<"jid">>, jid:to_string(JID)}], children = []} end, search_affiliation(SAffiliation, StateData)). @@ -2589,7 +2629,7 @@ user_to_item(#user{role = Role, nick = Nick, jid = JID}, [{<<"role">>, role_to_list(Role)}, {<<"affiliation">>, affiliation_to_list(Affiliation)}, {<<"nick">>, Nick}, - {<<"jid">>, jlib:jid_to_string(JID)}], + {<<"jid">>, jid:to_string(JID)}], children = []}. search_role(Role, StateData) -> @@ -2615,8 +2655,8 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> {result, Res} -> ?INFO_MSG("Processing MUC admin query from ~s in " "room ~s:~n ~p", - [jlib:jid_to_string(UJID), - jlib:jid_to_string(StateData#state.jid), Res]), + [jid:to_string(UJID), + jid:to_string(StateData#state.jid), Res]), NSD = lists:foldl(process_item_change(UJID), StateData, lists:flatten(Res)), case (NSD#state.config)#config.persistent of @@ -2661,7 +2701,7 @@ process_item_change(E, SD, UJID) -> set_role(JID, none, SD1); _ -> SD1 = set_affiliation(JID, none, SD), - send_update_presence(JID, SD1), + send_update_presence(JID, SD1, SD), SD1 end; {JID, affiliation, outcast, Reason} -> @@ -2679,21 +2719,21 @@ process_item_change(E, SD, UJID) -> when (A == admin) or (A == owner) -> SD1 = set_affiliation(JID, A, SD, Reason), SD2 = set_role(JID, moderator, SD1), - send_update_presence(JID, Reason, SD2), + send_update_presence(JID, Reason, SD2, SD), SD2; {JID, affiliation, member, Reason} -> SD1 = set_affiliation(JID, member, SD, Reason), SD2 = set_role(JID, participant, SD1), - send_update_presence(JID, Reason, SD2), + send_update_presence(JID, Reason, SD2, SD), SD2; {JID, role, Role, Reason} -> SD1 = set_role(JID, Role, SD), catch - send_new_presence(JID, Reason, SD1), + send_new_presence(JID, Reason, SD1, SD), SD1; {JID, affiliation, A, _Reason} -> SD1 = set_affiliation(JID, A, SD), - send_update_presence(JID, SD1), + send_update_presence(JID, SD1, SD), SD1 end of @@ -2714,9 +2754,9 @@ find_changed_items(UJID, UAffiliation, URole, [#xmlel{name = <<"item">>, attrs = Attrs} = Item | Items], Lang, StateData, Res) -> - TJID = case xml:get_attr(<<"jid">>, Attrs) of + TJID = case fxml:get_attr(<<"jid">>, Attrs) of {value, S} -> - case jlib:string_to_jid(S) of + case jid:from_string(S) of error -> ErrText = iolist_to_binary( io_lib:format(translate:translate( @@ -2727,7 +2767,7 @@ find_changed_items(UJID, UAffiliation, URole, J -> {value, [J]} end; _ -> - case xml:get_attr(<<"nick">>, Attrs) of + case fxml:get_attr(<<"nick">>, Attrs) of {value, N} -> case find_jids_by_nick(N, StateData) of false -> @@ -2747,9 +2787,9 @@ find_changed_items(UJID, UAffiliation, URole, {value, [JID | _] = JIDs} -> TAffiliation = get_affiliation(JID, StateData), TRole = get_role(JID, StateData), - case xml:get_attr(<<"role">>, Attrs) of + case fxml:get_attr(<<"role">>, Attrs) of false -> - case xml:get_attr(<<"affiliation">>, Attrs) of + case fxml:get_attr(<<"affiliation">>, Attrs) of false -> {error, ?ERR_BAD_REQUEST}; {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of @@ -2777,9 +2817,9 @@ find_changed_items(UJID, UAffiliation, URole, StateData) of [{OJID, _}] -> - jlib:jid_remove_resource(OJID) + jid:remove_resource(OJID) /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); + jid:tolower(jid:remove_resource(UJID)); _ -> true end; _ -> false @@ -2790,10 +2830,10 @@ find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res); true -> - Reason = xml:get_path_s(Item, + Reason = fxml:get_path_s(Item, [{elem, <<"reason">>}, cdata]), - MoreRes = [{jlib:jid_remove_resource(Jidx), + MoreRes = [{jid:remove_resource(Jidx), affiliation, SAffiliation, Reason} || Jidx <- JIDs], find_changed_items(UJID, UAffiliation, URole, @@ -2825,9 +2865,9 @@ find_changed_items(UJID, UAffiliation, URole, StateData) of [{OJID, _}] -> - jlib:jid_remove_resource(OJID) + jid:remove_resource(OJID) /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); + jid:tolower(jid:remove_resource(UJID)); _ -> true end; _ -> false @@ -2837,7 +2877,7 @@ find_changed_items(UJID, UAffiliation, URole, find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res); true -> - Reason = xml:get_path_s(Item, + Reason = fxml:get_path_s(Item, [{elem, <<"reason">>}, cdata]), MoreRes = [{Jidx, role, SRole, Reason} @@ -2986,7 +3026,7 @@ send_kickban_presence(UJID, JID, Reason, Code, StateData) -> send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jid:tolower(JID), LJIDs = case LJID of {U, S, <<"">>} -> (?DICT):fold(fun (J, _, Js) -> @@ -3015,14 +3055,14 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, StateData) -> {ok, #user{jid = RealJID, nick = Nick}} = - (?DICT):find(jlib:jid_tolower(UJID), + (?DICT):find(jid:tolower(UJID), StateData#state.users), SAffiliation = affiliation_to_list(Affiliation), - BannedJIDString = jlib:jid_to_string(RealJID), + BannedJIDString = jid:to_string(RealJID), case MJID /= <<"">> of true -> {ok, #user{nick = ActorNick}} = - (?DICT):find(jlib:jid_tolower(MJID), + (?DICT):find(jid:tolower(MJID), StateData#state.users); false -> ActorNick = <<"">> @@ -3075,7 +3115,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, Code}], children = []}]}]}, - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet) end, @@ -3089,10 +3129,10 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> case FAffiliation of owner -> #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [#xmlel{name = <<"x">>} = XEl] -> - case {xml:get_tag_attr_s(<<"xmlns">>, XEl), - xml:get_tag_attr_s(<<"type">>, XEl)} + case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), + fxml:get_tag_attr_s(<<"type">>, XEl)} of {?NS_XDATA, <<"cancel">>} -> {result, [], StateData}; {?NS_XDATA, <<"submit">>} -> @@ -3110,8 +3150,8 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> end; [#xmlel{name = <<"destroy">>} = SubEl1] -> ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", - [jlib:jid_to_string(StateData#state.jid), - jlib:jid_to_string(From)]), + [jid:to_string(StateData#state.jid), + jid:to_string(From)]), add_to_log(room_existence, destroyed, StateData), destroy_room(SubEl1, StateData); Items -> @@ -3126,10 +3166,10 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> case FAffiliation of owner -> #xmlel{children = Els} = SubEl, - case xml:remove_cdata(Els) of + case fxml:remove_cdata(Els) of [] -> get_config(Lang, StateData, From); [Item] -> - case xml:get_tag_attr(<<"affiliation">>, Item) of + case fxml:get_tag_attr(<<"affiliation">>, Item) of false -> {error, ?ERR_BAD_REQUEST}; {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of @@ -3276,7 +3316,7 @@ is_password_settings_correct(XEl, StateData) -> {<<"var">>, Var}], children = [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, jlib:jid_to_string(JID)}]} + children = [{xmlcdata, jid:to_string(JID)}]} || JID <- JIDList]}). get_default_room_maxusers(RoomState) -> @@ -3312,7 +3352,7 @@ get_config(Lang, StateData, From) -> translate:translate( Lang, <<"Configuration of room ~s">>), - [jlib:jid_to_string(StateData#state.jid)]))}]}, + [jid:to_string(StateData#state.jid)]))}]}, #xmlel{name = <<"field">>, attrs = [{<<"type">>, <<"hidden">>}, @@ -3429,6 +3469,53 @@ get_config(Lang, StateData, From) -> children = [{xmlcdata, <<"anyone">>}]}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-multi">>}, + {<<"label">>, + translate:translate(Lang, + <<"Roles for which Presence is Broadcasted">>)}, + {<<"var">>, <<"muc#roomconfig_presencebroadcast">>}], + children = + lists:map( + fun(Role) -> + #xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + atom_to_binary(Role, utf8)}]} + end, Config#config.presence_broadcast + ) ++ + [#xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + translate:translate(Lang, + <<"Moderator">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"moderator">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + translate:translate(Lang, + <<"Participant">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"participant">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + translate:translate(Lang, + <<"Visitor">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"visitor">>}]}]} + ]}, ?BOOLXFIELD(<<"Make room members-only">>, <<"muc#roomconfig_membersonly">>, (Config#config.members_only)), @@ -3519,6 +3606,13 @@ get_config(Lang, StateData, From) -> <<"captcha_protected">>, (Config#config.captcha_protected))]; false -> [] + end ++ + case gen_mod:is_loaded(StateData#state.server_host, mod_mam) of + true -> + [?BOOLXFIELD(<<"Enable message archiving">>, + <<"muc#roomconfig_mam">>, + (Config#config.mam))]; + false -> [] end ++ [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>, @@ -3695,6 +3789,28 @@ set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} set_xoption([{<<"anonymous">>, [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(anonymous, Val); +set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts], + Config) -> + Roles = + lists:foldl( + fun(_S, error) -> error; + (S, {M, P, V}) -> + case S of + <<"moderator">> -> {true, P, V}; + <<"participant">> -> {M, true, V}; + <<"visitor">> -> {M, P, true}; + _ -> error + end + end, {false, false, false}, Vals), + case Roles of + error -> {error, ?ERR_BAD_REQUEST}; + {M, P, V} -> + Res = + if M -> [moderator]; true -> [] end ++ + if P -> [participant]; true -> [] end ++ + if V -> [visitor]; true -> [] end, + set_xoption(Opts, Config#config{presence_broadcast = Res}) + end; set_xoption([{<<"muc#roomconfig_allowvoicerequests">>, [Val]} | Opts], @@ -3728,11 +3844,13 @@ set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(logging, Val); +set_xoption([{<<"muc#roomconfig_mam">>, [Val]}|Opts], Config) -> + ?SET_BOOL_XOPT(mam, Val); set_xoption([{<<"muc#roomconfig_captcha_whitelist">>, Vals} | Opts], Config) -> - JIDs = [jlib:string_to_jid(Val) || Val <- Vals], + JIDs = [jid:from_string(Val) || Val <- Vals], ?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs); set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) -> set_xoption(Opts, Config); @@ -3740,6 +3858,7 @@ set_xoption([_ | _Opts], _Config) -> {error, ?ERR_BAD_REQUEST}. change_config(Config, StateData) -> + send_config_change_info(Config, StateData), NSD = StateData#state{config = Config}, case {(StateData#state.config)#config.persistent, Config#config.persistent} @@ -3760,6 +3879,39 @@ change_config(Config, StateData) -> _ -> {result, [], NSD} end. +send_config_change_info(Config, #state{config = Config}) -> ok; +send_config_change_info(New, #state{config = Old} = StateData) -> + Codes = case {Old#config.logging, New#config.logging} of + {false, true} -> [<<"170">>]; + {true, false} -> [<<"171">>]; + _ -> [] + end + ++ + case {Old#config.anonymous, New#config.anonymous} of + {true, false} -> [<<"172">>]; + {false, true} -> [<<"173">>]; + _ -> [] + end + ++ + case Old#config{anonymous = New#config.anonymous, + logging = New#config.logging} of + New -> []; + _ -> [<<"104">>] + end, + StatusEls = [#xmlel{name = <<"status">>, + attrs = [{<<"code">>, Code}], + children = []} || Code <- Codes], + Message = #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}, + {<<"id">>, randoms:get_string()}], + children = [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_MUC_USER}], + children = StatusEls}]}, + send_multiple(StateData#state.jid, + StateData#state.server_host, + StateData#state.users, + Message). + remove_nonmembers(StateData) -> lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) -> Affiliation = get_affiliation(JID, SD), @@ -3852,10 +4004,17 @@ set_opts([{Opt, Val} | Opts], StateData) -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}}; + presence_broadcast -> + StateData#state{config = + (StateData#state.config)#config{presence_broadcast = + Val}}; logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}}; + mam -> + StateData#state{config = + (StateData#state.config)#config{mam = Val}}; captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist @@ -3912,6 +4071,7 @@ make_opts(StateData) -> ?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous), ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users), ?MAKE_CONFIG_OPT(allow_voice_requests), + ?MAKE_CONFIG_OPT(mam), ?MAKE_CONFIG_OPT(voice_request_min_interval), ?MAKE_CONFIG_OPT(vcard), {captcha_whitelist, @@ -3942,7 +4102,7 @@ destroy_room(DEl, StateData) -> children = []}, DEl]}]}, - ejabberd_router:route(jlib:jid_replace_resource(StateData#state.jid, + ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet) end, @@ -3995,6 +4155,15 @@ process_iq_disco_info(_From, get, Lang, StateData) -> <<"muc_moderated">>, <<"muc_unmoderated">>), ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), <<"muc_passwordprotected">>, <<"muc_unsecured">>)] + ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam), + Config#config.mam} of + {true, true} -> + [?FEATURE(?NS_MAM_TMP), + ?FEATURE(?NS_MAM_0), + ?FEATURE(?NS_MAM_1)]; + _ -> + [] + end ++ iq_disco_info_extras(Lang, StateData), StateData}. @@ -4056,7 +4225,7 @@ process_iq_captcha(_From, set, _Lang, SubEl, process_iq_vcard(_From, get, _Lang, _SubEl, StateData) -> #state{config = #config{vcard = VCardRaw}} = StateData, - case xml_stream:parse_element(VCardRaw) of + case fxml_stream:parse_element(VCardRaw) of #xmlel{children = VCardEls} -> {result, VCardEls, StateData}; {error, _} -> @@ -4065,7 +4234,7 @@ process_iq_vcard(_From, get, _Lang, _SubEl, StateData) -> process_iq_vcard(From, set, Lang, SubEl, StateData) -> case get_affiliation(From, StateData) of owner -> - VCardRaw = xml:element_to_binary(SubEl), + VCardRaw = fxml:element_to_binary(SubEl), Config = StateData#state.config, NewConfig = Config#config{vcard = VCardRaw}, change_config(NewConfig, StateData); @@ -4109,7 +4278,7 @@ get_mucroom_disco_items(StateData) -> #xmlel{name = <<"item">>, attrs = [{<<"jid">>, - jlib:jid_to_string({StateData#state.room, + jid:to_string({StateData#state.room, StateData#state.host, Nick})}, {<<"name">>, Nick}], @@ -4124,7 +4293,7 @@ is_voice_request(Els) -> lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = El, false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> case jlib:parse_xdata_submit(El) of [_ | _] = Fields -> @@ -4186,7 +4355,7 @@ prepare_request_form(Requester, Nick, Lang) -> [{xmlcdata, <<"participant">>}]}]}, ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, - (jlib:jid_to_string(Requester))), + (jid:to_string(Requester))), ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, Nick), ?BOOLXFIELD(<<"Grant voice to this person?">>, @@ -4207,7 +4376,7 @@ is_voice_approvement(Els) -> lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = El, false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_XDATA -> case jlib:parse_xdata_submit(El) of [_ | _] = Fs -> @@ -4243,7 +4412,7 @@ extract_jid_from_voice_approvement(Els) -> Res -> Res end, lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) -> - case jlib:string_to_jid(JIDStr) of + case jid:from_string(JIDStr) of error -> error; J -> {ok, J} end; @@ -4261,9 +4430,9 @@ is_invitation(Els) -> lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = El, false) -> - case xml:get_attr_s(<<"xmlns">>, Attrs) of + case fxml:get_attr_s(<<"xmlns">>, Attrs) of ?NS_MUC_USER -> - case xml:get_subtag(El, <<"invite">>) of + case fxml:get_subtag(El, <<"invite">>) of false -> false; _ -> true end; @@ -4279,20 +4448,20 @@ check_invitation(From, Els, Lang, StateData) -> (StateData#state.config)#config.allow_user_invites orelse FAffiliation == admin orelse FAffiliation == owner, - InviteEl = case xml:remove_cdata(Els) of + InviteEl = case fxml:remove_cdata(Els) of [#xmlel{name = <<"x">>, children = Els1} = XEl] -> - case xml:get_tag_attr_s(<<"xmlns">>, XEl) of + case fxml:get_tag_attr_s(<<"xmlns">>, XEl) of ?NS_MUC_USER -> ok; _ -> throw({error, ?ERR_BAD_REQUEST}) end, - case xml:remove_cdata(Els1) of + case fxml:remove_cdata(Els1) of [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1; _ -> throw({error, ?ERR_BAD_REQUEST}) end; _ -> throw({error, ?ERR_BAD_REQUEST}) end, JID = case - jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, + jid:from_string(fxml:get_tag_attr_s(<<"to">>, InviteEl)) of error -> throw({error, ?ERR_JID_MALFORMED}); @@ -4301,16 +4470,16 @@ check_invitation(From, Els, Lang, StateData) -> case CanInvite of false -> throw({error, ?ERR_NOT_ALLOWED}); true -> - Reason = xml:get_path_s(InviteEl, + Reason = fxml:get_path_s(InviteEl, [{elem, <<"reason">>}, cdata]), - ContinueEl = case xml:get_path_s(InviteEl, + ContinueEl = case fxml:get_path_s(InviteEl, [{elem, <<"continue">>}]) of <<>> -> []; Continue1 -> [Continue1] end, IEl = [#xmlel{name = <<"invite">>, - attrs = [{<<"from">>, jlib:jid_to_string(From)}], + attrs = [{<<"from">>, jid:to_string(From)}], children = [#xmlel{name = <<"reason">>, attrs = [], children = [{xmlcdata, Reason}]}] @@ -4333,11 +4502,10 @@ check_invitation(From, Els, Lang, StateData) -> translate:translate( Lang, <<"~s invites you to the room ~s">>), - [jlib:jid_to_string(From), - jlib:jid_to_string({StateData#state.room, + [jid:to_string(From), + jid:to_string({StateData#state.room, StateData#state.host, <<"">>})]), - case (StateData#state.config)#config.password_protected of @@ -4365,7 +4533,7 @@ check_invitation(From, Els, Lang, StateData) -> attrs = [{<<"xmlns">>, ?NS_XCONFERENCE}, {<<"jid">>, - jlib:jid_to_string({StateData#state.room, + jid:to_string({StateData#state.room, StateData#state.host, <<"">>})}], children = [{xmlcdata, Reason}]}, @@ -4390,15 +4558,15 @@ handle_roommessage_from_nonparticipant(Packet, Lang, %% Check in the packet is a decline. %% If so, also returns the splitted packet. -%% This function must be catched, +%% This function must be catched, %% because it crashes when the packet is not a decline message. check_decline_invitation(Packet) -> #xmlel{name = <<"message">>} = Packet, - XEl = xml:get_subtag(Packet, <<"x">>), - (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl), - DEl = xml:get_subtag(XEl, <<"decline">>), - ToString = xml:get_tag_attr_s(<<"to">>, DEl), - ToJID = jlib:string_to_jid(ToString), + XEl = fxml:get_subtag(Packet, <<"x">>), + (?NS_MUC_USER) = fxml:get_tag_attr_s(<<"xmlns">>, XEl), + DEl = fxml:get_subtag(XEl, <<"decline">>), + ToString = fxml:get_tag_attr_s(<<"to">>, DEl), + ToJID = jid:from_string(ToString), {true, {Packet, XEl, DEl, ToJID}}. %% Send the decline to the inviter user. @@ -4406,7 +4574,7 @@ check_decline_invitation(Packet) -> send_decline_invitation({Packet, XEl, DEl, ToJID}, RoomJID, FromJID) -> FromString = - jlib:jid_to_string(jlib:jid_remove_resource(FromJID)), + jid:to_string(jid:remove_resource(FromJID)), #xmlel{name = <<"decline">>, attrs = DAttrs, children = DEls} = DEl, @@ -4418,7 +4586,7 @@ send_decline_invitation({Packet, XEl, DEl, ToJID}, Packet2 = replace_subelement(Packet, XEl2), ejabberd_router:route(RoomJID, ToJID, Packet2). -%% Given an element and a new subelement, +%% Given an element and a new subelement, %% replace the instance of the subelement in element with the new subelement. replace_subelement(#xmlel{name = Name, attrs = Attrs, children = SubEls}, @@ -4456,7 +4624,7 @@ add_to_log(Type, Data, StateData) -> %% Users number checking tab_add_online_user(JID, StateData) -> - {LUser, LServer, LResource} = jlib:jid_tolower(JID), + {LUser, LServer, LResource} = jid:tolower(JID), US = {LUser, LServer}, Room = StateData#state.room, Host = StateData#state.host, @@ -4465,7 +4633,7 @@ tab_add_online_user(JID, StateData) -> room = Room, host = Host}). tab_remove_online_user(JID, StateData) -> - {LUser, LServer, LResource} = jlib:jid_tolower(JID), + {LUser, LServer, LResource} = jid:tolower(JID), US = {LUser, LServer}, Room = StateData#state.room, Host = StateData#state.host, @@ -4474,7 +4642,7 @@ tab_remove_online_user(JID, StateData) -> room = Room, host = Host}). tab_count_user(JID) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), + {LUser, LServer, _} = jid:tolower(JID), US = {LUser, LServer}, case catch ets:select(muc_online_users, [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) @@ -4484,7 +4652,14 @@ tab_count_user(JID) -> end. element_size(El) -> - byte_size(xml:element_to_binary(El)). + byte_size(fxml:element_to_binary(El)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Multicast + +send_multiple(From, Server, Users, Packet) -> + JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)], + ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Detect messange stanzas that don't have meaninful content @@ -4495,10 +4670,3 @@ has_body_or_subject(Packet) -> (#xmlel{name = <<"subject">>}) -> false; (_) -> true end, Packet#xmlel.children). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Multicast - -send_multiple(From, Server, Users, Packet) -> - JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)], - ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). |