summaryrefslogtreecommitdiff
path: root/src/mod_muc_room.erl
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-07-25 13:50:30 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-07-25 13:50:30 +0300
commit179fcd9521ef8db4626ca110ba80c502d810c814 (patch)
tree78e0b2410b0f8a4cbe95f84bfb30e58d1b205e3e /src/mod_muc_room.erl
parentFix hooks de-registration (diff)
Rewrite mod_mam and mod_muc to use XML generator
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r--src/mod_muc_room.erl4469
1 files changed, 1865 insertions, 2604 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 773953c4..29b7942c 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -51,7 +51,7 @@
-include("ejabberd.hrl").
-include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_muc_room.hrl").
@@ -72,6 +72,18 @@
-endif.
+-type state() :: #state{}.
+-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]).
+
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@@ -133,349 +145,187 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
{ok, normal_state, State}.
normal_state({route, From, <<"">>,
- #xmlel{name = <<"message">>, attrs = Attrs,
- children = Els} =
- Packet},
- StateData) ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+ #message{type = Type, lang = Lang} = Packet}, StateData) ->
case is_user_online(From, StateData) orelse
- is_user_allowed_message_nonparticipant(From, StateData)
- of
- true ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"groupchat">> ->
- Activity = get_user_activity(From, StateData),
- 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)
- * 1000000),
- Size = element_size(Packet),
- {MessageShaper, MessageShaperInterval} =
- shaper:update(Activity#activity.message_shaper, Size),
- if Activity#activity.message /= undefined ->
- ErrText = <<"Traffic rate limit is exceeded">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_RESOURCE_CONSTRAINT(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData};
- Now >=
- Activity#activity.message_time + MinMessageInterval,
- MessageShaperInterval == 0 ->
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- RoomQueueEmpty =
- queue:is_empty(StateData#state.room_queue),
- if RoomShaperInterval == 0, RoomQueueEmpty ->
- NewActivity = Activity#activity{message_time =
- Now,
- message_shaper =
- MessageShaper},
- StateData1 = store_user_activity(From,
- NewActivity,
- StateData),
- StateData2 = StateData1#state{room_shaper =
- RoomShaper},
- process_groupchat_message(From, Packet,
- StateData2);
- true ->
- StateData1 = if RoomQueueEmpty ->
- erlang:send_after(RoomShaperInterval,
- self(),
- process_room_queue),
- StateData#state{room_shaper =
- RoomShaper};
- true -> StateData
- end,
- NewActivity = Activity#activity{message_time =
- Now,
- message_shaper =
- MessageShaper,
- message = Packet},
- RoomQueue = queue:in({message, From},
- StateData#state.room_queue),
- StateData2 = store_user_activity(From,
- NewActivity,
- StateData1),
- StateData3 = StateData2#state{room_queue =
- RoomQueue},
- {next_state, normal_state, StateData3}
- end;
- true ->
- MessageInterval = (Activity#activity.message_time +
- MinMessageInterval
- - Now)
- div 1000,
- Interval = lists:max([MessageInterval,
- MessageShaperInterval]),
- erlang:send_after(Interval, self(),
- {process_user_message, From}),
- NewActivity = Activity#activity{message = Packet,
- message_shaper =
- MessageShaper},
- StateData1 = store_user_activity(From, NewActivity,
- StateData),
- {next_state, normal_state, StateData1}
- end;
- <<"error">> ->
- case is_user_online(From, StateData) of
- true ->
- 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)),
- close_room_if_temporary_and_empty(NewState);
- _ -> {next_state, normal_state, StateData}
- end;
- <<"chat">> ->
- ErrText =
- <<"It is not allowed to send private messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData};
- Type when (Type == <<"">>) or (Type == <<"normal">>) ->
- IsInvitation = is_invitation(Els),
- IsVoiceRequest = is_voice_request(Els) and
- is_visitor(From, StateData),
- IsVoiceApprovement = is_voice_approvement(Els) and
- not is_visitor(From, StateData),
- if IsInvitation ->
- case catch check_invitation(From, Packet, Lang, StateData)
- of
- {error, Error} ->
- Err = jlib:make_error_reply(Packet, Error),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData};
- IJID ->
- Config = StateData#state.config,
- case Config#config.members_only of
- true ->
- case get_affiliation(IJID, StateData) of
- none ->
- NSD = set_affiliation(IJID, member,
- StateData),
- send_affiliation(IJID, member,
- StateData),
- store_room(NSD),
- {next_state, normal_state, NSD};
- _ -> {next_state, normal_state, StateData}
- end;
- false -> {next_state, normal_state, StateData}
- end
- end;
- IsVoiceRequest ->
- NewStateData = case
- (StateData#state.config)#config.allow_voice_requests
- of
- true ->
- MinInterval =
- (StateData#state.config)#config.voice_request_min_interval,
- BareFrom =
- jid:remove_resource(jid:tolower(From)),
- NowPriority = -p1_time_compat:system_time(micro_seconds),
- CleanPriority = NowPriority +
- MinInterval *
- 1000000,
- Times =
- clean_treap(StateData#state.last_voice_request_time,
- CleanPriority),
- case treap:lookup(BareFrom, Times)
- of
- error ->
- Times1 =
- treap:insert(BareFrom,
- NowPriority,
- true, Times),
- NSD =
- StateData#state{last_voice_request_time
- =
- Times1},
- send_voice_request(From, NSD),
- NSD;
- {ok, _, _} ->
- ErrText =
- <<"Please, wait for a while before sending "
- "new voice request">>,
- Err =
- jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData#state{last_voice_request_time
- = Times}
- end;
- false ->
- ErrText =
- <<"Voice requests are disabled in this "
- "conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData
- end,
- {next_state, normal_state, NewStateData};
- IsVoiceApprovement ->
- NewStateData = case is_moderator(From, StateData) of
- true ->
- case
- extract_jid_from_voice_approvement(Els)
- of
- error ->
- ErrText =
- <<"Failed to extract JID from your voice "
- "request approval">>,
- Err =
- jlib:make_error_reply(Packet,
- ?ERRT_BAD_REQUEST(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData;
- {ok, TargetJid} ->
- case is_visitor(TargetJid,
- StateData)
- of
- true ->
- Reason = <<>>,
- NSD =
- set_role(TargetJid,
- participant,
- StateData),
- catch
- send_new_presence(TargetJid,
- Reason,
- NSD,
- StateData),
- NSD;
- _ -> StateData
- end
- end;
- _ ->
- ErrText =
- <<"Only moderators can approve voice requests">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ALLOWED(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid,
- From, Err),
- StateData
- end,
- {next_state, normal_state, NewStateData};
- true -> {next_state, normal_state, StateData}
- end;
- _ ->
- ErrText = <<"Improper message type">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
- end;
- _ ->
- case fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- _ ->
- handle_roommessage_from_nonparticipant(Packet, Lang,
- StateData, From)
- end,
- {next_state, normal_state, StateData}
- end;
-normal_state({route, From, <<"">>,
- #xmlel{name = <<"iq">>} = Packet},
- StateData) ->
- case jlib:iq_query_info(Packet) of
- 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)),
+ is_user_allowed_message_nonparticipant(From, StateData) of
+ true when Type == groupchat ->
+ Activity = get_user_activity(From, StateData),
+ 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)
+ * 1000000),
+ Size = element_size(Packet),
+ {MessageShaper, MessageShaperInterval} =
+ shaper:update(Activity#activity.message_shaper, Size),
+ if Activity#activity.message /= undefined ->
+ ErrText = <<"Traffic rate limit is exceeded">>,
+ Err = xmpp:make_error(
+ Packet,
+ xmpp:err_resource_constraint(ErrText, Lang)),
+ ejabberd_router:route(StateData#state.jid, From, Err),
{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_MUCSUB))
- 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, _} ->
- Txt = <<"Disco info is not available for this node">>,
- {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
- end;
- ?NS_DISCO_ITEMS ->
- process_iq_disco_items(From, Type, Lang, StateData);
- ?NS_VCARD ->
- process_iq_vcard(From, Type, Lang, SubEl, StateData);
- ?NS_MUCSUB ->
- process_iq_mucsub(From, Packet, IQ, 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};
- {ignore, SD} -> {ignore, SD};
- {error, Error, ResStateData} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- ResStateData};
- {error, Error} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- StateData}
- end,
- if IQRes /= ignore ->
- ejabberd_router:route(
- StateData#state.jid, From, jlib:iq_to_xml(IQRes));
+ Now >= Activity#activity.message_time + MinMessageInterval,
+ MessageShaperInterval == 0 ->
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
+ if RoomShaperInterval == 0, RoomQueueEmpty ->
+ NewActivity = Activity#activity{
+ message_time = Now,
+ message_shaper = MessageShaper},
+ StateData1 = store_user_activity(From,
+ NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_shaper =
+ RoomShaper},
+ process_groupchat_message(From, Packet,
+ StateData2);
true ->
- ok
- end,
- case NewStateData of
- stop -> {stop, normal, StateData};
- _ -> {next_state, normal_state, NewStateData}
+ StateData1 = if RoomQueueEmpty ->
+ erlang:send_after(RoomShaperInterval,
+ self(),
+ process_room_queue),
+ StateData#state{room_shaper =
+ RoomShaper};
+ true -> StateData
+ end,
+ NewActivity = Activity#activity{
+ message_time = Now,
+ message_shaper = MessageShaper,
+ message = Packet},
+ RoomQueue = queue:in({message, From},
+ StateData#state.room_queue),
+ StateData2 = store_user_activity(From,
+ NewActivity,
+ StateData1),
+ StateData3 = StateData2#state{room_queue = RoomQueue},
+ {next_state, normal_state, StateData3}
end;
+ true ->
+ MessageInterval = (Activity#activity.message_time +
+ MinMessageInterval - Now) div 1000,
+ Interval = lists:max([MessageInterval,
+ MessageShaperInterval]),
+ erlang:send_after(Interval, self(),
+ {process_user_message, From}),
+ NewActivity = Activity#activity{
+ message = Packet,
+ message_shaper = MessageShaper},
+ StateData1 = store_user_activity(From, NewActivity, StateData),
+ {next_state, normal_state, StateData1}
+ end;
+ true when Type == error ->
+ case is_user_online(From, StateData) of
+ true ->
+ 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)),
+ close_room_if_temporary_and_empty(NewState);
_ ->
- 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;
+ true when Type == chat ->
+ ErrText = <<"It is not allowed to send private messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
+ {next_state, normal_state, StateData};
+ true when Type == normal ->
+ {next_state, normal_state,
+ try xmpp:decode_els(Packet) of
+ Pkt -> process_normal_message(From, Pkt, StateData)
+ catch _:{xmpp_codec, Why} ->
+ Txt = xmpp:format_error(Why),
+ Err = xmpp:err_bad_request(Txt, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Packet, Err),
+ StateData
+ end};
+ true ->
+ ErrText = <<"Improper message type">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ 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);
+ false ->
+ {next_state, normal_state, StateData}
end;
-normal_state({route, From, Nick,
- #xmlel{name = <<"presence">>} = Packet},
- StateData) ->
+normal_state({route, From, <<"">>,
+ #iq{type = Type, lang = Lang, sub_els = [_]} = IQ0},
+ StateData) when Type == get; Type == set ->
+ try
+ case ejabberd_hooks:run_fold(
+ muc_process_iq,
+ StateData#state.server_host,
+ xmpp:set_from_to(xmpp:decode_els(IQ0),
+ From, StateData#state.jid),
+ [StateData]) of
+ ignore ->
+ {next_state, normal_state, StateData};
+ #iq{type = T} = IQRes when T == error; T == result ->
+ ejabberd_router:route(StateData#state.jid, From, IQRes),
+ {next_state, normal_state, StateData};
+ #iq{sub_els = [SubEl]} = IQ ->
+ Res1 = case xmpp:get_ns(SubEl) of
+ ?NS_MUC_ADMIN ->
+ process_iq_admin(From, IQ, StateData);
+ ?NS_MUC_OWNER ->
+ process_iq_owner(From, IQ, StateData);
+ ?NS_DISCO_INFO when SubEl#disco_info.node == undefined ->
+ 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 ->
+ process_iq_vcard(From, IQ, StateData);
+ ?NS_MUCSUB ->
+ process_iq_mucsub(From, IQ, StateData);
+ ?NS_CAPTCHA ->
+ process_iq_captcha(From, IQ, StateData);
+ _ ->
+ {error, xmpp:err_feature_not_implemented()}
+ end,
+ {IQRes, NewStateData} =
+ case Res1 of
+ {result, Res, SD} ->
+ {xmpp:make_iq_result(IQ, Res), SD};
+ {result, Res} ->
+ {xmpp:make_iq_result(IQ, Res), StateData};
+ {ignore, SD} ->
+ {ignore, SD};
+ {error, Error, ResStateData} ->
+ {xmpp:make_error(IQ0, Error), ResStateData};
+ {error, Error} ->
+ {xmpp:make_error(IQ0, Error), StateData}
+ end,
+ if IQRes /= ignore ->
+ ejabberd_router:route(StateData#state.jid, From, IQRes);
+ true ->
+ ok
+ end,
+ case NewStateData of
+ stop -> {stop, normal, StateData};
+ _ -> {next_state, normal_state, NewStateData}
+ end
+ end
+ catch _:{xmpp_codec, Why} ->
+ ErrTxt = xmpp:format_error(Why),
+ Err = xmpp:make_error(IQ0, xmpp:err_bad_request(ErrTxt, Lang)),
+ ejabberd_router:route(StateData#state.jid, From, Err)
+ end;
+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};
+normal_state({route, From, Nick, #presence{} = Packet}, StateData) ->
Activity = get_user_activity(From, StateData),
Now = p1_time_compat:system_time(micro_seconds),
MinPresenceInterval =
@@ -485,185 +335,135 @@ normal_state({route, From, Nick,
I
end, 0)
* 1000000),
- if (Now >=
- Activity#activity.presence_time + MinPresenceInterval)
- and (Activity#activity.presence == undefined) ->
- NewActivity = Activity#activity{presence_time = Now},
- StateData1 = store_user_activity(From, NewActivity,
- StateData),
- process_presence(From, Nick, Packet, StateData1);
+ if (Now >= Activity#activity.presence_time + MinPresenceInterval)
+ and (Activity#activity.presence == undefined) ->
+ NewActivity = Activity#activity{presence_time = Now},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ process_presence(From, Nick, Packet, StateData1);
true ->
- if Activity#activity.presence == undefined ->
- Interval = (Activity#activity.presence_time +
- MinPresenceInterval
- - Now)
- div 1000,
- erlang:send_after(Interval, self(),
- {process_user_presence, From});
- true -> ok
- end,
- NewActivity = Activity#activity{presence =
- {Nick, Packet}},
- StateData1 = store_user_activity(From, NewActivity,
- StateData),
- {next_state, normal_state, StateData1}
+ if Activity#activity.presence == undefined ->
+ Interval = (Activity#activity.presence_time +
+ MinPresenceInterval - Now) div 1000,
+ erlang:send_after(Interval, self(),
+ {process_user_presence, From});
+ true -> ok
+ end,
+ NewActivity = Activity#activity{presence = {Nick, Packet}},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ {next_state, normal_state, StateData1}
end;
normal_state({route, From, ToNick,
- #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
+ #message{type = Type, lang = Lang} = Packet},
StateData) ->
- 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 = <<"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};
- forget_message -> {next_state, normal_state, StateData};
- continue_delivery ->
- case
- {(StateData#state.config)#config.allow_private_messages,
- is_user_online(From, StateData)}
- of
- {true, true} ->
- case Type of
- <<"groupchat">> ->
- ErrText =
- <<"It is not allowed to send private messages "
- "of type \"groupchat\"">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_BAD_REQUEST(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err);
- _ ->
- case find_jids_by_nick(ToNick, StateData) of
- false ->
- ErrText =
- <<"Recipient is not in the conference room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_ITEM_NOT_FOUND(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err);
+ case decide_fate_message(Packet, From, StateData) of
+ {expulse_sender, Reason} ->
+ ?DEBUG(Reason, []),
+ 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};
+ forget_message ->
+ {next_state, normal_state, StateData};
+ continue_delivery ->
+ case {(StateData#state.config)#config.allow_private_messages,
+ is_user_online(From, StateData)} of
+ {true, true} when Type == groupchat ->
+ ErrText = <<"It is not allowed to send private messages "
+ "of type \"groupchat\"">>,
+ Err = xmpp:err_bad_request(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ {true, true} ->
+ case find_jids_by_nick(ToNick, StateData) of
+ [] ->
+ ErrText = <<"Recipient is not in the conference room">>,
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
ToJIDs ->
SrcIsVisitor = is_visitor(From, StateData),
- DstIsModerator = is_moderator(hd(ToJIDs),
- StateData),
+ DstIsModerator = is_moderator(hd(ToJIDs), StateData),
PmFromVisitors =
(StateData#state.config)#config.allow_private_messages_from_visitors,
if SrcIsVisitor == false;
PmFromVisitors == anyone;
(PmFromVisitors == moderators) and
- DstIsModerator ->
- {ok, #user{nick = FromNick}} =
- (?DICT):find(jid:tolower(From),
- StateData#state.users),
- FromNickJID =
- jid:replace_resource(StateData#state.jid,
- FromNick),
- X = #xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}]},
- PrivMsg = fxml:append_subtags(Packet, [X]),
- [ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
- || ToJID <- ToJIDs];
+ DstIsModerator ->
+ {ok, #user{nick = FromNick}} =
+ (?DICT):find(jid:tolower(From),
+ StateData#state.users),
+ FromNickJID =
+ jid:replace_resource(StateData#state.jid,
+ FromNick),
+ X = #muc_user{},
+ PrivMsg = xmpp:set_subtag(Packet, X),
+ [ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
+ || ToJID <- ToJIDs];
true ->
- ErrText =
- <<"It is not allowed to send private messages">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
+ ErrText = <<"It is not allowed to send private messages">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err)
end
- end
- end;
- {true, false} ->
- ErrText =
- <<"Only occupants are allowed to send messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err);
- {false, _} ->
- ErrText =
- <<"It is not allowed to send private messages">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end,
+ end;
+ {true, false} ->
+ ErrText = <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ {false, _} ->
+ ErrText = <<"It is not allowed to send private messages">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err)
+ end,
{next_state, normal_state, StateData}
end;
normal_state({route, From, ToNick,
- #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
+ #iq{id = StanzaId, lang = Lang} = Packet},
StateData) ->
- 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
- {true, {true, NewId, FromFull}} ->
- case find_jid_by_nick(ToNick, StateData) of
- false ->
- case jlib:iq_query_info(Packet) of
- reply -> ok;
- _ ->
- ErrText = <<"Recipient is not in the conference room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_ITEM_NOT_FOUND(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end;
- ToJID ->
- {ok, #user{nick = FromNick}} =
- (?DICT):find(jid:tolower(FromFull),
- StateData#state.users),
- {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
- StanzaId, NewId, Packet),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- FromNick),
- ToJID2, Packet2)
- end;
- {_, {false, _, _}} ->
- case jlib:iq_query_info(Packet) of
- reply -> ok;
- _ ->
- ErrText =
- <<"Only occupants are allowed to send queries "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang,
- ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end;
- _ ->
- case jlib:iq_query_info(Packet) of
- reply -> ok;
- _ ->
- ErrText = <<"Queries to the conference members are "
- "not allowed in this room">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ALLOWED(Lang, ErrText)),
- ejabberd_router:route(jid:replace_resource(StateData#state.jid,
- ToNick),
- From, Err)
- end
+ is_user_online_iq(StanzaId, From, StateData)} of
+ {true, {true, NewId, FromFull}} ->
+ case find_jid_by_nick(ToNick, StateData) of
+ false ->
+ ErrText = <<"Recipient is not in the conference room">>,
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ ToJID ->
+ {ok, #user{nick = FromNick}} =
+ (?DICT):find(jid:tolower(FromFull), StateData#state.users),
+ {ToJID2, Packet2} = handle_iq_vcard(ToJID, NewId, Packet),
+ ejabberd_router:route(
+ jid:replace_resource(StateData#state.jid, FromNick),
+ ToJID2, Packet2)
+ end;
+ {_, {false, _, _}} ->
+ ErrText = <<"Only occupants are allowed to send queries "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err);
+ _ ->
+ ErrText = <<"Queries to the conference members are "
+ "not allowed in this room">>,
+ Err = xmpp:err_not_allowed(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, ToNick),
+ From, Packet, Err)
end,
{next_state, normal_state, StateData};
normal_state(_Event, StateData) ->
@@ -671,11 +471,7 @@ normal_state(_Event, StateData) ->
handle_event({service_message, Msg}, _StateName,
StateData) ->
- MessagePkt = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"body">>, attrs = [],
- children = [{xmlcdata, Msg}]}]},
+ MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)},
send_wrapped_multiple(
StateData#state.jid,
StateData#state.users,
@@ -687,22 +483,9 @@ handle_event({service_message, Msg}, _StateName,
{next_state, normal_state, NSD};
handle_event({destroy, Reason}, _StateName,
StateData) ->
- {result, [], stop} = destroy_room(#xmlel{name =
- <<"destroy">>,
- attrs =
- [{<<"xmlns">>, ?NS_MUC_OWNER}],
- children =
- case Reason of
- none -> [];
- _Else ->
- [#xmlel{name =
- <<"reason">>,
- attrs = [],
- children =
- [{xmlcdata,
- Reason}]}]
- end},
- StateData),
+ {result, undefined, stop} =
+ destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason},
+ StateData),
?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
[jid:to_string(StateData#state.jid), Reason]),
add_to_log(room_existence, destroyed, StateData),
@@ -710,7 +493,7 @@ handle_event({destroy, Reason}, _StateName,
handle_event(destroy, StateName, StateData) ->
?INFO_MSG("Destroyed MUC room ~s",
[jid:to_string(StateData#state.jid)]),
- handle_event({destroy, none}, StateName, StateData);
+ handle_event({destroy, undefined}, StateName, StateData);
handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
{next_state, StateName,
@@ -741,7 +524,7 @@ handle_sync_event(get_state, _From, StateName,
{reply, {ok, StateData}, StateName, StateData};
handle_sync_event({change_config, Config}, _From,
StateName, StateData) ->
- {result, [], NSD} = change_config(Config, StateData),
+ {result, undefined, NSD} = change_config(Config, StateData),
{reply, {ok, NSD#state.config}, StateName, NSD};
handle_sync_event({change_state, NewStateData}, _From,
StateName, _StateData) ->
@@ -821,12 +604,11 @@ handle_info({captcha_failed, From}, normal_state,
{ok, {Nick, Packet}} ->
Robots = (?DICT):erase(From, StateData#state.robots),
Txt = <<"The CAPTCHA verification has failed">>,
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_AUTHORIZED(?MYLANG, Txt)),
- ejabberd_router:route % TODO: s/Nick/""/
- (jid:replace_resource(StateData#state.jid,
- Nick),
- From, Err),
+ Lang = xmpp:get_lang(Packet),
+ Err = xmpp:err_not_authorized(Txt, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
StateData#state{robots = Robots};
_ -> StateData
end,
@@ -845,22 +627,12 @@ terminate(Reason, _StateName, StateData) ->
"because of a system shutdown">>;
_ -> <<"Room terminates">>
end,
- ItemAttrs = [{<<"affiliation">>, <<"none">>},
- {<<"role">>, <<"none">>}],
- ReasonEl = #xmlel{name = <<"reason">>, attrs = [],
- children = [{xmlcdata, ReasonT}]},
- Packet = #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs = ItemAttrs,
- children = [ReasonEl]},
- #xmlel{name = <<"status">>,
- attrs = [{<<"code">>, <<"332">>}],
- children = []}]}]},
+ Packet = #presence{
+ type = unavailable,
+ sub_els = [#muc_user{items = [#muc_item{affiliation = none,
+ reason = ReasonT,
+ role = none}],
+ status_codes = [332]}]},
(?DICT):fold(fun (LJID, Info, _) ->
Nick = Info#user.nick,
case Reason of
@@ -883,14 +655,12 @@ terminate(Reason, _StateName, StateData) ->
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
-
+-spec route(pid(), jid(), binary(), stanza()) -> ok.
route(Pid, From, ToNick, Packet) ->
gen_fsm:send_event(Pid, {route, From, ToNick, Packet}).
-process_groupchat_message(From,
- #xmlel{name = <<"message">>, attrs = Attrs} = Packet,
- StateData) ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+-spec process_groupchat_message(jid(), message(), state()) -> fsm_next().
+process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) ->
case is_user_online(From, StateData) orelse
is_user_allowed_message_nonparticipant(From, StateData)
of
@@ -932,7 +702,7 @@ process_groupchat_message(From,
drop ->
{next_state, normal_state, StateData};
NewPacket1 ->
- NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
+ NewPacket = xmpp:remove_subtag(NewPacket1, #nick{}),
Node = if Subject == false -> ?NS_MUCSUB_NODES_MESSAGES;
true -> ?NS_MUCSUB_NODES_SUBJECT
end,
@@ -951,41 +721,136 @@ process_groupchat_message(From,
{next_state, normal_state, NewStateData2}
end;
_ ->
- Err = case
- (StateData#state.config)#config.allow_change_subj
- of
+ Err = case (StateData#state.config)#config.allow_change_subj of
true ->
- ?ERRT_FORBIDDEN(Lang,
- <<"Only moderators and participants are "
- "allowed to change the subject in this "
- "room">>);
+ xmpp:err_forbidden(
+ <<"Only moderators and participants are "
+ "allowed to change the subject in this "
+ "room">>, Lang);
_ ->
- ?ERRT_FORBIDDEN(Lang,
- <<"Only moderators are allowed to change "
- "the subject in this room">>)
+ xmpp:err_forbidden(
+ <<"Only moderators are allowed to change "
+ "the subject in this room">>, Lang)
end,
- ejabberd_router:route(StateData#state.jid, From,
- jlib:make_error_reply(Packet, Err)),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Packet, Err),
{next_state, normal_state, StateData}
end;
true ->
ErrText = <<"Visitors are not allowed to send messages "
"to all occupants">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Packet, Err),
{next_state, normal_state, StateData}
end;
false ->
- ErrText =
- <<"Only occupants are allowed to send messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(StateData#state.jid, From, Err),
+ ErrText = <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
{next_state, normal_state, StateData}
end.
+-spec process_normal_message(jid(), message(), state()) -> state().
+process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
+ IsInvitation = is_invitation(Pkt),
+ IsVoiceRequest = is_voice_request(Pkt) and
+ is_visitor(From, StateData),
+ IsVoiceApprovement = is_voice_approvement(Pkt) and
+ not is_visitor(From, StateData),
+ if IsInvitation ->
+ case check_invitation(From, Pkt, StateData) of
+ {error, Error} ->
+ ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error),
+ StateData;
+ IJID ->
+ Config = StateData#state.config,
+ case Config#config.members_only of
+ true ->
+ case get_affiliation(IJID, StateData) of
+ none ->
+ NSD = set_affiliation(IJID, member, StateData),
+ send_affiliation(IJID, member, StateData),
+ store_room(NSD),
+ NSD;
+ _ ->
+ StateData
+ end;
+ false ->
+ StateData
+ end
+ end;
+ IsVoiceRequest ->
+ case (StateData#state.config)#config.allow_voice_requests of
+ true ->
+ MinInterval = (StateData#state.config)#config.voice_request_min_interval,
+ BareFrom = jid:remove_resource(jid:tolower(From)),
+ NowPriority = -p1_time_compat:system_time(micro_seconds),
+ CleanPriority = NowPriority + MinInterval * 1000000,
+ Times = clean_treap(StateData#state.last_voice_request_time,
+ CleanPriority),
+ case treap:lookup(BareFrom, Times) of
+ error ->
+ Times1 = treap:insert(BareFrom,
+ NowPriority,
+ true, Times),
+ NSD = StateData#state{last_voice_request_time = Times1},
+ send_voice_request(From, Lang, NSD),
+ NSD;
+ {ok, _, _} ->
+ ErrText = <<"Please, wait for a while before sending "
+ "new voice request">>,
+ Err = xmpp:err_not_acceptable(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData#state{last_voice_request_time = Times}
+ end;
+ false ->
+ ErrText = <<"Voice requests are disabled in this conference">>,
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData
+ end;
+ IsVoiceApprovement ->
+ case is_moderator(From, StateData) of
+ true ->
+ case extract_jid_from_voice_approvement(Pkt) of
+ error ->
+ ErrText = <<"Failed to extract JID from your voice "
+ "request approval">>,
+ Err = xmpp:err_bad_request(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData;
+ TargetJid ->
+ case is_visitor(TargetJid, StateData) of
+ true ->
+ Reason = <<>>,
+ NSD = set_role(TargetJid,
+ participant,
+ StateData),
+ catch send_new_presence(TargetJid,
+ Reason,
+ NSD,
+ StateData),
+ NSD;
+ _ ->
+ StateData
+ end
+ end;
+ _ ->
+ ErrText = <<"Only moderators can approve voice requests">>,
+ Err = xmpp:err_not_allowed(ErrText, Lang),
+ ejabberd_router:route_error(
+ StateData#state.jid, From, Pkt, Err),
+ StateData
+ end;
+ true ->
+ StateData
+ end.
+
%% @doc Check if this non participant can send message to room.
%%
%% XEP-0045 v1.23:
@@ -993,6 +858,7 @@ process_groupchat_message(From,
%% an implementation MAY allow users with certain privileges
%% (e.g., a room owner, room admin, or service-level admin)
%% to send messages to the room even if those users are not occupants.
+-spec is_user_allowed_message_nonparticipant(jid(), state()) -> boolean().
is_user_allowed_message_nonparticipant(JID,
StateData) ->
case get_service_affiliation(JID, StateData) of
@@ -1002,6 +868,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.
+-spec get_participant_data(jid(), state()) -> {binary(), role(), boolean()}.
get_participant_data(From, StateData) ->
case (?DICT):find(jid:tolower(From),
StateData#state.users)
@@ -1011,14 +878,11 @@ get_participant_data(From, StateData) ->
error -> {<<"">>, moderator, false}
end.
-process_presence(From, Nick,
- #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0,
- StateData) ->
- Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
+-spec process_presence(jid(), binary(), presence(), state()) -> fsm_transition().
+process_presence(From, Nick, #presence{type = Type0} = Packet0, StateData) ->
IsOnline = is_user_online(From, StateData),
- IsSubscriber = is_subscriber(From, StateData),
- if Type0 == <<"">>;
- IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
+ if Type0 == available;
+ IsOnline and ((Type0 == unavailable) or (Type0 == error)) ->
case ejabberd_hooks:run_fold(muc_filter_presence,
StateData#state.server_host,
Packet0,
@@ -1027,119 +891,104 @@ process_presence(From, Nick,
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, IsSubscriber, 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;
- _ ->
- case is_initial_presence(From, StateData) of
- true ->
- subscriber_becomes_available(
- From, Nick, Packet, StateData);
- false ->
- change_nick(From, Nick, StateData)
- end
- end;
- _NotNickChange ->
- case is_initial_presence(From, StateData) of
- true ->
- subscriber_becomes_available(
- From, Nick, Packet, StateData);
- false ->
- Stanza = maybe_strip_status_from_presence(
- From, Packet, StateData),
- NewState = add_user_presence(From, Stanza,
- StateData),
- send_new_presence(From, NewState, StateData),
- NewState
- end
- end
- end
- end,
- close_room_if_temporary_and_empty(StateData1)
+ #presence{} = Packet ->
+ close_room_if_temporary_and_empty(
+ do_process_presence(From, Nick, Packet, StateData))
end;
true ->
- {next_state, normal_state, StateData}
+ {next_state, normal_state, StateData}
end.
+-spec do_process_presence(jid(), binary(), presence(), state()) ->
+ state().
+do_process_presence(From, Nick, #presence{type = available, lang = Lang} = Packet,
+ StateData) ->
+ case is_user_online(From, StateData) of
+ false ->
+ 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 = xmpp:err_not_allowed(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
+ StateData;
+ {true, _, _} ->
+ ErrText = <<"That nickname is already in use by another "
+ "occupant">>,
+ Err = xmpp:err_conflict(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
+ StateData;
+ {_, false, _} ->
+ ErrText = <<"That nickname is registered by another "
+ "person">>,
+ Err = xmpp:err_conflict(ErrText, Lang),
+ ejabberd_router:route_error(
+ jid:replace_resource(StateData#state.jid, Nick),
+ From, Packet, Err),
+ StateData;
+ _ ->
+ case is_initial_presence(From, StateData) of
+ true ->
+ subscriber_becomes_available(
+ From, Nick, Packet, StateData);
+ false ->
+ change_nick(From, Nick, StateData)
+ end
+ end;
+ _NotNickChange ->
+ case is_initial_presence(From, StateData) of
+ true ->
+ subscriber_becomes_available(
+ From, Nick, Packet, StateData);
+ false ->
+ Stanza = maybe_strip_status_from_presence(
+ From, Packet, StateData),
+ NewState = add_user_presence(From, Stanza,
+ StateData),
+ send_new_presence(From, NewState, StateData),
+ NewState
+ end
+ end
+ end;
+do_process_presence(From, Nick, #presence{type = unavailable} = Packet,
+ StateData) ->
+ IsSubscriber = is_subscriber(From, StateData),
+ 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 = xmpp:get_text(NewPacket#presence.status),
+ remove_online_user(From, NewState, IsSubscriber, Reason);
+do_process_presence(From, _Nick, #presence{type = error, lang = Lang} = Packet,
+ StateData) ->
+ 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)).
+
+-spec maybe_strip_status_from_presence(jid(), presence(),
+ state()) -> presence().
maybe_strip_status_from_presence(From, Packet, StateData) ->
case {(StateData#state.config)#config.allow_visitor_status,
is_visitor(From, StateData)} of
@@ -1148,6 +997,8 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
_Allowed -> Packet
end.
+-spec subscriber_becomes_available(jid(), binary(), presence(),
+ state()) -> state().
subscriber_becomes_available(From, Nick, Packet, StateData) ->
Stanza = maybe_strip_status_from_presence(From, Packet, StateData),
State1 = add_user_presence(From, Stanza, StateData),
@@ -1159,9 +1010,10 @@ subscriber_becomes_available(From, Nick, Packet, StateData) ->
send_initial_presence(From, State3, StateData),
State3.
+-spec close_room_if_temporary_and_empty(state()) -> fsm_transition().
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
- andalso (?DICT):to_list(StateData1#state.users) == []
+ andalso (?DICT):size(StateData1#state.users) == 0
of
true ->
?INFO_MSG("Destroyed MUC room ~s because it's temporary "
@@ -1172,10 +1024,12 @@ close_room_if_temporary_and_empty(StateData1) ->
_ -> {next_state, normal_state, StateData1}
end.
+-spec is_user_online(jid(), state()) -> boolean().
is_user_online(JID, StateData) ->
LJID = jid:tolower(JID),
(?DICT):is_key(LJID, StateData#state.users).
+-spec is_subscriber(jid(), state()) -> boolean().
is_subscriber(JID, StateData) ->
LJID = jid:tolower(JID),
case (?DICT):find(LJID, StateData#state.users) of
@@ -1186,6 +1040,7 @@ is_subscriber(JID, StateData) ->
end.
%% Check if the user is occupant of the room, or at least is an admin or owner.
+-spec is_occupant_or_admin(jid(), state()) -> boolean().
is_occupant_or_admin(JID, StateData) ->
FAffiliation = get_affiliation(JID, StateData),
FRole = get_role(JID, StateData),
@@ -1200,6 +1055,8 @@ is_occupant_or_admin(JID, StateData) ->
%%%
%%% Handle IQ queries of vCard
%%%
+-spec is_user_online_iq(binary(), jid(), state()) ->
+ {boolean(), binary(), jid()}.
is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource /= <<"">> ->
{is_user_online(JID, StateData), StanzaId, JID};
@@ -1207,93 +1064,55 @@ is_user_online_iq(StanzaId, JID, StateData)
when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
{OriginalId, Resource} ->
- JIDWithResource = jid:replace_resource(JID,
- Resource),
+ JIDWithResource = jid:replace_resource(JID, Resource),
{is_user_online(JIDWithResource, StateData), OriginalId,
JIDWithResource}
catch
_:_ -> {is_user_online(JID, StateData), StanzaId, JID}
end.
-handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
- Packet) ->
+-spec handle_iq_vcard(jid(), binary(), iq()) -> {jid(), iq()}.
+handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) ->
ToBareJID = jid:remove_resource(ToJID),
- IQ = jlib:iq_query_info(Packet),
- handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId,
- NewId, IQ, Packet).
-
-handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId,
- _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet)
- when ToBareJID /= ToJID ->
- {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
-handle_iq_vcard2(_FromFull, ToJID, _ToBareJID,
- _StanzaId, NewId, _IQ, Packet) ->
- {ToJID, change_stanzaid(NewId, Packet)}.
+ case SubEls of
+ [SubEl] when Type == get, ToBareJID /= ToJID ->
+ case xmpp:get_ns(SubEl) of
+ ?NS_VCARD ->
+ {ToBareJID, change_stanzaid(ToJID, IQ)};
+ _ ->
+ {ToJID, xmpp:set_id(IQ, NewId)}
+ end;
+ _ ->
+ {ToJID, xmpp:set_id(IQ, NewId)}
+ end.
+-spec stanzaid_pack(binary(), binary()) -> binary().
stanzaid_pack(OriginalId, Resource) ->
<<"berd",
(jlib:encode_base64(<<"ejab\000",
OriginalId/binary, "\000",
Resource/binary>>))/binary>>.
+-spec stanzaid_unpack(binary()) -> {binary(), binary()}.
stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
StanzaId = jlib:decode_base64(StanzaIdBase64),
[<<"ejab">>, OriginalId, Resource] =
str:tokens(StanzaId, <<"\000">>),
{OriginalId, Resource}.
-change_stanzaid(NewId, Packet) ->
- #xmlel{name = Name, attrs = Attrs, children = Els} =
- jlib:remove_attr(<<"id">>, Packet),
- #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs],
- children = Els}.
-
-change_stanzaid(PreviousId, ToJID, Packet) ->
+-spec change_stanzaid(jid(), iq()) -> iq().
+change_stanzaid(ToJID, #iq{id = PreviousId} = Packet) ->
NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
- change_stanzaid(NewId, Packet).
-
-%%%
-%%%
-
-role_to_list(Role) ->
- case Role of
- moderator -> <<"moderator">>;
- participant -> <<"participant">>;
- visitor -> <<"visitor">>;
- none -> <<"none">>
- end.
-
-affiliation_to_list(Affiliation) ->
- case Affiliation of
- owner -> <<"owner">>;
- admin -> <<"admin">>;
- member -> <<"member">>;
- outcast -> <<"outcast">>;
- none -> <<"none">>
- end.
-
-list_to_role(Role) ->
- case Role of
- <<"moderator">> -> moderator;
- <<"participant">> -> participant;
- <<"visitor">> -> visitor;
- <<"none">> -> none
- end.
-
-list_to_affiliation(Affiliation) ->
- case Affiliation of
- <<"owner">> -> owner;
- <<"admin">> -> admin;
- <<"member">> -> member;
- <<"outcast">> -> outcast;
- <<"none">> -> none
- end.
+ xmpp:set_id(Packet, NewId).
%% Decide the fate of the message and its sender
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-decide_fate_message(<<"error">>, Packet, From,
- StateData) ->
- PD = case check_error_kick(Packet) of
+-spec decide_fate_message(message(), jid(), state()) ->
+ continue_delivery | forget_message |
+ {expulse_sender, binary()}.
+decide_fate_message(#message{type = error, error = Err},
+ From, StateData) ->
+ PD = case check_error_kick(Err) of
%% If this is an error stanza and its condition matches a criteria
true ->
Reason =
@@ -1311,67 +1130,61 @@ decide_fate_message(<<"error">>, Packet, From,
end;
Other -> Other
end;
-decide_fate_message(_, _, _, _) -> continue_delivery.
+decide_fate_message(_, _, _) -> continue_delivery.
%% Check if the elements of this error stanza indicate
%% that the sender is a dead participant.
%% If so, return true to kick the participant.
-check_error_kick(Packet) ->
- case get_error_condition(Packet) of
- <<"gone">> -> true;
- <<"internal-server-error">> -> true;
- <<"item-not-found">> -> true;
- <<"jid-malformed">> -> true;
- <<"recipient-unavailable">> -> true;
- <<"redirect">> -> true;
- <<"remote-server-not-found">> -> true;
- <<"remote-server-timeout">> -> true;
- <<"service-unavailable">> -> true;
- _ -> false
- end.
-
-get_error_condition(Packet) ->
- case catch get_error_condition2(Packet) of
- {condition, ErrorCondition} -> ErrorCondition;
- {'EXIT', _} -> <<"badformed error stanza">>
- end.
+-spec check_error_kick(error()) -> boolean().
+check_error_kick(#error{reason = Reason}) ->
+ case Reason of
+ #gone{} -> true;
+ 'internal-server-error' -> true;
+ 'item-not-found' -> true;
+ 'jid-malformed' -> true;
+ 'recipient-unavailable' -> true;
+ #redirect{} -> true;
+ 'remote-server-not-found' -> true;
+ 'remote-server-timeout' -> true;
+ 'service-unavailable' -> true;
+ _ -> false
+ end;
+check_error_kick(undefined) ->
+ false.
-get_error_condition2(Packet) ->
- #xmlel{children = EEls} = fxml:get_subtag(Packet,
- <<"error">>),
- [Condition] = [Name
- || #xmlel{name = Name,
- attrs = [{<<"xmlns">>, ?NS_STANZAS}],
- children = []}
- <- EEls],
- {condition, Condition}.
+-spec get_error_condition(error()) -> string().
+get_error_condition(#error{reason = Reason}) ->
+ case Reason of
+ #gone{} -> "gone";
+ #redirect{} -> "redirect";
+ Atom -> atom_to_list(Atom)
+ end;
+get_error_condition(undefined) ->
+ "undefined".
+-spec make_reason(stanza(), jid(), state(), binary()) -> binary().
make_reason(Packet, From, StateData, Reason1) ->
{ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users),
- Condition = get_error_condition(Packet),
+ Condition = get_error_condition(xmpp:get_error(Packet)),
iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])).
+-spec expulse_participant(stanza(), jid(), state(), binary()) ->
+ state().
expulse_participant(Packet, From, StateData, Reason1) ->
IsSubscriber = is_subscriber(From, StateData),
Reason2 = make_reason(Packet, From, StateData, Reason1),
NewState = add_user_presence_un(From,
- #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"unavailable">>}],
- children =
- [#xmlel{name = <<"status">>,
- attrs = [],
- children =
- [{xmlcdata,
- Reason2}]}]},
+ #presence{type = unavailable,
+ status = xmpp:mk_text(Reason2)},
StateData),
send_new_presence(From, NewState, StateData),
remove_online_user(From, NewState, IsSubscriber).
+-spec set_affiliation(jid(), affiliation(), state()) -> state().
set_affiliation(JID, Affiliation, StateData) ->
set_affiliation(JID, Affiliation, StateData, <<"">>).
+-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state().
set_affiliation(JID, Affiliation, StateData, Reason) ->
LJID = jid:remove_resource(jid:tolower(JID)),
Affiliations = case Affiliation of
@@ -1383,6 +1196,7 @@ set_affiliation(JID, Affiliation, StateData, Reason) ->
end,
StateData#state{affiliations = Affiliations}.
+-spec get_affiliation(jid(), state()) -> affiliation().
get_affiliation(JID, StateData) ->
{_AccessRoute, _AccessCreate, AccessAdmin,
_AccessPersistent} =
@@ -1423,6 +1237,7 @@ get_affiliation(JID, StateData) ->
_ -> Res
end.
+-spec get_service_affiliation(jid(), state()) -> owner | none.
get_service_affiliation(JID, StateData) ->
{_AccessRoute, _AccessCreate, AccessAdmin,
_AccessPersistent} =
@@ -1434,6 +1249,7 @@ get_service_affiliation(JID, StateData) ->
_ -> none
end.
+-spec set_role(jid(), role(), state()) -> state().
set_role(JID, Role, StateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
@@ -1482,6 +1298,7 @@ set_role(JID, Role, StateData) ->
end,
StateData#state{users = Users, nicks = Nicks}.
+-spec get_role(jid(), state()) -> role().
get_role(JID, StateData) ->
LJID = jid:tolower(JID),
case (?DICT):find(LJID, StateData#state.users) of
@@ -1489,6 +1306,7 @@ get_role(JID, StateData) ->
_ -> none
end.
+-spec get_default_role(affiliation(), state()) -> role().
get_default_role(Affiliation, StateData) ->
case Affiliation of
owner -> moderator;
@@ -1507,12 +1325,15 @@ get_default_role(Affiliation, StateData) ->
end
end.
+-spec is_visitor(jid(), state()) -> boolean().
is_visitor(Jid, StateData) ->
get_role(Jid, StateData) =:= visitor.
+-spec is_moderator(jid(), state()) -> boolean().
is_moderator(Jid, StateData) ->
get_role(Jid, StateData) =:= moderator.
+-spec get_max_users(state()) -> non_neg_integer().
get_max_users(StateData) ->
MaxUsers = (StateData#state.config)#config.max_users,
ServiceMaxUsers = get_service_max_users(StateData),
@@ -1520,18 +1341,21 @@ get_max_users(StateData) ->
true -> ServiceMaxUsers
end.
+-spec get_service_max_users(state()) -> pos_integer().
get_service_max_users(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_users,
fun(I) when is_integer(I), I>0 -> I end,
?MAX_USERS_DEFAULT).
+-spec get_max_users_admin_threshold(state()) -> pos_integer().
get_max_users_admin_threshold(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_users_admin_threshold,
fun(I) when is_integer(I), I>0 -> I end,
5).
+-spec get_user_activity(jid(), state()) -> #activity{}.
get_user_activity(JID, StateData) ->
case treap:lookup(jid:tolower(JID),
StateData#state.activity)
@@ -1552,6 +1376,7 @@ get_user_activity(JID, StateData) ->
presence_shaper = PresenceShaper}
end.
+-spec store_user_activity(jid(), #activity{}, state()) -> state().
store_user_activity(JID, UserActivity, StateData) ->
MinMessageInterval =
trunc(gen_mod:get_module_opt(StateData#state.server_host,
@@ -1613,6 +1438,7 @@ store_user_activity(JID, UserActivity, StateData) ->
end,
StateData1.
+-spec clean_treap(treap:treap(), integer()) -> treap:treap().
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
true -> Treap;
@@ -1624,6 +1450,7 @@ clean_treap(Treap, CleanPriority) ->
end
end.
+-spec prepare_room_queue(state()) -> state().
prepare_room_queue(StateData) ->
case queue:out(StateData#state.room_queue) of
{{value, {message, From}}, _RoomQueue} ->
@@ -1647,6 +1474,7 @@ prepare_room_queue(StateData) ->
{empty, _} -> StateData
end.
+-spec update_online_user(jid(), #user{}, state()) -> state().
update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
is_subscriber = IsSubscriber} = User, StateData) ->
LJID = jid:tolower(JID),
@@ -1681,6 +1509,7 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
end,
NewStateData.
+-spec add_online_user(jid(), binary(), role(), boolean(), [binary()], state()) -> state().
add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) ->
tab_add_online_user(JID, StateData),
User = #user{jid = JID, nick = Nick, role = Role,
@@ -1693,9 +1522,11 @@ add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) ->
end,
StateData1.
+-spec remove_online_user(jid(), state(), boolean()) -> state().
remove_online_user(JID, StateData, IsSubscriber) ->
remove_online_user(JID, StateData, IsSubscriber, <<"">>).
+-spec remove_online_user(jid(), state(), boolean(), binary()) -> state().
remove_online_user(JID, StateData, _IsSubscriber = true, _Reason) ->
LJID = jid:tolower(JID),
Users = case (?DICT):find(LJID, StateData#state.users) of
@@ -1723,38 +1554,23 @@ remove_online_user(JID, StateData, _IsSubscriber, Reason) ->
end,
StateData#state{users = Users, nicks = Nicks}.
-filter_presence(#xmlel{name = <<"presence">>,
- attrs = Attrs, children = Els}) ->
- FEls = lists:filter(fun (El) ->
- case El of
- {xmlcdata, _} -> false;
- #xmlel{attrs = Attrs1} ->
- XMLNS = fxml:get_attr_s(<<"xmlns">>,
- Attrs1),
- NS_MUC = ?NS_MUC,
- Size = byte_size(NS_MUC),
- case XMLNS of
- <<NS_MUC:Size/binary, _/binary>> ->
- false;
- _ ->
- true
- end
- end
- end,
- Els),
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = FEls}.
-
-strip_status(#xmlel{name = <<"presence">>,
- attrs = Attrs, children = Els}) ->
- FEls = lists:filter(fun (#xmlel{name = <<"status">>}) ->
- false;
- (_) -> true
- end,
- Els),
- #xmlel{name = <<"presence">>, attrs = Attrs,
- children = FEls}.
-
+-spec filter_presence(presence()) -> presence().
+filter_presence(Presence) ->
+ Els = lists:filter(
+ fun(El) ->
+ XMLNS = xmpp:get_ns(El),
+ case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
+ ?NS_MUC -> false;
+ _ -> true
+ end
+ end, xmpp:get_els(Presence)),
+ xmpp:set_els(Presence, Els).
+
+-spec strip_status(presence()) -> presence().
+strip_status(Presence) ->
+ Presence#presence{status = []}.
+
+-spec add_user_presence(jid(), presence(), state()) -> state().
add_user_presence(JID, Presence, StateData) ->
LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
@@ -1765,6 +1581,7 @@ add_user_presence(JID, Presence, StateData) ->
StateData#state.users),
StateData#state{users = Users}.
+-spec add_user_presence_un(jid(), presence(), state()) -> state().
add_user_presence_un(JID, Presence, StateData) ->
LJID = jid:tolower(JID),
FPresence = filter_presence(Presence),
@@ -1778,15 +1595,17 @@ add_user_presence_un(JID, Presence, StateData) ->
%% Find and return a list of the full JIDs of the users of Nick.
%% Return jid record.
+-spec find_jids_by_nick(binary(), state()) -> [jid()].
find_jids_by_nick(Nick, StateData) ->
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [User]} -> [jid:make(User)];
{ok, Users} -> [jid:make(LJID) || LJID <- Users];
- error -> false
+ error -> []
end.
%% Find and return the full JID of the user of Nick with
%% highest-priority presence. Return jid record.
+-spec find_jid_by_nick(binary(), state()) -> jid() | false.
find_jid_by_nick(Nick, StateData) ->
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [User]} -> jid:make(User);
@@ -1811,6 +1630,8 @@ find_jid_by_nick(Nick, StateData) ->
error -> false
end.
+-spec higher_presence(undefined | presence(),
+ undefined | presence()) -> boolean().
higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
Pri1 = get_priority_from_presence(Pres1),
Pri2 = get_priority_from_presence(Pres2),
@@ -1818,26 +1639,20 @@ higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
higher_presence(Pres1, Pres2) ->
Pres1 > Pres2.
-get_priority_from_presence(PresencePacket) ->
- case fxml:get_subtag(PresencePacket, <<"priority">>) of
- false -> 0;
- SubEl ->
- case catch
- jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
- of
- P when is_integer(P) -> P;
- _ -> 0
- end
+-spec get_priority_from_presence(presence()) -> integer().
+get_priority_from_presence(#presence{priority = Prio}) ->
+ case Prio of
+ undefined -> 0;
+ _ -> Prio
end.
-find_nick_by_jid(Jid, StateData) ->
- [{_, #user{nick = Nick}}] = lists:filter(fun ({_,
- #user{jid = FJid}}) ->
- FJid == Jid
- end,
- (?DICT):to_list(StateData#state.users)),
+-spec find_nick_by_jid(jid(), state()) -> binary().
+find_nick_by_jid(JID, StateData) ->
+ LJID = jid:tolower(JID),
+ {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
Nick.
+-spec is_nick_change(jid(), binary(), state()) -> boolean().
is_nick_change(JID, Nick, StateData) ->
LJID = jid:tolower(JID),
case Nick of
@@ -1848,16 +1663,20 @@ is_nick_change(JID, Nick, StateData) ->
Nick /= OldNick
end.
+-spec nick_collision(jid(), binary(), state()) -> boolean().
nick_collision(User, Nick, StateData) ->
UserOfNick = find_jid_by_nick(Nick, StateData),
(UserOfNick /= false andalso
jid:remove_resource(jid:tolower(UserOfNick))
/= jid:remove_resource(jid:tolower(User))).
-add_new_user(From, Nick,
- #xmlel{name = Name, attrs = Attrs, children = Els} = Packet,
- StateData) ->
- Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+-spec add_new_user(jid(), binary(), presence() | iq(), state()) ->
+ state() |
+ {error, error()} |
+ {ignore, state()} |
+ {result, xmpp_element(), state()}.
+add_new_user(From, Nick, Packet, StateData) ->
+ Lang = xmpp:get_lang(Packet),
UserRoomJID = jid:replace_resource(StateData#state.jid, Nick),
MaxUsers = get_max_users(StateData),
MaxAdminUsers = MaxUsers +
@@ -1874,7 +1693,7 @@ add_new_user(From, Nick,
fun(I) when is_integer(I), I>0 -> I end,
10),
Collision = nick_collision(From, Nick, StateData),
- IsSubscribeRequest = Name /= <<"presence">>,
+ IsSubscribeRequest = not is_record(Packet, presence),
case {(ServiceAffiliation == owner orelse
((Affiliation == admin orelse Affiliation == owner)
andalso NUsers < MaxAdminUsers)
@@ -1887,72 +1706,72 @@ add_new_user(From, Nick,
of
{false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
Txt = <<"Too many users in this conference">>,
- Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_resource_constraint(Txt, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{false, _, _, _} when NConferences >= MaxConferences ->
Txt = <<"You have joined too many conferences">>,
- Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_resource_constraint(Txt, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{false, _, _, _} ->
- Err = ?ERR_SERVICE_UNAVAILABLE,
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_service_unavailable(),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, _, _, none} ->
Err = case Affiliation of
outcast ->
ErrText = <<"You have been banned from this room">>,
- ?ERRT_FORBIDDEN(Lang, ErrText);
+ xmpp:err_forbidden(ErrText, Lang);
_ ->
ErrText = <<"Membership is required to enter this room">>,
- ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText)
+ xmpp:err_registration_required(ErrText, Lang)
end,
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, true, _, _} ->
ErrText = <<"That nickname is already in use by another occupant">>,
- Err = ?ERRT_CONFLICT(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_conflict(ErrText, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, _, false, _} ->
ErrText = <<"That nickname is registered by another person">>,
- Err = ?ERRT_CONFLICT(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_conflict(ErrText, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
{_, _, _, Role} ->
case check_password(ServiceAffiliation, Affiliation,
- Els, From, StateData)
+ Packet, From, StateData)
of
true ->
Nodes = get_subscription_nodes(Packet),
@@ -1965,7 +1784,7 @@ add_new_user(From, Nick,
Nodes, StateData)),
send_existing_presences(From, NewState),
send_initial_presence(From, NewState, StateData),
- Shift = count_stanza_shift(Nick, Els, NewState),
+ Shift = count_stanza_shift(Nick, Packet, NewState),
case send_history(From, Shift, NewState) of
true -> ok;
_ -> send_subject(From, StateData)
@@ -1985,20 +1804,20 @@ add_new_user(From, Nick,
NewStateData#state{robots = Robots}
end,
if not IsSubscribeRequest -> ResultState;
- true -> {result, subscription_nodes_to_events(Nodes), ResultState}
+ true -> {result, subscribe_result(Packet), ResultState}
end;
nopass ->
ErrText = <<"A password is required to enter this room">>,
- Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_not_authorized(ErrText, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
captcha_required ->
- SID = fxml:get_attr_s(<<"id">>, Attrs),
+ SID = xmpp:get_id(Packet),
RoomJID = StateData#state.jid,
To = jid:replace_resource(RoomJID, Nick),
Limiter = {From#jid.luser, From#jid.lserver},
@@ -2006,9 +1825,7 @@ add_new_user(From, Nick,
Lang, Limiter, From)
of
{ok, ID, CaptchaEls} ->
- MsgPkt = #xmlel{name = <<"message">>,
- attrs = [{<<"id">>, ID}],
- children = CaptchaEls},
+ MsgPkt = #message{id = ID, sub_els = CaptchaEls},
Robots = (?DICT):store(From, {Nick, Packet},
StateData#state.robots),
ejabberd_router:route(RoomJID, From, MsgPkt),
@@ -2020,49 +1837,51 @@ add_new_user(From, Nick,
end;
{error, limit} ->
ErrText = <<"Too many CAPTCHA requests">>,
- Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_resource_constraint(ErrText, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end;
_ ->
ErrText = <<"Unable to generate a CAPTCHA">>,
- Err = ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_internal_server_error(ErrText, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end
end;
_ ->
ErrText = <<"Incorrect password">>,
- Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText),
- ErrPacket = jlib:make_error_reply(Packet, Err),
+ Err = xmpp:err_not_authorized(ErrText, Lang),
+ ErrPacket = xmpp:make_error(Packet, Err),
if not IsSubscribeRequest ->
ejabberd_router:route(UserRoomJID, From, ErrPacket),
StateData;
true ->
- {error, Err, StateData}
+ {error, Err}
end
end
end.
-check_password(owner, _Affiliation, _Els, _From,
+-spec check_password(affiliation(), affiliation(),
+ stanza(), jid(), state()) -> boolean() | nopass.
+check_password(owner, _Affiliation, _Packet, _From,
_StateData) ->
%% Don't check pass if user is owner in MUC service (access_admin option)
true;
-check_password(_ServiceAffiliation, Affiliation, Els,
+check_password(_ServiceAffiliation, Affiliation, Packet,
From, StateData) ->
case (StateData#state.config)#config.password_protected
of
false -> check_captcha(Affiliation, From, StateData);
true ->
- Pass = extract_password(Els),
+ Pass = extract_password(Packet),
case Pass of
false -> nopass;
_ ->
@@ -2073,6 +1892,7 @@ check_password(_ServiceAffiliation, Affiliation, Els,
end
end.
+-spec check_captcha(affiliation(), jid(), state()) -> true | captcha_required.
check_captcha(Affiliation, From, StateData) ->
case (StateData#state.config)#config.captcha_protected
andalso ejabberd_captcha:is_feature_available()
@@ -2101,47 +1921,52 @@ check_captcha(Affiliation, From, StateData) ->
_ -> true
end.
-extract_password([]) -> false;
-extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC ->
- case fxml:get_subtag(El, <<"password">>) of
- false -> false;
- SubEl -> fxml:get_tag_cdata(SubEl)
- end;
- _ -> extract_password(Els)
- end;
-extract_password([_ | Els]) -> extract_password(Els).
+-spec extract_password(stanza()) -> binary() | false.
+extract_password(Packet) ->
+ case xmpp:get_subtag(Packet, #muc{}) of
+ #muc{password = Password} when is_binary(Password) ->
+ Password;
+ _ ->
+ false
+ end.
-count_stanza_shift(Nick, Els, StateData) ->
- HL = lqueue_to_list(StateData#state.history),
- Since = extract_history(Els, <<"since">>),
- Shift0 = case Since of
- false -> 0;
- _ ->
- Sin = calendar:datetime_to_gregorian_seconds(Since),
- count_seconds_shift(Sin, HL)
- end,
- Seconds = extract_history(Els, <<"seconds">>),
- Shift1 = case Seconds of
- false -> 0;
- _ ->
- Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time())
- - Seconds,
- count_seconds_shift(Sec, HL)
- end,
- MaxStanzas = extract_history(Els, <<"maxstanzas">>),
- Shift2 = case MaxStanzas of
- false -> 0;
- _ -> count_maxstanzas_shift(MaxStanzas, HL)
- end,
- MaxChars = extract_history(Els, <<"maxchars">>),
- Shift3 = case MaxChars of
- false -> 0;
- _ -> count_maxchars_shift(Nick, MaxChars, HL)
- end,
- lists:max([Shift0, Shift1, Shift2, Shift3]).
+-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}) ->
@@ -2153,12 +1978,16 @@ count_seconds_shift(Seconds, HistoryList) ->
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,
@@ -2168,41 +1997,20 @@ count_maxchars_shift(Nick, MaxSize, HistoryList) ->
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)
end.
-extract_history([], _Type) -> false;
-extract_history([#xmlel{attrs = Attrs} = El | Els],
- Type) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC ->
- AttrVal = fxml:get_path_s(El,
- [{elem, <<"history">>}, {attr, Type}]),
- case Type of
- <<"since">> ->
- case jlib:datetime_string_to_timestamp(AttrVal) of
- undefined -> false;
- TS -> calendar:now_to_universal_time(TS)
- end;
- _ ->
- case catch jlib:binary_to_integer(AttrVal) of
- IntVal when is_integer(IntVal) and (IntVal >= 0) ->
- IntVal;
- _ -> false
- end
- end;
- _ -> extract_history(Els, Type)
- end;
-extract_history([_ | Els], Type) ->
- extract_history(Els, Type).
-
+-spec is_room_overcrowded(state()) -> boolean().
is_room_overcrowded(StateData) ->
MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_users_presence,
@@ -2210,10 +2018,12 @@ is_room_overcrowded(StateData) ->
?DEFAULT_MAX_USERS_PRESENCE),
(?DICT):size(StateData#state.users) > MaxUsersPresence.
+-spec presence_broadcast_allowed(jid(), state()) -> boolean().
presence_broadcast_allowed(JID, StateData) ->
Role = get_role(JID, StateData),
lists:member(Role, (StateData#state.config)#config.presence_broadcast).
+-spec is_initial_presence(jid(), state()) -> boolean().
is_initial_presence(From, StateData) ->
LJID = jid:tolower(From),
case (?DICT):find(LJID, StateData#state.users) of
@@ -2223,18 +2033,22 @@ is_initial_presence(From, StateData) ->
true
end.
+-spec send_initial_presence(jid(), state(), state()) -> ok.
send_initial_presence(NJID, StateData, OldStateData) ->
send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
+-spec send_update_presence(jid(), state(), state()) -> ok.
send_update_presence(JID, StateData, OldStateData) ->
send_update_presence(JID, <<"">>, StateData, OldStateData).
+-spec send_update_presence(jid(), binary(), state(), state()) -> ok.
send_update_presence(JID, Reason, StateData, OldStateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
false -> send_update_presence1(JID, Reason, StateData, OldStateData)
end.
+-spec send_update_presence1(jid(), binary(), state(), state()) -> ok.
send_update_presence1(JID, Reason, StateData, OldStateData) ->
LJID = jid:tolower(JID),
LJIDs = case LJID of
@@ -2258,12 +2072,15 @@ send_update_presence1(JID, Reason, StateData, OldStateData) ->
end,
LJIDs).
+-spec send_new_presence(jid(), state(), state()) -> ok.
send_new_presence(NJID, StateData, OldStateData) ->
send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
+-spec send_new_presence(jid(), binary(), state(), state()) -> ok.
send_new_presence(NJID, Reason, StateData, OldStateData) ->
send_new_presence(NJID, Reason, false, StateData, OldStateData).
+-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok.
send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
@@ -2271,6 +2088,7 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
OldStateData)
end.
+-spec is_ra_changed(jid() | ljid(), boolean(), state(), state()) -> boolean().
is_ra_changed(_, _IsInitialPresence = true, _, _) ->
false;
is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) ->
@@ -2289,6 +2107,7 @@ is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) ->
(NewRole /= OldRole) or (NewAff /= OldAff)
end.
+-spec send_new_presence1(jid(), binary(), boolean(), state(), state()) -> ok.
send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
LNJID = jid:tolower(NJID),
#user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
@@ -2301,15 +2120,9 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
{Role1, Presence1} =
case presence_broadcast_allowed(NJID, StateData) of
true -> {Role0, Presence0};
- false ->
- {none,
- #xmlel{name = <<"presence">>,
- attrs = [{<<"type">>, <<"unavailable">>}],
- children = []}
- }
+ false -> {none, #presence{type = unavailable}}
end,
Affiliation = get_affiliation(LJID, StateData),
- SAffiliation = affiliation_to_list(Affiliation),
UserList =
case not (presence_broadcast_allowed(NJID, StateData) orelse
presence_broadcast_allowed(NJID, OldStateData)) of
@@ -2323,59 +2136,38 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
{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">>,
- jid:to_string(RealJID)},
- {<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}];
- _ ->
- [{<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}]
- end,
- ItemEls = case Reason of
- <<"">> -> [];
- _ ->
- [#xmlel{name = <<"reason">>,
- attrs = [],
- children =
- [{xmlcdata, Reason}]}]
- end,
- StatusEls = status_els(IsInitialPresence, NJID, Info,
- StateData),
- Pres = if Presence == undefined -> #xmlel{name = <<"presence">>};
+ 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};
+ false -> Item0
+ end,
+ Item = if is_binary(Reason), Reason /= <<"">> ->
+ Item1#muc_item{reason = Reason};
+ true ->
+ Item1
+ end,
+ StatusCodes = status_codes(IsInitialPresence, NJID, Info,
+ StateData),
+ Pres = if Presence == undefined -> #presence{};
true -> Presence
end,
- Packet = fxml:append_subtags(Pres,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs
- =
- ItemAttrs,
- children
- =
- ItemEls}
- | StatusEls]}]),
+ Packet = xmpp:set_subtag(
+ Pres, #muc_user{items = [Item],
+ status_codes = StatusCodes}),
Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
false -> ?NS_MUCSUB_NODES_PRESENCE
end,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
- Type = fxml:get_tag_attr_s(<<"type">>, Packet),
+ Type = xmpp:get_type(Packet),
IsSubscriber = Info#user.is_subscriber,
IsOccupant = Info#user.last_presence /= undefined,
if (IsSubscriber and not IsOccupant) and
- (IsInitialPresence or (Type == <<"unavailable">>)) ->
+ (IsInitialPresence or (Type == unavailable)) ->
Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node2, StateData);
@@ -2385,12 +2177,14 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
end,
UserList).
+-spec send_existing_presences(jid(), state()) -> ok.
send_existing_presences(ToJID, StateData) ->
case is_room_overcrowded(StateData) of
true -> ok;
false -> send_existing_presences1(ToJID, StateData)
end.
+-spec send_existing_presences1(jid(), state()) -> ok.
send_existing_presences1(ToJID, StateData) ->
LToJID = jid:tolower(ToJID),
{ok, #user{jid = RealToJID, role = Role}} =
@@ -2410,46 +2204,23 @@ send_existing_presences1(ToJID, StateData) ->
{_, false} -> ok;
_ ->
FromAffiliation = get_affiliation(LJID, StateData),
- ItemAttrs = case Role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>,
- jid:to_string(FromJID)},
- {<<"affiliation">>,
- affiliation_to_list(FromAffiliation)},
- {<<"role">>,
- role_to_list(FromRole)}];
- _ ->
- [{<<"affiliation">>,
- affiliation_to_list(FromAffiliation)},
- {<<"role">>,
- role_to_list(FromRole)}]
- end,
- Packet = fxml:append_subtags(
- Presence,
- [#xmlel{name =
- <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- ItemAttrs,
- children
- =
- []}]}]),
+ Item0 = #muc_item{affiliation = FromAffiliation,
+ role = FromRole},
+ Item = case Role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false of
+ true -> Item0#muc_item{jid = FromJID};
+ false -> Item0
+ end,
+ Packet = xmpp:set_subtag(
+ Presence, #muc_user{items = [Item]}),
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
end
end,
(?DICT):to_list(StateData#state.nicks)).
+-spec set_nick(jid(), binary(), state()) -> state().
set_nick(JID, Nick, State) ->
LJID = jid:tolower(JID),
{ok, #user{nick = OldNick}} = (?DICT):find(LJID, State#state.users),
@@ -2472,6 +2243,7 @@ set_nick(JID, Nick, State) ->
end,
State#state{users = Users, nicks = Nicks}.
+-spec change_nick(jid(), binary(), state()) -> state().
change_nick(JID, Nick, StateData) ->
LJID = jid:tolower(JID),
{ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users),
@@ -2492,6 +2264,7 @@ change_nick(JID, Nick, StateData) ->
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
+-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok.
send_nick_changing(JID, OldNick, StateData,
SendOldUnavailable, SendNewAvailable) ->
{ok,
@@ -2500,104 +2273,52 @@ send_nick_changing(JID, OldNick, StateData,
(?DICT):find(jid:tolower(JID),
StateData#state.users),
Affiliation = get_affiliation(JID, StateData),
- SAffiliation = affiliation_to_list(Affiliation),
- SRole = role_to_list(Role),
- lists:foreach(fun ({_LJID, Info}) when Presence /= undefined ->
- ItemAttrs1 = case Info#user.role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>,
- jid:to_string(RealJID)},
- {<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole},
- {<<"nick">>, Nick}];
- _ ->
- [{<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole},
- {<<"nick">>, Nick}]
- end,
- ItemAttrs2 = case Info#user.role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>,
- jid:to_string(RealJID)},
- {<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}];
- _ ->
- [{<<"affiliation">>, SAffiliation},
- {<<"role">>, SRole}]
- end,
- Status110 = case JID == Info#user.jid of
- true ->
- [#xmlel{name = <<"status">>,
- attrs = [{<<"code">>, <<"110">>}]
- }];
- false ->
- []
- end,
- Packet1 = #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>,
- <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs =
- ItemAttrs1,
- children =
- []},
- #xmlel{name =
- <<"status">>,
- attrs =
- [{<<"code">>,
- <<"303">>}],
- children =
- []}|Status110]}]},
- Packet2 = fxml:append_subtags(Presence,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name
- =
- <<"item">>,
- attrs
- =
- ItemAttrs2,
- children
- =
- []}|Status110]}]),
- if SendOldUnavailable ->
- send_wrapped(jid:replace_resource(StateData#state.jid,
- OldNick),
- Info#user.jid, Packet1,
- ?NS_MUCSUB_NODES_PRESENCE,
- StateData);
- true -> ok
+ lists:foreach(
+ fun({_LJID, Info}) when Presence /= undefined ->
+ 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
+ true -> [110];
+ false -> []
end,
- if SendNewAvailable ->
- send_wrapped(jid:replace_resource(StateData#state.jid,
- Nick),
- Info#user.jid, Packet2,
- ?NS_MUCSUB_NODES_PRESENCE,
- StateData);
- true -> ok
- end;
- (_) ->
- ok
- end,
- (?DICT):to_list(StateData#state.users)).
+ Packet1 = #presence{type = unavailable,
+ sub_els = [#muc_user{
+ items = [Item1],
+ status_codes = [303|Status110]}]},
+ Packet2 = xmpp:set_subtag(Presence,
+ #muc_user{items = [Item2],
+ status_codes = Status110}),
+ if SendOldUnavailable ->
+ send_wrapped(
+ jid:replace_resource(StateData#state.jid, OldNick),
+ Info#user.jid, Packet1, ?NS_MUCSUB_NODES_PRESENCE,
+ StateData);
+ true -> ok
+ end,
+ if SendNewAvailable ->
+ send_wrapped(
+ jid:replace_resource(StateData#state.jid, Nick),
+ Info#user.jid, Packet2, ?NS_MUCSUB_NODES_PRESENCE,
+ StateData);
+ true -> ok
+ end;
+ (_) ->
+ ok
+ end,
+ (?DICT):to_list(StateData#state.users)).
+-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
IsOccupant = case LJID of
@@ -2617,18 +2338,13 @@ maybe_send_affiliation(JID, Affiliation, StateData) ->
send_affiliation(LJID, Affiliation, StateData)
end.
+-spec send_affiliation(ljid(), affiliation(), state()) -> ok.
send_affiliation(LJID, Affiliation, StateData) ->
- ItemAttrs = [{<<"jid">>, jid:to_string(LJID)},
- {<<"affiliation">>, affiliation_to_list(Affiliation)},
- {<<"role">>, <<"none">>}],
- Message = #xmlel{name = <<"message">>,
- attrs = [{<<"id">>, randoms:get_string()}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children =
- [#xmlel{name = <<"item">>,
- attrs = ItemAttrs}]}]},
+ Item = #muc_item{jid = jid:make(LJID),
+ affiliation = Affiliation,
+ role = none},
+ Message = #message{id = randoms:get_string(),
+ sub_els = [#muc_user{items = [Item]}]},
Recipients = case (StateData#state.config)#config.anonymous of
true ->
(?DICT):filter(fun(_, #user{role = moderator}) ->
@@ -2643,43 +2359,33 @@ send_affiliation(LJID, Affiliation, StateData) ->
StateData#state.server_host,
Recipients, Message).
-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) -> [].
+-spec status_codes(boolean(), jid(), #user{}, state()) -> [pos_integer()].
+status_codes(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
+ S0 = [110],
+ case IsInitialPresence of
+ true ->
+ S1 = case StateData#state.just_created of
+ true -> [201|S0];
+ false -> S0
+ end,
+ S2 = case (StateData#state.config)#config.anonymous of
+ true -> S1;
+ false -> [100|S1]
+ end,
+ S3 = case (StateData#state.config)#config.logging of
+ true -> [170|S2];
+ false -> S2
+ end,
+ S3;
+ false -> S0
+ end;
+status_codes(_IsInitialPresence, _JID, _Info, _StateData) -> [].
+-spec lqueue_new(non_neg_integer()) -> lqueue().
lqueue_new(Max) ->
#lqueue{queue = queue:new(), len = 0, max = Max}.
+-spec lqueue_in(term(), lqueue()) -> lqueue().
%% If the message queue limit is set to 0, do not store messages.
lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
%% Otherwise, rotate messages in the queue store.
@@ -2692,39 +2398,33 @@ lqueue_in(Item,
true -> #lqueue{queue = Q2, len = Len + 1, max = Max}
end.
+-spec lqueue_cut(queue:queue(), non_neg_integer()) -> queue:queue().
lqueue_cut(Q, 0) -> Q;
lqueue_cut(Q, N) ->
{_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1).
+-spec lqueue_to_list(lqueue()) -> list().
lqueue_to_list(#lqueue{queue = Q1}) ->
queue:to_list(Q1).
-
+-spec add_message_to_history(binary(), jid(), message(), state()) -> state().
add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case fxml:get_subtag(Packet, <<"subject">>)
- of
- false -> false;
- _ -> true
- end,
+ HaveSubject = Packet#message.subject /= [],
TimeStamp = p1_time_compat:timestamp(),
AddrPacket = case (StateData#state.config)#config.anonymous of
true -> Packet;
false ->
- Address = #xmlel{name = <<"address">>,
- attrs = [{<<"type">>, <<"ofrom">>},
- {<<"jid">>,
- jid:to_string(FromJID)}],
- children = []},
- Addresses = #xmlel{name = <<"addresses">>,
- attrs = [{<<"xmlns">>, ?NS_ADDRESS}],
- children = [Address]},
- fxml:append_subtags(Packet, [Addresses])
+ Addresses = #addresses{
+ list = [#address{type = ofrom,
+ jid = FromJID}]},
+ xmpp:set_subtag(Packet, Addresses)
end,
- TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp),
- SPacket =
- jlib:replace_from_to(jid:replace_resource(StateData#state.jid,
- FromNick),
- StateData#state.jid, TSPacket),
+ 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},
@@ -2732,6 +2432,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
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},
@@ -2745,23 +2446,19 @@ send_history(JID, Shift, StateData) ->
lists:nthtail(Shift,
lqueue_to_list(StateData#state.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 = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"groupchat">>}],
- children =
- [#xmlel{name = <<"subject">>, attrs = [],
- children = [{xmlcdata, Subject}]}]},
+ Packet = #message{type = groupchat, subject = xmpp:mk_text(Subject)},
ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID,
Packet).
-check_subject(Packet) ->
- case fxml:get_subtag(Packet, <<"subject">>) of
- false -> false;
- SubjEl -> fxml:get_tag_cdata(SubjEl)
- end.
+-spec check_subject(message()) -> false | binary().
+check_subject(#message{subject = []}) -> false;
+check_subject(#message{subject = Subj}) -> xmpp:get_text(Subj).
+-spec can_change_subject(role(), state()) -> boolean().
can_change_subject(Role, StateData) ->
case (StateData#state.config)#config.allow_change_subj
of
@@ -2772,99 +2469,89 @@ can_change_subject(Role, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Admin stuff
-process_iq_admin(From, set, Lang, SubEl, StateData) ->
- #xmlel{children = Items} = SubEl,
+-spec process_iq_admin(jid(), iq(), #state{}) -> {error, error()} |
+ {result, undefined, #state{}} |
+ {result, muc_admin()}.
+process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]},
+ _StateData) ->
+ Txt = <<"No 'item' element found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+process_iq_admin(From, #iq{type = set, lang = Lang,
+ sub_els = [#muc_admin{items = Items}]},
+ StateData) ->
process_admin_items_set(From, Items, Lang, StateData);
-process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case fxml:get_subtag(SubEl, <<"item">>) of
- false ->
- Txt = <<"No 'item' element found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- Item ->
- FAffiliation = get_affiliation(From, StateData),
- FRole = get_role(From, StateData),
- case fxml:get_tag_attr(<<"role">>, Item) of
- false ->
- case fxml:get_tag_attr(<<"affiliation">>, Item) of
- false ->
- Txt = <<"No 'affiliation' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
- SAffiliation ->
- if (FAffiliation == owner) or
- (FAffiliation == admin) or
- ((FAffiliation == member) and not
- (StateData#state.config)#config.anonymous) ->
- Items = items_with_affiliation(SAffiliation,
- StateData),
- {result, Items, StateData};
- true ->
- ErrText =
- <<"Administrator privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
- {'EXIT', _} ->
- Txt = <<"Incorrect value of 'role' attribute">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- SRole ->
- if FRole == moderator ->
- Items = items_with_role(SRole, StateData),
- {result, Items, StateData};
- true ->
- ErrText = <<"Moderator privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
- end
- end
- end
- end.
+process_iq_admin(From, #iq{type = get, lang = Lang,
+ sub_els = [#muc_admin{items = [Item]}]},
+ StateData) ->
+ FAffiliation = get_affiliation(From, StateData),
+ FRole = get_role(From, StateData),
+ case Item of
+ #muc_item{role = undefined, affiliation = undefined} ->
+ Txt = <<"Neither 'role' nor 'affiliation' attribute found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ #muc_item{role = undefined, affiliation = Affiliation} ->
+ if (FAffiliation == owner) or
+ (FAffiliation == admin) or
+ ((FAffiliation == member) and
+ not (StateData#state.config)#config.anonymous) ->
+ Items = items_with_affiliation(Affiliation, StateData),
+ {result, #muc_admin{items = Items}};
+ true ->
+ ErrText = <<"Administrator privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)}
+ end;
+ #muc_item{role = Role} ->
+ if FRole == moderator ->
+ Items = items_with_role(Role, StateData),
+ {result, #muc_admin{items = Items}};
+ true ->
+ ErrText = <<"Moderator privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)}
+ end
+ end;
+process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) ->
+ ErrText = <<"Too many <item/> elements">>,
+ {error, xmpp:err_bad_request(ErrText, Lang)}.
+-spec items_with_role(role(), state()) -> [muc_item()].
items_with_role(SRole, StateData) ->
lists:map(fun ({_, U}) -> user_to_item(U, StateData)
end,
search_role(SRole, StateData)).
+-spec items_with_affiliation(affiliation(), state()) -> [muc_item()].
items_with_affiliation(SAffiliation, StateData) ->
- lists:map(fun ({JID, {Affiliation, Reason}}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- affiliation_to_list(Affiliation)},
- {<<"jid">>, jid:to_string(JID)}],
- children =
- [#xmlel{name = <<"reason">>, attrs = [],
- children = [{xmlcdata, Reason}]}]};
- ({JID, Affiliation}) ->
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"affiliation">>,
- affiliation_to_list(Affiliation)},
- {<<"jid">>, jid:to_string(JID)}],
- children = []}
- end,
- search_affiliation(SAffiliation, StateData)).
+ lists:map(
+ fun({JID, {Affiliation, Reason}}) ->
+ #muc_item{affiliation = Affiliation, jid = JID,
+ reason = if is_binary(Reason), Reason /= <<"">> ->
+ Reason;
+ true ->
+ undefined
+ end};
+ ({JID, Affiliation}) ->
+ #muc_item{affiliation = Affiliation, jid = JID}
+ end,
+ search_affiliation(SAffiliation, StateData)).
+-spec user_to_item(#user{}, state()) -> muc_item().
user_to_item(#user{role = Role, nick = Nick, jid = JID},
StateData) ->
Affiliation = get_affiliation(JID, StateData),
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"role">>, role_to_list(Role)},
- {<<"affiliation">>, affiliation_to_list(Affiliation)},
- {<<"nick">>, Nick},
- {<<"jid">>, jid:to_string(JID)}],
- children = []}.
+ #muc_item{role = Role,
+ affiliation = Affiliation,
+ nick = Nick,
+ jid = JID}.
+-spec search_role(role(), state()) -> [{ljid(), #user{}}].
search_role(Role, StateData) ->
lists:filter(fun ({_, #user{role = R}}) -> Role == R
end,
(?DICT):to_list(StateData#state.users)).
+-spec search_affiliation(affiliation(), state()) ->
+ [{ljid(),
+ affiliation() | {affiliation(), binary()}}].
search_affiliation(Affiliation, StateData) ->
lists:filter(fun ({_, A}) ->
case A of
@@ -2874,11 +2561,14 @@ search_affiliation(Affiliation, StateData) ->
end,
(?DICT):to_list(StateData#state.affiliations)).
+-spec process_admin_items_set(jid(), [muc_item()], binary() | undefined,
+ #state{}) -> {result, undefined, #state{}} |
+ {error, error()}.
process_admin_items_set(UJID, Items, Lang, StateData) ->
UAffiliation = get_affiliation(UJID, StateData),
URole = get_role(UJID, StateData),
- case find_changed_items(UJID, UAffiliation, URole,
- Items, Lang, StateData, [])
+ case catch find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData, [])
of
{result, Res} ->
?INFO_MSG("Processing MUC admin query from ~s in "
@@ -2888,249 +2578,161 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
NSD = lists:foldl(process_item_change(UJID),
StateData, lists:flatten(Res)),
store_room(NSD),
- {result, [], NSD};
- Err -> Err
+ {result, undefined, NSD};
+ {error, Err} -> {error, Err}
end.
+-spec process_item_change(jid()) -> function().
process_item_change(UJID) ->
fun(E, SD) ->
process_item_change(E, SD, UJID)
end.
-process_item_change(E, SD, UJID) ->
- case catch case E of
- {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
- %% If the provided JID does not have username,
- %% forget the affiliation completely
- SD;
- {JID, role, none, Reason} ->
- catch
- 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),
- 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),
- maybe_send_affiliation(JID, none, SD1),
- SD1
- end;
- {JID, affiliation, outcast, Reason} ->
- catch
- 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) ->
- SD1 = set_affiliation(JID, A, SD, Reason),
- SD2 = set_role(JID, moderator, SD1),
- send_update_presence(JID, Reason, SD2, SD),
- maybe_send_affiliation(JID, A, SD2),
- SD2;
- {JID, affiliation, member, Reason} ->
- SD1 = set_affiliation(JID, member, SD, Reason),
- SD2 = set_role(JID, participant, SD1),
- send_update_presence(JID, Reason, SD2, SD),
- maybe_send_affiliation(JID, member, SD2),
- SD2;
- {JID, role, Role, Reason} ->
- SD1 = set_role(JID, Role, SD),
- catch
- send_new_presence(JID, Reason, SD1, SD),
- SD1;
- {JID, affiliation, A, _Reason} ->
- SD1 = set_affiliation(JID, A, SD),
- send_update_presence(JID, SD1, SD),
- maybe_send_affiliation(JID, A, SD1),
- SD1
- end
- of
- {'EXIT', ErrReason} ->
- ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n", [ErrReason]),
- SD;
- NSD -> NSD
+-type admin_action() :: {jid(), affiliation | role,
+ affiliation() | role(), binary()}.
+
+-spec process_item_change(admin_action(), state(), jid()) -> state().
+process_item_change(Item, SD, UJID) ->
+ try case Item of
+ {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
+ %% If the provided JID does not have username,
+ %% forget the affiliation completely
+ SD;
+ {JID, role, none, Reason} ->
+ catch 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),
+ 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),
+ maybe_send_affiliation(JID, none, SD1),
+ SD1
+ end;
+ {JID, affiliation, outcast, Reason} ->
+ catch 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) ->
+ SD1 = set_affiliation(JID, A, SD, Reason),
+ SD2 = set_role(JID, moderator, SD1),
+ send_update_presence(JID, Reason, SD2, SD),
+ maybe_send_affiliation(JID, A, SD2),
+ SD2;
+ {JID, affiliation, member, Reason} ->
+ SD1 = set_affiliation(JID, member, SD, Reason),
+ SD2 = set_role(JID, participant, SD1),
+ send_update_presence(JID, Reason, SD2, SD),
+ maybe_send_affiliation(JID, member, SD2),
+ SD2;
+ {JID, role, Role, Reason} ->
+ SD1 = set_role(JID, Role, SD),
+ catch send_new_presence(JID, Reason, SD1, SD),
+ SD1;
+ {JID, affiliation, A, _Reason} ->
+ SD1 = set_affiliation(JID, A, SD),
+ send_update_presence(JID, SD1, SD),
+ maybe_send_affiliation(JID, A, SD1),
+ SD1
+ end
+ catch E:R ->
+ ?ERROR_MSG("failed to set item ~p from ~s: ~p",
+ [Item, jid:to_string(UJID),
+ {E, {R, erlang:get_stacktrace()}}]),
+ SD
end.
+-spec find_changed_items(jid(), affiliation(), role(),
+ [muc_item()], binary(), state(), [admin_action()]) ->
+ {result, [admin_action()]}.
find_changed_items(_UJID, _UAffiliation, _URole, [],
_Lang, _StateData, Res) ->
{result, Res};
+find_changed_items(_UJID, _UAffiliation, _URole,
+ [#muc_item{jid = undefined, nick = undefined}|_],
+ Lang, _StateData, _Res) ->
+ Txt = <<"Neither 'jid' nor 'nick' attribute found">>,
+ throw({error, xmpp:err_bad_request(Txt, Lang)});
+find_changed_items(_UJID, _UAffiliation, _URole,
+ [#muc_item{role = undefined, affiliation = undefined}|_],
+ Lang, _StateData, _Res) ->
+ Txt = <<"Neither 'role' nor 'affiliation' attribute found">>,
+ throw({error, xmpp:err_bad_request(Txt, Lang)});
find_changed_items(UJID, UAffiliation, URole,
- [{xmlcdata, _} | Items], Lang, StateData, Res) ->
- find_changed_items(UJID, UAffiliation, URole, Items,
- Lang, StateData, Res);
-find_changed_items(UJID, UAffiliation, URole,
- [#xmlel{name = <<"item">>, attrs = Attrs} = Item
- | Items],
+ [#muc_item{jid = J, nick = Nick, reason = Reason0,
+ role = Role, affiliation = Affiliation}|Items],
Lang, StateData, Res) ->
- TJID = case fxml:get_attr(<<"jid">>, Attrs) of
- {value, S} ->
- case jid:from_string(S) of
- error ->
- ErrText = iolist_to_binary(
- io_lib:format(translate:translate(
- Lang,
- <<"Jabber ID ~s is invalid">>),
- [S])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J -> {value, [J]}
- end;
- _ ->
- case fxml:get_attr(<<"nick">>, Attrs) of
- {value, N} ->
- case find_jids_by_nick(N, StateData) of
- false ->
- ErrText = iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Nickname ~s does not exist in the room">>),
- [N])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J -> {value, J}
- end;
- _ ->
- Txt1 = <<"No 'nick' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}
- end
- end,
- case TJID of
- {value, [JID | _] = JIDs} ->
- TAffiliation = get_affiliation(JID, StateData),
- TRole = get_role(JID, StateData),
- case fxml:get_attr(<<"role">>, Attrs) of
- false ->
- case fxml:get_attr(<<"affiliation">>, Attrs) of
- false ->
- Txt2 = <<"No 'affiliation' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt2)};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText1 = iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Invalid affiliation: ~s">>),
- [StrAffiliation])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)};
- SAffiliation ->
- ServiceAf = get_service_affiliation(JID, StateData),
- CanChangeRA = case can_change_ra(UAffiliation,
- URole,
- TAffiliation,
- TRole, affiliation,
- SAffiliation,
- ServiceAf)
- of
- nothing -> nothing;
- true -> true;
- check_owner ->
- case search_affiliation(owner,
- StateData)
- of
- [{OJID, _}] ->
- jid:remove_resource(OJID)
- /=
- jid:tolower(jid:remove_resource(UJID));
- _ -> true
- end;
- _ -> false
- end,
- case CanChangeRA of
- nothing ->
- find_changed_items(UJID, UAffiliation, URole,
- Items, Lang, StateData,
- Res);
- true ->
- Reason = fxml:get_path_s(Item,
- [{elem, <<"reason">>},
- cdata]),
- MoreRes = [{jid:remove_resource(Jidx),
- affiliation, SAffiliation, Reason}
- || Jidx <- JIDs],
- find_changed_items(UJID, UAffiliation, URole,
- Items, Lang, StateData,
- [MoreRes | Res]);
- false ->
- Txt3 = <<"Changing role/affiliation is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
- {'EXIT', _} ->
- ErrText1 = iolist_to_binary(
- io_lib:format(translate:translate(
- Lang,
- <<"Invalid role: ~s">>),
- [StrRole])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
- SRole ->
- ServiceAf = get_service_affiliation(JID, StateData),
- CanChangeRA = case can_change_ra(UAffiliation, URole,
- TAffiliation, TRole,
- role, SRole, ServiceAf)
- of
- nothing -> nothing;
- true -> true;
- check_owner ->
- case search_affiliation(owner,
- StateData)
- of
- [{OJID, _}] ->
- jid:remove_resource(OJID)
- /=
- jid:tolower(jid:remove_resource(UJID));
- _ -> true
- end;
- _ -> false
- end,
- case CanChangeRA of
- nothing ->
- find_changed_items(UJID, UAffiliation, URole, Items,
- Lang, StateData, Res);
- true ->
- Reason = fxml:get_path_s(Item,
- [{elem, <<"reason">>},
- cdata]),
- MoreRes = [{Jidx, role, SRole, Reason}
- || Jidx <- JIDs],
- find_changed_items(UJID, UAffiliation, URole, Items,
- Lang, StateData,
- [MoreRes | Res]);
- _ ->
- Txt4 = <<"Changing role/affiliation is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)}
- end
+ [JID | _] = JIDs =
+ if J /= undefined ->
+ [J];
+ Nick /= undefined ->
+ 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])),
+ throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
+ JIDList ->
+ JIDList
end
- end;
- Err -> Err
- end;
-find_changed_items(_UJID, _UAffiliation, _URole, _Items,
- _Lang, _StateData, _Res) ->
- {error, ?ERR_BAD_REQUEST}.
+ end,
+ {RoleOrAff, RoleOrAffValue} = if Role == undefined ->
+ {affiliation, Affiliation};
+ true ->
+ {role, Role}
+ end,
+ TAffiliation = get_affiliation(JID, StateData),
+ TRole = get_role(JID, StateData),
+ ServiceAf = get_service_affiliation(JID, StateData),
+ CanChangeRA = case can_change_ra(UAffiliation,
+ URole,
+ TAffiliation,
+ TRole, RoleOrAff, RoleOrAffValue,
+ ServiceAf) of
+ nothing -> nothing;
+ true -> true;
+ check_owner ->
+ case search_affiliation(owner, StateData) of
+ [{OJID, _}] ->
+ jid:remove_resource(OJID)
+ /=
+ jid:tolower(jid:remove_resource(UJID));
+ _ -> true
+ end;
+ _ -> false
+ end,
+ case CanChangeRA of
+ nothing ->
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ Res);
+ true ->
+ Reason = if is_binary(Reason0) -> Reason0;
+ true -> <<"">>
+ end,
+ MoreRes = [{jid:remove_resource(Jidx),
+ RoleOrAff, RoleOrAffValue, Reason}
+ || Jidx <- JIDs],
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ [MoreRes | Res]);
+ false ->
+ Txt = <<"Changing role/affiliation is not allowed">>,
+ throw({error, xmpp:err_not_allowed(Txt, Lang)})
+ end.
+-spec can_change_ra(affiliation(), role(), affiliation(), role(),
+ affiliation, affiliation(), affiliation()) -> boolean();
+ (affiliation(), role(), affiliation(), role(),
+ role, role(), affiliation()) -> boolean().
can_change_ra(_FAffiliation, _FRole, owner, _TRole,
affiliation, owner, owner) ->
%% A room owner tries to add as persistent owner a
@@ -3255,11 +2857,15 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation,
_TRole, role, _Value, _ServiceAf) ->
false.
+-spec send_kickban_presence(jid(), jid(), binary(),
+ pos_integer(), state()) -> ok.
send_kickban_presence(UJID, JID, Reason, Code, StateData) ->
NewAffiliation = get_affiliation(JID, StateData),
send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
StateData).
+-spec send_kickban_presence(jid(), jid(), binary(), pos_integer(),
+ affiliation(), state()) -> ok.
send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
StateData) ->
LJID = jid:tolower(JID),
@@ -3288,77 +2894,51 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
end,
LJIDs).
+-spec send_kickban_presence1(jid(), jid(), binary(), pos_integer(),
+ affiliation(), state()) -> ok.
send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData) ->
{ok, #user{jid = RealJID, nick = Nick}} =
(?DICT):find(jid:tolower(UJID),
StateData#state.users),
- SAffiliation = affiliation_to_list(Affiliation),
- BannedJIDString = jid:to_string(RealJID),
ActorNick = get_actor_nick(MJID, StateData),
- lists:foreach(fun ({_LJID, Info}) ->
- JidAttrList = case Info#user.role == moderator orelse
- (StateData#state.config)#config.anonymous
- == false
- of
- true ->
- [{<<"jid">>, BannedJIDString}];
- false -> []
- end,
- ItemAttrs = [{<<"affiliation">>, SAffiliation},
- {<<"role">>, <<"none">>}]
- ++ JidAttrList,
- ItemEls = case Reason of
- <<"">> -> [];
- _ ->
- [#xmlel{name = <<"reason">>,
- attrs = [],
- children =
- [{xmlcdata, Reason}]}]
- end,
- ItemElsActor = case MJID of
- <<"">> -> [];
- _ -> [#xmlel{name = <<"actor">>,
- attrs =
- [{<<"nick">>, ActorNick}]}]
- end,
- Packet = #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs =
- ItemAttrs,
- children =
- ItemElsActor ++ ItemEls},
- #xmlel{name =
- <<"status">>,
- attrs =
- [{<<"code">>,
- Code}],
- children =
- []}]}]},
- RoomJIDNick = jid:replace_resource(
- StateData#state.jid, Nick),
- send_wrapped(RoomJIDNick, Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
- IsSubscriber = Info#user.is_subscriber,
- IsOccupant = Info#user.last_presence /= undefined,
- if (IsSubscriber and not IsOccupant) ->
- send_wrapped(RoomJIDNick, Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
- true ->
- ok
- end
- end,
- (?DICT):to_list(StateData#state.users)).
+ lists:foreach(
+ fun({_LJID, Info}) ->
+ Item0 = #muc_item{affiliation = Affiliation,
+ role = none},
+ Item1 = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false of
+ true -> Item0#muc_item{jid = RealJID};
+ false -> Item0
+ end,
+ Item2 = if is_binary(Reason), Reason /= <<"">> ->
+ Item1#muc_item{reason = Reason};
+ true ->
+ Item1
+ end,
+ Item = case ActorNick of
+ <<"">> -> Item2;
+ _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}}
+ end,
+ Packet = #presence{type = unavailable,
+ sub_els = [#muc_user{items = [Item],
+ status_codes = [Code]}]},
+ RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick),
+ send_wrapped(RoomJIDNick, Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
+ IsSubscriber = Info#user.is_subscriber,
+ IsOccupant = Info#user.last_presence /= undefined,
+ if (IsSubscriber and not IsOccupant) ->
+ send_wrapped(RoomJIDNick, Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
+ true ->
+ ok
+ end
+ end,
+ (?DICT):to_list(StateData#state.users)).
+-spec get_actor_nick(binary() | jid(), state()) -> binary().
get_actor_nick(<<"">>, _StateData) ->
<<"">>;
get_actor_nick(MJID, StateData) ->
@@ -3369,97 +2949,86 @@ get_actor_nick(MJID, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Owner stuff
-
-process_iq_owner(From, set, Lang, SubEl, StateData) ->
+-spec process_iq_owner(jid(), iq(), state()) ->
+ {result, undefined | muc_owner()} |
+ {result, undefined | muc_owner(), state() | stop} |
+ {error, error()}.
+process_iq_owner(From, #iq{type = set, lang = Lang,
+ sub_els = [#muc_owner{destroy = Destroy,
+ config = Config,
+ items = Items}]},
+ StateData) ->
FAffiliation = get_affiliation(From, StateData),
- case FAffiliation of
- owner ->
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>} = 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">>} ->
- case is_allowed_log_change(XEl, StateData, From) andalso
- is_allowed_persistent_change(XEl, StateData, From)
- andalso
- is_allowed_room_name_desc_limits(XEl, StateData)
- andalso
- is_password_settings_correct(XEl, StateData)
- of
- true -> set_config(XEl, StateData, Lang);
- false -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
- end;
- [#xmlel{name = <<"destroy">>} = SubEl1] ->
- ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
- [jid:to_string(StateData#state.jid),
- jid:to_string(From)]),
- add_to_log(room_existence, destroyed, StateData),
- destroy_room(SubEl1, StateData);
- Items ->
- process_admin_items_set(From, Items, Lang, StateData)
- end;
- _ ->
- ErrText = <<"Owner privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ if FAffiliation /= owner ->
+ ErrText = <<"Owner privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)};
+ Destroy /= undefined, Config == undefined, Items == [] ->
+ ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
+ [jid:to_string(StateData#state.jid), jid:to_string(From)]),
+ add_to_log(room_existence, destroyed, StateData),
+ destroy_room(Destroy, StateData);
+ Config /= undefined, Destroy == undefined, Items == [] ->
+ 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()}
+ end;
+ _ ->
+ Txt = <<"Incorrect data form">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ Items /= [], Config == undefined, Destroy == undefined ->
+ process_admin_items_set(From, Items, Lang, StateData);
+ true ->
+ {error, xmpp:err_bad_request()}
end;
-process_iq_owner(From, get, Lang, SubEl, StateData) ->
+process_iq_owner(From, #iq{type = get, lang = Lang,
+ sub_els = [#muc_owner{destroy = Destroy,
+ config = Config,
+ items = Items}]},
+ StateData) ->
FAffiliation = get_affiliation(From, StateData),
- case FAffiliation of
- owner ->
- #xmlel{children = Els} = SubEl,
- case fxml:remove_cdata(Els) of
- [] -> get_config(Lang, StateData, From);
- [Item] ->
- case fxml:get_tag_attr(<<"affiliation">>, Item) of
- false ->
- Txt = <<"No 'affiliation' attribute found">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText = iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Invalid affiliation: ~s">>),
- [StrAffiliation])),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- SAffiliation ->
- Items = items_with_affiliation(SAffiliation,
- StateData),
- {result, Items, StateData}
- end
- end;
- _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
- end;
- _ ->
- ErrText = <<"Owner privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ if FAffiliation /= owner ->
+ ErrText = <<"Owner privileges required">>,
+ {error, xmpp:err_forbidden(ErrText, Lang)};
+ Destroy == undefined, Config == undefined ->
+ case Items of
+ [] ->
+ {result,
+ #muc_owner{config = get_config(Lang, StateData, From)}};
+ [#muc_item{affiliation = undefined}] ->
+ Txt = <<"No 'affiliation' attribute found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ [#muc_item{affiliation = Affiliation}] ->
+ Items = items_with_affiliation(Affiliation, StateData),
+ {result, #muc_owner{items = Items}};
+ [_|_] ->
+ Txt = <<"Too many <item/> elements">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end;
+ true ->
+ {error, xmpp:err_bad_request()}
end.
-is_allowed_log_change(XEl, StateData, From) ->
- case lists:keymember(<<"muc#roomconfig_enablelogging">>,
- 1, jlib:parse_xdata_submit(XEl))
- of
- false -> true;
- true ->
- allow ==
- mod_muc_log:check_access_log(StateData#state.server_host,
- From)
+-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
+ false -> true;
+ true ->
+ allow ==
+ mod_muc_log:check_access_log(StateData#state.server_host,
+ From)
end.
-is_allowed_persistent_change(XEl, StateData, From) ->
- case
- lists:keymember(<<"muc#roomconfig_persistentroom">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
+-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
false -> true;
true ->
{_AccessRoute, _AccessCreate, _AccessAdmin,
@@ -3472,58 +3041,57 @@ is_allowed_persistent_change(XEl, StateData, From) ->
%% Check if the Room Name and Room Description defined in the Data Form
%% are conformant to the configured limits
-is_allowed_room_name_desc_limits(XEl, StateData) ->
- IsNameAccepted = case
- lists:keysearch(<<"muc#roomconfig_roomname">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [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
+-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
- lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [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
+ 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.
%% Return false if:
%% "the password for a password-protected room is blank"
-is_password_settings_correct(XEl, StateData) ->
+-spec is_password_settings_correct(xdata(), state()) -> boolean().
+is_password_settings_correct(XData, StateData) ->
Config = StateData#state.config,
OldProtected = Config#config.password_protected,
OldPassword = Config#config.password,
- NewProtected = case
- lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>,
- 1, jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [<<"1">>]}} -> true;
- {value, {_, [<<"0">>]}} -> false;
- _ -> undefined
+ NewProtected = case xmpp_util:get_xdata_values(
+ <<"muc#roomconfig_passwordprotectedroom">>, XData) of
+ [<<"1">>] -> true;
+ [<<"true">>] -> true;
+ [<<"0">>] -> false;
+ [<<"false">>] -> false;
+ _ -> undefined
end,
- NewPassword = case
- lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1,
- jlib:parse_xdata_submit(XEl))
- of
- {value, {_, [P]}} -> P;
- _ -> undefined
+ NewPassword = case xmpp_util:get_xdata_values(
+ <<"muc#roomconfig_roomsecret">>, XData) of
+ [P] -> P;
+ _ -> undefined
end,
case {OldProtected, NewProtected, OldPassword,
NewPassword}
@@ -3535,40 +3103,35 @@ is_password_settings_correct(XEl, StateData) ->
_ -> true
end.
--define(XFIELD(Type, Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, Type},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
+-define(XFIELD(Type, Label, Var, Vals),
+ #xdata_field{type = Type,
+ label = translate:translate(Lang, Label),
+ var = Var,
+ values = Vals}).
-define(BOOLXFIELD(Label, Var, Val),
- ?XFIELD(<<"boolean">>, Label, Var,
+ ?XFIELD(boolean, Label, Var,
case Val of
- true -> <<"1">>;
- _ -> <<"0">>
+ true -> [<<"1">>];
+ _ -> [<<"0">>]
end)).
-define(STRINGXFIELD(Label, Var, Val),
- ?XFIELD(<<"text-single">>, Label, Var, Val)).
+ ?XFIELD('text-single', Label, Var, [Val])).
-define(PRIVATEXFIELD(Label, Var, Val),
- ?XFIELD(<<"text-private">>, Label, Var, Val)).
+ ?XFIELD('text-private', Label, Var, [Val])).
-define(JIDMULTIXFIELD(Label, Var, JIDList),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"jid-multi">>},
- {<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, jid:to_string(JID)}]}
- || JID <- JIDList]}).
+ ?XFIELD('jid-multi', Label, Var,
+ [jid:to_string(JID) || JID <- JIDList])).
+-spec make_options([{binary(), binary()}], binary()) -> [xdata_option()].
+make_options(Options, Lang) ->
+ [#xdata_option{label = translate:translate(Lang, Label),
+ value = Value} || {Label, Value} <- Options].
+
+-spec get_default_room_maxusers(state()) -> non_neg_integer().
get_default_room_maxusers(RoomState) ->
DefRoomOpts =
gen_mod:get_module_opt(RoomState#state.server_host,
@@ -3578,342 +3141,193 @@ get_default_room_maxusers(RoomState) ->
RoomState2 = set_opts(DefRoomOpts, RoomState),
(RoomState2#state.config)#config.max_users.
+-spec get_config(binary(), state(), jid()) -> xdata().
get_config(Lang, StateData, From) ->
- {_AccessRoute, _AccessCreate, _AccessAdmin,
- AccessPersistent} =
+ {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} =
StateData#state.access,
ServiceMaxUsers = get_service_max_users(StateData),
- DefaultRoomMaxUsers =
- get_default_room_maxusers(StateData),
+ DefaultRoomMaxUsers = get_default_room_maxusers(StateData),
Config = StateData#state.config,
- {MaxUsersRoomInteger, MaxUsersRoomString} = case
- get_max_users(StateData)
- of
- N when is_integer(N) ->
- {N,
- jlib:integer_to_binary(N)};
- _ -> {0, <<"none">>}
- end,
- Res = [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- io_lib:format(
- translate:translate(
- Lang,
- <<"Configuration of room ~s">>),
- [jid:to_string(StateData#state.jid)]))}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"hidden">>},
- {<<"var">>, <<"FORM_TYPE">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"http://jabber.org/protocol/muc#roomconfig">>}]}]},
- ?STRINGXFIELD(<<"Room title">>,
- <<"muc#roomconfig_roomname">>, (Config#config.title)),
- ?STRINGXFIELD(<<"Room description">>,
- <<"muc#roomconfig_roomdesc">>,
- (Config#config.description))]
- ++
- case acl:match_rule(StateData#state.server_host,
- AccessPersistent, From)
- of
- allow ->
- [?BOOLXFIELD(<<"Make room persistent">>,
- <<"muc#roomconfig_persistentroom">>,
- (Config#config.persistent))];
- _ -> []
- end
- ++
- [?BOOLXFIELD(<<"Make room public searchable">>,
- <<"muc#roomconfig_publicroom">>,
- (Config#config.public)),
- ?BOOLXFIELD(<<"Make participants list public">>,
- <<"public_list">>, (Config#config.public_list)),
- ?BOOLXFIELD(<<"Make room password protected">>,
- <<"muc#roomconfig_passwordprotectedroom">>,
- (Config#config.password_protected)),
- ?PRIVATEXFIELD(<<"Password">>,
- <<"muc#roomconfig_roomsecret">>,
- case Config#config.password_protected of
- true -> Config#config.password;
- false -> <<"">>
- end),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Maximum Number of Occupants">>)},
- {<<"var">>, <<"muc#roomconfig_maxusers">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, MaxUsersRoomString}]}]
- ++
- if is_integer(ServiceMaxUsers) -> [];
- true ->
- [#xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"No limit">>)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- <<"none">>}]}]}]
- end
- ++
- [#xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- jlib:integer_to_binary(N)}],
- children =
- [#xmlel{name = <<"value">>,
- attrs = [],
- children =
- [{xmlcdata,
- jlib:integer_to_binary(N)}]}]}
- || N
- <- lists:usort([ServiceMaxUsers,
- DefaultRoomMaxUsers,
- MaxUsersRoomInteger
- | ?MAX_USERS_DEFAULT_LIST]),
- N =< ServiceMaxUsers]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Present real Jabber IDs to">>)},
- {<<"var">>, <<"muc#roomconfig_whois">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- if Config#config.anonymous ->
- <<"moderators">>;
- true -> <<"anyone">>
- end}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"moderators only">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"moderators">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"anyone">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- 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)),
- ?BOOLXFIELD(<<"Make room moderated">>,
- <<"muc#roomconfig_moderatedroom">>,
- (Config#config.moderated)),
- ?BOOLXFIELD(<<"Default users as participants">>,
- <<"members_by_default">>,
- (Config#config.members_by_default)),
- ?BOOLXFIELD(<<"Allow users to change the subject">>,
- <<"muc#roomconfig_changesubject">>,
- (Config#config.allow_change_subj)),
- ?BOOLXFIELD(<<"Allow users to send private messages">>,
- <<"allow_private_messages">>,
- (Config#config.allow_private_messages)),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"type">>, <<"list-single">>},
- {<<"label">>,
- translate:translate(Lang,
- <<"Allow visitors to send private messages to">>)},
- {<<"var">>,
- <<"allow_private_messages_from_visitors">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- case
- Config#config.allow_private_messages_from_visitors
- of
- anyone -> <<"anyone">>;
- moderators -> <<"moderators">>;
- nobody -> <<"nobody">>
- end}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"nobody">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata, <<"nobody">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"moderators only">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"moderators">>}]}]},
- #xmlel{name = <<"option">>,
- attrs =
- [{<<"label">>,
- translate:translate(Lang,
- <<"anyone">>)}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"anyone">>}]}]}]},
- ?BOOLXFIELD(<<"Allow users to query other users">>,
- <<"allow_query_users">>,
- (Config#config.allow_query_users)),
- ?BOOLXFIELD(<<"Allow users to send invites">>,
- <<"muc#roomconfig_allowinvites">>,
- (Config#config.allow_user_invites)),
- ?BOOLXFIELD(<<"Allow visitors to send status text in "
- "presence updates">>,
- <<"muc#roomconfig_allowvisitorstatus">>,
- (Config#config.allow_visitor_status)),
- ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
- <<"muc#roomconfig_allowvisitornickchange">>,
- (Config#config.allow_visitor_nickchange)),
- ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
- <<"muc#roomconfig_allowvoicerequests">>,
- (Config#config.allow_voice_requests)),
- ?BOOLXFIELD(<<"Allow subscription">>,
- <<"muc#roomconfig_allow_subscription">>,
- (Config#config.allow_subscription)),
- ?STRINGXFIELD(<<"Minimum interval between voice requests "
- "(in seconds)">>,
- <<"muc#roomconfig_voicerequestmininterval">>,
- (jlib:integer_to_binary(Config#config.voice_request_min_interval)))]
- ++
- case ejabberd_captcha:is_feature_available() of
- true ->
- [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
- <<"captcha_protected">>,
- (Config#config.captcha_protected))];
- false -> []
- end ++
- [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
- <<"muc#roomconfig_captcha_whitelist">>,
- ((?SETS):to_list(Config#config.captcha_whitelist)))]
- ++
- case
- mod_muc_log:check_access_log(StateData#state.server_host,
- From)
- of
- allow ->
- [?BOOLXFIELD(<<"Enable logging">>,
- <<"muc#roomconfig_enablelogging">>,
- (Config#config.logging))];
- _ -> []
- end,
- X = ejabberd_hooks:run_fold(get_room_config,
- StateData#state.server_host,
- Res,
- [StateData, From, Lang]),
- {result,
- [#xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"You need an x:data capable client to "
- "configure room">>)}]},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children = X}],
- StateData}.
-
-set_config(XEl, StateData, Lang) ->
- XData = jlib:parse_xdata_submit(XEl),
- case XData of
- invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
- _ ->
- case set_xoption(XData, StateData#state.config,
- StateData#state.server_host, Lang) of
- #config{} = Config ->
- Res = change_config(Config, StateData),
- {result, _, NSD} = Res,
- Type = case {(StateData#state.config)#config.logging,
- Config#config.logging}
- of
- {true, false} -> roomconfig_change_disabledlogging;
- {false, true} -> roomconfig_change_enabledlogging;
- {_, _} -> roomconfig_change
- end,
- Users = [{U#user.jid, U#user.nick, U#user.role}
- || {_, U} <- (?DICT):to_list(StateData#state.users)],
- add_to_log(Type, Users, NSD),
- Res;
- Err -> Err
- end
+ {MaxUsersRoomInteger, MaxUsersRoomString} =
+ case get_max_users(StateData) of
+ N when is_integer(N) ->
+ {N, integer_to_binary(N)};
+ _ -> {0, <<"none">>}
+ end,
+ Title = iolist_to_binary(
+ io_lib:format(
+ translate:translate(Lang, <<"Configuration of room ~s">>),
+ [jid:to_string(StateData#state.jid)])),
+ Fs = [#xdata_field{type = hidden,
+ var = <<"FORM_TYPE">>,
+ values = [<<"http://jabber.org/protocol/muc#roomconfig">>]},
+ ?STRINGXFIELD(<<"Room title">>,
+ <<"muc#roomconfig_roomname">>, (Config#config.title)),
+ ?STRINGXFIELD(<<"Room description">>,
+ <<"muc#roomconfig_roomdesc">>,
+ (Config#config.description))] ++
+ case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of
+ allow ->
+ [?BOOLXFIELD(<<"Make room persistent">>,
+ <<"muc#roomconfig_persistentroom">>,
+ (Config#config.persistent))];
+ deny -> []
+ end ++
+ [?BOOLXFIELD(<<"Make room public searchable">>,
+ <<"muc#roomconfig_publicroom">>,
+ (Config#config.public)),
+ ?BOOLXFIELD(<<"Make participants list public">>,
+ <<"public_list">>, (Config#config.public_list)),
+ ?BOOLXFIELD(<<"Make room password protected">>,
+ <<"muc#roomconfig_passwordprotectedroom">>,
+ (Config#config.password_protected)),
+ ?PRIVATEXFIELD(<<"Password">>,
+ <<"muc#roomconfig_roomsecret">>,
+ case Config#config.password_protected of
+ true -> Config#config.password;
+ false -> <<"">>
+ end),
+ #xdata_field{type = 'list-single',
+ label = translate:translate(
+ Lang, <<"Maximum Number of Occupants">>),
+ var = <<"muc#roomconfig_maxusers">>,
+ values = [MaxUsersRoomString],
+ options =
+ if is_integer(ServiceMaxUsers) -> [];
+ true -> make_options(
+ [{<<"No limit">>, <<"none">>}],
+ Lang)
+ end ++
+ make_options(
+ [{integer_to_binary(N), integer_to_binary(N)}
+ || N <- lists:usort([ServiceMaxUsers,
+ DefaultRoomMaxUsers,
+ MaxUsersRoomInteger
+ | ?MAX_USERS_DEFAULT_LIST]),
+ N =< ServiceMaxUsers],
+ Lang)},
+ #xdata_field{type = 'list-single',
+ label = translate:translate(
+ Lang, <<"Present real Jabber IDs to">>),
+ var = <<"muc#roomconfig_whois">>,
+ values = [if Config#config.anonymous -> <<"moderators">>;
+ true -> <<"anyone">>
+ end],
+ options = make_options(
+ [{<<"moderators only">>, <<"moderators">>},
+ {<<"anyone">>, <<"anyone">>}],
+ Lang)},
+ #xdata_field{type = 'list-multi',
+ label = translate:translate(
+ Lang,
+ <<"Roles for which Presence is Broadcasted">>),
+ var = <<"muc#roomconfig_presencebroadcast">>,
+ values = [atom_to_binary(Role, utf8)
+ || Role <- Config#config.presence_broadcast],
+ options = make_options(
+ [{<<"Moderator">>, <<"moderator">>},
+ {<<"Participant">>, <<"participant">>},
+ {<<"Visitor">>, <<"visitor">>}],
+ Lang)},
+ ?BOOLXFIELD(<<"Make room members-only">>,
+ <<"muc#roomconfig_membersonly">>,
+ (Config#config.members_only)),
+ ?BOOLXFIELD(<<"Make room moderated">>,
+ <<"muc#roomconfig_moderatedroom">>,
+ (Config#config.moderated)),
+ ?BOOLXFIELD(<<"Default users as participants">>,
+ <<"members_by_default">>,
+ (Config#config.members_by_default)),
+ ?BOOLXFIELD(<<"Allow users to change the subject">>,
+ <<"muc#roomconfig_changesubject">>,
+ (Config#config.allow_change_subj)),
+ ?BOOLXFIELD(<<"Allow users to send private messages">>,
+ <<"allow_private_messages">>,
+ (Config#config.allow_private_messages)),
+ #xdata_field{type = 'list-single',
+ label = translate:translate(
+ Lang,
+ <<"Allow visitors to send private messages to">>),
+ var = <<"allow_private_messages_from_visitors">>,
+ values = [case Config#config.allow_private_messages_from_visitors of
+ anyone -> <<"anyone">>;
+ moderators -> <<"moderators">>;
+ nobody -> <<"nobody">>
+ end],
+ options = make_options(
+ [{<<"nobody">>, <<"nobody">>},
+ {<<"moderators only">>, <<"moderators">>},
+ {<<"anyone">>, <<"anyone">>}],
+ Lang)},
+ ?BOOLXFIELD(<<"Allow users to query other users">>,
+ <<"allow_query_users">>,
+ (Config#config.allow_query_users)),
+ ?BOOLXFIELD(<<"Allow users to send invites">>,
+ <<"muc#roomconfig_allowinvites">>,
+ (Config#config.allow_user_invites)),
+ ?BOOLXFIELD(<<"Allow visitors to send status text in "
+ "presence updates">>,
+ <<"muc#roomconfig_allowvisitorstatus">>,
+ (Config#config.allow_visitor_status)),
+ ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
+ <<"muc#roomconfig_allowvisitornickchange">>,
+ (Config#config.allow_visitor_nickchange)),
+ ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
+ <<"muc#roomconfig_allowvoicerequests">>,
+ (Config#config.allow_voice_requests)),
+ ?BOOLXFIELD(<<"Allow subscription">>,
+ <<"muc#roomconfig_allow_subscription">>,
+ (Config#config.allow_subscription)),
+ ?STRINGXFIELD(<<"Minimum interval between voice requests "
+ "(in seconds)">>,
+ <<"muc#roomconfig_voicerequestmininterval">>,
+ integer_to_binary(Config#config.voice_request_min_interval))]
+ ++
+ case ejabberd_captcha:is_feature_available() of
+ true ->
+ [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
+ <<"captcha_protected">>,
+ (Config#config.captcha_protected))];
+ false -> []
+ end ++
+ [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
+ <<"muc#roomconfig_captcha_whitelist">>,
+ ((?SETS):to_list(Config#config.captcha_whitelist)))]
+ ++
+ case mod_muc_log:check_access_log(StateData#state.server_host, From) of
+ allow ->
+ [?BOOLXFIELD(<<"Enable logging">>,
+ <<"muc#roomconfig_enablelogging">>,
+ (Config#config.logging))];
+ deny -> []
+ end,
+ Fields = ejabberd_hooks:run_fold(get_room_config,
+ StateData#state.server_host,
+ Fs,
+ [StateData, From, Lang]),
+ #xdata{type = form, title = Title, fields = Fields}.
+
+-spec set_config(xdata(), state(), binary()) -> {error, error()} |
+ {result, undefined, state()}.
+set_config(#xdata{fields = Fields}, StateData, Lang) ->
+ Options = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- Fields],
+ case set_xoption(Options, StateData#state.config,
+ StateData#state.server_host, Lang) of
+ #config{} = Config ->
+ Res = change_config(Config, StateData),
+ {result, _, NSD} = Res,
+ Type = case {(StateData#state.config)#config.logging,
+ Config#config.logging}
+ of
+ {true, false} -> roomconfig_change_disabledlogging;
+ {false, true} -> roomconfig_change_enabledlogging;
+ {_, _} -> roomconfig_change
+ end,
+ Users = [{U#user.jid, U#user.nick, U#user.role}
+ || {_, U} <- (?DICT):to_list(StateData#state.users)],
+ add_to_log(Type, Users, NSD),
+ Res;
+ Err -> Err
end.
-define(SET_BOOL_XOPT(Opt, Val),
@@ -3928,21 +3342,32 @@ set_config(XEl, StateData, Lang) ->
_ ->
Txt = <<"Value of '~s' should be boolean">>,
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
+ {error, xmpp:err_bad_request(ErrTxt, Lang)}
end).
-define(SET_NAT_XOPT(Opt, Val),
- case catch jlib:binary_to_integer(Val) of
+ case catch binary_to_integer(Val) of
I when is_integer(I), I > 0 ->
set_xoption(Opts, Config#config{Opt = I}, ServerHost, Lang);
_ ->
Txt = <<"Value of '~s' should be integer">>,
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
+ {error, xmpp:err_bad_request(ErrTxt, Lang)}
end).
--define(SET_STRING_XOPT(Opt, Val),
- set_xoption(Opts, Config#config{Opt = Val}, ServerHost, Lang)).
+-define(SET_STRING_XOPT(Opt, Vals),
+ try
+ V = case Vals of
+ [] -> <<"">>;
+ [Val] -> Val;
+ _ when is_atom(Vals) -> Vals
+ end,
+ set_xoption(Opts, Config#config{Opt = V}, ServerHost, Lang)
+ catch _:_ ->
+ Txt = <<"Incorrect value of option '~s'">>,
+ ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+ {error, xmpp:err_bad_request(ErrTxt, Lang)}
+ end).
-define(SET_JIDMULTI_XOPT(Opt, Vals),
begin
@@ -3957,15 +3382,17 @@ set_config(XEl, StateData, Lang) ->
set_xoption(Opts, Config#config{Opt = Set}, ServerHost, Lang)
end).
+-spec set_xoption([{binary(), [binary()]}], #config{},
+ binary(), binary()) -> #config{} | {error, error()}.
set_xoption([], Config, _ServerHost, _Lang) -> Config;
-set_xoption([{<<"muc#roomconfig_roomname">>, [Val]}
+set_xoption([{<<"muc#roomconfig_roomname">>, Vals}
| Opts],
Config, ServerHost, Lang) ->
- ?SET_STRING_XOPT(title, Val);
-set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]}
+ ?SET_STRING_XOPT(title, Vals);
+set_xoption([{<<"muc#roomconfig_roomdesc">>, Vals}
| Opts],
Config, ServerHost, Lang) ->
- ?SET_STRING_XOPT(description, Val);
+ ?SET_STRING_XOPT(description, Vals);
set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
| Opts],
Config, ServerHost, Lang) ->
@@ -3994,7 +3421,7 @@ set_xoption([{<<"allow_private_messages_from_visitors">>,
_ ->
Txt = <<"Value of 'allow_private_messages_from_visitors' "
"should be anyone|moderators|nobody">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+ {error, xmpp:err_bad_request(Txt, Lang)}
end;
set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
[Val]}
@@ -4045,10 +3472,10 @@ set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
| Opts],
Config, ServerHost, Lang) ->
?SET_BOOL_XOPT(password_protected, Val);
-set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
+set_xoption([{<<"muc#roomconfig_roomsecret">>, Vals}
| Opts],
Config, ServerHost, Lang) ->
- ?SET_STRING_XOPT(password, Val);
+ ?SET_STRING_XOPT(password, Vals);
set_xoption([{<<"anonymous">>, [Val]} | Opts],
Config, ServerHost, Lang) ->
?SET_BOOL_XOPT(anonymous, Val);
@@ -4069,7 +3496,7 @@ set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
error ->
Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should "
"be moderator|participant|visitor">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+ {error, xmpp:err_bad_request(Txt, Lang)};
{M, P, V} ->
Res =
if M -> [moderator]; true -> [] end ++
@@ -4101,7 +3528,7 @@ set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
_ ->
Txt = <<"Value of 'muc#roomconfig_whois' should be "
"moderators|anyone">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+ {error, xmpp:err_bad_request(Txt, Lang)}
end;
set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
| Opts],
@@ -4125,7 +3552,7 @@ set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) ->
set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) ->
Txt = <<"Unknown option '~s'">>,
ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
- Err = {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)},
+ Err = {error, xmpp:err_bad_request(ErrTxt, Lang)},
case ejabberd_hooks:run_fold(set_room_option,
ServerHost,
Err,
@@ -4136,6 +3563,7 @@ set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) ->
set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang)
end.
+-spec change_config(#config{}, state()) -> {result, undefined, state()}.
change_config(Config, StateData) ->
send_config_change_info(Config, StateData),
NSD = remove_subscriptions(StateData#state{config = Config}),
@@ -4154,57 +3582,54 @@ change_config(Config, StateData) ->
Config#config.members_only}
of
{false, true} ->
- NSD1 = remove_nonmembers(NSD), {result, [], NSD1};
- _ -> {result, [], NSD}
+ NSD1 = remove_nonmembers(NSD), {result, undefined, NSD1};
+ _ -> {result, undefined, NSD}
end.
+-spec send_config_change_info(#config{}, state()) -> ok.
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">>];
+ {false, true} -> [170];
+ {true, false} -> [171];
_ -> []
end
++
case {Old#config.anonymous, New#config.anonymous} of
- {true, false} -> [<<"172">>];
- {false, true} -> [<<"173">>];
+ {true, false} -> [172];
+ {false, true} -> [173];
_ -> []
end
++
case Old#config{anonymous = New#config.anonymous,
logging = New#config.logging} of
New -> [];
- _ -> [<<"104">>]
+ _ -> [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}]},
+ 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).
+-spec remove_nonmembers(state()) -> state().
remove_nonmembers(StateData) ->
lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
Affiliation = get_affiliation(JID, SD),
case Affiliation of
none ->
catch send_kickban_presence(<<"">>, JID, <<"">>,
- <<"322">>, SD),
+ 322, SD),
set_role(JID, none, SD);
_ -> SD
end
end,
StateData, (?DICT):to_list(StateData#state.users)).
+-spec set_opts([{atom(), any()}], state()) -> state().
set_opts([], StateData) -> StateData;
set_opts([{Opt, Val} | Opts], StateData) ->
NSD = case Opt of
@@ -4342,7 +3767,7 @@ set_opts([{Opt, Val} | Opts], StateData) ->
-define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
-
+-spec make_opts(state()) -> [{atom(), any()}].
make_opts(StateData) ->
Config = StateData#state.config,
Subscribers = (?DICT):fold(
@@ -4381,243 +3806,221 @@ make_opts(StateData) ->
{subject_author, StateData#state.subject_author},
{subscribers, Subscribers}].
+-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
destroy_room(DEl, StateData) ->
- lists:foreach(fun ({_LJID, Info}) ->
- Nick = Info#user.nick,
- ItemAttrs = [{<<"affiliation">>, <<"none">>},
- {<<"role">>, <<"none">>}],
- Packet = #xmlel{name = <<"presence">>,
- attrs =
- [{<<"type">>, <<"unavailable">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>,
- ?NS_MUC_USER}],
- children =
- [#xmlel{name =
- <<"item">>,
- attrs =
- ItemAttrs,
- children =
- []},
- DEl]}]},
- send_wrapped(jid:replace_resource(StateData#state.jid,
- Nick),
- Info#user.jid, Packet,
- ?NS_MUCSUB_NODES_CONFIG, StateData)
- end,
- (?DICT):to_list(StateData#state.users)),
+ Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER},
+ lists:foreach(
+ fun({_LJID, Info}) ->
+ Nick = Info#user.nick,
+ Item = #muc_item{affiliation = none,
+ role = none},
+ Packet = #presence{
+ type = unavailable,
+ sub_els = [#muc_user{items = [Item],
+ destroy = Destroy}]},
+ send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
+ Info#user.jid, Packet,
+ ?NS_MUCSUB_NODES_CONFIG, StateData)
+ end,
+ (?DICT):to_list(StateData#state.users)),
case (StateData#state.config)#config.persistent of
true ->
mod_muc:forget_room(StateData#state.server_host,
StateData#state.host, StateData#state.room);
false -> ok
end,
- {result, [], stop}.
+ {result, undefined, stop}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
--define(FEATURE(Var),
- #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}],
- children = []}).
-
-define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse),
case Opt of
- true -> ?FEATURE(Fiftrue);
- false -> ?FEATURE(Fiffalse)
+ true -> Fiftrue;
+ false -> Fiffalse
end).
-process_iq_disco_info(_From, set, Lang, _StateData) ->
+-spec process_iq_disco_info(jid(), iq(), state()) ->
+ {result, disco_info()} | {error, error()}.
+process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_disco_info(_From, get, Lang, StateData) ->
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) ->
Config = StateData#state.config,
- {result,
- [#xmlel{name = <<"identity">>,
- attrs =
- [{<<"category">>, <<"conference">>},
- {<<"type">>, <<"text">>},
- {<<"name">>, get_title(StateData)}],
- children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_VCARD}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MUC}], children = []},
- ?CONFIG_OPT_TO_FEATURE((Config#config.public),
- <<"muc_public">>, <<"muc_hidden">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
- <<"muc_persistent">>, <<"muc_temporary">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
- <<"muc_membersonly">>, <<"muc_open">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
- <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
- <<"muc_moderated">>, <<"muc_unmoderated">>),
- ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
- <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
- ++ case Config#config.allow_subscription of
- true -> [?FEATURE(?NS_MUCSUB)];
- false -> []
- end
- ++ 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}.
-
--define(RFIELDT(Type, Var, Val),
- #xmlel{name = <<"field">>,
- attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
--define(RFIELD(Label, Var, Val),
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"label">>, translate:translate(Lang, Label)},
- {<<"var">>, Var}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children = [{xmlcdata, Val}]}]}).
-
+ Feats = [?NS_VCARD, ?NS_MUC,
+ ?CONFIG_OPT_TO_FEATURE((Config#config.public),
+ <<"muc_public">>, <<"muc_hidden">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
+ <<"muc_persistent">>, <<"muc_temporary">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
+ <<"muc_membersonly">>, <<"muc_open">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
+ <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
+ <<"muc_moderated">>, <<"muc_unmoderated">>),
+ ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
+ <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
+ ++ case Config#config.allow_subscription of
+ true -> [?NS_MUCSUB];
+ false -> []
+ end
+ ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
+ Config#config.mam} of
+ {true, true} ->
+ [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
+ _ ->
+ []
+ end,
+ {result, #disco_info{xdata = [iq_disco_info_extras(Lang, StateData)],
+ identities = [#identity{category = <<"conference">>,
+ type = <<"text">>,
+ name = get_title(StateData)}],
+ features = Feats}}.
+
+-spec mk_rfieldt('boolean' | 'fixed' | 'hidden' |
+ 'jid-multi' | 'jid-single' | 'list-multi' |
+ 'list-single' | 'text-multi' | 'text-private' |
+ 'text-single', binary(), binary()) -> xdata_field().
+mk_rfieldt(Type, Var, Val) ->
+ #xdata_field{type = Type, var = Var, values = [Val]}.
+
+-spec mk_rfield(binary(), binary(), binary(), binary()) -> xdata_field().
+mk_rfield(Label, Var, Val, Lang) ->
+ #xdata_field{type = 'text-single',
+ label = translate:translate(Lang, Label),
+ var = Var,
+ values = [Val]}.
+
+-spec iq_disco_info_extras(binary(), state()) -> xdata().
iq_disco_info_extras(Lang, StateData) ->
Len = (?DICT):size(StateData#state.users),
- RoomDescription =
- (StateData#state.config)#config.description,
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
- children =
- [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>,
- <<"http://jabber.org/protocol/muc#roominfo">>),
- ?RFIELD(<<"Room description">>,
- <<"muc#roominfo_description">>, RoomDescription),
- ?RFIELD(<<"Number of occupants">>,
- <<"muc#roominfo_occupants">>,
- (iolist_to_binary(integer_to_list(Len))))]}].
-
-process_iq_disco_items(_From, set, Lang, _StateData) ->
+ RoomDescription = (StateData#state.config)#config.description,
+ #xdata{type = result,
+ fields = [mk_rfieldt(hidden, <<"FORM_TYPE">>,
+ "http://jabber.org/protocol/muc#roominfo"),
+ mk_rfield(<<"Room description">>,
+ <<"muc#roominfo_description">>,
+ RoomDescription, Lang),
+ mk_rfield(<<"Number of occupants">>,
+ <<"muc#roominfo_occupants">>,
+ integer_to_binary(Len), Lang)]}.
+
+-spec process_iq_disco_items(jid(), iq(), state()) ->
+ {error, error()} | {result, disco_items()}.
+process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_disco_items(From, get, Lang, StateData) ->
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_disco_items(From, #iq{type = get, lang = Lang}, StateData) ->
case (StateData#state.config)#config.public_list of
true ->
- {result, get_mucroom_disco_items(StateData), StateData};
+ {result, get_mucroom_disco_items(StateData)};
_ ->
case is_occupant_or_admin(From, StateData) of
true ->
- {result, get_mucroom_disco_items(StateData), StateData};
+ {result, get_mucroom_disco_items(StateData)};
_ ->
Txt = <<"Only occupants or administrators can perform this query">>,
- {error, ?ERRT_FORBIDDEN(Lang, Txt)}
+ {error, xmpp:err_forbidden(Txt, Lang)}
end
end.
-process_iq_captcha(_From, get, Lang, _SubEl,
- _StateData) ->
+-spec process_iq_captcha(jid(), iq(), state()) -> {error, error()} |
+ {result, undefined}.
+process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) ->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_captcha(_From, set, Lang, SubEl,
- StateData) ->
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
+ _StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
- ok -> {result, [], StateData};
+ ok -> {result, undefined};
{error, malformed} ->
Txt = <<"Incorrect CAPTCHA submit">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+ {error, xmpp:err_bad_request(Txt, Lang)};
_ ->
Txt = <<"The CAPTCHA verification has failed">>,
- {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
+ {error, xmpp:err_not_allowed(Txt, Lang)}
end.
-process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
+-spec process_iq_vcard(jid(), iq(), state()) ->
+ {result, vcard_temp() | xmlel()} |
+ {result, undefined, state()} |
+ {error, error()}.
+process_iq_vcard(_From, #iq{type = get}, StateData) ->
#state{config = #config{vcard = VCardRaw}} = StateData,
case fxml_stream:parse_element(VCardRaw) of
- #xmlel{children = VCardEls} ->
- {result, VCardEls, StateData};
+ #xmlel{} = VCard ->
+ {result, VCard};
{error, _} ->
- {result, [], StateData}
+ {result, #vcard_temp{}}
end;
-process_iq_vcard(From, set, Lang, SubEl, StateData) ->
+process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
+ StateData) ->
case get_affiliation(From, StateData) of
owner ->
- VCardRaw = fxml:element_to_binary(SubEl),
+ VCardRaw = fxml:element_to_binary(xmpp:encode(SubEl)),
Config = StateData#state.config,
NewConfig = Config#config{vcard = VCardRaw},
change_config(NewConfig, StateData);
_ ->
ErrText = <<"Owner privileges required">>,
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ {error, xmpp:err_forbidden(ErrText, Lang)}
end.
-process_iq_mucsub(From, Packet,
+-spec process_iq_mucsub(jid(), iq(), state()) ->
+ {error, error()} |
+ {result, undefined | muc_subscribe(), state()} |
+ {ignore, state()}.
+process_iq_mucsub(_From, #iq{type = set, lang = Lang,
+ sub_els = [#muc_subscribe{}]},
+ #state{config = #config{allow_subscription = false}}) ->
+ {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)};
+process_iq_mucsub(From,
#iq{type = set, lang = Lang,
- sub_el = #xmlel{name = <<"subscribe">>} = SubEl},
- #state{config = Config} = StateData) ->
- case fxml:get_tag_attr_s(<<"nick">>, SubEl) of
- <<"">> ->
- Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>),
- {error, Err};
- Nick when Config#config.allow_subscription ->
- LJID = jid:tolower(From),
- case (?DICT):find(LJID, StateData#state.users) of
- {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick ->
- Nodes = get_subscription_nodes(Packet),
- case {nick_collision(From, Nick, StateData),
- mod_muc:can_use_nick(StateData#state.server_host,
- StateData#state.host,
- From, Nick)} of
- {true, _} ->
- ErrText = <<"That nickname is already in use by another occupant">>,
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
- {_, false} ->
- ErrText = <<"That nickname is registered by another person">>,
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
- _ ->
- NewStateData = add_online_user(
- From, Nick, Role, true, Nodes, StateData),
- {result, subscription_nodes_to_events(Nodes), NewStateData}
- end;
- {ok, #user{role = Role}} ->
- Nodes = get_subscription_nodes(Packet),
+ sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
+ StateData) ->
+ LJID = jid:tolower(From),
+ case (?DICT):find(LJID, StateData#state.users) of
+ {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick ->
+ Nodes = get_subscription_nodes(Packet),
+ case {nick_collision(From, Nick, StateData),
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host,
+ From, Nick)} of
+ {true, _} ->
+ ErrText = <<"That nickname is already in use by another occupant">>,
+ {error, xmpp:err_conflict(ErrText, Lang)};
+ {_, false} ->
+ ErrText = <<"That nickname is registered by another person">>,
+ {error, xmpp:err_conflict(ErrText, Lang)};
+ _ ->
NewStateData = add_online_user(
From, Nick, Role, true, Nodes, StateData),
- {result, subscription_nodes_to_events(Nodes), NewStateData};
- error ->
- add_new_user(From, Nick, Packet, StateData)
+ {result, subscribe_result(Packet), NewStateData}
end;
- _ ->
- Err = ?ERRT_NOT_ALLOWED(Lang, <<"Subscriptions are not allowed">>),
- {error, Err}
+ {ok, #user{role = Role}} ->
+ Nodes = get_subscription_nodes(Packet),
+ NewStateData = add_online_user(
+ From, Nick, Role, true, Nodes, StateData),
+ {result, subscribe_result(Packet), NewStateData};
+ error ->
+ add_new_user(From, Nick, Packet, StateData)
end;
-process_iq_mucsub(From, _Packet,
- #iq{type = set,
- sub_el = #xmlel{name = <<"unsubscribe">>}},
+process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
StateData) ->
LJID = jid:tolower(From),
case ?DICT:find(LJID, StateData#state.users) of
{ok, #user{is_subscriber = true} = User} ->
NewStateData = remove_subscription(From, User, StateData),
store_room(NewStateData),
- {result, [], NewStateData};
+ {result, undefined, NewStateData};
_ ->
- {result, [], StateData}
+ {result, undefined, StateData}
end;
-process_iq_mucsub(_From, _Packet, #iq{type = set, lang = Lang}, _StateData) ->
- Txt = <<"Unrecognized subscription command">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-process_iq_mucsub(_From, _Packet, #iq{type = get, lang = Lang}, _StateData) ->
+process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)}.
+ {error, xmpp:err_bad_request(Txt, Lang)}.
+-spec remove_subscription(jid(), #user{}, state()) -> state().
remove_subscription(JID, #user{is_subscriber = true} = User, StateData) ->
case User#user.last_presence of
undefined ->
@@ -4631,6 +4034,7 @@ remove_subscription(JID, #user{is_subscriber = true} = User, StateData) ->
remove_subscription(_JID, #user{}, StateData) ->
StateData.
+-spec remove_subscriptions(state()) -> state().
remove_subscriptions(StateData) ->
if not (StateData#state.config)#config.allow_subscription ->
dict:fold(
@@ -4641,41 +4045,32 @@ remove_subscriptions(StateData) ->
StateData
end.
-get_subscription_nodes(#xmlel{name = <<"iq">>} = Packet) ->
- case fxml:get_subtag_with_xmlns(Packet, <<"subscribe">>, ?NS_MUCSUB) of
- #xmlel{children = Els} ->
- lists:flatmap(
- fun(#xmlel{name = <<"event">>, attrs = Attrs}) ->
- Node = fxml:get_attr_s(<<"node">>, Attrs),
- case lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
- ?NS_MUCSUB_NODES_MESSAGES,
- ?NS_MUCSUB_NODES_AFFILIATIONS,
- ?NS_MUCSUB_NODES_SUBJECT,
- ?NS_MUCSUB_NODES_CONFIG,
- ?NS_MUCSUB_NODES_PARTICIPANTS]) of
- true ->
- [Node];
- false ->
- []
- end;
- (_) ->
- []
- end, Els);
- false ->
- []
- end;
+-spec get_subscription_nodes(iq()) -> [binary()].
+get_subscription_nodes(#iq{sub_els = [#muc_subscribe{events = Nodes}]}) ->
+ lists:filter(
+ fun(Node) ->
+ lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
+ ?NS_MUCSUB_NODES_MESSAGES,
+ ?NS_MUCSUB_NODES_AFFILIATIONS,
+ ?NS_MUCSUB_NODES_SUBJECT,
+ ?NS_MUCSUB_NODES_CONFIG,
+ ?NS_MUCSUB_NODES_PARTICIPANTS])
+ end, Nodes);
get_subscription_nodes(_) ->
[].
-subscription_nodes_to_events(Nodes) ->
- [#xmlel{name = <<"event">>, attrs = [{<<"node">>, Node}]} || Node <- Nodes].
+-spec subscribe_result(iq()) -> muc_subscribe().
+subscribe_result(#iq{sub_els = [#muc_subscribe{nick = Nick}]} = Packet) ->
+ #muc_subscribe{nick = Nick, events = get_subscription_nodes(Packet)}.
+-spec get_title(state()) -> binary().
get_title(StateData) ->
case (StateData#state.config)#config.title of
<<"">> -> StateData#state.room;
Name -> Name
end.
+-spec get_roomdesc_reply(jid(), state(), binary()) -> {item, binary()} | false.
get_roomdesc_reply(JID, StateData, Tail) ->
IsOccupantOrAdmin = is_occupant_or_admin(JID,
StateData),
@@ -4689,352 +4084,215 @@ get_roomdesc_reply(JID, StateData, Tail) ->
true -> false
end.
+-spec get_roomdesc_tail(state(), binary()) -> binary().
get_roomdesc_tail(StateData, Lang) ->
Desc = case (StateData#state.config)#config.public of
true -> <<"">>;
_ -> translate:translate(Lang, <<"private, ">>)
end,
- Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0,
- StateData#state.users),
+ Len = (?DICT):size(StateData#state.users),
<<" (", Desc/binary,
(iolist_to_binary(integer_to_list(Len)))/binary, ")">>.
+-spec get_mucroom_disco_items(state()) -> disco_items().
get_mucroom_disco_items(StateData) ->
- lists:map(fun ({_LJID, Info}) ->
+ Items = lists:map(
+ fun({_LJID, Info}) ->
Nick = Info#user.nick,
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string({StateData#state.room,
- StateData#state.host,
- Nick})},
- {<<"name">>, Nick}],
- children = []}
+ #disco_item{jid = jid:make(StateData#state.room,
+ StateData#state.host,
+ Nick),
+ name = Nick}
end,
- (?DICT):to_list(StateData#state.users)).
+ (?DICT):to_list(StateData#state.users)),
+ #disco_items{items = Items}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Voice request support
-is_voice_request(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
- El,
- false) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- case jlib:parse_xdata_submit(El) of
- [_ | _] = Fields ->
- case {lists:keysearch(<<"FORM_TYPE">>, 1,
- Fields),
- lists:keysearch(<<"muc#role">>, 1,
- Fields)}
- of
- {{value,
- {_,
- [<<"http://jabber.org/protocol/muc#request">>]}},
- {value, {_, [<<"participant">>]}}} ->
- true;
- _ -> false
- end;
- _ -> false
- end;
- _ -> false
- end;
- (_, Acc) -> Acc
- end,
- false, Els).
+-spec is_voice_request(message()) -> boolean().
+is_voice_request(Packet) ->
+ Els = xmpp:get_els(Packet),
+ lists:any(
+ fun(#xdata{} = X) ->
+ case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+ xmpp_util:get_xdata_values(<<"muc#role">>, X)} of
+ {[<<"http://jabber.org/protocol/muc#request">>],
+ [<<"participant">>]} ->
+ true;
+ _ ->
+ false
+ end;
+ (_) ->
+ false
+ end, Els).
+-spec prepare_request_form(jid(), binary(), binary()) -> message().
prepare_request_form(Requester, Nick, Lang) ->
- #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"normal">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
- children =
- [#xmlel{name = <<"title">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Voice request">>)}]},
- #xmlel{name = <<"instructions">>, attrs = [],
- children =
- [{xmlcdata,
- translate:translate(Lang,
- <<"Either approve or decline the voice "
- "request.">>)}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"FORM_TYPE">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"http://jabber.org/protocol/muc#request">>}]}]},
- #xmlel{name = <<"field">>,
- attrs =
- [{<<"var">>, <<"muc#role">>},
- {<<"type">>, <<"hidden">>}],
- children =
- [#xmlel{name = <<"value">>, attrs = [],
- children =
- [{xmlcdata,
- <<"participant">>}]}]},
- ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
- (jid:to_string(Requester))),
- ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>,
- Nick),
- ?BOOLXFIELD(<<"Grant voice to this person?">>,
- <<"muc#request_allow">>,
- (jlib:binary_to_atom(<<"false">>)))]}]}.
-
-send_voice_request(From, StateData) ->
+ Title = translate:translate(Lang, <<"Voice request">>),
+ Instruction = translate:translate(
+ Lang, <<"Either approve or decline the voice request.">>),
+ Fs = [#xdata_field{var = <<"FORM_TYPE">>,
+ type = hidden,
+ values = [<<"http://jabber.org/protocol/muc#request">>]},
+ #xdata_field{var = <<"muc#role">>,
+ type = hidden,
+ values = [<<"participant">>]},
+ ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
+ jid:to_string(Requester)),
+ ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, Nick),
+ ?BOOLXFIELD(<<"Grant voice to this person?">>,
+ <<"muc#request_allow">>, false)],
+ #message{type = normal,
+ sub_els = [#xdata{type = form,
+ title = Title,
+ instructions = [Instruction],
+ fields = Fs}]}.
+
+-spec send_voice_request(jid(), binary(), state()) -> ok.
+send_voice_request(From, Lang, StateData) ->
Moderators = search_role(moderator, StateData),
FromNick = find_nick_by_jid(From, StateData),
lists:foreach(fun ({_, User}) ->
ejabberd_router:route(
StateData#state.jid, User#user.jid,
- prepare_request_form(From, FromNick, <<"">>))
+ prepare_request_form(From, FromNick, Lang))
end,
Moderators).
-is_voice_approvement(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
- El,
- false) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_XDATA ->
- case jlib:parse_xdata_submit(El) of
- [_ | _] = Fs ->
- case {lists:keysearch(<<"FORM_TYPE">>, 1,
- Fs),
- lists:keysearch(<<"muc#role">>, 1,
- Fs),
- lists:keysearch(<<"muc#request_allow">>,
- 1, Fs)}
- of
- {{value,
- {_,
- [<<"http://jabber.org/protocol/muc#request">>]}},
- {value, {_, [<<"participant">>]}},
- {value, {_, [Flag]}}}
- when Flag == <<"true">>;
- Flag == <<"1">> ->
- true;
- _ -> false
- end;
- _ -> false
- end;
- _ -> false
- end;
- (_, Acc) -> Acc
- end,
- false, Els).
-
-extract_jid_from_voice_approvement(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) ->
- Fields = case jlib:parse_xdata_submit(El) of
- invalid -> [];
- Res -> Res
- end,
- lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) ->
- case jid:from_string(JIDStr) of
- error -> error;
- J -> {ok, J}
- end;
- (_, Acc) -> Acc
- end,
- error, Fields);
- (_, Acc) -> Acc
- end,
- error, Els).
+-spec is_voice_approvement(message()) -> boolean().
+is_voice_approvement(Packet) ->
+ Els = xmpp:get_els(Packet),
+ lists:any(
+ fun(#xdata{} = X) ->
+ case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+ xmpp_util:get_xdata_values(<<"muc#role">>, X),
+ xmpp_util:get_xdata_values(<<"muc#request_allow">>, X)} of
+ {[<<"http://jabber.org/protocol/muc#request">>],
+ [<<"participant">>], [Flag]} when Flag == <<"true">>;
+ Flag == <<"1">> ->
+ true;
+ _ ->
+ false
+ end;
+ (_) ->
+ false
+ end, Els).
+
+-spec extract_jid_from_voice_approvement(message()) -> jid() | error.
+extract_jid_from_voice_approvement(Packet) ->
+ Els = xmpp:get_els(Packet),
+ lists:foldl(
+ fun(#xdata{} = X, error) ->
+ case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+ xmpp_util:get_xdata_values(<<"muc#role">>, X),
+ xmpp_util:get_xdata_values(<<"muc#request_allow">>, X),
+ xmpp_util:get_xdata_values(<<"muc#jid">>, X)} of
+ {[<<"http://jabber.org/protocol/muc#request">>],
+ [<<"participant">>], [Flag], [J]} when Flag == <<"true">>;
+ Flag == <<"1">> ->
+ jid:from_string(J);
+ _ ->
+ error
+ end;
+ (_, Acc) ->
+ Acc
+ end, error, Els).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Invitation support
-is_invitation(Els) ->
- lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
- El,
- false) ->
- case fxml:get_attr_s(<<"xmlns">>, Attrs) of
- ?NS_MUC_USER ->
- case fxml:get_subtag(El, <<"invite">>) of
- false -> false;
- _ -> true
- end;
- _ -> false
- end;
- (_, Acc) -> Acc
- end,
- false, Els).
-
-check_invitation(From, Packet, Lang, StateData) ->
+-spec is_invitation(message()) -> boolean().
+is_invitation(Packet) ->
+ Els = xmpp:get_els(Packet),
+ lists:any(
+ fun(#muc_user{invites = [_|_]}) -> true;
+ (_) -> false
+ end, Els).
+
+-spec check_invitation(jid(), message(), state()) -> {error, error()} | jid().
+check_invitation(From, Packet, StateData) ->
+ Lang = xmpp:get_lang(Packet),
FAffiliation = get_affiliation(From, StateData),
- CanInvite =
- (StateData#state.config)#config.allow_user_invites
- orelse
- FAffiliation == admin orelse FAffiliation == owner,
- InviteEl = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of
- false ->
- Txt1 = <<"No 'x' element found">>,
- throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)});
- XEl ->
- case fxml:get_subtag(XEl, <<"invite">>) of
- false ->
- Txt2 = <<"No 'invite' element found">>,
- throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)});
- InviteEl1 ->
- InviteEl1
- end
- end,
- JID = case
- jid:from_string(fxml:get_tag_attr_s(<<"to">>,
- InviteEl))
- of
- error ->
- Txt = <<"Incorrect value of 'to' attribute">>,
- throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
- JID1 -> JID1
- end,
+ CanInvite = (StateData#state.config)#config.allow_user_invites
+ orelse
+ FAffiliation == admin orelse FAffiliation == owner,
case CanInvite of
- false ->
- Txt3 = <<"Invitations are not allowed in this conference">>,
- throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)});
- true ->
- Reason = fxml:get_path_s(InviteEl,
- [{elem, <<"reason">>}, cdata]),
- ContinueEl = case fxml:get_path_s(InviteEl,
- [{elem, <<"continue">>}])
- of
- <<>> -> [];
- Continue1 -> [Continue1]
- end,
- IEl = [#xmlel{name = <<"invite">>,
- attrs = [{<<"from">>, jid:to_string(From)}],
- children =
- [#xmlel{name = <<"reason">>, attrs = [],
- children = [{xmlcdata, Reason}]}]
- ++ ContinueEl}],
- PasswdEl = case
- (StateData#state.config)#config.password_protected
- of
- true ->
- [#xmlel{name = <<"password">>, attrs = [],
- children =
- [{xmlcdata,
- (StateData#state.config)#config.password}]}];
- _ -> []
- end,
- Body = #xmlel{name = <<"body">>, attrs = [],
- children =
- [{xmlcdata,
- iolist_to_binary(
- [io_lib:format(
- translate:translate(
- Lang,
- <<"~s invites you to the room ~s">>),
- [jid:to_string(From),
- jid:to_string({StateData#state.room,
- StateData#state.host,
- <<"">>})]),
- case
- (StateData#state.config)#config.password_protected
- of
+ false ->
+ Txt = <<"Invitations are not allowed in this conference">>,
+ {error, xmpp:err_not_allowed(Txt, Lang)};
+ true ->
+ case xmpp:get_subtag(Packet, #muc_user{}) of
+ #muc_user{invites = [#muc_invite{to = undefined}]} ->
+ Txt = <<"No 'to' attribute found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ #muc_user{invites = [#muc_invite{to = JID, reason = Reason} = I]} ->
+ Invite = I#muc_invite{to = undefined, from = From},
+ Password = case (StateData#state.config)#config.password_protected of
+ true ->
+ (StateData#state.config)#config.password;
+ false ->
+ undefined
+ end,
+ XUser = #muc_user{password = Password, invites = [Invite]},
+ XConference = #x_conference{jid = jid:make(StateData#state.room,
+ StateData#state.host),
+ reason = Reason},
+ Body = iolist_to_binary(
+ [io_lib:format(
+ translate:translate(
+ Lang,
+ <<"~s invites you to the room ~s">>),
+ [jid:to_string(From),
+ jid:to_string({StateData#state.room,
+ StateData#state.host,
+ <<"">>})]),
+ case (StateData#state.config)#config.password_protected of
true ->
<<", ",
- (translate:translate(Lang,
- <<"the password is">>))/binary,
+ (translate:translate(
+ Lang, <<"the password is">>))/binary,
" '",
((StateData#state.config)#config.password)/binary,
"'">>;
_ -> <<"">>
- end
- ,
- case Reason of
- <<"">> -> <<"">>;
- _ -> <<" (", Reason/binary, ") ">>
- end])}]},
- Msg = #xmlel{name = <<"message">>,
- attrs = [{<<"type">>, <<"normal">>}],
- children =
- [#xmlel{name = <<"x">>,
- attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
- children = IEl ++ PasswdEl},
- #xmlel{name = <<"x">>,
- attrs =
- [{<<"xmlns">>, ?NS_XCONFERENCE},
- {<<"jid">>,
- jid:to_string({StateData#state.room,
- StateData#state.host,
- <<"">>})}],
- children = [{xmlcdata, Reason}]},
- Body]},
- ejabberd_router:route(StateData#state.jid, JID, Msg),
- JID
+ end,
+ case Reason of
+ <<"">> -> <<"">>;
+ _ -> <<" (", Reason/binary, ") ">>
+ end]),
+ Msg = #message{type = normal,
+ body = xmpp:mk_text(Body),
+ sub_els = [XUser, XConference]},
+ ejabberd_router:route(StateData#state.jid, JID, Msg),
+ JID;
+ #muc_user{invites = [_|_]} ->
+ Txt = <<"Multiple <invite/> elements are not allowed">>,
+ {error, xmpp:err_forbidden(Txt, Lang)};
+ _ ->
+ Txt = <<"No <invite/> element found">>,
+ {error, xmpp:err_bad_request(Txt, Lang)}
+ end
end.
%% Handle a message sent to the room by a non-participant.
%% If it is a decline, send to the inviter.
%% Otherwise, an error message is sent to the sender.
-handle_roommessage_from_nonparticipant(Packet, Lang,
- StateData, From) ->
- case catch check_decline_invitation(Packet) of
- {true, Decline_data} ->
- send_decline_invitation(Decline_data,
- StateData#state.jid, From);
- _ ->
- send_error_only_occupants(Packet, Lang,
- StateData#state.jid, From)
+-spec handle_roommessage_from_nonparticipant(message(), state(), jid()) -> ok.
+handle_roommessage_from_nonparticipant(Packet, StateData, From) ->
+ case xmpp:get_subtag(Packet, #muc_user{}) of
+ #muc_user{decline = #muc_decline{to = #jid{} = To} = Decline} = XUser ->
+ NewDecline = Decline#muc_decline{to = undefined, from = From},
+ NewXUser = XUser#muc_user{decline = NewDecline},
+ NewPacket = xmpp:set_subtag(Packet, NewXUser),
+ ejabberd_router:route(StateData#state.jid, To, NewPacket);
+ _ ->
+ ErrText = <<"Only occupants are allowed to send messages "
+ "to the conference">>,
+ Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)),
+ ejabberd_router:route_error(StateData#state.jid, From, Packet, Err)
end.
-%% Check in the packet is a decline.
-%% If so, also returns the splitted packet.
-%% 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 = 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.
-%% The original stanza must be slightly modified.
-send_decline_invitation({Packet, XEl, DEl, ToJID},
- RoomJID, FromJID) ->
- FromString =
- jid:to_string(jid:remove_resource(FromJID)),
- #xmlel{name = <<"decline">>, attrs = DAttrs,
- children = DEls} =
- DEl,
- DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs),
- DAttrs3 = [{<<"from">>, FromString} | DAttrs2],
- DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3,
- children = DEls},
- XEl2 = replace_subelement(XEl, DEl2),
- Packet2 = replace_subelement(Packet, XEl2),
- ejabberd_router:route(RoomJID, ToJID, Packet2).
-
-%% 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},
- NewSubEl) ->
- {_, NameNewSubEl, _, _} = NewSubEl,
- SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
- #xmlel{name = Name, attrs = Attrs, children = SubEls2}.
-
-send_error_only_occupants(Packet, Lang, RoomJID, From) ->
- ErrText =
- <<"Only occupants are allowed to send messages "
- "to the conference">>,
- Err = jlib:make_error_reply(Packet,
- ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- ejabberd_router:route(RoomJID, From, Err).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Logging
@@ -5055,6 +4313,7 @@ add_to_log(Type, Data, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Users number checking
+-spec tab_add_online_user(jid(), state()) -> ok.
tab_add_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
@@ -5062,8 +4321,10 @@ tab_add_online_user(JID, StateData) ->
Host = StateData#state.host,
catch ets:insert(muc_online_users,
#muc_online_users{us = US, resource = LResource,
- room = Room, host = Host}).
+ room = Room, host = Host}),
+ ok.
+-spec tab_remove_online_user(jid(), state()) -> ok.
tab_remove_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
@@ -5071,8 +4332,10 @@ tab_remove_online_user(JID, StateData) ->
Host = StateData#state.host,
catch ets:delete_object(muc_online_users,
#muc_online_users{us = US, resource = LResource,
- room = Room, host = Host}).
+ room = Room, host = Host}),
+ ok.
+-spec tab_count_user(jid()) -> non_neg_integer().
tab_count_user(JID) ->
{LUser, LServer, _} = jid:tolower(JID),
US = {LUser, LServer},
@@ -5083,9 +4346,11 @@ tab_count_user(JID) ->
_ -> 0
end.
+-spec element_size(stanza()) -> non_neg_integer().
element_size(El) ->
- byte_size(fxml:element_to_binary(El)).
+ byte_size(fxml:element_to_binary(xmpp:encode(El))).
+-spec store_room(state()) -> ok.
store_room(StateData) ->
if (StateData#state.config)#config.persistent ->
mod_muc:store_room(StateData#state.server_host,
@@ -5095,6 +4360,7 @@ store_room(StateData) ->
ok
end.
+-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok.
send_wrapped(From, To, Packet, Node, State) ->
LTo = jid:tolower(To),
case ?DICT:find(LTo, State#state.users) of
@@ -5112,27 +4378,26 @@ send_wrapped(From, To, Packet, Node, State) ->
ejabberd_router:route(From, To, Packet)
end.
+-spec wrap(jid(), jid(), stanza(), binary()) -> message().
wrap(From, To, Packet, Node) ->
- Pkt1 = jlib:replace_from_to(From, To, Packet),
- Pkt2 = #xmlel{attrs = Attrs} = jlib:remove_attr(<<"xmlns">>, Pkt1),
- Pkt3 = Pkt2#xmlel{attrs = [{<<"xmlns">>, <<"jabber:client">>}|Attrs]},
- Item = #xmlel{name = <<"item">>,
- attrs = [{<<"id">>, randoms:get_string()}],
- children = [Pkt3]},
- Items = #xmlel{name = <<"items">>, attrs = [{<<"node">>, Node}],
- children = [Item]},
- Event = #xmlel{name = <<"event">>,
- attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
- children = [Items]},
- #xmlel{name = <<"message">>, children = [Event]}.
+ El = xmpp:encode(xmpp:set_from_to(Packet, From, To)),
+ #message{
+ sub_els = [#pubsub_event{
+ items = [#pubsub_event_items{
+ node = Node,
+ items = [#pubsub_event_item{
+ id = randoms:get_string(),
+ xml_els = [El]}]}]}]}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Multicast
+-spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok.
send_multiple(From, Server, Users, Packet) ->
JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
+-spec send_wrapped_multiple(jid(), [#user{}], stanza(), binary(), state()) -> ok.
send_wrapped_multiple(From, Users, Packet, Node, State) ->
lists:foreach(
fun({_, #user{jid = To}}) ->
@@ -5141,10 +4406,6 @@ send_wrapped_multiple(From, Users, Packet, Node, State) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaninful content
-
-has_body_or_subject(Packet) ->
- [] /= lists:dropwhile(fun
- (#xmlel{name = <<"body">>}) -> false;
- (#xmlel{name = <<"subject">>}) -> false;
- (_) -> true
- end, Packet#xmlel.children).
+-spec has_body_or_subject(message()) -> boolean().
+has_body_or_subject(#message{body = Body, subject = Subj}) ->
+ Body /= [] orelse Subj /= [].