diff options
author | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
---|---|---|
committer | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
commit | 9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch) | |
tree | 7e1066c130250627ee0abab44a135f583a28d07f /src/mod_irc | |
parent | list_to_integer/2 only works in OTP R14 and newer (diff) |
Accumulated patch to binarize and indent code
Diffstat (limited to 'src/mod_irc')
-rw-r--r-- | src/mod_irc/Makefile.in | 2 | ||||
-rw-r--r-- | src/mod_irc/iconv.erl | 46 | ||||
-rw-r--r-- | src/mod_irc/mod_irc.erl | 1860 | ||||
-rw-r--r-- | src/mod_irc/mod_irc_connection.erl | 2274 |
4 files changed, 2294 insertions, 1888 deletions
diff --git a/src/mod_irc/Makefile.in b/src/mod_irc/Makefile.in index e1551f929..9dcf9f182 100644 --- a/src/mod_irc/Makefile.in +++ b/src/mod_irc/Makefile.in @@ -24,7 +24,7 @@ EFLAGS += -pz .. # make debug=true to compile Erlang module with debug informations. ifdef debug - EFLAGS+=+debug_info +export_all + EFLAGS+=+debug_info endif ERLSHLIBS = ../iconv_erl.so diff --git a/src/mod_irc/iconv.erl b/src/mod_irc/iconv.erl index b93bb2ea3..4d8180539 100644 --- a/src/mod_irc/iconv.erl +++ b/src/mod_irc/iconv.erl @@ -25,6 +25,7 @@ %%%---------------------------------------------------------------------- -module(iconv). + -author('alexey@process-one.net'). -behaviour(gen_server). @@ -32,63 +33,50 @@ -export([start/0, start_link/0, convert/3]). %% Internal exports, call-back functions. --export([init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - code_change/3, - terminate/2]). - - +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, code_change/3, terminate/2]). start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + gen_server:start_link({local, ?MODULE}, ?MODULE, [], + []). init([]) -> - case erl_ddll:load_driver(ejabberd:get_so_path(), iconv_erl) of - ok -> ok; - {error, already_loaded} -> ok + case erl_ddll:load_driver(ejabberd:get_so_path(), + iconv_erl) + of + ok -> ok; + {error, already_loaded} -> ok end, Port = open_port({spawn, "iconv_erl"}, []), ets:new(iconv_table, [set, public, named_table]), ets:insert(iconv_table, {port, Port}), {ok, Port}. - %%% -------------------------------------------------------- %%% The call-back functions. %%% -------------------------------------------------------- -handle_call(_, _, State) -> - {noreply, State}. +handle_call(_, _, State) -> {noreply, State}. -handle_cast(_, State) -> - {noreply, State}. +handle_cast(_, State) -> {noreply, State}. handle_info({'EXIT', Port, Reason}, Port) -> {stop, {port_died, Reason}, Port}; handle_info({'EXIT', _Pid, _Reason}, Port) -> {noreply, Port}; -handle_info(_, State) -> - {noreply, State}. +handle_info(_, State) -> {noreply, State}. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -terminate(_Reason, Port) -> - Port ! {self, close}, - ok. +code_change(_OldVsn, State, _Extra) -> {ok, State}. +terminate(_Reason, Port) -> Port ! {self, close}, ok. +-spec convert(binary(), binary(), binary()) -> binary(). convert(From, To, String) -> [{port, Port} | _] = ets:lookup(iconv_table, port), Bin = term_to_binary({From, To, String}), BRes = port_control(Port, 1, Bin), - binary_to_list(BRes). - - - + (BRes). diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index 554a75c6b..53069671d 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -25,34 +25,52 @@ %%%---------------------------------------------------------------------- -module(mod_irc). + -author('alexey@process-one.net'). -behaviour(gen_server). + -behaviour(gen_mod). %% API --export([start_link/2, - start/2, - stop/1, - closed_connection/3, - get_connection_params/3]). +-export([start_link/2, start/2, stop/1, export/1, + closed_connection/3, get_connection_params/3]). %% 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("adhoc.hrl"). --define(DEFAULT_IRC_ENCODING, "iso8859-1"). +-define(DEFAULT_IRC_ENCODING, <<"iso8859-1">>). + -define(DEFAULT_IRC_PORT, 6667). --define(POSSIBLE_ENCODINGS, ["koi8-r", "iso8859-1", "iso8859-2", "utf-8", "utf-8+latin-1"]). --record(irc_connection, {jid_server_host, pid}). --record(irc_custom, {us_host, data}). +-define(POSSIBLE_ENCODINGS, + [<<"koi8-r">>, <<"iso8859-1">>, <<"iso8859-2">>, + <<"utf-8">>, <<"utf-8+latin-1">>]). + +-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} | + {binary(), binary(), inet:port_number()} | + {binary(), binary()}. + +-record(irc_connection, + {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()}, + pid = self() :: pid()}). --record(state, {host, server_host, access}). +-record(irc_custom, + {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, + binary()}, + data = [] :: [{username, binary()} | + {connections_params, [conn_param()]}]}). + +-record(state, {host = <<"">> :: binary(), + server_host = <<"">> :: binary(), + access = all :: atom()}). -define(PROCNAME, ejabberd_mod_irc). @@ -65,18 +83,14 @@ %%-------------------------------------------------------------------- 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) -> @@ -98,25 +112,26 @@ stop(Host) -> %%-------------------------------------------------------------------- init([Host, Opts]) -> iconv:start(), - MyHost = gen_mod:get_opt_host(Host, Opts, "irc.@HOST@"), + MyHost = gen_mod:get_opt_host(Host, Opts, + <<"irc.@HOST@">>), case gen_mod:db_type(Opts) of - mnesia -> - mnesia:create_table(irc_custom, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, irc_custom)}]), - update_table(MyHost); - _ -> - ok + mnesia -> + mnesia:create_table(irc_custom, + [{disc_copies, [node()]}, + {attributes, record_info(fields, irc_custom)}]), + update_table(); + _ -> ok end, - Access = gen_mod:get_opt(access, Opts, all), - catch ets:new(irc_connection, [named_table, - public, - {keypos, #irc_connection.jid_server_host}]), + Access = gen_mod:get_opt(access, Opts, + fun(A) when is_atom(A) -> A end, + all), + catch ets:new(irc_connection, + [named_table, public, + {keypos, #irc_connection.jid_server_host}]), ejabberd_router:register_route(MyHost), - {ok, #state{host = MyHost, - server_host = Host, - access = Access}}. + {ok, + #state{host = MyHost, server_host = Host, + access = Access}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | @@ -136,8 +151,7 @@ handle_call(stop, _From, State) -> %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast(_Msg, State) -> - {noreply, State}. +handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | @@ -146,18 +160,17 @@ handle_cast(_Msg, State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({route, From, To, Packet}, - #state{host = Host, - server_host = ServerHost, - access = Access} = State) -> - case catch do_route(Host, ServerHost, Access, From, To, Packet) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - _ -> - ok + #state{host = Host, server_host = ServerHost, + access = Access} = + State) -> + case catch do_route(Host, ServerHost, Access, From, To, + Packet) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + _ -> ok end, {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() @@ -167,919 +180,1110 @@ handle_info(_Info, State) -> %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, State) -> - ejabberd_router:unregister_route(State#state.host), - ok. + 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_irc_sup), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, mod_irc_connection]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, + Proc = gen_mod:get_module_proc(Host, + ejabberd_mod_irc_sup), + ChildSpec = {Proc, + {ejabberd_tmp_sup, start_link, + [Proc, mod_irc_connection]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, ejabberd_mod_irc_sup), + Proc = gen_mod:get_module_proc(Host, + ejabberd_mod_irc_sup), supervisor:terminate_child(ejabberd_sup, Proc), supervisor:delete_child(ejabberd_sup, Proc). do_route(Host, ServerHost, Access, From, To, Packet) -> case acl:match_rule(ServerHost, Access, From) of - allow -> - do_route1(Host, ServerHost, From, To, Packet); - _ -> - {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(To, From, Err) + allow -> do_route1(Host, ServerHost, From, To, Packet); + _ -> + #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(To, From, Err) end. do_route1(Host, ServerHost, From, To, Packet) -> #jid{user = ChanServ, resource = Resource} = To, - {xmlelement, _Name, _Attrs, _Els} = Packet, + #xmlel{} = Packet, case ChanServ of - "" -> - case Resource of - "" -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, - sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s("node", SubEl), - Info = ejabberd_hooks:run_fold( - disco_info, ServerHost, [], - [ServerHost, ?MODULE, "", ""]), - case iq_disco(ServerHost, Node, Lang) of - [] -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)); - DiscoInfo -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - DiscoInfo ++ Info}]}, - ejabberd_router:route(To, - From, - jlib:iq_to_xml(Res)) - end; - #iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS, - sub_el = SubEl, lang = Lang} = IQ -> - Node = xml:get_tag_attr_s("node", SubEl), - case Node of - [] -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - "join" -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - "register" -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - []}]}, - Res = jlib:iq_to_xml(ResIQ); - ?NS_COMMANDS -> - ResIQ = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}, - {"node", Node}], - command_items(ServerHost, - Host, Lang)}]}, - Res = jlib:iq_to_xml(ResIQ); - _ -> - Res = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND) - end, - ejabberd_router:route(To, - From, - Res); - #iq{xmlns = ?NS_REGISTER} = IQ -> - process_register(ServerHost, Host, From, To, IQ); - #iq{type = get, xmlns = ?NS_VCARD = XMLNS, - lang = Lang} = IQ -> + <<"">> -> + case Resource of + <<"">> -> + case jlib:iq_query_info(Packet) of + #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS, + sub_el = SubEl, lang = Lang} = + IQ -> + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + Info = ejabberd_hooks:run_fold(disco_info, ServerHost, + [], + [ServerHost, ?MODULE, + <<"">>, <<"">>]), + case iq_disco(ServerHost, Node, Lang) of + [] -> 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 = set, xmlns = ?NS_COMMANDS, - lang = _Lang, sub_el = SubEl} = IQ -> - Request = adhoc:parse_request(IQ), - case lists:keysearch(Request#adhoc_request.node, - 1, commands(ServerHost)) of - {value, {_, _, Function}} -> - case catch Function(From, To, Request) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", - [Reason, {From, To, IQ}]), - Res = IQ#iq{type = error, sub_el = [SubEl, - ?ERR_INTERNAL_SERVER_ERROR]}; - ignore -> - Res = ignore; - {error, Error} -> - Res = IQ#iq{type = error, sub_el = [SubEl, Error]}; - Command -> - Res = IQ#iq{type = result, sub_el = [Command]} - end, - if Res /= ignore -> - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - true -> - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = []}]}, + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + DiscoInfo -> + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = + DiscoInfo ++ Info}]}, + ejabberd_router:route(To, From, jlib:iq_to_xml(Res)) + end; + #iq{type = get, xmlns = (?NS_DISCO_ITEMS) = XMLNS, + sub_el = SubEl, lang = Lang} = + IQ -> + Node = xml:get_tag_attr_s(<<"node">>, SubEl), + case Node of + <<>> -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = []}]}, + Res = jlib:iq_to_xml(ResIQ); + <<"join">> -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = []}]}, + Res = jlib:iq_to_xml(ResIQ); + <<"register">> -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, + XMLNS}], + children = []}]}, + Res = jlib:iq_to_xml(ResIQ); + ?NS_COMMANDS -> + ResIQ = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}, + {<<"node">>, Node}], + children = + command_items(ServerHost, + Host, + Lang)}]}, + Res = jlib:iq_to_xml(ResIQ); + _ -> + Res = jlib:make_error_reply(Packet, + ?ERR_ITEM_NOT_FOUND) + end, + ejabberd_router:route(To, From, Res); + #iq{xmlns = ?NS_REGISTER} = IQ -> + process_register(ServerHost, Host, From, To, IQ); + #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, + lang = Lang} = + 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 = set, xmlns = ?NS_COMMANDS, lang = _Lang, + sub_el = SubEl} = + IQ -> + Request = adhoc:parse_request(IQ), + case lists:keysearch(Request#adhoc_request.node, 1, + commands(ServerHost)) + of + {value, {_, _, Function}} -> + case catch Function(From, To, Request) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nfor ad-hoc handler of ~p", + [Reason, {From, To, IQ}]), + Res = IQ#iq{type = error, + sub_el = + [SubEl, + ?ERR_INTERNAL_SERVER_ERROR]}; + ignore -> Res = ignore; + {error, Error} -> + Res = IQ#iq{type = error, + sub_el = [SubEl, Error]}; + Command -> + Res = IQ#iq{type = result, sub_el = [Command]} + end, + if Res /= ignore -> + ejabberd_router:route(To, From, + jlib:iq_to_xml(Res)); + true -> ok end; - #iq{} = _IQ -> - Err = jlib:make_error_reply( - Packet, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); _ -> - ok - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end; - _ -> - case string:tokens(ChanServ, "%") of - [[_ | _] = Channel, [_ | _] = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of + Err = jlib:make_error_reply(Packet, + ?ERR_ITEM_NOT_FOUND), + ejabberd_router:route(To, From, Err) + end; + #iq{} = _IQ -> + Err = jlib:make_error_reply(Packet, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + _ -> ok + end; + _ -> + Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, Err) + end; + _ -> + case str:tokens(ChanServ, <<"%">>) of + [<<_, _/binary>> = Channel, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of + [] -> + ?DEBUG("open new connection~n", []), + {Username, Encoding, Port, Password} = + get_connection_params(Host, ServerHost, From, Server), + ConnectionUsername = case Packet of + %% If the user tries to join a + %% chatroom, the packet for sure + %% contains the desired username. + #xmlel{name = <<"presence">>} -> + Resource; + %% Otherwise, there is no firm + %% conclusion from the packet. + %% Better to use the configured + %% username (which defaults to the + %% username part of the JID). + _ -> Username + end, + {ok, Pid} = mod_irc_connection:start(From, Host, + ServerHost, Server, + ConnectionUsername, + Encoding, Port, + Password, ?MODULE), + ets:insert(irc_connection, + #irc_connection{jid_server_host = + {From, Server, Host}, + pid = Pid}), + mod_irc_connection:route_chan(Pid, Channel, Resource, + Packet), + ok; + [R] -> + Pid = R#irc_connection.pid, + ?DEBUG("send to process ~p~n", [Pid]), + mod_irc_connection:route_chan(Pid, Channel, Resource, + Packet), + ok + end; + _ -> + case str:tokens(ChanServ, <<"!">>) of + [<<_, _/binary>> = Nick, <<_, _/binary>> = Server] -> + case ets:lookup(irc_connection, {From, Server, Host}) of [] -> - ?DEBUG("open new connection~n", []), - {Username, Encoding, Port, Password} = get_connection_params( - Host, ServerHost, From, Server), - ConnectionUsername = - case Packet of - %% If the user tries to join a - %% chatroom, the packet for sure - %% contains the desired username. - {xmlelement, "presence", _, _} -> - Resource; - %% Otherwise, there is no firm - %% conclusion from the packet. - %% Better to use the configured - %% username (which defaults to the - %% username part of the JID). - _ -> - Username - end, - {ok, Pid} = mod_irc_connection:start( - From, Host, ServerHost, Server, - ConnectionUsername, Encoding, Port, - Password, ?MODULE), - ets:insert( - irc_connection, - #irc_connection{jid_server_host = {From, Server, Host}, - pid = Pid}), - mod_irc_connection:route_chan( - Pid, Channel, Resource, Packet), - ok; + Err = jlib:make_error_reply(Packet, + ?ERR_SERVICE_UNAVAILABLE), + ejabberd_router:route(To, From, Err); [R] -> Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", - [Pid]), - mod_irc_connection:route_chan( - Pid, Channel, Resource, Packet), + ?DEBUG("send to process ~p~n", [Pid]), + mod_irc_connection:route_nick(Pid, Nick, Packet), ok - end; - _ -> - case string:tokens(ChanServ, "!") of - [[_ | _] = Nick, [_ | _] = Server] -> - case ets:lookup(irc_connection, {From, Server, Host}) of - [] -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err); - [R] -> - Pid = R#irc_connection.pid, - ?DEBUG("send to process ~p~n", - [Pid]), - mod_irc_connection:route_nick( - Pid, Nick, Packet), - ok - end; - _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_BAD_REQUEST), - ejabberd_router:route(To, From, Err) - end - end + end; + _ -> + Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST), + ejabberd_router:route(To, From, Err) + end + end end. - closed_connection(Host, From, Server) -> ets:delete(irc_connection, {From, Server, Host}). - -iq_disco(_ServerHost, [], Lang) -> - [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "irc"}, - {"name", translate:translate(Lang, "IRC Transport")}], []}, - {xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []}, - {xmlelement, "feature", [{"var", ?NS_MUC}], []}, - {xmlelement, "feature", [{"var", ?NS_REGISTER}], []}, - {xmlelement, "feature", [{"var", ?NS_VCARD}], []}, - {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []}]; +iq_disco(_ServerHost, <<>>, Lang) -> + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"conference">>}, + {<<"type">>, <<"irc">>}, + {<<"name">>, + translate:translate(Lang, <<"IRC Transport">>)}], + children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_MUC}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_VCARD}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}]; iq_disco(ServerHost, Node, Lang) -> case lists:keysearch(Node, 1, commands(ServerHost)) of - {value, {_, Name, _}} -> - [{xmlelement, "identity", - [{"category", "automation"}, - {"type", "command-node"}, - {"name", translate:translate(Lang, Name)}], []}, - {xmlelement, "feature", - [{"var", ?NS_COMMANDS}], []}, - {xmlelement, "feature", - [{"var", ?NS_XDATA}], []}]; - _ -> - [] + {value, {_, Name, _}} -> + [#xmlel{name = <<"identity">>, + attrs = + [{<<"category">>, <<"automation">>}, + {<<"type">>, <<"command-node">>}, + {<<"name">>, translate:translate(Lang, Name)}], + children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_COMMANDS}], children = []}, + #xmlel{name = <<"feature">>, + attrs = [{<<"var">>, ?NS_XDATA}], children = []}]; + _ -> [] end. iq_get_vcard(Lang) -> - [{xmlelement, "FN", [], - [{xmlcdata, "ejabberd/mod_irc"}]}, - {xmlelement, "URL", [], - [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++ - "\nCopyright (c) 2003-2013 ProcessOne"}]}]. + [#xmlel{name = <<"FN">>, attrs = [], + children = [{xmlcdata, <<"ejabberd/mod_irc">>}]}, + #xmlel{name = <<"URL">>, attrs = [], + children = [{xmlcdata, ?EJABBERD_URI}]}, + #xmlel{name = <<"DESC">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"ejabberd IRC module">>))/binary, + "\nCopyright (c) 2003-2013 ProcessOne">>}]}]. command_items(ServerHost, Host, Lang) -> - lists:map(fun({Node, Name, _Function}) - -> {xmlelement, "item", - [{"jid", Host}, - {"node", Node}, - {"name", translate:translate(Lang, Name)}], []} - end, commands(ServerHost)). + lists:map(fun ({Node, Name, _Function}) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, Host}, {<<"node">>, Node}, + {<<"name">>, + translate:translate(Lang, Name)}], + children = []} + end, + commands(ServerHost)). commands(ServerHost) -> - [{"join", "Join channel", fun adhoc_join/3}, - {"register", "Configure username, encoding, port and password", - fun(From, To, Request) -> - adhoc_register(ServerHost, From, To, Request) + [{<<"join">>, <<"Join channel">>, fun adhoc_join/3}, + {<<"register">>, + <<"Configure username, encoding, port and " + "password">>, + fun (From, To, Request) -> + adhoc_register(ServerHost, From, To, Request) end}]. -process_register(ServerHost, Host, From, To, #iq{} = IQ) -> - case catch process_irc_register(ServerHost, Host, From, To, IQ) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - ResIQ -> - if - ResIQ /= ignore -> - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)); - true -> - ok - end +process_register(ServerHost, Host, From, To, + #iq{} = IQ) -> + case catch process_irc_register(ServerHost, Host, From, + To, IQ) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + ResIQ -> + if ResIQ /= ignore -> + ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); + true -> ok + end end. -find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> +find_xdata_el(#xmlel{children = SubEls}) -> find_xdata_el1(SubEls). -find_xdata_el1([]) -> - false; - -find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_XDATA -> - {xmlelement, Name, Attrs, SubEls}; - _ -> - find_xdata_el1(Els) +find_xdata_el1([]) -> false; +find_xdata_el1([#xmlel{name = Name, attrs = Attrs, + children = SubEls} + | Els]) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_XDATA -> + #xmlel{name = Name, attrs = Attrs, children = SubEls}; + _ -> find_xdata_el1(Els) end; - -find_xdata_el1([_ | Els]) -> - find_xdata_el1(Els). +find_xdata_el1([_ | Els]) -> find_xdata_el1(Els). process_irc_register(ServerHost, Host, From, _To, - #iq{type = Type, xmlns = XMLNS, - lang = Lang, sub_el = SubEl} = IQ) -> + #iq{type = Type, xmlns = XMLNS, lang = Lang, + sub_el = SubEl} = + IQ) -> case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, _SubEls} -> - case xml:get_attr_s("type", Attrs) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_form( - ServerHost, Host, From, - Node, Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> + set -> + XDataEl = find_xdata_el(SubEl), + case XDataEl of + false -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; + #xmlel{attrs = Attrs} -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"cancel">> -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = []}]}; + <<"submit">> -> + XData = jlib:parse_xdata_submit(XDataEl), + case XData of + invalid -> IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end - end; - get -> - Node = - string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), - case get_form(ServerHost, Host, From, Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end + sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + _ -> + Node = str:tokens(xml:get_tag_attr_s(<<"node">>, + SubEl), + <<"/">>), + case set_form(ServerHost, Host, From, Node, Lang, + XData) + of + {result, Res} -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = + [{<<"xmlns">>, XMLNS}], + children = Res}]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end + end; + _ -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end + end; + get -> + Node = str:tokens(xml:get_tag_attr_s(<<"node">>, SubEl), + <<"/">>), + case get_form(ServerHost, Host, From, Node, Lang) of + {result, Res} -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = Res}]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end end. get_data(ServerHost, Host, From) -> LServer = jlib:nameprep(ServerHost), - get_data(LServer, Host, From, gen_mod:db_type(LServer, ?MODULE)). + get_data(LServer, Host, From, + gen_mod:db_type(LServer, ?MODULE)). get_data(_LServer, Host, From, mnesia) -> #jid{luser = LUser, lserver = LServer} = From, US = {LUser, LServer}, - case catch mnesia:dirty_read({irc_custom, {US, Host}}) of - {'EXIT', _Reason} -> - error; - [] -> - empty; - [#irc_custom{data = Data}] -> - Data + case catch mnesia:dirty_read({irc_custom, {US, Host}}) + of + {'EXIT', _Reason} -> error; + [] -> empty; + [#irc_custom{data = Data}] -> Data end; get_data(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 data from irc_custom where " - "jid='", SJID, "' and host='", SHost, "';"]) of - {selected, ["data"], [{SData}]} -> - ejabberd_odbc:decode_term(SData); - {'EXIT', _} -> - error; - {selected, _, _} -> - empty + case catch ejabberd_odbc:sql_query(LServer, + [<<"select data from irc_custom where jid='">>, + SJID, <<"' and host='">>, SHost, + <<"';">>]) + of + {selected, [<<"data">>], [[SData]]} -> + data_to_binary(ejabberd_odbc:decode_term(SData)); + {'EXIT', _} -> error; + {selected, _, _} -> empty end. get_form(ServerHost, Host, From, [], Lang) -> #jid{user = User, server = Server} = From, DefaultEncoding = get_default_encoding(Host), - Customs = - case get_data(ServerHost, Host, From) of - error -> - {error, ?ERR_INTERNAL_SERVER_ERROR}; - empty -> - {User, []}; - Data -> - {xml:get_attr_s(username, Data), - xml:get_attr_s(connections_params, Data)} - end, + Customs = case get_data(ServerHost, Host, From) of + error -> {error, ?ERR_INTERNAL_SERVER_ERROR}; + empty -> {User, []}; + Data -> get_username_and_connection_params(Data) + end, case Customs of - {error, _Error} -> - Customs; - {Username, ConnectionsParams} -> - {result, - [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "You need an x:data capable client " - "to configure mod_irc settings")}]}, - {xmlelement, "x", [{"xmlns", ?NS_XDATA}], - [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, - "Registration in mod_irc for ") ++ User ++ "@" ++ Server}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "Enter username, encodings, ports and passwords you wish to use for " - "connecting to IRC servers")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "IRC Username")}, - {"var", "username"}], - [{xmlelement, "value", [], [{xmlcdata, Username}]}]}, - {xmlelement, "field", [{"type", "fixed"}], - [{xmlelement, "value", [], - [{xmlcdata, - lists:flatten( - io_lib:format( - translate:translate( - Lang, - "If you want to specify different ports, " - "passwords, encodings for IRC servers, fill " - "this list with values in format " - "'{\"irc server\", \"encoding\", port, \"password\"}'. " - "By default this service use \"~s\" encoding, port ~p, " - "empty password."), - [DefaultEncoding, ?DEFAULT_IRC_PORT]))}]}]}, - {xmlelement, "field", [{"type", "fixed"}], - [{xmlelement, "value", [], - [{xmlcdata, - translate:translate( - Lang, - "Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, " - "{\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]." - )}]}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - translate:translate(Lang, "Connections parameters")}, - {"var", "connections_params"}], - lists:map( - fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten( - io_lib:format("~p.", [ConnectionsParams])), - "\n")) - } - ]}]} + {error, _Error} -> Customs; + {Username, ConnectionsParams} -> + {result, + [#xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"You need an x:data capable client to " + "configure mod_irc settings">>)}]}, + #xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_XDATA}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + <<(translate:translate(Lang, + <<"Registration in mod_irc for ">>))/binary, + User/binary, "@", Server/binary>>}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Enter username, encodings, ports and " + "passwords you wish to use for connecting " + "to IRC servers">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"IRC Username">>)}, + {<<"var">>, <<"username">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Username}]}]}, + #xmlel{name = <<"field">>, + attrs = [{<<"type">>, <<"fixed">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"If you want to specify" + " different ports, " + "passwords, encodings " + "for IRC servers, " + "fill this list with " + "values in format " + "'{\"irc server\", " + "\"encoding\", port, " + "\"password\"}'. " + "By default this " + "service use \"~s\" " + "encoding, port ~p, " + "empty password.">>), + [DefaultEncoding, + ?DEFAULT_IRC_PORT]))}]}]}, + #xmlel{name = <<"field">>, + attrs = [{<<"type">>, <<"fixed">>}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Example: [{\"irc.lucky.net\", \"koi8-r\", " + "6667, \"secret\"}, {\"vendetta.fef.net\", " + "\"iso8859-1\", 7000}, {\"irc.sometestserver.n" + "et\", \"utf-8\"}].">>)}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"text-multi">>}, + {<<"label">>, + translate:translate(Lang, + <<"Connections parameters">>)}, + {<<"var">>, <<"connections_params">>}], + children = + lists:map(fun (S) -> + #xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, S}]} + end, + str:tokens(list_to_binary( + io_lib:format( + "~p.", + [conn_params_to_list( + ConnectionsParams)])), + <<"\n">>))}]}]} end; - get_form(_ServerHost, _Host, _, _, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - set_data(ServerHost, Host, From, Data) -> LServer = jlib:nameprep(ServerHost), - set_data(LServer, Host, From, Data, gen_mod:db_type(LServer, ?MODULE)). + set_data(LServer, Host, From, data_to_binary(Data), + gen_mod:db_type(LServer, ?MODULE)). set_data(_LServer, Host, From, Data, mnesia) -> {LUser, LServer, _} = jlib:jid_tolower(From), US = {LUser, LServer}, - F = fun() -> - mnesia:write(#irc_custom{us_host = {US, Host}, data = Data}) - end, + F = fun () -> + mnesia:write(#irc_custom{us_host = {US, Host}, + data = Data}) + end, mnesia:transaction(F); set_data(LServer, Host, From, Data, 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), SData = ejabberd_odbc:encode_term(Data), - F = fun() -> - odbc_queries:update_t( - "irc_custom", - ["jid", "host", "data"], - [SJID, SHost, SData], - ["jid='", SJID, - "' and host='", - SHost, "'"]), - ok - end, + F = fun () -> + odbc_queries:update_t(<<"irc_custom">>, + [<<"jid">>, <<"host">>, <<"data">>], + [SJID, SHost, SData], + [<<"jid='">>, SJID, <<"' and host='">>, + SHost, <<"'">>]), + ok + end, ejabberd_odbc:sql_transaction(LServer, F). set_form(ServerHost, Host, From, [], _Lang, XData) -> - case {lists:keysearch("username", 1, XData), - lists:keysearch("connections_params", 1, XData)} of - {{value, {_, [Username]}}, {value, {_, Strings}}} -> - EncString = lists:foldl(fun(S, Res) -> - Res ++ S ++ "\n" - end, "", Strings), - case erl_scan:string(EncString) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, ConnectionsParams} -> - case set_data(ServerHost, Host, From, - [{username, - Username}, - {connections_params, - ConnectionsParams}]) of - {atomic, _} -> - {result, []}; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - {error, ?ERR_NOT_ACCEPTABLE} + case {lists:keysearch(<<"username">>, 1, XData), + lists:keysearch(<<"connections_params">>, 1, XData)} + of + {{value, {_, [Username]}}, {value, {_, Strings}}} -> + EncString = lists:foldl(fun (S, Res) -> + <<Res/binary, S/binary, "\n">> + end, + <<"">>, Strings), + case erl_scan:string(binary_to_list(EncString)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, ConnectionsParams} -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) + of + {atomic, _} -> {result, []}; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_NOT_ACCEPTABLE} + end; + _ -> {error, ?ERR_NOT_ACCEPTABLE} end; - - set_form(_ServerHost, _Host, _, _, _Lang, _XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. - %% Host = "irc.example.com" %% ServerHost = "example.com" get_connection_params(Host, From, IRCServer) -> - [_ | HostTail] = string:tokens(Host, "."), - ServerHost = string:join(HostTail, "."), - get_connection_params(Host, ServerHost, From, IRCServer). + [_ | HostTail] = str:tokens(Host, <<".">>), + ServerHost = str:join(HostTail, <<".">>), + get_connection_params(Host, ServerHost, From, + IRCServer). get_default_encoding(ServerHost) -> - Result = gen_mod:get_module_opt( - ServerHost, ?MODULE, default_encoding, - ?DEFAULT_IRC_ENCODING), - ?INFO_MSG("The default_encoding configured for host ~p is: ~p~n", [ServerHost, Result]), + Result = gen_mod:get_module_opt(ServerHost, ?MODULE, default_encoding, + fun iolist_to_binary/1, + ?DEFAULT_IRC_ENCODING), + ?INFO_MSG("The default_encoding configured for " + "host ~p is: ~p~n", + [ServerHost, Result]), Result. -get_connection_params(Host, ServerHost, From, IRCServer) -> +get_connection_params(Host, ServerHost, From, + IRCServer) -> #jid{user = User, server = _Server} = From, DefaultEncoding = get_default_encoding(ServerHost), case get_data(ServerHost, Host, From) of - error -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - empty -> - {User, DefaultEncoding, ?DEFAULT_IRC_PORT, ""}; - Data -> - Username = xml:get_attr_s(username, Data), - {NewUsername, NewEncoding, NewPort, NewPassword} = - case lists:keysearch(IRCServer, 1, xml:get_attr_s(connections_params, Data)) of - {value, {_, Encoding, Port, Password}} -> - {Username, Encoding, Port, Password}; - {value, {_, Encoding, Port}} -> - {Username, Encoding, Port, ""}; - {value, {_, Encoding}} -> - {Username, Encoding, ?DEFAULT_IRC_PORT, ""}; - _ -> - {Username, DefaultEncoding, ?DEFAULT_IRC_PORT, ""} - end, - {NewUsername, - NewEncoding, - if - NewPort >= 0 andalso NewPort =< 65535 -> - NewPort; - true -> - ?DEFAULT_IRC_PORT - end, - NewPassword} - end. + error -> + {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>}; + empty -> + {User, DefaultEncoding, ?DEFAULT_IRC_PORT, <<"">>}; + Data -> + {Username, ConnParams} = get_username_and_connection_params(Data), + {NewUsername, NewEncoding, NewPort, NewPassword} = case + lists:keysearch(IRCServer, + 1, + ConnParams) + of + {value, + {_, Encoding, + Port, + Password}} -> + {Username, + Encoding, + Port, + Password}; + {value, + {_, Encoding, + Port}} -> + {Username, + Encoding, + Port, + <<"">>}; + {value, + {_, + Encoding}} -> + {Username, + Encoding, + ?DEFAULT_IRC_PORT, + <<"">>}; + _ -> + {Username, + DefaultEncoding, + ?DEFAULT_IRC_PORT, + <<"">>} + end, + {iolist_to_binary(NewUsername), + iolist_to_binary(NewEncoding), + if NewPort >= 0 andalso NewPort =< 65535 -> NewPort; + true -> ?DEFAULT_IRC_PORT + end, + iolist_to_binary(NewPassword)} + end. -adhoc_join(_From, _To, #adhoc_request{action = "cancel"} = Request) -> +adhoc_join(_From, _To, + #adhoc_request{action = <<"cancel">>} = Request) -> adhoc:produce_response(Request, #adhoc_response{status = canceled}); -adhoc_join(From, To, #adhoc_request{lang = Lang, - node = _Node, - action = _Action, - xdata = XData} = Request) -> - %% Access control has already been taken care of in do_route. +adhoc_join(From, To, + #adhoc_request{lang = Lang, node = _Node, + action = _Action, xdata = XData} = + Request) -> if XData == false -> - Form = - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "Join IRC channel")}]}, - {xmlelement, "field", - [{"var", "channel"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC channel (don't put the first #)")}], - [{xmlelement, "required", [], []}]}, - {xmlelement, "field", - [{"var", "server"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC server")}], - [{xmlelement, "required", [], []}]}]}, - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form]}); + Form = #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, + {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Join IRC channel">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"channel">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"IRC channel (don't put the first #)">>)}], + children = + [#xmlel{name = <<"required">>, + attrs = [], children = []}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"server">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, + <<"IRC server">>)}], + children = + [#xmlel{name = <<"required">>, + attrs = [], children = []}]}]}, + adhoc:produce_response(Request, + #adhoc_response{status = executing, + elements = [Form]}); true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - {error, ?ERR_BAD_REQUEST}; - Fields -> - Channel = case lists:keysearch("channel", 1, Fields) of - {value, {"channel", C}} -> - C; - _ -> - false - end, - Server = case lists:keysearch("server", 1, Fields) of - {value, {"server", S}} -> - S; - _ -> - false - end, - if Channel /= false, - Server /= false -> - RoomJID = Channel ++ "%" ++ Server ++ "@" ++ To#jid.server, - Invite = {xmlelement, "message", [], - [{xmlelement, "x", - [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "invite", - [{"from", jlib:jid_to_string(From)}], - [{xmlelement, "reason", [], - [{xmlcdata, - translate:translate(Lang, - "Join the IRC channel here.")}]}]}]}, - {xmlelement, "x", - [{"xmlns", ?NS_XCONFERENCE}], - [{xmlcdata, translate:translate(Lang, - "Join the IRC channel here.")}]}, - {xmlelement, "body", [], - [{xmlcdata, io_lib:format( - translate:translate(Lang, - "Join the IRC channel in this Jabber ID: ~s"), - [RoomJID])}]}]}, - ejabberd_router:route(jlib:string_to_jid(RoomJID), From, Invite), - adhoc:produce_response(Request, #adhoc_response{status = completed}); - true -> - {error, ?ERR_BAD_REQUEST} - end - end + case jlib:parse_xdata_submit(XData) of + invalid -> {error, ?ERR_BAD_REQUEST}; + Fields -> + Channel = case lists:keysearch(<<"channel">>, 1, Fields) + of + {value, {<<"channel">>, [C]}} -> C; + _ -> false + end, + Server = case lists:keysearch(<<"server">>, 1, Fields) + of + {value, {<<"server">>, [S]}} -> S; + _ -> false + end, + if Channel /= false, Server /= false -> + RoomJID = <<Channel/binary, "%", Server/binary, "@", + (To#jid.server)/binary>>, + Invite = #xmlel{name = <<"message">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children = + [#xmlel{name = + <<"invite">>, + attrs = + [{<<"from">>, + jlib:jid_to_string(From)}], + children = + [#xmlel{name + = + <<"reason">>, + attrs + = + [], + children + = + [{xmlcdata, + translate:translate(Lang, + <<"Join the IRC channel here.">>)}]}]}]}, + #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_XCONFERENCE}], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Join the IRC channel here.">>)}]}, + #xmlel{name = <<"body">>, + attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"Join the IRC channel in this Jabber ID: ~s">>), + [RoomJID]))}]}]}, + ejabberd_router:route(jlib:string_to_jid(RoomJID), From, + Invite), + adhoc:produce_response(Request, + #adhoc_response{status = + completed}); + true -> {error, ?ERR_BAD_REQUEST} + end + end end. -adhoc_register(_ServerHost, _From, _To, #adhoc_request{action = "cancel"} = Request) -> +adhoc_register(_ServerHost, _From, _To, + #adhoc_request{action = <<"cancel">>} = Request) -> adhoc:produce_response(Request, #adhoc_response{status = canceled}); -adhoc_register(ServerHost, From, To, #adhoc_request{lang = Lang, - node = _Node, - xdata = XData, - action = Action} = Request) -> +adhoc_register(ServerHost, From, To, + #adhoc_request{lang = Lang, node = _Node, xdata = XData, + action = Action} = + Request) -> #jid{user = User} = From, #jid{lserver = Host} = To, - %% Generate form for setting username and encodings. If the user - %% hasn't begun to fill out the form, generate an initial form - %% based on current values. if XData == false -> - case get_data(ServerHost, Host, From) of - error -> - Username = User, - ConnectionsParams = []; - empty -> - Username = User, - ConnectionsParams = []; - Data -> - Username = xml:get_attr_s(username, Data), - ConnectionsParams = xml:get_attr_s(connections_params, Data) - end, - Error = false; + case get_data(ServerHost, Host, From) of + error -> Username = User, ConnectionsParams = []; + empty -> Username = User, ConnectionsParams = []; + Data -> + {Username, ConnectionsParams} = + get_username_and_connection_params(Data) + end, + Error = false; true -> - case jlib:parse_xdata_submit(XData) of - invalid -> - Error = {error, ?ERR_BAD_REQUEST}, - Username = false, - ConnectionsParams = false; - Fields -> - Username = case lists:keysearch("username", 1, Fields) of - {value, {"username", U}} -> - U; - _ -> - User - end, - ConnectionsParams = parse_connections_params(Fields), - Error = false - end + case jlib:parse_xdata_submit(XData) of + invalid -> + Error = {error, ?ERR_BAD_REQUEST}, + Username = false, + ConnectionsParams = false; + Fields -> + Username = case lists:keysearch(<<"username">>, 1, + Fields) + of + {value, {<<"username">>, U}} -> U; + _ -> User + end, + ConnectionsParams = parse_connections_params(Fields), + Error = false + end end, - - if Error /= false -> - Error; - Action == "complete" -> - case set_data(ServerHost, Host, From, - [{username, - Username}, - {connections_params, - ConnectionsParams}]) of - {atomic, _} -> - adhoc:produce_response(Request, #adhoc_response{status = completed}); - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end; + if Error /= false -> Error; + Action == <<"complete">> -> + case set_data(ServerHost, Host, From, + [{username, Username}, + {connections_params, ConnectionsParams}]) + of + {atomic, _} -> + adhoc:produce_response(Request, + #adhoc_response{status = completed}); + _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} + end; true -> - Form = generate_adhoc_register_form(Lang, Username, ConnectionsParams), - adhoc:produce_response(Request, - #adhoc_response{status = executing, - elements = [Form], - actions = ["next", "complete"]}) + Form = generate_adhoc_register_form(Lang, Username, + ConnectionsParams), + adhoc:produce_response(Request, + #adhoc_response{status = executing, + elements = [Form], + actions = + [<<"next">>, + <<"complete">>]}) end. -generate_adhoc_register_form(Lang, Username, ConnectionsParams) -> - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - [{xmlelement, "title", [], [{xmlcdata, translate:translate(Lang, "IRC settings")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, - "Enter username and encodings you wish to use for " - "connecting to IRC servers. Press 'Next' to get more fields " - "to fill in. Press 'Complete' to save settings.")}]}, - {xmlelement, "field", - [{"var", "username"}, - {"type", "text-single"}, - {"label", translate:translate(Lang, "IRC username")}], - [{xmlelement, "required", [], []}, - {xmlelement, "value", [], [{xmlcdata, Username}]}]}] ++ - generate_connection_params_fields(Lang, ConnectionsParams, 1, [])}. +generate_adhoc_register_form(Lang, Username, + ConnectionsParams) -> + #xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, <<"IRC settings">>)}]}, + #xmlel{name = <<"instructions">>, attrs = [], + children = + [{xmlcdata, + translate:translate(Lang, + <<"Enter username and encodings you wish " + "to use for connecting to IRC servers. " + " Press 'Next' to get more fields to " + "fill in. Press 'Complete' to save settings.">>)}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"username">>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + translate:translate(Lang, <<"IRC username">>)}], + children = + [#xmlel{name = <<"required">>, attrs = [], + children = []}, + #xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Username}]}]}] + ++ + generate_connection_params_fields(Lang, + ConnectionsParams, 1, [])}. -generate_connection_params_fields(Lang, [], Number, Acc) -> - Field = generate_connection_params_field(Lang, "", "", -1, "", Number), +generate_connection_params_fields(Lang, [], Number, + Acc) -> + Field = generate_connection_params_field(Lang, <<"">>, + <<"">>, -1, <<"">>, Number), lists:reverse(Field ++ Acc); - -generate_connection_params_fields(Lang, [ConnectionParams | ConnectionsParams], Number, Acc) -> +generate_connection_params_fields(Lang, + [ConnectionParams | ConnectionsParams], + Number, Acc) -> case ConnectionParams of - {Server, Encoding, Port, Password} -> - Field = generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - {Server, Encoding, Port} -> - Field = generate_connection_params_field(Lang, Server, Encoding, Port, [], Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - {Server, Encoding} -> - Field = generate_connection_params_field(Lang, Server, Encoding, [], [], Number), - generate_connection_params_fields(Lang, ConnectionsParams, Number + 1, Field ++ Acc); - _ -> - [] + {Server, Encoding, Port, Password} -> + Field = generate_connection_params_field(Lang, Server, + Encoding, Port, Password, + Number), + generate_connection_params_fields(Lang, + ConnectionsParams, Number + 1, + Field ++ Acc); + {Server, Encoding, Port} -> + Field = generate_connection_params_field(Lang, Server, + Encoding, Port, <<"">>, Number), + generate_connection_params_fields(Lang, + ConnectionsParams, Number + 1, + Field ++ Acc); + {Server, Encoding} -> + Field = generate_connection_params_field(Lang, Server, + Encoding, -1, <<"">>, Number), + generate_connection_params_fields(Lang, + ConnectionsParams, Number + 1, + Field ++ Acc); + _ -> [] end. -generate_connection_params_field(Lang, Server, Encoding, Port, Password, Number) -> +generate_connection_params_field(Lang, Server, Encoding, + Port, Password, Number) -> EncodingUsed = case Encoding of - [] -> - get_default_encoding(Server); - _ -> - Encoding + <<>> -> get_default_encoding(Server); + _ -> Encoding end, - PortUsedInt = if - Port >= 0 andalso Port =< 65535 -> - Port; - true -> - ?DEFAULT_IRC_PORT - end, - PortUsed = integer_to_list(PortUsedInt), + PortUsedInt = if Port >= 0 andalso Port =< 65535 -> + Port; + true -> ?DEFAULT_IRC_PORT + end, + PortUsed = + iolist_to_binary(integer_to_list(PortUsedInt)), PasswordUsed = case Password of - [] -> - ""; - _ -> - Password - end, - NumberString = integer_to_list(Number), - %% Fields are in reverse order, as they will be reversed again later. - [{xmlelement, "field", - [{"var", "password" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Password ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, PasswordUsed}]}]}, - {xmlelement, "field", - [{"var", "port" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Port ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, PortUsed}]}]}, - {xmlelement, "field", - [{"var", "encoding" ++ NumberString}, - {"type", "list-single"}, - {"label", io_lib:format(translate:translate(Lang, "Encoding for server ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, EncodingUsed}]} | - lists:map(fun(E) -> - {xmlelement, "option", [{"label", E}], - [{xmlelement, "value", [], [{xmlcdata, E}]}]} - end, ?POSSIBLE_ENCODINGS)]}, - {xmlelement, "field", - [{"var", "server" ++ NumberString}, - {"type", "text-single"}, - {"label", io_lib:format(translate:translate(Lang, "Server ~b"), [Number])}], - [{xmlelement, "value", [], [{xmlcdata, Server}]}]}]. + <<>> -> <<>>; + _ -> Password + end, + NumberString = + iolist_to_binary(integer_to_list(Number)), + [#xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"password", NumberString/binary>>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + iolist_to_binary( + io_lib:format( + translate:translate(Lang, <<"Password ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, PasswordUsed}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"port", NumberString/binary>>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + iolist_to_binary( + io_lib:format(translate:translate(Lang, <<"Port ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, PortUsed}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"encoding", NumberString/binary>>}, + {<<"type">>, <<"list-single">>}, + {<<"label">>, + list_to_binary( + io_lib:format(translate:translate( + Lang, + <<"Encoding for server ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, EncodingUsed}]} + | lists:map(fun (E) -> + #xmlel{name = <<"option">>, + attrs = [{<<"label">>, E}], + children = + [#xmlel{name = <<"value">>, + attrs = [], + children = + [{xmlcdata, E}]}]} + end, + ?POSSIBLE_ENCODINGS)]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"var">>, <<"server", NumberString/binary>>}, + {<<"type">>, <<"text-single">>}, + {<<"label">>, + list_to_binary( + io_lib:format(translate:translate(Lang, <<"Server ~b">>), + [Number]))}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = [{xmlcdata, Server}]}]}]. parse_connections_params(Fields) -> - %% Find all fields staring with serverN, encodingN, portN and passwordN for any values - %% of N, and generate lists of {"N", Value}. - Servers = lists:sort( - [{lists:nthtail(6, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("server", Var)]), - Encodings = lists:sort( - [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("encoding", Var)]), - - Ports = lists:sort( - [{lists:nthtail(4, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("port", Var)]), - - Passwords = lists:sort( - [{lists:nthtail(8, Var), lists:flatten(Value)} || {Var, Value} <- Fields, - lists:prefix("password", Var)]), - - %% Now sort the lists, and find the corresponding pairs. - parse_connections_params(Servers, Encodings, Ports, Passwords). - -retrieve_connections_params(ConnectionParams, ServerN) -> + Servers = lists:flatmap( + fun({<<"server", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + Encodings = lists:flatmap( + fun({<<"encoding", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + Ports = lists:flatmap( + fun({<<"port", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + Passwords = lists:flatmap( + fun({<<"password", Var/binary>>, Value}) -> + [{Var, Value}]; + (_) -> + [] + end, Fields), + parse_connections_params(Servers, Encodings, Ports, + Passwords). + +retrieve_connections_params(ConnectionParams, + ServerN) -> case ConnectionParams of - [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail] -> - if - ServerN == ConnectionParamN -> - {ConnectionParam, ConnectionParamsTail}; - ServerN < ConnectionParamN -> - {[], [{ConnectionParamN, ConnectionParam} | ConnectionParamsTail]}; - ServerN > ConnectionParamN -> - {[], ConnectionParamsTail} - end; - _ -> - {[], []} - end. - -parse_connections_params([], _, _, _) -> - []; -parse_connections_params(_, [], [], []) -> - []; + [{ConnectionParamN, ConnectionParam} + | ConnectionParamsTail] -> + if ServerN == ConnectionParamN -> + {ConnectionParam, ConnectionParamsTail}; + ServerN < ConnectionParamN -> + {[], + [{ConnectionParamN, ConnectionParam} + | ConnectionParamsTail]}; + ServerN > ConnectionParamN -> {[], ConnectionParamsTail} + end; + _ -> {[], []} + end. -parse_connections_params([{ServerN, Server} | Servers], Encodings, Ports, Passwords) -> - %% Try to match matches of servers, ports, passwords and encodings, no matter what fields - %% the client might have left out. - {NewEncoding, NewEncodings} = retrieve_connections_params(Encodings, ServerN), - {NewPort, NewPorts} = retrieve_connections_params(Ports, ServerN), - {NewPassword, NewPasswords} = retrieve_connections_params(Passwords, ServerN), - [{Server, NewEncoding, NewPort, NewPassword} | parse_connections_params(Servers, NewEncodings, NewPorts, NewPasswords)]. - -update_table(Host) -> +parse_connections_params([], _, _, _) -> []; +parse_connections_params(_, [], [], []) -> []; +parse_connections_params([{ServerN, Server} | Servers], + Encodings, Ports, Passwords) -> + {NewEncoding, NewEncodings} = + retrieve_connections_params(Encodings, ServerN), + {NewPort, NewPorts} = retrieve_connections_params(Ports, + ServerN), + {NewPassword, NewPasswords} = + retrieve_connections_params(Passwords, ServerN), + [{Server, NewEncoding, NewPort, NewPassword} + | parse_connections_params(Servers, NewEncodings, + NewPorts, NewPasswords)]. + +get_username_and_connection_params(Data) -> + Username = case lists:keysearch(username, 1, Data) of + {value, {_, U}} when is_binary(U) -> + U; + _ -> + <<"">> + end, + ConnParams = case lists:keysearch(connections_params, 1, Data) of + {value, {_, L}} when is_list(L) -> + L; + _ -> + [] + end, + {Username, ConnParams}. + +data_to_binary(Data) -> + lists:map( + fun({username, U}) -> + {username, iolist_to_binary(U)}; + ({connections_params, Params}) -> + {connections_params, + lists:map( + fun({S, E}) -> + {iolist_to_binary(S), iolist_to_binary(E)}; + ({S, E, Port}) -> + {iolist_to_binary(S), iolist_to_binary(E), Port}; + ({S, E, Port, P}) -> + {iolist_to_binary(S), iolist_to_binary(E), + Port, iolist_to_binary(P)} + end, Params)}; + (Opt) -> + Opt + end, Data). + +conn_params_to_list(Params) -> + lists:map( + fun({S, E}) -> + {binary_to_list(S), binary_to_list(E)}; + ({S, E, Port}) -> + {binary_to_list(S), binary_to_list(E), Port}; + ({S, E, Port, P}) -> + {binary_to_list(S), binary_to_list(E), + Port, binary_to_list(P)} + end, Params). + +update_table() -> Fields = record_info(fields, irc_custom), case mnesia:table_info(irc_custom, attributes) of - Fields -> - ok; - [userserver, data] -> - ?INFO_MSG("Converting irc_custom table from " - "{userserver, data} format", []), - {atomic, ok} = mnesia:create_table( - mod_irc_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, irc_custom}, - {attributes, record_info(fields, irc_custom)}]), - mnesia:transform_table(irc_custom, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_irc_tmp_table), - mnesia:foldl( - fun(#irc_custom{us_host = US} = R, _) -> - mnesia:dirty_write( - mod_irc_tmp_table, - R#irc_custom{us_host = {US, Host}}) - end, ok, irc_custom) - end, - mnesia:transaction(F1), - mnesia:clear_table(irc_custom), - F2 = fun() -> - mnesia:write_lock_table(irc_custom), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_irc_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_irc_tmp_table); - _ -> - ?INFO_MSG("Recreating irc_custom table", []), - mnesia:transform_table(irc_custom, ignore, Fields) + Fields -> + ejabberd_config:convert_table_to_binary( + irc_custom, Fields, set, + fun(#irc_custom{us_host = {_, H}}) -> H end, + fun(#irc_custom{us_host = {{U, S}, H}, + data = Data} = R) -> + R#irc_custom{us_host = {{iolist_to_binary(U), + iolist_to_binary(S)}, + iolist_to_binary(H)}, + data = data_to_binary(Data)} + end); + _ -> + ?INFO_MSG("Recreating irc_custom table", []), + mnesia:transform_table(irc_custom, ignore, Fields) end. + +export(_Server) -> + [{irc_custom, + fun(Host, #irc_custom{us_host = {{U, S}, IRCHost}, + data = Data}) -> + case str:suffix(Host, IRCHost) of + true -> + SJID = ejabberd_odbc:escape( + jlib:jid_to_string( + jlib:make_jid(U, S, <<"">>))), + SIRCHost = ejabberd_odbc:escape(IRCHost), + SData = ejabberd_odbc:encode_term(Data), + [[<<"delete from irc_custom where jid='">>, SJID, + <<"' and host='">>, SIRCHost, <<"';">>], + [<<"insert into irc_custom(jid, host, " + "data) values ('">>, + SJID, <<"', '">>, SIRCHost, <<"', '">>, SData, + <<"');">>]]; + false -> + [] + end + end}]. diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl index 1c9bd0c95..ba0cb4345 100644 --- a/src/mod_irc/mod_irc_connection.erl +++ b/src/mod_irc/mod_irc_connection.erl @@ -25,53 +25,71 @@ %%%---------------------------------------------------------------------- -module(mod_irc_connection). + -author('alexey@process-one.net'). -behaviour(gen_fsm). %% External exports --export([start_link/8, start/9, route_chan/4, route_nick/3]). +-export([start_link/8, start/9, route_chan/4, + route_nick/3]). %% gen_fsm callbacks --export([init/1, - open_socket/2, - wait_for_registration/2, - stream_established/2, - handle_event/3, - handle_sync_event/4, - handle_info/3, - terminate/3, +-export([init/1, open_socket/2, wait_for_registration/2, + stream_established/2, handle_event/3, + handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -include("ejabberd.hrl"). + -include("jlib.hrl"). -define(SETS, gb_sets). --record(state, {socket, encoding, port, password, - queue, user, host, server, nick, - channels = dict:new(), - nickchannel, mod, - inbuf = "", outbuf = ""}). +-record(state, + {socket :: inet:socket(), + encoding = <<"">> :: binary(), + port = 0 :: inet:port_number(), + password = <<"">> :: binary(), + queue = queue:new() :: queue(), + user = #jid{} :: jid(), + host = <<"">> :: binary(), + server = <<"">> :: binary(), + nick = <<"">> :: binary(), + channels = dict:new() :: dict(), + nickchannel :: binary(), + mod = mod_irc :: atom(), + inbuf = <<"">> :: binary(), + outbuf = <<"">> :: binary()}). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). --endif. %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(From, Host, ServerHost, Server, Username, Encoding, Port, Password, Mod) -> - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_irc_sup), - supervisor:start_child( - Supervisor, [From, Host, Server, Username, Encoding, Port, Password, Mod]). +-endif. -start_link(From, Host, Server, Username, Encoding, Port, Password, Mod) -> - gen_fsm:start_link(?MODULE, [From, Host, Server, Username, Encoding, Port, Password, Mod], +start(From, Host, ServerHost, Server, Username, + Encoding, Port, Password, Mod) -> + Supervisor = gen_mod:get_module_proc(ServerHost, + ejabberd_mod_irc_sup), + supervisor:start_child(Supervisor, + [From, Host, Server, Username, Encoding, Port, + Password, Mod]). + +start_link(From, Host, Server, Username, Encoding, Port, + Password, Mod) -> + gen_fsm:start_link(?MODULE, + [From, Host, Server, Username, Encoding, Port, Password, + Mod], ?FSMOPTS). %%%---------------------------------------------------------------------- @@ -85,17 +103,14 @@ start_link(From, Host, Server, Username, Encoding, Port, Password, Mod) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([From, Host, Server, Username, Encoding, Port, Password, Mod]) -> +init([From, Host, Server, Username, Encoding, Port, + Password, Mod]) -> gen_fsm:send_event(self(), init), - {ok, open_socket, #state{queue = queue:new(), - mod = Mod, - encoding = Encoding, - port = Port, - password = Password, - user = From, - nick = Username, - host = Host, - server = Server}}. + {ok, open_socket, + #state{queue = queue:new(), mod = Mod, + encoding = Encoding, port = Port, password = Password, + user = From, nick = Username, host = Host, + server = Server}}. %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -107,42 +122,42 @@ open_socket(init, StateData) -> Addr = StateData#state.server, Port = StateData#state.port, ?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]), - Connect6 = gen_tcp:connect(Addr, Port, [inet6, binary, {packet, 0}]), + Connect6 = gen_tcp:connect(binary_to_list(Addr), Port, + [inet6, binary, {packet, 0}]), Connect = case Connect6 of - {error, _} -> - ?DEBUG("Connection with IPv6 to ~s:~p failed. Now using IPv4.", [Addr, Port]), - gen_tcp:connect(Addr, Port, [inet, binary, {packet, 0}]); - _ -> - Connect6 + {error, _} -> + ?DEBUG("Connection with IPv6 to ~s:~p failed. " + "Now using IPv4.", + [Addr, Port]), + gen_tcp:connect(binary_to_list(Addr), Port, + [inet, binary, {packet, 0}]); + _ -> Connect6 end, case Connect of - {ok, Socket} -> - NewStateData = StateData#state{socket = Socket}, - if - StateData#state.password /= "" -> - send_text(NewStateData, - io_lib:format("PASS ~s\r\n", [StateData#state.password])); - true -> true - end, - send_text(NewStateData, - io_lib:format("NICK ~s\r\n", [StateData#state.nick])), - send_text(NewStateData, - io_lib:format( - "USER ~s ~s ~s :~s\r\n", - [StateData#state.nick, - StateData#state.nick, - StateData#state.host, - StateData#state.nick])), - {next_state, wait_for_registration, - NewStateData}; - {error, Reason} -> - ?DEBUG("connect return ~p~n", [Reason]), - Text = case Reason of - timeout -> "Server Connect Timeout"; - _ -> "Server Connect Failed" - end, - bounce_messages(Text), - {stop, normal, StateData} + {ok, Socket} -> + NewStateData = StateData#state{socket = Socket}, + if StateData#state.password /= <<"">> -> + send_text(NewStateData, + io_lib:format("PASS ~s\r\n", + [StateData#state.password])); + true -> true + end, + send_text(NewStateData, + io_lib:format("NICK ~s\r\n", [StateData#state.nick])), + send_text(NewStateData, + io_lib:format("USER ~s ~s ~s :~s\r\n", + [StateData#state.nick, StateData#state.nick, + StateData#state.host, + StateData#state.nick])), + {next_state, wait_for_registration, NewStateData}; + {error, Reason} -> + ?DEBUG("connect return ~p~n", [Reason]), + Text = case Reason of + timeout -> <<"Server Connect Timeout">>; + _ -> <<"Server Connect Failed">> + end, + bounce_messages(Text), + {stop, normal, StateData} end. wait_for_registration(closed, StateData) -> @@ -150,15 +165,11 @@ wait_for_registration(closed, StateData) -> stream_established({xmlstreamend, _Name}, StateData) -> {stop, normal, StateData}; - stream_established(timeout, StateData) -> {stop, normal, StateData}; - stream_established(closed, StateData) -> {stop, normal, StateData}. - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -190,46 +201,43 @@ handle_event(_Event, StateName, StateData) -> %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, 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}. -define(SEND(S), - if - StateName == stream_established -> - send_text(StateData, S), - StateData; - true -> - StateData#state{outbuf = StateData#state.outbuf ++ S} + if StateName == stream_established -> + send_text(StateData, S), StateData; + true -> + StateData#state{outbuf = <<(StateData#state.outbuf)/binary, + (iolist_to_binary(S))/binary>>} end). -get_password_from_presence({xmlelement, "presence", _Attrs, Els}) -> - case lists:filter(fun(El) -> - case El of - {xmlelement, "x", Attrs, _Els} -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_MUC -> - true; - _ -> - false - end; - _ -> - false - end - end, Els) of - [ElXMUC | _] -> - case xml:get_subtag(ElXMUC, "password") of - {xmlelement, "password", _, _} = PasswordTag -> - {true, xml:get_tag_cdata(PasswordTag)}; - _ -> - false - end; - _ -> - false - end. +get_password_from_presence(#xmlel{name = <<"presence">>, + children = Els}) -> + case lists:filter(fun (El) -> + case El of + #xmlel{name = <<"x">>, attrs = Attrs} -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_MUC -> true; + _ -> false + end; + _ -> false + end + end, + Els) + of + [ElXMUC | _] -> + case xml:get_subtag(ElXMUC, <<"password">>) of + #xmlel{name = <<"password">>} = PasswordTag -> + {true, xml:get_tag_cdata(PasswordTag)}; + _ -> false + end; + _ -> false + end. %%---------------------------------------------------------------------- %% Func: handle_info/3 @@ -238,432 +246,438 @@ get_password_from_presence({xmlelement, "presence", _Attrs, Els}) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_info({route_chan, Channel, Resource, - {xmlelement, "presence", Attrs, _Els} = Presence}, + #xmlel{name = <<"presence">>, attrs = Attrs} = + Presence}, StateName, StateData) -> - NewStateData = - case xml:get_attr_s("type", Attrs) of - "unavailable" -> - send_stanza_unavailable(Channel, StateData), - S1 = ?SEND(io_lib:format("PART #~s\r\n", [Channel])), - S1#state{channels = - dict:erase(Channel, S1#state.channels)}; - "subscribe" -> StateData; - "subscribed" -> StateData; - "unsubscribe" -> StateData; - "unsubscribed" -> StateData; - "error" -> stop; - _ -> - Nick = case Resource of - "" -> - StateData#state.nick; + NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of + <<"unavailable">> -> + send_stanza_unavailable(Channel, StateData), + S1 = (?SEND((io_lib:format("PART #~s\r\n", + [Channel])))), + S1#state{channels = + dict:erase(Channel, S1#state.channels)}; + <<"subscribe">> -> StateData; + <<"subscribed">> -> StateData; + <<"unsubscribe">> -> StateData; + <<"unsubscribed">> -> StateData; + <<"error">> -> stop; + _ -> + Nick = case Resource of + <<"">> -> StateData#state.nick; + _ -> Resource + end, + S1 = if Nick /= StateData#state.nick -> + S11 = (?SEND((io_lib:format("NICK ~s\r\n", + [Nick])))), + S11#state{nickchannel = Channel}; + true -> StateData + end, + case dict:is_key(Channel, S1#state.channels) of + true -> S1; _ -> - Resource - end, - S1 = if - Nick /= StateData#state.nick -> - S11 = ?SEND(io_lib:format("NICK ~s\r\n", [Nick])), - % The server reply will change the copy of the - % nick in the state (or indicate a clash). - S11#state{nickchannel = Channel}; - true -> - StateData - end, - case dict:is_key(Channel, S1#state.channels) of - true -> - S1; - _ -> - case get_password_from_presence(Presence) of - {true, Password} -> - S2 = ?SEND(io_lib:format("JOIN #~s ~s\r\n", [Channel, Password])); - _ -> - S2 = ?SEND(io_lib:format("JOIN #~s\r\n", [Channel])) - end, - S2#state{channels = - dict:store(Channel, ?SETS:new(), - S1#state.channels)} - end - end, - if - NewStateData == stop -> - {stop, normal, StateData}; - true -> - case dict:fetch_keys(NewStateData#state.channels) of - [] -> {stop, normal, NewStateData}; - _ -> {next_state, StateName, NewStateData} - end + case get_password_from_presence(Presence) of + {true, Password} -> + S2 = + (?SEND((io_lib:format("JOIN #~s ~s\r\n", + [Channel, + Password])))); + _ -> + S2 = (?SEND((io_lib:format("JOIN #~s\r\n", + [Channel])))) + end, + S2#state{channels = + dict:store(Channel, (?SETS):new(), + S1#state.channels)} + end + end, + if NewStateData == stop -> {stop, normal, StateData}; + true -> + case dict:fetch_keys(NewStateData#state.channels) of + [] -> {stop, normal, NewStateData}; + _ -> {next_state, StateName, NewStateData} + end end; - handle_info({route_chan, Channel, Resource, - {xmlelement, "message", Attrs, _Els} = El}, + #xmlel{name = <<"message">>, attrs = Attrs} = El}, StateName, StateData) -> - NewStateData = - case xml:get_attr_s("type", Attrs) of - "groupchat" -> - case xml:get_path_s(El, [{elem, "subject"}, cdata]) of - "" -> - ejabberd_router:route( - jlib:make_jid( - lists:concat( - [Channel, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, El), - Body = xml:get_path_s(El, [{elem, "body"}, cdata]), - case Body of - "/quote " ++ Rest -> - ?SEND(Rest ++ "\r\n"); - "/msg " ++ Rest -> - ?SEND("PRIVMSG " ++ Rest ++ "\r\n"); - "/me " ++ Rest -> - Strings = string:tokens(Rest, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG #~s :\001ACTION ~s\001\r\n", - [Channel, S]) - end, Strings)), - ?SEND(Res); - "/ctcp " ++ Rest -> - Words = string:tokens(Rest, " "), - case Words of - [CtcpDest | _] -> - CtcpCmd = - toupper( - string:substr( - Rest, - string:str(Rest, " ") + 1)), - Res = io_lib:format( - "PRIVMSG ~s :\001~s\001\r\n", - [CtcpDest, CtcpCmd]), - ?SEND(Res); - _ -> - ok - end; - _ -> - Strings = string:tokens(Body, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG #~s :~s\r\n", - [Channel, S]) - end, Strings)), - ?SEND(Res) - end; - Subject -> - Strings = string:tokens(Subject, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format("TOPIC #~s :~s\r\n", - [Channel, S]) - end, Strings)), - ?SEND(Res) - end; - Type when Type == "chat"; Type == ""; Type == "normal" -> - Body = xml:get_path_s(El, [{elem, "body"}, cdata]), - case Body of - "/quote " ++ Rest -> - ?SEND(Rest ++ "\r\n"); - "/msg " ++ Rest -> - ?SEND("PRIVMSG " ++ Rest ++ "\r\n"); - "/me " ++ Rest -> - Strings = string:tokens(Rest, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Resource, S]) - end, Strings)), - ?SEND(Res); - "/ctcp " ++ Rest -> - Words = string:tokens(Rest, " "), - case Words of - [CtcpDest | _ ] -> - CtcpCmd = - toupper( - string:substr( - Rest, string:str(Rest, " ") + 1)), - Res = io_lib:format( - "PRIVMSG ~s :~s\r\n", - [CtcpDest, "\001" ++ CtcpCmd ++ "\001"]), - ?SEND(Res); - _ -> - ok - end; - _ -> - Strings = string:tokens(Body, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format("PRIVMSG ~s :~s\r\n", - [Resource, S]) - end, Strings)), - ?SEND(Res) - end; - "error" -> - stop; - _ -> - StateData - end, - if - NewStateData == stop -> - {stop, normal, StateData}; - true -> - {next_state, StateName, NewStateData} + NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of + <<"groupchat">> -> + case xml:get_path_s(El, [{elem, <<"subject">>}, cdata]) + of + <<"">> -> + ejabberd_router:route( + jlib:make_jid( + iolist_to_binary([Channel, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, El), + Body = xml:get_path_s(El, + [{elem, <<"body">>}, + cdata]), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<<Rest/binary, "\r\n">>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG #~s :\001ACTION ~s\001\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = + io_lib:format("PRIVMSG ~s :\001~s\001\r\n", + [CtcpDest, + CtcpCmd]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("PRIVMSG #~s :~s\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res) + end; + Subject -> + Strings = str:tokens(Subject, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format("TOPIC #~s :~s\r\n", + [Channel, S]) + end, + Strings)), + ?SEND(Res) + end; + Type + when Type == <<"chat">>; + Type == <<"">>; + Type == <<"normal">> -> + Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<<Rest/binary, "\r\n">>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Resource, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", + CtcpCmd/binary, + "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :~s\r\n", + [Resource, S]) + end, + Strings)), + ?SEND(Res) + end; + <<"error">> -> stop; + _ -> StateData + end, + if NewStateData == stop -> {stop, normal, StateData}; + true -> {next_state, StateName, NewStateData} end; - - handle_info({route_chan, Channel, Resource, - {xmlelement, "iq", _Attrs, _Els} = El}, + #xmlel{name = <<"iq">>} = El}, StateName, StateData) -> From = StateData#state.user, - To = jlib:make_jid(lists:concat([Channel, "%", StateData#state.server]), + To = jlib:make_jid(iolist_to_binary([Channel, <<"%">>, + StateData#state.server]), StateData#state.host, StateData#state.nick), _ = case jlib:iq_query_info(El) of - #iq{xmlns = ?NS_MUC_ADMIN} = IQ -> - iq_admin(StateData, Channel, From, To, IQ); - #iq{xmlns = ?NS_VERSION} -> - Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", - [Resource]), - _ = ?SEND(Res), - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_TIME} -> - Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", - [Resource]), - _ = ?SEND(Res), - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{xmlns = ?NS_VCARD} -> - Res = io_lib:format("WHOIS ~s \r\n", - [Resource]), - _ = ?SEND(Res), - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - #iq{} -> - Err = jlib:make_error_reply( - El, ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> - ok - end, + #iq{xmlns = ?NS_MUC_ADMIN} = IQ -> + iq_admin(StateData, Channel, From, To, IQ); + #iq{xmlns = ?NS_VERSION} -> + Res = io_lib:format("PRIVMSG ~s :\001VERSION\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + #iq{xmlns = ?NS_TIME} -> + Res = io_lib:format("PRIVMSG ~s :\001TIME\001\r\n", + [Resource]), + _ = (?SEND(Res)), + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + #iq{xmlns = ?NS_VCARD} -> + Res = io_lib:format("WHOIS ~s \r\n", [Resource]), + _ = (?SEND(Res)), + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + #iq{} -> + Err = jlib:make_error_reply(El, + ?ERR_FEATURE_NOT_IMPLEMENTED), + ejabberd_router:route(To, From, Err); + _ -> ok + end, {next_state, StateName, StateData}; - -handle_info({route_chan, _Channel, _Resource, _Packet}, StateName, StateData) -> +handle_info({route_chan, _Channel, _Resource, _Packet}, + StateName, StateData) -> {next_state, StateName, StateData}; - - handle_info({route_nick, Nick, - {xmlelement, "message", Attrs, _Els} = El}, + #xmlel{name = <<"message">>, attrs = Attrs} = El}, StateName, StateData) -> - NewStateData = - case xml:get_attr_s("type", Attrs) of - "chat" -> - Body = xml:get_path_s(El, [{elem, "body"}, cdata]), - case Body of - "/quote " ++ Rest -> - ?SEND(Rest ++ "\r\n"); - "/msg " ++ Rest -> - ?SEND("PRIVMSG " ++ Rest ++ "\r\n"); - "/me " ++ Rest -> - Strings = string:tokens(Rest, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format( - "PRIVMSG ~s :\001ACTION ~s\001\r\n", - [Nick, S]) - end, Strings)), - ?SEND(Res); - "/ctcp " ++ Rest -> - Words = string:tokens(Rest, " "), - case Words of - [CtcpDest | _ ] -> - CtcpCmd = toupper(string:substr(Rest, string:str(Rest, " ")+1 )), - Res = io_lib:format( - "PRIVMSG ~s :~s\r\n", - [CtcpDest, "\001" ++ CtcpCmd ++ "\001"]), - ?SEND(Res); - _ -> - ok - end; - _ -> - Strings = string:tokens(Body, "\n"), - Res = lists:concat( - lists:map( - fun(S) -> - io_lib:format("PRIVMSG ~s :~s\r\n", - [Nick, S]) - end, Strings)), - ?SEND(Res) - end; - "error" -> - stop; - _ -> - StateData - end, - if - NewStateData == stop -> - {stop, normal, StateData}; - true -> - {next_state, StateName, NewStateData} + NewStateData = case xml:get_attr_s(<<"type">>, Attrs) of + <<"chat">> -> + Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]), + case Body of + <<"/quote ", Rest/binary>> -> + ?SEND(<<Rest/binary, "\r\n">>); + <<"/msg ", Rest/binary>> -> + ?SEND(<<"PRIVMSG ", Rest/binary, "\r\n">>); + <<"/me ", Rest/binary>> -> + Strings = str:tokens(Rest, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :\001ACTION ~s\001\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res); + <<"/ctcp ", Rest/binary>> -> + Words = str:tokens(Rest, <<" ">>), + case Words of + [CtcpDest | _] -> + CtcpCmd = str:to_upper( + str:substr(Rest, + str:str(Rest, + <<" ">>) + + 1)), + Res = io_lib:format("PRIVMSG ~s :~s\r\n", + [CtcpDest, + <<"\001", + CtcpCmd/binary, + "\001">>]), + ?SEND(Res); + _ -> ok + end; + _ -> + Strings = str:tokens(Body, <<"\n">>), + Res = iolist_to_binary( + lists:map( + fun (S) -> + io_lib:format( + "PRIVMSG ~s :~s\r\n", + [Nick, S]) + end, + Strings)), + ?SEND(Res) + end; + <<"error">> -> stop; + _ -> StateData + end, + if NewStateData == stop -> {stop, normal, StateData}; + true -> {next_state, StateName, NewStateData} end; - -handle_info({route_nick, _Nick, _Packet}, StateName, StateData) -> +handle_info({route_nick, _Nick, _Packet}, StateName, + StateData) -> {next_state, StateName, StateData}; - - -handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) -> - send_text(StateData, "PONG " ++ ID ++ "\r\n"), +handle_info({ircstring, + <<$P, $I, $N, $G, $\s, ID/binary>>}, + StateName, StateData) -> + send_text(StateData, <<"PONG ", ID/binary, "\r\n">>), {next_state, StateName, StateData}; - -handle_info({ircstring, [$: | String]}, wait_for_registration, StateData) -> - Words = string:tokens(String, " "), - {NewState, NewStateData} = - case Words of - [_, "001" | _] -> - send_text(StateData, - io_lib:format("CODEPAGE ~s\r\n", [StateData#state.encoding])), - {stream_established, StateData}; - [_, "433" | _] -> - {error, - {error, error_nick_in_use(StateData, String), StateData}}; - [_, [$4, _, _] | _] -> - {error, - {error, error_unknown_num(StateData, String, "cancel"), - StateData}}; - [_, [$5, _, _] | _] -> - {error, - {error, error_unknown_num(StateData, String, "cancel"), - StateData}}; - _ -> - ?DEBUG("unknown irc command '~s'~n", [String]), - {wait_for_registration, StateData} - end, - % Note that we don't send any data at this stage. - if - NewState == error -> - {stop, normal, NewStateData}; - true -> - {next_state, NewState, NewStateData} +handle_info({ircstring, <<$:, String/binary>>}, + wait_for_registration, StateData) -> + Words = str:tokens(String, <<" ">>), + {NewState, NewStateData} = case Words of + [_, <<"001">> | _] -> + send_text(StateData, + io_lib:format("CODEPAGE ~s\r\n", + [StateData#state.encoding])), + {stream_established, StateData}; + [_, <<"433">> | _] -> + {error, + {error, + error_nick_in_use(StateData, String), + StateData}}; + [_, <<$4, _, _>> | _] -> + {error, + {error, + error_unknown_num(StateData, String, + <<"cancel">>), + StateData}}; + [_, <<$5, _, _>> | _] -> + {error, + {error, + error_unknown_num(StateData, String, + <<"cancel">>), + StateData}}; + _ -> + ?DEBUG("unknown irc command '~s'~n", + [String]), + {wait_for_registration, StateData} + end, + if NewState == error -> {stop, normal, NewStateData}; + true -> {next_state, NewState, NewStateData} end; - -handle_info({ircstring, [$: | String]}, _StateName, StateData) -> - Words = string:tokens(String, " "), - NewStateData = - case Words of - [_, "353" | Items] -> - process_channel_list(StateData, Items); - [_, "332", _Nick, [$# | Chan] | _] -> - process_channel_topic(StateData, Chan, String), - StateData; - [_, "333", _Nick, [$# | Chan] | _] -> - process_channel_topic_who(StateData, Chan, String), - StateData; - [_, "318", _, Nick | _] -> - process_endofwhois(StateData, String, Nick), - StateData; - [_, "311", _, Nick, Ident, Irchost | _ ] -> - process_whois311(StateData, String, Nick, Ident, Irchost), - StateData; - [_, "312", _, Nick, Ircserver | _ ] -> - process_whois312(StateData, String, Nick, Ircserver), - StateData; - [_, "319", _, Nick | _ ] -> - process_whois319(StateData, String, Nick), - StateData; - [_, "433" | _] -> - process_nick_in_use(StateData, String); - % CODEPAGE isn't standard, so don't complain if it's not there. - [_, "421", _, "CODEPAGE" | _] -> - StateData; - [_, [$4, _, _] | _] -> - process_num_error(StateData, String); - [_, [$5, _, _] | _] -> - process_num_error(StateData, String); - [From, "PRIVMSG", [$# | Chan] | _] -> - process_chanprivmsg(StateData, Chan, From, String), - StateData; - [From, "NOTICE", [$# | Chan] | _] -> - process_channotice(StateData, Chan, From, String), - StateData; - [From, "PRIVMSG", Nick, ":\001VERSION\001" | _] -> - process_version(StateData, Nick, From), - StateData; - [From, "PRIVMSG", Nick, ":\001USERINFO\001" | _] -> - process_userinfo(StateData, Nick, From), - StateData; - [From, "PRIVMSG", Nick | _] -> - process_privmsg(StateData, Nick, From, String), - StateData; - [From, "NOTICE", Nick | _] -> - process_notice(StateData, Nick, From, String), - StateData; - [From, "TOPIC", [$# | Chan] | _] -> - process_topic(StateData, Chan, From, String), - StateData; - [From, "PART", [$# | Chan] | _] -> - process_part(StateData, Chan, From, String); - [From, "QUIT" | _] -> - process_quit(StateData, From, String); - [From, "JOIN", Chan | _] -> - process_join(StateData, Chan, From, String); - [From, "MODE", [$# | Chan], "+o", Nick | _] -> - process_mode_o(StateData, Chan, From, Nick, - "admin", "moderator"), - StateData; - [From, "MODE", [$# | Chan], "-o", Nick | _] -> - process_mode_o(StateData, Chan, From, Nick, - "member", "participant"), - StateData; - [From, "KICK", [$# | Chan], Nick | _] -> - process_kick(StateData, Chan, From, Nick, String), - StateData; - [From, "NICK", Nick | _] -> - process_nick(StateData, From, Nick); - _ -> - ?DEBUG("unknown irc command '~s'~n", [String]), - StateData - end, - NewStateData1 = - case StateData#state.outbuf of - "" -> - NewStateData; - Data -> - send_text(NewStateData, Data), - NewStateData#state{outbuf = ""} - end, +handle_info({ircstring, <<$:, String/binary>>}, + _StateName, StateData) -> + Words = str:tokens(String, <<" ">>), + NewStateData = case Words of + [_, <<"353">> | Items] -> + process_channel_list(StateData, Items); + [_, <<"332">>, _Nick, <<$#, Chan/binary>> | _] -> + process_channel_topic(StateData, Chan, String), + StateData; + [_, <<"333">>, _Nick, <<$#, Chan/binary>> | _] -> + process_channel_topic_who(StateData, Chan, String), + StateData; + [_, <<"318">>, _, Nick | _] -> + process_endofwhois(StateData, String, Nick), StateData; + [_, <<"311">>, _, Nick, Ident, Irchost | _] -> + process_whois311(StateData, String, Nick, Ident, + Irchost), + StateData; + [_, <<"312">>, _, Nick, Ircserver | _] -> + process_whois312(StateData, String, Nick, Ircserver), + StateData; + [_, <<"319">>, _, Nick | _] -> + process_whois319(StateData, String, Nick), StateData; + [_, <<"433">> | _] -> + process_nick_in_use(StateData, String); + % CODEPAGE isn't standard, so don't complain if it's not there. + [_, <<"421">>, _, <<"CODEPAGE">> | _] -> StateData; + [_, <<$4, _, _>> | _] -> + process_num_error(StateData, String); + [_, <<$5, _, _>> | _] -> + process_num_error(StateData, String); + [From, <<"PRIVMSG">>, <<$#, Chan/binary>> | _] -> + process_chanprivmsg(StateData, Chan, From, String), + StateData; + [From, <<"NOTICE">>, <<$#, Chan/binary>> | _] -> + process_channotice(StateData, Chan, From, String), + StateData; + [From, <<"PRIVMSG">>, Nick, <<":\001VERSION\001">> + | _] -> + process_version(StateData, Nick, From), StateData; + [From, <<"PRIVMSG">>, Nick, <<":\001USERINFO\001">> + | _] -> + process_userinfo(StateData, Nick, From), StateData; + [From, <<"PRIVMSG">>, Nick | _] -> + process_privmsg(StateData, Nick, From, String), + StateData; + [From, <<"NOTICE">>, Nick | _] -> + process_notice(StateData, Nick, From, String), + StateData; + [From, <<"TOPIC">>, <<$#, Chan/binary>> | _] -> + process_topic(StateData, Chan, From, String), + StateData; + [From, <<"PART">>, <<$#, Chan/binary>> | _] -> + process_part(StateData, Chan, From, String); + [From, <<"QUIT">> | _] -> + process_quit(StateData, From, String); + [From, <<"JOIN">>, Chan | _] -> + process_join(StateData, Chan, From, String); + [From, <<"MODE">>, <<$#, Chan/binary>>, <<"+o">>, Nick + | _] -> + process_mode_o(StateData, Chan, From, Nick, + <<"admin">>, <<"moderator">>), + StateData; + [From, <<"MODE">>, <<$#, Chan/binary>>, <<"-o">>, Nick + | _] -> + process_mode_o(StateData, Chan, From, Nick, + <<"member">>, <<"participant">>), + StateData; + [From, <<"KICK">>, <<$#, Chan/binary>>, Nick | _] -> + process_kick(StateData, Chan, From, Nick, String), + StateData; + [From, <<"NICK">>, Nick | _] -> + process_nick(StateData, From, Nick); + _ -> + ?DEBUG("unknown irc command '~s'~n", [String]), + StateData + end, + NewStateData1 = case StateData#state.outbuf of + <<"">> -> NewStateData; + Data -> + send_text(NewStateData, Data), + NewStateData#state{outbuf = <<"">>} + end, {next_state, stream_established, NewStateData1}; - -handle_info({ircstring, [$E, $R, $R, $O, $R | _] = String}, +handle_info({ircstring, + <<$E, $R, $R, $O, $R, _/binary>> = String}, StateName, StateData) -> process_error(StateData, String), {next_state, StateName, StateData}; - - -handle_info({ircstring, String}, StateName, StateData) -> +handle_info({ircstring, String}, StateName, + StateData) -> ?DEBUG("unknown irc command '~s'~n", [String]), {next_state, StateName, StateData}; - - handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), {next_state, StateName, StateData}; -handle_info({tcp, _Socket, Data}, StateName, StateData) -> - Buf = StateData#state.inbuf ++ binary_to_list(Data), - Strings = ejabberd_regexp:split([C || C <- Buf, C /= $\r], "\n"), +handle_info({tcp, _Socket, Data}, StateName, + StateData) -> + Buf = <<(StateData#state.inbuf)/binary, Data/binary>>, + Strings = ejabberd_regexp:split(<< <<C>> + || <<C>> <= Buf, C /= $\r >>, + <<"\n">>), ?DEBUG("strings=~p~n", [Strings]), - NewBuf = process_lines(StateData#state.encoding, Strings), - {next_state, StateName, StateData#state{inbuf = NewBuf}}; -handle_info({tcp_closed, _Socket}, StateName, StateData) -> + NewBuf = process_lines(StateData#state.encoding, + Strings), + {next_state, StateName, + StateData#state{inbuf = NewBuf}}; +handle_info({tcp_closed, _Socket}, StateName, + StateData) -> gen_fsm:send_event(self(), closed), {next_state, StateName, StateData}; -handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) -> +handle_info({tcp_error, _Socket, _Reason}, StateName, + StateData) -> gen_fsm:send_event(self(), closed), {next_state, StateName, StateData}. @@ -673,67 +687,72 @@ handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) -> %% Returns: any %%---------------------------------------------------------------------- terminate(_Reason, _StateName, FullStateData) -> - % Extract error message if there was one. {Error, StateData} = case FullStateData of - {error, SError, SStateData} -> - {SError, SStateData}; - _ -> - {{xmlelement, "error", [{"code", "502"}], - [{xmlcdata, "Server Connect Failed"}]}, - FullStateData} - end, + {error, SError, SStateData} -> {SError, SStateData}; + _ -> + {#xmlel{name = <<"error">>, + attrs = [{<<"code">>, <<"502">>}], + children = + [{xmlcdata, + <<"Server Connect Failed">>}]}, + FullStateData} + end, (FullStateData#state.mod):closed_connection(StateData#state.host, - StateData#state.user, - StateData#state.server), - bounce_messages("Server Connect Failed"), - lists:foreach( - fun(Chan) -> - Stanza = {xmlelement, "presence", [{"type", "error"}], - [Error]}, - send_stanza(Chan, StateData, Stanza) - end, dict:fetch_keys(StateData#state.channels)), + StateData#state.user, + StateData#state.server), + bounce_messages(<<"Server Connect Failed">>), + lists:foreach(fun (Chan) -> + Stanza = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"error">>}], + children = [Error]}, + send_stanza(Chan, StateData, Stanza) + end, + dict:fetch_keys(StateData#state.channels)), case StateData#state.socket of - undefined -> - ok; - Socket -> - gen_tcp:close(Socket) + undefined -> ok; + Socket -> gen_tcp:close(Socket) end, ok. send_stanza(Chan, StateData, Stanza) -> ejabberd_router:route( jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - Stanza). + iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, Stanza). send_stanza_unavailable(Chan, StateData) -> - Affiliation = "member", % this is a simplification - Role = "none", - Stanza = - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", Affiliation}, - {"role", Role}], - []}, - {xmlelement, "status", - [{"code", "110"}], - []} - ]}]}, + Affiliation = <<"member">>, + Role = <<"none">>, + Stanza = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"x">>, + attrs = [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + Affiliation}, + {<<"role">>, Role}], + children = []}, + #xmlel{name = <<"status">>, + attrs = [{<<"code">>, <<"110">>}], + children = []}]}]}, send_stanza(Chan, StateData, Stanza). %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- -send_text(#state{socket = Socket, encoding = Encoding}, Text) -> - CText = iconv:convert("utf-8", Encoding, lists:flatten(Text)), - %?DEBUG("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]), +send_text(#state{socket = Socket, encoding = Encoding}, + Text) -> + CText = iconv:convert(<<"utf-8">>, Encoding, iolist_to_binary(Text)), gen_tcp:send(Socket, CText). - %send_queue(Socket, Q) -> % case queue:out(Q) of % {{value, El}, Q1} -> @@ -745,35 +764,32 @@ send_text(#state{socket = Socket, encoding = Encoding}, Text) -> bounce_messages(Reason) -> receive - {send_element, El} -> - {xmlelement, _Name, Attrs, _SubTags} = El, - case xml:get_attr_s("type", Attrs) of - "error" -> - ok; - _ -> - Err = jlib:make_error_reply(El, - "502", Reason), - From = jlib:string_to_jid(xml:get_attr_s("from", Attrs)), - To = jlib:string_to_jid(xml:get_attr_s("to", Attrs)), - ejabberd_router:route(To, From, Err) - end, - bounce_messages(Reason) - after 0 -> - ok + {send_element, El} -> + #xmlel{attrs = Attrs} = El, + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> ok; + _ -> + Err = jlib:make_error_reply(El, <<"502">>, Reason), + From = jlib:string_to_jid(xml:get_attr_s(<<"from">>, + Attrs)), + To = jlib:string_to_jid(xml:get_attr_s(<<"to">>, + Attrs)), + ejabberd_router:route(To, From, Err) + end, + bounce_messages(Reason) + after 0 -> ok end. - route_chan(Pid, Channel, Resource, Packet) -> Pid ! {route_chan, Channel, Resource, Packet}. route_nick(Pid, Nick, Packet) -> Pid ! {route_nick, Nick, Packet}. - -process_lines(_Encoding, [S]) -> - S; +process_lines(_Encoding, [S]) -> S; process_lines(Encoding, [S | Ss]) -> - self() ! {ircstring, iconv:convert(Encoding, "utf-8", S)}, + self() ! + {ircstring, iconv:convert(Encoding, <<"utf-8">>, S)}, process_lines(Encoding, Ss). process_channel_list(StateData, Items) -> @@ -781,587 +797,785 @@ process_channel_list(StateData, Items) -> process_channel_list_find_chan(StateData, []) -> StateData; -process_channel_list_find_chan(StateData, [[$# | Chan] | Items]) -> +process_channel_list_find_chan(StateData, + [<<$#, Chan/binary>> | Items]) -> process_channel_list_users(StateData, Chan, Items); -process_channel_list_find_chan(StateData, [_ | Items]) -> +process_channel_list_find_chan(StateData, + [_ | Items]) -> process_channel_list_find_chan(StateData, Items). process_channel_list_users(StateData, _Chan, []) -> StateData; -process_channel_list_users(StateData, Chan, [User | Items]) -> - NewStateData = process_channel_list_user(StateData, Chan, User), +process_channel_list_users(StateData, Chan, + [User | Items]) -> + NewStateData = process_channel_list_user(StateData, + Chan, User), process_channel_list_users(NewStateData, Chan, Items). process_channel_list_user(StateData, Chan, User) -> User1 = case User of - [$: | U1] -> U1; - _ -> User + <<$:, U1/binary>> -> U1; + _ -> User end, - {User2, Affiliation, Role} = - case User1 of - [$@ | U2] -> {U2, "admin", "moderator"}; - [$+ | U2] -> {U2, "member", "participant"}; - [$\% | U2] -> {U2, "admin", "moderator"}; - [$& | U2] -> {U2, "admin", "moderator"}; - [$~ | U2] -> {U2, "admin", "moderator"}; - _ -> {User1, "member", "participant"} - end, - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, User2), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", Affiliation}, - {"role", Role}], - []}]}]}), + {User2, Affiliation, Role} = case User1 of + <<$@, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + <<$+, U2/binary>> -> + {U2, <<"member">>, <<"participant">>}; + <<$%, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + <<$&, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + <<$~, U2/binary>> -> + {U2, <<"admin">>, <<"moderator">>}; + _ -> {User1, <<"member">>, <<"participant">>} + end, + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, User2), + StateData#state.user, + #xmlel{name = <<"presence">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + Affiliation}, + {<<"role">>, + Role}], + children = []}]}]}), case catch dict:update(Chan, - fun(Ps) -> - ?SETS:add_element(User2, Ps) - end, StateData#state.channels) of - {'EXIT', _} -> - StateData; - NS -> - StateData#state{channels = NS} + fun (Ps) -> (?SETS):add_element(User2, Ps) end, + StateData#state.channels) + of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. - process_channel_topic(StateData, Chan, String) -> - Msg = ejabberd_regexp:replace(String, ".*332[^:]*:", ""), + Msg = ejabberd_regexp:replace(String, <<".*332[^:]*:">>, + <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "subject", [], [{xmlcdata, Msg1}]}, - {xmlelement, "body", [], [{xmlcdata, "Topic for #" ++ Chan ++ ": " ++ Msg1}]} - ]}). + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"subject">>, attrs = [], + children = [{xmlcdata, Msg1}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + <<"Topic for #", Chan/binary, + ": ", Msg1/binary>>}]}]}). process_channel_topic_who(StateData, Chan, String) -> - Words = string:tokens(String, " "), + Words = str:tokens(String, <<" ">>), Msg1 = case Words of - [_, "333", _, _Chan, Whoset , Timeset] -> - {Unixtimeset, _Rest} = string:to_integer(Timeset), - "Topic for #" ++ Chan ++ " set by " ++ Whoset ++ - " at " ++ unixtime2string(Unixtimeset); - [_, "333", _, _Chan, Whoset | _] -> - "Topic for #" ++ Chan ++ " set by " ++ Whoset; - _ -> - String + [_, <<"333">>, _, _Chan, Whoset, Timeset] -> + {Unixtimeset, _Rest} = str:to_integer(Timeset), + <<"Topic for #", Chan/binary, " set by ", Whoset/binary, + " at ", (unixtime2string(Unixtimeset))/binary>>; + [_, <<"333">>, _, _Chan, Whoset | _] -> + <<"Topic for #", Chan/binary, " set by ", + Whoset/binary>>; + _ -> String end, Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). error_nick_in_use(_StateData, String) -> - Msg = ejabberd_regexp:replace(String, ".*433 +[^ ]* +", ""), + Msg = ejabberd_regexp:replace(String, + <<".*433 +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - {xmlelement, "error", [{"code", "409"}, {"type", "cancel"}], - [{xmlelement, "conflict", [{"xmlns", ?NS_STANZAS}], []}, - {xmlelement, "text", [{"xmlns", ?NS_STANZAS}], - [{xmlcdata, Msg1}]}]}. + #xmlel{name = <<"error">>, + attrs = + [{<<"code">>, <<"409">>}, {<<"type">>, <<"cancel">>}], + children = + [#xmlel{name = <<"conflict">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, + #xmlel{name = <<"text">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = [{xmlcdata, Msg1}]}]}. process_nick_in_use(StateData, String) -> - % We can't use the jlib macro because we don't know the language of the - % message. Error = error_nick_in_use(StateData, String), case StateData#state.nickchannel of - undefined -> - % Shouldn't happen with a well behaved server - StateData; - Chan -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "error"}], [Error]}), - StateData#state{nickchannel = undefined} + undefined -> + % Shouldn't happen with a well behaved server + StateData; + Chan -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"error">>}], + children = [Error]}), + StateData#state{nickchannel = undefined} end. process_num_error(StateData, String) -> - Error = error_unknown_num(StateData, String, "continue"), - lists:foreach( - fun(Chan) -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "message", [{"type", "error"}], - [Error]}) - end, dict:fetch_keys(StateData#state.channels)), - StateData. + Error = error_unknown_num(StateData, String, + <<"continue">>), + lists:foreach(fun (Chan) -> + ejabberd_router:route( + jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = + [{<<"type">>, + <<"error">>}], + children = [Error]}) + end, + dict:fetch_keys(StateData#state.channels)), + StateData. process_endofwhois(StateData, _String, Nick) -> - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], [{xmlcdata, "End of WHOIS"}]}]}). - -process_whois311(StateData, String, Nick, Ident, Irchost) -> - Fullname = ejabberd_regexp:replace(String, ".*311[^:]*:", ""), - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, lists:concat( - ["WHOIS: ", Nick, " is ", - Ident, "@" , Irchost, " : " , Fullname])}]}]}). + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + <<"End of WHOIS">>}]}]}). + +process_whois311(StateData, String, Nick, Ident, + Irchost) -> + Fullname = ejabberd_regexp:replace(String, + <<".*311[^:]*:">>, <<"">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + [<<"WHOIS: ">>, + Nick, + <<" is ">>, + Ident, + <<"@">>, + Irchost, + <<" : ">>, + Fullname])}]}]}). process_whois312(StateData, String, Nick, Ircserver) -> - Ircserverdesc = ejabberd_regexp:replace(String, ".*312[^:]*:", ""), - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, lists:concat(["WHOIS: ", Nick, " use ", - Ircserver, " : ", Ircserverdesc])}]}]}). + Ircserverdesc = ejabberd_regexp:replace(String, + <<".*312[^:]*:">>, <<"">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary([Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + [<<"WHOIS: ">>, + Nick, + <<" use ">>, + Ircserver, + <<" : ">>, + Ircserverdesc])}]}]}). process_whois319(StateData, String, Nick) -> - Chanlist = ejabberd_regexp:replace(String, ".*319[^:]*:", ""), - ejabberd_router:route( - jlib:make_jid(lists:concat([Nick, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, lists:concat(["WHOIS: ", Nick, " is on ", - Chanlist])}]}]}). - - + Chanlist = ejabberd_regexp:replace(String, + <<".*319[^:]*:">>, <<"">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Nick, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + iolist_to_binary( + [<<"WHOIS: ">>, + Nick, + <<" is on ">>, + Chanlist])}]}]}). process_chanprivmsg(StateData, Chan, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*PRIVMSG[^:]*:", ""), + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*PRIVMSG[^:]*:">>, <<"">>), Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - Msg + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> Msg end, Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - - + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). process_channotice(StateData, Chan, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*NOTICE[^:]*:", ""), + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*NOTICE[^:]*:">>, <<"">>), Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - "/me NOTICE: " ++ Msg + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> <<"/me NOTICE: ", Msg/binary>> end, Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - - - + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). process_privmsg(StateData, _Nick, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*PRIVMSG[^:]*:", ""), + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*PRIVMSG[^:]*:">>, <<"">>), Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - Msg + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> Msg end, Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [FromUser, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). process_notice(StateData, _Nick, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*NOTICE[^:]*:", ""), + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*NOTICE[^:]*:">>, <<"">>), Msg1 = case Msg of - [1, $A, $C, $T, $I, $O, $N, $ | Rest] -> - "/me " ++ Rest; - _ -> - "/me NOTICE: " ++ Msg + <<1, $A, $C, $T, $I, $O, $N, $\s, Rest/binary>> -> + <<"/me ", Rest/binary>>; + _ -> <<"/me NOTICE: ", Msg/binary>> end, Msg2 = filter_message(Msg1), - ejabberd_router:route( - jlib:make_jid(lists:concat([FromUser, "!", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}). - + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [FromUser, + <<"!">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"chat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}). process_version(StateData, _Nick, From) -> - [FromUser | _] = string:tokens(From, "!"), - send_text( - StateData, - io_lib:format("NOTICE ~s :\001VERSION " - "ejabberd IRC transport ~s (c) Alexey Shchepin" - "\001\r\n", - [FromUser, ?VERSION]) ++ - io_lib:format("NOTICE ~s :\001VERSION " - "http://ejabberd.jabberstudio.org/" - "\001\r\n", - [FromUser])). - + [FromUser | _] = str:tokens(From, <<"!">>), + send_text(StateData, + io_lib:format("NOTICE ~s :\001VERSION ejabberd IRC " + "transport ~s (c) Alexey Shchepin\001\r\n", + [FromUser, ?VERSION]) + ++ + io_lib:format("NOTICE ~s :\001VERSION http://ejabberd.jabber" + "studio.org/\001\r\n", + [FromUser])). process_userinfo(StateData, _Nick, From) -> - [FromUser | _] = string:tokens(From, "!"), - send_text( - StateData, - io_lib:format("NOTICE ~s :\001USERINFO " - "xmpp:~s" - "\001\r\n", - [FromUser, - jlib:jid_to_string(StateData#state.user)])). - + [FromUser | _] = str:tokens(From, <<"!">>), + send_text(StateData, + io_lib:format("NOTICE ~s :\001USERINFO xmpp:~s\001\r\n", + [FromUser, + jlib:jid_to_string(StateData#state.user)])). process_topic(StateData, Chan, From, String) -> - [FromUser | _] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*TOPIC[^:]*:", ""), + [FromUser | _] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*TOPIC[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "subject", [], [{xmlcdata, Msg1}]}, - {xmlelement, "body", [], - [{xmlcdata, "/me has changed the subject to: " ++ - Msg1}]}]}). + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"subject">>, attrs = [], + children = [{xmlcdata, Msg1}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [{xmlcdata, + <<"/me has changed the subject to: ", + Msg1/binary>>}]}]}). process_part(StateData, Chan, From, String) -> - [FromUser | FromIdent] = string:tokens(From, "!"), - Msg = ejabberd_regexp:replace(String, ".*PART[^:]*:", ""), + [FromUser | FromIdent] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*PART[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "none"}], - []}]}, - {xmlelement, "status", [], - [{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]}] - }), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"none">>}], + children = []}]}, + #xmlel{name = <<"status">>, attrs = [], + children = + [{xmlcdata, + list_to_binary( + [Msg1, " (", + FromIdent, ")"])}]}]}), case catch dict:update(Chan, - fun(Ps) -> - remove_element(FromUser, Ps) - end, StateData#state.channels) of - {'EXIT', _} -> - StateData; - NS -> - StateData#state{channels = NS} + fun (Ps) -> remove_element(FromUser, Ps) end, + StateData#state.channels) + of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. - process_quit(StateData, From, String) -> - [FromUser | FromIdent] = string:tokens(From, "!"), - - Msg = ejabberd_regexp:replace(String, ".*QUIT[^:]*:", ""), + [FromUser | FromIdent] = str:tokens(From, <<"!">>), + Msg = ejabberd_regexp:replace(String, + <<".*QUIT[^:]*:">>, <<"">>), Msg1 = filter_message(Msg), - %%NewChans = - dict:map( - fun(Chan, Ps) -> - case ?SETS:is_member(FromUser, Ps) of - true -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "none"}], - []}]}, - {xmlelement, "status", [], - [{xmlcdata, Msg1 ++ " (" ++ FromIdent ++ ")"}]} - ]}), - remove_element(FromUser, Ps); - _ -> - Ps - end - end, StateData#state.channels), + dict:map(fun (Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = + [{<<"type">>, + <<"unavailable">>}], + children = + [#xmlel{name = + <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children = + [#xmlel{name + = + <<"item">>, + attrs + = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"none">>}], + children + = + []}]}, + #xmlel{name = + <<"status">>, + attrs = [], + children = + [{xmlcdata, + list_to_binary( + [Msg1, " (", + FromIdent, + ")"])}]}]}), + remove_element(FromUser, Ps); + _ -> Ps + end + end, + StateData#state.channels), StateData. - process_join(StateData, Channel, From, _String) -> - [FromUser | FromIdent] = string:tokens(From, "!"), - Chan = lists:subtract(Channel, ":#"), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "participant"}], - []}]}, - {xmlelement, "status", [], - [{xmlcdata, FromIdent}]}]}), - + [FromUser | FromIdent] = str:tokens(From, <<"!">>), + [Chan | _] = binary:split(Channel, <<":#">>), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, FromUser), + StateData#state.user, + #xmlel{name = <<"presence">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"participant">>}], + children = []}]}, + #xmlel{name = <<"status">>, attrs = [], + children = + [{xmlcdata, + list_to_binary(FromIdent)}]}]}), case catch dict:update(Chan, - fun(Ps) -> - ?SETS:add_element(FromUser, Ps) - end, StateData#state.channels) of - {'EXIT', _} -> - StateData; - NS -> - StateData#state{channels = NS} + fun (Ps) -> (?SETS):add_element(FromUser, Ps) end, + StateData#state.channels) + of + {'EXIT', _} -> StateData; + NS -> StateData#state{channels = NS} end. - - -process_mode_o(StateData, Chan, _From, Nick, Affiliation, Role) -> - %Msg = lists:last(string:tokens(String, ":")), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", Affiliation}, - {"role", Role}], - []}]}]}). +process_mode_o(StateData, Chan, _From, Nick, + Affiliation, Role) -> + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #xmlel{name = <<"presence">>, attrs = [], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + Affiliation}, + {<<"role">>, + Role}], + children = []}]}]}). process_kick(StateData, Chan, From, Nick, String) -> - Msg = lists:last(string:tokens(String, ":")), - Msg2 = Nick ++ " kicked by " ++ From ++ " (" ++ filter_message(Msg) ++ ")", - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, ""), - StateData#state.user, - {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}), - ejabberd_router:route( - jlib:make_jid(lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "none"}, - {"role", "none"}], - []}, - {xmlelement, "status", [{"code", "307"}], []} - ]}]}). + Msg = lists:last(str:tokens(String, <<":">>)), + Msg2 = <<Nick/binary, " kicked by ", From/binary, " (", + (filter_message(Msg))/binary, ")">>, + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, <<"">>), + StateData#state.user, + #xmlel{name = <<"message">>, + attrs = [{<<"type">>, <<"groupchat">>}], + children = + [#xmlel{name = <<"body">>, attrs = [], + children = [{xmlcdata, Msg2}]}]}), + ejabberd_router:route(jlib:make_jid(iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, Nick), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, ?NS_MUC_USER}], + children = + [#xmlel{name = <<"item">>, + attrs = + [{<<"affiliation">>, + <<"none">>}, + {<<"role">>, + <<"none">>}], + children = []}, + #xmlel{name = <<"status">>, + attrs = + [{<<"code">>, + <<"307">>}], + children = []}]}]}). process_nick(StateData, From, NewNick) -> - [FromUser | _] = string:tokens(From, "!"), - Nick = lists:subtract(NewNick, ":"), - NewChans = - dict:map( - fun(Chan, Ps) -> - case ?SETS:is_member(FromUser, Ps) of - true -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, FromUser), - StateData#state.user, - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "participant"}, - {"nick", Nick}], - []}, - {xmlelement, "status", [{"code", "303"}], []} - ]}]}), - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, Nick), - StateData#state.user, - {xmlelement, "presence", [], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", - [{"affiliation", "member"}, - {"role", "participant"}], - []} - ]}]}), - ?SETS:add_element(Nick, - remove_element(FromUser, Ps)); - _ -> - Ps - end - end, StateData#state.channels), - if - FromUser == StateData#state.nick -> - StateData#state{nick = Nick, - nickchannel = undefined, - channels = NewChans}; - true -> - StateData#state{channels = NewChans} + [FromUser | _] = str:tokens(From, <<"!">>), + [Nick | _] = binary:split(NewNick, <<":">>), + NewChans = dict:map(fun (Chan, Ps) -> + case (?SETS):is_member(FromUser, Ps) of + true -> + ejabberd_router:route(jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + FromUser), + StateData#state.user, + #xmlel{name = + <<"presence">>, + attrs = + [{<<"type">>, + <<"unavailable">>}], + children = + [#xmlel{name + = + <<"x">>, + attrs + = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children + = + [#xmlel{name + = + <<"item">>, + attrs + = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"participant">>}, + {<<"nick">>, + Nick}], + children + = + []}, + #xmlel{name + = + <<"status">>, + attrs + = + [{<<"code">>, + <<"303">>}], + children + = + []}]}]}), + ejabberd_router:route(jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + Nick), + StateData#state.user, + #xmlel{name = + <<"presence">>, + attrs = [], + children = + [#xmlel{name + = + <<"x">>, + attrs + = + [{<<"xmlns">>, + ?NS_MUC_USER}], + children + = + [#xmlel{name + = + <<"item">>, + attrs + = + [{<<"affiliation">>, + <<"member">>}, + {<<"role">>, + <<"participant">>}], + children + = + []}]}]}), + (?SETS):add_element(Nick, + remove_element(FromUser, + Ps)); + _ -> Ps + end + end, + StateData#state.channels), + if FromUser == StateData#state.nick -> + StateData#state{nick = Nick, nickchannel = undefined, + channels = NewChans}; + true -> StateData#state{channels = NewChans} end. - process_error(StateData, String) -> - lists:foreach( - fun(Chan) -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "error"}], - [{xmlelement, "error", [{"code", "502"}], - [{xmlcdata, String}]}]}) - end, dict:fetch_keys(StateData#state.channels)). + lists:foreach(fun (Chan) -> + ejabberd_router:route(jlib:make_jid( + iolist_to_binary( + [Chan, + <<"%">>, + StateData#state.server]), + StateData#state.host, + StateData#state.nick), + StateData#state.user, + #xmlel{name = <<"presence">>, + attrs = + [{<<"type">>, + <<"error">>}], + children = + [#xmlel{name = + <<"error">>, + attrs = + [{<<"code">>, + <<"502">>}], + children = + [{xmlcdata, + String}]}]}) + end, + dict:fetch_keys(StateData#state.channels)). error_unknown_num(_StateData, String, Type) -> - Msg = ejabberd_regexp:replace(String, ".*[45][0-9][0-9] +[^ ]* +", ""), + Msg = ejabberd_regexp:replace(String, + <<".*[45][0-9][0-9] +[^ ]* +">>, <<"">>), Msg1 = filter_message(Msg), - {xmlelement, "error", [{"code", "500"}, {"type", Type}], - [{xmlelement, "undefined-condition", [{"xmlns", ?NS_STANZAS}], []}, - {xmlelement, "text", [{"xmlns", ?NS_STANZAS}], - [{xmlcdata, Msg1}]}]}. - - + #xmlel{name = <<"error">>, + attrs = [{<<"code">>, <<"500">>}, {<<"type">>, Type}], + children = + [#xmlel{name = <<"undefined-condition">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], children = []}, + #xmlel{name = <<"text">>, + attrs = [{<<"xmlns">>, ?NS_STANZAS}], + children = [{xmlcdata, Msg1}]}]}. remove_element(E, Set) -> - case ?SETS:is_element(E, Set) of - true -> - ?SETS:del_element(E, Set); - _ -> - Set + case (?SETS):is_element(E, Set) of + true -> (?SETS):del_element(E, Set); + _ -> Set end. - - iq_admin(StateData, Channel, From, To, #iq{type = Type, xmlns = XMLNS, sub_el = SubEl} = IQ) -> - case catch process_iq_admin(StateData, Channel, Type, SubEl) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p", [Reason]); - Res -> - if - Res /= ignore -> - ResIQ = case Res of - {result, ResEls} -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - ResEls - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end, - ejabberd_router:route(To, From, - jlib:iq_to_xml(ResIQ)); - true -> - ok - end + case catch process_iq_admin(StateData, Channel, Type, + SubEl) + of + {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]); + Res -> + if Res /= ignore -> + ResIQ = case Res of + {result, ResEls} -> + IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, XMLNS}], + children = ResEls}]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end, + ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ)); + true -> ok + end end. - process_iq_admin(StateData, Channel, set, SubEl) -> - case xml:get_subtag(SubEl, "item") of - false -> - {error, ?ERR_BAD_REQUEST}; - ItemEl -> - Nick = xml:get_tag_attr_s("nick", ItemEl), - Affiliation = xml:get_tag_attr_s("affiliation", ItemEl), - Role = xml:get_tag_attr_s("role", ItemEl), - Reason = xml:get_path_s(ItemEl, [{elem, "reason"}, cdata]), - process_admin(StateData, Channel, Nick, Affiliation, Role, Reason) + case xml:get_subtag(SubEl, <<"item">>) of + false -> {error, ?ERR_BAD_REQUEST}; + ItemEl -> + Nick = xml:get_tag_attr_s(<<"nick">>, ItemEl), + Affiliation = xml:get_tag_attr_s(<<"affiliation">>, + ItemEl), + Role = xml:get_tag_attr_s(<<"role">>, ItemEl), + Reason = xml:get_path_s(ItemEl, + [{elem, <<"reason">>}, cdata]), + process_admin(StateData, Channel, Nick, Affiliation, + Role, Reason) end; process_iq_admin(_StateData, _Channel, get, _SubEl) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. - - -process_admin(_StateData, _Channel, "", _Affiliation, _Role, _Reason) -> +process_admin(_StateData, _Channel, <<"">>, + _Affiliation, _Role, _Reason) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}; - -process_admin(StateData, Channel, Nick, _Affiliation, "none", Reason) -> +process_admin(StateData, Channel, Nick, _Affiliation, + <<"none">>, Reason) -> case Reason of - "" -> - send_text(StateData, - io_lib:format("KICK #~s ~s\r\n", - [Channel, Nick])); - _ -> - send_text(StateData, - io_lib:format("KICK #~s ~s :~s\r\n", - [Channel, Nick, Reason])) + <<"">> -> + send_text(StateData, + io_lib:format("KICK #~s ~s\r\n", [Channel, Nick])); + _ -> + send_text(StateData, + io_lib:format("KICK #~s ~s :~s\r\n", + [Channel, Nick, Reason])) end, {result, []}; - - - -process_admin(_StateData, _Channel, _Nick, _Affiliation, _Role, _Reason) -> +process_admin(_StateData, _Channel, _Nick, _Affiliation, + _Role, _Reason) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}. - - filter_message(Msg) -> - lists:filter( - fun(C) -> - if (C < 32) and - (C /= 9) and - (C /= 10) and - (C /= 13) -> - false; - true -> true - end - end, filter_mirc_colors(Msg)). + list_to_binary( + lists:filter(fun (C) -> + if (C < 32) and (C /= 9) and (C /= 10) and (C /= 13) -> + false; + true -> true + end + end, + binary_to_list(filter_mirc_colors(Msg)))). filter_mirc_colors(Msg) -> - ejabberd_regexp:greplace(Msg, "(\\003[0-9]+)(,[0-9]+)?", ""). + ejabberd_regexp:greplace(Msg, + <<"(\\003[0-9]+)(,[0-9]+)?">>, <<"">>). unixtime2string(Unixtime) -> - Secs = Unixtime + calendar:datetime_to_gregorian_seconds( - {{1970, 1, 1}, {0,0,0}}), + Secs = Unixtime + + calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, + {0, 0, 0}}), {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:universal_time_to_local_time( - calendar:gregorian_seconds_to_datetime(Secs)), - lists:flatten( - io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", - [Year, Month, Day, Hour, Minute, Second])). - -toupper([C | Cs]) -> - if - C >= $a, C =< $z -> - [C - 32 | toupper(Cs)]; - true -> - [C | toupper(Cs)] - end; -toupper([]) -> - []. + calendar:universal_time_to_local_time(calendar:gregorian_seconds_to_datetime(Secs)), + iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Minute, Second])). |