aboutsummaryrefslogtreecommitdiff
path: root/src/mod_muc
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc')
-rw-r--r--src/mod_muc/Makefile.in2
-rw-r--r--src/mod_muc/mod_muc.erl1985
-rw-r--r--src/mod_muc/mod_muc_log.erl1661
-rw-r--r--src/mod_muc/mod_muc_room.erl7043
-rw-r--r--src/mod_muc/mod_muc_room.hrl146
5 files changed, 5667 insertions, 5170 deletions
diff --git a/src/mod_muc/Makefile.in b/src/mod_muc/Makefile.in
index 41315ad29..5ede5e521 100644
--- a/src/mod_muc/Makefile.in
+++ b/src/mod_muc/Makefile.in
@@ -14,7 +14,7 @@ EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
- EFLAGS+=+debug_info +export_all
+ EFLAGS+=+debug_info
endif
ifeq (@transient_supervisors@, false)
diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl
index 1f40ca0a4..09fe95bee 100644
--- a/src/mod_muc/mod_muc.erl
+++ b/src/mod_muc/mod_muc.erl
@@ -25,147 +25,131 @@
%%%----------------------------------------------------------------------
-module(mod_muc).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
--export([start_link/2,
- start/2,
- stop/1,
- room_destroyed/4,
- store_room/4,
- restore_room/3,
- forget_room/3,
- create_room/5,
- process_iq_disco_items/4,
- broadcast_service_message/2,
- register_room/3,
- node_up/1,
- node_down/1,
- migrate/3,
- get_vh_rooms/1,
- is_broadcasted/1,
- moderate_room_history/2,
- persist_recent_messages/1,
- can_use_nick/4]).
+-export([start_link/2, start/2, stop/1, export/1,
+ room_destroyed/4, store_room/4, restore_room/3,
+ forget_room/3, create_room/5, process_iq_disco_items/4,
+ broadcast_service_message/2, register_room/3, node_up/1,
+ node_down/1, migrate/3, get_vh_rooms/1,
+ is_broadcasted/1, moderate_room_history/2,
+ persist_recent_messages/1, can_use_nick/4]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+-record(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} |
+ {'_', binary()},
+ opts = [] :: list() | '_'}).
+
+-record(muc_online_room,
+ {name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
+ pid = self() :: pid() | '$2' | '_'}).
--record(muc_room, {name_host, opts}).
--record(muc_online_room, {name_host, pid}).
--record(muc_registered, {us_host, nick}).
+-record(muc_registered,
+ {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
+ nick = <<"">> :: binary()}).
--record(state, {host,
- server_host,
- access,
- history_size,
- persist_history,
- default_room_opts,
- room_shaper}).
+-record(state,
+ {host = <<"">> :: binary(),
+ server_host = <<"">> :: binary(),
+ access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
+ history_size = 20 :: non_neg_integer(),
+ persist_history = false :: boolean(),
+ default_room_opts = [] :: list(),
+ room_shaper = none :: shaper:shaper()}).
-define(PROCNAME, ejabberd_mod_muc).
-%%====================================================================
-%% API
-%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+ gen_server:start_link({local, Proc}, ?MODULE,
+ [Host, Opts], []).
start(Host, Opts) ->
start_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- temporary,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
- %% if compiled with no transient supervisor, we need to manually shutdown
- %% the rooms to give them a chance to store persistent messages to DB
- Rooms = shutdown_rooms(Host),
+ Rooms = shutdown_rooms(Host),
stop_supervisor(Host),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc),
- {wait, Rooms}. %%wait for rooms shutdown before stopping ejabberd
+ {wait, Rooms}.
shutdown_rooms(Host) ->
- MyHost = gen_mod:get_module_opt_host(Host, mod_muc, "conference.@HOST@"),
+ MyHost = gen_mod:get_module_opt_host(Host, mod_muc,
+ <<"conference.@HOST@">>),
Rooms = mnesia:dirty_select(muc_online_room,
- [{#muc_online_room{name_host = '$1', pid = '$2'},
- [{'==', {element, 2, '$1'}, MyHost}],
- ['$2']}]),
- [Pid ! 'shutdown' || Pid <- Rooms],
+ [{#muc_online_room{name_host = '$1',
+ pid = '$2'},
+ [{'==', {element, 2, '$1'}, MyHost}],
+ ['$2']}]),
+ [Pid ! shutdown || Pid <- Rooms],
Rooms.
-%% Returns {RoomsPersisted, MessagesPersisted}
persist_recent_messages(Host) ->
- MyHost = gen_mod:get_module_opt_host(Host, mod_muc, "conference.@HOST@"),
+ MyHost = gen_mod:get_module_opt_host(Host, mod_muc,
+ <<"conference.@HOST@">>),
Rooms = mnesia:dirty_select(muc_online_room,
- [{#muc_online_room{name_host = '$1', pid = '$2'},
- [{'==', {element, 2, '$1'}, MyHost}],
- ['$2']}]),
- lists:foldl(fun(Pid, {NRooms, Messages}) ->
- case mod_muc_room:persist_recent_messages(Pid) of
- {ok, {persisted, N}} -> {NRooms +1, Messages +N};
- {ok, not_persistent} -> {NRooms, Messages}
- end end, {0, 0}, Rooms).
+ [{#muc_online_room{name_host = '$1',
+ pid = '$2'},
+ [{'==', {element, 2, '$1'}, MyHost}],
+ ['$2']}]),
+ lists:foldl(fun (Pid, {NRooms, Messages}) ->
+ case mod_muc_room:persist_recent_messages(Pid) of
+ {ok, {persisted, N}} -> {NRooms + 1, Messages + N};
+ {ok, not_persistent} -> {NRooms, Messages}
+ end
+ end,
+ {0, 0}, Rooms).
moderate_room_history(RoomStr, Nick) ->
- Room = jlib:string_to_jid(RoomStr),
- Name = Room#jid.luser,
- Host = Room#jid.lserver,
- case mnesia:dirty_read(muc_online_room, {Name, Host}) of
- [] ->
- {error, not_found};
- [R] ->
- Pid = R#muc_online_room.pid,
- mod_muc_room:moderate_room_history(Pid, Nick)
- end.
-
-%% This function is called by a room in three situations:
-%% A) The owner of the room destroyed it
-%% B) The only participant of a temporary room leaves it
-%% C) mod_muc:stop was called, and each room is being terminated
-%% In this case, the mod_muc process died before the room processes
-%% So the message sending must be catched
+ Room = jlib:string_to_jid(RoomStr),
+ Name = Room#jid.luser,
+ Host = Room#jid.lserver,
+ case mnesia:dirty_read(muc_online_room, {Name, Host}) of
+ [] -> {error, not_found};
+ [R] ->
+ Pid = R#muc_online_room.pid,
+ mod_muc_room:moderate_room_history(Pid, Nick)
+ end.
+
room_destroyed(Host, Room, Pid, ServerHost) ->
catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
- {room_destroyed, {Room, Host}, Pid},
+ {room_destroyed, {Room, Host}, Pid},
ok.
-%% @doc Create a room.
-%% If Opts = default, the default room options are used.
-%% Else use the passed options as defined in mod_muc_room.
create_room(Host, Name, From, Nick, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- RoomHost = gen_mod:get_module_opt_host(Host, ?MODULE, "conference.@HOST@"),
+ RoomHost = gen_mod:get_module_opt_host(Host, ?MODULE,
+ <<"conference.@HOST@">>),
Node = get_node({Name, RoomHost}),
- gen_server:call({Proc, Node}, {create, Name, From, Nick, Opts}).
+ gen_server:call({Proc, Node},
+ {create, Name, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) ->
LServer = jlib:nameprep(ServerHost),
- store_room(LServer, Host, Name, Opts, gen_mod:db_type(LServer, ?MODULE)).
+ store_room(LServer, Host, Name, Opts,
+ gen_mod:db_type(LServer, ?MODULE)).
store_room(_LServer, Host, Name, Opts, mnesia) ->
- F = fun() ->
+ F = fun () ->
mnesia:write(#muc_room{name_host = {Name, Host},
opts = Opts})
end,
@@ -174,143 +158,132 @@ store_room(LServer, Host, Name, Opts, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
SOpts = ejabberd_odbc:encode_term(Opts),
- F = fun() ->
- odbc_queries:update_t(
- "muc_room",
- ["name", "host", "opts"],
- [SName, SHost, SOpts],
- ["name='", SName, "' and host='", SHost, "'"])
+ F = fun () ->
+ odbc_queries:update_t(<<"muc_room">>,
+ [<<"name">>, <<"host">>, <<"opts">>],
+ [SName, SHost, SOpts],
+ [<<"name='">>, SName, <<"' and host='">>,
+ SHost, <<"'">>])
end,
ejabberd_odbc:sql_transaction(LServer, F).
restore_room(ServerHost, Host, Name) ->
LServer = jlib:nameprep(ServerHost),
- restore_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)).
+ restore_room(LServer, Host, Name,
+ gen_mod:db_type(LServer, ?MODULE)).
restore_room(_LServer, Host, Name, mnesia) ->
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
- [#muc_room{opts = Opts}] ->
- Opts;
- _ ->
- error
+ [#muc_room{opts = Opts}] -> Opts;
+ _ -> error
end;
restore_room(LServer, Host, Name, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select opts from muc_room where name='",
- SName, "' and host='", SHost, "';"]) of
- {selected, ["opts"], [{Opts}]} ->
- ejabberd_odbc:decode_term(Opts);
- _ ->
- error
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select opts from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>])
+ of
+ {selected, [<<"opts">>], [[Opts]]} ->
+ opts_to_binary(ejabberd_odbc:decode_term(Opts));
+ _ -> error
end.
forget_room(ServerHost, Host, Name) ->
LServer = jlib:nameprep(ServerHost),
- forget_room(LServer, Host, Name, gen_mod:db_type(LServer, ?MODULE)).
+ forget_room(LServer, Host, Name,
+ gen_mod:db_type(LServer, ?MODULE)).
forget_room(_LServer, Host, Name, mnesia) ->
- F = fun() ->
- mnesia:delete({muc_room, {Name, Host}})
+ F = fun () -> mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F);
forget_room(LServer, Host, Name, odbc) ->
SName = ejabberd_odbc:escape(Name),
SHost = ejabberd_odbc:escape(Host),
- F = fun() ->
- ejabberd_odbc:sql_query_t(
- ["delete from muc_room where name='",
- SName, "' and host='", SHost, "';"])
+ F = fun () ->
+ ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>,
+ SName, <<"' and host='">>, SHost,
+ <<"';">>])
end,
ejabberd_odbc:sql_transaction(LServer, F).
-process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
+process_iq_disco_items(Host, From, To,
+ #iq{lang = Lang} = IQ) ->
Rsm = jlib:rsm_decode(IQ),
Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_ITEMS}],
- iq_disco_items(Host, From, Lang, Rsm)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res)).
-
-can_use_nick(_ServerHost, _Host, _JID, "") ->
- false;
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
+ children = iq_disco_items(Host, From, Lang, Rsm)}]},
+ ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
+
+can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
can_use_nick(ServerHost, Host, JID, Nick) ->
LServer = jlib:nameprep(ServerHost),
- can_use_nick(LServer, Host, JID, Nick, gen_mod:db_type(LServer, ?MODULE)).
+ can_use_nick(LServer, Host, JID, Nick,
+ gen_mod:db_type(LServer, ?MODULE)).
can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
- case catch mnesia:dirty_select(
- muc_registered,
- [{#muc_registered{us_host = '$1',
- nick = Nick,
- _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}]) of
- {'EXIT', _Reason} ->
- true;
- [] ->
- true;
- [#muc_registered{us_host = {U, _Host}}] ->
- U == LUS
+ case catch mnesia:dirty_select(muc_registered,
+ [{#muc_registered{us_host = '$1',
+ nick = Nick, _ = '_'},
+ [{'==', {element, 2, '$1'}, Host}],
+ ['$_']}])
+ of
+ {'EXIT', _Reason} -> true;
+ [] -> true;
+ [#muc_registered{us_host = {U, _Host}}] -> U == LUS
end;
can_use_nick(LServer, Host, JID, Nick, odbc) ->
- SJID = jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(JID))),
+ SJID =
+ jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))),
SNick = ejabberd_odbc:escape(Nick),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select jid from muc_registered ",
- "where nick='", SNick, "' and host='",
- SHost, "';"]) of
- {selected, ["jid"], [{SJID1}]} ->
- SJID == SJID1;
- _ ->
- true
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select jid from muc_registered ">>,
+ <<"where nick='">>, SNick,
+ <<"' and host='">>, SHost, <<"';">>])
+ of
+ {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
+ _ -> true
end.
migrate(_Node, _UpOrDown, After) ->
- Rs = mnesia:dirty_select(
- muc_online_room,
- [{#muc_online_room{name_host = '$1', pid = '$2', _ = '_'},
- [],
- ['$$']}]),
- lists:foreach(
- fun([NameHost, Pid]) ->
- case get_node(NameHost) of
- Node when Node /= node() ->
- mod_muc_room:migrate(Pid, Node, random:uniform(After));
- _ ->
- ok
- end
- end, Rs).
+ Rs = mnesia:dirty_select(muc_online_room,
+ [{#muc_online_room{name_host = '$1', pid = '$2',
+ _ = '_'},
+ [], ['$$']}]),
+ lists:foreach(fun ([NameHost, Pid]) ->
+ case get_node(NameHost) of
+ Node when Node /= node() ->
+ mod_muc_room:migrate(Pid, Node,
+ random:uniform(After));
+ _ -> ok
+ end
+ end,
+ Rs).
node_up(_Node) ->
copy_rooms(mnesia:dirty_first(muc_online_room)).
node_down(Node) when Node == node() ->
copy_rooms(mnesia:dirty_first(muc_online_room));
-node_down(_) ->
- ok.
+node_down(_) -> ok.
-copy_rooms('$end_of_table') ->
- ok;
+copy_rooms('$end_of_table') -> ok;
copy_rooms(Key) ->
case mnesia:dirty_read(muc_online_room, Key) of
- [#muc_online_room{name_host = NameHost} = Room] ->
- case get_node_new(NameHost) of
- Node when node() /= Node ->
- rpc:cast(Node, mnesia, dirty_write, [Room]);
- _ ->
- ok
- end;
- _ ->
- ok
+ [#muc_online_room{name_host = NameHost} = Room] ->
+ case get_node_new(NameHost) of
+ Node when node() /= Node ->
+ rpc:cast(Node, mnesia, dirty_write, [Room]);
+ _ -> ok
+ end;
+ _ -> ok
end,
copy_rooms(mnesia:dirty_next(muc_online_room, Key)).
@@ -318,1015 +291,1031 @@ copy_rooms(Key) ->
%% gen_server callbacks
%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Host, Opts]) ->
- MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
+ MyHost = gen_mod:get_opt_host(Host, Opts,
+ <<"conference.@HOST@">>),
case gen_mod:db_type(Opts) of
- mnesia ->
- update_muc_online_table(),
- update_tables(MyHost),
- mnesia:create_table(muc_room,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, muc_room)}]),
- mnesia:create_table(muc_registered,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, muc_registered)}]),
- mnesia:add_table_index(muc_registered, nick);
- _ ->
- ok
+ mnesia ->
+ mnesia:create_table(muc_room,
+ [{disc_copies, [node()]},
+ {attributes, record_info(fields, muc_room)}]),
+ mnesia:create_table(muc_registered,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, muc_registered)}]),
+ update_tables(),
+ mnesia:add_table_index(muc_registered, nick);
+ _ -> ok
end,
mnesia:create_table(muc_online_room,
- [{ram_copies, [node()]},
- {local_content, true},
+ [{ram_copies, [node()]}, {local_content, true},
{attributes, record_info(fields, muc_online_room)}]),
- mnesia:add_table_copy(muc_online_room, node(), ram_copies),
- catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
+ mnesia:add_table_copy(muc_online_room, node(),
+ ram_copies),
+ catch ets:new(muc_online_users,
+ [bag, named_table, public, {keypos, 2}]),
mnesia:subscribe(system),
- Access = gen_mod:get_opt(access, Opts, all),
- AccessCreate = gen_mod:get_opt(access_create, Opts, all),
- AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
- AccessPersistent = gen_mod:get_opt(access_persistent, Opts, all),
- HistorySize = gen_mod:get_opt(history_size, Opts, 20),
- PersistHistory = gen_mod:get_opt(persist_history, Opts, false),
- DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
- RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
+ Access = gen_mod:get_opt(access, Opts,
+ fun(A) when is_atom(A) -> A end, all),
+ AccessCreate = gen_mod:get_opt(access_create, Opts,
+ fun(A) when is_atom(A) -> A end, all),
+ AccessAdmin = gen_mod:get_opt(access_admin, Opts,
+ fun(A) when is_atom(A) -> A end,
+ none),
+ AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
+ fun(A) when is_atom(A) -> A end,
+ all),
+ HistorySize = gen_mod:get_opt(history_size, Opts,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 20),
+ PersistHistory = gen_mod:get_opt(persist_history, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ false),
+ DefRoomOpts = gen_mod:get_opt(default_room_options, Opts,
+ fun(L) when is_list(L) -> L end,
+ []),
+ RoomShaper = gen_mod:get_opt(room_shaper, Opts,
+ fun(A) when is_atom(A) -> A end,
+ none),
ejabberd_router:register_route(MyHost),
ejabberd_hooks:add(node_up, ?MODULE, node_up, 100),
ejabberd_hooks:add(node_down, ?MODULE, node_down, 100),
- ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
+ ejabberd_hooks:add(node_hash_update, ?MODULE, migrate,
+ 100),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
- HistorySize,
- PersistHistory,
- RoomShaper),
- {ok, #state{host = MyHost,
- server_host = Host,
- access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
- default_room_opts = DefRoomOpts,
- history_size = HistorySize,
- persist_history = PersistHistory,
- room_shaper = RoomShaper}}.
-
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
+ HistorySize, PersistHistory, RoomShaper),
+ {ok,
+ #state{host = MyHost, server_host = Host,
+ access =
+ {Access, AccessCreate, AccessAdmin, AccessPersistent},
+ default_room_opts = DefRoomOpts,
+ history_size = HistorySize,
+ persist_history = PersistHistory,
+ room_shaper = RoomShaper}}.
+
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
-
-handle_call({create, Room, From, Nick, Opts},
- _From,
- #state{host = Host,
- server_host = ServerHost,
- access = Access,
- default_room_opts = DefOpts,
+handle_call({create, Room, From, Nick, Opts}, _From,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, default_room_opts = DefOpts,
history_size = HistorySize,
persist_history = PersistHistory,
- room_shaper = RoomShaper} = State) ->
+ room_shaper = RoomShaper} =
+ State) ->
?DEBUG("MUC: create new room '~s'~n", [Room]),
NewOpts = case Opts of
- default -> DefOpts;
- _ -> Opts
+ default -> DefOpts;
+ _ -> Opts
end,
- {ok, Pid} = mod_muc_room:start(
- Host, ServerHost, Access,
- Room, HistorySize, PersistHistory,
- RoomShaper, From,
- Nick, NewOpts),
+ {ok, Pid} = mod_muc_room:start(Host, ServerHost, Access,
+ Room, HistorySize, PersistHistory,
+ RoomShaper, From, Nick, NewOpts),
register_room(Host, Room, Pid),
{reply, ok, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
+handle_cast(_Msg, State) -> {noreply, State}.
+
handle_info({route, From, To, Packet},
- #state{host = Host,
- server_host = ServerHost,
- access = Access,
- default_room_opts = DefRoomOpts,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize,
persist_history = PersistHistory,
- room_shaper = RoomShaper} = State) ->
+ room_shaper = RoomShaper} =
+ State) ->
{U, S, _} = jlib:jid_tolower(To),
case get_node({U, S}) of
- Node when Node == node() ->
- case catch do_route(Host, ServerHost, Access, HistorySize, PersistHistory,
- RoomShaper, From, To, Packet, DefRoomOpts) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- _ ->
- ok
- end;
- Node ->
- Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME),
- {Proc, Node} ! {route, From, To, Packet}
+ Node when Node == node() ->
+ case catch do_route(Host, ServerHost, Access,
+ HistorySize, PersistHistory, RoomShaper, From, To,
+ Packet, DefRoomOpts)
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
+ end;
+ Node ->
+ Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME),
+ {Proc, Node} ! {route, From, To, Packet}
end,
{noreply, State};
handle_info({room_destroyed, RoomHost, Pid}, State) ->
- F = fun() ->
- mnesia:delete_object(#muc_online_room{name_host = RoomHost,
+ F = fun () ->
+ mnesia:delete_object(#muc_online_room{name_host =
+ RoomHost,
pid = Pid})
end,
mnesia:async_dirty(F),
case get_node_new(RoomHost) of
- Node when Node /= node() ->
- rpc:cast(Node, mnesia, dirty_delete_object,
- [#muc_online_room{name_host = RoomHost,
- pid = Pid}]);
- _ ->
- ok
+ Node when Node /= node() ->
+ rpc:cast(Node, mnesia, dirty_delete_object,
+ [#muc_online_room{name_host = RoomHost, pid = Pid}]);
+ _ -> ok
end,
{noreply, State};
-handle_info(_Info, State) ->
- {noreply, State}.
-
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: This function is called by a gen_server when it is about to
-%% terminate. It should be the opposite of Module:init/1 and do any necessary
-%% cleaning up. When it returns, the gen_server terminates with Reason.
-%% The return value is ignored.
-%%--------------------------------------------------------------------
+handle_info(_Info, State) -> {noreply, State}.
+
terminate(_Reason, State) ->
ejabberd_hooks:delete(node_up, ?MODULE, node_up, 100),
- ejabberd_hooks:delete(node_down, ?MODULE, node_down, 100),
- ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
+ ejabberd_hooks:delete(node_down, ?MODULE, node_down,
+ 100),
+ ejabberd_hooks:delete(node_hash_update, ?MODULE,
+ migrate, 100),
ejabberd_router:unregister_route(State#state.host),
ok.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
start_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
- ChildSpec =
- {Proc,
- {ejabberd_tmp_sup, start_link,
- [Proc, mod_muc_room]},
- permanent,
- infinity,
- supervisor,
- [ejabberd_tmp_sup]},
+ Proc = gen_mod:get_module_proc(Host,
+ ejabberd_mod_muc_sup),
+ ChildSpec = {Proc,
+ {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]},
+ permanent, infinity, supervisor, [ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop_supervisor(Host) ->
- Proc = gen_mod:get_module_proc(Host, ejabberd_mod_muc_sup),
+ Proc = gen_mod:get_module_proc(Host,
+ ejabberd_mod_muc_sup),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
-do_route(Host, ServerHost, Access, HistorySize, PersistHistory, RoomShaper,
- From, To, Packet, DefRoomOpts) ->
- {AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
+do_route(Host, ServerHost, Access, HistorySize,
+ PersistHistory, RoomShaper, From, To, Packet,
+ DefRoomOpts) ->
+ {AccessRoute, _AccessCreate, _AccessAdmin,
+ _AccessPersistent} =
+ Access,
case acl:match_rule(ServerHost, AccessRoute, From) of
- allow ->
- do_route1(Host, ServerHost, Access, HistorySize, PersistHistory, RoomShaper,
- From, To, Packet, DefRoomOpts);
- _ ->
- {xmlelement, _Name, Attrs, _Els} = Packet,
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Access denied by service policy",
- Err = jlib:make_error_reply(Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route_error(To, From, Err, Packet)
+ allow ->
+ do_route1(Host, ServerHost, Access, HistorySize,
+ PersistHistory, RoomShaper, From, To, Packet,
+ DefRoomOpts);
+ _ ->
+ #xmlel{attrs = Attrs} = Packet,
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Access denied by service policy">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang, ErrText)),
+ ejabberd_router:route_error(To, From, Err, Packet)
end.
-
-do_route1(Host, ServerHost, Access, HistorySize, PersistHistory, RoomShaper,
- From, To, Packet, DefRoomOpts) ->
- {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
+do_route1(Host, ServerHost, Access, HistorySize,
+ PersistHistory, RoomShaper, From, To, Packet,
+ DefRoomOpts) ->
+ {_AccessRoute, AccessCreate, AccessAdmin,
+ _AccessPersistent} =
+ Access,
{Room, _, Nick} = jlib:jid_tolower(To),
- {xmlelement, Name, Attrs, _Els} = Packet,
+ #xmlel{name = Name, attrs = Attrs} = Packet,
case Room of
- "" ->
- case Nick of
- "" ->
- case Name of
- "iq" ->
- case jlib:iq_query_info(Packet) of
- #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
- sub_el = _SubEl, lang = Lang} = IQ ->
- Info = ejabberd_hooks:run_fold(
- disco_info, ServerHost, [],
- [ServerHost, ?MODULE, "", ""]),
- Res = IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- iq_disco_info(Lang)
- ++Info}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = get,
- xmlns = ?NS_DISCO_ITEMS} = IQ ->
- spawn(?MODULE,
- process_iq_disco_items,
- [Host, From, To, IQ]);
- #iq{type = get,
- xmlns = ?NS_REGISTER = XMLNS,
- lang = Lang,
- sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- iq_get_register_info(
- ServerHost, Host, From, Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = set,
- xmlns = ?NS_REGISTER = XMLNS,
- lang = Lang,
- sub_el = SubEl} = IQ ->
- case process_iq_register_set(
- ServerHost, Host, From, SubEl, Lang) of
- {result, IQRes} ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- IQRes}]},
- ejabberd_router:route(
- To, From, jlib:iq_to_xml(Res));
- {error, Error} ->
- Err = jlib:make_error_reply(
- Packet, Error),
- ejabberd_router:route(
- To, From, Err)
- end;
- #iq{type = get,
- xmlns = ?NS_VCARD = XMLNS,
- lang = Lang,
- sub_el = _SubEl} = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "vCard",
- [{"xmlns", XMLNS}],
- iq_get_vcard(Lang)}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{type = get,
- xmlns = ?NS_MUC_UNIQUE
- } = IQ ->
- Res = IQ#iq{type = result,
- sub_el =
- [{xmlelement, "unique",
- [{"xmlns", ?NS_MUC_UNIQUE}],
- [iq_get_unique(From)]}]},
- ejabberd_router:route(To,
- From,
- jlib:iq_to_xml(Res));
- #iq{} ->
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_FEATURE_NOT_IMPLEMENTED),
- ejabberd_router:route(To, From, Err);
- _ ->
- ok
+ <<"">> ->
+ case Nick of
+ <<"">> ->
+ case Name of
+ <<"iq">> ->
+ case jlib:iq_query_info(Packet) of
+ #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
+ sub_el = _SubEl, lang = Lang} =
+ IQ ->
+ Info = ejabberd_hooks:run_fold(disco_info,
+ ServerHost, [],
+ [ServerHost, ?MODULE,
+ <<"">>, <<"">>]),
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ iq_disco_info(Lang) ++
+ Info}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
+ spawn(?MODULE, process_iq_disco_items,
+ [Host, From, To, IQ]);
+ #iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
+ lang = Lang, sub_el = _SubEl} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ iq_get_register_info(ServerHost,
+ Host,
+ From,
+ Lang)}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS,
+ lang = Lang, sub_el = SubEl} =
+ IQ ->
+ case process_iq_register_set(ServerHost, Host, From,
+ SubEl, Lang)
+ of
+ {result, IQRes} ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = IQRes}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ {error, Error} ->
+ Err = jlib:make_error_reply(Packet, Error),
+ ejabberd_router:route(To, From, Err)
end;
- "message" ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- case acl:match_rule(ServerHost, AccessAdmin, From) of
- allow ->
- Msg = xml:get_path_s(
- Packet,
- [{elem, "body"}, cdata]),
- broadcast_service_message(Host, Msg);
- _ ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Only service administrators "
- "are allowed to send service messages",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(
- To, From, Err)
- end
- end;
- "presence" ->
- ok
- end;
- _ ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- "result" ->
- ok;
+ #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
+ lang = Lang, sub_el = _SubEl} =
+ IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"vCard">>,
+ attrs =
+ [{<<"xmlns">>, XMLNS}],
+ children =
+ iq_get_vcard(Lang)}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
+ Res = IQ#iq{type = result,
+ sub_el =
+ [#xmlel{name = <<"unique">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_UNIQUE}],
+ children =
+ [iq_get_unique(From)]}]},
+ ejabberd_router:route(To, From,
+ jlib:iq_to_xml(Res));
+ #iq{} ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ ejabberd_router:route(To, From, Err);
+ _ -> ok
+ end;
+ <<"message">> ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
_ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(To, From, Err)
- end
- end;
- _ ->
- case mnesia:dirty_read(muc_online_room, {Room, Host}) of
- [] ->
- Type = xml:get_attr_s("type", Attrs),
- case {Name, Type} of
- {"presence", ""} ->
- case check_user_can_create_room(ServerHost,
- AccessCreate, From,
- Room) of
- true ->
- case start_new_room(
- Host, ServerHost, Access,
- Room, HistorySize, PersistHistory,
- RoomShaper, From,
- Nick, DefRoomOpts) of
- {ok, Pid} ->
- mod_muc_room:route(Pid, From, Nick, Packet),
- register_room(Host, Room, Pid),
- ok;
- _Err ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_INTERNAL_SERVER_ERROR),
- ejabberd_router:route(To, From, Err)
- end;
- false ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Room creation is denied by service policy",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
- ejabberd_router:route(To, From, Err)
+ case acl:match_rule(ServerHost, AccessAdmin, From)
+ of
+ allow ->
+ Msg = xml:get_path_s(Packet,
+ [{elem, <<"body">>},
+ cdata]),
+ broadcast_service_message(Host, Msg);
+ _ ->
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText =
+ <<"Only service administrators are allowed "
+ "to send service messages">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
+ ejabberd_router:route(To, From, Err)
+ end
+ end;
+ <<"presence">> -> ok
+ end;
+ _ ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"error">> -> ok;
+ <<"result">> -> ok;
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(To, From, Err)
+ end
+ end;
+ _ ->
+ case mnesia:dirty_read(muc_online_room, {Room, Host}) of
+ [] ->
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ case {Name, Type} of
+ {<<"presence">>, <<"">>} ->
+ case check_user_can_create_room(ServerHost,
+ AccessCreate, From, Room)
+ of
+ true ->
+ case start_new_room(Host, ServerHost, Access, Room,
+ HistorySize, PersistHistory,
+ RoomShaper, From, Nick,
+ DefRoomOpts)
+ of
+ {ok, Pid} ->
+ mod_muc_room:route(Pid, From, Nick, Packet),
+ register_room(Host, Room, Pid),
+ ok;
+ _Err ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_INTERNAL_SERVER_ERROR),
+ ejabberd_router:route(To, From, Err)
end;
- _ ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "Conference room does not exist",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
+ false ->
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText =
+ <<"Room creation is denied by service policy">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
ejabberd_router:route(To, From, Err)
- end;
- [R] ->
- Pid = R#muc_online_room.pid,
- ?DEBUG("MUC: send to process ~p~n", [Pid]),
- mod_muc_room:route(Pid, From, Nick, Packet),
- ok
- end
+ end;
+ _ ->
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ ErrText = <<"Conference room does not exist">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_ITEM_NOT_FOUND(Lang,
+ ErrText)),
+ ejabberd_router:route(To, From, Err)
+ end;
+ [R] ->
+ Pid = R#muc_online_room.pid,
+ ?DEBUG("MUC: send to process ~p~n", [Pid]),
+ mod_muc_room:route(Pid, From, Nick, Packet),
+ ok
+ end
end.
-check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) ->
+check_user_can_create_room(ServerHost, AccessCreate,
+ From, RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
- allow ->
- (length(RoomID) =< gen_mod:get_module_opt(ServerHost, ?MODULE,
- max_room_id, infinite));
- _ ->
- false
+ allow ->
+ byte_size(RoomID) =<
+ gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
+ fun(infinity) -> infinity;
+ (I) when is_integer(I), I>0 -> I
+ end, infinity);
+ _ -> false
end.
get_rooms(ServerHost, Host) ->
LServer = jlib:nameprep(ServerHost),
- get_rooms(LServer, Host, gen_mod:db_type(LServer, ?MODULE)).
+ get_rooms(LServer, Host,
+ gen_mod:db_type(LServer, ?MODULE)).
get_rooms(_LServer, Host, mnesia) ->
- case catch mnesia:dirty_select(
- muc_room, [{#muc_room{name_host = {'_', Host}, _ = '_'},
- [],
- ['$_']}]) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]),
- [];
- Rs ->
- Rs
+ case catch mnesia:dirty_select(muc_room,
+ [{#muc_room{name_host = {'_', Host},
+ _ = '_'},
+ [], ['$_']}])
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
+ Rs -> Rs
end;
get_rooms(LServer, Host, odbc) ->
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select name, opts from muc_room ",
- "where host='", SHost, "';"]) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]),
- [];
- {selected, ["name", "opts"], RoomOpts} ->
- lists:map(
- fun({Room, Opts}) ->
- #muc_room{name_host = {Room, Host},
- opts = ejabberd_odbc:decode_term(Opts)}
- end, RoomOpts)
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select name, opts from muc_room ">>,
+ <<"where host='">>, SHost, <<"';">>])
+ of
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
+ {selected, [<<"name">>, <<"opts">>], RoomOpts} ->
+ lists:map(fun ([Room, Opts]) ->
+ #muc_room{name_host = {Room, Host},
+ opts = opts_to_binary(
+ ejabberd_odbc:decode_term(Opts))}
+ end,
+ RoomOpts)
end.
-load_permanent_rooms(Host, ServerHost, Access, HistorySize, PersistHistory, RoomShaper) ->
- lists:foreach(
- fun(R) ->
- {Room, Host} = R#muc_room.name_host,
- case get_node({Room, Host}) of
- Node when Node == node() ->
- case mnesia:dirty_read(muc_online_room, {Room, Host}) of
- [] ->
- case get_room_state_if_broadcasted(
- {Room, Host}) of
- {ok, RoomState} ->
- mod_muc_room:start(
- normal_state, RoomState);
- error ->
- {ok, Pid} = mod_muc_room:start(
- Host,
- ServerHost,
- Access,
- Room,
- HistorySize,
- PersistHistory,
- RoomShaper,
- R#muc_room.opts),
- register_room(Host, Room, Pid);
- _ ->
- ok
- end;
- _ ->
- ok
- end;
- _ ->
- ok
- end
- end, get_rooms(ServerHost, Host)).
+load_permanent_rooms(Host, ServerHost, Access,
+ HistorySize, PersistHistory, RoomShaper) ->
+ lists:foreach(fun (R) ->
+ {Room, Host} = R#muc_room.name_host,
+ case get_node({Room, Host}) of
+ Node when Node == node() ->
+ case mnesia:dirty_read(muc_online_room,
+ {Room, Host})
+ of
+ [] ->
+ case get_room_state_if_broadcasted({Room,
+ Host})
+ of
+ {ok, RoomState} ->
+ mod_muc_room:start(normal_state,
+ RoomState);
+ error ->
+ {ok, Pid} = mod_muc_room:start(Host,
+ ServerHost,
+ Access,
+ Room,
+ HistorySize,
+ PersistHistory,
+ RoomShaper,
+ R#muc_room.opts),
+ register_room(Host, Room, Pid);
+ _ -> ok
+ end;
+ _ -> ok
+ end;
+ _ -> ok
+ end
+ end,
+ get_rooms(ServerHost, Host)).
start_new_room(Host, ServerHost, Access, Room,
- HistorySize, PersistHistory, RoomShaper, From,
- Nick, DefRoomOpts) ->
+ HistorySize, PersistHistory, RoomShaper, From, Nick,
+ DefRoomOpts) ->
case get_room_state_if_broadcasted({Room, Host}) of
- {ok, RoomState} ->
- ?DEBUG("MUC: restore room '~s' from other node~n", [Room]),
- mod_muc_room:start(normal_state, RoomState);
- error ->
- case restore_room(ServerHost, Room, Host) of
- error ->
- ?DEBUG("MUC: open new room '~s'~n", [Room]),
- mod_muc_room:start(Host, ServerHost, Access,
- Room, HistorySize, PersistHistory,
- RoomShaper, From,
- Nick, DefRoomOpts);
- Opts ->
- ?DEBUG("MUC: restore room '~s'~n", [Room]),
- mod_muc_room:start(Host, ServerHost, Access,
- Room, HistorySize, PersistHistory,
- RoomShaper, Opts)
- end
+ {ok, RoomState} ->
+ ?DEBUG("MUC: restore room '~s' from other node~n",
+ [Room]),
+ mod_muc_room:start(normal_state, RoomState);
+ error ->
+ case restore_room(ServerHost, Room, Host) of
+ error ->
+ ?DEBUG("MUC: open new room '~s'~n", [Room]),
+ mod_muc_room:start(Host, ServerHost, Access, Room,
+ HistorySize, PersistHistory, RoomShaper,
+ From, Nick, DefRoomOpts);
+ Opts ->
+ ?DEBUG("MUC: restore room '~s'~n", [Room]),
+ mod_muc_room:start(Host, ServerHost, Access, Room,
+ HistorySize, PersistHistory, RoomShaper,
+ Opts)
+ end
end.
register_room(Host, Room, Pid) ->
- F = fun() ->
- mnesia:write(#muc_online_room{name_host = {Room, Host},
- pid = Pid})
- end,
+ F = fun () ->
+ mnesia:write(#muc_online_room{name_host = {Room, Host},
+ pid = Pid})
+ end,
mnesia:async_dirty(F),
case get_node_new({Room, Host}) of
- Node when Node /= node() ->
- %% New node has just been added. But we may miss MUC records
- %% copy procedure, so we copy the MUC record manually just
- %% to make sure
- rpc:cast(Node, mnesia, dirty_write,
- [#muc_online_room{name_host = {Room, Host},
- pid = Pid}]),
- case get_node({Room, Host}) of
- Node when node() /= Node ->
- %% Migration to new node has completed, and seems like
- %% we missed it, so we migrate the MUC room pid manually.
- %% It is not a problem if we have already got migration
- %% notification: dups are just ignored by the MUC room pid.
- mod_muc_room:migrate(Pid, Node, 0);
- _ ->
- ok
- end;
- _ ->
- ok
+ Node when Node /= node() ->
+ rpc:cast(Node, mnesia, dirty_write,
+ [#muc_online_room{name_host = {Room, Host},
+ pid = Pid}]),
+ case get_node({Room, Host}) of
+ Node when node() /= Node ->
+ mod_muc_room:migrate(Pid, Node, 0);
+ _ -> ok
+ end;
+ _ -> ok
end.
iq_disco_info(Lang) ->
- [{xmlelement, "identity",
- [{"category", "conference"},
- {"type", "text"},
- {"name", translate:translate(Lang, "Chatrooms")}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
- {xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
- {xmlelement, "feature", [{"var", ?NS_MUC}], []},
- {xmlelement, "feature", [{"var", ?NS_MUC_UNIQUE}], []},
- {xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
- {xmlelement, "feature", [{"var", ?NS_RSM}], []},
- {xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
-
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"text">>},
+ {<<"name">>,
+ translate:translate(Lang, <<"Chatrooms">>)}],
+ children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MUC}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_RSM}], children = []},
+ #xmlel{name = <<"feature">>,
+ attrs = [{<<"var">>, ?NS_VCARD}], children = []}].
iq_disco_items(Host, From, Lang, none) ->
- lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
- case catch gen_fsm:sync_send_all_state_event(
- Pid, {get_disco_item, From, Lang}, 100) of
- {item, Desc} ->
- flush(),
- {true,
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string({Name, Host, ""})},
- {"name", Desc}], []}};
- _ ->
- false
+ lists:zf(fun (#muc_online_room{name_host =
+ {Name, _Host},
+ pid = Pid}) ->
+ case catch gen_fsm:sync_send_all_state_event(Pid,
+ {get_disco_item,
+ From, Lang},
+ 100)
+ of
+ {item, Desc} ->
+ flush(),
+ {true,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string({Name, Host,
+ <<"">>})},
+ {<<"name">>, Desc}],
+ children = []}};
+ _ -> false
end
- end, get_vh_rooms_all_nodes(Host));
-
+ end,
+ get_vh_rooms_all_nodes(Host));
iq_disco_items(Host, From, Lang, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
RsmOut = jlib:rsm_encode(RsmO),
- lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
- case catch gen_fsm:sync_send_all_state_event(
- Pid, {get_disco_item, From, Lang}, 100) of
- {item, Desc} ->
- flush(),
- {true,
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string({Name, Host, ""})},
- {"name", Desc}], []}};
- _ ->
- false
+ lists:zf(fun (#muc_online_room{name_host =
+ {Name, _Host},
+ pid = Pid}) ->
+ case catch gen_fsm:sync_send_all_state_event(Pid,
+ {get_disco_item,
+ From, Lang},
+ 100)
+ of
+ {item, Desc} ->
+ flush(),
+ {true,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string({Name, Host,
+ <<"">>})},
+ {<<"name">>, Desc}],
+ children = []}};
+ _ -> false
end
- end, Rooms) ++ RsmOut.
+ end,
+ Rooms)
+ ++ RsmOut.
-get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
+get_vh_rooms(Host,
+ #rsm_in{max = M, direction = Direction, id = I,
+ index = Index}) ->
AllRooms = get_vh_rooms_all_nodes(Host),
Count = erlang:length(AllRooms),
- L = get_vh_rooms_direction(Direction, I, Index, AllRooms),
- L2 = if
- Index == undefined andalso Direction == before ->
- lists:reverse(lists:sublist(lists:reverse(L), 1, M));
- Index == undefined ->
- lists:sublist(L, 1, M);
- Index > Count orelse Index < 0 ->
- [];
- true ->
- lists:sublist(L, Index+1, M)
+ L = get_vh_rooms_direction(Direction, I, Index,
+ AllRooms),
+ L2 = if Index == undefined andalso
+ Direction == before ->
+ lists:reverse(lists:sublist(lists:reverse(L), 1, M));
+ Index == undefined -> lists:sublist(L, 1, M);
+ Index > Count orelse Index < 0 -> [];
+ true -> lists:sublist(L, Index + 1, M)
end,
- if
- L2 == [] ->
- {L2, #rsm_out{count=Count}};
- true ->
- H = hd(L2),
- NewIndex = get_room_pos(H, AllRooms),
- T=lists:last(L2),
- {F, _}=H#muc_online_room.name_host,
- {Last, _}=T#muc_online_room.name_host,
- {L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
+ if L2 == [] -> {L2, #rsm_out{count = Count}};
+ true ->
+ H = hd(L2),
+ NewIndex = get_room_pos(H, AllRooms),
+ T = lists:last(L2),
+ {F, _} = H#muc_online_room.name_host,
+ {Last, _} = T#muc_online_room.name_host,
+ {L2,
+ #rsm_out{first = F, last = Last, count = Count,
+ index = NewIndex}}
end.
-get_vh_rooms_direction(_Direction, _I, Index, AllRooms) when Index =/= undefined ->
- AllRooms;
+get_vh_rooms_direction(_Direction, _I, Index, AllRooms)
+ when Index =/= undefined ->
+ AllRooms;
get_vh_rooms_direction(aft, I, _Index, AllRooms) ->
- {_Before, After} =
- lists:splitwith(
- fun(#muc_online_room{name_host = {Na, _}}) ->
- Na < I end, AllRooms),
+ {_Before, After} = lists:splitwith(fun
+ (#muc_online_room{name_host =
+ {Na, _}}) ->
+ Na < I
+ end,
+ AllRooms),
case After of
- [] -> [];
- [#muc_online_room{name_host = {I, _Host}} | AfterTail] -> AfterTail;
- _ -> After
+ [] -> [];
+ [#muc_online_room{name_host = {I, _Host}}
+ | AfterTail] ->
+ AfterTail;
+ _ -> After
end;
-get_vh_rooms_direction(before, I, _Index, AllRooms) when I =/= []->
- {Before, _} =
- lists:splitwith(
- fun(#muc_online_room{name_host = {Na, _}}) ->
- Na < I end, AllRooms),
+get_vh_rooms_direction(before, I, _Index, AllRooms)
+ when I =/= [] ->
+ {Before, _} = lists:splitwith(fun
+ (#muc_online_room{name_host = {Na, _}}) ->
+ Na < I
+ end,
+ AllRooms),
Before;
-get_vh_rooms_direction(_Direction, _I, _Index, AllRooms) ->
+get_vh_rooms_direction(_Direction, _I, _Index,
+ AllRooms) ->
AllRooms.
-%% @doc Return the position of desired room in the list of rooms.
-%% The room must exist in the list. The count starts in 0.
-%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
get_room_pos(Desired, Rooms) ->
get_room_pos(Desired, Rooms, 0).
+
get_room_pos(Desired, [HeadRoom | _], HeadPosition)
- when (Desired#muc_online_room.name_host ==
- HeadRoom#muc_online_room.name_host) ->
+ when Desired#muc_online_room.name_host ==
+ HeadRoom#muc_online_room.name_host ->
HeadPosition;
get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
get_room_pos(Desired, Rooms, HeadPosition + 1).
-flush() ->
- receive
- _ ->
- flush()
- after 0 ->
- ok
- end.
+flush() -> receive _ -> flush() after 0 -> ok end.
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
-
-%% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
-%% the requester JID, the local time and a random salt.
-%%
-%% "pseudo" because we don't verify that there is not a room
-%% with the returned Name already created, nor mark the generated Name
-%% as "already used". But in practice, it is unique enough. See
-%% http://xmpp.org/extensions/xep-0045.html#createroom-unique
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
+
iq_get_unique(From) ->
- {xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}.
+ {xmlcdata,
+ sha:sha(term_to_binary([From, now(),
+ randoms:get_string()]))}.
get_nick(ServerHost, Host, From) ->
LServer = jlib:nameprep(ServerHost),
- get_nick(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)).
+ get_nick(LServer, Host, From,
+ gen_mod:db_type(LServer, ?MODULE)).
get_nick(_LServer, Host, From, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
- case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of
- {'EXIT', _Reason} ->
- error;
- [] ->
- error;
- [#muc_registered{nick = Nick}] ->
- Nick
+ case catch mnesia:dirty_read(muc_registered,
+ {LUS, Host})
+ of
+ {'EXIT', _Reason} -> error;
+ [] -> error;
+ [#muc_registered{nick = Nick}] -> Nick
end;
get_nick(LServer, Host, From, odbc) ->
- SJID = ejabberd_odbc:escape(
- jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(From)))),
+ SJID =
+ ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
- case catch ejabberd_odbc:sql_query(
- LServer, ["select nick from muc_registered where "
- "jid='", SJID, "' and host='", SHost, "';"]) of
- {selected, ["nick"], [{Nick}]} ->
- Nick;
- _ ->
- error
+ case catch ejabberd_odbc:sql_query(LServer,
+ [<<"select nick from muc_registered where "
+ "jid='">>,
+ SJID, <<"' and host='">>, SHost,
+ <<"';">>])
+ of
+ {selected, [<<"nick">>], [[Nick]]} -> Nick;
+ _ -> error
end.
iq_get_register_info(ServerHost, Host, From, Lang) ->
- {Nick, Registered} =
- case get_nick(ServerHost, Host, From) of
- error ->
- {"", []};
- N ->
- {N, [{xmlelement, "registered", [], []}]}
- end,
+ {Nick, Registered} = case get_nick(ServerHost, Host,
+ From)
+ of
+ error -> {<<"">>, []};
+ N ->
+ {N,
+ [#xmlel{name = <<"registered">>, attrs = [],
+ children = []}]}
+ end,
Registered ++
- [{xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "You need a client that supports x:data to register the nickname")}]},
- {xmlelement, "x",
- [{"xmlns", ?NS_XDATA}],
- [{xmlelement, "title", [],
- [{xmlcdata,
- translate:translate(
- Lang, "Nickname Registration at ") ++ Host}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "Enter nickname you want to register")}]},
- ?XFIELD("text-single", "Nickname", "nick", Nick)]}].
+ [#xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"You need a client that supports x:data "
+ "to register the nickname">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs = [{<<"xmlns">>, ?NS_XDATA}],
+ children =
+ [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"Nickname Registration at ">>))/binary,
+ Host/binary>>}]},
+ #xmlel{name = <<"instructions">>, attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"Enter nickname you want to register">>)}]},
+ ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
+ Nick)]}].
set_nick(ServerHost, Host, From, Nick) ->
LServer = jlib:nameprep(ServerHost),
- set_nick(LServer, Host, From, Nick, gen_mod:db_type(LServer, ?MODULE)).
+ set_nick(LServer, Host, From, Nick,
+ gen_mod:db_type(LServer, ?MODULE)).
set_nick(_LServer, Host, From, Nick, mnesia) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
LUS = {LUser, LServer},
- F = fun() ->
+ F = fun () ->
case Nick of
- "" ->
- mnesia:delete({muc_registered, {LUS, Host}}),
- ok;
- _ ->
- Allow =
- case mnesia:select(
- muc_registered,
- [{#muc_registered{us_host = '$1',
- nick = Nick,
- _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}]) of
- [] ->
- true;
+ <<"">> ->
+ mnesia:delete({muc_registered, {LUS, Host}}), ok;
+ _ ->
+ Allow = case mnesia:select(muc_registered,
+ [{#muc_registered{us_host =
+ '$1',
+ nick = Nick,
+ _ = '_'},
+ [{'==', {element, 2, '$1'},
+ Host}],
+ ['$_']}])
+ of
+ [] -> true;
[#muc_registered{us_host = {U, _Host}}] ->
U == LUS
- end,
- if
- Allow ->
- mnesia:write(
- #muc_registered{us_host = {LUS, Host},
- nick = Nick}),
- ok;
- true ->
- false
- end
+ end,
+ if Allow ->
+ mnesia:write(#muc_registered{us_host = {LUS, Host},
+ nick = Nick}),
+ ok;
+ true -> false
+ end
end
end,
mnesia:transaction(F);
set_nick(LServer, Host, From, Nick, odbc) ->
- JID = jlib:jid_to_string(
- jlib:jid_tolower(
- jlib:jid_remove_resource(From))),
+ JID =
+ jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))),
SJID = ejabberd_odbc:escape(JID),
SNick = ejabberd_odbc:escape(Nick),
SHost = ejabberd_odbc:escape(Host),
- F = fun() ->
- case Nick of
- "" ->
- ejabberd_odbc:sql_query_t(
- ["delete from muc_registered where ",
- "jid='", SJID, "' and host='", Host, "';"]),
- ok;
- _ ->
- Allow =
- case ejabberd_odbc:sql_query_t(
- ["select jid from muc_registered ",
- "where nick='", SNick, "' and host='",
- SHost, "';"]) of
- {selected, ["jid"], [{J}]} ->
- J == JID;
- _ ->
- true
- end,
- if Allow ->
- odbc_queries:update_t(
- "muc_registered",
- ["jid", "host", "nick"],
- [SJID, SHost, SNick],
- ["jid='", SJID, "' and host='", SHost, "'"]),
- ok;
- true ->
- false
- end
- end
- end,
+ F = fun () ->
+ case Nick of
+ <<"">> ->
+ ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>,
+ <<"jid='">>, SJID,
+ <<"' and host='">>, Host,
+ <<"';">>]),
+ ok;
+ _ ->
+ Allow = case
+ ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>,
+ <<"where nick='">>,
+ SNick,
+ <<"' and host='">>,
+ SHost, <<"';">>])
+ of
+ {selected, [<<"jid">>], [[J]]} -> J == JID;
+ _ -> true
+ end,
+ if Allow ->
+ odbc_queries:update_t(<<"muc_registered">>,
+ [<<"jid">>, <<"host">>,
+ <<"nick">>],
+ [SJID, SHost, SNick],
+ [<<"jid='">>, SJID,
+ <<"' and host='">>, SHost,
+ <<"'">>]),
+ ok;
+ true -> false
+ end
+ end
+ end,
ejabberd_odbc:sql_transaction(LServer, F).
-iq_set_register_info(ServerHost, Host, From, Nick, Lang) ->
+iq_set_register_info(ServerHost, Host, From, Nick,
+ Lang) ->
case set_nick(ServerHost, Host, From, Nick) of
- {atomic, ok} ->
- {result, []};
- {atomic, false} ->
- ErrText = "That nickname is registered by another person",
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
- _ ->
- {error, ?ERR_INTERNAL_SERVER_ERROR}
+ {atomic, ok} -> {result, []};
+ {atomic, false} ->
+ ErrText = <<"That nickname is registered by another "
+ "person">>,
+ {error, ?ERRT_CONFLICT(Lang, ErrText)};
+ _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
end.
-process_iq_register_set(ServerHost, Host, From, SubEl, Lang) ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- case xml:get_subtag(SubEl, "remove") of
- false ->
- case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
- case {xml:get_tag_attr_s("xmlns", XEl),
- xml:get_tag_attr_s("type", XEl)} of
- {?NS_XDATA, "cancel"} ->
- {result, []};
- {?NS_XDATA, "submit"} ->
- XData = jlib:parse_xdata_submit(XEl),
- case XData of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- case lists:keysearch("nick", 1, XData) of
- {value, {_, [Nick]}} when Nick /= "" ->
- iq_set_register_info(ServerHost, Host,
- From, Nick, Lang);
- _ ->
- ErrText = "You must fill in field \"Nickname\" in the form",
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
- end
- end;
+process_iq_register_set(ServerHost, Host, From, SubEl,
+ Lang) ->
+ #xmlel{children = Els} = SubEl,
+ case xml:get_subtag(SubEl, <<"remove">>) of
+ false ->
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ xml:get_tag_attr_s(<<"type">>, XEl)}
+ of
+ {?NS_XDATA, <<"cancel">>} -> {result, []};
+ {?NS_XDATA, <<"submit">>} ->
+ XData = jlib:parse_xdata_submit(XEl),
+ case XData of
+ invalid -> {error, ?ERR_BAD_REQUEST};
_ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- _ ->
- iq_set_register_info(ServerHost, Host, From, "", Lang)
+ case lists:keysearch(<<"nick">>, 1, XData) of
+ {value, {_, [Nick]}} when Nick /= <<"">> ->
+ iq_set_register_info(ServerHost, Host, From,
+ Nick, Lang);
+ _ ->
+ ErrText =
+ <<"You must fill in field \"Nickname\" "
+ "in the form">>,
+ {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
+ end
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ _ ->
+ iq_set_register_info(ServerHost, Host, From, <<"">>,
+ Lang)
end.
iq_get_vcard(Lang) ->
- [{xmlelement, "FN", [],
- [{xmlcdata, "ejabberd/mod_muc"}]},
- {xmlelement, "URL", [],
- [{xmlcdata, ?EJABBERD_URI}]},
- {xmlelement, "DESC", [],
- [{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++
- "\nCopyright (c) 2003-2012 ProcessOne"}]}].
-
+ [#xmlel{name = <<"FN">>, attrs = [],
+ children = [{xmlcdata, <<"ejabberd/mod_muc">>}]},
+ #xmlel{name = <<"URL">>, attrs = [],
+ children = [{xmlcdata, ?EJABBERD_URI}]},
+ #xmlel{name = <<"DESC">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<(translate:translate(Lang,
+ <<"ejabberd MUC module">>))/binary,
+ "\nCopyright (c) 2003-2012 ProcessOne">>}]}].
broadcast_service_message(Host, Msg) ->
- lists:foreach(
- fun(#muc_online_room{pid = Pid}) ->
- gen_fsm:send_all_state_event(
- Pid, {service_message, Msg})
- end, get_vh_rooms_all_nodes(Host)).
+ lists:foreach(fun (#muc_online_room{pid = Pid}) ->
+ gen_fsm:send_all_state_event(Pid,
+ {service_message, Msg})
+ end,
+ get_vh_rooms_all_nodes(Host)).
get_vh_rooms_all_nodes(Host) ->
- Rooms = lists:foldl(
- fun(Node, Acc) when Node == node() ->
- get_vh_rooms(Host) ++ Acc;
- (Node, Acc) ->
- case catch rpc:call(Node, ?MODULE, get_vh_rooms,
- [Host], 5000) of
- Res when is_list(Res) ->
- Res ++ Acc;
- _ ->
- Acc
- end
- end, [], get_nodes(Host)),
+ Rooms = lists:foldl(fun (Node, Acc)
+ when Node == node() ->
+ get_vh_rooms(Host) ++ Acc;
+ (Node, Acc) ->
+ case catch rpc:call(Node, ?MODULE, get_vh_rooms,
+ [Host], 5000)
+ of
+ Res when is_list(Res) -> Res ++ Acc;
+ _ -> Acc
+ end
+ end,
+ [], get_nodes(Host)),
lists:ukeysort(#muc_online_room.name_host, Rooms).
get_vh_rooms(Host) ->
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
- [{'==', {element, 2, '$1'}, Host}],
- ['$_']}]).
-
-update_tables(Host) ->
- update_muc_room_table(Host),
- update_muc_registered_table(Host).
+ [{'==', {element, 2, '$1'}, Host}], ['$_']}]).
+
+opts_to_binary(Opts) ->
+ lists:map(
+ fun({title, Title}) ->
+ {title, iolist_to_binary(Title)};
+ ({description, Desc}) ->
+ {description, iolist_to_binary(Desc)};
+ ({password, Pass}) ->
+ {password, iolist_to_binary(Pass)};
+ ({subject, Subj}) ->
+ {subject, iolist_to_binary(Subj)};
+ ({subject_author, Author}) ->
+ {subject_author, iolist_to_binary(Author)};
+ ({affiliations, Affs}) ->
+ {affiliations, lists:map(
+ fun({{U, S, R}, Aff}) ->
+ NewAff =
+ case Aff of
+ {A, Reason} ->
+ {A, iolist_to_binary(Reason)};
+ _ ->
+ Aff
+ end,
+ {{iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(R)},
+ NewAff}
+ end, Affs)};
+ ({captcha_whitelist, CWList}) ->
+ {captcha_whitelist, lists:map(
+ fun({U, S, R}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(S),
+ iolist_to_binary(R)}
+ end, CWList)};
+ (Opt) ->
+ Opt
+ end, Opts).
+
+update_tables() ->
+ update_muc_online_table(),
+ update_muc_room_table(),
+ update_muc_registered_table().
update_muc_online_table() ->
- case catch mnesia:table_info(muc_online_room, local_content) of
- false ->
- mnesia:delete_table(muc_online_room);
- _ ->
- ok
+ case catch mnesia:table_info(muc_online_room,
+ local_content)
+ of
+ false -> mnesia:delete_table(muc_online_room);
+ _ -> ok
end.
-update_muc_room_table(Host) ->
+update_muc_room_table() ->
Fields = record_info(fields, muc_room),
case mnesia:table_info(muc_room, attributes) of
- Fields ->
- ok;
- [name, opts] ->
- ?INFO_MSG("Converting muc_room table from "
- "{name, opts} format", []),
- {atomic, ok} = mnesia:create_table(
- mod_muc_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, muc_room},
- {attributes, record_info(fields, muc_room)}]),
- mnesia:transform_table(muc_room, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_muc_tmp_table),
- mnesia:foldl(
- fun(#muc_room{name_host = Name} = R, _) ->
- mnesia:dirty_write(
- mod_muc_tmp_table,
- R#muc_room{name_host = {Name, Host}})
- end, ok, muc_room)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(muc_room),
- F2 = fun() ->
- mnesia:write_lock_table(muc_room),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_muc_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_muc_tmp_table);
- _ ->
- ?INFO_MSG("Recreating muc_room table", []),
- mnesia:transform_table(muc_room, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_room, Fields, set,
+ fun(#muc_room{name_host = {N, _}}) -> N end,
+ fun(#muc_room{name_host = {N, H},
+ opts = Opts} = R) ->
+ R#muc_room{name_host = {iolist_to_binary(N),
+ iolist_to_binary(H)},
+ opts = opts_to_binary(Opts)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_room table", []),
+ mnesia:transform_table(muc_room, ignore, Fields)
end.
-
-update_muc_registered_table(Host) ->
+update_muc_registered_table() ->
Fields = record_info(fields, muc_registered),
case mnesia:table_info(muc_registered, attributes) of
- Fields ->
- ok;
- [user, nick] ->
- ?INFO_MSG("Converting muc_registered table from "
- "{user, nick} format", []),
- {atomic, ok} = mnesia:create_table(
- mod_muc_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, muc_registered},
- {attributes, record_info(fields, muc_registered)}]),
- mnesia:del_table_index(muc_registered, nick),
- mnesia:transform_table(muc_registered, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(mod_muc_tmp_table),
- mnesia:foldl(
- fun(#muc_registered{us_host = US} = R, _) ->
- mnesia:dirty_write(
- mod_muc_tmp_table,
- R#muc_registered{us_host = {US, Host}})
- end, ok, muc_registered)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(muc_registered),
- F2 = fun() ->
- mnesia:write_lock_table(muc_registered),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, mod_muc_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(mod_muc_tmp_table);
- _ ->
- ?INFO_MSG("Recreating muc_registered table", []),
- mnesia:transform_table(muc_registered, ignore, Fields)
+ Fields ->
+ ejabberd_config:convert_table_to_binary(
+ muc_registered, Fields, set,
+ fun(#muc_registered{us_host = {_, H}}) -> H end,
+ fun(#muc_registered{us_host = {{U, S}, H},
+ nick = Nick} = R) ->
+ R#muc_registered{us_host = {{iolist_to_binary(U),
+ iolist_to_binary(S)},
+ iolist_to_binary(H)},
+ nick = iolist_to_binary(Nick)}
+ end);
+ _ ->
+ ?INFO_MSG("Recreating muc_registered table", []),
+ mnesia:transform_table(muc_registered, ignore, Fields)
end.
is_broadcasted(RoomHost) ->
- case ejabberd_config:get_local_option({domain_balancing, RoomHost}) of
- broadcast ->
- true;
- _ ->
- false
+ case ejabberd_router:get_domain_balancing(RoomHost) of
+ broadcast -> true;
+ _ -> false
end.
get_node({_, RoomHost} = Key) ->
case is_broadcasted(RoomHost) of
- true ->
- node();
- false ->
- ejabberd_cluster:get_node(Key)
+ true -> node();
+ false -> ejabberd_cluster:get_node(Key)
end;
-get_node(RoomHost) ->
- get_node({"", RoomHost}).
+get_node(RoomHost) -> get_node({<<"">>, RoomHost}).
get_node_new({_, RoomHost} = Key) ->
case is_broadcasted(RoomHost) of
- true ->
- node();
- false ->
- ejabberd_cluster:get_node_new(Key)
+ true -> node();
+ false -> ejabberd_cluster:get_node_new(Key)
end;
get_node_new(RoomHost) ->
- get_node_new({"", RoomHost}).
+ get_node_new({<<"">>, RoomHost}).
get_nodes(RoomHost) ->
case is_broadcasted(RoomHost) of
- true ->
- [node()];
- false ->
- ejabberd_cluster:get_nodes()
+ true -> [node()];
+ false -> ejabberd_cluster:get_nodes()
end.
get_room_state_if_broadcasted({Room, Host}) ->
case is_broadcasted(Host) of
- true ->
- lists:foldl(
- fun(_, {ok, StateData}) ->
- {ok, StateData};
- (Node, _) when Node /= node() ->
- case catch rpc:call(
- Node, mnesia, dirty_read,
- [muc_online_room, {Room, Host}], 5000) of
- [#muc_online_room{pid = Pid}] ->
- case catch gen_fsm:sync_send_all_state_event(
- Pid, get_state, 5000) of
- {ok, StateData} ->
- {ok, StateData};
- _ ->
- error
- end;
- _ ->
- error
- end;
- (_, Acc) ->
- Acc
- end, error, ejabberd_cluster:get_nodes());
- false ->
- error
+ true ->
+ lists:foldl(fun (_, {ok, StateData}) -> {ok, StateData};
+ (Node, _) when Node /= node() ->
+ case catch rpc:call(Node, mnesia, dirty_read,
+ [muc_online_room,
+ {Room, Host}],
+ 5000)
+ of
+ [#muc_online_room{pid = Pid}] ->
+ case catch
+ gen_fsm:sync_send_all_state_event(Pid,
+ get_state,
+ 5000)
+ of
+ {ok, StateData} -> {ok, StateData};
+ _ -> error
+ end;
+ _ -> error
+ end;
+ (_, Acc) -> Acc
+ end,
+ error, ejabberd_cluster:get_nodes());
+ false -> error
end.
+
+export(_Server) ->
+ [{muc_room,
+ fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
+ case str:suffix(Host, RoomHost) of
+ true ->
+ SName = ejabberd_odbc:escape(Name),
+ SRoomHost = ejabberd_odbc:escape(RoomHost),
+ SOpts = ejabberd_odbc:encode_term(Opts),
+ [[<<"delete from muc_room where name='">>, SName,
+ <<"' and host='">>, SRoomHost, <<"';">>],
+ [<<"insert into muc_room(name, host, opts) ",
+ "values (">>,
+ <<"'">>, SName, <<"', '">>, SRoomHost,
+ <<"', '">>, SOpts, <<"');">>]];
+ false ->
+ []
+ end
+ end},
+ {muc_registered,
+ fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
+ nick = Nick}) ->
+ case str:suffix(Host, RoomHost) of
+ true ->
+ SJID = ejabberd_odbc:escape(
+ jlib:jid_to_string(
+ jlib:make_jid(U, S, <<"">>))),
+ SNick = ejabberd_odbc:escape(Nick),
+ SRoomHost = ejabberd_odbc:escape(RoomHost),
+ [[<<"delete from muc_registered where jid='">>,
+ SJID, <<"' and host='">>, SRoomHost, <<"';">>],
+ [<<"insert into muc_registered(jid, host, "
+ "nick) values ('">>,
+ SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
+ <<"');">>]];
+ false ->
+ []
+ end
+ end}].
diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl
index 47d4a3c96..e06795ee3 100644
--- a/src/mod_muc/mod_muc_log.erl
+++ b/src/mod_muc/mod_muc_log.erl
@@ -25,66 +25,60 @@
%%%----------------------------------------------------------------------
-module(mod_muc_log).
+
-author('badlop@process-one.net').
-behaviour(gen_server).
+
-behaviour(gen_mod).
%% API
--export([start_link/2,
- start/2,
- stop/1,
- check_access_log/2,
- add_to_log/5]).
+-export([start_link/2, start/2, stop/1,
+ check_access_log/2, add_to_log/5]).
%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_muc_room.hrl").
-%% Copied from mod_muc/mod_muc.erl
--record(muc_online_room, {name_host, pid}).
+-record(muc_online_room, {name_host = {<<>>, <<>>} :: {binary(), binary()},
+ pid = self() :: pid()}).
-define(T(Text), translate:translate(Lang, Text)).
--define(PROCNAME, ejabberd_mod_muc_log).
--record(room, {jid, title, subject, subject_author, config}).
+-define(PROCNAME, ejabberd_mod_muc_log).
--record(logstate, {host,
- out_dir,
- dir_type,
- dir_name,
- file_format,
- css_file,
- access,
- lang,
- timezone,
- spam_prevention,
- top_link}).
+-record(room, {jid = <<"">> :: binary(),
+ title = <<"">> :: binary(),
+ subject = <<"">> :: binary(),
+ subject_author = <<"">> :: binary(),
+ config = [] :: list()}).
+
+-record(logstate, {host = <<"">> :: binary(),
+ out_dir = <<"">> :: binary(),
+ dir_type = subdirs :: subdirs | plain,
+ dir_name = room_jid :: room_jid| room_name,
+ file_format = html :: html | plaintext,
+ css_file = false :: false | binary(),
+ access :: atom(),
+ lang = <<"">> :: binary(),
+ timezone = local :: local | universal,
+ spam_prevention = true :: boolean(),
+ top_link = {<<>>, <<>>} :: {binary(), binary()}}).
-%%====================================================================
-%% API
-%%====================================================================
-%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = get_proc_name(Host),
gen_server:start_link(Proc, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = get_proc_name(Host),
- ChildSpec =
- {Proc,
- {?MODULE, start_link, [Host, Opts]},
- temporary,
- 1000,
- worker,
- [?MODULE]},
+ ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
+ temporary, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -98,837 +92,1019 @@ add_to_log(Host, Type, Data, Room, Opts) ->
check_access_log(Host, From) ->
case catch gen_server:call(get_proc_name(Host),
- {check_access_log, Host, From}) of
- {'EXIT', _Error} ->
- deny;
- Res ->
- Res
+ {check_access_log, Host, From})
+ of
+ {'EXIT', _Error} -> deny;
+ Res -> Res
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
-
-%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%% Description: Initiates the server
-%%--------------------------------------------------------------------
init([Host, Opts]) ->
- OutDir = gen_mod:get_opt(outdir, Opts, "www/muc"),
- DirType = gen_mod:get_opt(dirtype, Opts, subdirs),
- DirName = gen_mod:get_opt(dirname, Opts, room_jid),
- FileFormat = gen_mod:get_opt(file_format, Opts, html), % Allowed values: html|plaintext
- CSSFile = gen_mod:get_opt(cssfile, Opts, false),
- AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin),
- Timezone = gen_mod:get_opt(timezone, Opts, local),
- Top_link = gen_mod:get_opt(top_link, Opts, {"/", "Home"}),
- NoFollow = gen_mod:get_opt(spam_prevention, Opts, true),
- Lang = case ejabberd_config:get_local_option({language, Host}) of
- undefined ->
- case ejabberd_config:get_global_option(language) of
- undefined -> "en";
- L -> L
- end;
- L -> L
- end,
- {ok, #logstate{host = Host,
- out_dir = OutDir,
- dir_type = DirType,
- dir_name = DirName,
- file_format = FileFormat,
- css_file = CSSFile,
- access = AccessLog,
- lang = Lang,
- timezone = Timezone,
- spam_prevention = NoFollow,
- top_link = Top_link}}.
-
-%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
-%% Description: Handling call messages
-%%--------------------------------------------------------------------
+ OutDir = gen_mod:get_opt(outdir, Opts,
+ fun iolist_to_binary/1,
+ <<"www/muc">>),
+ DirType = gen_mod:get_opt(dirtype, Opts,
+ fun(subdirs) -> subdirs;
+ (plain) -> plain
+ end, subdirs),
+ DirName = gen_mod:get_opt(dirname, Opts,
+ fun(room_jid) -> room_jid;
+ (room_name) -> room_name
+ end, room_jid),
+ FileFormat = gen_mod:get_opt(file_format, Opts,
+ fun(html) -> html;
+ (plaintext) -> plaintext
+ end, html),
+ CSSFile = gen_mod:get_opt(cssfile, Opts,
+ fun iolist_to_binary/1,
+ false),
+ AccessLog = gen_mod:get_opt(access_log, Opts,
+ fun(A) when is_atom(A) -> A end,
+ muc_admin),
+ Timezone = gen_mod:get_opt(timezone, Opts,
+ fun(local) -> local;
+ (universal) -> universal
+ end, local),
+ Top_link = gen_mod:get_opt(top_link, Opts,
+ fun({S1, S2}) ->
+ {iolist_to_binary(S1),
+ iolist_to_binary(S2)}
+ end, {<<"/">>, <<"Home">>}),
+ NoFollow = gen_mod:get_opt(spam_prevention, Opts,
+ fun(B) when is_boolean(B) -> B end,
+ true),
+ Lang = ejabberd_config:get_local_option(
+ {language, Host},
+ fun iolist_to_binary/1,
+ ?MYLANG),
+ {ok,
+ #logstate{host = Host, out_dir = OutDir,
+ dir_type = DirType, dir_name = DirName,
+ file_format = FileFormat, css_file = CSSFile,
+ access = AccessLog, lang = Lang, timezone = Timezone,
+ spam_prevention = NoFollow, top_link = Top_link}}.
+
handle_call({check_access_log, ServerHost, FromJID}, _From, State) ->
Reply = acl:match_rule(ServerHost, State#logstate.access, FromJID),
{reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
-%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling cast messages
-%%--------------------------------------------------------------------
handle_cast({add_to_log, Type, Data, Room, Opts}, State) ->
case catch add_to_log2(Type, Data, Room, Opts, State) of
- {'EXIT', Reason} ->
- ?ERROR_MSG("~p", [Reason]);
- _ ->
- ok
+ {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
+ _ -> ok
end,
{noreply, State};
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
-%% Description: Handling all non call/cast messages
-%%--------------------------------------------------------------------
-handle_info(_Info, State) ->
- {noreply, State}.
-
-%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
-%% Description: This function is called by a gen_server when it is about to
-%% terminate. It should be the opposite of Module:init/1 and do any necessary
-%% cleaning up. When it returns, the gen_server terminates with Reason.
-%% The return value is ignored.
-%%--------------------------------------------------------------------
-terminate(_Reason, _State) ->
- ok.
+handle_cast(_Msg, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description: Convert process state when code is changed
-%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+handle_info(_Info, State) -> {noreply, State}.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
- case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of
- {false, false} ->
- ok;
- {false, SubEl} ->
- Message = {body, xml:get_tag_cdata(SubEl)},
- add_message_to_log(Nick, Message, Room, Opts, State);
- {SubEl, _} ->
- Message = {subject, xml:get_tag_cdata(SubEl)},
- add_message_to_log(Nick, Message, Room, Opts, State)
- end;
-
-add_to_log2(roomconfig_change, _Occupants, Room, Opts, State) ->
- add_message_to_log("", roomconfig_change, Room, Opts, State);
+terminate(_Reason, _State) -> ok.
-add_to_log2(roomconfig_change_enabledlogging, Occupants, Room, Opts, State) ->
- add_message_to_log("", {roomconfig_change, Occupants}, Room, Opts, State);
-
-add_to_log2(room_existence, NewStatus, Room, Opts, State) ->
- add_message_to_log("", {room_existence, NewStatus}, Room, Opts, State);
-
-add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) ->
- add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State);
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
+ case {xml:get_subtag(Packet, <<"subject">>),
+ xml:get_subtag(Packet, <<"body">>)}
+ of
+ {false, false} -> ok;
+ {false, SubEl} ->
+ Message = {body, xml:get_tag_cdata(SubEl)},
+ add_message_to_log(Nick, Message, Room, Opts, State);
+ {SubEl, _} ->
+ Message = {subject, xml:get_tag_cdata(SubEl)},
+ add_message_to_log(Nick, Message, Room, Opts, State)
+ end;
+add_to_log2(roomconfig_change, _Occupants, Room, Opts,
+ State) ->
+ add_message_to_log(<<"">>, roomconfig_change, Room,
+ Opts, State);
+add_to_log2(roomconfig_change_enabledlogging, Occupants,
+ Room, Opts, State) ->
+ add_message_to_log(<<"">>,
+ {roomconfig_change, Occupants}, Room, Opts, State);
+add_to_log2(room_existence, NewStatus, Room, Opts,
+ State) ->
+ add_message_to_log(<<"">>, {room_existence, NewStatus},
+ Room, Opts, State);
+add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts,
+ State) ->
+ add_message_to_log(NewNick, {nickchange, OldNick}, Room,
+ Opts, State);
add_to_log2(join, Nick, Room, Opts, State) ->
add_message_to_log(Nick, join, Room, Opts, State);
-
add_to_log2(leave, {Nick, Reason}, Room, Opts, State) ->
case Reason of
- "" -> add_message_to_log(Nick, leave, Room, Opts, State);
- _ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State)
+ <<"">> ->
+ add_message_to_log(Nick, leave, Room, Opts, State);
+ _ ->
+ add_message_to_log(Nick, {leave, Reason}, Room, Opts,
+ State)
end;
-
-add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) ->
- add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State).
-
+add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts,
+ State) ->
+ add_message_to_log(Nick, {kickban, Code, Reason}, Room,
+ Opts, State).
%%----------------------------------------------------------------------
%% Core
-build_filename_string(TimeStamp, OutDir, RoomJID, DirType, DirName, FileFormat) ->
+build_filename_string(TimeStamp, OutDir, RoomJID,
+ DirType, DirName, FileFormat) ->
{{Year, Month, Day}, _Time} = TimeStamp,
-
- %% Directory and file names
- {Dir, Filename, Rel} =
- case DirType of
- subdirs ->
- SYear = lists:flatten(io_lib:format("~4..0w", [Year])),
- SMonth = lists:flatten(io_lib:format("~2..0w", [Month])),
- SDay = lists:flatten(io_lib:format("~2..0w", [Day])),
- {filename:join(SYear, SMonth), SDay, "../.."};
- plain ->
- Date = lists:flatten(
- io_lib:format("~4..0w-~2..0w-~2..0w",
- [Year, Month, Day])),
- {"", Date, "."}
- end,
-
+ {Dir, Filename, Rel} = case DirType of
+ subdirs ->
+ SYear =
+ iolist_to_binary(io_lib:format("~4..0w",
+ [Year])),
+ SMonth =
+ iolist_to_binary(io_lib:format("~2..0w",
+ [Month])),
+ SDay = iolist_to_binary(io_lib:format("~2..0w",
+ [Day])),
+ {fjoin([SYear, SMonth]), SDay,
+ <<"../..">>};
+ plain ->
+ Date =
+ iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w",
+ [Year,
+ Month,
+ Day])),
+ {<<"">>, Date, <<".">>}
+ end,
RoomString = case DirName of
- room_jid -> RoomJID;
- room_name -> get_room_name(RoomJID)
+ room_jid -> RoomJID;
+ room_name -> get_room_name(RoomJID)
end,
Extension = case FileFormat of
- html -> ".html";
- plaintext -> ".txt"
+ html -> <<".html">>;
+ plaintext -> <<".txt">>
end,
- Fd = filename:join([OutDir, RoomString, Dir]),
- Fn = filename:join([Fd, Filename ++ Extension]),
- Fnrel = filename:join([Rel, Dir, Filename ++ Extension]),
+ Fd = fjoin([OutDir, RoomString, Dir]),
+ Fn = fjoin([Fd, <<Filename/binary, Extension/binary>>]),
+ Fnrel = fjoin([Rel, Dir, <<Filename/binary, Extension/binary>>]),
{Fd, Fn, Fnrel}.
get_room_name(RoomJID) ->
- JID = jlib:string_to_jid(RoomJID),
- JID#jid.user.
+ JID = jlib:string_to_jid(RoomJID), JID#jid.user.
-%% calculate day before
get_timestamp_daydiff(TimeStamp, Daydiff) ->
{Date1, HMS} = TimeStamp,
- Date2 = calendar:gregorian_days_to_date(
- calendar:date_to_gregorian_days(Date1) + Daydiff),
+ Date2 =
+ calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(Date1)
+ + Daydiff),
{Date2, HMS}.
-%% Try to close the previous day log, if it exists
close_previous_log(Fn, Images_dir, FileFormat) ->
case file:read_file_info(Fn) of
- {ok, _} ->
- {ok, F} = file:open(Fn, [append]),
- write_last_lines(F, Images_dir, FileFormat),
- file:close(F);
- _ -> ok
+ {ok, _} ->
+ {ok, F} = file:open(Fn, [append]),
+ write_last_lines(F, Images_dir, FileFormat),
+ file:close(F);
+ _ -> ok
end.
-write_last_lines(_, _, plaintext) ->
- ok;
+write_last_lines(_, _, plaintext) -> ok;
write_last_lines(F, Images_dir, _FileFormat) ->
- %%fw(F, "<div class=\"legend\">ejabberd/mod_muc log<span class=\"w3c\">"),
- fw(F, "<div class=\"legend\">"),
- fw(F, " <a href=\"http://www.ejabberd.im\"><img style=\"border:0\" src=\"~s/powered-by-ejabberd.png\" alt=\"Powered by ejabberd\"/></a>", [Images_dir]),
- fw(F, " <a href=\"http://www.erlang.org/\"><img style=\"border:0\" src=\"~s/powered-by-erlang.png\" alt=\"Powered by Erlang\"/></a>", [Images_dir]),
- fw(F, "<span class=\"w3c\">"),
- fw(F, " <a href=\"http://validator.w3.org/check?uri=referer\"><img style=\"border:0;width:88px;height:31px\" src=\"~s/valid-xhtml10.png\" alt=\"Valid XHTML 1.0 Transitional\" /></a>", [Images_dir]),
- fw(F, " <a href=\"http://jigsaw.w3.org/css-validator/\"><img style=\"border:0;width:88px;height:31px\" src=\"~s/vcss.png\" alt=\"Valid CSS!\"/></a>", [Images_dir]),
- fw(F, "</span></div></body></html>").
-
-add_message_to_log(Nick1, Message, RoomJID, Opts, State) ->
- #logstate{out_dir = OutDir,
- dir_type = DirType,
- dir_name = DirName,
- file_format = FileFormat,
- css_file = CSSFile,
- lang = Lang,
- timezone = Timezone,
- spam_prevention = NoFollow,
- top_link = TopLink} = State,
+ fw(F, <<"<div class=\"legend\">">>),
+ fw(F,
+ <<" <a href=\"http://www.ejabberd.im\"><img "
+ "style=\"border:0\" src=\"~s/powered-by-ejabbe"
+ "rd.png\" alt=\"Powered by ejabberd\"/></a>">>,
+ [Images_dir]),
+ fw(F,
+ <<" <a href=\"http://www.erlang.org/\"><img "
+ "style=\"border:0\" src=\"~s/powered-by-erlang"
+ ".png\" alt=\"Powered by Erlang\"/></a>">>,
+ [Images_dir]),
+ fw(F, <<"<span class=\"w3c\">">>),
+ fw(F,
+ <<" <a href=\"http://validator.w3.org/check?uri"
+ "=referer\"><img style=\"border:0;width:88px;h"
+ "eight:31px\" src=\"~s/valid-xhtml10.png\" "
+ "alt=\"Valid XHTML 1.0 Transitional\" "
+ "/></a>">>,
+ [Images_dir]),
+ fw(F,
+ <<" <a href=\"http://jigsaw.w3.org/css-validato"
+ "r/\"><img style=\"border:0;width:88px;height:"
+ "31px\" src=\"~s/vcss.png\" alt=\"Valid "
+ "CSS!\"/></a>">>,
+ [Images_dir]),
+ fw(F, <<"</span></div></body></html>">>).
+
+add_message_to_log(Nick1, Message, RoomJID, Opts,
+ State) ->
+ #logstate{out_dir = OutDir, dir_type = DirType,
+ dir_name = DirName, file_format = FileFormat,
+ css_file = CSSFile, lang = Lang, timezone = Timezone,
+ spam_prevention = NoFollow, top_link = TopLink} =
+ State,
Room = get_room_info(RoomJID, Opts),
Nick = htmlize(Nick1, FileFormat),
- Nick2 = htmlize("<"++Nick1++">", FileFormat),
+ Nick2 = htmlize(<<"<", Nick1/binary, ">">>, FileFormat),
Now = now(),
TimeStamp = case Timezone of
- local -> calendar:now_to_local_time(Now);
- universal -> calendar:now_to_universal_time(Now)
+ local -> calendar:now_to_local_time(Now);
+ universal -> calendar:now_to_universal_time(Now)
end,
- {Fd, Fn, _Dir} = build_filename_string(TimeStamp, OutDir, Room#room.jid, DirType, DirName, FileFormat),
+ {Fd, Fn, _Dir} = build_filename_string(TimeStamp,
+ OutDir, Room#room.jid, DirType,
+ DirName, FileFormat),
{Date, Time} = TimeStamp,
-
- %% Open file, create if it does not exist, create parent dirs if needed
case file:read_file_info(Fn) of
- {ok, _} ->
- {ok, F} = file:open(Fn, [append]);
- {error, enoent} ->
- make_dir_rec(Fd),
- {ok, F} = file:open(Fn, [append]),
- Datestring = get_dateweek(Date, Lang),
-
- TimeStampYesterday = get_timestamp_daydiff(TimeStamp, -1),
- {_FdYesterday, FnYesterday, DatePrev} =
- build_filename_string(
- TimeStampYesterday, OutDir, Room#room.jid, DirType, DirName, FileFormat),
-
- TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1),
- {_FdTomorrow, _FnTomorrow, DateNext} =
- build_filename_string(
- TimeStampTomorrow, OutDir, Room#room.jid, DirType, DirName, FileFormat),
-
- HourOffset = calc_hour_offset(TimeStamp),
- put_header(F, Room, Datestring, CSSFile, Lang,
- HourOffset, DatePrev, DateNext, TopLink, FileFormat),
-
- Images_dir = filename:join([OutDir, "images"]),
- file:make_dir(Images_dir),
- create_image_files(Images_dir),
- Images_url = case DirType of
- subdirs -> "../../../images";
- plain -> "../images"
- end,
- close_previous_log(FnYesterday, Images_url, FileFormat)
+ {ok, _} -> {ok, F} = file:open(Fn, [append]);
+ {error, enoent} ->
+ make_dir_rec(Fd),
+ {ok, F} = file:open(Fn, [append]),
+ Datestring = get_dateweek(Date, Lang),
+ TimeStampYesterday = get_timestamp_daydiff(TimeStamp,
+ -1),
+ {_FdYesterday, FnYesterday, DatePrev} =
+ build_filename_string(TimeStampYesterday, OutDir,
+ Room#room.jid, DirType, DirName,
+ FileFormat),
+ TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1),
+ {_FdTomorrow, _FnTomorrow, DateNext} =
+ build_filename_string(TimeStampTomorrow, OutDir,
+ Room#room.jid, DirType, DirName,
+ FileFormat),
+ HourOffset = calc_hour_offset(TimeStamp),
+ put_header(F, Room, Datestring, CSSFile, Lang,
+ HourOffset, DatePrev, DateNext, TopLink, FileFormat),
+ Images_dir = fjoin([OutDir, <<"images">>]),
+ file:make_dir(Images_dir),
+ create_image_files(Images_dir),
+ Images_url = case DirType of
+ subdirs -> <<"../../../images">>;
+ plain -> <<"../images">>
+ end,
+ close_previous_log(FnYesterday, Images_url, FileFormat)
end,
-
- %% Build message
Text = case Message of
- roomconfig_change ->
- RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat),
- put_room_config(F, RoomConfig, Lang, FileFormat),
- io_lib:format("<font class=\"mrcm\">~s</font><br/>",
- [?T("Chatroom configuration modified")]);
- {roomconfig_change, Occupants} ->
- RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat),
- put_room_config(F, RoomConfig, Lang, FileFormat),
- RoomOccupants = roomoccupants_to_string(Occupants, FileFormat),
- put_room_occupants(F, RoomOccupants, Lang, FileFormat),
- io_lib:format("<font class=\"mrcm\">~s</font><br/>",
- [?T("Chatroom configuration modified")]);
- join ->
- io_lib:format("<font class=\"mj\">~s ~s</font><br/>",
- [Nick, ?T("joins the room")]);
- leave ->
- io_lib:format("<font class=\"ml\">~s ~s</font><br/>",
- [Nick, ?T("leaves the room")]);
- {leave, Reason} ->
- io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
- [Nick, ?T("leaves the room"), htmlize(Reason,NoFollow,FileFormat)]);
- {kickban, "301", ""} ->
- io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
- [Nick, ?T("has been banned")]);
- {kickban, "301", Reason} ->
- io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
- [Nick, ?T("has been banned"), htmlize(Reason,FileFormat)]);
- {kickban, "307", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked")]);
- {kickban, "307", Reason} ->
- io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
- [Nick, ?T("has been kicked"), htmlize(Reason,FileFormat)]);
- {kickban, "321", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked because of an affiliation change")]);
- {kickban, "322", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked because the room has been changed to members-only")]);
- {kickban, "332", ""} ->
- io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
- [Nick, ?T("has been kicked because of a system shutdown")]);
- {nickchange, OldNick} ->
- io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>",
- [htmlize(OldNick,FileFormat), ?T("is now known as"), Nick]);
- {subject, T} ->
- io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
- [Nick, ?T(" has set the subject to: "), htmlize(T,NoFollow,FileFormat)]);
- {body, T} ->
- case {ejabberd_regexp:run(T, "^/me\s"), Nick} of
- {_, ""} ->
- io_lib:format("<font class=\"msm\">~s</font><br/>",
- [htmlize(T,NoFollow,FileFormat)]);
- {match, _} ->
- io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
- [Nick, string:substr(htmlize(T,FileFormat), 5)]);
- {nomatch, _} ->
- io_lib:format("<font class=\"mn\">~s</font> ~s<br/>",
- [Nick2, htmlize(T,NoFollow,FileFormat)])
- end;
- {room_existence, RoomNewExistence} ->
- io_lib:format("<font class=\"mrcm\">~s</font><br/>",
- [get_room_existence_string(RoomNewExistence, Lang)])
+ roomconfig_change ->
+ RoomConfig = roomconfig_to_string(Room#room.config,
+ Lang, FileFormat),
+ put_room_config(F, RoomConfig, Lang, FileFormat),
+ io_lib:format("<font class=\"mrcm\">~s</font><br/>",
+ [?T(<<"Chatroom configuration modified">>)]);
+ {roomconfig_change, Occupants} ->
+ RoomConfig = roomconfig_to_string(Room#room.config,
+ Lang, FileFormat),
+ put_room_config(F, RoomConfig, Lang, FileFormat),
+ RoomOccupants = roomoccupants_to_string(Occupants,
+ FileFormat),
+ put_room_occupants(F, RoomOccupants, Lang, FileFormat),
+ io_lib:format("<font class=\"mrcm\">~s</font><br/>",
+ [?T(<<"Chatroom configuration modified">>)]);
+ join ->
+ io_lib:format("<font class=\"mj\">~s ~s</font><br/>",
+ [Nick, ?T(<<"joins the room">>)]);
+ leave ->
+ io_lib:format("<font class=\"ml\">~s ~s</font><br/>",
+ [Nick, ?T(<<"leaves the room">>)]);
+ {leave, Reason} ->
+ io_lib:format("<font class=\"ml\">~s ~s: ~s</font><br/>",
+ [Nick, ?T(<<"leaves the room">>),
+ htmlize(Reason, NoFollow, FileFormat)]);
+ {kickban, <<"301">>, <<"">>} ->
+ io_lib:format("<font class=\"mb\">~s ~s</font><br/>",
+ [Nick, ?T(<<"has been banned">>)]);
+ {kickban, <<"301">>, Reason} ->
+ io_lib:format("<font class=\"mb\">~s ~s: ~s</font><br/>",
+ [Nick, ?T(<<"has been banned">>),
+ htmlize(Reason, FileFormat)]);
+ {kickban, <<"307">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick, ?T(<<"has been kicked">>)]);
+ {kickban, <<"307">>, Reason} ->
+ io_lib:format("<font class=\"mk\">~s ~s: ~s</font><br/>",
+ [Nick, ?T(<<"has been kicked">>),
+ htmlize(Reason, FileFormat)]);
+ {kickban, <<"321">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick,
+ ?T(<<"has been kicked because of an affiliation "
+ "change">>)]);
+ {kickban, <<"322">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick,
+ ?T(<<"has been kicked because the room has "
+ "been changed to members-only">>)]);
+ {kickban, <<"332">>, <<"">>} ->
+ io_lib:format("<font class=\"mk\">~s ~s</font><br/>",
+ [Nick,
+ ?T(<<"has been kicked because of a system "
+ "shutdown">>)]);
+ {nickchange, OldNick} ->
+ io_lib:format("<font class=\"mnc\">~s ~s ~s</font><br/>",
+ [htmlize(OldNick, FileFormat),
+ ?T(<<"is now known as">>), Nick]);
+ {subject, T} ->
+ io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
+ [Nick, ?T(<<" has set the subject to: ">>),
+ htmlize(T, NoFollow, FileFormat)]);
+ {body, T} ->
+ case {ejabberd_regexp:run(T, <<"^/me ">>), Nick} of
+ {_, <<"">>} ->
+ io_lib:format("<font class=\"msm\">~s</font><br/>",
+ [htmlize(T, NoFollow, FileFormat)]);
+ {match, _} ->
+ io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
+ [Nick,
+ str:substr(htmlize(T, FileFormat), 5)]);
+ {nomatch, _} ->
+ io_lib:format("<font class=\"mn\">~s</font> ~s<br/>",
+ [Nick2, htmlize(T, NoFollow, FileFormat)])
+ end;
+ {room_existence, RoomNewExistence} ->
+ io_lib:format("<font class=\"mrcm\">~s</font><br/>",
+ [get_room_existence_string(RoomNewExistence,
+ Lang)])
end,
{Hour, Minute, Second} = Time,
- STime = lists:flatten(
- io_lib:format("~2..0w:~2..0w:~2..0w", [Hour, Minute, Second])),
+ STime = io_lib:format("~2..0w:~2..0w:~2..0w",
+ [Hour, Minute, Second]),
{_, _, Microsecs} = Now,
- STimeUnique = io_lib:format("~s.~w", [STime, Microsecs]),
-
- %% Write message
- fw(F, io_lib:format("<a id=\"~s\" name=\"~s\" href=\"#~s\" class=\"ts\">[~s]</a> ",
- [STimeUnique, STimeUnique, STimeUnique, STime]) ++ Text, FileFormat),
-
- %% Close file
+ STimeUnique = io_lib:format("~s.~w",
+ [STime, Microsecs]),
+ fw(F,
+ list_to_binary(
+ io_lib:format("<a id=\"~s\" name=\"~s\" href=\"#~s\" "
+ "class=\"ts\">[~s]</a> ",
+ [STimeUnique, STimeUnique, STimeUnique, STime])
+ ++ Text),
+ FileFormat),
file:close(F),
ok.
-
%%----------------------------------------------------------------------
%% Utilities
-get_room_existence_string(created, Lang) -> ?T("Chatroom is created");
-get_room_existence_string(destroyed, Lang) -> ?T("Chatroom is destroyed");
-get_room_existence_string(started, Lang) -> ?T("Chatroom is started");
-get_room_existence_string(stopped, Lang) -> ?T("Chatroom is stopped").
+get_room_existence_string(created, Lang) ->
+ ?T(<<"Chatroom is created">>);
+get_room_existence_string(destroyed, Lang) ->
+ ?T(<<"Chatroom is destroyed">>);
+get_room_existence_string(started, Lang) ->
+ ?T(<<"Chatroom is started">>);
+get_room_existence_string(stopped, Lang) ->
+ ?T(<<"Chatroom is stopped">>).
get_dateweek(Date, Lang) ->
Weekday = case calendar:day_of_the_week(Date) of
- 1 -> ?T("Monday");
- 2 -> ?T("Tuesday");
- 3 -> ?T("Wednesday");
- 4 -> ?T("Thursday");
- 5 -> ?T("Friday");
- 6 -> ?T("Saturday");
- 7 -> ?T("Sunday")
+ 1 -> ?T(<<"Monday">>);
+ 2 -> ?T(<<"Tuesday">>);
+ 3 -> ?T(<<"Wednesday">>);
+ 4 -> ?T(<<"Thursday">>);
+ 5 -> ?T(<<"Friday">>);
+ 6 -> ?T(<<"Saturday">>);
+ 7 -> ?T(<<"Sunday">>)
end,
{Y, M, D} = Date,
Month = case M of
- 1 -> ?T("January");
- 2 -> ?T("February");
- 3 -> ?T("March");
- 4 -> ?T("April");
- 5 -> ?T("May");
- 6 -> ?T("June");
- 7 -> ?T("July");
- 8 -> ?T("August");
- 9 -> ?T("September");
- 10 -> ?T("October");
- 11 -> ?T("November");
- 12 -> ?T("December")
+ 1 -> ?T(<<"January">>);
+ 2 -> ?T(<<"February">>);
+ 3 -> ?T(<<"March">>);
+ 4 -> ?T(<<"April">>);
+ 5 -> ?T(<<"May">>);
+ 6 -> ?T(<<"June">>);
+ 7 -> ?T(<<"July">>);
+ 8 -> ?T(<<"August">>);
+ 9 -> ?T(<<"September">>);
+ 10 -> ?T(<<"October">>);
+ 11 -> ?T(<<"November">>);
+ 12 -> ?T(<<"December">>)
end,
- case Lang of
- "en" -> io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
- "es" -> io_lib:format("~s ~w de ~s de ~w", [Weekday, D, Month, Y]);
- _ -> io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
- end.
+ list_to_binary(
+ case Lang of
+ <<"en">> ->
+ io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
+ <<"es">> ->
+ io_lib:format("~s ~w de ~s de ~w",
+ [Weekday, D, Month, Y]);
+ _ ->
+ io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
+ end).
make_dir_rec(Dir) ->
- case file:read_file_info(Dir) of
- {ok, _} ->
- ok;
- {error, enoent} ->
- DirS = filename:split(Dir),
- DirR = lists:sublist(DirS, length(DirS)-1),
- make_dir_rec(filename:join(DirR)),
- file:make_dir(Dir)
+ DirS = binary_to_list(Dir),
+ case file:read_file_info(DirS) of
+ {ok, _} -> ok;
+ {error, enoent} ->
+ DirL = [list_to_binary(F) || F <- filename:split(DirS)],
+ DirR = lists:sublist(DirL, length(DirL) - 1),
+ make_dir_rec(fjoin(DirR)),
+ file:make_dir(DirS)
end.
-
%% {ok, F1}=file:open("valid-xhtml10.png", [read]).
%% {ok, F1b}=file:read(F1, 1000000).
%% c("../../ejabberd/src/jlib.erl").
%% jlib:encode_base64(F1b).
-image_base64("powered-by-erlang.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoAAADN0lEQVRo3u1a"
- "P0waURz+rjGRRQ+nUyRCYmJyDPTapDARaSIbTUjt1gVSh8ZW69aBAR0cWLSx"
- "CXWp59LR1jbdqKnGxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu"
- "33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOHo8HkiQxDBXEOjg9"
- "PcHc3BxuUSqsI8jR0REAUFGsCCoKFYWCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJ"
- "ut9hMQlvdNbUhKWshLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm"
- "pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1MdG8rSKKXlJ7lHr"
- "fjouCut2IrC82BDPbe/gc+xlXez7KxEz63H4lmIN473Rh8Si1BKhRY6aEJI8"
- "pLmbjSPN0xOnBBILmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG"
- "2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFOTYznzpPO+IQi+IK"
- "+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN3Olvk4ge/IU98spne+FfGtlslm16nn"
- "a8fduntfDscoVjGJqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5"
- "ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizgwwu5SLK8jllu1t9"
- "ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y1m+hSj13WjqOiJKK0qpXKhSFxJAYBv"
- "KYaZ9TjWRu4SiWi2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g"
- "icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJvHsdL+utNYrm2uf"
- "ZDVZSkzPKaQkW8kthpyS297BvRdRzR6DdTurJbPy9Ov1K6xr3HBPQuIMowR3"
- "asegUyDuU9SuUG+dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q"
- "lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafIYrizbfESoB+dd9/"
- "KF/d/wX3cJvREzl1vAAAAABJRU5ErkJggg==";
-
-image_base64("valid-xhtml10.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEAAACiFBMVEUAAADe"
- "5+fOezmtra3ejEKlhELvvWO9WlrehELOe3vepaWclHvetVLGc3PerVKcCAj3"
- "vVqUjHOUe1JjlL0xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY"
- "WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoICACEazEhGAgIAACE"
- "YzFra2utjELWcznGnEr/7+9jY2POazHOYzGta2NShLVrlL05OUqctdacCADG"
- "a2ucAADGpVqUtc61ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe"
- "3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7/fGxsa9pVqEOSkp"
- "Y5xznL29tZxahLXOpVr/99ZrY1L/79ZjUiljSikAOYTvxmMAMYScezmchFqU"
- "czGtlFp7c2utjFqUlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC"
- "Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1mtCMRhzY1L/zmvn"
- "vVpSQiHOpVJrUinntVr3zmOEc1L3xmNaWlq1nFo5QkrGWim1lFoISpRSUlK1"
- "zt4hWpwASoz///////8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3"
- "9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUlHtKORh7a1IxIRCU"
- "jHtaSiHWrVIpIQhzWinvvVpaQiH/1mPWpVKMe1L/zmP/xmNrUiGErc4YGBj/"
- "73PG1ucQWpT/53O9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS"
- "SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////iP9sSf//dP//////"
- "//////////////////////////////////////////8M////////////ef//"
- "////////////////////////////////////////////////////////////"
- "//////////////////////9d////////////////////////////////////"
- "AP//////////////CP//RP//////////////////////////////////////"
- "//////////////////////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba"
- "iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpIvGQGzokvTTA85VH"
- "KTpbRoeJnPno/p1+M7t3txj20e/Nzu7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSA"
- "NeBDxVmxZZSwEUYkGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb"
- "Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6VbdtBblesOs+MjmEmz"
- "JKNIJWFEfEQTCWNPFKvcKEymjLO1b8bwYQd1hCiiDCl5KsrDCIlhj4fSuvcp"
- "fSpgJmyv6dzeZv+nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk"
- "WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDNE9uzA1YD/5MhnzW"
- "1KRqF7mYG8jFYXLcfLpjOe2LA0fuGqQrQHl10sdK0sFcFSOSlzF0BgXQH9h3"
- "QZDBI0ccNEhftjXuippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7"
- "eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woWSRJ0m2ewhvAOUiY"
- "YtZtaZL0CZZmtmVOQttLfr/dbveLZodrfrL7W75wG/JjqkQxoNTtNsTKELQp"
- "QL6/D5loaSmyTT8TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+"
- "AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qqmj5bHqVZlJKZIRk"
- "1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p1V76DeIs0MX+s3GxRlA1vtw83KhgNp"
- "hc1nyErLO5zcvbOsrq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N"
- "E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4Fsvjmuv662jsWxPZ"
- "x2xg890bYMYnTgya7bjmCiEY0qgJ0vMF3c+NoFdPyzxz6V3Uxs3AOWCDchRv"
- "OsQtBrbFsrT2fhHEc7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1"
- "7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3e4HFmYPDl/thYcR"
- "Vi8qPEoXVUWBl6FTBFTtnqmKKg5wnlF4wZ1yeLv7TiwXKektE+iDBNicWEyL"
- "pnFhfDkpJc3q2khSPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx"
- "Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdiMOlEOJHw1jOQ4sW"
- "t5rQopRkXZhMEi7pmeDCVWBlfUKwhMZ7rsF6elKsvbwiKxgxIdewa3ErsaYo"
- "mCVZFYJb0GUu3JqGUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+"
- "rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx17XhBuDCaOerbt5"
- "38erta7TFktLvdHghZcCbcPQO33zIJG9kxF5hoVXnzTzRz0r5js8oTj6uyPk"
- "GRf346HOLcasgFexueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl"
- "0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1juvBSWNFmNWGZy8Lv"
- "zUl5PN4JCwyNDzbQ0aAj4Zrjz0FatGJJYhvq4j7mGSpvytGFlZtHf2C4o/28"
- "Zu8z7wo7eYPfXysnF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO"
- "DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+faCSJPJ1bmy6Thyvp"
- "ivBmYWgjxPDPAp7JTemY/yGKFEiRt/jG/2P79s8KCwoLCgoLC/khUBA5F0Sf"
- "QZ+RYfpNE/4Xosmq7jsZAJsAAAAASUVORK5CYII=";
-
-image_base64("vcss.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSAAABKVBMVEUAAAAj"
- "Ix8MR51ZVUqAdlmdnZ3ejEWLDAuNjY1kiMG0n2d9fX19Ghfrp1FtbW3y39+3"
- "Ph6lIRNdXV2qJBFcVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd"
- "GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T09RRDwsJBG+vr73"
- "wV6fkG6eCQRFcLSurq6/X1+ht9nXfz5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5t"
- "WCsmWajsz8+Sq9NMPh4hVaY8MRj///////////////////////9MTEyOp9Lu"
- "8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn82dfz7/zGafDw+f"
- "Dw+zRSlzlMcMDAyNcji1tbXf5vIcFgvATJOjAAAAY3RSTlP/8///////////"
- "//////8A//////P/////ov//8//////////////z///T//////////+i////"
- "//////////8w/////6IA/xAgMP//////////8/////////8w0/////////+z"
- "ehebAAACkUlEQVR42u2VfVPTQBDG19VqC6LY+lKrRIxFQaFSBPuSvhBPF8SI"
- "UZK2J5Yav/+HcO8uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6"
- "9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE24pFtBtzG1Q4AoD5"
- "zvw5pEDcJvIQV/TE3/l+H9GnNJwcdABS5wAbFQLMqI98/UReoAaOTlaJsp0z"
- "aHx7LwZvY0BUR2xpWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7"
- "34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45OeuPgm4+Xjtv7xm4N"
- "7PMV4C61+Mrz3H2WImm3ATiWrAiwZRWcUA5Ej4dgIEMxDv6yxHHcNuAutnjv"
- "2HZ1NeuycoVPh0mwC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW"
- "KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyGqidQSw1eqtJcA8c"
- "Zq4zXqhPuCBYE1fKJjh/5X6MwRm9c2xf7WVdLf5oSdt64esVIwVAKC1HJ2ol"
- "i8vj3L0YzC4zjkMagt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD"
- "VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz54uUyet+hBM7BXX"
- "DS8Xc5+8Gmmbu1xwSoGIokA3oTptQecQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo"
- "+W9xC8oWpugVCXLuwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua"
- "ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/AV5htgaQ9SeRAAAA"
- "AElFTkSuQmCC";
-
-image_base64("powered-by-ejabberd.png") ->
- "iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaAAAAw1BMVEUAAAAj"
- "BgYtBAM5AwFCAAAYGAJNAABcAABIDQ5qAAAoJRV7AACFAAAoKSdJHByLAAAw"
- "Lwk1NQA1MzFJKyo4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb"
- "W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEgAB3dnd4d2+OjACD"
- "hYKcmACJi4iQkpWspgCYmJm5swCmqazEwACwsbS4ub3X0QLExsPLyszW1Nnc"
- "3ODm5ugMBwAWAwPHm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ"
- "cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VBkCMAAACcElEQVRI"
- "x72WjXKiMBSFQalIFbNiy1pdrJZaRVYR5deGwPs/VRNBSBB2OjvQO0oYjPfj"
- "5J6bCcdx8i2UldxKcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv"
- "1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJlRpsUei0JF6Qi+O"
- "HyGK7ijXxPklHe/umIllim3iUBMJDIEULxxPP0TVWhhKJoN9fUpdmQLteV8a"
- "DgEAg9gIcTjL4F4L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF"
- "FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7rovDjMpIHHGYeid"
- "Xn4TQcaxMYqP3RV3C8oCl2WvrlSPaNpGZadRnmPGCk8ylM2okAJ4i9TEe1Ke"
- "rsXxSl6jUt5uayiIodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs"
- "VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzvBo1I2tLmuL69kSi"
- "tAweyHKN9b3leDfQMnu3nIIKWfmXnqGVKedJT6QpICbJvf2f8aOsvn68v+k7"
- "/cwUQdPoxaMoRTnKFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG"
- "Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCTLav3VrqbPvP3vdd"
- "uGEhQJzilncbpSA4F3vsihErO+dayv/sY5/yRE0GDEXCu2VoNiMlo5i+P2Kl"
- "gMEvTNk2eYa5XEyh12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA"
- "AElFTkSuQmCC".
+image_base64(<<"powered-by-erlang.png">>) ->
+ <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoA"
+ "AADN0lEQVRo3u1aP0waURz+rjGRRQ+nUyRCYmJyDPTapD"
+ "ARaSIbTUjt1gVSh8ZW69aBAR0cWLSxCXWp59LR1jbdqKn"
+ "GxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu"
+ "33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOH"
+ "o8HkiQxDBXEOjg9PcHc3BxuUSqsI8jR0REAUFGsCCoKFY"
+ "WCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJut9hMQlvdNbUhKW"
+ "shLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm"
+ "pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1"
+ "MdG8rSKKXlJ7lHrfjouCut2IrC82BDPbe/gc+xlXez7Kx"
+ "Ez63H4lmIN473Rh8Si1BKhRY6aEJI8pLmbjSPN0xOnBBI"
+ "Lmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG"
+ "2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFO"
+ "TYznzpPO+IQi+IK+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN"
+ "3Olvk4ge/IU98spne+FfGtlslm16nna8fduntfDscoVjG"
+ "JqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5"
+ "ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizg"
+ "wwu5SLK8jllu1t9ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y"
+ "1m+hSj13WjqOiJKK0qpXKhSFxJAYBvKYaZ9TjWRu4SiWi"
+ "2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g"
+ "icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJ"
+ "vHsdL+utNYrm2ufZDVZSkzPKaQkW8kthpyS297BvRdRzR"
+ "6DdTurJbPy9Ov1K6xr3HBPQuIMowR3asegUyDuU9SuUG+"
+ "dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q"
+ "lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafI"
+ "YrizbfESoB+dd9/KF/d/wX3cJvREzl1vAAAAABJRU5Erk"
+ "Jggg==">>;
+image_base64(<<"valid-xhtml10.png">>) ->
+ <<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEA"
+ "AACiFBMVEUAAADe5+fOezmtra3ejEKlhELvvWO9WlrehE"
+ "LOe3vepaWclHvetVLGc3PerVKcCAj3vVqUjHOUe1JjlL0"
+ "xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY"
+ "WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoIC"
+ "ACEazEhGAgIAACEYzFra2utjELWcznGnEr/7+9jY2POaz"
+ "HOYzGta2NShLVrlL05OUqctdacCADGa2ucAADGpVqUtc6"
+ "1ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe"
+ "3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7"
+ "/fGxsa9pVqEOSkpY5xznL29tZxahLXOpVr/99ZrY1L/79"
+ "ZjUiljSikAOYTvxmMAMYScezmchFqUczGtlFp7c2utjFq"
+ "UlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC"
+ "Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1"
+ "mtCMRhzY1L/zmvnvVpSQiHOpVJrUinntVr3zmOEc1L3xm"
+ "NaWlq1nFo5QkrGWim1lFoISpRSUlK1zt4hWpwASoz////"
+ "///8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3"
+ "9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUl"
+ "HtKORh7a1IxIRCUjHtaSiHWrVIpIQhzWinvvVpaQiH/1m"
+ "PWpVKMe1L/zmP/xmNrUiGErc4YGBj/73PG1ucQWpT/53O"
+ "9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS"
+ "SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////i"
+ "P9sSf//dP////////////////////////////////////"
+ "////////////8M////////////ef/////////////////"
+ "/////////////////////////////////////////////"
+ "//////////////////////9d/////////////////////"
+ "///////////////AP//////////////CP//RP////////"
+ "/////////////////////////////////////////////"
+ "///////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba"
+ "iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpI"
+ "vGQGzokvTTA85VHKTpbRoeJnPno/p1+M7t3txj20e/Nzu"
+ "7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSANeBDxVmxZZSwEUY"
+ "kGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb"
+ "Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6Vbd"
+ "tBblesOs+MjmEmzJKNIJWFEfEQTCWNPFKvcKEymjLO1b8"
+ "bwYQd1hCiiDCl5KsrDCIlhj4fSuvcpfSpgJmyv6dzeZv+"
+ "nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk"
+ "WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDN"
+ "E9uzA1YD/5MhnzW1KRqF7mYG8jFYXLcfLpjOe2LA0fuGq"
+ "QrQHl10sdK0sFcFSOSlzF0BgXQH9h3QZDBI0ccNEhftjX"
+ "uippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7"
+ "eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woW"
+ "SRJ0m2ewhvAOUiYYtZtaZL0CZZmtmVOQttLfr/dbveLZo"
+ "drfrL7W75wG/JjqkQxoNTtNsTKELQpQL6/D5loaSmyTT8"
+ "TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+"
+ "AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qq"
+ "mj5bHqVZlJKZIRk1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p"
+ "1V76DeIs0MX+s3GxRlA1vtw83KhgNphc1nyErLO5zcvbO"
+ "srq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N"
+ "E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4F"
+ "svjmuv662jsWxPZx2xg890bYMYnTgya7bjmCiEY0qgJ0v"
+ "MF3c+NoFdPyzxz6V3Uxs3AOWCDchRvOsQtBrbFsrT2fhH"
+ "Ec7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1"
+ "7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3"
+ "e4HFmYPDl/thYcRVi8qPEoXVUWBl6FTBFTtnqmKKg5wnl"
+ "F4wZ1yeLv7TiwXKektE+iDBNicWEyLpnFhfDkpJc3q2kh"
+ "SPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx"
+ "Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdi"
+ "MOlEOJHw1jOQ4sWt5rQopRkXZhMEi7pmeDCVWBlfUKwhM"
+ "Z7rsF6elKsvbwiKxgxIdewa3ErsaYomCVZFYJb0GUu3Jq"
+ "GUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+"
+ "rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx"
+ "17XhBuDCaOerbt538erta7TFktLvdHghZcCbcPQO33zIJ"
+ "G9kxF5hoVXnzTzRz0r5js8oTj6uyPkGRf346HOLcasgFe"
+ "xueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl"
+ "0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1ju"
+ "vBSWNFmNWGZy8LvzUl5PN4JCwyNDzbQ0aAj4Zrjz0FatG"
+ "JJYhvq4j7mGSpvytGFlZtHf2C4o/28Zu8z7wo7eYPfXys"
+ "nF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO"
+ "DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+fa"
+ "CSJPJ1bmy6ThyvpivBmYWgjxPDPAp7JTemY/yGKFEiRt/"
+ "jG/2P79s8KCwoLCgoLC/khUBA5F0SfQZ+RYfpNE/4Xosm"
+ "q7jsZAJsAAAAASUVORK5CYII=">>;
+image_base64(<<"vcss.png">>) ->
+ <<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSA"
+ "AABKVBMVEUAAAAjIx8MR51ZVUqAdlmdnZ3ejEWLDAuNjY"
+ "1kiMG0n2d9fX19Ghfrp1FtbW3y39+3Ph6lIRNdXV2qJBF"
+ "cVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd"
+ "GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T"
+ "09RRDwsJBG+vr73wV6fkG6eCQRFcLSurq6/X1+ht9nXfz"
+ "5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5tWCsmWajsz8+Sq9N"
+ "MPh4hVaY8MRj///////////////////////9MTEyOp9Lu"
+ "8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn"
+ "82dfz7/zGafDw+fDw+zRSlzlMcMDAyNcji1tbXf5vIcFg"
+ "vATJOjAAAAY3RSTlP/8/////////////////8A//////P"
+ "/////ov//8//////////////z///T//////////+i////"
+ "//////////8w/////6IA/xAgMP//////////8////////"
+ "/8w0/////////+zehebAAACkUlEQVR42u2VfVPTQBDG19"
+ "VqC6LY+lKrRIxFQaFSBPuSvhBPF8SIUZK2J5Yav/+HcO8"
+ "uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6"
+ "9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE2"
+ "4pFtBtzG1Q4AoD5zvw5pEDcJvIQV/TE3/l+H9GnNJwcdA"
+ "BS5wAbFQLMqI98/UReoAaOTlaJsp0zaHx7LwZvY0BUR2x"
+ "pWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7"
+ "34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45Oe"
+ "uPgm4+Xjtv7xm4N7PMV4C61+Mrz3H2WImm3ATiWrAiwZR"
+ "WcUA5Ej4dgIEMxDv6yxHHcNuAutnjv2HZ1NeuycoVPh0m"
+ "wC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW"
+ "KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyG"
+ "qidQSw1eqtJcA8cZq4zXqhPuCBYE1fKJjh/5X6MwRm9c2"
+ "xf7WVdLf5oSdt64esVIwVAKC1HJ2oli8vj3L0YzC4zjkM"
+ "agt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD"
+ "VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz"
+ "54uUyet+hBM7BXXDS8Xc5+8Gmmbu1xwSoGIokA3oTptQe"
+ "cQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo+W9xC8oWpugVCXL"
+ "uwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua"
+ "ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/A"
+ "V5htgaQ9SeRAAAAAElFTkSuQmCC">>;
+image_base64(<<"powered-by-ejabberd.png">>) ->
+ <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaA"
+ "AAAw1BMVEUAAAAjBgYtBAM5AwFCAAAYGAJNAABcAABIDQ"
+ "5qAAAoJRV7AACFAAAoKSdJHByLAAAwLwk1NQA1MzFJKyo"
+ "4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb"
+ "W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEg"
+ "AB3dnd4d2+OjACDhYKcmACJi4iQkpWspgCYmJm5swCmqa"
+ "zEwACwsbS4ub3X0QLExsPLyszW1Nnc3ODm5ugMBwAWAwP"
+ "Hm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ"
+ "cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VB"
+ "kCMAAACcElEQVRIx72WjXKiMBSFQalIFbNiy1pdrJZaRV"
+ "YR5deGwPs/VRNBSBB2OjvQO0oYjPfj5J6bCcdx8i2Uldx"
+ "KcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv"
+ "1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJ"
+ "lRpsUei0JF6Qi+OHyGK7ijXxPklHe/umIllim3iUBMJDI"
+ "EULxxPP0TVWhhKJoN9fUpdmQLteV8aDgEAg9gIcTjL4F4"
+ "L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF"
+ "FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7"
+ "rovDjMpIHHGYeidXn4TQcaxMYqP3RV3C8oCl2WvrlSPaN"
+ "pGZadRnmPGCk8ylM2okAJ4i9TEe1KersXxSl6jUt5uayi"
+ "IodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs"
+ "VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzv"
+ "Bo1I2tLmuL69kSitAweyHKN9b3leDfQMnu3nIIKWfmXnq"
+ "GVKedJT6QpICbJvf2f8aOsvn68v+k7/cwUQdPoxaMoRTn"
+ "KFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG"
+ "Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCT"
+ "Lav3VrqbPvP3vdduGEhQJzilncbpSA4F3vsihErO+dayv"
+ "/sY5/yRE0GDEXCu2VoNiMlo5i+P2KlgMEvTNk2eYa5XEy"
+ "h12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA"
+ "AElFTkSuQmCC">>.
create_image_files(Images_dir) ->
- Filenames = ["powered-by-ejabberd.png",
- "powered-by-erlang.png",
- "valid-xhtml10.png",
- "vcss.png"
- ],
- lists:foreach(
- fun(Filename) ->
- Filename_full = filename:join([Images_dir, Filename]),
- {ok, F} = file:open(Filename_full, [write]),
- Image = jlib:decode_base64(image_base64(Filename)),
- io:format(F, "~s", [Image]),
- file:close(F)
- end,
- Filenames),
+ Filenames = [<<"powered-by-ejabberd.png">>,
+ <<"powered-by-erlang.png">>, <<"valid-xhtml10.png">>,
+ <<"vcss.png">>],
+ lists:foreach(fun (Filename) ->
+ Filename_full = fjoin([Images_dir, Filename]),
+ {ok, F} = file:open(Filename_full, [write]),
+ Image = jlib:decode_base64(image_base64(Filename)),
+ io:format(F, <<"~s">>, [Image]),
+ file:close(F)
+ end,
+ Filenames),
ok.
fw(F, S) -> fw(F, S, [], html).
-fw(F, S, O) when is_list(O) ->
- fw(F, S, O, html);
+fw(F, S, O) when is_list(O) -> fw(F, S, O, html);
fw(F, S, FileFormat) when is_atom(FileFormat) ->
fw(F, S, [], FileFormat).
fw(F, S, O, FileFormat) ->
- S1 = io_lib:format(S ++ "~n", O),
+ S1 = list_to_binary(io_lib:format(binary_to_list(S) ++ "~n", O)),
S2 = case FileFormat of
- html ->
- S1;
- plaintext ->
- ejabberd_regexp:greplace(S1, "<[^>]*>", "")
+ html -> S1;
+ plaintext ->
+ ejabberd_regexp:greplace(S1, <<"<[^>]*>">>, <<"">>)
end,
io:format(F, S2, []).
-put_header(_, _, _, _, _, _, _, _, _, plaintext) ->
- ok;
-put_header(F, Room, Date, CSSFile, Lang, Hour_offset, Date_prev, Date_next, Top_link, FileFormat) ->
- fw(F, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"),
- fw(F, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"~s\" lang=\"~s\">", [Lang, Lang]),
- fw(F, "<head>"),
- fw(F, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"),
- fw(F, "<title>~s - ~s</title>", [htmlize(Room#room.title), Date]),
+put_header(_, _, _, _, _, _, _, _, _, plaintext) -> ok;
+put_header(F, Room, Date, CSSFile, Lang, Hour_offset,
+ Date_prev, Date_next, Top_link, FileFormat) ->
+ fw(F,
+ <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" \"http://www.w3."
+ "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>),
+ fw(F,
+ <<"<html xmlns=\"http://www.w3.org/1999/xhtml\" "
+ "xml:lang=\"~s\" lang=\"~s\">">>,
+ [Lang, Lang]),
+ fw(F, <<"<head>">>),
+ fw(F,
+ <<"<meta http-equiv=\"Content-Type\" content=\"t"
+ "ext/html; charset=utf-8\" />">>),
+ fw(F, <<"<title>~s - ~s</title>">>,
+ [htmlize(Room#room.title), Date]),
put_header_css(F, CSSFile),
put_header_script(F),
- fw(F, "</head>"),
- fw(F, "<body>"),
+ fw(F, <<"</head>">>),
+ fw(F, <<"<body>">>),
{Top_url, Top_text} = Top_link,
- fw(F, "<div style=\"text-align: right;\"><a style=\"color: #AAAAAA; font-family: monospace; text-decoration: none; font-weight: bold;\" href=\"~s\">~s</a></div>", [Top_url, Top_text]),
- fw(F, "<div class=\"roomtitle\">~s</div>", [htmlize(Room#room.title)]),
- fw(F, "<a class=\"roomjid\" href=\"xmpp:~s?join\">~s</a>", [Room#room.jid, Room#room.jid]),
- fw(F, "<div class=\"logdate\">~s<span class=\"w3c\"><a class=\"nav\" href=\"~s\">&lt;</a> <a class=\"nav\" href=\".\/\">^</a> <a class=\"nav\" href=\"~s\">&gt;</a></span></div>", [Date, Date_prev, Date_next]),
- case {htmlize(Room#room.subject_author), htmlize(Room#room.subject)} of
- {"", ""} -> ok;
- {SuA, Su} -> fw(F, "<div class=\"roomsubject\">~s~s~s</div>", [SuA, ?T(" has set the subject to: "), Su])
+ fw(F,
+ <<"<div style=\"text-align: right;\"><a "
+ "style=\"color: #AAAAAA; font-family: "
+ "monospace; text-decoration: none; font-weight"
+ ": bold;\" href=\"~s\">~s</a></div>">>,
+ [Top_url, Top_text]),
+ fw(F, <<"<div class=\"roomtitle\">~s</div>">>,
+ [htmlize(Room#room.title)]),
+ fw(F,
+ <<"<a class=\"roomjid\" href=\"xmpp:~s?join\">~s"
+ "</a>">>,
+ [Room#room.jid, Room#room.jid]),
+ fw(F,
+ <<"<div class=\"logdate\">~s<span class=\"w3c\">"
+ "<a class=\"nav\" href=\"~s\">&lt;</a> "
+ "<a class=\"nav\" href=\"./\">^</a> <a "
+ "class=\"nav\" href=\"~s\">&gt;</a></span></di"
+ "v>">>,
+ [Date, Date_prev, Date_next]),
+ case {htmlize(Room#room.subject_author),
+ htmlize(Room#room.subject)}
+ of
+ {<<"">>, <<"">>} -> ok;
+ {SuA, Su} ->
+ fw(F, <<"<div class=\"roomsubject\">~s~s~s</div>">>,
+ [SuA, ?T(<<" has set the subject to: ">>), Su])
end,
- RoomConfig = roomconfig_to_string(Room#room.config, Lang, FileFormat),
+ RoomConfig = roomconfig_to_string(Room#room.config,
+ Lang, FileFormat),
put_room_config(F, RoomConfig, Lang, FileFormat),
Occupants = get_room_occupants(Room#room.jid),
- RoomOccupants = roomoccupants_to_string(Occupants, FileFormat),
+ RoomOccupants = roomoccupants_to_string(Occupants,
+ FileFormat),
put_room_occupants(F, RoomOccupants, Lang, FileFormat),
- Time_offset_str = case Hour_offset<0 of
- true -> io_lib:format("~p", [Hour_offset]);
- false -> io_lib:format("+~p", [Hour_offset])
+ Time_offset_str = case Hour_offset < 0 of
+ true -> io_lib:format("~p", [Hour_offset]);
+ false -> io_lib:format("+~p", [Hour_offset])
end,
- fw(F, "<br/><a class=\"ts\">GMT~s</a><br/>", [Time_offset_str]).
+ fw(F, <<"<br/><a class=\"ts\">GMT~s</a><br/>">>,
+ [Time_offset_str]).
put_header_css(F, false) ->
- fw(F, "<style type=\"text/css\">"),
- fw(F, "<!--"),
- fw(F, ".ts {color: #AAAAAA; text-decoration: none;}"),
- fw(F, ".mrcm {color: #009900; font-style: italic; font-weight: bold;}"),
- fw(F, ".msc {color: #009900; font-style: italic; font-weight: bold;}"),
- fw(F, ".msm {color: #000099; font-style: italic; font-weight: bold;}"),
- fw(F, ".mj {color: #009900; font-style: italic;}"),
- fw(F, ".ml {color: #009900; font-style: italic;}"),
- fw(F, ".mk {color: #009900; font-style: italic;}"),
- fw(F, ".mb {color: #009900; font-style: italic;}"),
- fw(F, ".mnc {color: #009900; font-style: italic;}"),
- fw(F, ".mn {color: #0000AA;}"),
- fw(F, ".mne {color: #AA0099;}"),
- fw(F, "a.nav {color: #AAAAAA; font-family: monospace; letter-spacing: 3px; text-decoration: none;}"),
- fw(F, "div.roomtitle {border-bottom: #224466 solid 3pt; margin-left: 20pt;}"),
- fw(F, "div.roomtitle {color: #336699; font-size: 24px; font-weight: bold; font-family: sans-serif; letter-spacing: 3px; text-decoration: none;}"),
- fw(F, "a.roomjid {color: #336699; font-size: 24px; font-weight: bold; font-family: sans-serif; letter-spacing: 3px; margin-left: 20pt; text-decoration: none;}"),
- fw(F, "div.logdate {color: #663399; font-size: 20px; font-weight: bold; font-family: sans-serif; letter-spacing: 2px; border-bottom: #224466 solid 1pt; margin-left:80pt; margin-top:20px;}"),
- fw(F, "div.roomsubject {color: #336699; font-size: 18px; font-family: sans-serif; margin-left: 80pt; margin-bottom: 10px;}"),
- fw(F, "div.rc {color: #336699; font-size: 12px; font-family: sans-serif; margin-left: 50%; text-align: right; background: #f3f6f9; border-bottom: 1px solid #336699; border-right: 4px solid #336699;}"),
- fw(F, "div.rct {font-weight: bold; background: #e3e6e9; padding-right: 10px;}"),
- fw(F, "div.rcos {padding-right: 10px;}"),
- fw(F, "div.rcoe {color: green;}"),
- fw(F, "div.rcod {color: red;}"),
- fw(F, "div.rcoe:after {content: \": v\";}"),
- fw(F, "div.rcod:after {content: \": x\";}"),
- fw(F, "div.rcot:after {}"),
- fw(F, ".legend {width: 100%; margin-top: 30px; border-top: #224466 solid 1pt; padding: 10px 0px 10px 0px; text-align: left; font-family: monospace; letter-spacing: 2px;}"),
- fw(F, ".w3c {position: absolute; right: 10px; width: 60%; text-align: right; font-family: monospace; letter-spacing: 1px;}"),
- fw(F, "//-->"),
- fw(F, "</style>");
-
+ fw(F, <<"<style type=\"text/css\">">>),
+ fw(F, <<"<!--">>),
+ fw(F,
+ <<".ts {color: #AAAAAA; text-decoration: "
+ "none;}">>),
+ fw(F,
+ <<".mrcm {color: #009900; font-style: italic; "
+ "font-weight: bold;}">>),
+ fw(F,
+ <<".msc {color: #009900; font-style: italic; "
+ "font-weight: bold;}">>),
+ fw(F,
+ <<".msm {color: #000099; font-style: italic; "
+ "font-weight: bold;}">>),
+ fw(F, <<".mj {color: #009900; font-style: italic;}">>),
+ fw(F, <<".ml {color: #009900; font-style: italic;}">>),
+ fw(F, <<".mk {color: #009900; font-style: italic;}">>),
+ fw(F, <<".mb {color: #009900; font-style: italic;}">>),
+ fw(F, <<".mnc {color: #009900; font-style: italic;}">>),
+ fw(F, <<".mn {color: #0000AA;}">>),
+ fw(F, <<".mne {color: #AA0099;}">>),
+ fw(F,
+ <<"a.nav {color: #AAAAAA; font-family: "
+ "monospace; letter-spacing: 3px; text-decorati"
+ "on: none;}">>),
+ fw(F,
+ <<"div.roomtitle {border-bottom: #224466 "
+ "solid 3pt; margin-left: 20pt;}">>),
+ fw(F,
+ <<"div.roomtitle {color: #336699; font-size: "
+ "24px; font-weight: bold; font-family: "
+ "sans-serif; letter-spacing: 3px; text-decorat"
+ "ion: none;}">>),
+ fw(F,
+ <<"a.roomjid {color: #336699; font-size: "
+ "24px; font-weight: bold; font-family: "
+ "sans-serif; letter-spacing: 3px; margin-left: "
+ "20pt; text-decoration: none;}">>),
+ fw(F,
+ <<"div.logdate {color: #663399; font-size: "
+ "20px; font-weight: bold; font-family: "
+ "sans-serif; letter-spacing: 2px; border-botto"
+ "m: #224466 solid 1pt; margin-left:80pt; "
+ "margin-top:20px;}">>),
+ fw(F,
+ <<"div.roomsubject {color: #336699; font-size: "
+ "18px; font-family: sans-serif; margin-left: "
+ "80pt; margin-bottom: 10px;}">>),
+ fw(F,
+ <<"div.rc {color: #336699; font-size: 12px; "
+ "font-family: sans-serif; margin-left: "
+ "50%; text-align: right; background: "
+ "#f3f6f9; border-bottom: 1px solid #336699; "
+ "border-right: 4px solid #336699;}">>),
+ fw(F,
+ <<"div.rct {font-weight: bold; background: "
+ "#e3e6e9; padding-right: 10px;}">>),
+ fw(F, <<"div.rcos {padding-right: 10px;}">>),
+ fw(F, <<"div.rcoe {color: green;}">>),
+ fw(F, <<"div.rcod {color: red;}">>),
+ fw(F, <<"div.rcoe:after {content: \": v\";}">>),
+ fw(F, <<"div.rcod:after {content: \": x\";}">>),
+ fw(F, <<"div.rcot:after {}">>),
+ fw(F,
+ <<".legend {width: 100%; margin-top: 30px; "
+ "border-top: #224466 solid 1pt; padding: "
+ "10px 0px 10px 0px; text-align: left; "
+ "font-family: monospace; letter-spacing: "
+ "2px;}">>),
+ fw(F,
+ <<".w3c {position: absolute; right: 10px; "
+ "width: 60%; text-align: right; font-family: "
+ "monospace; letter-spacing: 1px;}">>),
+ fw(F, <<"//-->">>),
+ fw(F, <<"</style>">>);
put_header_css(F, CSSFile) ->
- fw(F, "<link rel=\"stylesheet\" type=\"text/css\" href=\"~s\" media=\"all\">", [CSSFile]).
+ fw(F,
+ <<"<link rel=\"stylesheet\" type=\"text/css\" "
+ "href=\"~s\" media=\"all\">">>,
+ [CSSFile]).
put_header_script(F) ->
- fw(F, "<script type=\"text/javascript\">"),
- fw(F, "function sh(e) // Show/Hide an element"),
- fw(F, "{if(document.getElementById(e).style.display=='none')"),
- fw(F, "{document.getElementById(e).style.display='block';}"),
- fw(F, "else {document.getElementById(e).style.display='none';}}"),
- fw(F, "</script>").
+ fw(F, <<"<script type=\"text/javascript\">">>),
+ fw(F, <<"function sh(e) // Show/Hide an element">>),
+ fw(F,
+ <<"{if(document.getElementById(e).style.display="
+ "='none')">>),
+ fw(F,
+ <<"{document.getElementById(e).style.display='bl"
+ "ock';}">>),
+ fw(F,
+ <<"else {document.getElementById(e).style.displa"
+ "y='none';}}">>),
+ fw(F, <<"</script>">>).
put_room_config(_F, _RoomConfig, _Lang, plaintext) ->
ok;
put_room_config(F, RoomConfig, Lang, _FileFormat) ->
{_, Now2, _} = now(),
- fw(F, "<div class=\"rc\">"),
- fw(F, "<div class=\"rct\" onclick=\"sh('a~p');return false;\">~s</div>", [Now2, ?T("Room Configuration")]),
- fw(F, "<div class=\"rcos\" id=\"a~p\" style=\"display: none;\" ><br/>~s</div>", [Now2, RoomConfig]),
- fw(F, "</div>").
-
-put_room_occupants(_F, _RoomOccupants, _Lang, plaintext) ->
+ fw(F, <<"<div class=\"rc\">">>),
+ fw(F,
+ <<"<div class=\"rct\" onclick=\"sh('a~p');return "
+ "false;\">~s</div>">>,
+ [Now2, ?T(<<"Room Configuration">>)]),
+ fw(F,
+ <<"<div class=\"rcos\" id=\"a~p\" style=\"displa"
+ "y: none;\" ><br/>~s</div>">>,
+ [Now2, RoomConfig]),
+ fw(F, <<"</div>">>).
+
+put_room_occupants(_F, _RoomOccupants, _Lang,
+ plaintext) ->
ok;
-put_room_occupants(F, RoomOccupants, Lang, _FileFormat) ->
+put_room_occupants(F, RoomOccupants, Lang,
+ _FileFormat) ->
{_, Now2, _} = now(),
- fw(F, "<div class=\"rc\">"),
- fw(F, "<div class=\"rct\" onclick=\"sh('o~p');return false;\">~s</div>", [Now2, ?T("Room Occupants")]),
- fw(F, "<div class=\"rcos\" id=\"o~p\" style=\"display: none;\" ><br/>~s</div>", [Now2, RoomOccupants]),
- fw(F, "</div>").
-
-%% htmlize
-%% The default behaviour is to ignore the nofollow spam prevention on links
-%% (NoFollow=false)
-htmlize(S1) ->
- htmlize(S1, html).
-
-htmlize(S1, plaintext) ->
- S1;
+ fw(F, <<"<div class=\"rc\">">>),
+ fw(F,
+ <<"<div class=\"rct\" onclick=\"sh('o~p');return "
+ "false;\">~s</div>">>,
+ [Now2, ?T(<<"Room Occupants">>)]),
+ fw(F,
+ <<"<div class=\"rcos\" id=\"o~p\" style=\"displa"
+ "y: none;\" ><br/>~s</div>">>,
+ [Now2, RoomOccupants]),
+ fw(F, <<"</div>">>).
+
+htmlize(S1) -> htmlize(S1, html).
+
+htmlize(S1, plaintext) -> S1;
htmlize(S1, FileFormat) ->
htmlize(S1, false, FileFormat).
-%% The NoFollow parameter tell if the spam prevention should be applied to the link found
-%% true means 'apply nofollow on links'.
-htmlize(S1, _NoFollow, plaintext) ->
- S1;
+htmlize(S1, _NoFollow, plaintext) -> S1;
htmlize(S1, NoFollow, _FileFormat) ->
- S2_list = string:tokens(S1, "\n"),
- lists:foldl(
- fun(Si, Res) ->
- Si2 = htmlize2(Si, NoFollow),
- case Res of
- "" -> Si2;
- _ -> Res ++ "<br/>" ++ Si2
- end
- end,
- "",
- S2_list).
+ S2_list = str:tokens(S1, <<"\n">>),
+ lists:foldl(fun (Si, Res) ->
+ Si2 = htmlize2(Si, NoFollow),
+ case Res of
+ <<"">> -> Si2;
+ _ -> <<Res/binary, "<br/>", Si2/binary>>
+ end
+ end,
+ <<"">>, S2_list).
htmlize2(S1, NoFollow) ->
- S2 = ejabberd_regexp:greplace(S1, "\\&", "\\&amp;"),
- S3 = ejabberd_regexp:greplace(S2, "<", "\\&lt;"),
- S4 = ejabberd_regexp:greplace(S3, ">", "\\&gt;"),
- S5 = ejabberd_regexp:greplace(S4, "((http|https|ftp)://|(mailto|xmpp):)[^] )\'\"}]+",
- link_regexp(NoFollow)),
- %% Remove 'right-to-left override' unicode character 0x202e
- S6 = ejabberd_regexp:greplace(S5, " ", "\\&nbsp;\\&nbsp;"),
- S7 = ejabberd_regexp:greplace(S6, "\\t", "\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;"),
- ejabberd_regexp:greplace(S7, [226,128,174], "[RLO]").
-
-%% Regexp link
-%% Add the nofollow rel attribute when required
-link_regexp(false) ->
- "<a href=\"&\">&</a>";
+ S2 = ejabberd_regexp:greplace(S1, <<"\\&">>,
+ <<"\\&amp;">>),
+ S3 = ejabberd_regexp:greplace(S2, <<"<">>,
+ <<"\\&lt;">>),
+ S4 = ejabberd_regexp:greplace(S3, <<">">>,
+ <<"\\&gt;">>),
+ S5 = ejabberd_regexp:greplace(S4,
+ <<"((http|https|ftp)://|(mailto|xmpp):)[^] "
+ ")'\"}]+">>,
+ link_regexp(NoFollow)),
+ S6 = ejabberd_regexp:greplace(S5, <<" ">>,
+ <<"\\&nbsp;\\&nbsp;">>),
+ S7 = ejabberd_regexp:greplace(S6, <<"\\t">>,
+ <<"\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;">>),
+ ejabberd_regexp:greplace(S7, <<226, 128, 174>>,
+ <<"[RLO]">>).
+
+link_regexp(false) -> <<"<a href=\"&\">&</a>">>;
link_regexp(true) ->
- "<a href=\"&\" rel=\"nofollow\">&</a>".
+ <<"<a href=\"&\" rel=\"nofollow\">&</a>">>.
get_room_info(RoomJID, Opts) ->
- Title =
- case lists:keysearch(title, 1, Opts) of
- {value, {_, T}} -> T;
- false -> ""
- end,
- Subject =
- case lists:keysearch(subject, 1, Opts) of
- {value, {_, S}} -> S;
- false -> ""
- end,
- SubjectAuthor =
- case lists:keysearch(subject_author, 1, Opts) of
- {value, {_, SA}} -> SA;
- false -> ""
- end,
- #room{jid = jlib:jid_to_string(RoomJID),
- title = Title,
- subject = Subject,
- subject_author = SubjectAuthor,
- config = Opts
- }.
+ Title = case lists:keysearch(title, 1, Opts) of
+ {value, {_, T}} -> T;
+ false -> <<"">>
+ end,
+ Subject = case lists:keysearch(subject, 1, Opts) of
+ {value, {_, S}} -> S;
+ false -> <<"">>
+ end,
+ SubjectAuthor = case lists:keysearch(subject_author, 1,
+ Opts)
+ of
+ {value, {_, SA}} -> SA;
+ false -> <<"">>
+ end,
+ #room{jid = jlib:jid_to_string(RoomJID), title = Title,
+ subject = Subject, subject_author = SubjectAuthor,
+ config = Opts}.
roomconfig_to_string(Options, Lang, FileFormat) ->
- %% Get title, if available
Title = case lists:keysearch(title, 1, Options) of
- {value, Tuple} -> [Tuple];
- false -> []
+ {value, Tuple} -> [Tuple];
+ false -> []
end,
-
- %% Remove title from list
Os1 = lists:keydelete(title, 1, Options),
-
- %% Order list
Os2 = lists:sort(Os1),
-
- %% Add title to ordered list
Options2 = Title ++ Os2,
-
- lists:foldl(
- fun({Opt, Val}, R) ->
- case get_roomconfig_text(Opt) of
- undefined ->
- R;
- OptT ->
- OptText = ?T(OptT),
- R2 = case Val of
- false -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
- true -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
- "" -> "<div class=\"rcod\">" ++ OptText ++ "</div>";
- T ->
- case Opt of
- password -> "<div class=\"rcoe\">" ++ OptText ++ "</div>";
- max_users -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(integer_to_list(T), FileFormat) ++ "\"</div>";
- title -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(T, FileFormat) ++ "\"</div>";
- description -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(T, FileFormat) ++ "\"</div>";
- allow_private_messages_from_visitors -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(?T(atom_to_list(T)), FileFormat) ++ "\"</div>";
- _ -> "\"" ++ T ++ "\""
- end
- end,
- R ++ R2
- end
- end,
- "",
- Options2).
-
-get_roomconfig_text(title) -> "Room title";
-get_roomconfig_text(persistent) -> "Make room persistent";
-get_roomconfig_text(public) -> "Make room public searchable";
-get_roomconfig_text(public_list) -> "Make participants list public";
-get_roomconfig_text(password_protected) -> "Make room password protected";
-get_roomconfig_text(password) -> "Password";
-get_roomconfig_text(anonymous) -> "This room is not anonymous";
-get_roomconfig_text(members_only) -> "Make room members-only";
-get_roomconfig_text(moderated) -> "Make room moderated";
-get_roomconfig_text(members_by_default) -> "Default users as participants";
-get_roomconfig_text(allow_change_subj) -> "Allow users to change the subject";
-get_roomconfig_text(allow_private_messages) -> "Allow users to send private messages";
-get_roomconfig_text(allow_private_messages_from_visitors) -> "Allow visitors to send private messages to";
-get_roomconfig_text(allow_query_users) -> "Allow users to query other users";
-get_roomconfig_text(allow_user_invites) -> "Allow users to send invites";
-get_roomconfig_text(logging) -> "Enable logging";
-get_roomconfig_text(allow_visitor_nickchange) -> "Allow visitors to change nickname";
-get_roomconfig_text(allow_visitor_status) -> "Allow visitors to send status text in presence updates";
-get_roomconfig_text(captcha_protected) -> "Make room captcha protected";
-get_roomconfig_text(description) -> "Room description";
+ lists:foldl(fun ({Opt, Val}, R) ->
+ case get_roomconfig_text(Opt) of
+ undefined -> R;
+ OptT ->
+ OptText = (?T(OptT)),
+ R2 = case Val of
+ false ->
+ <<"<div class=\"rcod\">",
+ OptText/binary, "</div>">>;
+ true ->
+ <<"<div class=\"rcoe\">",
+ OptText/binary, "</div>">>;
+ <<"">> ->
+ <<"<div class=\"rcod\">",
+ OptText/binary, "</div>">>;
+ T ->
+ case Opt of
+ password ->
+ <<"<div class=\"rcoe\">",
+ OptText/binary, "</div>">>;
+ max_users ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(jlib:integer_to_binary(T),
+ FileFormat))/binary,
+ "\"</div>">>;
+ title ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(T,
+ FileFormat))/binary,
+ "\"</div>">>;
+ description ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(T,
+ FileFormat))/binary,
+ "\"</div>">>;
+ allow_private_messages_from_visitors ->
+ <<"<div class=\"rcot\">",
+ OptText/binary, ": \"",
+ (htmlize(?T((jlib:atom_to_binary(T))),
+ FileFormat))/binary,
+ "\"</div>">>;
+ _ -> <<"\"", T/binary, "\"">>
+ end
+ end,
+ <<R/binary, R2/binary>>
+ end
+ end,
+ <<"">>, Options2).
+
+get_roomconfig_text(title) -> <<"Room title">>;
+get_roomconfig_text(persistent) ->
+ <<"Make room persistent">>;
+get_roomconfig_text(public) ->
+ <<"Make room public searchable">>;
+get_roomconfig_text(public_list) ->
+ <<"Make participants list public">>;
+get_roomconfig_text(password_protected) ->
+ <<"Make room password protected">>;
+get_roomconfig_text(password) -> <<"Password">>;
+get_roomconfig_text(anonymous) ->
+ <<"This room is not anonymous">>;
+get_roomconfig_text(members_only) ->
+ <<"Make room members-only">>;
+get_roomconfig_text(moderated) ->
+ <<"Make room moderated">>;
+get_roomconfig_text(members_by_default) ->
+ <<"Default users as participants">>;
+get_roomconfig_text(allow_change_subj) ->
+ <<"Allow users to change the subject">>;
+get_roomconfig_text(allow_private_messages) ->
+ <<"Allow users to send private messages">>;
+get_roomconfig_text(allow_private_messages_from_visitors) ->
+ <<"Allow visitors to send private messages to">>;
+get_roomconfig_text(allow_query_users) ->
+ <<"Allow users to query other users">>;
+get_roomconfig_text(allow_user_invites) ->
+ <<"Allow users to send invites">>;
+get_roomconfig_text(logging) -> <<"Enable logging">>;
+get_roomconfig_text(allow_visitor_nickchange) ->
+ <<"Allow visitors to change nickname">>;
+get_roomconfig_text(allow_visitor_status) ->
+ <<"Allow visitors to send status text in "
+ "presence updates">>;
+get_roomconfig_text(captcha_protected) ->
+ <<"Make room captcha protected">>;
+get_roomconfig_text(description) ->
+ <<"Room description">>;
%% get_roomconfig_text(subject) -> "Subject";
%% get_roomconfig_text(subject_author) -> "Subject author";
-get_roomconfig_text(max_users) -> "Maximum Number of Occupants";
+get_roomconfig_text(max_users) ->
+ <<"Maximum Number of Occupants">>;
get_roomconfig_text(_) -> undefined.
-%% Users = [{JID, Nick, Role}]
roomoccupants_to_string(Users, _FileFormat) ->
Res = [role_users_to_string(RoleS, Users1)
- || {RoleS, Users1} <- group_by_role(Users), Users1 /= []],
- lists:flatten(["<div class=\"rcot\">", Res, "</div>"]).
+ || {RoleS, Users1} <- group_by_role(Users),
+ Users1 /= []],
+ iolist_to_binary([<<"<div class=\"rcot\">">>, Res, <<"</div>">>]).
-%% Users = [{JID, Nick, Role}]
group_by_role(Users) ->
- {Ms, Ps, Vs, Ns} =
- lists:foldl(
- fun({JID, Nick, moderator}, {Mod, Par, Vis, Non}) ->
- {[{JID, Nick}]++Mod, Par, Vis, Non};
- ({JID, Nick, participant}, {Mod, Par, Vis, Non}) ->
- {Mod, [{JID, Nick}]++Par, Vis, Non};
- ({JID, Nick, visitor}, {Mod, Par, Vis, Non}) ->
- {Mod, Par, [{JID, Nick}]++Vis, Non};
- ({JID, Nick, none}, {Mod, Par, Vis, Non}) ->
- {Mod, Par, Vis, [{JID, Nick}]++Non}
- end,
- {[], [], [], []},
- Users),
- case Ms of [] -> []; _ -> [{"Moderator", Ms}] end
- ++ case Ms of [] -> []; _ -> [{"Participant", Ps}] end
- ++ case Ms of [] -> []; _ -> [{"Visitor", Vs}] end
- ++ case Ms of [] -> []; _ -> [{"None", Ns}] end.
-
-%% Role = atom()
-%% Users = [{JID, Nick}]
+ {Ms, Ps, Vs, Ns} = lists:foldl(fun ({JID, Nick,
+ moderator},
+ {Mod, Par, Vis, Non}) ->
+ {[{JID, Nick}] ++ Mod, Par, Vis,
+ Non};
+ ({JID, Nick, participant},
+ {Mod, Par, Vis, Non}) ->
+ {Mod, [{JID, Nick}] ++ Par, Vis,
+ Non};
+ ({JID, Nick, visitor},
+ {Mod, Par, Vis, Non}) ->
+ {Mod, Par, [{JID, Nick}] ++ Vis,
+ Non};
+ ({JID, Nick, none},
+ {Mod, Par, Vis, Non}) ->
+ {Mod, Par, Vis, [{JID, Nick}] ++ Non}
+ end,
+ {[], [], [], []}, Users),
+ case Ms of
+ [] -> [];
+ _ -> [{<<"Moderator">>, Ms}]
+ end
+ ++
+ case Ms of
+ [] -> [];
+ _ -> [{<<"Participant">>, Ps}]
+ end
+ ++
+ case Ms of
+ [] -> [];
+ _ -> [{<<"Visitor">>, Vs}]
+ end
+ ++
+ case Ms of
+ [] -> [];
+ _ -> [{<<"None">>, Ns}]
+ end.
+
role_users_to_string(RoleS, Users) ->
SortedUsers = lists:keysort(2, Users),
- UsersString = [[Nick, "<br/>"] || {_JID, Nick} <- SortedUsers],
- [RoleS, ": ", UsersString].
+ UsersString = << <<Nick/binary, "<br/>">>
+ || {_JID, Nick} <- SortedUsers >>,
+ <<RoleS/binary, ": ", UsersString/binary>>.
get_room_occupants(RoomJIDString) ->
RoomJID = jlib:string_to_jid(RoomJIDString),
@@ -936,19 +1112,25 @@ get_room_occupants(RoomJIDString) ->
MucService = RoomJID#jid.lserver,
StateData = get_room_state(RoomName, MucService),
[{U#user.jid, U#user.nick, U#user.role}
- || {_, U} <- ?DICT:to_list(StateData#state.users)].
+ || {_, U} <- (?DICT):to_list(StateData#state.users)].
+
+-spec get_room_state(binary(), binary()) -> muc_room_state().
get_room_state(RoomName, MucService) ->
- case mnesia:dirty_read(muc_online_room, {RoomName, MucService}) of
- [R] ->
- RoomPid = R#muc_online_room.pid,
- get_room_state(RoomPid);
- [] ->
- #state{}
+ case mnesia:dirty_read(muc_online_room,
+ {RoomName, MucService})
+ of
+ [R] ->
+ RoomPid = R#muc_online_room.pid,
+ get_room_state(RoomPid);
+ [] -> #state{}
end.
+-spec get_room_state(pid()) -> muc_room_state().
+
get_room_state(RoomPid) ->
- {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, get_state),
+ {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
+ get_state),
R.
get_proc_name(Host) ->
@@ -956,6 +1138,13 @@ get_proc_name(Host) ->
calc_hour_offset(TimeHere) ->
TimeZero = calendar:now_to_universal_time(now()),
- TimeHereHour = calendar:datetime_to_gregorian_seconds(TimeHere) div 3600,
- TimeZeroHour = calendar:datetime_to_gregorian_seconds(TimeZero) div 3600,
+ TimeHereHour =
+ calendar:datetime_to_gregorian_seconds(TimeHere) div
+ 3600,
+ TimeZeroHour =
+ calendar:datetime_to_gregorian_seconds(TimeZero) div
+ 3600,
TimeHereHour - TimeZeroHour.
+
+fjoin(FileList) ->
+ list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl
index ecf9c9581..6c139d5c5 100644
--- a/src/mod_muc/mod_muc_room.erl
+++ b/src/mod_muc/mod_muc_room.erl
@@ -25,37 +25,27 @@
%%%----------------------------------------------------------------------
-module(mod_muc_room).
+
-author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
-
%% External exports
--export([start_link/10,
- start_link/8,
- start_link/2,
- start/10,
- start/8,
- start/2,
- migrate/3,
- route/4,
- moderate_room_history/2,
- persist_recent_messages/1]).
+-export([start_link/10, start_link/8, start_link/2,
+ start/10, start/8, start/2, migrate/3, route/4,
+ moderate_room_history/2, persist_recent_messages/1]).
%% gen_fsm callbacks
--export([init/1,
- normal_state/2,
- handle_event/3,
- handle_sync_event/4,
- handle_info/3,
- terminate/3,
- print_state/1,
- code_change/4]).
+-export([init/1, normal_state/2, handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3,
+ print_state/1, code_change/4]).
-include("ejabberd.hrl").
+
-include("jlib.hrl").
+
-include("mod_muc_room.hrl").
-define(MAX_USERS_DEFAULT_LIST,
@@ -64,865 +54,866 @@
%-define(DBGFSM, true).
-ifdef(DBGFSM).
+
-define(FSMOPTS, [{debug, [trace]}]).
+
-else.
+
-define(FSMOPTS, []).
+
-endif.
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
--define(SUPERVISOR_START(Args),
- ?GEN_FSM:start(?MODULE, Args, ?FSMOPTS)).
+
+-define(SUPERVISOR_START(Args),
+ (?GEN_FSM):start(?MODULE, Args, ?FSMOPTS)).
+
-else.
--define(SUPERVISOR_START(Args),
- Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
+
+-define(SUPERVISOR_START(Args),
+ Supervisor = gen_mod:get_module_proc(ServerHost,
+ ejabberd_mod_muc_sup),
supervisor:start_child(Supervisor, Args)).
--endif.
-%%%----------------------------------------------------------------------
-%%% API
-%%%----------------------------------------------------------------------
-start(Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper,
- Creator, Nick, DefRoomOpts) ->
- ?SUPERVISOR_START([Host, ServerHost, Access, Room, HistorySize, PersistHistory,
- RoomShaper, Creator, Nick, DefRoomOpts]).
+-endif.
-start(Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, Opts) ->
- Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
- supervisor:start_child(
- Supervisor, [Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper,
- Opts]).
+start(Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Creator, Nick,
+ DefRoomOpts) ->
+ ?SUPERVISOR_START([Host, ServerHost, Access, Room,
+ HistorySize, PersistHistory, RoomShaper, Creator, Nick,
+ DefRoomOpts]).
+
+start(Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Opts) ->
+ Supervisor = gen_mod:get_module_proc(ServerHost,
+ ejabberd_mod_muc_sup),
+ supervisor:start_child(Supervisor,
+ [Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Opts]).
start(StateName, StateData) ->
ServerHost = StateData#state.server_host,
?SUPERVISOR_START([StateName, StateData]).
-start_link(Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper,
- Creator, Nick, DefRoomOpts) ->
- ?GEN_FSM:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, PersistHistory,
- RoomShaper, Creator, Nick, DefRoomOpts],
- ?FSMOPTS).
-
-start_link(Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, Opts) ->
- ?GEN_FSM:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize, PersistHistory,
- RoomShaper, Opts],
- ?FSMOPTS).
+start_link(Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Creator, Nick,
+ DefRoomOpts) ->
+ (?GEN_FSM):start_link(?MODULE,
+ [Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Creator, Nick,
+ DefRoomOpts],
+ ?FSMOPTS).
+
+start_link(Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Opts) ->
+ (?GEN_FSM):start_link(?MODULE,
+ [Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Opts],
+ ?FSMOPTS).
start_link(StateName, StateData) ->
- ?GEN_FSM:start_link(?MODULE, [StateName, StateData], ?FSMOPTS).
+ (?GEN_FSM):start_link(?MODULE, [StateName, StateData],
+ ?FSMOPTS).
migrate(FsmRef, Node, After) ->
erlang:send_after(After, FsmRef, {migrate, Node}).
moderate_room_history(FsmRef, Nick) ->
- ?GEN_FSM:sync_send_all_state_event(FsmRef, {moderate_room_history, Nick}).
+ (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ {moderate_room_history, Nick}).
persist_recent_messages(FsmRef) ->
- ?GEN_FSM:sync_send_all_state_event(FsmRef, persist_recent_messages).
-%%%----------------------------------------------------------------------
-%%% Callback functions from gen_fsm
-%%%----------------------------------------------------------------------
+ (?GEN_FSM):sync_send_all_state_event(FsmRef,
+ persist_recent_messages).
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, StateName, StateData} |
-%% {ok, StateName, StateData, Timeout} |
-%% ignore |
-%% {stop, StopReason}
-%%----------------------------------------------------------------------
-init([Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, Creator, _Nick, DefRoomOpts]) ->
+init([Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Creator, _Nick,
+ DefRoomOpts]) ->
process_flag(trap_exit, true),
Shaper = shaper:new(RoomShaper),
State = set_affiliation(Creator, owner,
- #state{host = Host,
- server_host = ServerHost,
- access = Access,
- room = Room,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, room = Room,
history = lqueue_new(HistorySize),
persist_history = PersistHistory,
- jid = jlib:make_jid(Room, Host, ""),
- just_created = true,
- room_shaper = Shaper}),
+ jid = jlib:make_jid(Room, Host, <<"">>),
+ just_created = true, room_shaper = Shaper}),
State1 = set_opts(DefRoomOpts, State),
- %% this will trigger a write of the muc to disc if it is persistent.
- %% we need to do this because otherwise if muc are persistent by default,
- %% but never configured in any way by the client, we were never
- %% storing it on disc to be recreated on startup.
- if
- (State1#state.config)#config.persistent ->
- mod_muc:store_room(State1#state.host, State1#state.room, make_opts(State1));
- true ->
- ok
+ if (State1#state.config)#config.persistent ->
+ mod_muc:store_room(State1#state.server_host,
+ State1#state.host, State1#state.room,
+ make_opts(State1));
+ true -> ok
end,
- ?INFO_MSG("Created MUC room ~s@~s by ~s",
+ ?INFO_MSG("Created MUC room ~s@~s by ~s",
[Room, Host, jlib:jid_to_string(Creator)]),
add_to_log(room_existence, created, State1),
add_to_log(room_existence, started, State1),
{ok, normal_state, State1};
-init([Host, ServerHost, Access, Room, HistorySize, PersistHistory, RoomShaper, Opts]) ->
+init([Host, ServerHost, Access, Room, HistorySize,
+ PersistHistory, RoomShaper, Opts]) ->
process_flag(trap_exit, true),
Shaper = shaper:new(RoomShaper),
- State = set_opts(Opts, #state{host = Host,
- server_host = ServerHost,
- access = Access,
- room = Room,
- history = load_history(ServerHost, Room, PersistHistory, lqueue_new(HistorySize)),
- persist_history = PersistHistory,
- jid = jlib:make_jid(Room, Host, ""),
- room_shaper = Shaper}),
+ State = set_opts(Opts,
+ #state{host = Host, server_host = ServerHost,
+ access = Access, room = Room,
+ history =
+ load_history(ServerHost, Room, PersistHistory,
+ lqueue_new(HistorySize)),
+ persist_history = PersistHistory,
+ jid = jlib:make_jid(Room, Host, <<"">>),
+ room_shaper = Shaper}),
add_to_log(room_existence, started, State),
{ok, normal_state, State};
-init([StateName, #state{room = Room, host = Host} = StateData]) ->
+init([StateName,
+ #state{room = Room, host = Host} = StateData]) ->
process_flag(trap_exit, true),
mod_muc:register_room(Host, Room, self()),
{ok, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: StateName/2
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-normal_state({route, From, "",
- {xmlelement, "message", Attrs, Els} = Packet},
+normal_state({route, From, <<"">>,
+ #xmlel{name = <<"message">>, attrs = Attrs,
+ children = Els} =
+ Packet},
StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
- is_user_allowed_message_nonparticipant(From, StateData) of
- true ->
- case xml:get_attr_s("type", Attrs) of
- "groupchat" ->
- Activity = get_user_activity(From, StateData),
- Now = now_to_usec(now()),
- MinMessageInterval =
- trunc(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_message_interval, 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)),
- route_stanza(
- 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 = "This participant is kicked from the room because "
- "he sent an error message",
- NewState = expulse_participant(Packet, From, StateData,
- translate:translate(Lang, ErrorText)),
- {next_state, normal_state, 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)),
- route_stanza(
- 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, Els, Lang, StateData) of
- {error, Error} ->
- Err = jlib:make_error_reply(
- Packet, Error),
- route_stanza(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData};
- IJID ->
- Config = StateData#state.config,
- case Config#config.members_only of
+ is_user_allowed_message_nonparticipant(From, StateData)
+ of
+ true ->
+ case xml:get_attr_s(<<"type">>, Attrs) of
+ <<"groupchat">> ->
+ Activity = get_user_activity(From, StateData),
+ Now = now_to_usec(now()),
+ MinMessageInterval =
+ trunc(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_message_interval,
+ fun(I) when is_number(I),
+ I>=0 ->
+ I
+ 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)),
+ route_stanza(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 = <<"This participant is kicked from the "
+ "room because he sent an error message">>,
+ NewState = expulse_participant(Packet, From, StateData,
+ translate:translate(Lang,
+ ErrorText)),
+ {next_state, normal_state, 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)),
+ route_stanza(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, Els, Lang, StateData)
+ of
+ {error, Error} ->
+ Err = jlib:make_error_reply(Packet, Error),
+ route_stanza(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),
+ case
+ (NSD#state.config)#config.persistent
+ of
+ true ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host,
+ NSD#state.room,
+ make_opts(NSD));
+ _ -> ok
+ end,
+ {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 ->
- case get_affiliation(IJID, StateData) of
- none ->
- NSD = set_affiliation(
- IJID,
- member,
- StateData),
- case (NSD#state.config)#config.persistent of
- true ->
- mod_muc:store_room(
- NSD#state.server_host,
- NSD#state.host,
- NSD#state.room,
- make_opts(NSD));
- _ ->
- ok
- end,
- {next_state, normal_state, NSD};
- _ ->
- {next_state, normal_state,
- StateData}
- end;
+ MinInterval =
+ (StateData#state.config)#config.voice_request_min_interval,
+ BareFrom =
+ jlib:jid_remove_resource(jlib:jid_tolower(From)),
+ NowPriority = -now_to_usec(now()),
+ 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)),
+ route_stanza(StateData#state.jid,
+ From, Err),
+ StateData#state{last_voice_request_time
+ = Times}
+ 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 = jlib:jid_remove_resource(
- jlib:jid_tolower(From)),
- NowPriority = -now_to_usec(now()),
- 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)),
- route_stanza(
- 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)),
- route_stanza(
- 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)),
- route_stanza(
- 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),
+ ErrText =
+ <<"Voice requests are disabled in this "
+ "conference">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
+ route_stanza(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)),
+ route_stanza(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),
NSD;
- _ ->
- StateData
- end
- end;
- _ ->
- ErrText = "Only moderators can "
- "approve voice requests",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_NOT_ALLOWED(
- Lang, ErrText)),
- route_stanza(
- 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)),
- route_stanza(
- StateData#state.jid,
- From, Err),
- {next_state, normal_state, StateData}
- end;
- _ ->
- case xml:get_attr_s("type", Attrs) of
- "error" ->
- ok;
- _ ->
- handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From)
- end,
- {next_state, normal_state, StateData}
+ _ -> StateData
+ end
+ end;
+ _ ->
+ ErrText =
+ <<"Only moderators can approve voice requests">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ALLOWED(Lang,
+ ErrText)),
+ route_stanza(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)),
+ route_stanza(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
+ end;
+ _ ->
+ case xml: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, "",
- {xmlelement, "iq", _Attrs, _Els} = Packet},
+normal_state({route, From, <<"">>,
+ #xmlel{name = <<"iq">>} = Packet},
StateData) ->
case jlib:iq_query_info(Packet) of
- #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = 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_CAPTCHA) ->
- Res1 = case XMLNS of
- ?NS_MUC_ADMIN ->
- process_iq_admin(From, Type, Lang, SubEl, StateData);
- ?NS_MUC_OWNER ->
- process_iq_owner(From, Type, Lang, SubEl, StateData);
- ?NS_DISCO_INFO ->
- process_iq_disco_info(From, Type, Lang, StateData);
- ?NS_DISCO_ITEMS ->
- process_iq_disco_items(From, Type, Lang, StateData);
- ?NS_CAPTCHA ->
- process_iq_captcha(From, Type, Lang, SubEl, StateData)
- end,
- {IQRes, NewStateData} =
- case Res1 of
- {result, Res, SD} ->
- {IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", XMLNS}],
- Res
- }]},
- SD};
- {error, Error} ->
- {IQ#iq{type = error,
- sub_el = [SubEl, Error]},
- StateData}
- end,
- route_stanza(StateData#state.jid,
- From,
- jlib:iq_to_xml(IQRes)),
- case NewStateData of
- stop ->
- {stop, normal, StateData};
- _ ->
- {next_state, normal_state, NewStateData}
- end;
- reply ->
- {next_state, normal_state, StateData};
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
- route_stanza(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
+ #iq{type = Type, xmlns = XMLNS, lang = Lang,
+ sub_el = 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_CAPTCHA)) ->
+ Res1 = case XMLNS of
+ ?NS_MUC_ADMIN ->
+ process_iq_admin(From, Type, Lang, SubEl, StateData);
+ ?NS_MUC_OWNER ->
+ process_iq_owner(From, Type, Lang, SubEl, StateData);
+ ?NS_DISCO_INFO ->
+ process_iq_disco_info(From, Type, Lang, StateData);
+ ?NS_DISCO_ITEMS ->
+ process_iq_disco_items(From, Type, Lang, StateData);
+ ?NS_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 = <<"query">>,
+ attrs =
+ [{<<"xmlns">>,
+ XMLNS}],
+ children = Res}]},
+ SD};
+ {error, Error} ->
+ {IQ#iq{type = error,
+ sub_el = [SubEl, Error]},
+ StateData}
+ end,
+ route_stanza(StateData#state.jid, From,
+ jlib:iq_to_xml(IQRes)),
+ case NewStateData of
+ stop -> {stop, normal, StateData};
+ _ -> {next_state, normal_state, NewStateData}
+ end;
+ reply -> {next_state, normal_state, StateData};
+ _ ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_FEATURE_NOT_IMPLEMENTED),
+ route_stanza(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
end;
-
normal_state({route, From, Nick,
- {xmlelement, "presence", _Attrs, _Els} = Packet},
+ #xmlel{name = <<"presence">>} = Packet},
StateData) ->
Activity = get_user_activity(From, StateData),
Now = now_to_usec(now()),
MinPresenceInterval =
- trunc(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_presence_interval, 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);
- 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}
+ trunc(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_presence_interval,
+ fun(I) when is_number(I), I>=0 ->
+ 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);
+ 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}
end;
-
normal_state({route, From, ToNick,
- {xmlelement, "message", Attrs, _} = Packet},
+ #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
StateData) ->
- Type = xml:get_attr_s("type", Attrs),
- Lang = xml:get_attr_s("xml:lang", Attrs),
- case decide_fate_message(Type, Packet, From, StateData) of
- {expulse_sender, Reason} ->
- ?DEBUG(Reason, []),
- ErrorText = "This participant is kicked from the room because "
- "he sent an error message to another participant",
- 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)),
- route_stanza(
- jlib: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)),
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err);
- ToJIDs ->
- SrcIsVisitor = is_visitor(From, 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(jlib:jid_tolower(From),
- StateData#state.users),
- FromNickJID = jlib:jid_replace_resource(StateData#state.jid, FromNick),
- [route_stanza(FromNickJID, ToJID, Packet) || ToJID <- ToJIDs];
- true ->
- ErrText = "It is not allowed to send private messages",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err)
- end
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ case decide_fate_message(Type, Packet, From, StateData)
+ of
+ {expulse_sender, Reason} ->
+ ?DEBUG(Reason, []),
+ ErrorText = <<"This participant is kicked from the "
+ "room because he sent an error message "
+ "to another participant">>,
+ 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)),
+ route_stanza(jlib: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)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err);
+ ToJIDs ->
+ SrcIsVisitor = is_visitor(From, 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(jlib:jid_tolower(From),
+ StateData#state.users),
+ FromNickJID =
+ jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ [route_stanza(FromNickJID, ToJID, Packet)
+ || ToJID <- ToJIDs];
+ true ->
+ ErrText =
+ <<"It is not allowed to send private messages">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_FORBIDDEN(Lang,
+ ErrText)),
+ route_stanza(jlib: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 = jlib:make_error_reply(
- Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
- route_stanza(
- jlib: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)),
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid,
- ToNick),
- From, Err)
- end,
- {next_state, normal_state, StateData}
+ 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)),
+ route_stanza(jlib: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)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end,
+ {next_state, normal_state, StateData}
end;
-
normal_state({route, From, ToNick,
- {xmlelement, "iq", Attrs, _Els} = Packet},
+ #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- StanzaId = xml:get_attr_s("id", Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ StanzaId = xml: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)),
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid, ToNick),
- From, Err)
- end;
- ToJID ->
- {ok, #user{nick = FromNick}} =
- ?DICT:find(jlib:jid_tolower(FromFull),
- StateData#state.users),
- {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
- StanzaId, NewId,Packet),
- route_stanza(
- jlib: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)),
- route_stanza(
- jlib: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)),
- route_stanza(
- jlib: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 ->
+ 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)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end;
+ ToJID ->
+ {ok, #user{nick = FromNick}} =
+ (?DICT):find(jlib:jid_tolower(FromFull),
+ StateData#state.users),
+ {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
+ StanzaId, NewId, Packet),
+ route_stanza(jlib: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)),
+ route_stanza(jlib: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)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ ToNick),
+ From, Err)
+ end
end,
{next_state, normal_state, StateData};
-
normal_state(_Event, StateData) ->
{next_state, normal_state, StateData}.
-
-
-%%----------------------------------------------------------------------
-%% Func: handle_event/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-handle_event({service_message, Msg}, _StateName, StateData) ->
- MessagePkt = {xmlelement, "message",
- [{"type", "groupchat"}],
- [{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
- lists:foreach(
- fun({_LJID, Info}) ->
- route_stanza(
- StateData#state.jid,
- Info#user.jid,
- MessagePkt)
- end,
- ?DICT:to_list(StateData#state.users)),
- NSD = add_message_to_history("",
- StateData#state.jid,
- MessagePkt,
- StateData),
+handle_event({service_message, Msg}, _StateName,
+ StateData) ->
+ MessagePkt = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>, attrs = [],
+ children = [{xmlcdata, Msg}]}]},
+ send_multiple(
+ StateData#state.jid,
+ StateData#state.server_host,
+ StateData#state.users,
+ MessagePkt),
+ NSD = add_message_to_history(<<"">>,
+ StateData#state.jid, MessagePkt, StateData),
{next_state, normal_state, NSD};
-
-handle_event({destroy, Reason}, _StateName, StateData) ->
- {result, [], stop} =
- destroy_room(
- {xmlelement, "destroy",
- [{"xmlns", ?NS_MUC_OWNER}],
- case Reason of
- none -> [];
- _Else ->
- [{xmlelement, "reason",
- [], [{xmlcdata, Reason}]}]
- end}, StateData),
- ?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
+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),
+ ?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
[jlib:jid_to_string(StateData#state.jid), Reason]),
add_to_log(room_existence, destroyed, StateData),
{stop, shutdown, StateData};
handle_event(destroy, StateName, StateData) ->
- ?INFO_MSG("Destroyed MUC room ~s",
+ ?INFO_MSG("Destroyed MUC room ~s",
[jlib:jid_to_string(StateData#state.jid)]),
handle_event({destroy, none}, StateName, StateData);
-
-handle_event({set_affiliations, Affiliations}, StateName, StateData) ->
- {next_state, StateName, StateData#state{affiliations = Affiliations}};
-
+handle_event({set_affiliations, Affiliations},
+ StateName, StateData) ->
+ {next_state, StateName,
+ StateData#state{affiliations = Affiliations}};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: handle_sync_event/4
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {reply, Reply, NextStateName, NextStateData} |
-%% {reply, Reply, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData} |
-%% {stop, Reason, Reply, NewStateData}
-%%----------------------------------------------------------------------
-handle_sync_event({moderate_room_history, Nick}, _From, StateName, #state{history = History} = StateData) ->
- NewHistory = lqueue_filter(fun({FromNick, _TSPacket, _HaveSubject, _Timestamp, _Size}) ->
- FromNick /= Nick
- end, History),
- Moderated = History#lqueue.len - NewHistory#lqueue.len,
- {reply, {ok, integer_to_list(Moderated)}, StateName, StateData#state{history = NewHistory}};
-
-handle_sync_event(persist_recent_messages, _From, StateName, StateData) ->
- {reply, persist_muc_history(StateData), StateName, StateData};
-
-handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) ->
+handle_sync_event({moderate_room_history, Nick}, _From,
+ StateName, #state{history = History} = StateData) ->
+ NewHistory = lqueue_filter(fun ({FromNick, _TSPacket,
+ _HaveSubject, _Timestamp, _Size}) ->
+ FromNick /= Nick
+ end,
+ History),
+ Moderated = History#lqueue.len - NewHistory#lqueue.len,
+ {reply,
+ {ok, iolist_to_binary(integer_to_list(Moderated))},
+ StateName, StateData#state{history = NewHistory}};
+handle_sync_event(persist_recent_messages, _From,
+ StateName, StateData) ->
+ {reply, persist_muc_history(StateData), StateName,
+ StateData};
+handle_sync_event({get_disco_item, JID, Lang}, _From,
+ StateName, StateData) ->
Reply = get_roomdesc_reply(JID, StateData,
get_roomdesc_tail(StateData, Lang)),
{reply, Reply, StateName, StateData};
-handle_sync_event(get_config, _From, StateName, StateData) ->
- {reply, {ok, StateData#state.config}, StateName, StateData};
-handle_sync_event(get_state, _From, StateName, StateData) ->
+handle_sync_event(get_config, _From, StateName,
+ StateData) ->
+ {reply, {ok, StateData#state.config}, StateName,
+ StateData};
+handle_sync_event(get_state, _From, StateName,
+ StateData) ->
{reply, {ok, StateData}, StateName, StateData};
-handle_sync_event({change_config, Config}, _From, StateName, StateData) ->
+handle_sync_event({change_config, Config}, _From,
+ StateName, StateData) ->
{result, [], NSD} = change_config(Config, StateData),
{reply, {ok, NSD#state.config}, StateName, NSD};
-handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) ->
+handle_sync_event({change_state, NewStateData}, _From,
+ StateName, _StateData) ->
{reply, {ok, NewStateData}, StateName, NewStateData};
-handle_sync_event(_Event, _From, StateName, StateData) ->
- Reply = ok,
- {reply, Reply, StateName, StateData}.
+handle_sync_event(_Event, _From, StateName,
+ StateData) ->
+ Reply = ok, {reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
-print_state(StateData) ->
- StateData.
-
-%%----------------------------------------------------------------------
-%% Func: handle_info/3
-%% Returns: {next_state, NextStateName, NextStateData} |
-%% {next_state, NextStateName, NextStateData, Timeout} |
-%% {stop, Reason, NewStateData}
-%%----------------------------------------------------------------------
-handle_info({process_user_presence, From}, normal_state = _StateName, StateData) ->
- RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
- RoomQueue = queue:in({presence, From}, StateData#state.room_queue),
+print_state(StateData) -> StateData.
+
+handle_info({process_user_presence, From},
+ normal_state = _StateName, StateData) ->
+ RoomQueueEmpty =
+ queue:is_empty(StateData#state.room_queue),
+ RoomQueue = queue:in({presence, From},
+ StateData#state.room_queue),
StateData1 = StateData#state{room_queue = RoomQueue},
- if
- RoomQueueEmpty ->
- StateData2 = prepare_room_queue(StateData1),
- {next_state, normal_state, StateData2};
- true ->
- {next_state, normal_state, StateData1}
+ if RoomQueueEmpty ->
+ StateData2 = prepare_room_queue(StateData1),
+ {next_state, normal_state, StateData2};
+ true -> {next_state, normal_state, StateData1}
end;
-handle_info({process_user_message, From}, normal_state = _StateName, StateData) ->
- RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
- RoomQueue = queue:in({message, From}, StateData#state.room_queue),
+handle_info({process_user_message, From},
+ normal_state = _StateName, StateData) ->
+ RoomQueueEmpty =
+ queue:is_empty(StateData#state.room_queue),
+ RoomQueue = queue:in({message, From},
+ StateData#state.room_queue),
StateData1 = StateData#state{room_queue = RoomQueue},
- if
- RoomQueueEmpty ->
- StateData2 = prepare_room_queue(StateData1),
- {next_state, normal_state, StateData2};
- true ->
- {next_state, normal_state, StateData1}
+ if RoomQueueEmpty ->
+ StateData2 = prepare_room_queue(StateData1),
+ {next_state, normal_state, StateData2};
+ true -> {next_state, normal_state, StateData1}
end;
-handle_info(process_room_queue, normal_state = StateName, StateData) ->
+handle_info(process_room_queue,
+ normal_state = StateName, StateData) ->
case queue:out(StateData#state.room_queue) of
- {{value, {message, From}}, RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- Packet = Activity#activity.message,
- NewActivity = Activity#activity{message = undefined},
- StateData1 =
- store_user_activity(
- From, NewActivity, StateData),
- StateData2 =
- StateData1#state{
- room_queue = RoomQueue},
- StateData3 = prepare_room_queue(StateData2),
- process_groupchat_message(From, Packet, StateData3);
- {{value, {presence, From}}, RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- {Nick, Packet} = Activity#activity.presence,
- NewActivity = Activity#activity{presence = undefined},
- StateData1 =
- store_user_activity(
- From, NewActivity, StateData),
- StateData2 =
- StateData1#state{
- room_queue = RoomQueue},
- StateData3 = prepare_room_queue(StateData2),
- process_presence(From, Nick, Packet, StateData3);
- {empty, _} ->
- {next_state, StateName, StateData}
+ {{value, {message, From}}, RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ Packet = Activity#activity.message,
+ NewActivity = Activity#activity{message = undefined},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_queue = RoomQueue},
+ StateData3 = prepare_room_queue(StateData2),
+ process_groupchat_message(From, Packet, StateData3);
+ {{value, {presence, From}}, RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ {Nick, Packet} = Activity#activity.presence,
+ NewActivity = Activity#activity{presence = undefined},
+ StateData1 = store_user_activity(From, NewActivity,
+ StateData),
+ StateData2 = StateData1#state{room_queue = RoomQueue},
+ StateData3 = prepare_room_queue(StateData2),
+ process_presence(From, Nick, Packet, StateData3);
+ {empty, _} -> {next_state, StateName, StateData}
end;
-handle_info({captcha_succeed, From}, normal_state, StateData) ->
- NewState = case ?DICT:find(From, StateData#state.robots) of
- {ok, {Nick, Packet}} ->
- Robots = ?DICT:store(From, passed, StateData#state.robots),
- add_new_user(From, Nick, Packet, StateData#state{robots=Robots});
- _ ->
- StateData
+handle_info({captcha_succeed, From}, normal_state,
+ StateData) ->
+ NewState = case (?DICT):find(From,
+ StateData#state.robots)
+ of
+ {ok, {Nick, Packet}} ->
+ Robots = (?DICT):store(From, passed,
+ StateData#state.robots),
+ add_new_user(From, Nick, Packet,
+ StateData#state{robots = Robots});
+ _ -> StateData
end,
{next_state, normal_state, NewState};
-handle_info({captcha_failed, From}, normal_state, StateData) ->
- NewState = case ?DICT:find(From, StateData#state.robots) of
- {ok, {Nick, Packet}} ->
- Robots = ?DICT:erase(From, StateData#state.robots),
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_AUTHORIZED),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData#state{robots=Robots};
- _ ->
- StateData
+handle_info({captcha_failed, From}, normal_state,
+ StateData) ->
+ NewState = case (?DICT):find(From,
+ StateData#state.robots)
+ of
+ {ok, {Nick, Packet}} ->
+ Robots = (?DICT):erase(From, StateData#state.robots),
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_NOT_AUTHORIZED),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData#state{robots = Robots};
+ _ -> StateData
end,
{next_state, normal_state, NewState};
handle_info({migrate, Node}, StateName, StateData) ->
if Node /= node() ->
- {migrate, StateData,
- {Node, ?MODULE, start, [StateName, StateData]}, 0};
- true ->
- {next_state, StateName, StateData}
+ {migrate, StateData,
+ {Node, ?MODULE, start, [StateName, StateData]}, 0};
+ true -> {next_state, StateName, StateData}
end;
-handle_info('shutdown', _StateName, StateData) ->
- {stop, 'shutdown', StateData};
+handle_info(shutdown, _StateName, StateData) ->
+ {stop, shutdown, StateData};
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
-%%----------------------------------------------------------------------
-%% Func: terminate/3
-%% Purpose: Shutdown the fsm
-%% Returns: any
-%%----------------------------------------------------------------------
terminate({migrated, Clone}, _StateName, StateData) ->
?INFO_MSG("Migrating room ~s@~s to ~p on node ~p",
- [StateData#state.room, StateData#state.host,
- Clone, node(Clone)]),
- mod_muc:room_destroyed(StateData#state.host, StateData#state.room,
- self(), StateData#state.server_host),
+ [StateData#state.room, StateData#state.host, Clone,
+ node(Clone)]),
+ mod_muc:room_destroyed(StateData#state.host,
+ StateData#state.room, self(),
+ StateData#state.server_host),
ok;
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("Stopping MUC room ~s@~s",
[StateData#state.room, StateData#state.host]),
ReasonT = case Reason of
- shutdown -> "You are being removed from the room because"
- " of a system shutdown";
- _ -> "Room terminates"
+ shutdown ->
+ <<"You are being removed from the room "
+ "because of a system shutdown">>;
+ _ -> <<"Room terminates">>
end,
- ItemAttrs = [{"affiliation", "none"}, {"role", "none"}],
- ReasonEl = {xmlelement, "reason", [], [{xmlcdata, ReasonT}]},
- Packet = {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, [ReasonEl]},
- {xmlelement, "status", [{"code", "332"}], []}
- ]}]},
- ?DICT:fold(
- fun(LJID, Info, _) ->
- Nick = Info#user.nick,
- case Reason of
- shutdown ->
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet);
- _ -> ok
- end,
- tab_remove_online_user(LJID, StateData)
- end, [], StateData#state.users),
+ 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 = []}]}]},
+ (?DICT):fold(fun (LJID, Info, _) ->
+ Nick = Info#user.nick,
+ case Reason of
+ shutdown ->
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet);
+ _ -> ok
+ end,
+ tab_remove_online_user(LJID, StateData)
+ end,
+ [], StateData#state.users),
add_to_log(room_existence, stopped, StateData),
- if
- Reason == 'shutdown' ->
- persist_muc_history(StateData);
- true ->
- ok
+ if Reason == shutdown -> persist_muc_history(StateData);
+ true -> ok
end,
- mod_muc:room_destroyed(StateData#state.host, StateData#state.room, self(),
+ mod_muc:room_destroyed(StateData#state.host,
+ StateData#state.room, self(),
StateData#state.server_host),
ok.
@@ -930,592 +921,596 @@ terminate(Reason, _StateName, StateData) ->
%%% Internal functions
%%%----------------------------------------------------------------------
-load_history(_Host, _Room, false, Queue) ->
- Queue;
+load_history(_Host, _Room, false, Queue) -> Queue;
load_history(Host, Room, true, Queue) ->
- ?INFO_MSG("Loading history for room ~s on host ~s", [Room, Host]),
- case odbc_queries:load_roomhistory(Host, ejabberd_odbc:escape(Room)) of
- {selected, ["nick", "packet", "have_subject", "timestamp", "size"], Items} ->
- ?DEBUG("Found ~p messages on history for ~s", [length(Items), Room]),
- lists:foldl(fun(I, Q) ->
- {Nick, XML, HS, Ts, Size} = I,
- Item = {Nick,
- xml_stream:parse_element(XML),
- HS /= "0",
- calendar:gregorian_seconds_to_datetime(list_to_integer(Ts)),
- list_to_integer(Size)},
- lqueue_in(Item, Q)
- end, Queue, Items);
- _ ->
- Queue
- end.
-
-
-persist_muc_history(#state{room = Room, server_host = Server, config = #config{persistent = true} ,persist_history = true, history = Q}) ->
- ?INFO_MSG("Persisting history for room ~s on host ~s", [Room, Server]),
- Queries = lists:map(fun({FromNick, Packet, HaveSubject, Timestamp, Size}) ->
- odbc_queries:add_roomhistory_sql(
- ejabberd_odbc:escape(Room),
- ejabberd_odbc:escape(FromNick),
- ejabberd_odbc:escape(xml:element_to_binary(Packet)),
- atom_to_list(HaveSubject),
- integer_to_list(calendar:datetime_to_gregorian_seconds(Timestamp)),
- integer_to_list(Size))
- end, lqueue_to_list(Q)),
- odbc_queries:clear_and_add_roomhistory(Server,ejabberd_odbc:escape(Room), Queries),
- {ok, {persisted, length(Queries)}};
- %% en mod_muc, cuando se levantan los muc persistentes, si se crea, y el flag persist_history esta en true,
- %% se levantan los mensajes persistentes tb.
-
-persist_muc_history(_) ->
- {ok, not_persistent}.
+ ?INFO_MSG("Loading history for room ~s on host ~s",
+ [Room, Host]),
+ case odbc_queries:load_roomhistory(Host,
+ ejabberd_odbc:escape(Room))
+ of
+ {selected,
+ [<<"nick">>, <<"packet">>, <<"have_subject">>,
+ <<"timestamp">>, <<"size">>],
+ Items} ->
+ ?DEBUG("Found ~p messages on history for ~s",
+ [length(Items), Room]),
+ lists:foldl(fun (I, Q) ->
+ [Nick, XML, HS, Ts, Size] = I,
+ Item = {Nick, xml_stream:parse_element(XML),
+ HS /= <<"0">>,
+ calendar:gregorian_seconds_to_datetime(jlib:binary_to_integer(Ts)),
+ jlib:binary_to_integer(Size)},
+ lqueue_in(Item, Q)
+ end,
+ Queue, Items);
+ _ -> Queue
+ end.
+
+persist_muc_history(#state{room = Room,
+ server_host = Server,
+ config = #config{persistent = true},
+ persist_history = true, history = Q}) ->
+ ?INFO_MSG("Persisting history for room ~s on host ~s",
+ [Room, Server]),
+ Queries = lists:map(fun ({FromNick, Packet, HaveSubject,
+ Timestamp, Size}) ->
+ odbc_queries:add_roomhistory_sql(ejabberd_odbc:escape(Room),
+ ejabberd_odbc:escape(FromNick),
+ ejabberd_odbc:escape(xml:element_to_binary(Packet)),
+ iolist_to_binary(atom_to_list(HaveSubject)),
+ iolist_to_binary(integer_to_list(calendar:datetime_to_gregorian_seconds(Timestamp))),
+ iolist_to_binary(integer_to_list(Size)))
+ end,
+ lqueue_to_list(Q)),
+ odbc_queries:clear_and_add_roomhistory(Server,
+ ejabberd_odbc:escape(Room), Queries),
+ {ok, {persisted, length(Queries)}};
+%% en mod_muc, cuando se levantan los muc persistentes, si se crea, y el flag persist_history esta en true,
+%% se levantan los mensajes persistentes tb.
+persist_muc_history(_) -> {ok, not_persistent}.
route(Pid, From, ToNick, Packet) ->
- ?GEN_FSM:send_event(Pid, {route, From, ToNick, Packet}).
+ (?GEN_FSM):send_event(Pid,
+ {route, From, ToNick, Packet}).
-process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
+process_groupchat_message(From,
+ #xmlel{name = <<"message">>, attrs = Attrs} = Packet,
StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
case is_user_online(From, StateData) orelse
- is_user_allowed_message_nonparticipant(From, StateData) of
- true ->
- {FromNick, Role} = get_participant_data(From, StateData),
- if
- (Role == moderator) or (Role == participant)
- or ((StateData#state.config)#config.moderated == false) ->
- {NewStateData1, IsAllowed} =
- case check_subject(Packet) of
- false ->
- {StateData, true};
- Subject ->
- case can_change_subject(Role,
- StateData) of
- true ->
- NSD =
- StateData#state{
- subject = Subject,
- subject_author =
- FromNick},
- case (NSD#state.config)#config.persistent of
- true ->
- mod_muc:store_room(
- NSD#state.server_host,
- NSD#state.host,
- NSD#state.room,
- make_opts(NSD));
- _ ->
- ok
- end,
- {NSD, true};
- _ ->
- {StateData, false}
- end
- end,
- case IsAllowed of
- true ->
- lists:foreach(
- fun({_LJID, Info}) ->
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid,
- FromNick),
- Info#user.jid,
- Packet)
- end,
- ?DICT:to_list(StateData#state.users)),
- NewStateData2 =
- add_message_to_history(FromNick,
- From,
- Packet,
- NewStateData1),
- {next_state, normal_state, NewStateData2};
- _ ->
- 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");
- _ ->
- ?ERRT_FORBIDDEN(
- Lang,
- "Only moderators "
- "are allowed to change the subject in this room")
- end,
- route_stanza(
- StateData#state.jid,
- From,
- jlib:make_error_reply(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)),
- route_stanza(
- StateData#state.jid,
- From, 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)),
- route_stanza(StateData#state.jid, From, Err),
- {next_state, normal_state, StateData}
+ is_user_allowed_message_nonparticipant(From, StateData)
+ of
+ true ->
+ {FromNick, Role} = get_participant_data(From,
+ StateData),
+ if (Role == moderator) or (Role == participant) or
+ ((StateData#state.config)#config.moderated == false) ->
+ {NewStateData1, IsAllowed} = case check_subject(Packet)
+ of
+ false -> {StateData, true};
+ Subject ->
+ case
+ can_change_subject(Role,
+ StateData)
+ of
+ true ->
+ NSD =
+ StateData#state{subject
+ =
+ Subject,
+ subject_author
+ =
+ FromNick},
+ case
+ (NSD#state.config)#config.persistent
+ of
+ true ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host,
+ NSD#state.room,
+ make_opts(NSD));
+ _ -> ok
+ end,
+ {NSD, true};
+ _ -> {StateData, false}
+ end
+ end,
+ case IsAllowed of
+ true ->
+ send_multiple(
+ jlib:jid_replace_resource(StateData#state.jid, FromNick),
+ StateData#state.server_host,
+ StateData#state.users,
+ Packet),
+ NewStateData2 = add_message_to_history(FromNick, From,
+ Packet,
+ NewStateData1),
+ {next_state, normal_state, NewStateData2};
+ _ ->
+ 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">>);
+ _ ->
+ ?ERRT_FORBIDDEN(Lang,
+ <<"Only moderators are allowed to change "
+ "the subject in this room">>)
+ end,
+ route_stanza(StateData#state.jid, From,
+ jlib:make_error_reply(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)),
+ route_stanza(StateData#state.jid, From, 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)),
+ route_stanza(StateData#state.jid, From, Err),
+ {next_state, normal_state, StateData}
end.
-%% @doc Check if this non participant can send message to room.
-%%
-%% XEP-0045 v1.23:
-%% 7.9 Sending a Message to All Occupants
-%% 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.
-is_user_allowed_message_nonparticipant(JID, StateData) ->
+is_user_allowed_message_nonparticipant(JID,
+ StateData) ->
case get_service_affiliation(JID, StateData) of
- owner ->
- true;
- _ -> false
+ owner -> true;
+ _ -> false
end.
-%% @doc Get information of this participant, or default values.
-%% If the JID is not a participant, return values for a service message.
get_participant_data(From, StateData) ->
- case ?DICT:find(jlib:jid_tolower(From), StateData#state.users) of
- {ok, #user{nick = FromNick, role = Role}} ->
- {FromNick, Role};
- error ->
- {"", moderator}
+ case (?DICT):find(jlib:jid_tolower(From),
+ StateData#state.users)
+ of
+ {ok, #user{nick = FromNick, role = Role}} ->
+ {FromNick, Role};
+ error -> {<<"">>, moderator}
end.
-
-process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet,
+process_presence(From, Nick,
+ #xmlel{name = <<"presence">>, attrs = Attrs} = Packet,
StateData) ->
- Type = xml:get_attr_s("type", Attrs),
- Lang = xml:get_attr_s("xml:lang", Attrs),
- StateData1 =
- case Type of
- "unavailable" ->
- case is_user_online(From, StateData) of
- true ->
- NewPacket = case {(StateData#state.config)#config.allow_visitor_status,
- is_visitor(From, StateData)} of
- {false, true} ->
- strip_status(Packet);
- _ ->
- Packet
- end,
- NewState =
- add_user_presence_un(From, NewPacket, StateData),
- case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [_, _ | _]} -> ok;
- _ -> send_new_presence(From, NewState)
- end,
- Reason = case xml:get_subtag(NewPacket, "status") of
- false -> "";
- Status_el -> xml:get_tag_cdata(Status_el)
- end,
- remove_online_user(From, NewState, Reason);
- _ ->
- StateData
- end;
- "error" ->
- case is_user_online(From, StateData) of
- true ->
- ErrorText = "This participant is kicked from the room because "
- "he sent an error presence",
- expulse_participant(Packet, From, StateData,
- translate:translate(Lang, ErrorText));
- _ ->
- StateData
- end;
- "" ->
- case is_user_online(From, StateData) of
- true ->
- case is_nick_change(From, Nick, StateData) of
- true ->
- case {nick_collision(From, Nick, StateData),
- mod_muc:can_use_nick(
- StateData#state.server_host,
- StateData#state.host, From, Nick),
- {(StateData#state.config)#config.allow_visitor_nickchange,
- is_visitor(From, StateData)}} of
- {_, _, {false, true}} ->
- ErrText = "Visitors are not allowed to change their nicknames in this room",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_NOT_ALLOWED(Lang, ErrText)),
- route_stanza(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid,
- Nick),
- From, Err),
- StateData;
- {true, _, _} ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
- ErrText = "That nickname is already in use by another occupant",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_CONFLICT(Lang, ErrText)),
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid,
- Nick), % TODO: s/Nick/""/
- From, Err),
- StateData;
- {_, false, _} ->
- ErrText = "That nickname is registered by another person",
- Err = jlib:make_error_reply(
- Packet,
- ?ERRT_CONFLICT(Lang, ErrText)),
- route_stanza(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid,
- Nick),
- From, Err),
- StateData;
- _ ->
- change_nick(From, Nick, StateData)
- end;
- _NotNickChange ->
- Stanza = case {(StateData#state.config)#config.allow_visitor_status,
- is_visitor(From, StateData)} of
- {false, true} ->
- strip_status(Packet);
- _Allowed ->
- Packet
- end,
- NewState = add_user_presence(From, Stanza, StateData),
- send_new_presence(From, NewState),
- NewState
- end;
- _ ->
- add_new_user(From, Nick, Packet, StateData)
- end;
- _ ->
- StateData
- end,
- case (not (StateData1#state.config)#config.persistent) andalso
- (?DICT:to_list(StateData1#state.users) == []) of
- true ->
- ?INFO_MSG("Destroyed MUC room ~s because it's temporary and empty",
- [jlib:jid_to_string(StateData#state.jid)]),
- add_to_log(room_existence, destroyed, StateData),
- {stop, normal, StateData1};
- _ ->
- {next_state, normal_state, StateData1}
+ Type = xml:get_attr_s(<<"type">>, Attrs),
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
+ StateData1 = case Type of
+ <<"unavailable">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ NewPacket = case
+ {(StateData#state.config)#config.allow_visitor_status,
+ is_visitor(From, StateData)}
+ of
+ {false, true} ->
+ strip_status(Packet);
+ _ -> Packet
+ end,
+ NewState = add_user_presence_un(From, NewPacket,
+ StateData),
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [_, _ | _]} -> ok;
+ _ -> send_new_presence(From, NewState)
+ end,
+ Reason = case xml:get_subtag(NewPacket,
+ <<"status">>)
+ of
+ false -> <<"">>;
+ Status_el ->
+ xml:get_tag_cdata(Status_el)
+ end,
+ remove_online_user(From, NewState, Reason);
+ _ -> StateData
+ end;
+ <<"error">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ ErrorText =
+ <<"This participant is kicked from the "
+ "room because he sent an error presence">>,
+ expulse_participant(Packet, From, StateData,
+ translate:translate(Lang,
+ ErrorText));
+ _ -> StateData
+ end;
+ <<"">> ->
+ case is_user_online(From, StateData) of
+ true ->
+ case is_nick_change(From, Nick, StateData) of
+ true ->
+ case {nick_collision(From, Nick, StateData),
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host,
+ From, Nick),
+ {(StateData#state.config)#config.allow_visitor_nickchange,
+ is_visitor(From, StateData)}}
+ of
+ {_, _, {false, true}} ->
+ ErrText =
+ <<"Visitors are not allowed to change their "
+ "nicknames in this room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_ALLOWED(Lang,
+ ErrText)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {true, _, _} ->
+ Lang = xml:get_attr_s(<<"xml:lang">>,
+ Attrs),
+ ErrText =
+ <<"That nickname is already in use by another "
+ "occupant">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang,
+ ErrText)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick), % TODO: s/Nick/""/
+ From, Err),
+ StateData;
+ {_, false, _} ->
+ ErrText =
+ <<"That nickname is registered by another "
+ "person">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang,
+ ErrText)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ _ -> change_nick(From, Nick, StateData)
+ end;
+ _NotNickChange ->
+ Stanza = case
+ {(StateData#state.config)#config.allow_visitor_status,
+ is_visitor(From, StateData)}
+ of
+ {false, true} ->
+ strip_status(Packet);
+ _Allowed -> Packet
+ end,
+ NewState = add_user_presence(From, Stanza,
+ StateData),
+ send_new_presence(From, NewState),
+ NewState
+ end;
+ _ -> add_new_user(From, Nick, Packet, StateData)
+ end;
+ _ -> StateData
+ end,
+ case not (StateData1#state.config)#config.persistent
+ andalso (?DICT):to_list(StateData1#state.users) == []
+ of
+ true ->
+ ?INFO_MSG("Destroyed MUC room ~s because it's temporary "
+ "and empty",
+ [jlib:jid_to_string(StateData#state.jid)]),
+ add_to_log(room_existence, destroyed, StateData),
+ {stop, normal, StateData1};
+ _ -> {next_state, normal_state, StateData1}
end.
is_user_online(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
- ?DICT:is_key(LJID, StateData#state.users).
+ (?DICT):is_key(LJID, StateData#state.users).
-%% Check if the user is occupant of the room, or at least is an admin or owner.
is_occupant_or_admin(JID, StateData) ->
FAffiliation = get_affiliation(JID, StateData),
FRole = get_role(JID, StateData),
- case (FRole /= none) orelse
- (FAffiliation == admin) orelse
- (FAffiliation == owner) of
- true ->
- true;
- _ ->
- false
+ case FRole /= none orelse
+ FAffiliation == admin orelse FAffiliation == owner
+ of
+ true -> true;
+ _ -> false
end.
-%%%
-%%% Handle IQ queries of vCard
-%%%
-is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= "" ->
+is_user_online_iq(StanzaId, JID, StateData)
+ when JID#jid.lresource /= <<"">> ->
{is_user_online(JID, StateData), StanzaId, JID};
-is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == "" ->
+is_user_online_iq(StanzaId, JID, StateData)
+ when JID#jid.lresource == <<"">> ->
try stanzaid_unpack(StanzaId) of
- {OriginalId, Resource} ->
- JIDWithResource = jlib:jid_replace_resource(JID, Resource),
- {is_user_online(JIDWithResource, StateData),
- OriginalId, JIDWithResource}
+ {OriginalId, Resource} ->
+ JIDWithResource = jlib:jid_replace_resource(JID,
+ Resource),
+ {is_user_online(JIDWithResource, StateData), OriginalId,
+ JIDWithResource}
catch
- _:_ ->
- {is_user_online(JID, StateData), StanzaId, JID}
+ _:_ -> {is_user_online(JID, StateData), StanzaId, JID}
end.
-handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) ->
+handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
+ Packet) ->
ToBareJID = jlib: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 ->
+ 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) ->
+handle_iq_vcard2(_FromFull, ToJID, _ToBareJID,
+ _StanzaId, NewId, _IQ, Packet) ->
{ToJID, change_stanzaid(NewId, Packet)}.
stanzaid_pack(OriginalId, Resource) ->
- "berd"++base64:encode_to_string("ejab\0" ++ OriginalId ++ "\0" ++ Resource).
-stanzaid_unpack("berd"++StanzaIdBase64) ->
- StanzaId = base64:decode_to_string(StanzaIdBase64),
- ["ejab", OriginalId, Resource] = string:tokens(StanzaId, "\0"),
+ <<"berd",
+ (jlib:encode_base64(<<"ejab\000",
+ OriginalId/binary, "\000",
+ Resource/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) ->
- {xmlelement, Name, Attrs, Els} = jlib:remove_attr("id", Packet),
- {xmlelement, Name, [{"id", NewId} | Attrs], Els}.
+ #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) ->
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"
+ 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"
+ 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
+ <<"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
+ <<"owner">> -> owner;
+ <<"admin">> -> admin;
+ <<"member">> -> member;
+ <<"outcast">> -> outcast;
+ <<"none">> -> none
end.
-%% Decide the fate of the message and its sender
-%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-decide_fate_message("error", Packet, From, StateData) ->
- %% Make a preliminary decision
+decide_fate_message(<<"error">>, Packet, From,
+ StateData) ->
PD = case check_error_kick(Packet) of
- %% If this is an error stanza and its condition matches a criteria
- true ->
- Reason = io_lib:format("This participant is considered a ghost and is expulsed: ~s",
- [jlib:jid_to_string(From)]),
- {expulse_sender, Reason};
- false ->
- continue_delivery
+ %% If this is an error stanza and its condition matches a criteria
+ true ->
+ Reason =
+ io_lib:format("This participant is considered a ghost "
+ "and is expulsed: ~s",
+ [jlib:jid_to_string(From)]),
+ {expulse_sender, Reason};
+ false -> continue_delivery
end,
case PD of
- {expulse_sender, R} ->
- case is_user_online(From, StateData) of
- true ->
- {expulse_sender, R};
- false ->
- forget_message
- end;
- Other ->
- Other
+ {expulse_sender, R} ->
+ case is_user_online(From, StateData) of
+ true -> {expulse_sender, R};
+ false -> forget_message
+ 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
+ <<"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.
+ case catch get_error_condition2(Packet) of
+ {condition, ErrorCondition} -> ErrorCondition;
+ {'EXIT', _} -> <<"badformed error stanza">>
+ end.
+
get_error_condition2(Packet) ->
- {xmlelement, _, _, EEls} = xml:get_subtag(Packet, "error"),
- [Condition] = [Name || {xmlelement, Name, [{"xmlns", ?NS_STANZAS}], []} <- EEls],
- {condition, Condition}.
+ #xmlel{children = EEls} = xml:get_subtag(Packet,
+ <<"error">>),
+ [Condition] = [Name
+ || #xmlel{name = Name,
+ attrs = [{<<"xmlns">>, ?NS_STANZAS}],
+ children = []}
+ <- EEls],
+ {condition, Condition}.
expulse_participant(Packet, From, StateData, Reason1) ->
- ErrorCondition = get_error_condition(Packet),
- Reason2 = io_lib:format(Reason1 ++ ": " ++ "~s", [ErrorCondition]),
- NewState = add_user_presence_un(
- From,
- {xmlelement, "presence",
- [{"type", "unavailable"}],
- [{xmlelement, "status", [],
- [{xmlcdata, Reason2}]
- }]},
- StateData),
- send_new_presence(From, NewState),
- remove_online_user(From, NewState).
-
+ ErrorCondition = get_error_condition(Packet),
+ Reason2 = iolist_to_binary(
+ io_lib:format(binary_to_list(Reason1) ++ ": " ++ "~s",
+ [ErrorCondition])),
+ NewState = add_user_presence_un(From,
+ #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>,
+ <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"status">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ Reason2}]}]},
+ StateData),
+ send_new_presence(From, NewState),
+ remove_online_user(From, NewState).
set_affiliation(JID, Affiliation, StateData) ->
- set_affiliation(JID, Affiliation, StateData, "").
+ set_affiliation(JID, Affiliation, StateData, <<"">>).
set_affiliation(JID, Affiliation, StateData, Reason) ->
LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
Affiliations = case Affiliation of
- none ->
- ?DICT:erase(LJID,
- StateData#state.affiliations);
- _ ->
- ?DICT:store(LJID,
- {Affiliation, Reason},
+ none ->
+ (?DICT):erase(LJID, StateData#state.affiliations);
+ _ ->
+ (?DICT):store(LJID, {Affiliation, Reason},
StateData#state.affiliations)
end,
StateData#state{affiliations = Affiliations}.
get_affiliation(JID, StateData) ->
- {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = StateData#state.access,
- Res =
- case acl:match_rule(StateData#state.server_host, AccessAdmin, JID) of
- allow ->
- owner;
+ {_AccessRoute, _AccessCreate, AccessAdmin,
+ _AccessPersistent} =
+ StateData#state.access,
+ Res = case acl:match_rule(StateData#state.server_host,
+ AccessAdmin, JID)
+ of
+ allow -> owner;
_ ->
LJID = jlib:jid_tolower(JID),
- case ?DICT:find(LJID, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- LJID1 = jlib:jid_remove_resource(LJID),
- case ?DICT:find(LJID1, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- LJID2 = setelement(1, LJID, ""),
- case ?DICT:find(LJID2, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- LJID3 = jlib:jid_remove_resource(LJID2),
- case ?DICT:find(LJID3, StateData#state.affiliations) of
- {ok, Affiliation} ->
- Affiliation;
- _ ->
- none
- end
- end
- end
+ case (?DICT):find(LJID, StateData#state.affiliations) of
+ {ok, Affiliation} -> Affiliation;
+ _ ->
+ LJID1 = jlib:jid_remove_resource(LJID),
+ case (?DICT):find(LJID1, StateData#state.affiliations)
+ of
+ {ok, Affiliation} -> Affiliation;
+ _ ->
+ LJID2 = setelement(1, LJID, <<"">>),
+ case (?DICT):find(LJID2,
+ StateData#state.affiliations)
+ of
+ {ok, Affiliation} -> Affiliation;
+ _ ->
+ LJID3 = jlib:jid_remove_resource(LJID2),
+ case (?DICT):find(LJID3,
+ StateData#state.affiliations)
+ of
+ {ok, Affiliation} -> Affiliation;
+ _ -> none
+ end
+ end
+ end
end
- end,
+ end,
case Res of
- {A, _Reason} ->
- A;
- _ ->
- Res
+ {A, _Reason} -> A;
+ _ -> Res
end.
get_service_affiliation(JID, StateData) ->
- {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} =
+ {_AccessRoute, _AccessCreate, AccessAdmin,
+ _AccessPersistent} =
StateData#state.access,
- case acl:match_rule(StateData#state.server_host, AccessAdmin, JID) of
- allow ->
- owner;
- _ ->
- none
+ case acl:match_rule(StateData#state.server_host,
+ AccessAdmin, JID)
+ of
+ allow -> owner;
+ _ -> none
end.
set_role(JID, Role, StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
- {U, S, ""} ->
- ?DICT:fold(
- fun(J, _, Js) ->
- case J of
- {U, S, _} ->
- [J | Js];
- _ ->
- Js
- end
- end, [], StateData#state.users);
- _ ->
- case ?DICT:is_key(LJID, StateData#state.users) of
- true ->
- [LJID];
- _ ->
- []
- end
- end,
- {Users, Nicks}
- = case Role of
- none ->
- lists:foldl(fun(J, {Us, Ns}) ->
- NewNs =
- case ?DICT:find(J, Us) of
- {ok, #user{nick = Nick}} ->
- ?DICT:erase(Nick, Ns);
- _ ->
- Ns
- end,
- {?DICT:erase(J, Us), NewNs}
- end,
- {StateData#state.users, StateData#state.nicks},
- LJIDs);
+ {U, S, <<"">>} ->
+ (?DICT):fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end,
+ [], StateData#state.users);
_ ->
- {lists:foldl(fun(J, Us) ->
- {ok, User} = ?DICT:find(J, Us),
- ?DICT:store(J,
- User#user{role = Role},
- Us)
- end, StateData#state.users, LJIDs),
- StateData#state.nicks}
- end,
+ case (?DICT):is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
+ end,
+ {Users, Nicks} = case Role of
+ none ->
+ lists:foldl(fun (J, {Us, Ns}) ->
+ NewNs = case (?DICT):find(J, Us)
+ of
+ {ok,
+ #user{nick = Nick}} ->
+ (?DICT):erase(Nick,
+ Ns);
+ _ -> Ns
+ end,
+ {(?DICT):erase(J, Us), NewNs}
+ end,
+ {StateData#state.users,
+ StateData#state.nicks},
+ LJIDs);
+ _ ->
+ {lists:foldl(fun (J, Us) ->
+ {ok, User} = (?DICT):find(J,
+ Us),
+ (?DICT):store(J,
+ User#user{role =
+ Role},
+ Us)
+ end,
+ StateData#state.users, LJIDs),
+ StateData#state.nicks}
+ end,
StateData#state{users = Users, nicks = Nicks}.
get_role(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
- case ?DICT:find(LJID, StateData#state.users) of
- {ok, #user{role = Role}} ->
- Role;
- _ ->
- none
+ case (?DICT):find(LJID, StateData#state.users) of
+ {ok, #user{role = Role}} -> Role;
+ _ -> none
end.
get_default_role(Affiliation, StateData) ->
case Affiliation of
- owner -> moderator;
- admin -> moderator;
- member -> participant;
- outcast -> none;
- none ->
- case (StateData#state.config)#config.members_only of
- true ->
- none;
- _ ->
- case (StateData#state.config)#config.members_by_default of
- true ->
- participant;
- _ ->
- visitor
- end
- end
+ owner -> moderator;
+ admin -> moderator;
+ member -> participant;
+ outcast -> none;
+ none ->
+ case (StateData#state.config)#config.members_only of
+ true -> none;
+ _ ->
+ case (StateData#state.config)#config.members_by_default
+ of
+ true -> participant;
+ _ -> visitor
+ end
+ end
end.
is_visitor(Jid, StateData) ->
@@ -1527,269 +1522,254 @@ is_moderator(Jid, StateData) ->
get_max_users(StateData) ->
MaxUsers = (StateData#state.config)#config.max_users,
ServiceMaxUsers = get_service_max_users(StateData),
- if
- MaxUsers =< ServiceMaxUsers -> MaxUsers;
- true -> ServiceMaxUsers
+ if MaxUsers =< ServiceMaxUsers -> MaxUsers;
+ true -> ServiceMaxUsers
end.
get_service_max_users(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, max_users, ?MAX_USERS_DEFAULT).
+ mod_muc, max_users,
+ fun(I) when is_integer(I), I>0 -> I end,
+ ?MAX_USERS_DEFAULT).
get_max_users_admin_threshold(StateData) ->
gen_mod:get_module_opt(StateData#state.server_host,
- mod_muc, max_users_admin_threshold, 5).
+ mod_muc, max_users_admin_threshold,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 5).
get_user_activity(JID, StateData) ->
case treap:lookup(jlib:jid_tolower(JID),
- StateData#state.activity) of
- {ok, _P, A} -> A;
- error ->
- MessageShaper =
- shaper:new(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, user_message_shaper, none)),
- PresenceShaper =
- shaper:new(gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, user_presence_shaper, none)),
- #activity{message_shaper = MessageShaper,
- presence_shaper = PresenceShaper}
+ StateData#state.activity)
+ of
+ {ok, _P, A} -> A;
+ error ->
+ MessageShaper =
+ shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, user_message_shaper,
+ fun(A) when is_atom(A) -> A end,
+ none)),
+ PresenceShaper =
+ shaper:new(gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, user_presence_shaper,
+ fun(A) when is_atom(A) -> A end,
+ none)),
+ #activity{message_shaper = MessageShaper,
+ presence_shaper = PresenceShaper}
end.
store_user_activity(JID, UserActivity, StateData) ->
MinMessageInterval =
- gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_message_interval, 0),
+ gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_message_interval,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 0),
MinPresenceInterval =
- gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, min_presence_interval, 0),
+ gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, min_presence_interval,
+ fun(I) when is_integer(I), I>=0 -> I end,
+ 0),
Key = jlib:jid_tolower(JID),
Now = now_to_usec(now()),
- Activity1 = clean_treap(StateData#state.activity, {1, -Now}),
- Activity =
- case treap:lookup(Key, Activity1) of
- {ok, _P, _A} ->
- treap:delete(Key, Activity1);
- error ->
- Activity1
- end,
- StateData1 =
- case (MinMessageInterval == 0) andalso
- (MinPresenceInterval == 0) andalso
- (UserActivity#activity.message_shaper == none) andalso
- (UserActivity#activity.presence_shaper == none) andalso
- (UserActivity#activity.message == undefined) andalso
- (UserActivity#activity.presence == undefined) of
- true ->
- StateData#state{activity = Activity};
- false ->
- case (UserActivity#activity.message == undefined) andalso
- (UserActivity#activity.presence == undefined) of
- true ->
- {_, MessageShaperInterval} =
- shaper:update(UserActivity#activity.message_shaper,
- 100000),
- {_, PresenceShaperInterval} =
- shaper:update(UserActivity#activity.presence_shaper,
- 100000),
- Delay = lists:max([MessageShaperInterval,
- PresenceShaperInterval,
- MinMessageInterval * 1000,
- MinPresenceInterval * 1000]) * 1000,
- Priority = {1, -(Now + Delay)},
- StateData#state{
- activity = treap:insert(
- Key,
- Priority,
- UserActivity,
- Activity)};
- false ->
- Priority = {0, 0},
- StateData#state{
- activity = treap:insert(
- Key,
- Priority,
- UserActivity,
- Activity)}
- end
- end,
+ Activity1 = clean_treap(StateData#state.activity,
+ {1, -Now}),
+ Activity = case treap:lookup(Key, Activity1) of
+ {ok, _P, _A} -> treap:delete(Key, Activity1);
+ error -> Activity1
+ end,
+ StateData1 = case MinMessageInterval == 0 andalso
+ MinPresenceInterval == 0 andalso
+ UserActivity#activity.message_shaper == none andalso
+ UserActivity#activity.presence_shaper == none
+ andalso
+ UserActivity#activity.message == undefined andalso
+ UserActivity#activity.presence == undefined
+ of
+ true -> StateData#state{activity = Activity};
+ false ->
+ case UserActivity#activity.message == undefined andalso
+ UserActivity#activity.presence == undefined
+ of
+ true ->
+ {_, MessageShaperInterval} =
+ shaper:update(UserActivity#activity.message_shaper,
+ 100000),
+ {_, PresenceShaperInterval} =
+ shaper:update(UserActivity#activity.presence_shaper,
+ 100000),
+ Delay = lists:max([MessageShaperInterval,
+ PresenceShaperInterval,
+ MinMessageInterval * 1000,
+ MinPresenceInterval * 1000])
+ * 1000,
+ Priority = {1, -(Now + Delay)},
+ StateData#state{activity =
+ treap:insert(Key, Priority,
+ UserActivity,
+ Activity)};
+ false ->
+ Priority = {0, 0},
+ StateData#state{activity =
+ treap:insert(Key, Priority,
+ UserActivity,
+ Activity)}
+ end
+ end,
StateData1.
clean_treap(Treap, CleanPriority) ->
case treap:is_empty(Treap) of
- true ->
- Treap;
- false ->
- {_Key, Priority, _Value} = treap:get_root(Treap),
- if
- Priority > CleanPriority ->
- clean_treap(treap:delete_root(Treap), CleanPriority);
- true ->
- Treap
- end
+ true -> Treap;
+ false ->
+ {_Key, Priority, _Value} = treap:get_root(Treap),
+ if Priority > CleanPriority ->
+ clean_treap(treap:delete_root(Treap), CleanPriority);
+ true -> Treap
+ end
end.
-
prepare_room_queue(StateData) ->
case queue:out(StateData#state.room_queue) of
- {{value, {message, From}}, _RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- Packet = Activity#activity.message,
- Size = element_size(Packet),
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- erlang:send_after(
- RoomShaperInterval, self(),
- process_room_queue),
- StateData#state{
- room_shaper = RoomShaper};
- {{value, {presence, From}}, _RoomQueue} ->
- Activity = get_user_activity(From, StateData),
- {_Nick, Packet} = Activity#activity.presence,
- Size = element_size(Packet),
- {RoomShaper, RoomShaperInterval} =
- shaper:update(StateData#state.room_shaper, Size),
- erlang:send_after(
- RoomShaperInterval, self(),
- process_room_queue),
- StateData#state{
- room_shaper = RoomShaper};
- {empty, _} ->
- StateData
+ {{value, {message, From}}, _RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ Packet = Activity#activity.message,
+ Size = element_size(Packet),
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ erlang:send_after(RoomShaperInterval, self(),
+ process_room_queue),
+ StateData#state{room_shaper = RoomShaper};
+ {{value, {presence, From}}, _RoomQueue} ->
+ Activity = get_user_activity(From, StateData),
+ {_Nick, Packet} = Activity#activity.presence,
+ Size = element_size(Packet),
+ {RoomShaper, RoomShaperInterval} =
+ shaper:update(StateData#state.room_shaper, Size),
+ erlang:send_after(RoomShaperInterval, self(),
+ process_room_queue),
+ StateData#state{room_shaper = RoomShaper};
+ {empty, _} -> StateData
end.
-
add_online_user(JID, Nick, Role, StateData) ->
LJID = jlib:jid_tolower(JID),
- Users = ?DICT:store(LJID,
- #user{jid = JID,
- nick = Nick,
- role = Role},
- StateData#state.users),
+ Users = (?DICT):store(LJID,
+ #user{jid = JID, nick = Nick, role = Role},
+ StateData#state.users),
add_to_log(join, Nick, StateData),
- Nicks = ?DICT:update(Nick,
- fun(Entry) ->
- case lists:member(LJID, Entry) of
- true ->
- Entry;
- false ->
- [LJID|Entry]
- end
- end,
- [LJID],
- StateData#state.nicks),
+ Nicks = (?DICT):update(Nick,
+ fun (Entry) ->
+ case lists:member(LJID, Entry) of
+ true -> Entry;
+ false -> [LJID | Entry]
+ end
+ end,
+ [LJID], StateData#state.nicks),
tab_add_online_user(JID, StateData),
StateData#state{users = Users, nicks = Nicks}.
remove_online_user(JID, StateData) ->
- remove_online_user(JID, StateData, "").
+ remove_online_user(JID, StateData, <<"">>).
remove_online_user(JID, StateData, Reason) ->
LJID = jlib:jid_tolower(JID),
- {ok, #user{nick = Nick}} =
- ?DICT:find(LJID, StateData#state.users),
+ {ok, #user{nick = Nick}} = (?DICT):find(LJID,
+ StateData#state.users),
add_to_log(leave, {Nick, Reason}, StateData),
tab_remove_online_user(JID, StateData),
- Users = ?DICT:erase(LJID, StateData#state.users),
- Nicks = case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [LJID]} ->
- ?DICT:erase(Nick, StateData#state.nicks);
- {ok, U} ->
- ?DICT:store(Nick, U -- [LJID], StateData#state.nicks);
- error ->
- StateData#state.nicks
+ Users = (?DICT):erase(LJID, StateData#state.users),
+ Nicks = case (?DICT):find(Nick, StateData#state.nicks)
+ of
+ {ok, [LJID]} ->
+ (?DICT):erase(Nick, StateData#state.nicks);
+ {ok, U} ->
+ (?DICT):store(Nick, U -- [LJID], StateData#state.nicks);
+ error -> StateData#state.nicks
end,
StateData#state{users = Users, nicks = Nicks}.
-
-filter_presence({xmlelement, "presence", Attrs, Els}) ->
- FEls = lists:filter(
- fun(El) ->
- case El of
- {xmlcdata, _} ->
- false;
- {xmlelement, _Name1, Attrs1, _Els1} ->
- XMLNS = xml:get_attr_s("xmlns", Attrs1),
- case XMLNS of
- ?NS_MUC ++ _ ->
- false;
- _ ->
- true
- end
- end
- end, Els),
- {xmlelement, "presence", Attrs, FEls}.
-
-strip_status({xmlelement, "presence", Attrs, Els}) ->
- FEls = lists:filter(
- fun({xmlelement, "status", _Attrs1, _Els1}) ->
- false;
- (_) -> true
- end, Els),
- {xmlelement, "presence", Attrs, FEls}.
+filter_presence(#xmlel{name = <<"presence">>,
+ attrs = Attrs, children = Els}) ->
+ FEls = lists:filter(fun (El) ->
+ case El of
+ {xmlcdata, _} -> false;
+ #xmlel{attrs = Attrs1} ->
+ XMLNS = xml: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}.
add_user_presence(JID, Presence, StateData) ->
LJID = jlib:jid_tolower(JID),
FPresence = filter_presence(Presence),
- Users =
- ?DICT:update(
- LJID,
- fun(#user{} = User) ->
- User#user{last_presence = FPresence}
- end, StateData#state.users),
+ Users = (?DICT):update(LJID,
+ fun (#user{} = User) ->
+ User#user{last_presence = FPresence}
+ end,
+ StateData#state.users),
StateData#state{users = Users}.
add_user_presence_un(JID, Presence, StateData) ->
LJID = jlib:jid_tolower(JID),
FPresence = filter_presence(Presence),
- Users =
- ?DICT:update(
- LJID,
- fun(#user{} = User) ->
- User#user{last_presence = FPresence,
- role = none}
- end, StateData#state.users),
+ Users = (?DICT):update(LJID,
+ fun (#user{} = User) ->
+ User#user{last_presence = FPresence,
+ role = none}
+ end,
+ StateData#state.users),
StateData#state{users = Users}.
-
-%% Find and return a list of the full JIDs of the users of Nick.
-%% Return jid record.
find_jids_by_nick(Nick, StateData) ->
- case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [User]} ->
- [jlib:make_jid(User)];
- {ok, Users} ->
- [jlib:make_jid(LJID) || LJID <- Users];
- error ->
- false
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [User]} -> [jlib:make_jid(User)];
+ {ok, Users} -> [jlib:make_jid(LJID) || LJID <- Users];
+ error -> false
end.
-%% Find and return the full JID of the user of Nick with
-%% highest-priority presence. Return jid record.
find_jid_by_nick(Nick, StateData) ->
- case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, [User]} ->
- jlib:make_jid(User);
- {ok, [FirstUser|Users]} ->
- #user{last_presence = FirstPresence} =
- ?DICT:fetch(FirstUser, StateData#state.users),
- {LJID, _} =
- lists:foldl(fun(Compare, {HighestUser, HighestPresence}) ->
- #user{last_presence = P1} =
- ?DICT:fetch(Compare, StateData#state.users),
- case higher_presence(P1, HighestPresence) of
- true ->
- {Compare, P1};
- false ->
- {HighestUser, HighestPresence}
- end
- end, {FirstUser, FirstPresence}, Users),
- jlib:make_jid(LJID);
- error ->
- false
+ case (?DICT):find(Nick, StateData#state.nicks) of
+ {ok, [User]} -> jlib:make_jid(User);
+ {ok, [FirstUser | Users]} ->
+ #user{last_presence = FirstPresence} =
+ (?DICT):fetch(FirstUser, StateData#state.users),
+ {LJID, _} = lists:foldl(fun (Compare,
+ {HighestUser, HighestPresence}) ->
+ #user{last_presence = P1} =
+ (?DICT):fetch(Compare,
+ StateData#state.users),
+ case higher_presence(P1,
+ HighestPresence)
+ of
+ true -> {Compare, P1};
+ false ->
+ {HighestUser, HighestPresence}
+ end
+ end,
+ {FirstUser, FirstPresence}, Users),
+ jlib:make_jid(LJID);
+ error -> false
end.
higher_presence(Pres1, Pres2) ->
@@ -1798,1301 +1778,1357 @@ higher_presence(Pres1, Pres2) ->
Pri1 > Pri2.
get_priority_from_presence(PresencePacket) ->
- case xml:get_subtag(PresencePacket, "priority") of
- false ->
- 0;
- SubEl ->
- case catch list_to_integer(xml:get_tag_cdata(SubEl)) of
- P when is_integer(P) ->
- P;
- _ ->
- 0
- end
+ case xml:get_subtag(PresencePacket, <<"priority">>) of
+ false -> 0;
+ SubEl ->
+ case catch
+ jlib:binary_to_integer(xml:get_tag_cdata(SubEl))
+ of
+ P when is_integer(P) -> P;
+ _ -> 0
+ end
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)),
- Nick.
+ [{_, #user{nick = Nick}}] = lists:filter(fun ({_,
+ #user{jid = FJid}}) ->
+ FJid == Jid
+ end,
+ (?DICT):to_list(StateData#state.users)),
+ Nick.
is_nick_change(JID, Nick, StateData) ->
LJID = jlib:jid_tolower(JID),
case Nick of
- "" ->
- false;
- _ ->
- {ok, #user{nick = OldNick}} =
- ?DICT:find(LJID, StateData#state.users),
- Nick /= OldNick
+ <<"">> -> false;
+ _ ->
+ {ok, #user{nick = OldNick}} = (?DICT):find(LJID,
+ StateData#state.users),
+ Nick /= OldNick
end.
nick_collision(User, Nick, StateData) ->
UserOfNick = find_jid_by_nick(Nick, StateData),
- %% if nick is not used, or is used by another resource of the same
- %% user, it's ok.
UserOfNick /= false andalso
- jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick)) /=
- jlib:jid_remove_resource(jlib:jid_tolower(User)).
+ jlib:jid_remove_resource(jlib:jid_tolower(UserOfNick))
+ /= jlib:jid_remove_resource(jlib:jid_tolower(User)).
-add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
- Lang = xml:get_attr_s("xml:lang", Attrs),
+add_new_user(From, Nick,
+ #xmlel{attrs = Attrs, children = Els} = Packet,
+ StateData) ->
+ Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
MaxUsers = get_max_users(StateData),
- MaxAdminUsers = MaxUsers + get_max_users_admin_threshold(StateData),
- NUsers = dict:fold(fun(_, _, Acc) -> Acc + 1 end, 0,
+ MaxAdminUsers = MaxUsers +
+ get_max_users_admin_threshold(StateData),
+ NUsers = dict:fold(fun (_, _, Acc) -> Acc + 1 end, 0,
StateData#state.users),
Affiliation = get_affiliation(From, StateData),
- ServiceAffiliation = get_service_affiliation(From, StateData),
+ ServiceAffiliation = get_service_affiliation(From,
+ StateData),
NConferences = tab_count_user(From),
- MaxConferences = gen_mod:get_module_opt(
- StateData#state.server_host,
- mod_muc, max_user_conferences, 10),
+ MaxConferences =
+ gen_mod:get_module_opt(StateData#state.server_host,
+ mod_muc, max_user_conferences,
+ fun(I) when is_integer(I), I>0 -> I end,
+ 10),
Collision = nick_collision(From, Nick, StateData),
case {(ServiceAffiliation == owner orelse
- ((Affiliation == admin orelse Affiliation == owner) andalso
- NUsers < MaxAdminUsers) orelse
- NUsers < MaxUsers) andalso
- NConferences < MaxConferences,
+ (Affiliation == admin orelse Affiliation == owner)
+ andalso NUsers < MaxAdminUsers
+ orelse NUsers < MaxUsers)
+ andalso NConferences < MaxConferences,
Collision,
- mod_muc:can_use_nick(
- StateData#state.server_host,
- StateData#state.host, From, Nick),
- get_default_role(Affiliation, StateData)} of
- {false, _, _, _} ->
- % max user reached and user is not admin or owner
- Err = jlib:make_error_reply(
- Packet,
- ?ERR_SERVICE_UNAVAILABLE),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, _, _, none} ->
- Err = jlib:make_error_reply(
- Packet,
- case Affiliation of
- outcast ->
- ErrText = "You have been banned from this room",
- ?ERRT_FORBIDDEN(Lang, ErrText);
- _ ->
- ErrText = "Membership is required to enter this room",
- ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText)
- end),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, true, _, _} ->
- ErrText = "That nickname is already in use by another occupant",
- Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)),
- route_stanza(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, _, false, _} ->
- ErrText = "That nickname is registered by another person",
- Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)),
- route_stanza(
- % TODO: s/Nick/""/
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- From, Err),
- StateData;
- {_, _, _, Role} ->
- case check_password(ServiceAffiliation, Affiliation,
- Els, From, StateData) of
- true ->
- NewState =
- add_user_presence(
- From, Packet,
- add_online_user(From, Nick, Role, StateData)),
- if not (NewState#state.config)#config.anonymous ->
- WPacket = {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "body", [],
- [{xmlcdata, translate:translate(
- Lang,
- "This room is not anonymous")}]},
- {xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "status", [{"code", "100"}], []}]}]},
- route_stanza(
- StateData#state.jid,
- From, WPacket);
- true ->
- ok
- end,
- send_existing_presences(From, NewState),
- send_new_presence(From, NewState),
- Shift = count_stanza_shift(Nick, Els, NewState),
- case send_history(From, Shift, NewState) of
- true ->
- ok;
- _ ->
- send_subject(From, Lang, StateData)
- end,
- case NewState#state.just_created of
- true ->
- NewState#state{just_created = false};
- false ->
- Robots = ?DICT:erase(From, StateData#state.robots),
- NewState#state{robots = Robots}
- end;
- nopass ->
- ErrText = "A password is required to enter this room",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData;
- captcha_required ->
- SID = xml:get_attr_s("id", Attrs),
- RoomJID = StateData#state.jid,
- To = jlib:jid_replace_resource(RoomJID, Nick),
- Limiter = {From#jid.luser, From#jid.lserver},
- case ejabberd_captcha:create_captcha(
- SID, RoomJID, To, Lang, Limiter, From) of
- {ok, ID, CaptchaEls} ->
- MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls},
- Robots = ?DICT:store(From,
- {Nick, Packet}, StateData#state.robots),
- route_stanza(RoomJID, From, MsgPkt),
- StateData#state{robots = Robots};
- {error, limit} ->
- ErrText = "Too many CAPTCHA requests",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData;
- _ ->
- ErrText = "Unable to generate a CAPTCHA",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData
- end;
- _ ->
- ErrText = "Incorrect password",
- Err = jlib:make_error_reply(
- Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)),
- route_stanza( % TODO: s/Nick/""/
- jlib:jid_replace_resource(
- StateData#state.jid, Nick),
- From, Err),
- StateData
- end
+ mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host, From, Nick),
+ get_default_role(Affiliation, StateData)}
+ of
+ {false, _, _, _} ->
+ Err = jlib:make_error_reply(Packet,
+ ?ERR_SERVICE_UNAVAILABLE),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
+ {_, _, _, none} ->
+ Err = jlib:make_error_reply(Packet,
+ case Affiliation of
+ outcast ->
+ ErrText =
+ <<"You have been banned from this room">>,
+ ?ERRT_FORBIDDEN(Lang, ErrText);
+ _ ->
+ ErrText =
+ <<"Membership is required to enter this room">>,
+ ?ERRT_REGISTRATION_REQUIRED(Lang,
+ ErrText)
+ end),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid, Nick),
+ From, Err),
+ StateData;
+ {_, true, _, _} ->
+ ErrText = <<"That nickname is already in use by another occupant">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang, ErrText)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {_, _, false, _} ->
+ ErrText = <<"That nickname is registered by another person">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_CONFLICT(Lang, ErrText)),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ {_, _, _, Role} ->
+ case check_password(ServiceAffiliation, Affiliation,
+ Els, From, StateData)
+ of
+ true ->
+ NewState = add_user_presence(From, Packet,
+ add_online_user(From, Nick, Role,
+ StateData)),
+ if not (NewState#state.config)#config.anonymous ->
+ WPacket = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"body">>,
+ attrs = [],
+ children =
+ [{xmlcdata,
+ translate:translate(Lang,
+ <<"This room is not anonymous">>)}]},
+ #xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"status">>,
+ attrs =
+ [{<<"code">>,
+ <<"100">>}],
+ children =
+ []}]}]},
+ route_stanza(StateData#state.jid, From, WPacket);
+ true -> ok
+ end,
+ send_existing_presences(From, NewState),
+ send_new_presence(From, NewState),
+ Shift = count_stanza_shift(Nick, Els, NewState),
+ case send_history(From, Shift, NewState) of
+ true -> ok;
+ _ -> send_subject(From, Lang, StateData)
+ end,
+ case NewState#state.just_created of
+ true -> NewState#state{just_created = false};
+ false ->
+ Robots = (?DICT):erase(From, StateData#state.robots),
+ NewState#state{robots = Robots}
+ end;
+ nopass ->
+ ErrText = <<"A password is required to enter this room">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_AUTHORIZED(Lang,
+ ErrText)),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ captcha_required ->
+ SID = xml:get_attr_s(<<"id">>, Attrs),
+ RoomJID = StateData#state.jid,
+ To = jlib:jid_replace_resource(RoomJID, Nick),
+ Limiter = {From#jid.luser, From#jid.lserver},
+ case ejabberd_captcha:create_captcha(SID, RoomJID, To,
+ Lang, Limiter, From)
+ of
+ {ok, ID, CaptchaEls} ->
+ MsgPkt = #xmlel{name = <<"message">>,
+ attrs = [{<<"id">>, ID}],
+ children = CaptchaEls},
+ Robots = (?DICT):store(From, {Nick, Packet},
+ StateData#state.robots),
+ route_stanza(RoomJID, From, MsgPkt),
+ StateData#state{robots = Robots};
+ {error, limit} ->
+ ErrText = <<"Too many CAPTCHA requests">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_RESOURCE_CONSTRAINT(Lang,
+ ErrText)),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData;
+ _ ->
+ ErrText = <<"Unable to generate a CAPTCHA">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_INTERNAL_SERVER_ERROR(Lang,
+ ErrText)),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData
+ end;
+ _ ->
+ ErrText = <<"Incorrect password">>,
+ Err = jlib:make_error_reply(Packet,
+ ?ERRT_NOT_AUTHORIZED(Lang,
+ ErrText)),
+ route_stanza % TODO: s/Nick/""/
+ (jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ From, Err),
+ StateData
+ end
end.
-check_password(owner, _Affiliation, _Els, _From, _StateData) ->
+check_password(owner, _Affiliation, _Els, _From,
+ _StateData) ->
%% Don't check pass if user is owner in MUC service (access_admin option)
true;
-check_password(_ServiceAffiliation, Affiliation, Els, From, StateData) ->
- case (StateData#state.config)#config.password_protected of
- false ->
- check_captcha(Affiliation, From, StateData);
- true ->
- Pass = extract_password(Els),
- case Pass of
- false ->
- nopass;
- _ ->
- case (StateData#state.config)#config.password of
- Pass ->
- true;
- _ ->
- false
- end
- end
+check_password(_ServiceAffiliation, Affiliation, Els,
+ From, StateData) ->
+ case (StateData#state.config)#config.password_protected
+ of
+ false -> check_captcha(Affiliation, From, StateData);
+ true ->
+ Pass = extract_password(Els),
+ case Pass of
+ false -> nopass;
+ _ ->
+ case (StateData#state.config)#config.password of
+ Pass -> true;
+ _ -> false
+ end
+ end
end.
check_captcha(Affiliation, From, StateData) ->
case (StateData#state.config)#config.captcha_protected
- andalso ejabberd_captcha:is_feature_available() of
- true when Affiliation == none ->
- case ?DICT:find(From, StateData#state.robots) of
- {ok, passed} ->
- true;
- _ ->
- WList = (StateData#state.config)#config.captcha_whitelist,
- #jid{luser = U, lserver = S, lresource = R} = From,
- case ?SETS:is_element({U, S, R}, WList) of
- true ->
- true;
- false ->
- case ?SETS:is_element({U, S, ""}, WList) of
- true ->
- true;
- false ->
- case ?SETS:is_element({"", S, ""}, WList) of
- true ->
- true;
- false ->
- captcha_required
- end
- end
- end
- end;
- _ ->
- true
+ andalso ejabberd_captcha:is_feature_available()
+ of
+ true when Affiliation == none ->
+ case (?DICT):find(From, StateData#state.robots) of
+ {ok, passed} -> true;
+ _ ->
+ WList =
+ (StateData#state.config)#config.captcha_whitelist,
+ #jid{luser = U, lserver = S, lresource = R} = From,
+ case (?SETS):is_element({U, S, R}, WList) of
+ true -> true;
+ false ->
+ case (?SETS):is_element({U, S, <<"">>}, WList) of
+ true -> true;
+ false ->
+ case (?SETS):is_element({<<"">>, S, <<"">>}, WList)
+ of
+ true -> true;
+ false -> captcha_required
+ end
+ end
+ end
+ end;
+ _ -> true
end.
-extract_password([]) ->
- false;
-extract_password([{xmlelement, _Name, Attrs, _SubEls} = El | Els]) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC ->
- case xml:get_subtag(El, "password") of
- false ->
- false;
- SubEl ->
- xml:get_tag_cdata(SubEl)
- end;
- _ ->
- extract_password(Els)
+extract_password([]) -> false;
+extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC ->
+ case xml:get_subtag(El, <<"password">>) of
+ false -> false;
+ SubEl -> xml:get_tag_cdata(SubEl)
+ end;
+ _ -> extract_password(Els)
end;
-extract_password([_ | Els]) ->
- extract_password(Els).
+extract_password([_ | Els]) -> extract_password(Els).
count_stanza_shift(Nick, Els, StateData) ->
HL = lqueue_to_list(StateData#state.history),
- Since = extract_history(Els, "since"),
+ Since = extract_history(Els, <<"since">>),
Shift0 = case Since of
- false ->
- 0;
- _ ->
- Sin = calendar:datetime_to_gregorian_seconds(Since),
- count_seconds_shift(Sin, HL)
+ false -> 0;
+ _ ->
+ Sin = calendar:datetime_to_gregorian_seconds(Since),
+ count_seconds_shift(Sin, HL)
end,
- Seconds = extract_history(Els, "seconds"),
+ Seconds = extract_history(Els, <<"seconds">>),
Shift1 = case Seconds of
- false ->
- 0;
- _ ->
- Sec = calendar:datetime_to_gregorian_seconds(
- calendar:now_to_universal_time(now())) - Seconds,
- count_seconds_shift(Sec, HL)
+ false -> 0;
+ _ ->
+ Sec =
+ calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(now()))
+ - Seconds,
+ count_seconds_shift(Sec, HL)
end,
- MaxStanzas = extract_history(Els, "maxstanzas"),
+ MaxStanzas = extract_history(Els, <<"maxstanzas">>),
Shift2 = case MaxStanzas of
- false ->
- 0;
- _ ->
- count_maxstanzas_shift(MaxStanzas, HL)
+ false -> 0;
+ _ -> count_maxstanzas_shift(MaxStanzas, HL)
end,
- MaxChars = extract_history(Els, "maxchars"),
+ MaxChars = extract_history(Els, <<"maxchars">>),
Shift3 = case MaxChars of
- false ->
- 0;
- _ ->
- count_maxchars_shift(Nick, MaxChars, HL)
+ false -> 0;
+ _ -> count_maxchars_shift(Nick, MaxChars, HL)
end,
lists:max([Shift0, Shift1, Shift2, Shift3]).
count_seconds_shift(Seconds, HistoryList) ->
- lists:sum(
- lists:map(
- fun({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) ->
- T = calendar:datetime_to_gregorian_seconds(TimeStamp),
- if
- T < Seconds ->
- 1;
- true ->
- 0
- end
- end, HistoryList)).
+ lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject,
+ TimeStamp, _Size}) ->
+ T =
+ calendar:datetime_to_gregorian_seconds(TimeStamp),
+ if T < Seconds -> 1;
+ true -> 0
+ end
+ end,
+ HistoryList)).
count_maxstanzas_shift(MaxStanzas, HistoryList) ->
S = length(HistoryList) - MaxStanzas,
- if
- S =< 0 ->
- 0;
- true ->
- S
+ if S =< 0 -> 0;
+ true -> S
end.
count_maxchars_shift(Nick, MaxSize, HistoryList) ->
- NLen = string:len(Nick) + 1,
- Sizes = lists:map(
- fun({_Nick, _Packet, _HaveSubject, _TimeStamp, Size}) ->
- Size + NLen
- end, HistoryList),
+ NLen = byte_size(Nick) + 1,
+ Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject,
+ _TimeStamp, Size}) ->
+ Size + NLen
+ end,
+ HistoryList),
calc_shift(MaxSize, Sizes).
calc_shift(MaxSize, Sizes) ->
Total = lists:sum(Sizes),
calc_shift(MaxSize, Total, 0, Sizes).
-calc_shift(_MaxSize, _Size, Shift, []) ->
- Shift;
+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)
+ if MaxSize >= Size -> Shift;
+ true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
end.
-extract_history([], _Type) ->
- false;
-extract_history([{xmlelement, _Name, Attrs, _SubEls} = El | Els], Type) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC ->
- AttrVal = xml: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 list_to_integer(AttrVal) of
- IntVal when is_integer(IntVal) and (IntVal >= 0) ->
- IntVal;
- _ ->
- false
- end
- end;
- _ ->
- extract_history(Els, Type)
+extract_history([], _Type) -> false;
+extract_history([#xmlel{attrs = Attrs} = El | Els],
+ Type) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC ->
+ AttrVal = xml: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).
-
send_update_presence(JID, StateData) ->
- send_update_presence(JID, "", StateData).
+ send_update_presence(JID, <<"">>, StateData).
send_update_presence(JID, Reason, StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
- {U, S, ""} ->
- ?DICT:fold(
- fun(J, _, Js) ->
- case J of
- {U, S, _} ->
- [J | Js];
- _ ->
- Js
- end
- end, [], StateData#state.users);
- _ ->
- case ?DICT:is_key(LJID, StateData#state.users) of
- true ->
- [LJID];
- _ ->
- []
- end
+ {U, S, <<"">>} ->
+ (?DICT):fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end,
+ [], StateData#state.users);
+ _ ->
+ case (?DICT):is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
end,
- lists:foreach(fun(J) ->
+ lists:foreach(fun (J) ->
send_new_presence(J, Reason, StateData)
- end, LJIDs).
+ end,
+ LJIDs).
send_new_presence(NJID, StateData) ->
- send_new_presence(NJID, "", StateData).
+ send_new_presence(NJID, <<"">>, StateData).
send_new_presence(NJID, Reason, StateData) ->
- %% First, find the nick associated with this JID.
- #user{nick = Nick} = ?DICT:fetch(jlib:jid_tolower(NJID), StateData#state.users),
- %% Then find the JID using this nick with highest priority.
+ #user{nick = Nick} =
+ (?DICT):fetch(jlib:jid_tolower(NJID),
+ StateData#state.users),
LJID = find_jid_by_nick(Nick, StateData),
- %% Then we get the presence data we're supposed to send.
- {ok, #user{jid = RealJID,
- role = Role,
- last_presence = Presence}} =
- ?DICT:find(jlib:jid_tolower(LJID), StateData#state.users),
+ {ok,
+ #user{jid = RealJID, role = Role,
+ last_presence = Presence}} =
+ (?DICT):find(jlib:jid_tolower(LJID),
+ StateData#state.users),
Affiliation = get_affiliation(LJID, StateData),
SAffiliation = affiliation_to_list(Affiliation),
SRole = role_to_list(Role),
- lists:foreach(
- fun({_LJID, Info}) ->
- ItemAttrs =
- case (Info#user.role == moderator) orelse
- ((StateData#state.config)#config.anonymous == false) of
- true ->
- [{"jid", jlib:jid_to_string(RealJID)},
- {"affiliation", SAffiliation},
- {"role", SRole}];
- _ ->
- [{"affiliation", SAffiliation},
- {"role", SRole}]
+ lists:foreach(fun ({_LJID, Info}) ->
+ ItemAttrs = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib: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,
+ Status = case StateData#state.just_created of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>, <<"201">>}],
+ children = []}];
+ false -> []
+ end,
+ Status2 = case
+ (StateData#state.config)#config.anonymous
+ == false
+ andalso NJID == Info#user.jid
+ of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>, <<"100">>}],
+ children = []}
+ | Status];
+ false -> Status
+ end,
+ Status3 = case NJID == Info#user.jid of
+ true ->
+ [#xmlel{name = <<"status">>,
+ attrs =
+ [{<<"code">>, <<"110">>}],
+ children = []}
+ | Status2];
+ false -> Status2
+ end,
+ Packet = xml:append_subtags(Presence,
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"item">>,
+ attrs
+ =
+ ItemAttrs,
+ children
+ =
+ ItemEls}
+ | Status3]}]),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet)
end,
- ItemEls = case Reason of
- "" ->
- [];
- _ ->
- [{xmlelement, "reason", [],
- [{xmlcdata, Reason}]}]
- end,
- Status = case StateData#state.just_created of
- true ->
- [{xmlelement, "status", [{"code", "201"}], []}];
- false ->
- []
- end,
- Status2 = case ((StateData#state.config)#config.anonymous==false)
- andalso (NJID == Info#user.jid) of
- true ->
- [{xmlelement, "status", [{"code", "100"}], []}
- | Status];
- false ->
- Status
- end,
- Status3 = case NJID == Info#user.jid of
- true ->
- [{xmlelement, "status", [{"code", "110"}], []}
- | Status2];
- false ->
- Status2
- end,
- Packet = xml:append_subtags(
- Presence,
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, ItemEls} | Status3]}]),
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet)
- end, ?DICT:to_list(StateData#state.users)).
-
+ (?DICT):to_list(StateData#state.users)).
send_existing_presences(ToJID, StateData) ->
LToJID = jlib:jid_tolower(ToJID),
- {ok, #user{jid = RealToJID,
- role = Role}} =
- ?DICT:find(LToJID, StateData#state.users),
- lists:foreach(
- fun({FromNick, _Users}) ->
- LJID = find_jid_by_nick(FromNick, StateData),
- #user{jid = FromJID,
- role = FromRole,
- last_presence = Presence
- } = ?DICT:fetch(jlib:jid_tolower(LJID), StateData#state.users),
- case RealToJID of
- FromJID ->
- ok;
- _ ->
- FromAffiliation = get_affiliation(LJID, StateData),
- ItemAttrs =
- case (Role == moderator) orelse
- ((StateData#state.config)#config.anonymous ==
- false) of
- true ->
- [{"jid", jlib: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 = xml:append_subtags(
- Presence,
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, []}]}]),
- route_stanza(
- jlib:jid_replace_resource(
- StateData#state.jid, FromNick),
- RealToJID,
- Packet)
- end
- end, ?DICT:to_list(StateData#state.nicks)).
-
+ {ok, #user{jid = RealToJID, role = Role}} =
+ (?DICT):find(LToJID, StateData#state.users),
+ lists:foreach(fun ({FromNick, _Users}) ->
+ LJID = find_jid_by_nick(FromNick, StateData),
+ #user{jid = FromJID, role = FromRole,
+ last_presence = Presence} =
+ (?DICT):fetch(jlib:jid_tolower(LJID),
+ StateData#state.users),
+ case RealToJID of
+ FromJID -> ok;
+ _ ->
+ FromAffiliation = get_affiliation(LJID,
+ StateData),
+ ItemAttrs = case Role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib: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 = xml:append_subtags(Presence,
+ [#xmlel{name =
+ <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ ItemAttrs,
+ children
+ =
+ []}]}]),
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ RealToJID, Packet)
+ end
+ end,
+ (?DICT):to_list(StateData#state.nicks)).
now_to_usec({MSec, Sec, USec}) ->
- (MSec*1000000 + Sec)*1000000 + USec.
-
+ (MSec * 1000000 + Sec) * 1000000 + USec.
change_nick(JID, Nick, StateData) ->
LJID = jlib:jid_tolower(JID),
- {ok, #user{nick = OldNick}} =
- ?DICT:find(LJID, StateData#state.users),
- Users =
- ?DICT:update(
- LJID,
- fun(#user{} = User) ->
- User#user{nick = Nick}
- end, StateData#state.users),
- OldNickUsers = ?DICT:fetch(OldNick, StateData#state.nicks),
- NewNickUsers = case ?DICT:find(Nick, StateData#state.nicks) of
- {ok, U} -> U;
- error -> []
+ {ok, #user{nick = OldNick}} = (?DICT):find(LJID,
+ StateData#state.users),
+ Users = (?DICT):update(LJID,
+ fun (#user{} = User) -> User#user{nick = Nick} end,
+ StateData#state.users),
+ OldNickUsers = (?DICT):fetch(OldNick,
+ StateData#state.nicks),
+ NewNickUsers = case (?DICT):find(Nick,
+ StateData#state.nicks)
+ of
+ {ok, U} -> U;
+ error -> []
end,
- %% Send unavailable presence from the old nick if it's no longer
- %% used.
SendOldUnavailable = length(OldNickUsers) == 1,
- %% If we send unavailable presence from the old nick, we should
- %% probably send presence from the new nick, in order not to
- %% confuse clients. Otherwise, do it only if the new nick was
- %% unused.
SendNewAvailable = SendOldUnavailable orelse
- NewNickUsers == [],
- Nicks =
- case OldNickUsers of
- [LJID] ->
- ?DICT:store(Nick, [LJID|NewNickUsers],
- ?DICT:erase(OldNick, StateData#state.nicks));
- [_|_] ->
- ?DICT:store(Nick, [LJID|NewNickUsers],
- ?DICT:store(OldNick, OldNickUsers -- [LJID],
- StateData#state.nicks))
- end,
- NewStateData = StateData#state{users = Users, nicks = Nicks},
- send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable),
+ NewNickUsers == [],
+ Nicks = case OldNickUsers of
+ [LJID] ->
+ (?DICT):store(Nick, [LJID | NewNickUsers],
+ (?DICT):erase(OldNick, StateData#state.nicks));
+ [_ | _] ->
+ (?DICT):store(Nick, [LJID | NewNickUsers],
+ (?DICT):store(OldNick, OldNickUsers -- [LJID],
+ StateData#state.nicks))
+ end,
+ NewStateData = StateData#state{users = Users,
+ nicks = Nicks},
+ send_nick_changing(JID, OldNick, NewStateData,
+ SendOldUnavailable, SendNewAvailable),
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
send_nick_changing(JID, OldNick, StateData,
- SendOldUnavailable, SendNewAvailable) ->
- {ok, #user{jid = RealJID,
- nick = Nick,
- role = Role,
- last_presence = Presence}} =
- ?DICT:find(jlib:jid_tolower(JID), StateData#state.users),
+ SendOldUnavailable, SendNewAvailable) ->
+ {ok,
+ #user{jid = RealJID, nick = Nick, role = Role,
+ last_presence = Presence}} =
+ (?DICT):find(jlib: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}) ->
- ItemAttrs1 =
- case (Info#user.role == moderator) orelse
- ((StateData#state.config)#config.anonymous == false) of
- true ->
- [{"jid", jlib: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", jlib:jid_to_string(RealJID)},
- {"affiliation", SAffiliation},
- {"role", SRole}];
- _ ->
- [{"affiliation", SAffiliation},
- {"role", SRole}]
+ lists:foreach(fun ({_LJID, Info}) ->
+ ItemAttrs1 = case Info#user.role == moderator orelse
+ (StateData#state.config)#config.anonymous
+ == false
+ of
+ true ->
+ [{<<"jid">>,
+ jlib: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">>,
+ jlib:jid_to_string(RealJID)},
+ {<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole}];
+ _ ->
+ [{<<"affiliation">>, SAffiliation},
+ {<<"role">>, SRole}]
+ 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 =
+ []}]}]},
+ Packet2 = xml:append_subtags(Presence,
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name
+ =
+ <<"item">>,
+ attrs
+ =
+ ItemAttrs2,
+ children
+ =
+ []}]}]),
+ if SendOldUnavailable ->
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ OldNick),
+ Info#user.jid, Packet1);
+ true -> ok
+ end,
+ if SendNewAvailable ->
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet2);
+ true -> ok
+ end
end,
- Packet1 =
- {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs1, []},
- {xmlelement, "status", [{"code", "303"}], []}]}]},
- Packet2 = xml:append_subtags(
- Presence,
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs2, []}]}]),
- if SendOldUnavailable ->
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, OldNick),
- Info#user.jid,
- Packet1);
- true ->
- ok
- end,
- if SendNewAvailable ->
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet2);
- true ->
- ok
- end
- end, ?DICT:to_list(StateData#state.users)).
-
+ (?DICT):to_list(StateData#state.users)).
lqueue_new(Max) ->
- #lqueue{queue = queue:new(),
- len = 0,
- max = Max}.
+ #lqueue{queue = queue:new(), len = 0, max = Max}.
-%% If the message queue limit is set to 0, do not store messages.
-lqueue_in(_Item, LQ = #lqueue{max = 0}) ->
- LQ;
+lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
%% Otherwise, rotate messages in the queue store.
-lqueue_in(Item, #lqueue{queue = Q1, len = Len, max = Max}) ->
+lqueue_in(Item,
+ #lqueue{queue = Q1, len = Len, max = Max}) ->
Q2 = queue:in(Item, Q1),
- if
- Len >= Max ->
- Q3 = lqueue_cut(Q2, Len - Max + 1),
- #lqueue{queue = Q3, len = Max, max = Max};
- true ->
- #lqueue{queue = Q2, len = Len + 1, max = Max}
+ if Len >= Max ->
+ Q3 = lqueue_cut(Q2, Len - Max + 1),
+ #lqueue{queue = Q3, len = Max, max = Max};
+ true -> #lqueue{queue = Q2, len = Len + 1, max = Max}
end.
-lqueue_cut(Q, 0) ->
- Q;
+lqueue_cut(Q, 0) -> Q;
lqueue_cut(Q, N) ->
- {_, Q1} = queue:out(Q),
- lqueue_cut(Q1, N - 1).
+ {_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1).
lqueue_to_list(#lqueue{queue = Q1}) ->
queue:to_list(Q1).
lqueue_filter(F, #lqueue{queue = Q1} = LQ) ->
- Q2 = queue:filter(F, Q1),
- LQ#lqueue{queue = Q2, len = queue:len(Q2)}.
-
-add_message_to_history(FromNick, FromJID, Packet, StateData) ->
- HaveSubject = case xml:get_subtag(Packet, "subject") of
- false ->
- false;
- _ ->
- true
+ Q2 = queue:filter(F, Q1),
+ LQ#lqueue{queue = Q2, len = queue:len(Q2)}.
+
+add_message_to_history(FromNick, FromJID, Packet,
+ StateData) ->
+ HaveSubject = case xml:get_subtag(Packet, <<"subject">>)
+ of
+ false -> false;
+ _ -> true
end,
TimeStamp = calendar:now_to_universal_time(now()),
- %% Chatroom history is stored as XMPP packets, so
- %% the decision to include the original sender's JID or not is based on the
- %% chatroom configuration when the message was originally sent.
- %% Also, if the chatroom is anonymous, even moderators will not get the real JID
- SenderJid = case ((StateData#state.config)#config.anonymous) of
- true -> StateData#state.jid;
- false -> FromJID
- end,
+ SenderJid = case
+ (StateData#state.config)#config.anonymous
+ of
+ true -> StateData#state.jid;
+ false -> FromJID
+ end,
TSPacket = xml:append_subtags(Packet,
- [jlib:timestamp_to_xml(TimeStamp, utc, SenderJid, ""),
- %% TODO: Delete the next line once XEP-0091 is Obsolete
- jlib:timestamp_to_xml(TimeStamp)]),
- SPacket = jlib:replace_from_to(
- jlib:jid_replace_resource(StateData#state.jid, FromNick),
- StateData#state.jid,
- TSPacket),
+ [jlib:timestamp_to_xml(TimeStamp, utc,
+ SenderJid, <<"">>),
+ jlib:timestamp_to_xml(TimeStamp)]),
+ SPacket =
+ jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid,
+ FromNick),
+ StateData#state.jid, TSPacket),
Size = element_size(SPacket),
- Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size},
+ Q1 = lqueue_in({FromNick, TSPacket, HaveSubject,
+ TimeStamp, Size},
StateData#state.history),
add_to_log(text, {FromNick, Packet}, StateData),
StateData#state{history = Q1}.
send_history(JID, Shift, StateData) ->
- lists:foldl(
- fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) ->
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- JID,
- Packet),
- B or HaveSubject
- end, false, lists:nthtail(Shift, lqueue_to_list(StateData#state.history))).
-
+ lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp,
+ _Size},
+ B) ->
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ JID, Packet),
+ B or HaveSubject
+ end,
+ false,
+ lists:nthtail(Shift,
+ lqueue_to_list(StateData#state.history))).
send_subject(JID, Lang, StateData) ->
case StateData#state.subject_author of
- "" ->
- ok;
- Nick ->
- Subject = StateData#state.subject,
- Packet = {xmlelement, "message", [{"type", "groupchat"}],
- [{xmlelement, "subject", [], [{xmlcdata, Subject}]},
- {xmlelement, "body", [],
- [{xmlcdata,
- Nick ++
- translate:translate(Lang,
- " has set the subject to: ") ++
- Subject}]}]},
- route_stanza(
- StateData#state.jid,
- JID,
- Packet)
+ <<"">> -> ok;
+ Nick ->
+ Subject = StateData#state.subject,
+ Packet = #xmlel{name = <<"message">>,
+ attrs = [{<<"type">>, <<"groupchat">>}],
+ children =
+ [#xmlel{name = <<"subject">>, attrs = [],
+ children = [{xmlcdata, Subject}]},
+ #xmlel{name = <<"body">>, attrs = [],
+ children =
+ [{xmlcdata,
+ <<Nick/binary,
+ (translate:translate(Lang,
+ <<" has set the subject to: ">>))/binary,
+ Subject/binary>>}]}]},
+ route_stanza(StateData#state.jid, JID, Packet)
end.
check_subject(Packet) ->
- case xml:get_subtag(Packet, "subject") of
- false ->
- false;
- SubjEl ->
- xml:get_tag_cdata(SubjEl)
+ case xml:get_subtag(Packet, <<"subject">>) of
+ false -> false;
+ SubjEl -> xml:get_tag_cdata(SubjEl)
end.
can_change_subject(Role, StateData) ->
- case (StateData#state.config)#config.allow_change_subj of
- true ->
- (Role == moderator) orelse (Role == participant);
- _ ->
- Role == moderator
+ case (StateData#state.config)#config.allow_change_subj
+ of
+ true -> Role == moderator orelse Role == participant;
+ _ -> Role == moderator
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Admin stuff
process_iq_admin(From, set, Lang, SubEl, StateData) ->
- {xmlelement, _, _, Items} = SubEl,
+ #xmlel{children = Items} = SubEl,
process_admin_items_set(From, Items, Lang, StateData);
-
process_iq_admin(From, get, Lang, SubEl, StateData) ->
- case xml:get_subtag(SubEl, "item") of
- false ->
- {error, ?ERR_BAD_REQUEST};
- Item ->
- FAffiliation = get_affiliation(From, StateData),
- FRole = get_role(From, StateData),
- case xml:get_tag_attr("role", Item) of
- false ->
- case xml:get_tag_attr("affiliation", Item) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- {error, ?ERR_BAD_REQUEST};
- SAffiliation ->
- if
- (FAffiliation == owner) or
- (FAffiliation == admin) ->
- 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', _} ->
- {error, ?ERR_BAD_REQUEST};
- SRole ->
- if
- FRole == moderator ->
- Items = items_with_role(SRole, StateData),
- {result, Items, StateData};
- true ->
- ErrText = "Moderator privileges required",
- {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+ case xml:get_subtag(SubEl, <<"item">>) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ Item ->
+ FAffiliation = get_affiliation(From, StateData),
+ FRole = get_role(From, StateData),
+ case xml:get_tag_attr(<<"role">>, Item) of
+ false ->
+ case xml:get_tag_attr(<<"affiliation">>, Item) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, StrAffiliation} ->
+ case catch list_to_affiliation(StrAffiliation) of
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ SAffiliation ->
+ if (FAffiliation == owner) or
+ (FAffiliation == admin) ->
+ Items = items_with_affiliation(SAffiliation,
+ StateData),
+ {result, Items, StateData};
+ true ->
+ ErrText =
+ <<"Administrator privileges required">>,
+ {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
end
- end
- end
+ end
+ end;
+ {value, StrRole} ->
+ case catch list_to_role(StrRole) of
+ {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
+ 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.
-
items_with_role(SRole, StateData) ->
- lists:map(
- fun({_, U}) ->
- user_to_item(U, StateData)
- end, search_role(SRole, StateData)).
+ lists:map(fun ({_, U}) -> user_to_item(U, StateData)
+ end,
+ search_role(SRole, StateData)).
items_with_affiliation(SAffiliation, StateData) ->
- lists:map(
- fun({JID, {Affiliation, Reason}}) ->
- {xmlelement, "item",
- [{"affiliation", affiliation_to_list(Affiliation)},
- {"jid", jlib:jid_to_string(JID)}],
- [{xmlelement, "reason", [], [{xmlcdata, Reason}]}]};
- ({JID, Affiliation}) ->
- {xmlelement, "item",
- [{"affiliation", affiliation_to_list(Affiliation)},
- {"jid", jlib:jid_to_string(JID)}],
- []}
- end, search_affiliation(SAffiliation, StateData)).
-
-user_to_item(#user{role = Role,
- nick = Nick,
- jid = JID
- }, StateData) ->
+ lists:map(fun ({JID, {Affiliation, Reason}}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_list(Affiliation)},
+ {<<"jid">>, jlib:jid_to_string(JID)}],
+ children =
+ [#xmlel{name = <<"reason">>, attrs = [],
+ children = [{xmlcdata, Reason}]}]};
+ ({JID, Affiliation}) ->
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"affiliation">>,
+ affiliation_to_list(Affiliation)},
+ {<<"jid">>, jlib:jid_to_string(JID)}],
+ children = []}
+ end,
+ search_affiliation(SAffiliation, StateData)).
+
+user_to_item(#user{role = Role, nick = Nick, jid = JID},
+ StateData) ->
Affiliation = get_affiliation(JID, StateData),
- {xmlelement, "item",
- [{"role", role_to_list(Role)},
- {"affiliation", affiliation_to_list(Affiliation)},
- {"nick", Nick},
- {"jid", jlib:jid_to_string(JID)}],
- []}.
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"role">>, role_to_list(Role)},
+ {<<"affiliation">>, affiliation_to_list(Affiliation)},
+ {<<"nick">>, Nick},
+ {<<"jid">>, jlib:jid_to_string(JID)}],
+ children = []}.
search_role(Role, StateData) ->
- lists:filter(
- fun({_, #user{role = R}}) ->
- Role == R
- end, ?DICT:to_list(StateData#state.users)).
+ lists:filter(fun ({_, #user{role = R}}) -> Role == R
+ end,
+ (?DICT):to_list(StateData#state.users)).
search_affiliation(Affiliation, StateData) ->
- lists:filter(
- fun({_, A}) ->
- case A of
- {A1, _Reason} ->
- Affiliation == A1;
- _ ->
- Affiliation == A
- end
- end, ?DICT:to_list(StateData#state.affiliations)).
-
+ lists:filter(fun ({_, A}) ->
+ case A of
+ {A1, _Reason} -> Affiliation == A1;
+ _ -> Affiliation == A
+ end
+ end,
+ (?DICT):to_list(StateData#state.affiliations)).
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, []) of
- {result, Res} ->
- ?INFO_MSG("Processing MUC admin query from ~s in room ~s:~n ~p",
- [jlib:jid_to_string(UJID), jlib:jid_to_string(StateData#state.jid), Res]),
- NSD =
- lists:foldl(
- fun(E, SD) ->
- 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(
- 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(
- JID, Reason, "321", none, SD),
- SD1 = set_affiliation(JID, none, SD),
- set_role(JID, none, SD1);
- _ ->
- SD1 = set_affiliation(JID, none, SD),
- send_update_presence(JID, SD1),
- SD1
- end;
- {JID, affiliation, outcast, Reason} ->
- catch send_kickban_presence(
- JID, Reason, "301", 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),
- SD2;
- {JID, affiliation, member, Reason} ->
- SD1 = set_affiliation(
- JID, member, SD, Reason),
- SD2 = set_role(JID, participant, SD1),
- send_update_presence(JID, Reason, SD2),
- SD2;
- {JID, role, Role, Reason} ->
- SD1 = set_role(JID, Role, SD),
- catch send_new_presence(JID, Reason, SD1),
- SD1;
- {JID, affiliation, A, _Reason} ->
- SD1 = set_affiliation(JID, A, SD),
- send_update_presence(JID, SD1),
- SD1
- end
- ) of
- {'EXIT', ErrReason} ->
- ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n",
- [ErrReason]),
- SD;
- NSD ->
- NSD
- end
- end, StateData, lists:flatten(Res)),
- case (NSD#state.config)#config.persistent of
- true ->
- mod_muc:store_room(NSD#state.server_host,
- NSD#state.host, NSD#state.room,
- make_opts(NSD));
- _ ->
- ok
- end,
- {result, [], NSD};
- Err ->
- Err
+ case find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData, [])
+ of
+ {result, Res} ->
+ ?INFO_MSG("Processing MUC admin query from ~s in "
+ "room ~s:~n ~p",
+ [jlib:jid_to_string(UJID),
+ jlib:jid_to_string(StateData#state.jid), Res]),
+ NSD = lists:foldl(fun (E, SD) ->
+ 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(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(JID,
+ Reason,
+ <<"321">>,
+ none,
+ SD),
+ SD1 =
+ set_affiliation(JID,
+ none,
+ SD),
+ set_role(JID, none,
+ SD1);
+ _ ->
+ SD1 =
+ set_affiliation(JID,
+ none,
+ SD),
+ send_update_presence(JID,
+ SD1),
+ SD1
+ end;
+ {JID, affiliation, outcast,
+ Reason} ->
+ catch
+ send_kickban_presence(JID,
+ Reason,
+ <<"301">>,
+ 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),
+ SD2;
+ {JID, affiliation, member,
+ Reason} ->
+ SD1 = set_affiliation(JID,
+ member,
+ SD,
+ Reason),
+ SD2 = set_role(JID,
+ participant,
+ SD1),
+ send_update_presence(JID,
+ Reason,
+ SD2),
+ SD2;
+ {JID, role, Role, Reason} ->
+ SD1 = set_role(JID, Role,
+ SD),
+ catch
+ send_new_presence(JID,
+ Reason,
+ SD1),
+ SD1;
+ {JID, affiliation, A,
+ _Reason} ->
+ SD1 = set_affiliation(JID,
+ A,
+ SD),
+ send_update_presence(JID,
+ SD1),
+ SD1
+ end
+ of
+ {'EXIT', ErrReason} ->
+ ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n",
+ [ErrReason]),
+ SD;
+ NSD -> NSD
+ end
+ end,
+ StateData, lists:flatten(Res)),
+ case (NSD#state.config)#config.persistent of
+ true ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host, NSD#state.room,
+ make_opts(NSD));
+ _ -> ok
+ end,
+ {result, [], NSD};
+ Err -> Err
end.
-
-find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) ->
+find_changed_items(_UJID, _UAffiliation, _URole, [],
+ _Lang, _StateData, Res) ->
{result, Res};
-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,
- [{xmlelement, "item", Attrs, _Els} = Item | Items],
+ [{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],
Lang, StateData, Res) ->
- TJID = case xml:get_attr("jid", Attrs) of
- {value, S} ->
- case jlib:string_to_jid(S) of
- error ->
- ErrText = io_lib:format(
- translate:translate(
- Lang,
- "Jabber ID ~s is invalid"), [S]),
- {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
- J ->
- {value, [J]}
- end;
- _ ->
- case xml:get_attr("nick", Attrs) of
- {value, N} ->
- case find_jids_by_nick(N, StateData) of
- false ->
- ErrText =
- 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;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end
+ TJID = case xml:get_attr(<<"jid">>, Attrs) of
+ {value, S} ->
+ case jlib:string_to_jid(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 xml: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;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end
end,
case TJID of
- {value, [JID|_]=JIDs} ->
- TAffiliation = get_affiliation(JID, StateData),
- TRole = get_role(JID, StateData),
- case xml:get_attr("role", Attrs) of
- false ->
- case xml:get_attr("affiliation", Attrs) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText1 =
- 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, _}] ->
- jlib:jid_remove_resource(OJID) /=
- jlib:jid_tolower(jlib:jid_remove_resource(UJID));
- _ ->
- true
- end;
- _ ->
- false
- end,
- case CanChangeRA of
- nothing ->
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- Res);
- true ->
- Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]),
- MoreRes = [{jlib:jid_remove_resource(Jidx), affiliation, SAffiliation, Reason} || Jidx <- JIDs],
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- [MoreRes | Res]);
- false ->
- {error, ?ERR_NOT_ALLOWED}
- end
- end
- end;
- {value, StrRole} ->
- case catch list_to_role(StrRole) of
+ {value, [JID | _] = JIDs} ->
+ TAffiliation = get_affiliation(JID, StateData),
+ TRole = get_role(JID, StateData),
+ case xml:get_attr(<<"role">>, Attrs) of
+ false ->
+ case xml:get_attr(<<"affiliation">>, Attrs) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {value, StrAffiliation} ->
+ case catch list_to_affiliation(StrAffiliation) of
{'EXIT', _} ->
- ErrText1 =
- io_lib:format(
- translate:translate(
- Lang,
- "Invalid role: ~s"),
- [StrRole]),
- {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
- SRole ->
+ 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,
- role, SRole,
- ServiceAf) of
- nothing ->
- nothing;
- true ->
- true;
- check_owner ->
- case search_affiliation(
- owner, StateData) of
- [{OJID, _}] ->
- jlib:jid_remove_resource(OJID) /=
- jlib:jid_tolower(jlib:jid_remove_resource(UJID));
- _ ->
- true
- end;
- _ ->
- false
- end,
+ 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, _}] ->
+ jlib:jid_remove_resource(OJID)
+ /=
+ jlib:jid_tolower(jlib:jid_remove_resource(UJID));
+ _ -> true
+ end;
+ _ -> false
+ end,
case CanChangeRA of
- nothing ->
- find_changed_items(
- UJID,
- UAffiliation, URole,
- Items, Lang, StateData,
- Res);
- true ->
- Reason = xml: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]);
- _ ->
- {error, ?ERR_NOT_ALLOWED}
+ nothing ->
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ Res);
+ true ->
+ Reason = xml:get_path_s(Item,
+ [{elem, <<"reason">>},
+ cdata]),
+ MoreRes = [{jlib:jid_remove_resource(Jidx),
+ affiliation, SAffiliation, Reason}
+ || Jidx <- JIDs],
+ find_changed_items(UJID, UAffiliation, URole,
+ Items, Lang, StateData,
+ [MoreRes | Res]);
+ false -> {error, ?ERR_NOT_ALLOWED}
end
- end
- end;
- Err ->
- Err
+ 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, _}] ->
+ jlib:jid_remove_resource(OJID)
+ /=
+ jlib:jid_tolower(jlib:jid_remove_resource(UJID));
+ _ -> true
+ end;
+ _ -> false
+ end,
+ case CanChangeRA of
+ nothing ->
+ find_changed_items(UJID, UAffiliation, URole, Items,
+ Lang, StateData, Res);
+ true ->
+ Reason = xml: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]);
+ _ -> {error, ?ERR_NOT_ALLOWED}
+ end
+ end
+ end;
+ Err -> Err
end;
find_changed_items(_UJID, _UAffiliation, _URole, _Items,
_Lang, _StateData, _Res) ->
{error, ?ERR_BAD_REQUEST}.
-
-can_change_ra(_FAffiliation, _FRole,
- owner, _TRole,
+can_change_ra(_FAffiliation, _FRole, owner, _TRole,
affiliation, owner, owner) ->
%% A room owner tries to add as persistent owner a
%% participant that is already owner because he is MUC admin
true;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, _TRole,
- _RoleorAffiliation, _Value, owner) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ _TRole, _RoleorAffiliation, _Value, owner) ->
%% Nobody can decrease MUC admin's role/affiliation
false;
-can_change_ra(_FAffiliation, _FRole,
- TAffiliation, _TRole,
- affiliation, Value, _ServiceAf)
- when (TAffiliation == Value) ->
+can_change_ra(_FAffiliation, _FRole, TAffiliation,
+ _TRole, affiliation, Value, _ServiceAf)
+ when TAffiliation == Value ->
nothing;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, TRole,
- role, Value, _ServiceAf)
- when (TRole == Value) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ TRole, role, Value, _ServiceAf)
+ when TRole == Value ->
nothing;
-can_change_ra(FAffiliation, _FRole,
- outcast, _TRole,
+can_change_ra(FAffiliation, _FRole, outcast, _TRole,
affiliation, none, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(FAffiliation, _FRole,
- outcast, _TRole,
+can_change_ra(FAffiliation, _FRole, outcast, _TRole,
affiliation, member, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(owner, _FRole,
- outcast, _TRole,
+can_change_ra(owner, _FRole, outcast, _TRole,
affiliation, admin, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- outcast, _TRole,
+can_change_ra(owner, _FRole, outcast, _TRole,
affiliation, owner, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- none, _TRole,
+can_change_ra(FAffiliation, _FRole, none, _TRole,
affiliation, outcast, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(FAffiliation, _FRole,
- none, _TRole,
+can_change_ra(FAffiliation, _FRole, none, _TRole,
affiliation, member, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(owner, _FRole,
- none, _TRole,
- affiliation, admin, _ServiceAf) ->
+can_change_ra(owner, _FRole, none, _TRole, affiliation,
+ admin, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- none, _TRole,
- affiliation, owner, _ServiceAf) ->
+can_change_ra(owner, _FRole, none, _TRole, affiliation,
+ owner, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- member, _TRole,
+can_change_ra(FAffiliation, _FRole, member, _TRole,
affiliation, outcast, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(FAffiliation, _FRole,
- member, _TRole,
+can_change_ra(FAffiliation, _FRole, member, _TRole,
affiliation, none, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(owner, _FRole,
- member, _TRole,
+can_change_ra(owner, _FRole, member, _TRole,
affiliation, admin, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- member, _TRole,
+can_change_ra(owner, _FRole, member, _TRole,
affiliation, owner, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- admin, _TRole,
- affiliation, _Affiliation, _ServiceAf) ->
+can_change_ra(owner, _FRole, admin, _TRole, affiliation,
+ _Affiliation, _ServiceAf) ->
true;
-can_change_ra(owner, _FRole,
- owner, _TRole,
- affiliation, _Affiliation, _ServiceAf) ->
+can_change_ra(owner, _FRole, owner, _TRole, affiliation,
+ _Affiliation, _ServiceAf) ->
check_owner;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, _TRole,
- affiliation, _Value, _ServiceAf) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ _TRole, affiliation, _Value, _ServiceAf) ->
false;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, visitor,
- role, none, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ visitor, role, none, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, visitor,
- role, participant, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ visitor, role, participant, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- _TAffiliation, visitor,
- role, moderator, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+can_change_ra(FAffiliation, _FRole, _TAffiliation,
+ visitor, role, moderator, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, participant,
- role, none, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ participant, role, none, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, moderator,
- _TAffiliation, participant,
- role, visitor, _ServiceAf) ->
+can_change_ra(_FAffiliation, moderator, _TAffiliation,
+ participant, role, visitor, _ServiceAf) ->
true;
-can_change_ra(FAffiliation, _FRole,
- _TAffiliation, participant,
- role, moderator, _ServiceAf)
- when (FAffiliation == owner) or (FAffiliation == admin) ->
+can_change_ra(FAffiliation, _FRole, _TAffiliation,
+ participant, role, moderator, _ServiceAf)
+ when (FAffiliation == owner) or
+ (FAffiliation == admin) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- owner, moderator,
+can_change_ra(_FAffiliation, _FRole, owner, moderator,
role, visitor, _ServiceAf) ->
false;
-can_change_ra(owner, _FRole,
- _TAffiliation, moderator,
+can_change_ra(owner, _FRole, _TAffiliation, moderator,
role, visitor, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- admin, moderator,
+can_change_ra(_FAffiliation, _FRole, admin, moderator,
role, visitor, _ServiceAf) ->
false;
-can_change_ra(admin, _FRole,
- _TAffiliation, moderator,
+can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, visitor, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- owner, moderator,
+can_change_ra(_FAffiliation, _FRole, owner, moderator,
role, participant, _ServiceAf) ->
false;
-can_change_ra(owner, _FRole,
- _TAffiliation, moderator,
+can_change_ra(owner, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- admin, moderator,
+can_change_ra(_FAffiliation, _FRole, admin, moderator,
role, participant, _ServiceAf) ->
false;
-can_change_ra(admin, _FRole,
- _TAffiliation, moderator,
+can_change_ra(admin, _FRole, _TAffiliation, moderator,
role, participant, _ServiceAf) ->
true;
-can_change_ra(_FAffiliation, _FRole,
- _TAffiliation, _TRole,
- role, _Value, _ServiceAf) ->
+can_change_ra(_FAffiliation, _FRole, _TAffiliation,
+ _TRole, role, _Value, _ServiceAf) ->
false.
-
send_kickban_presence(JID, Reason, Code, StateData) ->
NewAffiliation = get_affiliation(JID, StateData),
- send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData).
+ send_kickban_presence(JID, Reason, Code, NewAffiliation,
+ StateData).
-send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData) ->
+send_kickban_presence(JID, Reason, Code, NewAffiliation,
+ StateData) ->
LJID = jlib:jid_tolower(JID),
LJIDs = case LJID of
- {U, S, ""} ->
- ?DICT:fold(
- fun(J, _, Js) ->
- case J of
- {U, S, _} ->
- [J | Js];
- _ ->
- Js
- end
- end, [], StateData#state.users);
- _ ->
- case ?DICT:is_key(LJID, StateData#state.users) of
- true ->
- [LJID];
- _ ->
- []
- end
+ {U, S, <<"">>} ->
+ (?DICT):fold(fun (J, _, Js) ->
+ case J of
+ {U, S, _} -> [J | Js];
+ _ -> Js
+ end
+ end,
+ [], StateData#state.users);
+ _ ->
+ case (?DICT):is_key(LJID, StateData#state.users) of
+ true -> [LJID];
+ _ -> []
+ end
end,
- lists:foreach(fun(J) ->
- {ok, #user{nick = Nick}} =
- ?DICT:find(J, StateData#state.users),
+ lists:foreach(fun (J) ->
+ {ok, #user{nick = Nick}} = (?DICT):find(J,
+ StateData#state.users),
add_to_log(kickban, {Nick, Reason, Code}, StateData),
tab_remove_online_user(J, StateData),
- send_kickban_presence1(J, Reason, Code, NewAffiliation, StateData)
- end, LJIDs).
+ send_kickban_presence1(J, Reason, Code,
+ NewAffiliation, StateData)
+ end,
+ LJIDs).
-send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) ->
- {ok, #user{jid = RealJID,
- nick = Nick}} =
- ?DICT:find(jlib:jid_tolower(UJID), StateData#state.users),
+send_kickban_presence1(UJID, Reason, Code, Affiliation,
+ StateData) ->
+ {ok, #user{jid = RealJID, nick = Nick}} =
+ (?DICT):find(jlib:jid_tolower(UJID),
+ StateData#state.users),
SAffiliation = affiliation_to_list(Affiliation),
BannedJIDString = jlib:jid_to_string(RealJID),
- 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
- "" ->
- [];
- _ ->
- [{xmlelement, "reason", [],
- [{xmlcdata, Reason}]}]
- end,
- Packet = {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, ItemEls},
- {xmlelement, "status", [{"code", Code}], []}]}]},
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet)
- end, ?DICT:to_list(StateData#state.users)).
-
-
+ 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,
+ Packet = #xmlel{name = <<"presence">>,
+ attrs =
+ [{<<"type">>, <<"unavailable">>}],
+ children =
+ [#xmlel{name = <<"x">>,
+ attrs =
+ [{<<"xmlns">>,
+ ?NS_MUC_USER}],
+ children =
+ [#xmlel{name =
+ <<"item">>,
+ attrs =
+ ItemAttrs,
+ children =
+ ItemEls},
+ #xmlel{name =
+ <<"status">>,
+ attrs =
+ [{<<"code">>,
+ Code}],
+ children =
+ []}]}]},
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet)
+ end,
+ (?DICT):to_list(StateData#state.users)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Owner stuff
@@ -3100,601 +3136,813 @@ send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) ->
process_iq_owner(From, set, Lang, SubEl, StateData) ->
FAffiliation = get_affiliation(From, StateData),
case FAffiliation of
- owner ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
- case {xml:get_tag_attr_s("xmlns", XEl),
- xml: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);
- false -> {error, ?ERR_NOT_ACCEPTABLE}
- end;
- _ ->
- {error, ?ERR_BAD_REQUEST}
- end;
- [{xmlelement, "destroy", _Attrs1, _Els1} = SubEl1] ->
- ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
- [jlib:jid_to_string(StateData#state.jid), jlib: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)}
+ owner ->
+ #xmlel{children = Els} = SubEl,
+ case xml:remove_cdata(Els) of
+ [#xmlel{name = <<"x">>} = XEl] ->
+ case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ xml: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);
+ false -> {error, ?ERR_NOT_ACCEPTABLE}
+ end;
+ _ -> {error, ?ERR_BAD_REQUEST}
+ end;
+ [#xmlel{name = <<"destroy">>} = SubEl1] ->
+ ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
+ [jlib:jid_to_string(StateData#state.jid),
+ jlib:jid_to_string(From)]),
+ 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)}
end;
-
process_iq_owner(From, get, Lang, SubEl, StateData) ->
FAffiliation = get_affiliation(From, StateData),
case FAffiliation of
- owner ->
- {xmlelement, _Name, _Attrs, Els} = SubEl,
- case xml:remove_cdata(Els) of
- [] ->
- get_config(Lang, StateData, From);
- [Item] ->
- case xml:get_tag_attr("affiliation", Item) of
- false ->
- {error, ?ERR_BAD_REQUEST};
- {value, StrAffiliation} ->
- case catch list_to_affiliation(StrAffiliation) of
- {'EXIT', _} ->
- ErrText =
- 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)}
+ owner ->
+ #xmlel{children = Els} = SubEl,
+ case xml:remove_cdata(Els) of
+ [] -> get_config(Lang, StateData, From);
+ [Item] ->
+ case xml:get_tag_attr(<<"affiliation">>, Item) of
+ false -> {error, ?ERR_BAD_REQUEST};
+ {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)}
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))
+ 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)
end.
is_allowed_persistent_change(XEl, StateData, From) ->
- case lists:keymember("muc#roomconfig_persistentroom", 1,
- jlib:parse_xdata_submit(XEl)) of
- false ->
- true;
- true ->
- {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access,
- (allow == acl:match_rule(StateData#state.server_host, AccessPersistent, From))
+ case
+ lists:keymember(<<"muc#roomconfig_persistentroom">>, 1,
+ jlib:parse_xdata_submit(XEl))
+ of
+ false -> true;
+ true ->
+ {_AccessRoute, _AccessCreate, _AccessAdmin,
+ AccessPersistent} =
+ StateData#state.access,
+ allow ==
+ acl:match_rule(StateData#state.server_host,
+ AccessPersistent, From)
end.
-%% 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]}} ->
- length(N) =< gen_mod:get_module_opt(StateData#state.server_host,
+ 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,
- infinite);
- _ ->
- true
- end,
- IsDescAccepted =
- case lists:keysearch("muc#roomconfig_roomdesc", 1,
- jlib:parse_xdata_submit(XEl)) of
- {value, {_, [D]}} ->
- length(D) =< gen_mod:get_module_opt(StateData#state.server_host,
+ 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,
- infinite);
- _ ->
- true
- end,
+ 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) ->
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
- end,
- NewPassword =
- case lists:keysearch("muc#roomconfig_roomsecret", 1,
- jlib:parse_xdata_submit(XEl)) of
- {value, {_, [P]}} ->
- P;
- _ ->
- undefined
- end,
- case {OldProtected, NewProtected, OldPassword, NewPassword} of
- {true, undefined, "", undefined} ->
- false;
- {true, undefined, _, ""} ->
- false;
- {_, true , "", undefined} ->
- false;
- {_, true, _, ""} ->
- false;
- _ ->
- true
+ NewProtected = case
+ lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>,
+ 1, jlib:parse_xdata_submit(XEl))
+ of
+ {value, {_, [<<"1">>]}} -> true;
+ {value, {_, [<<"0">>]}} -> false;
+ _ -> undefined
+ end,
+ NewPassword = case
+ lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1,
+ jlib:parse_xdata_submit(XEl))
+ of
+ {value, {_, [P]}} -> P;
+ _ -> undefined
+ end,
+ case {OldProtected, NewProtected, OldPassword,
+ NewPassword}
+ of
+ {true, undefined, <<"">>, undefined} -> false;
+ {true, undefined, _, <<"">>} -> false;
+ {_, true, <<"">>, undefined} -> false;
+ {_, true, _, <<"">>} -> false;
+ _ -> true
end.
-
-define(XFIELD(Type, Label, Var, Val),
- {xmlelement, "field", [{"type", Type},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, Type},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-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),
- {xmlelement, "field", [{"type", "jid-multi"},
- {"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, jlib:jid_to_string(JID)}]}
- || JID <- JIDList]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"type">>, <<"jid-multi">>},
+ {<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, jlib:jid_to_string(JID)}]}
+ || JID <- JIDList]}).
get_default_room_maxusers(RoomState) ->
- DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []),
+ DefRoomOpts =
+ gen_mod:get_module_opt(RoomState#state.server_host,
+ mod_muc, default_room_options,
+ fun(L) when is_list(L) -> L end,
+ []),
RoomState2 = set_opts(DefRoomOpts, RoomState),
(RoomState2#state.config)#config.max_users.
get_config(Lang, StateData, From) ->
- {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access,
+ {_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, erlang:integer_to_list(N)};
- _ -> {0, "none"}
- end,
- Res =
- [{xmlelement, "title", [],
- [{xmlcdata, io_lib:format(translate:translate(Lang, "Configuration of room ~s"), [jlib:jid_to_string(StateData#state.jid)])}]},
- {xmlelement, "field", [{"type", "hidden"},
- {"var", "FORM_TYPE"}],
- [{xmlelement, "value", [],
- [{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),
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", translate:translate(Lang, "Maximum Number of Occupants")},
- {"var", "muc#roomconfig_maxusers"}],
- [{xmlelement, "value", [], [{xmlcdata, MaxUsersRoomString}]}] ++
- if
- is_integer(ServiceMaxUsers) -> [];
- true ->
- [{xmlelement, "option",
- [{"label", translate:translate(Lang, "No limit")}],
- [{xmlelement, "value", [], [{xmlcdata, "none"}]}]}]
- end ++
- [{xmlelement, "option", [{"label", erlang:integer_to_list(N)}],
- [{xmlelement, "value", [],
- [{xmlcdata, erlang:integer_to_list(N)}]}]} ||
- N <- lists:usort([ServiceMaxUsers, DefaultRoomMaxUsers, MaxUsersRoomInteger |
- ?MAX_USERS_DEFAULT_LIST]), N =< ServiceMaxUsers]
- },
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", translate:translate(Lang, "Present real Jabber IDs to")},
- {"var", "muc#roomconfig_whois"}],
- [{xmlelement, "value", [], [{xmlcdata,
- if Config#config.anonymous ->
- "moderators";
- true ->
- "anyone"
- end}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}],
- [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}],
- [{xmlelement, "value", [], [{xmlcdata, "anyone"}]}]}]},
- ?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),
- {xmlelement, "field",
- [{"type", "list-single"},
- {"label", translate:translate(Lang, "Allow visitors to send private messages to")},
- {"var", "allow_private_messages_from_visitors"}],
- [{xmlelement, "value", [], [{xmlcdata,
- case Config#config.allow_private_messages_from_visitors of
- anyone ->
- "anyone";
- moderators ->
- "moderators";
- nobody ->
- "nobody"
- end}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "nobody")}],
- [{xmlelement, "value", [], [{xmlcdata, "nobody"}]}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}],
- [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]},
- {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}],
- [{xmlelement, "value", [], [{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),
- ?STRINGXFIELD("Minimum interval between voice requests (in seconds)",
- "muc#roomconfig_voicerequestmininterval",
- erlang:integer_to_list(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,
- {result, [{xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "You need an x:data capable client to configure room")}]},
- {xmlelement, "x", [{"xmlns", ?NS_XDATA},
- {"type", "form"}],
- Res}],
+ {MaxUsersRoomInteger, MaxUsersRoomString} = case
+ get_max_users(StateData)
+ of
+ N when is_integer(N) ->
+ {N,
+ erlang:integer_to_list(N)};
+ _ -> {0, <<"none">>}
+ end,
+ Res = [#xmlel{name = <<"title">>, attrs = [],
+ children =
+ [{xmlcdata,
+ iolist_to_binary(
+ io_lib:format(
+ translate:translate(
+ Lang,
+ <<"Configuration of room ~s">>),
+ [jlib: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">>}]}]}]},
+ ?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)),
+ ?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,
+ {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 = Res}],
StateData}.
-
-
set_config(XEl, StateData) ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
- invalid ->
- {error, ?ERR_BAD_REQUEST};
- _ ->
- case set_xoption(XData, StateData#state.config) 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
+ invalid -> {error, ?ERR_BAD_REQUEST};
+ _ ->
+ case set_xoption(XData, StateData#state.config) 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
end.
-define(SET_BOOL_XOPT(Opt, Val),
case Val of
- "0" -> set_xoption(Opts, Config#config{Opt = false});
- "false" -> set_xoption(Opts, Config#config{Opt = false});
- "1" -> set_xoption(Opts, Config#config{Opt = true});
- "true" -> set_xoption(Opts, Config#config{Opt = true});
- _ -> {error, ?ERR_BAD_REQUEST}
+ <<"0">> ->
+ set_xoption(Opts, Config#config{Opt = false});
+ <<"false">> ->
+ set_xoption(Opts, Config#config{Opt = false});
+ <<"1">> -> set_xoption(Opts, Config#config{Opt = true});
+ <<"true">> ->
+ set_xoption(Opts, Config#config{Opt = true});
+ _ -> {error, ?ERR_BAD_REQUEST}
end).
-define(SET_NAT_XOPT(Opt, Val),
- case catch list_to_integer(Val) of
- I when is_integer(I),
- I > 0 ->
- set_xoption(Opts, Config#config{Opt = I});
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ case catch jlib:binary_to_integer(Val) of
+ I when is_integer(I), I > 0 ->
+ set_xoption(Opts, Config#config{Opt = I});
+ _ -> {error, ?ERR_BAD_REQUEST}
end).
-define(SET_STRING_XOPT(Opt, Val),
set_xoption(Opts, Config#config{Opt = Val})).
-define(SET_JIDMULTI_XOPT(Opt, Vals),
- begin
- Set = lists:foldl(
- fun({U, S, R}, Set1) ->
- ?SETS:add_element({U, S, R}, Set1);
- (#jid{luser = U, lserver = S, lresource = R}, Set1) ->
- ?SETS:add_element({U, S, R}, Set1);
- (_, Set1) ->
- Set1
- end, ?SETS:empty(), Vals),
- set_xoption(Opts, Config#config{Opt = Set})
- end).
-
-set_xoption([], Config) ->
- Config;
-set_xoption([{"muc#roomconfig_roomname", [Val]} | Opts], Config) ->
+ begin
+ Set = lists:foldl(fun ({U, S, R}, Set1) ->
+ (?SETS):add_element({U, S, R}, Set1);
+ (#jid{luser = U, lserver = S, lresource = R},
+ Set1) ->
+ (?SETS):add_element({U, S, R}, Set1);
+ (_, Set1) -> Set1
+ end,
+ (?SETS):empty(), Vals),
+ set_xoption(Opts, Config#config{Opt = Set})
+ end).
+
+set_xoption([], Config) -> Config;
+set_xoption([{<<"muc#roomconfig_roomname">>, [Val]}
+ | Opts],
+ Config) ->
?SET_STRING_XOPT(title, Val);
-set_xoption([{"muc#roomconfig_roomdesc", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]}
+ | Opts],
+ Config) ->
?SET_STRING_XOPT(description, Val);
-set_xoption([{"muc#roomconfig_changesubject", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_change_subj, Val);
-set_xoption([{"allow_query_users", [Val]} | Opts], Config) ->
+set_xoption([{<<"allow_query_users">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_query_users, Val);
-set_xoption([{"allow_private_messages", [Val]} | Opts], Config) ->
+set_xoption([{<<"allow_private_messages">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_private_messages, Val);
-set_xoption([{"allow_private_messages_from_visitors", [Val]} | Opts], Config) ->
+set_xoption([{<<"allow_private_messages_from_visitors">>,
+ [Val]}
+ | Opts],
+ Config) ->
case Val of
- "anyone" ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors, anyone);
- "moderators" ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors, moderators);
- "nobody" ->
- ?SET_STRING_XOPT(allow_private_messages_from_visitors, nobody);
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ <<"anyone">> ->
+ ?SET_STRING_XOPT(allow_private_messages_from_visitors,
+ anyone);
+ <<"moderators">> ->
+ ?SET_STRING_XOPT(allow_private_messages_from_visitors,
+ moderators);
+ <<"nobody">> ->
+ ?SET_STRING_XOPT(allow_private_messages_from_visitors,
+ nobody);
+ _ -> {error, ?ERR_BAD_REQUEST}
end;
-set_xoption([{"muc#roomconfig_allowvisitorstatus", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_visitor_status, Val);
-set_xoption([{"muc#roomconfig_allowvisitornickchange", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowvisitornickchange">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_visitor_nickchange, Val);
-set_xoption([{"muc#roomconfig_publicroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_publicroom">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(public, Val);
-set_xoption([{"public_list", [Val]} | Opts], Config) ->
+set_xoption([{<<"public_list">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(public_list, Val);
-set_xoption([{"muc#roomconfig_persistentroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_persistentroom">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(persistent, Val);
-set_xoption([{"muc#roomconfig_moderatedroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_moderatedroom">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(moderated, Val);
-set_xoption([{"members_by_default", [Val]} | Opts], Config) ->
+set_xoption([{<<"members_by_default">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(members_by_default, Val);
-set_xoption([{"muc#roomconfig_membersonly", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_membersonly">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(members_only, Val);
-set_xoption([{"captcha_protected", [Val]} | Opts], Config) ->
+set_xoption([{<<"captcha_protected">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(captcha_protected, Val);
-set_xoption([{"muc#roomconfig_allowinvites", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowinvites">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(allow_user_invites, Val);
-set_xoption([{"muc#roomconfig_passwordprotectedroom", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
+ [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(password_protected, Val);
-set_xoption([{"muc#roomconfig_roomsecret", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
+ | Opts],
+ Config) ->
?SET_STRING_XOPT(password, Val);
-set_xoption([{"anonymous", [Val]} | Opts], Config) ->
+set_xoption([{<<"anonymous">>, [Val]} | Opts],
+ Config) ->
?SET_BOOL_XOPT(anonymous, Val);
-set_xoption([{"muc#roomconfig_allowvoicerequests", [Val]} | Opts], Config) ->
- ?SET_BOOL_XOPT(allow_voice_requests, Val);
-set_xoption([{"muc#roomconfig_voicerequestmininterval", [Val]} | Opts], Config) ->
- ?SET_NAT_XOPT(voice_request_min_interval, Val);
-set_xoption([{"muc#roomconfig_whois", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_allowvoicerequests">>,
+ [Val]}
+ | Opts],
+ Config) ->
+ ?SET_BOOL_XOPT(allow_voice_requests, Val);
+set_xoption([{<<"muc#roomconfig_voicerequestmininterval">>,
+ [Val]}
+ | Opts],
+ Config) ->
+ ?SET_NAT_XOPT(voice_request_min_interval, Val);
+set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
+ | Opts],
+ Config) ->
case Val of
- "moderators" ->
- ?SET_BOOL_XOPT(anonymous, integer_to_list(1));
- "anyone" ->
- ?SET_BOOL_XOPT(anonymous, integer_to_list(0));
- _ ->
- {error, ?ERR_BAD_REQUEST}
+ <<"moderators">> ->
+ ?SET_BOOL_XOPT(anonymous,
+ (iolist_to_binary(integer_to_list(1))));
+ <<"anyone">> ->
+ ?SET_BOOL_XOPT(anonymous,
+ (iolist_to_binary(integer_to_list(0))));
+ _ -> {error, ?ERR_BAD_REQUEST}
end;
-set_xoption([{"muc#roomconfig_maxusers", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
+ | Opts],
+ Config) ->
case Val of
- "none" ->
- ?SET_STRING_XOPT(max_users, none);
- _ ->
- ?SET_NAT_XOPT(max_users, Val)
+ <<"none">> -> ?SET_STRING_XOPT(max_users, none);
+ _ -> ?SET_NAT_XOPT(max_users, Val)
end;
-set_xoption([{"muc#roomconfig_enablelogging", [Val]} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_enablelogging">>, [Val]}
+ | Opts],
+ Config) ->
?SET_BOOL_XOPT(logging, Val);
-set_xoption([{"muc#roomconfig_captcha_whitelist", Vals} | Opts], Config) ->
+set_xoption([{<<"muc#roomconfig_captcha_whitelist">>,
+ Vals}
+ | Opts],
+ Config) ->
JIDs = [jlib:string_to_jid(Val) || Val <- Vals],
?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs);
-set_xoption([{"FORM_TYPE", _} | Opts], Config) ->
- %% Ignore our FORM_TYPE
+set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config) ->
set_xoption(Opts, Config);
set_xoption([_ | _Opts], _Config) ->
{error, ?ERR_BAD_REQUEST}.
-
change_config(Config, StateData) ->
NSD = StateData#state{config = Config},
case {(StateData#state.config)#config.persistent,
- Config#config.persistent} of
- {_, true} ->
- mod_muc:store_room(NSD#state.server_host, NSD#state.host,
- NSD#state.room, make_opts(NSD));
- {true, false} ->
- mod_muc:forget_room(NSD#state.server_host, NSD#state.host,
- NSD#state.room);
- {false, false} ->
- ok
+ Config#config.persistent}
+ of
+ {_, true} ->
+ mod_muc:store_room(NSD#state.server_host,
+ NSD#state.host, NSD#state.room, make_opts(NSD));
+ {true, false} ->
+ mod_muc:forget_room(NSD#state.server_host,
+ NSD#state.host, NSD#state.room);
+ {false, false} -> ok
end,
case {(StateData#state.config)#config.members_only,
- Config#config.members_only} of
- {false, true} ->
- NSD1 = remove_nonmembers(NSD),
- {result, [], NSD1};
- _ ->
- {result, [], NSD}
+ Config#config.members_only}
+ of
+ {false, true} ->
+ NSD1 = remove_nonmembers(NSD), {result, [], NSD1};
+ _ -> {result, [], NSD}
end.
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),
- set_role(JID, none, SD);
- _ ->
- SD
- end
- end, StateData, ?DICT:to_list(StateData#state.users)).
-
-
--define(CASE_CONFIG_OPT(Opt),
- Opt -> StateData#state{
- config = (StateData#state.config)#config{Opt = Val}}).
+ lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
+ Affiliation = get_affiliation(JID, SD),
+ case Affiliation of
+ none ->
+ catch send_kickban_presence(JID, <<"">>,
+ <<"322">>, SD),
+ set_role(JID, none, SD);
+ _ -> SD
+ end
+ end,
+ StateData, (?DICT):to_list(StateData#state.users)).
-set_opts([], StateData) ->
- StateData;
+set_opts([], StateData) -> StateData;
set_opts([{Opt, Val} | Opts], StateData) ->
NSD = case Opt of
- title -> StateData#state{config = (StateData#state.config)#config{title = Val}};
- description -> StateData#state{config = (StateData#state.config)#config{description = Val}};
- allow_change_subj -> StateData#state{config = (StateData#state.config)#config{allow_change_subj = Val}};
- allow_query_users -> StateData#state{config = (StateData#state.config)#config{allow_query_users = Val}};
- allow_private_messages -> StateData#state{config = (StateData#state.config)#config{allow_private_messages = Val}};
- allow_private_messages_from_visitors -> StateData#state{config = (StateData#state.config)#config{allow_private_messages_from_visitors = Val}};
- allow_visitor_nickchange -> StateData#state{config = (StateData#state.config)#config{allow_visitor_nickchange = Val}};
- allow_visitor_status -> StateData#state{config = (StateData#state.config)#config{allow_visitor_status = Val}};
- public -> StateData#state{config = (StateData#state.config)#config{public = Val}};
- public_list -> StateData#state{config = (StateData#state.config)#config{public_list = Val}};
- persistent -> StateData#state{config = (StateData#state.config)#config{persistent = Val}};
- moderated -> StateData#state{config = (StateData#state.config)#config{moderated = Val}};
- members_by_default -> StateData#state{config = (StateData#state.config)#config{members_by_default = Val}};
- members_only -> StateData#state{config = (StateData#state.config)#config{members_only = Val}};
- allow_user_invites -> StateData#state{config = (StateData#state.config)#config{allow_user_invites = Val}};
- password_protected -> StateData#state{config = (StateData#state.config)#config{password_protected = Val}};
- captcha_protected -> StateData#state{config = (StateData#state.config)#config{captcha_protected = Val}};
- password -> StateData#state{config = (StateData#state.config)#config{password = Val}};
- anonymous -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}};
- logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}};
- captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist = ?SETS:from_list(Val)}};
- allow_voice_requests -> StateData#state{config = (StateData#state.config)#config{allow_voice_requests = Val}};
- voice_request_min_interval -> StateData#state{config = (StateData#state.config)#config{voice_request_min_interval = Val}};
- max_users ->
- ServiceMaxUsers = get_service_max_users(StateData),
- MaxUsers = if
- Val =< ServiceMaxUsers -> Val;
- true -> ServiceMaxUsers
- end,
- StateData#state{
- config = (StateData#state.config)#config{
- max_users = MaxUsers}};
- affiliations ->
- StateData#state{affiliations = ?DICT:from_list(Val)};
- subject ->
- StateData#state{subject = Val};
- subject_author ->
- StateData#state{subject_author = Val};
- _ -> StateData
+ title ->
+ StateData#state{config =
+ (StateData#state.config)#config{title =
+ Val}};
+ description ->
+ StateData#state{config =
+ (StateData#state.config)#config{description
+ = Val}};
+ allow_change_subj ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_change_subj
+ = Val}};
+ allow_query_users ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_query_users
+ = Val}};
+ allow_private_messages ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_private_messages
+ = Val}};
+ allow_private_messages_from_visitors ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_private_messages_from_visitors
+ = Val}};
+ allow_visitor_nickchange ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_visitor_nickchange
+ = Val}};
+ allow_visitor_status ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_visitor_status
+ = Val}};
+ public ->
+ StateData#state{config =
+ (StateData#state.config)#config{public =
+ Val}};
+ public_list ->
+ StateData#state{config =
+ (StateData#state.config)#config{public_list
+ = Val}};
+ persistent ->
+ StateData#state{config =
+ (StateData#state.config)#config{persistent =
+ Val}};
+ moderated ->
+ StateData#state{config =
+ (StateData#state.config)#config{moderated =
+ Val}};
+ members_by_default ->
+ StateData#state{config =
+ (StateData#state.config)#config{members_by_default
+ = Val}};
+ members_only ->
+ StateData#state{config =
+ (StateData#state.config)#config{members_only
+ = Val}};
+ allow_user_invites ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_user_invites
+ = Val}};
+ password_protected ->
+ StateData#state{config =
+ (StateData#state.config)#config{password_protected
+ = Val}};
+ captcha_protected ->
+ StateData#state{config =
+ (StateData#state.config)#config{captcha_protected
+ = Val}};
+ password ->
+ StateData#state{config =
+ (StateData#state.config)#config{password =
+ Val}};
+ anonymous ->
+ StateData#state{config =
+ (StateData#state.config)#config{anonymous =
+ Val}};
+ logging ->
+ StateData#state{config =
+ (StateData#state.config)#config{logging =
+ Val}};
+ captcha_whitelist ->
+ StateData#state{config =
+ (StateData#state.config)#config{captcha_whitelist
+ =
+ (?SETS):from_list(Val)}};
+ allow_voice_requests ->
+ StateData#state{config =
+ (StateData#state.config)#config{allow_voice_requests
+ = Val}};
+ voice_request_min_interval ->
+ StateData#state{config =
+ (StateData#state.config)#config{voice_request_min_interval
+ = Val}};
+ max_users ->
+ ServiceMaxUsers = get_service_max_users(StateData),
+ MaxUsers = if Val =< ServiceMaxUsers -> Val;
+ true -> ServiceMaxUsers
+ end,
+ StateData#state{config =
+ (StateData#state.config)#config{max_users =
+ MaxUsers}};
+ affiliations ->
+ StateData#state{affiliations = (?DICT):from_list(Val)};
+ subject -> StateData#state{subject = Val};
+ subject_author -> StateData#state{subject_author = Val};
+ _ -> StateData
end,
set_opts(Opts, NSD).
-define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
+
make_opts(StateData) ->
Config = StateData#state.config,
- [
- ?MAKE_CONFIG_OPT(title),
- ?MAKE_CONFIG_OPT(description),
+ [?MAKE_CONFIG_OPT(title), ?MAKE_CONFIG_OPT(description),
?MAKE_CONFIG_OPT(allow_change_subj),
?MAKE_CONFIG_OPT(allow_query_users),
?MAKE_CONFIG_OPT(allow_private_messages),
?MAKE_CONFIG_OPT(allow_private_messages_from_visitors),
?MAKE_CONFIG_OPT(allow_visitor_status),
?MAKE_CONFIG_OPT(allow_visitor_nickchange),
- ?MAKE_CONFIG_OPT(public),
- ?MAKE_CONFIG_OPT(public_list),
+ ?MAKE_CONFIG_OPT(public), ?MAKE_CONFIG_OPT(public_list),
?MAKE_CONFIG_OPT(persistent),
?MAKE_CONFIG_OPT(moderated),
?MAKE_CONFIG_OPT(members_by_default),
@@ -3702,471 +3950,515 @@ make_opts(StateData) ->
?MAKE_CONFIG_OPT(allow_user_invites),
?MAKE_CONFIG_OPT(password_protected),
?MAKE_CONFIG_OPT(captcha_protected),
- ?MAKE_CONFIG_OPT(password),
- ?MAKE_CONFIG_OPT(anonymous),
- ?MAKE_CONFIG_OPT(logging),
- ?MAKE_CONFIG_OPT(max_users),
+ ?MAKE_CONFIG_OPT(password), ?MAKE_CONFIG_OPT(anonymous),
+ ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users),
?MAKE_CONFIG_OPT(allow_voice_requests),
?MAKE_CONFIG_OPT(voice_request_min_interval),
{captcha_whitelist,
- ?SETS:to_list((StateData#state.config)#config.captcha_whitelist)},
- {affiliations, ?DICT:to_list(StateData#state.affiliations)},
+ (?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
+ {affiliations,
+ (?DICT):to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
- {subject_author, StateData#state.subject_author}
- ].
-
-
+ {subject_author, StateData#state.subject_author}].
destroy_room(DEl, StateData) ->
- lists:foreach(
- fun({_LJID, Info}) ->
- Nick = Info#user.nick,
- ItemAttrs = [{"affiliation", "none"},
- {"role", "none"}],
- Packet = {xmlelement, "presence", [{"type", "unavailable"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
- [{xmlelement, "item", ItemAttrs, []}, DEl]}]},
- route_stanza(
- jlib:jid_replace_resource(StateData#state.jid, Nick),
- Info#user.jid,
- Packet)
- end, ?DICT:to_list(StateData#state.users)),
+ 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]}]},
+ route_stanza(jlib:jid_replace_resource(StateData#state.jid,
+ Nick),
+ Info#user.jid, Packet)
+ 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,
+ true ->
+ mod_muc:forget_room(StateData#state.server_host,
+ StateData#state.host, StateData#state.room);
+ false -> ok
+ end,
{result, [], stop}.
-
-
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Disco
--define(FEATURE(Var), {xmlelement, "feature", [{"var", Var}], []}).
+-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)
- end).
+ case Opt of
+ true -> ?FEATURE(Fiftrue);
+ false -> ?FEATURE(Fiffalse)
+ end).
process_iq_disco_info(_From, set, _Lang, _StateData) ->
{error, ?ERR_NOT_ALLOWED};
-
process_iq_disco_info(_From, get, Lang, StateData) ->
Config = StateData#state.config,
- {result, [{xmlelement, "identity",
- [{"category", "conference"},
- {"type", "text"},
- {"name", get_title(StateData)}], []},
- {xmlelement, "feature",
- [{"var", ?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")
- ] ++ iq_disco_info_extras(Lang, StateData), StateData}.
+ {result,
+ [#xmlel{name = <<"identity">>,
+ attrs =
+ [{<<"category">>, <<"conference">>},
+ {<<"type">>, <<"text">>},
+ {<<"name">>, get_title(StateData)}],
+ 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">>)]
+ ++ iq_disco_info_extras(Lang, StateData),
+ StateData}.
-define(RFIELDT(Type, Var, Val),
- {xmlelement, "field", [{"type", Type}, {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
-define(RFIELD(Label, Var, Val),
- {xmlelement, "field", [{"label", translate:translate(Lang, Label)},
- {"var", Var}],
- [{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+ #xmlel{name = <<"field">>,
+ attrs =
+ [{<<"label">>, translate:translate(Lang, Label)},
+ {<<"var">>, Var}],
+ children =
+ [#xmlel{name = <<"value">>, attrs = [],
+ children = [{xmlcdata, Val}]}]}).
iq_disco_info_extras(Lang, StateData) ->
- Len = ?DICT:size(StateData#state.users),
- RoomDescription = (StateData#state.config)#config.description,
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
- [?RFIELDT("hidden", "FORM_TYPE",
- "http://jabber.org/protocol/muc#roominfo"),
- ?RFIELD("Room description", "muc#roominfo_description",
- RoomDescription),
- ?RFIELD("Number of occupants", "muc#roominfo_occupants",
- integer_to_list(Len))
- ]}].
+ 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) ->
{error, ?ERR_NOT_ALLOWED};
-
process_iq_disco_items(From, get, _Lang, StateData) ->
case (StateData#state.config)#config.public_list of
- true ->
- {result, get_mucroom_disco_items(StateData), StateData};
- _ ->
- case is_occupant_or_admin(From, StateData) of
- true ->
- {result, get_mucroom_disco_items(StateData), StateData};
- _ ->
- {error, ?ERR_FORBIDDEN}
- end
+ true ->
+ {result, get_mucroom_disco_items(StateData), StateData};
+ _ ->
+ case is_occupant_or_admin(From, StateData) of
+ true ->
+ {result, get_mucroom_disco_items(StateData), StateData};
+ _ -> {error, ?ERR_FORBIDDEN}
+ end
end.
-process_iq_captcha(_From, get, _Lang, _SubEl, _StateData) ->
+process_iq_captcha(_From, get, _Lang, _SubEl,
+ _StateData) ->
{error, ?ERR_NOT_ALLOWED};
-
-process_iq_captcha(_From, set, _Lang, SubEl, StateData) ->
+process_iq_captcha(_From, set, _Lang, SubEl,
+ StateData) ->
case ejabberd_captcha:process_reply(SubEl) of
- ok ->
- {result, [], StateData};
- _ ->
- {error, ?ERR_NOT_ACCEPTABLE}
+ ok -> {result, [], StateData};
+ _ -> {error, ?ERR_NOT_ACCEPTABLE}
end.
get_title(StateData) ->
case (StateData#state.config)#config.title of
- "" ->
- StateData#state.room;
- Name ->
- Name
+ <<"">> -> StateData#state.room;
+ Name -> Name
end.
get_roomdesc_reply(JID, StateData, Tail) ->
- IsOccupantOrAdmin = is_occupant_or_admin(JID, StateData),
- if (StateData#state.config)#config.public or IsOccupantOrAdmin ->
- if (StateData#state.config)#config.public_list or IsOccupantOrAdmin ->
- {item, get_title(StateData) ++ Tail};
- true ->
- {item, get_title(StateData)}
- end;
- true ->
- false
+ IsOccupantOrAdmin = is_occupant_or_admin(JID,
+ StateData),
+ if (StateData#state.config)#config.public or
+ IsOccupantOrAdmin ->
+ if (StateData#state.config)#config.public_list or
+ IsOccupantOrAdmin ->
+ {item, <<(get_title(StateData))/binary,Tail/binary>>};
+ true -> {item, get_title(StateData)}
+ end;
+ true -> false
end.
get_roomdesc_tail(StateData, Lang) ->
Desc = case (StateData#state.config)#config.public of
- true ->
- "";
- _ ->
- translate:translate(Lang, "private, ")
+ true -> <<"">>;
+ _ -> translate:translate(Lang, <<"private, ">>)
end,
- Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0, StateData#state.users),
- " (" ++ Desc ++ integer_to_list(Len) ++ ")".
+ Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0,
+ StateData#state.users),
+ <<" (", Desc/binary,
+ (iolist_to_binary(integer_to_list(Len)))/binary, ")">>.
get_mucroom_disco_items(StateData) ->
- lists:map(
- fun({_LJID, Info}) ->
- Nick = Info#user.nick,
- {xmlelement, "item",
- [{"jid", jlib:jid_to_string({StateData#state.room,
- StateData#state.host, Nick})},
- {"name", Nick}], []}
- end,
- ?DICT:to_list(StateData#state.users)).
+ lists:map(fun ({_LJID, Info}) ->
+ Nick = Info#user.nick,
+ #xmlel{name = <<"item">>,
+ attrs =
+ [{<<"jid">>,
+ jlib:jid_to_string({StateData#state.room,
+ StateData#state.host,
+ Nick})},
+ {<<"name">>, Nick}],
+ children = []}
+ end,
+ (?DICT):to_list(StateData#state.users)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Voice request support
is_voice_request(Els) ->
- lists:foldl(
- fun({xmlelement, "x", Attrs, _} = El, false) ->
- case xml: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).
+ lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
+ El,
+ false) ->
+ case xml: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).
prepare_request_form(Requester, Nick, Lang) ->
- {xmlelement, "message", [{"type", "normal"}],
- [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
- [{xmlelement, "title", [],
- [{xmlcdata, translate:translate(Lang, "Voice request")}]},
- {xmlelement, "instructions", [],
- [{xmlcdata,
- translate:translate(
- Lang, "Either approve or decline the voice request.")}]},
- {xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
- [{xmlelement, "value", [],
- [{xmlcdata, "http://jabber.org/protocol/muc#request"}]}]},
- {xmlelement, "field", [{"var", "muc#role"}, {"type", "hidden"}],
- [{xmlelement, "value", [], [{xmlcdata, "participant"}]}]},
- ?STRINGXFIELD("User JID", "muc#jid", jlib:jid_to_string(Requester)),
- ?STRINGXFIELD("Nickname", "muc#roomnick", Nick),
- ?BOOLXFIELD("Grant voice to this person?", "muc#request_allow",
- list_to_atom("false"))
- ]}]}.
+ #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">>,
+ (jlib: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) ->
Moderators = search_role(moderator, StateData),
FromNick = find_nick_by_jid(From, StateData),
- lists:foreach(
- fun({_, User}) ->
- route_stanza(
- StateData#state.jid,
- User#user.jid,
- prepare_request_form(From, FromNick, ""))
- end, Moderators).
+ lists:foreach(fun ({_, User}) ->
+ route_stanza(StateData#state.jid, User#user.jid,
+ prepare_request_form(From, FromNick,
+ <<"">>))
+ end,
+ Moderators).
is_voice_approvement(Els) ->
- lists:foldl(
- fun({xmlelement, "x", Attrs, _} = El, false) ->
- case xml: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).
+ lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
+ El,
+ false) ->
+ case xml: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({xmlelement, "x", _, _} = El, error) ->
- Fields = case jlib:parse_xdata_submit(El) of
- invalid -> [];
- Res -> Res
- end,
- lists:foldl(
- fun({"muc#jid", [JIDStr]}, error) ->
- case jlib:string_to_jid(JIDStr) of
- error -> error;
- J -> {ok, J}
- end;
- (_, Acc) ->
- Acc
- end, error, Fields);
- (_, Acc) ->
- Acc
- end, error, 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 jlib:string_to_jid(JIDStr) of
+ error -> error;
+ J -> {ok, J}
+ end;
+ (_, Acc) -> Acc
+ end,
+ error, Fields);
+ (_, Acc) -> Acc
+ end,
+ error, Els).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Invitation support
is_invitation(Els) ->
- lists:foldl(
- fun({xmlelement, "x", Attrs, _} = El, false) ->
- case xml:get_attr_s("xmlns", Attrs) of
- ?NS_MUC_USER ->
- case xml:get_subtag(El, "invite") of
- false ->
- false;
- _ ->
- true
- end;
- _ ->
- false
- end;
- (_, Acc) ->
- Acc
- end, false, Els).
+ lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
+ El,
+ false) ->
+ case xml:get_attr_s(<<"xmlns">>, Attrs) of
+ ?NS_MUC_USER ->
+ case xml:get_subtag(El, <<"invite">>) of
+ false -> false;
+ _ -> true
+ end;
+ _ -> false
+ end;
+ (_, Acc) -> Acc
+ end,
+ false, Els).
check_invitation(From, Els, Lang, StateData) ->
FAffiliation = get_affiliation(From, StateData),
- CanInvite = (StateData#state.config)#config.allow_user_invites
- orelse (FAffiliation == admin) orelse (FAffiliation == owner),
+ CanInvite =
+ (StateData#state.config)#config.allow_user_invites
+ orelse
+ FAffiliation == admin orelse FAffiliation == owner,
InviteEl = case xml:remove_cdata(Els) of
- [{xmlelement, "x", _Attrs1, Els1} = XEl] ->
- case xml:get_tag_attr_s("xmlns", XEl) of
- ?NS_MUC_USER ->
- ok;
- _ ->
- throw({error, ?ERR_BAD_REQUEST})
- end,
- case xml:remove_cdata(Els1) of
- [{xmlelement, "invite", _Attrs2, _Els2} = InviteEl1] ->
- InviteEl1;
- _ ->
- throw({error, ?ERR_BAD_REQUEST})
- end;
- _ ->
- throw({error, ?ERR_BAD_REQUEST})
+ [#xmlel{name = <<"x">>, children = Els1} = XEl] ->
+ case xml:get_tag_attr_s(<<"xmlns">>, XEl) of
+ ?NS_MUC_USER -> ok;
+ _ -> throw({error, ?ERR_BAD_REQUEST})
+ end,
+ case xml:remove_cdata(Els1) of
+ [#xmlel{name = <<"invite">>} = InviteEl1] -> InviteEl1;
+ _ -> throw({error, ?ERR_BAD_REQUEST})
+ end;
+ _ -> throw({error, ?ERR_BAD_REQUEST})
end,
- JID = case jlib:string_to_jid(
- xml:get_tag_attr_s("to", InviteEl)) of
- error ->
- throw({error, ?ERR_JID_MALFORMED});
- JID1 ->
- JID1
+ JID = case
+ jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>,
+ InviteEl))
+ of
+ error -> throw({error, ?ERR_JID_MALFORMED});
+ JID1 -> JID1
end,
case CanInvite of
- false ->
- throw({error, ?ERR_NOT_ALLOWED});
- true ->
- Reason =
- xml:get_path_s(
- InviteEl,
- [{elem, "reason"}, cdata]),
- ContinueEl =
- case xml:get_path_s(
- InviteEl,
- [{elem, "continue"}]) of
- [] -> [];
- Continue1 -> [Continue1]
- end,
- IEl =
- [{xmlelement, "invite",
- [{"from",
- jlib:jid_to_string(From)}],
- [{xmlelement, "reason", [],
- [{xmlcdata, Reason}]}] ++ ContinueEl}],
- PasswdEl =
- case (StateData#state.config)#config.password_protected of
- true ->
- [{xmlelement, "password", [],
- [{xmlcdata, (StateData#state.config)#config.password}]}];
- _ ->
- []
- end,
- Body =
- {xmlelement, "body", [],
- [{xmlcdata,
- lists:flatten(
- io_lib:format(
- translate:translate(
- Lang,
- "~s invites you to the room ~s"),
- [jlib:jid_to_string(From),
- jlib:jid_to_string({StateData#state.room,
- StateData#state.host,
- ""})
- ])) ++
- case (StateData#state.config)#config.password_protected of
+ false -> throw({error, ?ERR_NOT_ALLOWED});
+ true ->
+ Reason = xml:get_path_s(InviteEl,
+ [{elem, <<"reason">>}, cdata]),
+ ContinueEl = case xml:get_path_s(InviteEl,
+ [{elem, <<"continue">>}])
+ of
+ <<>> -> [];
+ Continue1 -> [Continue1]
+ end,
+ IEl = [#xmlel{name = <<"invite">>,
+ attrs = [{<<"from">>, jlib:jid_to_string(From)}],
+ children =
+ [#xmlel{name = <<"reason">>, attrs = [],
+ children = [{xmlcdata, Reason}]}]
+ ++ ContinueEl}],
+ PasswdEl = case
+ (StateData#state.config)#config.password_protected
+ of
true ->
- ", " ++
- translate:translate(Lang, "the password is") ++
- " '" ++
- (StateData#state.config)#config.password ++ "'";
- _ ->
- ""
- end ++
- case Reason of
- "" -> "";
- _ -> " (" ++ Reason ++ ") "
- end
- }]},
- Msg =
- {xmlelement, "message",
- [{"type", "normal"}],
- [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], IEl ++ PasswdEl},
- {xmlelement, "x",
- [{"xmlns", ?NS_XCONFERENCE},
- {"jid", jlib:jid_to_string(
- {StateData#state.room,
- StateData#state.host,
- ""})}],
- [{xmlcdata, Reason}]},
- Body]},
- route_stanza(StateData#state.jid, JID, Msg),
- JID
+ [#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">>),
+ [jlib:jid_to_string(From),
+ jlib: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,
+ " '",
+ ((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">>,
+ jlib:jid_to_string({StateData#state.room,
+ StateData#state.host,
+ <<"">>})}],
+ children = [{xmlcdata, Reason}]},
+ Body]},
+ route_stanza(StateData#state.jid, JID, Msg),
+ JID
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) ->
+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)
+ {true, Decline_data} ->
+ send_decline_invitation(Decline_data,
+ StateData#state.jid, From);
+ _ ->
+ send_error_only_occupants(Packet, Lang,
+ StateData#state.jid, From)
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) ->
- {xmlelement, "message", _, _} = Packet,
- XEl = xml:get_subtag(Packet, "x"),
- ?NS_MUC_USER = xml:get_tag_attr_s("xmlns", XEl),
- DEl = xml:get_subtag(XEl, "decline"),
- ToString = xml:get_tag_attr_s("to", DEl),
+ #xmlel{name = <<"message">>} = Packet,
+ XEl = xml:get_subtag(Packet, <<"x">>),
+ (?NS_MUC_USER) = xml:get_tag_attr_s(<<"xmlns">>, XEl),
+ DEl = xml:get_subtag(XEl, <<"decline">>),
+ ToString = xml:get_tag_attr_s(<<"to">>, DEl),
ToJID = jlib:string_to_jid(ToString),
{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 = jlib:jid_to_string(jlib:jid_remove_resource(FromJID)),
- {xmlelement, "decline", DAttrs, DEls} = DEl,
- DAttrs2 = lists:keydelete("to", 1, DAttrs),
- DAttrs3 = [{"from", FromString} | DAttrs2],
- DEl2 = {xmlelement, "decline", DAttrs3, DEls},
+send_decline_invitation({Packet, XEl, DEl, ToJID},
+ RoomJID, FromJID) ->
+ FromString =
+ jlib:jid_to_string(jlib: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),
route_stanza(RoomJID, ToJID, Packet2).
-%% Given an element and a new subelement,
-%% replace the instance of the subelement in element with the new subelement.
-replace_subelement({xmlelement, Name, Attrs, SubEls}, NewSubEl) ->
+replace_subelement(#xmlel{name = Name, attrs = Attrs,
+ children = SubEls},
+ NewSubEl) ->
{_, NameNewSubEl, _, _} = NewSubEl,
- SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
- {xmlelement, Name, Attrs, 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)),
+ 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)),
route_stanza(RoomJID, From, Err).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Logging
add_to_log(Type, Data, StateData)
- when Type == roomconfig_change_disabledlogging ->
- %% When logging is disabled, the config change message must be logged:
- mod_muc_log:add_to_log(
- StateData#state.server_host, roomconfig_change, Data,
- StateData#state.jid, make_opts(StateData));
+ when Type == roomconfig_change_disabledlogging ->
+ mod_muc_log:add_to_log(StateData#state.server_host,
+ roomconfig_change, Data, StateData#state.jid,
+ make_opts(StateData));
add_to_log(Type, Data, StateData) ->
case (StateData#state.config)#config.logging of
- true ->
- mod_muc_log:add_to_log(
- StateData#state.server_host, Type, Data,
- StateData#state.jid, make_opts(StateData));
- false ->
- ok
+ true ->
+ mod_muc_log:add_to_log(StateData#state.server_host,
+ Type, Data, StateData#state.jid,
+ make_opts(StateData));
+ false -> ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -4177,45 +4469,46 @@ tab_add_online_user(JID, StateData) ->
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
- catch ets:insert(
- muc_online_users,
- #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
-
+ catch ets:insert(muc_online_users,
+ #muc_online_users{us = US, resource = LResource,
+ room = Room, host = Host}).
tab_remove_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jlib:jid_tolower(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
- catch ets:delete_object(
- muc_online_users,
- #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
+ catch ets:delete_object(muc_online_users,
+ #muc_online_users{us = US, resource = LResource,
+ room = Room, host = Host}).
tab_count_user(JID) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
US = {LUser, LServer},
- case catch ets:select(
- muc_online_users,
- [{#muc_online_users{us = US, _ = '_'}, [], [[]]}]) of
- Res when is_list(Res) ->
- length(Res);
- _ ->
- 0
+ case catch ets:select(muc_online_users,
+ [{#muc_online_users{us = US, _ = '_'}, [], [[]]}])
+ of
+ Res when is_list(Res) -> length(Res);
+ _ -> 0
end.
-element_size(El) ->
- size(xml:element_to_binary(El)).
+element_size(El) -> byte_size(xml:element_to_binary(El)).
route_stanza(From, To, El) ->
case mod_muc:is_broadcasted(From#jid.lserver) of
- true ->
- #jid{luser = LUser, lserver = LServer} = To,
- case ejabberd_cluster:get_node({LUser, LServer}) of
- Node when Node == node() ->
- ejabberd_router:route(From, To, El);
- _ ->
- ok
- end;
- false ->
- ejabberd_router:route(From, To, El)
+ true ->
+ #jid{luser = LUser, lserver = LServer} = To,
+ case ejabberd_cluster:get_node({LUser, LServer}) of
+ Node when Node == node() ->
+ ejabberd_router:route(From, To, El);
+ _ -> ok
+ end;
+ false -> ejabberd_router:route(From, To, El)
end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Multicast
+
+send_multiple(From, Server, Users, Packet) ->
+ JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
+ ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
diff --git a/src/mod_muc/mod_muc_room.hrl b/src/mod_muc/mod_muc_room.hrl
index ecc044909..b42e4757d 100644
--- a/src/mod_muc/mod_muc_room.hrl
+++ b/src/mod_muc/mod_muc_room.hrl
@@ -22,70 +22,96 @@
-define(MAX_USERS_DEFAULT, 200).
-define(SETS, gb_sets).
+
-define(DICT, dict).
--record(lqueue, {queue, len, max}).
+-record(lqueue,
+{
+ queue :: queue(),
+ len :: integer(),
+ max :: integer()
+}).
+
+-type lqueue() :: #lqueue{}.
+
+-record(config,
+{
+ title = <<"">> :: binary(),
+ description = <<"">> :: binary(),
+ allow_change_subj = true :: boolean(),
+ allow_query_users = true :: boolean(),
+ allow_private_messages = true :: boolean(),
+ allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
+ allow_visitor_status = true :: boolean(),
+ allow_visitor_nickchange = true :: boolean(),
+ public = true :: boolean(),
+ public_list = true :: boolean(),
+ persistent = false :: boolean(),
+ moderated = true :: boolean(),
+ captcha_protected = false :: boolean(),
+ members_by_default = true :: boolean(),
+ members_only = false :: boolean(),
+ allow_user_invites = false :: boolean(),
+ password_protected = false :: boolean(),
+ password = <<"">> :: binary(),
+ anonymous = true :: boolean(),
+ allow_voice_requests = true :: boolean(),
+ voice_request_min_interval = 1800 :: non_neg_integer(),
+ max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none,
+ logging = false :: boolean(),
+ captcha_whitelist = (?SETS):empty() :: gb_set()
+}).
+
+-type config() :: #config{}.
+
+-type role() :: moderator | participant | visitor | none.
+
+-record(user,
+{
+ jid :: jid(),
+ nick :: binary(),
+ role :: role(),
+ last_presence :: xmlel()
+}).
--record(config, {title = "",
- description = "",
- allow_change_subj = true,
- allow_query_users = true,
- allow_private_messages = true,
- allow_private_messages_from_visitors = anyone,
- allow_visitor_status = true,
- allow_visitor_nickchange = true,
- public = true,
- public_list = true,
- persistent = false,
- moderated = true,
- captcha_protected = false,
- members_by_default = true,
- members_only = false,
- allow_user_invites = false,
- password_protected = false,
- password = "",
- anonymous = true,
- allow_voice_requests = true,
- voice_request_min_interval = 1800,
- max_users = ?MAX_USERS_DEFAULT,
- logging = false,
- captcha_whitelist = ?SETS:empty()
- }).
+-record(activity,
+{
+ message_time = 0 :: integer(),
+ presence_time = 0 :: integer(),
+ message_shaper :: shaper:shaper(),
+ presence_shaper :: shaper:shaper(),
+ message :: xmlel(),
+ presence :: {binary(), xmlel()}
+}).
--record(user, {jid,
- nick,
- role,
- last_presence}).
+-record(state,
+{
+ room = <<"">> :: binary(),
+ host = <<"">> :: binary(),
+ server_host = <<"">> :: binary(),
+ access = {none,none,none,none} :: {atom(), atom(), atom(), atom()},
+ jid = #jid{} :: jid(),
+ config = #config{} :: config(),
+ users = (?DICT):new() :: dict(),
+ last_voice_request_time = treap:empty() :: treap:treap(),
+ robots = (?DICT):new() :: dict(),
+ nicks = (?DICT):new() :: dict(),
+ affiliations = (?DICT):new() :: dict(),
+ history :: lqueue(),
+ persist_history = false :: boolean(),
+ subject = <<"">> :: binary(),
+ subject_author = <<"">> :: binary(),
+ just_created = false :: boolean(),
+ activity = treap:empty() :: treap:treap(),
+ room_shaper = none :: shaper:shaper(),
+ room_queue = queue:new() :: queue()
+}).
--record(activity, {message_time = 0,
- presence_time = 0,
- message_shaper,
- presence_shaper,
- message,
- presence}).
+-record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()},
+ resource = <<>> :: binary() | '_',
+ room = <<>> :: binary() | '_',
+ host = <<>> :: binary() | '_'}).
--record(state, {room,
- host,
- server_host,
- mod,
- access,
- jid,
- config = #config{},
- users = ?DICT:new(),
- last_voice_request_time = treap:empty(),
- robots = ?DICT:new(),
- nicks = ?DICT:new(),
- affiliations = ?DICT:new(),
- history,
- persist_history = false,
- subject = "",
- subject_author = "",
- just_created = false,
- activity = treap:empty(),
- room_shaper,
- room_queue = queue:new()}).
+-type muc_online_users() :: #muc_online_users{}.
--record(muc_online_users, {us,
- resource,
- room,
- host}).
+-type muc_room_state() :: #state{}.