diff options
Diffstat (limited to 'src/web')
27 files changed, 8655 insertions, 9702 deletions
diff --git a/src/web/Makefile.in b/src/web/Makefile.in index 77f801410..5ac2fe5e6 100644 --- a/src/web/Makefile.in +++ b/src/web/Makefile.in @@ -15,7 +15,7 @@ EFLAGS += -pz .. # make debug=true to compile Erlang module with debug informations. ifdef debug - EFLAGS+=+debug_info +export_all + EFLAGS+=+debug_info endif SOURCES = $(wildcard *.erl) diff --git a/src/web/bosh.hrl b/src/web/bosh.hrl index d47bd7c94..70fbe8723 100644 --- a/src/web/bosh.hrl +++ b/src/web/bosh.hrl @@ -19,16 +19,34 @@ %%% %%%---------------------------------------------------------------------- --define(CT_XML, {"Content-Type", "text/xml; charset=utf-8"}). --define(CT_PLAIN, {"Content-Type", "text/plain"}). +-define(CT_XML, + {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). --define(AC_ALLOW_ORIGIN, {"Access-Control-Allow-Origin", "*"}). --define(AC_ALLOW_METHODS, {"Access-Control-Allow-Methods", "GET, POST, OPTIONS"}). --define(AC_ALLOW_HEADERS, {"Access-Control-Allow-Headers", "Content-Type"}). --define(AC_MAX_AGE, {"Access-Control-Max-Age", "86400"}). +-define(CT_PLAIN, + {<<"Content-Type">>, <<"text/plain">>}). --define(OPTIONS_HEADER, [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, - ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). --define(HEADER, [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]). +-define(CT_JSON, + {<<"Content-Type">>, <<"application/json">>}). + +-define(AC_ALLOW_ORIGIN, + {<<"Access-Control-Allow-Origin">>, <<"*">>}). + +-define(AC_ALLOW_METHODS, + {<<"Access-Control-Allow-Methods">>, + <<"GET, POST, OPTIONS">>}). + +-define(AC_ALLOW_HEADERS, + {<<"Access-Control-Allow-Headers">>, + <<"Content-Type">>}). + +-define(AC_MAX_AGE, + {<<"Access-Control-Max-Age">>, <<"86400">>}). + +-define(OPTIONS_HEADER, + [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, + ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). + +-define(HEADER(CType), + [CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]). -define(PROCNAME, ejabberd_mod_bosh). diff --git a/src/web/ejabberd_bosh.erl b/src/web/ejabberd_bosh.erl index 36f73abfd..1d0380e64 100644 --- a/src/web/ejabberd_bosh.erl +++ b/src/web/ejabberd_bosh.erl @@ -31,249 +31,278 @@ %% API -export([start/2, start/3, start_link/3]). --export([send_xml/2, setopts/2, controlling_process/2, migrate/3, - custom_receiver/1, become_controller/2, reset_stream/1, - change_shaper/2, monitor/1, close/1, sockname/1, - peername/1, process_request/2, send/2, change_controller/2]). + +-export([send_xml/2, setopts/2, controlling_process/2, + migrate/3, custom_receiver/1, become_controller/2, + reset_stream/1, change_shaper/2, monitor/1, close/1, + sockname/1, peername/1, process_request/3, send/2, + change_controller/2]). %% gen_fsm callbacks -export([init/1, wait_for_session/2, wait_for_session/3, - active/2, active/3, handle_event/3, print_state/1, - handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). + active/2, active/3, handle_event/3, print_state/1, + handle_sync_event/4, handle_info/3, terminate/3, + code_change/4]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("bosh.hrl"). %%-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. --define(BOSH_VERSION, "1.10"). --define(NS_CLIENT, "jabber:client"). --define(NS_BOSH, "urn:xmpp:xbosh"). --define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind"). +-define(BOSH_VERSION, <<"1.10">>). + +-define(NS_CLIENT, <<"jabber:client">>). + +-define(NS_BOSH, <<"urn:xmpp:xbosh">>). + +-define(NS_HTTP_BIND, + <<"http://jabber.org/protocol/httpbind">>). + +-define(DEFAULT_MAXPAUSE, 120). + +-define(DEFAULT_WAIT, 300). + +-define(DEFAULT_HOLD, 1). + +-define(DEFAULT_POLLING, 2). --define(DEFAULT_MAXPAUSE, 120). %% secs --define(DEFAULT_WAIT, 300). %% secs --define(DEFAULT_HOLD, 1). %% num --define(DEFAULT_POLLING, 2). %% secs --define(DEFAULT_INACTIVITY, 30). %% secs +-define(DEFAULT_INACTIVITY, 30). -define(MAX_SHAPED_REQUESTS_QUEUE_LEN, 1000). --define(SEND_TIMEOUT, 15000). %% millisecs - --record(state, {host, - sid, - el_ibuf, - el_obuf, - shaper_state, - c2s_pid, - xmpp_ver, - inactivity_timer, - wait_timer, - wait_timeout = ?DEFAULT_WAIT, - inactivity_timeout, - prev_rid, - prev_key, - prev_poll, - max_concat = unlimited, - responses = gb_trees:empty(), - receivers = gb_trees:empty(), - shaped_receivers = queue:new(), - ip, - max_requests}). - --record(body, {http_reason = "", %% Using HTTP reason phrase is - %% a hack, but we need a clue why - %% a connection gets terminated: - %% 'condition' attribute is not enough - attrs = [], - els = [], - size = 0}). - -%%%=================================================================== -%%% API -%%%=================================================================== -%% TODO: If compile with no supervisor option, start the session without -%% supervisor + +-define(SEND_TIMEOUT, 15000). + +-type bosh_socket() :: {http_bind, pid(), + {inet:ip_address(), + inet:port_number()}}. + +-export_type([bosh_socket/0]). + +-record(state, + {host = <<"">> :: binary(), + sid = <<"">> :: binary(), + el_ibuf = buf_new() :: queue(), + el_obuf = buf_new() :: queue(), + shaper_state = none :: shaper:shaper(), + c2s_pid :: pid(), + xmpp_ver = <<"">> :: binary(), + inactivity_timer :: reference(), + wait_timer :: reference(), + wait_timeout = ?DEFAULT_WAIT :: timeout(), + inactivity_timeout = ?DEFAULT_INACTIVITY :: timeout(), + prev_rid = 0 :: non_neg_integer(), + prev_key = <<"">> :: binary(), + prev_poll :: erlang:timestamp(), + max_concat = unlimited :: unlimited | non_neg_integer(), + responses = gb_trees:empty() :: gb_tree(), + receivers = gb_trees:empty() :: gb_tree(), + shaped_receivers = queue:new() :: queue(), + ip :: inet:ip_address(), + max_requests = 1 :: non_neg_integer()}). + +-record(body, + {http_reason = <<"">> :: binary(), + attrs = [] :: [{any(), any()}], + els = [] :: [xml_stream:xml_stream_el()], + size = 0 :: non_neg_integer()}). + start(#body{attrs = Attrs} = Body, IP, SID) -> - XMPPDomain = get_attr('to', Attrs), + XMPPDomain = get_attr(to, Attrs), Node = ejabberd_cluster:get_node(SID), - SupervisorProc = {gen_mod:get_module_proc(XMPPDomain, ?PROCNAME), Node}, - case catch supervisor:start_child(SupervisorProc, [Body, IP, SID]) of - {ok, Pid} -> - {ok, Pid}; - {'EXIT', {noproc, _}} -> - check_bosh_module(XMPPDomain), - {error, module_not_loaded}; - Err -> - ?ERROR_MSG("Failed to start BOSH session: ~p", [Err]), - {error, Err} + SupervisorProc = {gen_mod:get_module_proc(XMPPDomain, + ?PROCNAME), + Node}, + case catch supervisor:start_child(SupervisorProc, + [Body, IP, SID]) + of + {ok, Pid} -> {ok, Pid}; + {'EXIT', {noproc, _}} -> + check_bosh_module(XMPPDomain), + {error, module_not_loaded}; + Err -> + ?ERROR_MSG("Failed to start BOSH session: ~p", [Err]), + {error, Err} end. start(StateName, State) -> - ?GEN_FSM:start_link(?MODULE, [StateName, State], ?FSMOPTS). + (?GEN_FSM):start_link(?MODULE, [StateName, State], + ?FSMOPTS). start_link(Body, IP, SID) -> - ?GEN_FSM:start_link(?MODULE, [Body, IP, SID], ?FSMOPTS). + (?GEN_FSM):start_link(?MODULE, [Body, IP, SID], + ?FSMOPTS). -send({http_bind, _FsmRef, _IP}, _Packet) -> - {error, badarg}. +send({http_bind, FsmRef, IP}, Packet) -> + send_xml({http_bind, FsmRef, IP}, Packet). send_xml({http_bind, FsmRef, _IP}, Packet) -> - case catch ?GEN_FSM:sync_send_all_state_event( - FsmRef, {send_xml, Packet}, ?SEND_TIMEOUT) of - {'EXIT', {timeout, _}} -> - {error, timeout}; - {'EXIT', _} -> - {error, einval}; - Res -> - Res + case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + {send_xml, Packet}, + ?SEND_TIMEOUT) + of + {'EXIT', {timeout, _}} -> {error, timeout}; + {'EXIT', _} -> {error, einval}; + Res -> Res end. setopts({http_bind, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of - true -> - ?GEN_FSM:send_all_state_event(FsmRef, {activate, self()}); - _ -> - case lists:member({active, false}, Opts) of - true -> - case catch ?GEN_FSM:sync_send_all_state_event( - FsmRef, deactivate_socket) of - {'EXIT', _} -> - {error, einval}; - Res -> - Res - end; - _ -> - ok - end + true -> + (?GEN_FSM):send_all_state_event(FsmRef, + {activate, self()}); + _ -> + case lists:member({active, false}, Opts) of + true -> + case catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + deactivate_socket) + of + {'EXIT', _} -> {error, einval}; + Res -> Res + end; + _ -> ok + end end. -controlling_process(_Socket, _Pid) -> - ok. +controlling_process(_Socket, _Pid) -> ok. custom_receiver({http_bind, FsmRef, _IP}) -> {receiver, ?MODULE, FsmRef}. become_controller(FsmRef, C2SPid) -> - ?GEN_FSM:send_all_state_event(FsmRef, {become_controller, C2SPid}). + (?GEN_FSM):send_all_state_event(FsmRef, + {become_controller, C2SPid}). change_controller({http_bind, FsmRef, _IP}, C2SPid) -> become_controller(FsmRef, C2SPid). -reset_stream({http_bind, _FsmRef, _IP}) -> - ok. +reset_stream({http_bind, _FsmRef, _IP}) -> ok. change_shaper({http_bind, FsmRef, _IP}, Shaper) -> - ?GEN_FSM:send_all_state_event(FsmRef, {change_shaper, Shaper}). + (?GEN_FSM):send_all_state_event(FsmRef, + {change_shaper, Shaper}). monitor({http_bind, FsmRef, _IP}) -> erlang:monitor(process, FsmRef). close({http_bind, FsmRef, _IP}) -> - catch ?GEN_FSM:sync_send_all_state_event(FsmRef, close). + catch (?GEN_FSM):sync_send_all_state_event(FsmRef, + close). -sockname(_Socket) -> - {ok, {{0,0,0,0}, 0}}. +sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. -peername({http_bind, _FsmRef, IP}) -> - {ok, IP}. +peername({http_bind, _FsmRef, IP}) -> {ok, IP}. migrate(FsmRef, Node, After) -> erlang:send_after(After, FsmRef, {migrate, Node}). -process_request(Data, IP) -> +process_request(Data, IP, Type) -> Opts1 = ejabberd_c2s_config:get_c2s_limits(), - Opts = [{xml_socket, true} | Opts1], - MaxStanzaSize = - case lists:keysearch(max_stanza_size, 1, Opts) of - {value, {_, Size}} -> Size; - _ -> infinity - end, + Opts = case Type of + xml -> + [{xml_socket, true} | Opts1]; + json -> + Opts1 + end, + MaxStanzaSize = case lists:keysearch(max_stanza_size, 1, + Opts) + of + {value, {_, Size}} -> Size; + _ -> infinity + end, PayloadSize = iolist_size(Data), if PayloadSize > MaxStanzaSize -> - http_error(403, "Request Too Large"); + http_error(403, <<"Request Too Large">>, Type); true -> - case decode_body(Data, PayloadSize) of - {ok, #body{attrs = Attrs} = Body} -> - SID = get_attr('sid', Attrs), - To = get_attr('to', Attrs), - if SID == "", To == "" -> - %% Initial request which lacks "to" attribute - bosh_response( - #body{http_reason = "Missing 'to' attribute", - attrs = [{type, "terminate"}, - {condition, "improper-addressing"}]}); - SID == "" -> - %% Initial request - case start(Body, IP, make_sid()) of - {ok, Pid} -> - process_request(Pid, Body, IP); - _Err -> - bosh_response( - #body{http_reason = - "Failed to start BOSH session", - attrs = [{type, "terminate"}, - {condition, - "internal-server-error"}]}) - end; - true -> - case mod_bosh:find_session(SID) of - {ok, Pid} -> - process_request(Pid, Body, IP); - error -> - bosh_response( - #body{http_reason = "Session ID mismatch", - attrs = [{type, "terminate"}, - {condition, - "item-not-found"}]}) - end - end; - {error, Reason} -> - http_error(400, Reason) - end + case decode_body(Data, PayloadSize, Type) of + {ok, #body{attrs = Attrs} = Body} -> + SID = get_attr(sid, Attrs), + To = get_attr(to, Attrs), + if SID == <<"">>, To == <<"">> -> + bosh_response(#body{http_reason = + <<"Missing 'to' attribute">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"improper-addressing">>}]}, + Type); + SID == <<"">> -> + case start(Body, IP, make_sid()) of + {ok, Pid} -> process_request(Pid, Body, IP, Type); + _Err -> + bosh_response(#body{http_reason = + <<"Failed to start BOSH session">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"internal-server-error">>}]}, + Type) + end; + true -> + case mod_bosh:find_session(SID) of + {ok, Pid} -> process_request(Pid, Body, IP, Type); + error -> + bosh_response(#body{http_reason = + <<"Session ID mismatch">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"item-not-found">>}]}, + Type) + end + end; + {error, Reason} -> http_error(400, Reason, Type) + end end. -process_request(Pid, Req, _IP) -> - case catch ?GEN_FSM:sync_send_event(Pid, Req, infinity) of - #body{} = Resp -> - bosh_response(Resp); - {'EXIT', {Reason, _}} when Reason == noproc; Reason == normal -> - bosh_response(#body{http_reason = "BOSH session not found", - attrs = [{type, "terminate"}, - {condition, - "item-not-found"}]}); - {'EXIT', _} -> - bosh_response(#body{http_reason = "Unexpected error", - attrs = [{type, "terminate"}, - {condition, "internal-server-error"}]}) +process_request(Pid, Req, _IP, Type) -> + case catch (?GEN_FSM):sync_send_event(Pid, Req, + infinity) + of + #body{} = Resp -> bosh_response(Resp, Type); + {'EXIT', {Reason, _}} + when Reason == noproc; Reason == normal -> + bosh_response(#body{http_reason = + <<"BOSH session not found">>, + attrs = + [{type, <<"terminate">>}, + {condition, <<"item-not-found">>}]}, + Type); + {'EXIT', _} -> + bosh_response(#body{http_reason = + <<"Unexpected error">>, + attrs = + [{type, <<"terminate">>}, + {condition, <<"internal-server-error">>}]}, + Type) end. -%%%=================================================================== -%%% gen_fsm callbacks -%%%=================================================================== init([#body{attrs = Attrs}, IP, SID]) -> - %% Read c2s options from the first ejabberd_c2s configuration in - %% the config file listen section - %% TODO: We should have different access and shaper values for - %% each connector. The default behaviour should be however to use - %% the default c2s restrictions if not defined for the current - %% connector. Opts1 = ejabberd_c2s_config:get_c2s_limits(), Opts2 = [{xml_socket, true} | Opts1], Shaper = none, ShaperState = shaper:new(Shaper), Socket = make_socket(self(), IP), XMPPVer = get_attr('xmpp:version', Attrs), - XMPPDomain = get_attr('to', Attrs), - {InBuf, Opts} = case gen_mod:get_module_opt(XMPPDomain, mod_bosh, - prebind, false) of + XMPPDomain = get_attr(to, Attrs), + {InBuf, Opts} = case gen_mod:get_module_opt( + XMPPDomain, + mod_bosh, prebind, + fun(B) when is_boolean(B) -> B end, + false) of true -> JID = make_random_jid(XMPPDomain), {buf_new(), [{jid, JID} | Opts2]}; @@ -281,314 +310,333 @@ init([#body{attrs = Attrs}, IP, SID]) -> {buf_in([make_xmlstreamstart(XMPPDomain, XMPPVer)], buf_new()), Opts2} - end, - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), - Inactivity = gen_mod:get_module_opt(XMPPDomain, mod_bosh, - max_inactivity, ?DEFAULT_INACTIVITY), - MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, - max_concat, unlimited), - State = #state{host = XMPPDomain, - sid = SID, - ip = IP, - xmpp_ver = XMPPVer, - el_ibuf = InBuf, - max_concat = MaxConcat, - el_obuf = buf_new(), - inactivity_timeout = Inactivity, - shaper_state = ShaperState}, + end, + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), + Inactivity = gen_mod:get_module_opt(XMPPDomain, + mod_bosh, max_inactivity, + fun(I) when is_integer(I), I>0 -> I end, + ?DEFAULT_INACTIVITY), + MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat, + fun(unlimited) -> unlimited; + (N) when is_integer(N), N>0 -> N + end, unlimited), + State = #state{host = XMPPDomain, sid = SID, ip = IP, + xmpp_ver = XMPPVer, el_ibuf = InBuf, + max_concat = MaxConcat, el_obuf = buf_new(), + inactivity_timeout = Inactivity, + shaper_state = ShaperState}, NewState = restart_inactivity_timer(State), mod_bosh:open_session(SID, self()), {ok, wait_for_session, NewState}; init([StateName, State]) -> mod_bosh:open_session(State#state.sid, self()), case State#state.c2s_pid of - C2SPid when is_pid(C2SPid) -> - NewSocket = make_socket(self(), State#state.ip), - C2SPid ! {change_socket, NewSocket}, - NewState = restart_inactivity_timer(State), - {ok, StateName, NewState}; - _ -> - %% TODO: it seems like we're losing the connection :-/ - {stop, normal} + C2SPid when is_pid(C2SPid) -> + NewSocket = make_socket(self(), State#state.ip), + C2SPid ! {change_socket, NewSocket}, + NewState = restart_inactivity_timer(State), + {ok, StateName, NewState}; + _ -> {stop, normal} end. wait_for_session(_Event, State) -> - ?ERROR_MSG("unexpected event in 'wait_for_session': ~p", [_Event]), + ?ERROR_MSG("unexpected event in 'wait_for_session': ~p", + [_Event]), {next_state, wait_for_session, State}. -wait_for_session(#body{attrs = Attrs} = Req, From, State) -> - RID = get_attr('rid', Attrs), - ?DEBUG("got request:~n" - "** RequestID: ~p~n" - "** Request: ~p~n" - "** From: ~p~n" - "** State: ~p", - [RID, Req, From, State]), - Wait = min(get_attr('wait', Attrs, undefined), ?DEFAULT_WAIT), - Hold = min(get_attr('hold', Attrs, undefined), ?DEFAULT_HOLD), - NewKey = get_attr('newkey', Attrs), - Type = get_attr('type', Attrs), +wait_for_session(#body{attrs = Attrs} = Req, From, + State) -> + RID = get_attr(rid, Attrs), + ?DEBUG("got request:~n** RequestID: ~p~n** Request: " + "~p~n** From: ~p~n** State: ~p", + [RID, Req, From, State]), + Wait = min(get_attr(wait, Attrs, undefined), + ?DEFAULT_WAIT), + Hold = min(get_attr(hold, Attrs, undefined), + ?DEFAULT_HOLD), + NewKey = get_attr(newkey, Attrs), + Type = get_attr(type, Attrs), Requests = Hold + 1, {PollTime, Polling} = if Wait == 0, Hold == 0 -> - {now(), [{polling, ?DEFAULT_POLLING}]}; - true -> - {undefined, []} - end, - MaxPause = gen_mod:get_module_opt(State#state.host, mod_bosh, - max_pause, ?DEFAULT_MAXPAUSE), - Resp = #body{attrs = [{sid, State#state.sid}, - {wait, Wait}, - {ver, ?BOSH_VERSION}, - {polling, ?DEFAULT_POLLING}, - {inactivity, State#state.inactivity_timeout}, - {hold, Hold}, - {'xmpp:restartlogic', true}, - {requests, Requests}, - {secure, true}, - {maxpause, MaxPause}, - {'xmlns:xmpp', ?NS_BOSH}, - {'xmlns:stream', ?NS_STREAM}, - {from, State#state.host}|Polling]}, - {ShaperState, _} = shaper:update(State#state.shaper_state, Req#body.size), + {now(), [{polling, ?DEFAULT_POLLING}]}; + true -> {undefined, []} + end, + MaxPause = gen_mod:get_module_opt(State#state.host, + mod_bosh, max_pause, + fun(I) when is_integer(I), I>0 -> I end, + ?DEFAULT_MAXPAUSE), + Resp = #body{attrs = + [{sid, State#state.sid}, {wait, Wait}, + {ver, ?BOSH_VERSION}, {polling, ?DEFAULT_POLLING}, + {inactivity, State#state.inactivity_timeout}, + {hold, Hold}, {'xmpp:restartlogic', true}, + {requests, Requests}, {secure, true}, + {maxpause, MaxPause}, {'xmlns:xmpp', ?NS_BOSH}, + {'xmlns:stream', ?NS_STREAM}, {from, State#state.host} + | Polling]}, + {ShaperState, _} = + shaper:update(State#state.shaper_state, Req#body.size), State1 = State#state{wait_timeout = Wait, - prev_rid = RID, - prev_key = NewKey, - prev_poll = PollTime, - shaper_state = ShaperState, - max_requests = Requests}, + prev_rid = RID, prev_key = NewKey, + prev_poll = PollTime, shaper_state = ShaperState, + max_requests = Requests}, Els = maybe_add_xmlstreamend(Req#body.els, Type), State2 = route_els(State1, Els), {State3, RespEls} = get_response_els(State2), State4 = stop_inactivity_timer(State3), case RespEls of - [] -> - State5 = restart_wait_timer(State4), - Receivers = gb_trees:insert(RID, {From, Resp}, - State5#state.receivers), - {next_state, active, State5#state{receivers = Receivers}}; - _ -> - reply_next_state(State4, Resp#body{els = RespEls}, RID, From) + [] -> + State5 = restart_wait_timer(State4), + Receivers = gb_trees:insert(RID, {From, Resp}, + State5#state.receivers), + {next_state, active, + State5#state{receivers = Receivers}}; + _ -> + reply_next_state(State4, Resp#body{els = RespEls}, RID, + From) end; wait_for_session(_Event, _From, State) -> - ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p", [_Event]), + ?ERROR_MSG("unexpected sync event in 'wait_for_session': ~p", + [_Event]), {reply, {error, badarg}, wait_for_session, State}. active({#body{} = Body, From}, State) -> active1(Body, From, State); active(_Event, State) -> - ?ERROR_MSG("unexpected event in 'active': ~p", [_Event]), + ?ERROR_MSG("unexpected event in 'active': ~p", + [_Event]), {next_state, active, State}. -active(#body{attrs = Attrs, size = Size} = Req, From, State) -> - ?DEBUG("got request:~n" - "** Request: ~p~n" - "** From: ~p~n" - "** State: ~p", - [Req, From, State]), - {ShaperState, Pause} = shaper:update(State#state.shaper_state, Size), +active(#body{attrs = Attrs, size = Size} = Req, From, + State) -> + ?DEBUG("got request:~n** Request: ~p~n** From: " + "~p~n** State: ~p", + [Req, From, State]), + {ShaperState, Pause} = + shaper:update(State#state.shaper_state, Size), State1 = State#state{shaper_state = ShaperState}, if Pause > 0 -> - QLen = queue:len(State1#state.shaped_receivers), - if QLen < ?MAX_SHAPED_REQUESTS_QUEUE_LEN -> - TRef = start_shaper_timer(Pause), - Q = queue:in({TRef, From, Req}, State1#state.shaped_receivers), - State2 = stop_inactivity_timer(State1), - {next_state, active, State2#state{shaped_receivers = Q}}; - true -> - RID = get_attr('rid', Attrs), - reply_stop(State1, - #body{http_reason = "Too many requests", - attrs = [{"type", "terminate"}, - {"condition", "policy-violation"}]}, - From, RID) - end; - true -> - active1(Req, From, State1) + QLen = queue:len(State1#state.shaped_receivers), + if QLen < (?MAX_SHAPED_REQUESTS_QUEUE_LEN) -> + TRef = start_shaper_timer(Pause), + Q = queue:in({TRef, From, Req}, + State1#state.shaped_receivers), + State2 = stop_inactivity_timer(State1), + {next_state, active, + State2#state{shaped_receivers = Q}}; + true -> + RID = get_attr(rid, Attrs), + reply_stop(State1, + #body{http_reason = <<"Too many requests">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"policy-violation">>}]}, + From, RID) + end; + true -> active1(Req, From, State1) end; active(_Event, _From, State) -> - ?ERROR_MSG("unexpected sync event in 'active': ~p", [_Event]), + ?ERROR_MSG("unexpected sync event in 'active': ~p", + [_Event]), {reply, {error, badarg}, active, State}. active1(#body{attrs = Attrs} = Req, From, State) -> - RID = get_attr('rid', Attrs), - Key = get_attr('key', Attrs), + RID = get_attr(rid, Attrs), + Key = get_attr(key, Attrs), IsValidKey = is_valid_key(State#state.prev_key, Key), IsOveractivity = is_overactivity(State#state.prev_poll), - Type = get_attr('type', Attrs), - if RID > State#state.prev_rid + State#state.max_requests -> - reply_stop(State, - #body{http_reason = "Request ID is out of range", - attrs = [{"type", "terminate"}, - {"condition", "item-not-found"}]}, - From, RID); + Type = get_attr(type, Attrs), + if RID > + State#state.prev_rid + State#state.max_requests -> + reply_stop(State, + #body{http_reason = <<"Request ID is out of range">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, <<"item-not-found">>}]}, + From, RID); RID > State#state.prev_rid + 1 -> - State1 = restart_inactivity_timer(State), - %% TODO: gb_trees:insert/3 may raise an exception - Receivers = gb_trees:insert(RID, {From, Req}, - State1#state.receivers), - {next_state, active, State1#state{receivers = Receivers}}; + State1 = restart_inactivity_timer(State), + Receivers = gb_trees:insert(RID, {From, Req}, + State1#state.receivers), + {next_state, active, + State1#state{receivers = Receivers}}; RID =< State#state.prev_rid -> - %% TODO: do we need to check 'key' here? It seems so... - case gb_trees:lookup(RID, State#state.responses) of - {value, PrevBody} -> - {next_state, active, do_reply(State, From, PrevBody, RID)}; - none -> - reply_stop(State, - #body{http_reason = "Request ID is out of range", - attrs = [{"type", "terminate"}, - {"condition", "item-not-found"}]}, - From, RID) - end; + case gb_trees:lookup(RID, State#state.responses) of + {value, PrevBody} -> + {next_state, active, + do_reply(State, From, PrevBody, RID)}; + none -> + reply_stop(State, + #body{http_reason = + <<"Request ID is out of range">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"item-not-found">>}]}, + From, RID) + end; not IsValidKey -> - reply_stop(State, - #body{http_reason = "Session key mismatch", - attrs = [{"type", "terminate"}, - {"condition", "item-not-found"}]}, - From, RID); + reply_stop(State, + #body{http_reason = <<"Session key mismatch">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, <<"item-not-found">>}]}, + From, RID); IsOveractivity -> - reply_stop(State, - #body{http_reason = "Too many requests", - attrs = [{"type", "terminate"}, - {"condition", "policy-violation"}]}, - From, RID); + reply_stop(State, + #body{http_reason = <<"Too many requests">>, + attrs = + [{<<"type">>, <<"terminate">>}, + {<<"condition">>, <<"policy-violation">>}]}, + From, RID); true -> - State1 = stop_inactivity_timer(State), - State2 = stop_wait_timer(State1), - Els = case get_attr('xmpp:restart', Attrs, false) of - true -> - XMPPDomain = get_attr('to', Attrs, - State#state.host), - XMPPVer = get_attr('xmpp:version', Attrs, - State#state.xmpp_ver), - [make_xmlstreamstart(XMPPDomain, XMPPVer)]; - false -> - Req#body.els - end, - State3 = route_els(State2, maybe_add_xmlstreamend(Els, Type)), - {State4, RespEls} = get_response_els(State3), - NewKey = get_attr('newkey', Attrs, Key), - Pause = get_attr('pause', Attrs, undefined), - NewPoll = case State#state.prev_poll of - undefined -> undefined; - _ -> now() - end, - State5 = State4#state{prev_poll = NewPoll, - prev_key = NewKey}, - if Type == "terminate" -> - reply_stop(State5, #body{http_reason = "Session close", - attrs = [{"type", "terminate"}], - els = RespEls}, From, RID); - Pause /= undefined -> - State6 = drop_holding_receiver(State5), - State7 = restart_inactivity_timer(State6, Pause), - InBuf = buf_in(RespEls, State7#state.el_ibuf), - {next_state, active, - State7#state{prev_rid = RID, - el_ibuf = InBuf}}; - RespEls == [] -> - State6 = drop_holding_receiver(State5), - State7 = restart_wait_timer(State6), - %% TODO: gb_trees:insert/3 may raise an exception - Receivers = gb_trees:insert(RID, {From, #body{}}, - State7#state.receivers), - {next_state, active, State7#state{prev_rid = RID, - receivers = Receivers}}; - true -> - State6 = drop_holding_receiver(State5), - reply_next_state(State6#state{prev_rid = RID}, - #body{els = RespEls}, RID, From) - end + State1 = stop_inactivity_timer(State), + State2 = stop_wait_timer(State1), + Els = case get_attr('xmpp:restart', Attrs, false) of + true -> + XMPPDomain = get_attr(to, Attrs, State#state.host), + XMPPVer = get_attr('xmpp:version', Attrs, + State#state.xmpp_ver), + [make_xmlstreamstart(XMPPDomain, XMPPVer)]; + false -> Req#body.els + end, + State3 = route_els(State2, + maybe_add_xmlstreamend(Els, Type)), + {State4, RespEls} = get_response_els(State3), + NewKey = get_attr(newkey, Attrs, Key), + Pause = get_attr(pause, Attrs, undefined), + NewPoll = case State#state.prev_poll of + undefined -> undefined; + _ -> now() + end, + State5 = State4#state{prev_poll = NewPoll, + prev_key = NewKey}, + if Type == <<"terminate">> -> + reply_stop(State5, + #body{http_reason = <<"Session close">>, + attrs = [{<<"type">>, <<"terminate">>}], + els = RespEls}, + From, RID); + Pause /= undefined -> + State6 = drop_holding_receiver(State5), + State7 = restart_inactivity_timer(State6, Pause), + InBuf = buf_in(RespEls, State7#state.el_ibuf), + {next_state, active, + State7#state{prev_rid = RID, el_ibuf = InBuf}}; + RespEls == [] -> + State6 = drop_holding_receiver(State5), + State7 = restart_wait_timer(State6), + Receivers = gb_trees:insert(RID, {From, #body{}}, + State7#state.receivers), + {next_state, active, + State7#state{prev_rid = RID, receivers = Receivers}}; + true -> + State6 = drop_holding_receiver(State5), + reply_next_state(State6#state{prev_rid = RID}, + #body{els = RespEls}, RID, From) + end end. -handle_event({become_controller, C2SPid}, StateName, State) -> +handle_event({become_controller, C2SPid}, StateName, + State) -> State1 = route_els(State#state{c2s_pid = C2SPid}), {next_state, StateName, State1}; -handle_event({change_shaper, Shaper}, StateName, State) -> +handle_event({change_shaper, Shaper}, StateName, + State) -> NewShaperState = shaper:new(Shaper), - {next_state, StateName, State#state{shaper_state = NewShaperState}}; + {next_state, StateName, + State#state{shaper_state = NewShaperState}}; handle_event(_Event, StateName, State) -> - ?ERROR_MSG("unexpected event in '~s': ~p", [StateName, _Event]), + ?ERROR_MSG("unexpected event in '~s': ~p", + [StateName, _Event]), {next_state, StateName, State}. -handle_sync_event({send_xml, {xmlstreamstart, _, _} = El}, _From, - StateName, State) when State#state.xmpp_ver >= "1.0" -> - %% Avoid sending empty <body/> element +handle_sync_event({send_xml, + {xmlstreamstart, _, _} = El}, + _From, StateName, State) + when State#state.xmpp_ver >= <<"1.0">> -> OutBuf = buf_in([El], State#state.el_obuf), {reply, ok, StateName, State#state{el_obuf = OutBuf}}; -handle_sync_event({send_xml, El}, _From, StateName, State) -> +handle_sync_event({send_xml, El}, _From, StateName, + State) -> OutBuf = buf_in([El], State#state.el_obuf), State1 = State#state{el_obuf = OutBuf}, - case gb_trees:lookup(State1#state.prev_rid, State1#state.receivers) of - {value, {From, Body}} -> - {State2, Els} = get_response_els(State1), - {reply, ok, StateName, reply(State2, Body#body{els = Els}, - State2#state.prev_rid, From)}; - none -> - State2 = case queue:out(State1#state.shaped_receivers) of - {{value, {TRef, From, Body}}, Q} -> - cancel_timer(TRef), - ?GEN_FSM:send_event(self(), {Body, From}), - State1#state{shaped_receivers = Q}; - _ -> - State1 - end, - {reply, ok, StateName, State2} + case gb_trees:lookup(State1#state.prev_rid, + State1#state.receivers) + of + {value, {From, Body}} -> + {State2, Els} = get_response_els(State1), + {reply, ok, StateName, + reply(State2, Body#body{els = Els}, + State2#state.prev_rid, From)}; + none -> + State2 = case queue:out(State1#state.shaped_receivers) + of + {{value, {TRef, From, Body}}, Q} -> + cancel_timer(TRef), + (?GEN_FSM):send_event(self(), {Body, From}), + State1#state{shaped_receivers = Q}; + _ -> State1 + end, + {reply, ok, StateName, State2} end; handle_sync_event(close, _From, _StateName, State) -> {stop, normal, State}; -handle_sync_event(deactivate_socket, _From, StateName, StateData) -> - {reply, ok, StateName, StateData#state{c2s_pid = undefined}}; +handle_sync_event(deactivate_socket, _From, StateName, + StateData) -> + {reply, ok, StateName, + StateData#state{c2s_pid = undefined}}; handle_sync_event(_Event, _From, StateName, State) -> - ?ERROR_MSG("unexpected sync event in '~s': ~p", [StateName, _Event]), + ?ERROR_MSG("unexpected sync event in '~s': ~p", + [StateName, _Event]), {reply, {error, badarg}, StateName, State}. handle_info({timeout, TRef, wait_timeout}, StateName, - #state{wait_timer = TRef} = State) -> + #state{wait_timer = TRef} = State) -> {next_state, StateName, drop_holding_receiver(State)}; handle_info({timeout, TRef, inactive}, _StateName, - #state{inactivity_timer = TRef} = State) -> + #state{inactivity_timer = TRef} = State) -> {stop, normal, State}; -handle_info({timeout, TRef, shaper_timeout}, StateName, State) -> +handle_info({timeout, TRef, shaper_timeout}, StateName, + State) -> case queue:out(State#state.shaped_receivers) of - {{value, {TRef, From, Req}}, Q} -> - ?GEN_FSM:send_event(self(), {Req, From}), - {next_state, StateName, State#state{shaped_receivers = Q}}; - {{value, _}, _} -> - ?ERROR_MSG("shaper_timeout mismatch:~n" - "** TRef: ~p~n" - "** State: ~p", - [TRef, State]), - {stop, normal, State}; - _ -> - {next_state, StateName, State} + {{value, {TRef, From, Req}}, Q} -> + (?GEN_FSM):send_event(self(), {Req, From}), + {next_state, StateName, + State#state{shaped_receivers = Q}}; + {{value, _}, _} -> + ?ERROR_MSG("shaper_timeout mismatch:~n** TRef: ~p~n** " + "State: ~p", + [TRef, State]), + {stop, normal, State}; + _ -> {next_state, StateName, State} end; handle_info({migrate, Node}, StateName, State) -> if Node /= node() -> - NewState = bounce_receivers(State, migrated), - {migrate, NewState, - {Node, ?MODULE, start, [StateName, NewState]}, 0}; - true -> - {next_state, StateName, State} + NewState = bounce_receivers(State, migrated), + {migrate, NewState, + {Node, ?MODULE, start, [StateName, NewState]}, 0}; + true -> {next_state, StateName, State} end; handle_info(_Info, StateName, State) -> - ?ERROR_MSG("unexpected info:~n" - "** Msg: ~p~n" - "** StateName: ~p", - [_Info, StateName]), + ?ERROR_MSG("unexpected info:~n** Msg: ~p~n** StateName: ~p", + [_Info, StateName]), {next_state, StateName, State}. terminate({migrated, ClonePid}, _StateName, State) -> - ?INFO_MSG("Migrating session \"~s\" (c2s_pid = ~p) to ~p on node ~p", - [State#state.sid, State#state.c2s_pid, - ClonePid, node(ClonePid)]), + ?INFO_MSG("Migrating session \"~s\" (c2s_pid = " + "~p) to ~p on node ~p", + [State#state.sid, State#state.c2s_pid, ClonePid, + node(ClonePid)]), mod_bosh:close_session(State#state.sid); terminate(_Reason, _StateName, State) -> mod_bosh:close_session(State#state.sid), case State#state.c2s_pid of - C2SPid when is_pid(C2SPid) -> - ?GEN_FSM:send_event(C2SPid, closed); - _ -> - ok + C2SPid when is_pid(C2SPid) -> + (?GEN_FSM):send_event(C2SPid, closed); + _ -> ok end, bounce_receivers(State, closed), bounce_els_from_obuf(State). @@ -596,53 +644,57 @@ terminate(_Reason, _StateName, State) -> code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. -print_state(State) -> - State. +print_state(State) -> State. -%%%=================================================================== -%%% Internal functions -%%%=================================================================== route_els(#state{el_ibuf = Buf} = State) -> - route_els(State#state{el_ibuf = buf_new()}, buf_to_list(Buf)). + route_els(State#state{el_ibuf = buf_new()}, + buf_to_list(Buf)). route_els(State, Els) -> case State#state.c2s_pid of - C2SPid when is_pid(C2SPid) -> - lists:foreach( - fun(El) -> - ?GEN_FSM:send_event(C2SPid, El) - end, Els), - State; - _ -> - InBuf = buf_in(Els, State#state.el_ibuf), - State#state{el_ibuf = InBuf} + C2SPid when is_pid(C2SPid) -> + lists:foreach(fun (El) -> + (?GEN_FSM):send_event(C2SPid, El) + end, + Els), + State; + _ -> + InBuf = buf_in(Els, State#state.el_ibuf), + State#state{el_ibuf = InBuf} end. -get_response_els(#state{el_obuf = OutBuf, max_concat = MaxConcat} = State) -> +get_response_els(#state{el_obuf = OutBuf, + max_concat = MaxConcat} = + State) -> {Els, NewOutBuf} = buf_out(OutBuf, MaxConcat), {State#state{el_obuf = NewOutBuf}, Els}. reply(State, Body, RID, From) -> State1 = restart_inactivity_timer(State), - Receivers = gb_trees:delete_any(RID, State1#state.receivers), + Receivers = gb_trees:delete_any(RID, + State1#state.receivers), State2 = do_reply(State1, From, Body, RID), case catch gb_trees:take_smallest(Receivers) of - {NextRID, {From1, Req}, Receivers1} when NextRID == RID + 1 -> - ?GEN_FSM:send_event(self(), {Req, From1}), - State2#state{receivers = Receivers1}; - _ -> - State2#state{receivers = Receivers} + {NextRID, {From1, Req}, Receivers1} + when NextRID == RID + 1 -> + (?GEN_FSM):send_event(self(), {Req, From1}), + State2#state{receivers = Receivers1}; + _ -> State2#state{receivers = Receivers} end. reply_next_state(State, Body, RID, From) -> State1 = restart_inactivity_timer(State), - Receivers = gb_trees:delete_any(RID, State1#state.receivers), + Receivers = gb_trees:delete_any(RID, + State1#state.receivers), State2 = do_reply(State1, From, Body, RID), case catch gb_trees:take_smallest(Receivers) of - {NextRID, {From1, Req}, Receivers1} when NextRID == RID + 1 -> - active(Req, From1, State2#state{receivers = Receivers1}); - _ -> - {next_state, active, State2#state{receivers = Receivers}} + {NextRID, {From1, Req}, Receivers1} + when NextRID == RID + 1 -> + active(Req, From1, + State2#state{receivers = Receivers1}); + _ -> + {next_state, active, + State2#state{receivers = Receivers}} end. reply_stop(State, Body, From, RID) -> @@ -651,344 +703,407 @@ reply_stop(State, Body, From, RID) -> drop_holding_receiver(State) -> RID = State#state.prev_rid, case gb_trees:lookup(RID, State#state.receivers) of - {value, {From, Body}} -> - State1 = restart_inactivity_timer(State), - Receivers = gb_trees:delete_any(RID, State1#state.receivers), - State2 = State1#state{receivers = Receivers}, - do_reply(State2, From, Body, RID); - none -> - State + {value, {From, Body}} -> + State1 = restart_inactivity_timer(State), + Receivers = gb_trees:delete_any(RID, + State1#state.receivers), + State2 = State1#state{receivers = Receivers}, + do_reply(State2, From, Body, RID); + none -> State end. do_reply(State, From, Body, RID) -> - ?DEBUG("send reply:~n" - "** RequestID: ~p~n" - "** Reply: ~p~n" - "** To: ~p~n" - "** State: ~p", - [RID, Body, From, State]), - ?GEN_FSM:reply(From, Body), - Responses = gb_trees:delete_any(RID, State#state.responses), + ?DEBUG("send reply:~n** RequestID: ~p~n** Reply: " + "~p~n** To: ~p~n** State: ~p", + [RID, Body, From, State]), + (?GEN_FSM):reply(From, Body), + Responses = gb_trees:delete_any(RID, + State#state.responses), Responses1 = case gb_trees:size(Responses) of - N when N < State#state.max_requests; N == 0 -> - Responses; - _ -> - element(3, gb_trees:take_smallest(Responses)) - end, + N when N < State#state.max_requests; N == 0 -> + Responses; + _ -> element(3, gb_trees:take_smallest(Responses)) + end, Responses2 = gb_trees:insert(RID, Body, Responses1), State#state{responses = Responses2}. bounce_receivers(State, Reason) -> Receivers = gb_trees:to_list(State#state.receivers), - ShapedReceivers = lists:map( - fun({_, From, #body{attrs = Attrs} = Body}) -> - RID = get_attr('rid', Attrs), - {RID, {From, Body}} - end, queue:to_list(State#state.shaped_receivers)), - lists:foldl( - fun({RID, {From, Body}}, AccState) -> - NewBody = if Reason == closed -> - #body{http_reason = "Session closed", - attrs = [{type, "terminate"}, - {condition, "other-request"}]}; - Reason == migrated -> - Body#body{http_reason = "Session migrated"} - end, - do_reply(AccState, From, NewBody, RID) - end, State, Receivers ++ ShapedReceivers). + ShapedReceivers = lists:map(fun ({_, From, + #body{attrs = Attrs} = Body}) -> + RID = get_attr(rid, Attrs), + {RID, {From, Body}} + end, + queue:to_list(State#state.shaped_receivers)), + lists:foldl(fun ({RID, {From, Body}}, AccState) -> + NewBody = if Reason == closed -> + #body{http_reason = + <<"Session closed">>, + attrs = + [{type, <<"terminate">>}, + {condition, + <<"other-request">>}]}; + Reason == migrated -> + Body#body{http_reason = + <<"Session migrated">>} + end, + do_reply(AccState, From, NewBody, RID) + end, + State, Receivers ++ ShapedReceivers). bounce_els_from_obuf(State) -> - lists:foreach( - fun({xmlstreamelement, El}) -> - case El of - {xmlelement, Name, Attrs, _} - when Name == "presence"; - Name == "message"; - Name == "iq" -> - FromS = xml:get_attr_s("from", Attrs), - ToS = xml:get_attr_s("to", Attrs), - case {jlib:string_to_jid(FromS), - jlib:string_to_jid(ToS)} of - {#jid{} = From, #jid{} = To} -> - ejabberd_router:route(From, To, El); - _ -> - ok - end; - _ -> - ok - end; - (_) -> - ok - end, buf_to_list(State#state.el_obuf)). - -is_valid_key("", "") -> - true; -is_valid_key([_|_] = PrevKey, [_|_] = Key) -> - sha:sha(Key) == PrevKey; -is_valid_key(_, _) -> - false. - -is_overactivity(undefined) -> - false; + lists:foreach(fun ({xmlstreamelement, El}) -> + case El of + #xmlel{name = Name, attrs = Attrs} + when Name == <<"presence">>; + Name == <<"message">>; + Name == <<"iq">> -> + FromS = xml:get_attr_s(<<"from">>, Attrs), + ToS = xml:get_attr_s(<<"to">>, Attrs), + case {jlib:string_to_jid(FromS), + jlib:string_to_jid(ToS)} + of + {#jid{} = From, #jid{} = To} -> + ejabberd_router:route(From, To, El); + _ -> ok + end; + _ -> ok + end; + (_) -> ok + end, + buf_to_list(State#state.el_obuf)). + +is_valid_key(<<"">>, <<"">>) -> true; +is_valid_key(PrevKey, Key) -> + sha:sha(Key) == PrevKey. + +is_overactivity(undefined) -> false; is_overactivity(PrevPoll) -> - PollPeriod = timer:now_diff(now(), PrevPoll) div 1000000, - if PollPeriod < ?DEFAULT_POLLING -> - true; - true -> - false + PollPeriod = timer:now_diff(now(), PrevPoll) div + 1000000, + if PollPeriod < (?DEFAULT_POLLING) -> true; + true -> false end. make_xmlstreamstart(XMPPDomain, Version) -> VersionEl = case Version of - "" -> []; - _ -> [{"version", Version}] - end, - {xmlstreamstart, "stream:stream", - [{"to", XMPPDomain}, - {"xmlns", ?NS_CLIENT}, - {"xmlns:xmpp", ?NS_BOSH}, - {"xmlns:stream", ?NS_STREAM}|VersionEl]}. - -maybe_add_xmlstreamend(Els, "terminate") -> - Els ++ [{xmlstreamend, "stream:stream"}]; -maybe_add_xmlstreamend(Els, _) -> - Els. - -encode_body(#body{attrs = Attrs, els = Els}) -> - Attrs1 = lists:map( - fun({K, V}) when is_atom(K) -> - AmK = atom_to_list(K), - case V of - true -> {AmK, "true"}; - false -> {AmK, "false"}; - [_|_] -> {AmK, V}; - I when is_integer(I), I >= 0 -> - {AmK, integer_to_list(I)} - end; - ({K, V}) -> - {K, V} - end, Attrs), - Attrs2 = [{"xmlns", ?NS_HTTP_BIND}|Attrs1], - {Attrs3, XMLs} = - lists:foldr( - fun({xmlstreamraw, XML}, {AttrsAcc, XMLBuf}) -> - {AttrsAcc, [XML|XMLBuf]}; - ({xmlstreamelement, {xmlelement, "stream:error", _, _} = El}, - {AttrsAcc, XMLBuf}) -> - {[{"type", "terminate"}, - {"condition", "remote-stream-error"}, - {"xmlns:stream", ?NS_STREAM}|AttrsAcc], - [xml:element_to_binary(El)|XMLBuf]}; - ({xmlstreamelement, {xmlelement, "stream:features", _, _} = El}, {AttrsAcc, XMLBuf}) -> - {lists:keystore("xmlns:stream", 1, AttrsAcc, {"xmlns:stream", ?NS_STREAM}), - [xml:element_to_binary(El)|XMLBuf]}; - ({xmlstreamelement, El}, {AttrsAcc, XMLBuf}) -> - {AttrsAcc, [xml:element_to_binary(El)|XMLBuf]}; - ({xmlstreamend, _}, {AttrsAcc, XMLBuf}) -> - {[{"type", "terminate"}, - {"condition", "remote-stream-error"}|AttrsAcc], XMLBuf}; - ({xmlstreamstart, "stream:stream", SAttrs}, {AttrsAcc, XMLBuf}) -> - StreamID = xml:get_attr_s("id", SAttrs), - NewAttrs = case xml:get_attr_s("version", SAttrs) of - "" -> - [{"authid", StreamID}|AttrsAcc]; - V -> - lists:keystore("xmlns:xmpp", 1, [{"xmpp:version", V}, - {"authid", StreamID} | AttrsAcc], - {"xmlns:xmpp", ?NS_BOSH}) - end, - {NewAttrs, XMLBuf}; - ({xmlstreamerror, _}, {AttrsAcc, XMLBuf}) -> - {[{"type", "terminate"}, - {"condition", "remote-stream-error"}|AttrsAcc], - XMLBuf}; - (_, Acc) -> - Acc - end, {Attrs2, []}, Els), + <<"">> -> []; + _ -> [{<<"version">>, Version}] + end, + {xmlstreamstart, <<"stream:stream">>, + [{<<"to">>, XMPPDomain}, {<<"xmlns">>, ?NS_CLIENT}, + {<<"xmlns:xmpp">>, ?NS_BOSH}, + {<<"xmlns:stream">>, ?NS_STREAM} + | VersionEl]}. + +maybe_add_xmlstreamend(Els, <<"terminate">>) -> + Els ++ [{xmlstreamend, <<"stream:stream">>}]; +maybe_add_xmlstreamend(Els, _) -> Els. + +encode_body(#body{attrs = Attrs, els = Els}, Type) -> + Attrs1 = lists:map(fun ({K, V}) when is_atom(K) -> + AmK = iolist_to_binary(atom_to_list(K)), + case V of + true -> {AmK, <<"true">>}; + false -> {AmK, <<"false">>}; + I when is_integer(I), I >= 0 -> + {AmK, iolist_to_binary(integer_to_list(I))}; + _ -> {AmK, V} + end; + ({K, V}) -> {K, V} + end, + Attrs), + Attrs2 = [{<<"xmlns">>, ?NS_HTTP_BIND} | Attrs1], + {Attrs3, XMLs} = lists:foldr(fun ({xmlstreamraw, XML}, + {AttrsAcc, XMLBuf}) -> + {AttrsAcc, [XML | XMLBuf]}; + ({xmlstreamelement, + #xmlel{name = <<"stream:error">>} = El}, + {AttrsAcc, XMLBuf}) -> + {[{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"remote-stream-error">>}, + {<<"xmlns:stream">>, ?NS_STREAM} + | AttrsAcc], + [encode_element(El, Type) | XMLBuf]}; + ({xmlstreamelement, + #xmlel{name = <<"stream:features">>} = + El}, + {AttrsAcc, XMLBuf}) -> + {lists:keystore(<<"xmlns:stream">>, 1, + AttrsAcc, + {<<"xmlns:stream">>, + ?NS_STREAM}), + [encode_element(El, Type) | XMLBuf]}; + ({xmlstreamelement, + #xmlel{name = Name, attrs = EAttrs} = El}, + {AttrsAcc, XMLBuf}) + when Name == <<"message">>; + Name == <<"presence">>; + Name == <<"iq">> -> + NewAttrs = lists:keystore( + <<"xmlns">>, 1, EAttrs, + {<<"xmlns">>, ?NS_CLIENT}), + NewEl = El#xmlel{attrs = NewAttrs}, + {AttrsAcc, + [encode_element(NewEl, Type) | XMLBuf]}; + ({xmlstreamelement, El}, + {AttrsAcc, XMLBuf}) -> + {AttrsAcc, + [encode_element(El, Type) | XMLBuf]}; + ({xmlstreamend, _}, {AttrsAcc, XMLBuf}) -> + {[{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"remote-stream-error">>} + | AttrsAcc], + XMLBuf}; + ({xmlstreamstart, <<"stream:stream">>, + SAttrs}, + {AttrsAcc, XMLBuf}) -> + StreamID = xml:get_attr_s(<<"id">>, + SAttrs), + NewAttrs = case + xml:get_attr_s(<<"version">>, + SAttrs) + of + <<"">> -> + [{<<"authid">>, + StreamID} + | AttrsAcc]; + V -> + lists:keystore(<<"xmlns:xmpp">>, + 1, + [{<<"xmpp:version">>, + V}, + {<<"authid">>, + StreamID} + | AttrsAcc], + {<<"xmlns:xmpp">>, + ?NS_BOSH}) + end, + {NewAttrs, XMLBuf}; + ({xmlstreamerror, _}, + {AttrsAcc, XMLBuf}) -> + {[{<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"remote-stream-error">>} + | AttrsAcc], + XMLBuf}; + (_, Acc) -> Acc + end, + {Attrs2, []}, Els), case XMLs of - [] -> - ["<body", attrs_to_list(Attrs3), "/>"]; - _ -> - ["<body", attrs_to_list(Attrs3), $>, XMLs, "</body>"] + [] when Type == xml -> + [<<"<body">>, attrs_to_list(Attrs3), <<"/>">>]; + _ when Type == xml -> + [<<"<body">>, attrs_to_list(Attrs3), $>, XMLs, + <<"</body>">>]; + _ -> + jiffy:encode( + xmpp_json:to_json( + #xmlel{name = <<"body">>, + attrs = Attrs3, + children = XMLs})) end. -decode_body(BodyXML, Size) -> - case xml_stream:parse_element(BodyXML) of - {xmlelement, "body", Attrs, Els} -> - case attrs_to_body_attrs(Attrs) of - {error, _} = Err -> - Err; - BodyAttrs -> - case get_attr(rid, BodyAttrs) of - "" -> - {error, "Missing \"rid\" attribute"}; - _ -> - Els1 = lists:flatmap( - fun({xmlelement, _, _, _} = El) -> - [{xmlstreamelement, El}]; - (_) -> - [] - end, Els), - {ok, #body{attrs = BodyAttrs, - size = Size, - els = Els1}} - end - end; - {xmlelement, _, _, _} -> - {error, "Unexpected payload"}; - _ -> - {error, "XML is not well-formed"} +encode_element(El, xml) -> + xml:element_to_binary(El); +encode_element(El, json) -> + El. + +decode_body(Data, Size, Type) -> + case decode(Data, Type) of + #xmlel{name = <<"body">>, attrs = Attrs, + children = Els} -> + case attrs_to_body_attrs(Attrs) of + {error, _} = Err -> Err; + BodyAttrs -> + case get_attr(rid, BodyAttrs) of + <<"">> -> {error, <<"Missing \"rid\" attribute">>}; + _ -> + Els1 = lists:flatmap(fun (#xmlel{} = El) -> + [{xmlstreamelement, El}]; + (_) -> [] + end, + Els), + {ok, #body{attrs = BodyAttrs, size = Size, els = Els1}} + end + end; + #xmlel{} -> {error, <<"Unexpected payload">>}; + _ when Type == xml -> + {error, <<"XML is not well-formed">>}; + _ when Type == json -> + {error, <<"JSON is not well-formed">>} + end. + +decode(Data, xml) -> + xml_stream:parse_element(Data); +decode(Data, json) -> + case catch jiffy:decode(Data) of + {'EXIT', _} -> + {error, bad_json}; + JSON -> + case catch xmpp_json:from_json(JSON) of + {xmlstreamelement, NewEl} -> + NewEl; + XML -> + XML + end end. attrs_to_body_attrs(Attrs) -> - lists:foldl( - fun(_, {error, Reason}) -> - {error, Reason}; - ({Attr, Val}, Acc) -> - try - case Attr of - "ver" -> [{ver, Val}|Acc]; - "xmpp:version" -> [{'xmpp:version', Val}|Acc]; - "type" -> [{type, Val}|Acc]; - "key" -> [{key, Val}|Acc]; - "newkey" -> [{newkey, Val}|Acc]; - "xmlns" -> Val = ?NS_HTTP_BIND, Acc; - "secure" -> [{secure, to_bool(Val)}|Acc]; - "xmpp:restart" -> [{'xmpp:restart', to_bool(Val)}|Acc]; - "to" -> [{to, [_|_] = jlib:nameprep(Val)}|Acc]; - "wait" -> [{wait, to_int(Val, 0)}|Acc]; - "ack" -> [{ack, to_int(Val, 0)}|Acc]; - "sid" -> [{sid, Val}|Acc]; - "hold" -> [{hold, to_int(Val, 0)}|Acc]; - "rid" -> [{rid, to_int(Val, 0)}|Acc]; - "pause" -> [{pause, to_int(Val, 0)}|Acc]; - _ -> [{Attr, Val}|Acc] - end - catch _:_ -> - {error, "Invalid \"" ++ Attr ++ "\" attribute"} - end - end, [], Attrs). + lists:foldl(fun (_, {error, Reason}) -> {error, Reason}; + ({Attr, Val}, Acc) -> + try case Attr of + <<"ver">> -> [{ver, Val} | Acc]; + <<"xmpp:version">> -> + [{'xmpp:version', Val} | Acc]; + <<"type">> -> [{type, Val} | Acc]; + <<"key">> -> [{key, Val} | Acc]; + <<"newkey">> -> [{newkey, Val} | Acc]; + <<"xmlns">> -> Val = (?NS_HTTP_BIND), Acc; + <<"secure">> -> [{secure, to_bool(Val)} | Acc]; + <<"xmpp:restart">> -> + [{'xmpp:restart', to_bool(Val)} | Acc]; + <<"to">> -> + [{to, jlib:nameprep(Val)} | Acc]; + <<"wait">> -> [{wait, to_int(Val, 0)} | Acc]; + <<"ack">> -> [{ack, to_int(Val, 0)} | Acc]; + <<"sid">> -> [{sid, Val} | Acc]; + <<"hold">> -> [{hold, to_int(Val, 0)} | Acc]; + <<"rid">> -> [{rid, to_int(Val, 0)} | Acc]; + <<"pause">> -> [{pause, to_int(Val, 0)} | Acc]; + _ -> [{Attr, Val} | Acc] + end + catch + _:_ -> + {error, + <<"Invalid \"", Attr/binary, "\" attribute">>} + end + end, + [], Attrs). to_int(S, Min) -> - case list_to_integer(S) of - I when I >= Min -> - I; - _ -> - erlang:error(badarg) + case jlib:binary_to_integer(S) of + I when I >= Min -> I; + _ -> erlang:error(badarg) end. -to_bool("true") -> true; -to_bool("1") -> true; -to_bool("false") -> false; -to_bool("0") -> false. +to_bool(<<"true">>) -> true; +to_bool(<<"1">>) -> true; +to_bool(<<"false">>) -> false; +to_bool(<<"0">>) -> false. -attrs_to_list(Attrs) -> - [attr_to_list(A) || A <- Attrs]. +attrs_to_list(Attrs) -> [attr_to_list(A) || A <- Attrs]. attr_to_list({Name, Value}) -> [$\s, Name, $=, $', xml:crypt(Value), $']. -bosh_response(Body) -> - {200, Body#body.http_reason, ?HEADER, encode_body(Body)}. +bosh_response(Body, Type) -> + CType = case Type of + xml -> ?CT_XML; + json -> ?CT_JSON + end, + {200, Body#body.http_reason, ?HEADER(CType), + encode_body(Body, Type)}. + +http_error(Status, Reason, Type) -> + CType = case Type of + xml -> ?CT_XML; + json -> ?CT_JSON + end, + {Status, Reason, ?HEADER(CType), <<"">>}. -http_error(Status, Reason) -> - {Status, Reason, ?HEADER, ""}. +make_sid() -> sha:sha(randoms:get_string()). -make_sid() -> - sha:sha(randoms:get_string()). +-compile({no_auto_import, [{min, 2}]}). --compile({no_auto_import,[min/2]}). min(undefined, B) -> B; min(A, B) -> erlang:min(A, B). -%% Check that mod_bosh has been defined in config file. -%% Print a warning in log file if this is not the case. check_bosh_module(XmppDomain) -> case gen_mod:is_loaded(XmppDomain, mod_bosh) of - true -> ok; - false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) in host ~p," - " but the module mod_bosh is not started in" - " that host. Configure your BOSH client to connect" - " to the correct host, or add your desired host to" - " the configuration, or check your 'modules'" - " section in your ejabberd configuration file.", - [XmppDomain]) + true -> ok; + false -> + ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) " + "in host ~p, but the module mod_bosh " + "is not started in that host. Configure " + "your BOSH client to connect to the correct " + "host, or add your desired host to the " + "configuration, or check your 'modules' " + "section in your ejabberd configuration " + "file.", + [XmppDomain]) end. -get_attr(Attr, Attrs) -> - get_attr(Attr, Attrs, ""). +get_attr(Attr, Attrs) -> get_attr(Attr, Attrs, <<"">>). get_attr(Attr, Attrs, Default) -> case lists:keysearch(Attr, 1, Attrs) of - {value, {_, Val}} -> - Val; - _ -> - Default + {value, {_, Val}} -> Val; + _ -> Default end. -buf_new() -> - queue:new(). +buf_new() -> queue:new(). buf_in(Xs, Buf) -> - lists:foldl( - fun(X, Acc) -> - queue:in(X, Acc) - end, Buf, Xs). + lists:foldl(fun (X, Acc) -> queue:in(X, Acc) end, Buf, + Xs). buf_out(Buf, Num) when is_integer(Num), Num > 0 -> buf_out(Buf, Num, []); -buf_out(Buf, _) -> - {queue:to_list(Buf), buf_new()}. +buf_out(Buf, _) -> {queue:to_list(Buf), buf_new()}. -buf_out(Buf, 0, Els) -> - {lists:reverse(Els), Buf}; +buf_out(Buf, 0, Els) -> {lists:reverse(Els), Buf}; buf_out(Buf, I, Els) -> case queue:out(Buf) of - {{value, El}, NewBuf} -> - buf_out(NewBuf, I-1, [El|Els]); - {empty, _} -> - buf_out(Buf, 0, Els) + {{value, El}, NewBuf} -> + buf_out(NewBuf, I - 1, [El | Els]); + {empty, _} -> buf_out(Buf, 0, Els) end. -buf_to_list(Buf) -> - queue:to_list(Buf). +buf_to_list(Buf) -> queue:to_list(Buf). cancel_timer(TRef) when is_reference(TRef) -> - ?GEN_FSM:cancel_timer(TRef); -cancel_timer(_) -> - false. + (?GEN_FSM):cancel_timer(TRef); +cancel_timer(_) -> false. restart_timer(TRef, Timeout, Msg) -> cancel_timer(TRef), erlang:start_timer(timer:seconds(Timeout), self(), Msg). -restart_inactivity_timer(#state{inactivity_timeout = Timeout} = State) -> +restart_inactivity_timer(#state{inactivity_timeout = + Timeout} = + State) -> restart_inactivity_timer(State, Timeout). -restart_inactivity_timer(#state{inactivity_timer = TRef} = State, Timeout) -> +restart_inactivity_timer(#state{inactivity_timer = + TRef} = + State, + Timeout) -> NewTRef = restart_timer(TRef, Timeout, inactive), State#state{inactivity_timer = NewTRef}. -stop_inactivity_timer(#state{inactivity_timer = TRef} = State) -> +stop_inactivity_timer(#state{inactivity_timer = TRef} = + State) -> cancel_timer(TRef), State#state{inactivity_timer = undefined}. restart_wait_timer(#state{wait_timer = TRef, - wait_timeout = Timeout} = State) -> + wait_timeout = Timeout} = + State) -> NewTRef = restart_timer(TRef, Timeout, wait_timeout), State#state{wait_timer = NewTRef}. stop_wait_timer(#state{wait_timer = TRef} = State) -> - cancel_timer(TRef), - State#state{wait_timer = undefined}. + cancel_timer(TRef), State#state{wait_timer = undefined}. start_shaper_timer(Timeout) -> erlang:start_timer(Timeout, self(), shaper_timeout). make_random_jid(Host) -> - %% Copied from cyrsasl_anonymous.erl - User = lists:concat([randoms:get_string() | tuple_to_list(now())]), + User = iolist_to_binary([randoms:get_string() + | tuple_to_list(now())]), jlib:make_jid(User, Host, randoms:get_string()). -make_socket(Pid, IP) -> - {http_bind, Pid, IP}. +make_socket(Pid, IP) -> {http_bind, Pid, IP}. diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl index ffc78449f..dc1c2dd09 100644 --- a/src/web/ejabberd_http.erl +++ b/src/web/ejabberd_http.erl @@ -25,735 +25,648 @@ %%%---------------------------------------------------------------------- -module(ejabberd_http). + -author('alexey@process-one.net'). %% External exports --export([start/2, - start_link/2, - become_controller/1, - socket_type/0, - receive_headers/1, - url_encode/1]). +-export([start/2, start_link/2, become_controller/1, + socket_type/0, receive_headers/1, url_encode/1]). %% Callbacks -export([init/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). --include("ejabberd_http.hrl"). --record(state, {sockmod, - socket, - request_method, - request_version, - request_path, - request_auth, - request_keepalive, - request_content_length, - request_lang = "en", - %% XXX bard: request handlers are configured in - %% ejabberd.cfg under the HTTP service. For example, - %% to have the module test_web handle requests with - %% paths starting with "/test/module": - %% - %% {5280, ejabberd_http, [http_poll, web_admin, - %% {request_handlers, [{["test", "module"], mod_test_web}]}]} - %% - request_handlers = [], - request_host, - request_port, - request_tp, - request_headers = [], - end_of_request = false, - default_host, - trail = <<>>, - websocket_handlers = [] - }). +-include("ejabberd_http.hrl"). +-record(state, +{ + sockmod = gen_tcp :: gen_tcp | tls, + socket :: inet:socket() | tls:tls_socket(), + request_method :: method(), + request_version = {1, 1} :: {non_neg_integer(), non_neg_integer()}, + request_path :: {abs_path, binary()} | binary(), + request_auth :: {binary(), binary()}, + request_keepalive = false :: boolean(), + request_content_length = 0 :: non_neg_integer(), + request_lang = <<"en">> :: binary(), + request_handlers = [] :: [{[binary()], atom()}], + request_host :: binary(), + request_port = 5280 :: inet:port_number(), + request_tp = http :: protocol(), + request_headers = [] :: list(), + end_of_request = false :: boolean(), + default_host :: binary(), + trail = <<>> :: binary(), + websocket_handlers = [] :: [{[binary()], atom(), list()}] +}). -define(XHTML_DOCTYPE, - "<?xml version='1.0'?>\n" - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " - "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"). + <<"<?xml version='1.0'?>\n<!DOCTYPE html " + "PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//" + "EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1" + "-transitional.dtd\">\n">>). -define(HTML_DOCTYPE, - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " - "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"). - + <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD " + "XHTML 1.0 Transitional//EN\" \"http://www.w3." + "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" + "">>). start(SockData, Opts) -> - supervisor:start_child(ejabberd_http_sup, [SockData, Opts]). + supervisor:start_child(ejabberd_http_sup, + [SockData, Opts]). start_link(SockData, Opts) -> - {ok, proc_lib:spawn_link(ejabberd_http, init, [SockData, Opts])}. + {ok, + proc_lib:spawn_link(ejabberd_http, init, + [SockData, Opts])}. init({SockMod, Socket}, Opts) -> TLSEnabled = lists:member(tls, Opts), - TLSOpts1 = lists:filter(fun({certfile, _}) -> true; - (_) -> false - end, Opts), + TLSOpts1 = lists:filter(fun ({certfile, _}) -> true; + (_) -> false + end, + Opts), TLSOpts = [verify_none | TLSOpts1], - {SockMod1, Socket1} = - if - TLSEnabled -> - inet:setopts(Socket, [{recbuf, 8192}]), - {ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts), - {tls, TLSSocket}; - true -> - {SockMod, Socket} - end, + {SockMod1, Socket1} = if TLSEnabled -> + inet:setopts(Socket, [{recbuf, 8192}]), + {ok, TLSSocket} = tls:tcp_to_tls(Socket, + TLSOpts), + {tls, TLSSocket}; + true -> {SockMod, Socket} + end, case SockMod1 of - gen_tcp -> - inet:setopts(Socket1, [{packet, http}, {recbuf, 8192}]); - _ -> - ok + gen_tcp -> + inet:setopts(Socket1, [{packet, http_bin}, {recbuf, 8192}]); + _ -> ok end, - - %% XXX bard: for backward compatibility, expand in Opts: - %% web_admin -> {["admin"], ejabberd_web_admin} - %% http_bind -> {["http-bind"], mod_http_bind} - %% http_poll -> {["http-poll"], ejabberd_http_poll} - %% register -> {["register"], mod_register_web} - - RequestHandlers = - case lists:keysearch(request_handlers, 1, Opts) of - {value, {request_handlers, H}} -> H; - false -> [] - end ++ - case lists:member(captcha, Opts) of - true -> [{["captcha"], ejabberd_captcha}]; - false -> [] - end ++ - case lists:member(register, Opts) of - true -> [{["register"], mod_register_web}]; - false -> [] - end ++ - case lists:member(web_admin, Opts) of - true -> [{["admin"], ejabberd_web_admin}]; - false -> [] - end ++ - case lists:member(http_bind, Opts) of - true -> [{["http-bind"], mod_http_bind}]; - false -> [] - end ++ - case lists:member(http_poll, Opts) of - true -> [{["http-poll"], ejabberd_http_poll}]; - false -> [] - end, + Captcha = case lists:member(captcha, Opts) of + true -> [{[<<"captcha">>], ejabberd_captcha}]; + false -> [] + end, + Register = case lists:member(register, Opts) of + true -> [{[<<"register">>], mod_register_web}]; + false -> [] + end, + Admin = case lists:member(web_admin, Opts) of + true -> [{[<<"admin">>], ejabberd_web_admin}]; + false -> [] + end, + Bind = case lists:member(http_bind, Opts) of + true -> [{[<<"http-bind">>], mod_http_bind}]; + false -> [] + end, + Poll = case lists:member(http_poll, Opts) of + true -> [{[<<"http-poll">>], ejabberd_http_poll}]; + false -> [] + end, + DefinedHandlers = case lists:keysearch(request_handlers, + 1, Opts) + of + {value, {request_handlers, H}} -> H; + false -> [] + end, + RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ + Admin ++ Bind ++ Poll, ?DEBUG("S: ~p~n", [RequestHandlers]), - WebSocketHandlers = case lists:keysearch(websocket_handlers, 1, Opts) of - {value, {websocket_handlers, WH}} -> WH; - false -> [] - end, + WebSocketHandlers = case + lists:keysearch(websocket_handlers, 1, Opts) + of + {value, {websocket_handlers, WH}} -> WH; + false -> [] + end, ?DEBUG("WS: ~p~n", [WebSocketHandlers]), - DefaultHost = gen_mod:get_opt(default_host, Opts, undefined), + DefaultHost = gen_mod:get_opt(default_host, Opts, + fun iolist_to_binary/1), ?INFO_MSG("started: ~p", [{SockMod1, Socket1}]), - State = #state{sockmod = SockMod1, - socket = Socket1, - default_host = DefaultHost, - request_handlers = RequestHandlers, - websocket_handlers = WebSocketHandlers}, + State = #state{sockmod = SockMod1, socket = Socket1, + default_host = DefaultHost, + request_handlers = RequestHandlers, + websocket_handlers = WebSocketHandlers}, receive_headers(State). +become_controller(_Pid) -> ok. -become_controller(_Pid) -> - ok. - -socket_type() -> - raw. - +socket_type() -> raw. -send_text(_State, none) -> - exit(normal); +send_text(_State, none) -> exit(normal); send_text(State, Text) -> - case catch (State#state.sockmod):send(State#state.socket, Text) of - ok -> ok; - {error, timeout} -> - ?INFO_MSG("Timeout on ~p:send",[State#state.sockmod]), - exit(normal); - Error -> - ?DEBUG("Error in ~p:send: ~p",[State#state.sockmod, Error]), - exit(normal) + case catch + (State#state.sockmod):send(State#state.socket, Text) + of + ok -> ok; + {error, timeout} -> + ?INFO_MSG("Timeout on ~p:send", [State#state.sockmod]), + exit(normal); + Error -> + ?DEBUG("Error in ~p:send: ~p", + [State#state.sockmod, Error]), + exit(normal) end. -receive_headers(#state{trail=Trail} = State) -> +receive_headers(#state{trail = Trail} = State) -> SockMod = State#state.sockmod, Socket = State#state.socket, Data = SockMod:recv(Socket, 0, 300000), case State#state.sockmod of - gen_tcp -> - NewState = process_header(State, Data, true), - case NewState#state.end_of_request of - true -> - ok; - _ -> - receive_headers(NewState) - end; - _ -> - case Data of - {ok, D} -> - parse_headers(State#state{trail = <<Trail/binary, D/binary>>}); - {error, _} -> - ok - end + gen_tcp -> + NewState = process_header(State, Data), + case NewState#state.end_of_request of + true -> ok; + _ -> receive_headers(NewState) + end; + _ -> + case Data of + {ok, D} -> + parse_headers(State#state{trail = + <<Trail/binary, D/binary>>}); + {error, _} -> ok + end end. parse_headers(#state{trail = <<>>} = State) -> receive_headers(State); -parse_headers(#state{request_method = Method, trail = Data} = State) -> +parse_headers(#state{request_method = Method, + trail = Data} = + State) -> PktType = case Method of - undefined -> http; - _ -> httph - end, - case decode_packet(PktType, Data) of - {ok, Pkt, Rest} -> - NewState = process_header(State#state{trail = Rest}, {ok, Pkt}, false), - case NewState#state.end_of_request of - true -> - ok; - _ -> - parse_headers(NewState) - end; - {more, _} -> - receive_headers(State#state{trail = Data}); - _ -> - ok + undefined -> http_bin; + _ -> httph_bin + end, + case erlang:decode_packet(PktType, Data, []) of + {ok, Pkt, Rest} -> + NewState = process_header(State#state{trail = Rest}, + {ok, Pkt}), + case NewState#state.end_of_request of + true -> ok; + _ -> parse_headers(NewState) + end; + {more, _} -> receive_headers(State#state{trail = Data}); + _ -> ok end. -process_header(State, Data, Normalize) -> +process_header(State, Data) -> SockMod = State#state.sockmod, Socket = State#state.socket, case Data of - {ok, {http_request, Method, Uri, Version}} -> - KeepAlive = case Version of - {1, 1} -> - true; - _ -> - false - end, - Path = case Uri of - {absoluteURI, _Scheme, _Host, _Port, P} -> {abs_path, P}; - _ -> Uri - end, - State#state{request_method = Method, - request_version = Version, - request_path = Path, - request_keepalive = KeepAlive}; - {ok, {http_header, _, 'Connection'=Name, _, Conn}} -> - KeepAlive1 = case jlib:tolower(Conn) of - "keep-alive" -> - true; - "close" -> - false; - _ -> - State#state.request_keepalive - end, - State#state{request_keepalive = KeepAlive1, - request_headers=add_header(Name, Conn, State)}; - {ok, {http_header, _, 'Authorization'=Name, _, Auth}} -> - State#state{request_auth = parse_auth(Auth), - request_headers=add_header(Name, Auth, State)}; - {ok, {http_header, _, 'Content-Length'=Name, _, SLen}} -> - case catch list_to_integer(SLen) of - Len when is_integer(Len) -> - State#state{request_content_length = Len, - request_headers=add_header(Name, SLen, State)}; - _ -> - State - end; - {ok, {http_header, _, 'Accept-Language'=Name, _, Langs}} -> - State#state{request_lang = parse_lang(Langs), - request_headers=add_header(Name, Langs, State)}; - {ok, {http_header, _, 'Host'=Name, _, Host}} -> - State#state{request_host = Host, - request_headers=add_header(Name, Host, State)}; - {ok, {http_header, _, Name, _, Value}} when is_list(Name) andalso Normalize -> - State#state{request_headers=add_header(normalize_header_name(Name), Value, State)}; - {ok, {http_header, _, Name, _, Value}} -> - State#state{request_headers=add_header(Name, Value, State)}; - {ok, http_eoh} when State#state.request_host == undefined -> - ?WARNING_MSG("An HTTP request without 'Host' HTTP header was received.", []), - throw(http_request_no_host_header); - {ok, http_eoh} -> - ?DEBUG("(~w) http query: ~w ~s~n", - [State#state.socket, - State#state.request_method, - element(2, State#state.request_path)]), - {HostProvided, Port, TP} = get_transfer_protocol(SockMod, - State#state.request_host), - Host = get_host_really_served(State#state.default_host, HostProvided), - State2 = State#state{request_host = Host, - request_port = Port, - request_tp = TP}, - Out = process_request(State2), - send_text(State2, Out), - case State2#state.request_keepalive of - true -> - case SockMod of - gen_tcp -> - inet:setopts(Socket, [{packet, http}]); - _ -> - ok - end, - #state{sockmod = SockMod, - socket = Socket, - request_handlers = State#state.request_handlers}; - _ -> - #state{end_of_request = true, - request_handlers = State#state.request_handlers} - end; - {error, _Reason} -> - #state{end_of_request = true, - request_handlers = State#state.request_handlers}; - _ -> - #state{end_of_request = true, - request_handlers = State#state.request_handlers} + {ok, {http_request, Method, Uri, Version}} -> + KeepAlive = case Version of + {1, 1} -> true; + _ -> false + end, + Path = case Uri of + {absoluteURI, _Scheme, _Host, _Port, P} -> + {abs_path, P}; + {abs_path, P} -> + {abs_path, P}; + _ -> Uri + end, + State#state{request_method = Method, + request_version = Version, request_path = Path, + request_keepalive = KeepAlive}; + {ok, {http_header, _, 'Connection' = Name, _, Conn}} -> + KeepAlive1 = case jlib:tolower(Conn) of + <<"keep-alive">> -> true; + <<"close">> -> false; + _ -> State#state.request_keepalive + end, + State#state{request_keepalive = KeepAlive1, + request_headers = add_header(Name, Conn, State)}; + {ok, + {http_header, _, 'Authorization' = Name, _, Auth}} -> + State#state{request_auth = parse_auth(Auth), + request_headers = add_header(Name, Auth, State)}; + {ok, + {http_header, _, 'Content-Length' = Name, _, SLen}} -> + case catch jlib:binary_to_integer(SLen) of + Len when is_integer(Len) -> + State#state{request_content_length = Len, + request_headers = add_header(Name, SLen, State)}; + _ -> State + end; + {ok, + {http_header, _, 'Accept-Language' = Name, _, Langs}} -> + State#state{request_lang = parse_lang(Langs), + request_headers = add_header(Name, Langs, State)}; + {ok, {http_header, _, 'Host' = Name, _, Host}} -> + State#state{request_host = Host, + request_headers = add_header(Name, Host, State)}; + {ok, {http_header, _, Name, _, Value}} when is_binary(Name) -> + State#state{request_headers = + add_header(normalize_header_name(Name), + Value, + State)}; + {ok, {http_header, _, Name, _, Value}} -> + State#state{request_headers = + add_header(Name, Value, State)}; + {ok, http_eoh} + when State#state.request_host == undefined -> + ?WARNING_MSG("An HTTP request without 'Host' HTTP " + "header was received.", + []), + throw(http_request_no_host_header); + {ok, http_eoh} -> + ?DEBUG("(~w) http query: ~w ~s~n", + [State#state.socket, State#state.request_method, + element(2, State#state.request_path)]), + {HostProvided, Port, TP} = + get_transfer_protocol(SockMod, + State#state.request_host), + Host = get_host_really_served(State#state.default_host, + HostProvided), + State2 = State#state{request_host = Host, + request_port = Port, request_tp = TP}, + Out = process_request(State2), + send_text(State2, Out), + case State2#state.request_keepalive of + true -> + case SockMod of + gen_tcp -> inet:setopts(Socket, [{packet, http_bin}]); + _ -> ok + end, + #state{sockmod = SockMod, socket = Socket, + request_handlers = State#state.request_handlers}; + _ -> + #state{end_of_request = true, + request_handlers = State#state.request_handlers} + end; + {error, _Reason} -> + #state{end_of_request = true, + request_handlers = State#state.request_handlers}; + _ -> + #state{end_of_request = true, + request_handlers = State#state.request_handlers} end. -add_header(Name, Value, State) -> +add_header(Name, Value, State)-> [{Name, Value} | State#state.request_headers]. --define(GETOPT(Param, Opts), - case lists:keysearch(Param, 1, Opts) of - {value, {Param, V}} -> V; - false -> undefined - end). +-define(GETOPT(Param, Opts), + case lists:keysearch(Param, 1, Opts) of + {value, {Param, V}} -> V; + false -> undefined + end). -get_host_really_served(undefined, Provided) -> - Provided; +get_host_really_served(undefined, Provided) -> Provided; get_host_really_served(Default, Provided) -> case lists:member(Provided, ?MYHOSTS) of - true -> Provided; - false -> Default + true -> Provided; + false -> Default end. -%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP} -%% where -%% SockMod = gen_tcp | tls -%% HostPort = string() -%% TP = http | https -%% @doc Given a socket and hostport header, return data of transfer protocol. -%% Note that HostPort can be a string of a host like "example.org", -%% or a string of a host and port like "example.org:5280". get_transfer_protocol(SockMod, HostPort) -> - [Host | PortList] = string:tokens(HostPort, ":"), + [Host | PortList] = str:tokens(HostPort, <<":">>), case {SockMod, PortList} of - {gen_tcp, []} -> - {Host, 80, http}; - {gen_tcp, [Port]} -> - {Host, list_to_integer(Port), http}; - {tls, []} -> - {Host, 443, https}; - {tls, [Port]} -> - {Host, list_to_integer(Port), https} + {gen_tcp, []} -> {Host, 80, http}; + {gen_tcp, [Port]} -> + {Host, jlib:binary_to_integer(Port), http}; + {tls, []} -> {Host, 443, https}; + {tls, [Port]} -> + {Host, jlib:binary_to_integer(Port), https} end. %% XXX bard: search through request handlers looking for one that %% matches the requested URL path, and pass control to it. If none is %% found, answer with HTTP 404. -process([], _) -> - ejabberd_web:error(not_found); -process(Handlers, #ws{} = Ws)-> - [{HandlerPathPrefix, HandlerModule, HandlerOpts} | HandlersLeft] = Handlers, - case (lists:prefix(HandlerPathPrefix, Ws#ws.path) or - (HandlerPathPrefix==Ws#ws.path)) of - true -> - LocalPath = lists:nthtail(length(HandlerPathPrefix), Ws#ws.path), - ejabberd_hooks:run(ws_debug, [{LocalPath, Ws}]), - Protocol = case lists:keysearch(protocol, 1, HandlerOpts) of - {value, {protocol, P}} -> P; - false -> undefined - end, - Origins = case lists:keysearch(origins, 1, HandlerOpts) of - {value, {origins, O}} -> O; - false -> [] - end, - Auth = case lists:keysearch(auth, 1, HandlerOpts) of - {value, {auth, A}} -> A; - false -> undefined - end, - WS2 = Ws#ws{local_path = LocalPath, - protocol=Protocol, - acceptable_origins=Origins, - auth_module=Auth}, - case ejabberd_websocket:is_acceptable(WS2) of - true -> - ejabberd_websocket:connect(WS2, HandlerModule); - false -> - process(HandlersLeft, Ws) - end; - false -> - process(HandlersLeft, Ws) +process([], _) -> ejabberd_web:error(not_found); +process(Handlers, #ws{} = Ws) -> + [{HandlerPathPrefix, HandlerModule, HandlerOpts} | HandlersLeft] = + Handlers, + case lists:prefix(HandlerPathPrefix, Ws#ws.path) or + (HandlerPathPrefix == Ws#ws.path) + of + true -> + ?DEBUG("~p matches ~p", + [Ws#ws.path, HandlerPathPrefix]), + LocalPath = lists:nthtail(length(HandlerPathPrefix), + Ws#ws.path), + ejabberd_hooks:run(ws_debug, [{LocalPath, Ws}]), + Protocol = case lists:keysearch(protocol, 1, + HandlerOpts) + of + {value, {protocol, P}} -> P; + false -> undefined + end, + Origins = case lists:keysearch(origins, 1, HandlerOpts) + of + {value, {origins, O}} -> O; + false -> [] + end, + Auth = case lists:keysearch(auth, 1, HandlerOpts) of + {value, {auth, A}} -> A; + false -> undefined + end, + WS2 = Ws#ws{local_path = LocalPath, protocol = Protocol, + acceptable_origins = Origins, auth_module = Auth}, + case ejabberd_websocket:is_acceptable(WS2) of + true -> ejabberd_websocket:connect(WS2, HandlerModule); + false -> process(HandlersLeft, Ws) + end; + false -> process(HandlersLeft, Ws) end; process(Handlers, Request) -> - [{HandlerPathPrefix, HandlerModule} | HandlersLeft] = Handlers, - - case (lists:prefix(HandlerPathPrefix, Request#request.path) or - (HandlerPathPrefix==Request#request.path)) of - true -> - ?DEBUG("~p matches ~p", [Request#request.path, HandlerPathPrefix]), - %% LocalPath is the path "local to the handler", i.e. if - %% the handler was registered to handle "/test/" and the - %% requested path is "/test/foo/bar", the local path is - %% ["foo", "bar"] - LocalPath = lists:nthtail(length(HandlerPathPrefix), Request#request.path), - R = HandlerModule:process(LocalPath, Request), - ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]), - R; - false -> - process(HandlersLeft, Request) + %% Only the first element in the path prefix is checked + [{HandlerPathPrefix, HandlerModule} | HandlersLeft] = + Handlers, + case lists:prefix(HandlerPathPrefix, + Request#request.path) + or (HandlerPathPrefix == Request#request.path) + of + true -> + ?DEBUG("~p matches ~p", + [Request#request.path, HandlerPathPrefix]), + LocalPath = lists:nthtail(length(HandlerPathPrefix), + Request#request.path), + ?DEBUG("~p", [Request#request.headers]), + R = HandlerModule:process(LocalPath, Request), + ejabberd_hooks:run(http_request_debug, + [{LocalPath, Request}]), + R; + false -> process(HandlersLeft, Request) end. process_request(#state{request_method = Method, - request_path = {abs_path, Path}, - request_auth = Auth, - request_lang = Lang, - request_handlers = RequestHandlers, - request_host = Host, - request_port = Port, - request_tp = TP, - request_headers = RequestHeaders, + request_path = {abs_path, Path}, request_auth = Auth, + request_lang = Lang, request_handlers = RequestHandlers, + request_host = Host, request_port = Port, + request_tp = TP, request_headers = RequestHeaders, sockmod = SockMod, websocket_handlers = WebSocketHandlers, - socket = Socket} = State) - when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' -> - case (catch url_decode_q_split(Path)) of - {'EXIT', _} -> - make_bad_request(State); - {NPath, Query} -> - LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")], - LQuery = case (catch parse_urlencoded(Query)) of - {'EXIT', _Reason} -> - []; - LQ -> - LQ - end, - {ok, IPHere} = - case SockMod of - gen_tcp -> - inet:peername(Socket); - _ -> - SockMod:peername(Socket) - end, - XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []), - IP = analyze_ip_xff(IPHere, XFF, Host), - %% XXX bard: This previously passed control to - %% ejabberd_web:process_get, now passes it to a local - %% procedure (process) that handles dispatching based on - %% URL path prefix. - case ejabberd_websocket:check(Path, RequestHeaders) of - {true, VSN} -> - {_, Origin} = case lists:keyfind("Sec-Websocket-Origin", 1, RequestHeaders) of - false -> lists:keyfind("Origin", 1, RequestHeaders); - Value -> Value - end, - Ws = #ws{socket = Socket, - sockmod = SockMod, - ws_autoexit = false, - ip = IP, - path = LPath, - q = LQuery, - vsn = VSN, - host = Host, - port = Port, - origin = Origin, - headers = RequestHeaders - }, - process(WebSocketHandlers, Ws), - none; - false -> - Request = #request{method = Method, - path = LPath, - q = LQuery, - auth = Auth, - lang = Lang, - host = Host, - port = Port, - tp = TP, - headers = RequestHeaders, - ip = IP}, - case process(RequestHandlers, Request) of - El when element(1, El) == xmlelement -> - make_xhtml_output(State, 200, [], El); - {Status, Headers, El} when - element(1, El) == xmlelement -> - make_xhtml_output(State, Status, Headers, El); - Output when is_list(Output) or is_binary(Output) -> - make_text_output(State, 200, [], Output); - {Status, Headers, Output} when is_list(Output) or is_binary(Output) -> - make_text_output(State, Status, Headers, Output); - {Status, Reason, Headers, Output} when is_list(Output) or is_binary(Output) -> - make_text_output(State, Status, Reason, Headers, Output) - end - end + socket = Socket} = + State) + when Method =:= 'GET' orelse + Method =:= 'HEAD' orelse + Method =:= 'DELETE' orelse Method =:= 'OPTIONS' -> + case catch url_decode_q_split(Path) of + {'EXIT', _} -> make_bad_request(State); + {NPath, Query} -> + LPath = [path_decode(NPE) + || NPE <- str:tokens(NPath, <<"/">>)], + LQuery = case catch parse_urlencoded(Query) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {ok, IPHere} = case SockMod of + gen_tcp -> inet:peername(Socket); + _ -> SockMod:peername(Socket) + end, + XFF = proplists:get_value('X-Forwarded-For', + RequestHeaders, []), + IP = analyze_ip_xff(IPHere, XFF, Host), + case ejabberd_websocket:check(Path, RequestHeaders) of + {true, VSN} -> + {_, Origin} = case + lists:keyfind(<<"Sec-Websocket-Origin">>, 1, + RequestHeaders) + of + false -> + lists:keyfind(<<"Origin">>, 1, + RequestHeaders); + Value -> Value + end, + Ws = #ws{socket = Socket, sockmod = SockMod, + ws_autoexit = false, ip = IP, path = LPath, q = LQuery, + vsn = VSN, host = Host, port = Port, origin = Origin, + headers = RequestHeaders}, + process(WebSocketHandlers, Ws), + none; + false -> + Request = #request{method = Method, path = LPath, + q = LQuery, auth = Auth, lang = Lang, + host = Host, port = Port, tp = TP, + headers = RequestHeaders, ip = IP}, + case process(RequestHandlers, Request) of + El when is_record(El, xmlel) -> + make_xhtml_output(State, 200, [], El); + {Status, Headers, El} + when is_record(El, xmlel) -> + make_xhtml_output(State, Status, Headers, El); + Output when is_binary(Output) or is_list(Output) -> + make_text_output(State, 200, [], Output); + {Status, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Headers, Output); + {Status, Reason, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Reason, Headers, Output) + end + end end; - process_request(#state{request_method = Method, - request_path = {abs_path, Path}, - request_auth = Auth, - request_content_length = Len, - request_lang = Lang, - sockmod = SockMod, - socket = Socket, - request_host = Host, - request_port = Port, - request_tp = TP, + request_path = {abs_path, Path}, request_auth = Auth, + request_content_length = Len, request_lang = Lang, + sockmod = SockMod, socket = Socket, request_host = Host, + request_port = Port, request_tp = TP, request_headers = RequestHeaders, - request_handlers = RequestHandlers} = State) - when (Method=:='POST' orelse Method=:='PUT') andalso is_integer(Len) -> - {ok, IPHere} = - case SockMod of - gen_tcp -> - inet:peername(Socket); - _ -> - SockMod:peername(Socket) - end, - XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []), + request_handlers = RequestHandlers} = + State) + when (Method =:= 'POST' orelse Method =:= 'PUT') andalso + is_integer(Len) -> + {ok, IPHere} = case SockMod of + gen_tcp -> inet:peername(Socket); + _ -> SockMod:peername(Socket) + end, + XFF = proplists:get_value('X-Forwarded-For', + RequestHeaders, []), IP = analyze_ip_xff(IPHere, XFF, Host), case SockMod of - gen_tcp -> - inet:setopts(Socket, [{packet, 0}]); - _ -> - ok + gen_tcp -> inet:setopts(Socket, [{packet, 0}]); + _ -> ok end, Data = recv_data(State, Len), ?DEBUG("client data: ~p~n", [Data]), - case (catch url_decode_q_split(Path)) of - {'EXIT', _} -> - make_bad_request(State); - {NPath, _Query} -> - LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")], - LQuery = case (catch parse_urlencoded(Data)) of - {'EXIT', _Reason} -> - []; - LQ -> - LQ - end, - Request = #request{method = Method, - path = LPath, - q = LQuery, - auth = Auth, - data = Data, - lang = Lang, - host = Host, - port = Port, - tp = TP, - headers = RequestHeaders, - ip = IP}, - case process(RequestHandlers, Request) of - El when element(1, El) == xmlelement -> - make_xhtml_output(State, 200, [], El); - {Status, Headers, El} when - element(1, El) == xmlelement -> - make_xhtml_output(State, Status, Headers, El); - Output when is_list(Output) or is_binary(Output) -> - make_text_output(State, 200, [], Output); - {Status, Headers, Output} when is_list(Output) or is_binary(Output) -> - make_text_output(State, Status, Headers, Output); - {Status, Reason, Headers, Output} when is_list(Output) or is_binary(Output) -> - make_text_output(State, Status, Reason, Headers, Output) - end + case catch url_decode_q_split(Path) of + {'EXIT', _} -> make_bad_request(State); + {NPath, _Query} -> + LPath = [path_decode(NPE) + || NPE <- str:tokens(NPath, <<"/">>)], + LQuery = case catch parse_urlencoded(Data) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + Request = #request{method = Method, path = LPath, + q = LQuery, auth = Auth, data = Data, lang = Lang, + host = Host, port = Port, tp = TP, + headers = RequestHeaders, ip = IP}, + case process(RequestHandlers, Request) of + El when is_record(El, xmlel) -> + make_xhtml_output(State, 200, [], El); + {Status, Headers, El} + when is_record(El, xmlel) -> + make_xhtml_output(State, Status, Headers, El); + Output when is_binary(Output) or is_list(Output) -> + make_text_output(State, 200, [], Output); + {Status, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Headers, Output); + {Status, Reason, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Reason, Headers, Output) + end end; - -process_request(State) -> - make_bad_request(State). +process_request(State) -> make_bad_request(State). make_bad_request(State) -> - make_xhtml_output(State, - 400, - [], - ejabberd_web:make_xhtml([{xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}])). - -%% Support for X-Forwarded-From -analyze_ip_xff(IP, [], _Host) -> - IP; + make_xhtml_output(State, 400, [], + ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>, + attrs = [], + children = + [{xmlcdata, + <<"400 Bad Request">>}]}])). + +analyze_ip_xff(IP, [], _Host) -> IP; analyze_ip_xff({IPLast, Port}, XFF, Host) -> - [ClientIP | ProxiesIPs] = string:tokens(XFF, ", ") - ++ [inet_parse:ntoa(IPLast)], - TrustedProxies = case ejabberd_config:get_local_option( - {trusted_proxies, Host}) of - undefined -> []; - TPs -> TPs - end, - IPClient = case is_ipchain_trusted(ProxiesIPs, TrustedProxies) of - true -> - {ok, IPFirst} = inet_parse:address(ClientIP), - ?DEBUG("The IP ~w was replaced with ~w due to header " - "X-Forwarded-For: ~s", [IPLast, IPFirst, XFF]), - IPFirst; - false -> - IPLast + [ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++ + [jlib:ip_to_list(IPLast)], + TrustedProxies = ejabberd_config:get_local_option( + {trusted_proxies, Host}, + fun(TPs) -> + [iolist_to_binary(TP) || TP <- TPs] + end, []), + IPClient = case is_ipchain_trusted(ProxiesIPs, + TrustedProxies) + of + true -> + {ok, IPFirst} = inet_parse:address( + binary_to_list(ClientIP)), + ?DEBUG("The IP ~w was replaced with ~w due to " + "header X-Forwarded-For: ~s", + [IPLast, IPFirst, XFF]), + IPFirst; + false -> IPLast end, {IPClient, Port}. -is_ipchain_trusted(_UserIPs, all) -> - true; + +is_ipchain_trusted(_UserIPs, all) -> true; is_ipchain_trusted(UserIPs, TrustedIPs) -> - [] == UserIPs -- ["127.0.0.1" | TrustedIPs]. + [] == UserIPs -- [<<"127.0.0.1">> | TrustedIPs]. -recv_data(State, Len) -> - recv_data(State, Len, []). +recv_data(State, Len) -> recv_data(State, Len, <<>>). -recv_data(_State, 0, Acc) -> - binary_to_list(list_to_binary(Acc)); +recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc)); recv_data(State, Len, Acc) -> case State#state.trail of - <<>> -> - case (State#state.sockmod):recv(State#state.socket, Len, 300000) of - {ok, Data} -> - recv_data(State, Len - size(Data), [Acc | [Data]]); - _ -> - "" - end; - _ -> - Trail = binary_to_list(State#state.trail), - recv_data(State#state{trail = <<>>}, Len - length(Trail), [Acc | Trail]) + <<>> -> + case (State#state.sockmod):recv(State#state.socket, Len, + 300000) + of + {ok, Data} -> + recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>); + _ -> <<"">> + end; + _ -> + Trail = (State#state.trail), + recv_data(State#state{trail = <<>>}, + Len - byte_size(Trail), <<Acc/binary, Trail/binary>>) end. - make_xhtml_output(State, Status, Headers, XHTML) -> Data = case lists:member(html, Headers) of - true -> - list_to_binary([?HTML_DOCTYPE, - element_to_string(XHTML)]); - _ -> - list_to_binary([?XHTML_DOCTYPE, - element_to_string(XHTML)]) + true -> + iolist_to_binary([?HTML_DOCTYPE, + xml:element_to_binary(XHTML)]); + _ -> + iolist_to_binary([?XHTML_DOCTYPE, + xml:element_to_binary(XHTML)]) end, - Headers1 = case lists:keysearch("Content-Type", 1, Headers) of - {value, _} -> - [{"Content-Length", integer_to_list(size(Data))} | - Headers]; - _ -> - [{"Content-Type", "text/html; charset=utf-8"}, - {"Content-Length", integer_to_list(size(Data))} | - Headers] + Headers1 = case lists:keysearch(<<"Content-Type">>, 1, + Headers) + of + {value, _} -> + [{<<"Content-Length">>, + iolist_to_binary(integer_to_list(byte_size(Data)))} + | Headers]; + _ -> + [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, + {<<"Content-Length">>, + iolist_to_binary(integer_to_list(byte_size(Data)))} + | Headers] end, HeadersOut = case {State#state.request_version, - State#state.request_keepalive} of - {{1, 1}, true} -> Headers1; - {_, true} -> - [{"Connection", "keep-alive"} | Headers1]; - {_, false} -> - % not required for http versions < 1.1 - % but would make no harm - [{"Connection", "close"} | Headers1] + State#state.request_keepalive} + of + {{1, 1}, true} -> Headers1; + {_, true} -> + [{<<"Connection">>, <<"keep-alive">>} | Headers1]; + {_, false} -> + [{<<"Connection">>, <<"close">>} | Headers1] end, - Version = case State#state.request_version of - {1, 1} -> "HTTP/1.1 "; - _ -> "HTTP/1.0 " + {1, 1} -> <<"HTTP/1.1 ">>; + _ -> <<"HTTP/1.0 ">> end, - - H = lists:map(fun({Attr, Val}) -> - [Attr, ": ", Val, "\r\n"]; - (_) -> - [] - end, HeadersOut), - SL = [Version, integer_to_list(Status), " ", - code_to_phrase(Status), "\r\n"], - + H = lists:map(fun ({Attr, Val}) -> + [Attr, <<": ">>, Val, <<"\r\n">>]; + (_) -> [] + end, + HeadersOut), + SL = [Version, + iolist_to_binary(integer_to_list(Status)), <<" ">>, + code_to_phrase(Status), <<"\r\n">>], Data2 = case State#state.request_method of - 'HEAD' -> ""; - _ -> Data - end, - - [SL, H, "\r\n", Data2]. + 'HEAD' -> <<"">>; + _ -> Data + end, + [SL, H, <<"\r\n">>, Data2]. make_text_output(State, Status, Headers, Text) -> - make_text_output(State, Status, "", Headers, Text). - -make_text_output(State, Status, Reason, Headers, Text) when is_list(Text) -> - make_text_output(State, Status, Reason, Headers, list_to_binary(Text)); - -make_text_output(State, Status, Reason, Headers, Data) when is_binary(Data) -> - Headers1 = case lists:keysearch("Content-Type", 1, Headers) of - {value, _} -> - [{"Content-Length", integer_to_list(size(Data))} | - Headers]; - _ -> - [{"Content-Type", "text/html; charset=utf-8"}, - {"Content-Length", integer_to_list(size(Data))} | - Headers] + make_text_output(State, Status, <<"">>, Headers, Text). + +make_text_output(State, Status, Reason, Headers, Text) -> + Data = iolist_to_binary(Text), + Headers1 = case lists:keysearch(<<"Content-Type">>, 1, + Headers) + of + {value, _} -> + [{<<"Content-Length">>, + jlib:integer_to_binary(byte_size(Data))} + | Headers]; + _ -> + [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, + {<<"Content-Length">>, + jlib:integer_to_binary(byte_size(Data))} + | Headers] end, - HeadersOut = case {State#state.request_version, - State#state.request_keepalive} of - {{1, 1}, true} -> Headers1; - {_, true} -> - [{"Connection", "keep-alive"} | Headers1]; - {_, false} -> - % not required for http versions < 1.1 - % but would make no harm - [{"Connection", "close"} | Headers1] + State#state.request_keepalive} + of + {{1, 1}, true} -> Headers1; + {_, true} -> + [{<<"Connection">>, <<"keep-alive">>} | Headers1]; + {_, false} -> + [{<<"Connection">>, <<"close">>} | Headers1] end, - Version = case State#state.request_version of - {1, 1} -> "HTTP/1.1 "; - _ -> "HTTP/1.0 " + {1, 1} -> <<"HTTP/1.1 ">>; + _ -> <<"HTTP/1.0 ">> end, - - H = lists:map(fun({Attr, Val}) -> - [Attr, ": ", Val, "\r\n"] - end, HeadersOut), + H = lists:map(fun ({Attr, Val}) -> + [Attr, <<": ">>, Val, <<"\r\n">>] + end, + HeadersOut), NewReason = case Reason of - "" -> code_to_phrase(Status); - _ -> Reason - end, - SL = [Version, integer_to_list(Status), " ", - NewReason, "\r\n"], - + <<"">> -> code_to_phrase(Status); + _ -> Reason + end, + SL = [Version, + jlib:integer_to_binary(Status), <<" ">>, + NewReason, <<"\r\n">>], Data2 = case State#state.request_method of - 'HEAD' -> ""; - _ -> Data - end, - - [SL, H, "\r\n", Data2]. - + 'HEAD' -> <<"">>; + _ -> Data + end, + [SL, H, <<"\r\n">>, Data2]. parse_lang(Langs) -> - case string:tokens(Langs, ",; ") of - [First | _] -> - First; - [] -> - "en" + case str:tokens(Langs, <<",; ">>) of + [First | _] -> First; + [] -> <<"en">> end. -element_to_string(El) -> - case El of - {xmlelement, Name, Attrs, Els} -> - if - Els /= [] -> - [$<, Name, attrs_to_list(Attrs), $>, - [element_to_string(E) || E <- Els], - $<, $/, Name, $>]; - true -> - [$<, Name, attrs_to_list(Attrs), $/, $>] - end; - {xmlcdata, CData} -> - crypt(CData) - end. - -attrs_to_list(Attrs) -> - [attr_to_list(A) || A <- Attrs]. - -attr_to_list({Name, Value}) -> - [$\s, crypt(Name), $=, $", crypt(Value), $"]. - -crypt(S) when is_list(S) -> - [case C of - $& -> "&"; - $< -> "<"; - $> -> ">"; - $" -> """; - $' -> "'"; - _ -> C - end || C <- S]; -crypt(S) when is_binary(S) -> - crypt(binary_to_list(S)). - - % Code below is taken (with some modifications) from the yaws webserver, which % is distributed under the folowing license: % @@ -767,458 +680,204 @@ crypt(S) when is_binary(S) -> % 2. Redistributions in binary form must reproduce the above copyright % notice as well as this list of conditions. - -%% @doc Split the URL and return {Path, QueryPart} url_decode_q_split(Path) -> - url_decode_q_split(Path, []). -url_decode_q_split([$?|T], Ack) -> + url_decode_q_split(Path, <<>>). + +url_decode_q_split(<<$?, T/binary>>, Acc) -> %% Don't decode the query string here, that is parsed separately. - {path_norm_reverse(Ack), T}; -url_decode_q_split([H|T], Ack) when H /= 0 -> - url_decode_q_split(T, [H|Ack]); -url_decode_q_split([], Ack) -> - {path_norm_reverse(Ack), []}. + {path_norm_reverse(Acc), T}; +url_decode_q_split(<<H, T/binary>>, Acc) when H /= 0 -> + url_decode_q_split(T, <<H, Acc/binary>>); +url_decode_q_split(<<>>, Ack) -> + {path_norm_reverse(Ack), <<>>}. -%% @doc Decode a part of the URL and return string() -path_decode(Path) -> - path_decode(Path, []). -path_decode([$%, Hi, Lo | Tail], Ack) -> +path_decode(Path) -> path_decode(Path, <<>>). + +path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) -> Hex = hex_to_integer([Hi, Lo]), - if Hex == 0 -> exit(badurl); + if Hex == 0 -> exit(badurl); true -> ok end, - path_decode(Tail, [Hex|Ack]); -path_decode([H|T], Ack) when H /= 0 -> - path_decode(T, [H|Ack]); -path_decode([], Ack) -> - lists:reverse(Ack). - -path_norm_reverse("/" ++ T) -> start_dir(0, "/", T); -path_norm_reverse( T) -> start_dir(0, "", T). - -start_dir(N, Path, ".." ) -> rest_dir(N, Path, ""); -start_dir(N, Path, "/" ++ T ) -> start_dir(N , Path, T); -start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T); -start_dir(N, Path, "../" ++ T ) -> start_dir(N + 1, Path, T); -start_dir(N, Path, T ) -> rest_dir (N , Path, T). - -rest_dir (_N, Path, [] ) -> case Path of - [] -> "/"; - _ -> Path - end; -rest_dir (0, Path, [ $/ | T ] ) -> start_dir(0 , [ $/ | Path ], T); -rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T); -rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T); -rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T). + path_decode(Tail, <<Acc/binary, Hex>>); +path_decode(<<H, T/binary>>, Acc) when H /= 0 -> + path_decode(T, <<Acc/binary, H>>); +path_decode(<<>>, Acc) -> Acc. + +path_norm_reverse(<<"/", T/binary>>) -> start_dir(0, <<"/">>, T); +path_norm_reverse(T) -> start_dir(0, <<"">>, T). + +start_dir(N, Path, <<"..">>) -> rest_dir(N, Path, <<"">>); +start_dir(N, Path, <<"/", T/binary>>) -> start_dir(N, Path, T); +start_dir(N, Path, <<"./", T/binary>>) -> start_dir(N, Path, T); +start_dir(N, Path, <<"../", T/binary>>) -> + start_dir(N + 1, Path, T); +start_dir(N, Path, T) -> rest_dir(N, Path, T). + +rest_dir(_N, Path, <<>>) -> + case Path of + <<>> -> <<"/">>; + _ -> Path + end; +rest_dir(0, Path, <<$/, T/binary>>) -> + start_dir(0, <<$/, Path/binary>>, T); +rest_dir(N, Path, <<$/, T/binary>>) -> + start_dir(N - 1, Path, T); +rest_dir(0, Path, <<H, T/binary>>) -> + rest_dir(0, <<H, Path/binary>>, T); +rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T). %% hex_to_integer - hex_to_integer(Hex) -> - case catch erlang:list_to_integer(Hex, 16) of - {'EXIT', _} -> - old_hex_to_integer(Hex); - X -> - X + case catch list_to_integer(Hex, 16) of + {'EXIT', _} -> old_hex_to_integer(Hex); + X -> X end. - old_hex_to_integer(Hex) -> DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10; (H) when H >= $A, H =< $F -> H - $A + 10; (H) when H >= $0, H =< $9 -> H - $0 end, - lists:foldl(fun(E, Acc) -> Acc*16+DEHEX(E) end, 0, Hex). - -code_to_phrase(100) -> "Continue"; -code_to_phrase(101) -> "Switching Protocols "; -code_to_phrase(200) -> "OK"; -code_to_phrase(201) -> "Created"; -code_to_phrase(202) -> "Accepted"; -code_to_phrase(203) -> "Non-Authoritative Information"; -code_to_phrase(204) -> "No Content"; -code_to_phrase(205) -> "Reset Content"; -code_to_phrase(206) -> "Partial Content"; -code_to_phrase(300) -> "Multiple Choices"; -code_to_phrase(301) -> "Moved Permanently"; -code_to_phrase(302) -> "Found"; -code_to_phrase(303) -> "See Other"; -code_to_phrase(304) -> "Not Modified"; -code_to_phrase(305) -> "Use Proxy"; -code_to_phrase(306) -> "(Unused)"; -code_to_phrase(307) -> "Temporary Redirect"; -code_to_phrase(400) -> "Bad Request"; -code_to_phrase(401) -> "Unauthorized"; -code_to_phrase(402) -> "Payment Required"; -code_to_phrase(403) -> "Forbidden"; -code_to_phrase(404) -> "Not Found"; -code_to_phrase(405) -> "Method Not Allowed"; -code_to_phrase(406) -> "Not Acceptable"; -code_to_phrase(407) -> "Proxy Authentication Required"; -code_to_phrase(408) -> "Request Timeout"; -code_to_phrase(409) -> "Conflict"; -code_to_phrase(410) -> "Gone"; -code_to_phrase(411) -> "Length Required"; -code_to_phrase(412) -> "Precondition Failed"; -code_to_phrase(413) -> "Request Entity Too Large"; -code_to_phrase(414) -> "Request-URI Too Long"; -code_to_phrase(415) -> "Unsupported Media Type"; -code_to_phrase(416) -> "Requested Range Not Satisfiable"; -code_to_phrase(417) -> "Expectation Failed"; -code_to_phrase(500) -> "Internal Server Error"; -code_to_phrase(501) -> "Not Implemented"; -code_to_phrase(502) -> "Bad Gateway"; -code_to_phrase(503) -> "Service Unavailable"; -code_to_phrase(504) -> "Gateway Timeout"; -code_to_phrase(505) -> "HTTP Version Not Supported". - - -parse_auth(_Orig = "Basic " ++ Auth64) -> - case decode_base64(Auth64) of - {error, _Err} -> - undefined; - Auth -> - %% Auth should be a string with the format: user@server:password - %% Note that password can contain additional characters '@' and ':' - case string:chr(Auth, $:) of - 0 -> - undefined; - SplitIndex -> - {User, [$: | Pass]} = lists:split(SplitIndex-1, Auth), - {User, Pass} - end + lists:foldl(fun (E, Acc) -> Acc * 16 + DEHEX(E) end, 0, + Hex). + +code_to_phrase(100) -> <<"Continue">>; +code_to_phrase(101) -> <<"Switching Protocols ">>; +code_to_phrase(200) -> <<"OK">>; +code_to_phrase(201) -> <<"Created">>; +code_to_phrase(202) -> <<"Accepted">>; +code_to_phrase(203) -> + <<"Non-Authoritative Information">>; +code_to_phrase(204) -> <<"No Content">>; +code_to_phrase(205) -> <<"Reset Content">>; +code_to_phrase(206) -> <<"Partial Content">>; +code_to_phrase(300) -> <<"Multiple Choices">>; +code_to_phrase(301) -> <<"Moved Permanently">>; +code_to_phrase(302) -> <<"Found">>; +code_to_phrase(303) -> <<"See Other">>; +code_to_phrase(304) -> <<"Not Modified">>; +code_to_phrase(305) -> <<"Use Proxy">>; +code_to_phrase(306) -> <<"(Unused)">>; +code_to_phrase(307) -> <<"Temporary Redirect">>; +code_to_phrase(400) -> <<"Bad Request">>; +code_to_phrase(401) -> <<"Unauthorized">>; +code_to_phrase(402) -> <<"Payment Required">>; +code_to_phrase(403) -> <<"Forbidden">>; +code_to_phrase(404) -> <<"Not Found">>; +code_to_phrase(405) -> <<"Method Not Allowed">>; +code_to_phrase(406) -> <<"Not Acceptable">>; +code_to_phrase(407) -> + <<"Proxy Authentication Required">>; +code_to_phrase(408) -> <<"Request Timeout">>; +code_to_phrase(409) -> <<"Conflict">>; +code_to_phrase(410) -> <<"Gone">>; +code_to_phrase(411) -> <<"Length Required">>; +code_to_phrase(412) -> <<"Precondition Failed">>; +code_to_phrase(413) -> <<"Request Entity Too Large">>; +code_to_phrase(414) -> <<"Request-URI Too Long">>; +code_to_phrase(415) -> <<"Unsupported Media Type">>; +code_to_phrase(416) -> + <<"Requested Range Not Satisfiable">>; +code_to_phrase(417) -> <<"Expectation Failed">>; +code_to_phrase(500) -> <<"Internal Server Error">>; +code_to_phrase(501) -> <<"Not Implemented">>; +code_to_phrase(502) -> <<"Bad Gateway">>; +code_to_phrase(503) -> <<"Service Unavailable">>; +code_to_phrase(504) -> <<"Gateway Timeout">>; +code_to_phrase(505) -> <<"HTTP Version Not Supported">>. + +parse_auth(<<"Basic ", Auth64/binary>>) -> + Auth = jlib:decode_base64(Auth64), + %% Auth should be a string with the format: user@server:password + %% Note that password can contain additional characters '@' and ':' + case str:chr(Auth, $:) of + 0 -> + undefined; + Pos -> + {User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1), + {User, Pass} end; -parse_auth(_) -> - undefined. - - - -decode_base64([]) -> - []; -decode_base64([Sextet1,Sextet2,$=,$=|Rest]) -> - Bits2x6= - (d(Sextet1) bsl 18) bor - (d(Sextet2) bsl 12), - Octet1=Bits2x6 bsr 16, - [Octet1|decode_base64(Rest)]; -decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) -> - Bits3x6= - (d(Sextet1) bsl 18) bor - (d(Sextet2) bsl 12) bor - (d(Sextet3) bsl 6), - Octet1=Bits3x6 bsr 16, - Octet2=(Bits3x6 bsr 8) band 16#ff, - [Octet1,Octet2|decode_base64(Rest)]; -decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) -> - Bits4x6= - (d(Sextet1) bsl 18) bor - (d(Sextet2) bsl 12) bor - (d(Sextet3) bsl 6) bor - d(Sextet4), - Octet1=Bits4x6 bsr 16, - Octet2=(Bits4x6 bsr 8) band 16#ff, - Octet3=Bits4x6 band 16#ff, - [Octet1,Octet2,Octet3|decode_base64(Rest)]; -decode_base64(_CatchAll) -> - {error, bad_base64}. - -d(X) when X >= $A, X =<$Z -> - X-65; -d(X) when X >= $a, X =<$z -> - X-71; -d(X) when X >= $0, X =<$9 -> - X+4; -d($+) -> 62; -d($/) -> 63; -d(_) -> 63. - +parse_auth(<<_/binary>>) -> undefined. parse_urlencoded(S) -> - parse_urlencoded(S, nokey, [], key). + parse_urlencoded(S, nokey, <<>>, key). -parse_urlencoded([$%, Hi, Lo | Tail], Last, Cur, State) -> +parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur, + State) -> Hex = hex_to_integer([Hi, Lo]), - parse_urlencoded(Tail, Last, [Hex | Cur], State); - -parse_urlencoded([$& | Tail], _Last, Cur, key) -> - [{lists:reverse(Cur), ""} | - parse_urlencoded(Tail, nokey, [], key)]; %% cont keymode - -parse_urlencoded([$& | Tail], Last, Cur, value) -> - V = {Last, lists:reverse(Cur)}, - [V | parse_urlencoded(Tail, nokey, [], key)]; - -parse_urlencoded([$+ | Tail], Last, Cur, State) -> - parse_urlencoded(Tail, Last, [$\s | Cur], State); - -parse_urlencoded([$= | Tail], _Last, Cur, key) -> - parse_urlencoded(Tail, lists:reverse(Cur), [], value); %% change mode - -parse_urlencoded([H | Tail], Last, Cur, State) -> - parse_urlencoded(Tail, Last, [H|Cur], State); - -parse_urlencoded([], Last, Cur, _State) -> - [{Last, lists:reverse(Cur)}]; - -parse_urlencoded(undefined, _, _, _) -> - []. - - -url_encode([H|T]) -> - if - H >= $a, $z >= H -> - [H|url_encode(T)]; - H >= $A, $Z >= H -> - [H|url_encode(T)]; - H >= $0, $9 >= H -> - [H|url_encode(T)]; - H == $_; H == $.; H == $-; H == $/; H == $: -> % FIXME: more.. - [H|url_encode(T)]; - true -> - case integer_to_hex(H) of - [X, Y] -> - [$%, X, Y | url_encode(T)]; - [X] -> - [$%, $0, X | url_encode(T)] - end + parse_urlencoded(Tail, Last, <<Cur/binary, Hex>>, State); +parse_urlencoded(<<$&, Tail/binary>>, _Last, Cur, key) -> + [{Cur, <<"">>} | parse_urlencoded(Tail, + nokey, <<>>, + key)]; %% cont keymode +parse_urlencoded(<<$&, Tail/binary>>, Last, Cur, value) -> + V = {Last, Cur}, + [V | parse_urlencoded(Tail, nokey, <<>>, key)]; +parse_urlencoded(<<$+, Tail/binary>>, Last, Cur, State) -> + parse_urlencoded(Tail, Last, <<Cur/binary, $\s>>, State); +parse_urlencoded(<<$=, Tail/binary>>, _Last, Cur, key) -> + parse_urlencoded(Tail, Cur, <<>>, + value); %% change mode +parse_urlencoded(<<H, Tail/binary>>, Last, Cur, State) -> + parse_urlencoded(Tail, Last, <<Cur/binary, H>>, State); +parse_urlencoded(<<>>, Last, Cur, _State) -> + [{Last, Cur}]; +parse_urlencoded(undefined, _, _, _) -> []. + + +url_encode(A) -> + url_encode(A, <<>>). + +url_encode(<<H:8, T/binary>>, Acc) when + (H >= $a andalso H =< $z) orelse + (H >= $A andalso H =< $Z) orelse + (H >= $0 andalso H =< $9) orelse + H == $_ orelse + H == $. orelse + H == $- orelse + H == $/ orelse + H == $: -> + url_encode(T, <<Acc/binary, H>>); +url_encode(<<H:8, T/binary>>, Acc) -> + case integer_to_hex(H) of + [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>); + [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>) end; +url_encode(<<>>, Acc) -> + Acc. -url_encode([]) -> - []. integer_to_hex(I) -> case catch erlang:integer_to_list(I, 16) of - {'EXIT', _} -> - old_integer_to_hex(I); - Int -> - Int + {'EXIT', _} -> old_integer_to_hex(I); + Int -> Int end. - -old_integer_to_hex(I) when I<10 -> - integer_to_list(I); -old_integer_to_hex(I) when I<16 -> - [I-10+$A]; -old_integer_to_hex(I) when I>=16 -> - N = trunc(I/16), +old_integer_to_hex(I) when I < 10 -> integer_to_list(I); +old_integer_to_hex(I) when I < 16 -> [I - 10 + $A]; +old_integer_to_hex(I) when I >= 16 -> + N = trunc(I / 16), old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16). - % The following code is mostly taken from yaws_ssl.erl -extract_line(_, <<>>, _) -> - none; -extract_line(0, <<"\r", Rest/binary>>, Line) -> - extract_line(1, Rest, Line); -extract_line(0, <<A:8, Rest/binary>>, Line) -> - extract_line(0, Rest, <<Line/binary, A>>); -extract_line(1, <<"\n", Rest/binary>>, Line) -> - {Line, Rest}; -extract_line(1, Data, Line) -> - extract_line(0, Data, <<Line/binary, "\r">>). - -decode_packet(_, <<"\r\n", Rest/binary>>) -> - {ok, http_eoh, Rest}; -decode_packet(Type, Data) -> - case extract_line(0, Data, <<>>) of - {LineB, Rest} -> - Line = binary_to_list(LineB), - Result = case Type of - http -> - parse_req(Line); - httph -> - parse_header_line(Line) - end, - case Result of - {ok, H} -> - {ok, H, Rest}; - Err -> - {error, Err} - end; - _ -> - {more, undefined} - end. - -get_word(Line)-> - {Word, T} = lists:splitwith(fun(X)-> X /= $\ end, Line), - {Word, lists:dropwhile(fun(X) -> X == $\ end, T)}. +toupper(C) when C >= $a andalso C =< $z -> C - 32; +toupper(C) -> C. - -parse_req(Line) -> - {MethodStr, L1} = get_word(Line), - ?DEBUG("Method: ~p~n", [MethodStr]), - case L1 of - [] -> - bad_request; - _ -> - {URI, L2} = get_word(L1), - {VersionStr, L3} = get_word(L2), - ?DEBUG("URI: ~p~nVersion: ~p~nL3: ~p~n", - [URI, VersionStr, L3]), - case L3 of - [] -> - Method = case MethodStr of - "GET" -> 'GET'; - "POST" -> 'POST'; - "HEAD" -> 'HEAD'; - "OPTIONS" -> 'OPTIONS'; - "TRACE" -> 'TRACE'; - "PUT" -> 'PUT'; - "DELETE" -> 'DELETE'; - S -> S - end, - Path = case URI of - "*" -> - % Is this correct? - "*"; - _ -> - case string:str(URI, "://") of - 0 -> - % Relative URI - % ex: /index.html - {abs_path, URI}; - N -> - % Absolute URI - % ex: http://localhost/index.html - - % Remove scheme - % ex: URI2 = localhost/index.html - URI2 = string:substr(URI, N + 3), - % Look for the start of the path - % (or the lack of a path thereof) - case string:chr(URI2, $/) of - 0 -> {abs_path, "/"}; - M -> {abs_path, - string:substr(URI2, M + 1)} - end - end - end, - case VersionStr of - [] -> - {ok, {http_request, Method, Path, {0,9}}}; - "HTTP/1.0" -> - {ok, {http_request, Method, Path, {1,0}}}; - "HTTP/1.1" -> - {ok, {http_request, Method, Path, {1,1}}}; - _ -> - bad_request - end; - _ -> - bad_request - end - end. - - -toupper(C) when C >= $a andalso C =< $z -> - C - 32; -toupper(C) -> - C. - -tolower(C) when C >= $A andalso C =< $Z -> - C + 32; -tolower(C) -> - C. +tolower(C) when C >= $A andalso C =< $Z -> C + 32; +tolower(C) -> C. normalize_header_name(Name) -> - case parse_header_line(Name, "", true) of - {ok, RName, _} -> - lists:reverse(RName); - {eol, RName} -> - lists:reverse(RName) - end. - - -parse_header_line(Line) -> - case parse_header_line(Line, "", true) of - {ok, Name, Rest} -> - encode_header(lists:reverse(Name), Rest); - _ -> - bad_request - end. - - -parse_header_line("", Name, _) -> - {eol, Name}; -parse_header_line(":" ++ Rest, Name, _) -> - {ok, Name, Rest}; -parse_header_line("-" ++ Rest, Name, _) -> - parse_header_line(Rest, "-" ++ Name, true); -parse_header_line([C | Rest], Name, true) -> - parse_header_line(Rest, [toupper(C) | Name], false); -parse_header_line([C | Rest], Name, false) -> - parse_header_line(Rest, [tolower(C) | Name], false). - - -encode_header("Connection", Con) -> - {ok, {http_header, undefined, 'Connection', undefined, strip_spaces(Con)}}; -encode_header("Host", Con) -> - {ok, {http_header, undefined, 'Host', undefined, strip_spaces(Con)}}; -encode_header("Accept", Con) -> - {ok, {http_header, undefined, 'Accept', undefined, strip_spaces(Con)}}; -encode_header("If-Modified-Since", Con) -> - {ok, {http_header, undefined, 'If-Modified-Since', undefined, strip_spaces(Con)}}; -encode_header("If-Match", Con) -> - {ok, {http_header, undefined, 'If-Match', undefined, strip_spaces(Con)}}; -encode_header("If-None-Match", Con) -> - {ok, {http_header, undefined, 'If-None-Match', undefined, strip_spaces(Con)}}; -encode_header("If-Range", Con) -> - {ok, {http_header, undefined, 'If-Range', undefined, strip_spaces(Con)}}; -encode_header("If-Unmodified-Since", Con) -> - {ok, {http_header, undefined, 'If-Unmodified-Since', undefined, strip_spaces(Con)}}; -encode_header("Range", Con) -> - {ok, {http_header, undefined, 'Range', undefined, strip_spaces(Con)}}; -encode_header("User-Agent", Con) -> - {ok, {http_header, undefined, 'User-Agent', undefined, strip_spaces(Con)}}; -encode_header("Accept-Ranges", Con) -> - {ok, {http_header, undefined, 'Accept-Ranges', undefined, strip_spaces(Con)}}; -encode_header("Authorization", Con) -> - {ok, {http_header, undefined, 'Authorization', undefined, strip_spaces(Con)}}; -encode_header("Keep-Alive", Con) -> - {ok, {http_header, undefined, 'Keep-Alive', undefined, strip_spaces(Con)}}; -encode_header("Referer", Con) -> - {ok, {http_header, undefined, 'Referer', undefined, strip_spaces(Con)}}; -encode_header("Content-Type", Con) -> - {ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}}; -encode_header("Content-Length", Con) -> - {ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}}; -encode_header("Cookie", Con) -> - {ok, {http_header, undefined, 'Cookie', undefined, strip_spaces(Con)}}; -encode_header("Accept-Language", Con) -> - {ok, {http_header, undefined, 'Accept-Language', undefined, strip_spaces(Con)}}; -encode_header("Accept-Encoding", Con) -> - {ok, {http_header, undefined, 'Accept-Encoding', undefined, strip_spaces(Con)}}; -encode_header(Name, Val) -> - {ok, {http_header, undefined, Name, undefined, strip_spaces(Val)}}. - - -is_space($\s) -> - true; -is_space($\r) -> - true; -is_space($\n) -> - true; -is_space($\t) -> - true; -is_space(_) -> - false. - - -strip_spaces(String) -> - strip_spaces(String, both). - -%% strip_spaces(String, left) -> -%% drop_spaces(String); -strip_spaces(String, right) -> - lists:reverse(drop_spaces(lists:reverse(String))); -strip_spaces(String, both) -> - strip_spaces(drop_spaces(String), right). - -drop_spaces([]) -> - []; -drop_spaces(YS=[X|XS]) -> - case is_space(X) of - true -> - drop_spaces(XS); - false -> - YS - end. + normalize_header_name(Name, [], true). + +normalize_header_name(<<"">>, Acc, _) -> + iolist_to_binary(Acc); +normalize_header_name(<<"-", Rest/binary>>, Acc, _) -> + normalize_header_name(Rest, [Acc, "-"], true); +normalize_header_name(<<C:8, Rest/binary>>, Acc, true) -> + normalize_header_name(Rest, [Acc, toupper(C)], false); +normalize_header_name(<<C:8, Rest/binary>>, Acc, false) -> + normalize_header_name(Rest, [Acc, tolower(C)], false). + +%% -*- tab-width:8 diff --git a/src/web/ejabberd_http.hrl b/src/web/ejabberd_http.hrl index 5f1625ed5..51864f447 100644 --- a/src/web/ejabberd_http.hrl +++ b/src/web/ejabberd_http.hrl @@ -19,36 +19,39 @@ %%% %%%---------------------------------------------------------------------- --record(request, {method, - path, - q = [], - us, - auth, - lang = "", - data = "", - ip, - host, % string() - port, % integer() - tp, % transfer protocol = http | https - headers - }). +-record(request, + {method :: method(), + path = [] :: [binary()], + q = [] :: [{binary() | nokey, binary()}], + us = {<<>>, <<>>} :: {binary(), binary()}, + auth :: {binary(), binary()} | + {auth_jid, {binary(), binary()}, jlib:jid()}, + lang = <<"">> :: binary(), + data = <<"">> :: binary(), + ip :: {inet:ip_address(), inet:port_number()}, + host = <<"">> :: binary(), + port = 5280 :: inet:port_number(), + tp = http :: protocol(), + headers = [] :: [{atom() | binary(), binary()}]}). +-record(ws, + {socket :: inet:socket() | tls:tls_socket(), + sockmod = gen_tcp :: gen_tcp | tls, + ws_autoexit = false :: boolean(), + ip :: {inet:ip_address(), inet:port_number()}, + vsn :: vsn(), + origin = <<"">> :: binary(), + host = <<"">> :: binary(), + port = 5280 :: inet:port_number(), + path = [] :: [binary()], + headers = [] :: [{atom() | binary(), binary()}], + local_path = [] :: [binary()], + q = [] :: [{binary() | nokey, binary()}], + protocol :: binary(), + acceptable_origins = [] :: [binary()], + auth_module :: atom()}). -% Websocket Request --record(ws, { - socket, % the socket handling the request - sockmod, % gen_tcp | tls - ws_autoexit, % websocket process is automatically killed: true | false - ip, % peer IP | undefined - vsn, % {Maj,Min} | {'draft-hixie', Ver} - origin, % the originator - host, % the host - port, - path, % the websocket GET request path - headers, % [{Tag, Val}] - local_path, - q, - protocol, - acceptable_origins = [], - auth_module - }). +-type method() :: 'GET' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'PUT' | 'POST' | 'TRACE'. +-type protocol() :: http | https. +-type http_request() :: #request{}. +-type vsn() :: {'draft-hybi' | 'draft-hixie', non_neg_integer()}. diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index cbbe68c33..c8b688908 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -13,411 +13,373 @@ -behaviour(gen_fsm). %% External exports --export([start_link/4, - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - send/2, - send_xml/2, - sockname/1, - peername/1, - setopts/2, - controlling_process/2, - become_controller/2, - change_controller/2, - custom_receiver/1, - reset_stream/1, - change_shaper/2, - monitor/1, - close/1, - start/4, - handle_session_start/8, - handle_http_put/7, - http_put/7, - http_get/2, - prepare_response/4, - process_request/2]). +-export([start_link/4, init/1, handle_event/3, + handle_sync_event/4, code_change/4, handle_info/3, + terminate/3, send/2, send_xml/2, sockname/1, peername/1, + setopts/2, controlling_process/2, become_controller/2, + change_controller/2, custom_receiver/1, reset_stream/1, + change_shaper/2, monitor/1, close/1, start/4, + handle_session_start/8, handle_http_put/7, http_put/7, + http_get/2, prepare_response/4, process_request/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("http_bind.hrl"). --record(http_bind, {id, pid, to, hold, wait, process_delay, version}). +-record(http_bind, + {id, pid, to, hold, wait, process_delay, version}). -define(NULL_PEER, {{0, 0, 0, 0}, 0}). -%% http binding request --record(hbr, {rid, - key, - out}). - --record(state, {id, - rid = none, - key, - socket, - output = "", - input = queue:new(), - waiting_input = false, - shaper_state, - shaper_timer, - last_receiver, - last_poll, - http_receiver, - out_of_order_receiver = false, - wait_timer, - ctime = 0, - timer, - jid, - pause=0, - unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID} - req_list = [], % list of requests (cache) - max_inactivity, - max_pause, - ip = ?NULL_PEER - }). - -%% Internal request format: --record(http_put, {rid, - attrs, - payload, - payload_size, - hold, - stream, - ip}). +-record(hbr, {rid, key, out}). + +-record(state, +{ + id, + rid = none, + key, + socket, + output = [], + input = queue:new(), + waiting_input = false, + shaper_state, + shaper_timer, + last_receiver, + last_poll, + http_receiver, + out_of_order_receiver = false, + wait_timer, + ctime = 0, + timer, + jid, + pause = 0, + unprocessed_req_list = [], + req_list = [], + max_inactivity, + max_pause, + ip = ?NULL_PEER +}). + +-record(http_put, +{ + rid, + attrs, + payload, + payload_size, + hold, stream, + ip +}). %%-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. --define(BOSH_VERSION, "1.8"). --define(NS_CLIENT, "jabber:client"). --define(NS_BOSH, "urn:xmpp:xbosh"). --define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind"). - --define(MAX_REQUESTS, 2). % number of simultaneous requests --define(MIN_POLLING, 2000000). % don't poll faster than that or we will - % shoot you (time in microsec) --define(MAX_WAIT, 3600). % max num of secs to keep a request on hold --define(MAX_INACTIVITY, 30000). % msecs to wait before terminating - % idle sessions --define(MAX_PAUSE, 120). % may num of sec a client is allowed to pause - % the session - -%% Wait 100ms before continue processing, to allow the client provide more related stanzas. +-define(BOSH_VERSION, <<"1.8">>). + +-define(NS_CLIENT, <<"jabber:client">>). + +-define(NS_BOSH, <<"urn:xmpp:xbosh">>). + +-define(NS_HTTP_BIND, + <<"http://jabber.org/protocol/httpbind">>). + +-define(MAX_REQUESTS, 2). + +-define(MIN_POLLING, 2000000). + +-define(MAX_WAIT, 3600). + +-define(MAX_INACTIVITY, 30000). + +-define(MAX_PAUSE, 120). + -define(PROCESS_DELAY_DEFAULT, 100). + -define(PROCESS_DELAY_MIN, 0). + -define(PROCESS_DELAY_MAX, 1000). -%% Line copied from mod_http_bind.erl -define(PROCNAME_MHB, ejabberd_mod_http_bind). -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -%% TODO: If compile with no supervisor option, start the session without -%% supervisor start(XMPPDomain, Sid, Key, IP) -> ?DEBUG("Starting session", []), - SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME_MHB), - case catch supervisor:start_child(SupervisorProc, [XMPPDomain, Sid, Key, IP]) of - {ok, Pid} -> - {ok, Pid}; - {error, _} = Err -> - case check_bind_module(XMPPDomain) of - false -> - {error, "Cannot start HTTP bind session"}; - true -> - ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]), - Err - end; - Exit -> - ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]), - {error, Exit} + SupervisorProc = gen_mod:get_module_proc(XMPPDomain, + ?PROCNAME_MHB), + case catch supervisor:start_child(SupervisorProc, + [XMPPDomain, Sid, Key, IP]) + of + {ok, Pid} -> {ok, Pid}; + {error, _} = Err -> + case check_bind_module(XMPPDomain) of + false -> {error, <<"Cannot start HTTP bind session">>}; + true -> + ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]), + Err + end; + Exit -> + ?ERROR_MSG("Cannot start HTTP bind session: ~p", + [Exit]), + {error, Exit} end. start_link(ServerHost, Sid, Key, IP) -> - gen_fsm:start_link(?MODULE, [ServerHost, Sid, Key, IP], ?FSMOPTS). + gen_fsm:start_link(?MODULE, [ServerHost, Sid, Key, IP], + ?FSMOPTS). send({http_bind, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send, Packet}). send_xml({http_bind, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send_xml, Packet}). setopts({http_bind, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - case lists:member({active, false}, Opts) of - true -> - case catch gen_fsm:sync_send_all_state_event( - FsmRef, deactivate_socket) of - {'EXIT', _} -> - {error, einval}; - Res -> - Res - end; - _ -> - ok - end + true -> + gen_fsm:send_all_state_event(FsmRef, + {activate, self()}); + _ -> + case lists:member({active, false}, Opts) of + true -> + case catch gen_fsm:sync_send_all_state_event(FsmRef, + deactivate_socket) + of + {'EXIT', _} -> {error, einval}; + Res -> Res + end; + _ -> ok + end end. -controlling_process(_Socket, _Pid) -> - ok. +controlling_process(_Socket, _Pid) -> ok. custom_receiver({http_bind, FsmRef, _IP}) -> {receiver, ?MODULE, FsmRef}. become_controller(FsmRef, C2SPid) -> - gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). + gen_fsm:send_all_state_event(FsmRef, + {become_controller, C2SPid}). change_controller({http_bind, FsmRef, _IP}, C2SPid) -> become_controller(FsmRef, C2SPid). -reset_stream({http_bind, _FsmRef, _IP}) -> - ok. +reset_stream({http_bind, _FsmRef, _IP}) -> ok. change_shaper({http_bind, FsmRef, _IP}, Shaper) -> - gen_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}). + gen_fsm:send_all_state_event(FsmRef, + {change_shaper, Shaper}). monitor({http_bind, FsmRef, _IP}) -> erlang:monitor(process, FsmRef). close({http_bind, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}). + catch gen_fsm:sync_send_all_state_event(FsmRef, + {stop, close}). -sockname(_Socket) -> - {ok, ?NULL_PEER}. +sockname(_Socket) -> {ok, ?NULL_PEER}. -peername({http_bind, _FsmRef, IP}) -> - {ok, IP}. +peername({http_bind, _FsmRef, IP}) -> {ok, IP}. -%% Entry point for data coming from client through ejabberd HTTP server: process_request(Data, IP) -> Opts1 = ejabberd_c2s_config:get_c2s_limits(), Opts = [{xml_socket, true} | Opts1], - MaxStanzaSize = - case lists:keysearch(max_stanza_size, 1, Opts) of - {value, {_, Size}} -> Size; - _ -> infinity - end, + MaxStanzaSize = case lists:keysearch(max_stanza_size, 1, + Opts) + of + {value, {_, Size}} -> Size; + _ -> infinity + end, PayloadSize = iolist_size(Data), - case catch parse_request(Data, PayloadSize, MaxStanzaSize) of - %% No existing session: - {ok, {"", Rid, Attrs, Payload}} -> - case xml:get_attr_s("to",Attrs) of - "" -> - ?DEBUG("Session not created (Improper addressing)", []), - {200, ?HEADER, "<body type='terminate' " - "condition='improper-addressing' " - "xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"}; - XmppDomain -> - %% create new session - Sid = make_sid(), - case start(XmppDomain, Sid, "", IP) of - {error, _} -> - {500, ?HEADER, "<body type='terminate' " - "condition='internal-server-error' " - "xmlns='" ++ ?NS_HTTP_BIND ++ "'>Internal Server Error</body>"}; - {ok, Pid} -> - handle_session_start( - Pid, XmppDomain, Sid, Rid, Attrs, - Payload, PayloadSize, IP) - end - end; - %% Existing session - {ok, {Sid, Rid, Attrs, Payload1}} -> - StreamStart = - case xml:get_attr_s("xmpp:restart",Attrs) of - "true" -> - true; - _ -> - false - end, - Payload2 = case xml:get_attr_s("type",Attrs) of - "terminate" -> - %% close stream - Payload1 ++ [{xmlstreamend, "stream:stream"}]; - _ -> - Payload1 - end, - handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize, - StreamStart, IP); - {size_limit, Sid} -> - case get_session(Sid) of - {error, _} -> - {404, ?HEADER, ""}; - {ok, #http_bind{pid = FsmRef}} -> - gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}), - {200, ?HEADER, "<body type='terminate' " - "condition='undefined-condition' " - "xmlns='" ++ ?NS_HTTP_BIND ++ "'>Request Too Large</body>"} - end; - _ -> - ?DEBUG("Received bad request: ~p", [Data]), - {400, ?HEADER, ""} + case catch parse_request(Data, PayloadSize, + MaxStanzaSize) + of + %% No existing session: + {ok, {<<"">>, Rid, Attrs, Payload}} -> + case xml:get_attr_s(<<"to">>, Attrs) of + <<"">> -> + ?DEBUG("Session not created (Improper addressing)", []), + {200, ?HEADER, + <<"<body type='terminate' condition='improper-ad" + "dressing' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>}; + XmppDomain -> + Sid = make_sid(), + case start(XmppDomain, Sid, <<"">>, IP) of + {error, _} -> + {500, ?HEADER, + <<"<body type='terminate' condition='internal-se" + "rver-error' xmlns='", + (?NS_HTTP_BIND)/binary, + "'>Internal Server Error</body>">>}; + {ok, Pid} -> + handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, + Payload, PayloadSize, IP) + end + end; + %% Existing session + {ok, {Sid, Rid, Attrs, Payload1}} -> + StreamStart = case xml:get_attr_s(<<"xmpp:restart">>, + Attrs) + of + <<"true">> -> true; + _ -> false + end, + Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of + <<"terminate">> -> + Payload1 ++ [{xmlstreamend, <<"stream:stream">>}]; + _ -> Payload1 + end, + handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize, + StreamStart, IP); + {size_limit, Sid} -> + case get_session(Sid) of + {error, _} -> {404, ?HEADER, <<"">>}; + {ok, #http_bind{pid = FsmRef}} -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, close}), + {200, ?HEADER, + <<"<body type='terminate' condition='undefined-c" + "ondition' xmlns='", + (?NS_HTTP_BIND)/binary, "'>Request Too Large</body>">>} + end; + _ -> + ?DEBUG("Received bad request: ~p", [Data]), + {400, ?HEADER, <<"">>} end. handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, Payload, PayloadSize, IP) -> ?DEBUG("got pid: ~p", [Pid]), - Wait = case string:to_integer(xml:get_attr_s("wait",Attrs)) of - {error, _} -> - ?MAX_WAIT; - {CWait, _} -> - if - (CWait > ?MAX_WAIT) -> - ?MAX_WAIT; - true -> - CWait - end + Wait = case str:to_integer(xml:get_attr_s(<<"wait">>, + Attrs)) + of + {error, _} -> ?MAX_WAIT; + {CWait, _} -> + if CWait > (?MAX_WAIT) -> ?MAX_WAIT; + true -> CWait + end end, - Hold = case string:to_integer(xml:get_attr_s("hold",Attrs)) of - {error, _} -> - (?MAX_REQUESTS - 1); - {CHold, _} -> - if - (CHold > (?MAX_REQUESTS - 1)) -> - (?MAX_REQUESTS - 1); - true -> - CHold - end + Hold = case str:to_integer(xml:get_attr_s(<<"hold">>, + Attrs)) + of + {error, _} -> (?MAX_REQUESTS) - 1; + {CHold, _} -> + if CHold > (?MAX_REQUESTS) - 1 -> (?MAX_REQUESTS) - 1; + true -> CHold + end end, - Pdelay = case string:to_integer(xml:get_attr_s("process-delay",Attrs)) of - {error, _} -> - ?PROCESS_DELAY_DEFAULT; - {CPdelay, _} when - (?PROCESS_DELAY_MIN =< CPdelay) and - (CPdelay =< ?PROCESS_DELAY_MAX) -> - CPdelay; - {CPdelay, _} -> - lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]), ?PROCESS_DELAY_MIN]) + Pdelay = case + str:to_integer(xml:get_attr_s(<<"process-delay">>, + Attrs)) + of + {error, _} -> ?PROCESS_DELAY_DEFAULT; + {CPdelay, _} + when ((?PROCESS_DELAY_MIN) =< CPdelay) and + (CPdelay =< (?PROCESS_DELAY_MAX)) -> + CPdelay; + {CPdelay, _} -> + lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]), + ?PROCESS_DELAY_MIN]) end, - Version = - case catch list_to_float( - xml:get_attr_s("ver", Attrs)) of - {'EXIT', _} -> 0.0; - V -> V - end, - XmppVersion = xml:get_attr_s("xmpp:version", Attrs), + Version = case catch + list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs))) + of + {'EXIT', _} -> 0.0; + V -> V + end, + XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs), ?DEBUG("Create session: ~p", [Sid]), - mnesia:async_dirty( - fun() -> - mnesia:write( - #http_bind{id = Sid, - pid = Pid, - to = {XmppDomain, - XmppVersion}, - hold = Hold, - wait = Wait, - process_delay = Pdelay, - version = Version - }) - end), - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP). + mnesia:async_dirty(fun () -> + mnesia:write(#http_bind{id = Sid, pid = Pid, + to = + {XmppDomain, + XmppVersion}, + hold = Hold, wait = Wait, + process_delay = Pdelay, + version = Version}) + end), + handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + true, IP). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- init([ServerHost, Sid, Key, IP]) -> ?DEBUG("started: ~p", [{Sid, Key, IP}]), - - %% Read c2s options from the first ejabberd_c2s configuration in - %% the config file listen section - %% TODO: We should have different access and shaper values for - %% each connector. The default behaviour should be however to use - %% the default c2s restrictions if not defined for the current - %% connector. Opts1 = ejabberd_c2s_config:get_c2s_limits(), Opts = [{xml_socket, true} | Opts1], - Shaper = none, ShaperState = shaper:new(Shaper), Socket = {http_bind, self(), IP}, Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), - State = #state{id = Sid, - key = Key, - socket = Socket, - shaper_state = ShaperState, - max_inactivity = ?MAX_INACTIVITY, - max_pause = ?MAX_PAUSE, - timer = Timer}, - case gen_mod:get_module_opt(ServerHost, mod_http_bind, - prebind, false) of - true -> - JID = make_random_jid(ServerHost), - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, - [{jid, JID} | Opts]), - {ok, loop, State#state{jid = JID}}; - false -> - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), - {ok, loop, State} + State = #state{id = Sid, key = Key, socket = Socket, + shaper_state = ShaperState, + max_inactivity = ?MAX_INACTIVITY, + max_pause = ?MAX_PAUSE, timer = Timer}, + case gen_mod:get_module_opt(ServerHost, mod_http_bind, prebind, + fun(B) when is_boolean(B) -> B end, + false) + of + true -> + JID = make_random_jid(ServerHost), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + [{jid, JID} | Opts]), + {ok, loop, State#state{jid = JID}}; + false -> + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), + {ok, loop, State} end. -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({become_controller, C2SPid}, StateName, StateData) -> +handle_event({become_controller, C2SPid}, StateName, + StateData) -> erlang:monitor(process, C2SPid), case StateData#state.input of - cancel -> - {next_state, StateName, StateData#state{ - waiting_input = C2SPid}}; - Input -> - lists:foreach( - fun(Event) -> - C2SPid ! Event - end, queue:to_list(Input)), - {next_state, StateName, StateData#state{ - input = queue:new(), - waiting_input = C2SPid}} + cancel -> + {next_state, StateName, + StateData#state{waiting_input = C2SPid}}; + Input -> + lists:foreach(fun (Event) -> C2SPid ! Event end, + queue:to_list(Input)), + {next_state, StateName, + StateData#state{input = queue:new(), + waiting_input = C2SPid}} end; - -handle_event({change_shaper, Shaper}, StateName, StateData) -> +handle_event({change_shaper, Shaper}, StateName, + StateData) -> NewShaperState = shaper:new(Shaper), - {next_state, StateName, StateData#state{shaper_state = NewShaperState}}; + {next_state, StateName, + StateData#state{shaper_state = NewShaperState}}; handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- handle_sync_event({send_xml, Packet}, _From, StateName, #state{http_receiver = undefined} = StateData) -> Output = [Packet | StateData#state.output], Reply = ok, - {reply, Reply, StateName, StateData#state{output = Output}}; + {reply, Reply, StateName, + StateData#state{output = Output}}; handle_sync_event({send_xml, Packet}, _From, StateName, #state{out_of_order_receiver = true} = StateData) -> Output = [Packet | StateData#state.output], Reply = ok, - {reply, Reply, StateName, StateData#state{output = Output}}; -handle_sync_event({send_xml, Packet}, _From, StateName, StateData) -> + {reply, Reply, StateName, + StateData#state{output = Output}}; +handle_sync_event({send_xml, Packet}, _From, StateName, + StateData) -> Output = [Packet | StateData#state.output], cancel_timer(StateData#state.timer), Timer = set_inactivity_timer(StateData#state.pause, @@ -426,196 +388,161 @@ handle_sync_event({send_xml, Packet}, _From, StateName, StateData) -> gen_fsm:reply(StateData#state.http_receiver, HTTPReply), cancel_timer(StateData#state.wait_timer), Rid = StateData#state.rid, - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = Output - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = Output} + | [El + || El <- StateData#state.req_list, El#hbr.rid /= Rid]], Reply = ok, {reply, Reply, StateName, - StateData#state{output = [], - http_receiver = undefined, - req_list = ReqList, - wait_timer = undefined, + StateData#state{output = [], http_receiver = undefined, + req_list = ReqList, wait_timer = undefined, timer = Timer}}; - -handle_sync_event({stop,close}, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; -handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; -handle_sync_event(deactivate_socket, _From, StateName, StateData) -> - %% Input = case StateData#state.input of - %% cancel -> - %% queue:new(); - %% Q -> - %% Q - %% end, - {reply, ok, StateName, StateData#state{waiting_input = false}}; -handle_sync_event({stop,Reason}, _From, _StateName, StateData) -> - ?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]), +handle_sync_event({stop, close}, _From, _StateName, + StateData) -> + Reply = ok, {stop, normal, Reply, StateData}; +handle_sync_event({stop, stream_closed}, _From, + _StateName, StateData) -> + Reply = ok, {stop, normal, Reply, StateData}; +handle_sync_event(deactivate_socket, _From, StateName, + StateData) -> + {reply, ok, StateName, + StateData#state{waiting_input = false}}; +handle_sync_event({stop, Reason}, _From, _StateName, + StateData) -> + ?DEBUG("Closing bind session ~p - Reason: ~p", + [StateData#state.id, Reason]), Reply = ok, {stop, normal, Reply, StateData}; - %% HTTP PUT: Receive packets from the client -handle_sync_event(#http_put{rid = Rid}, - _From, StateName, StateData) - when StateData#state.shaper_timer /= undefined -> - Pause = - case erlang:read_timer(StateData#state.shaper_timer) of - false -> - 0; - P -> P - end, +handle_sync_event(#http_put{rid = Rid}, _From, + StateName, StateData) + when StateData#state.shaper_timer /= undefined -> + Pause = case + erlang:read_timer(StateData#state.shaper_timer) + of + false -> 0; + P -> P + end, Reply = {wait, Pause}, ?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]), {reply, Reply, StateName, StateData}; - -handle_sync_event(#http_put{payload_size = PayloadSize} = Request, +handle_sync_event(#http_put{payload_size = + PayloadSize} = + Request, _From, StateName, StateData) -> - ?DEBUG("New request: ~p",[Request]), - %% Updating trafic shaper + ?DEBUG("New request: ~p", [Request]), {NewShaperState, NewShaperTimer} = - update_shaper(StateData#state.shaper_state, PayloadSize), - + update_shaper(StateData#state.shaper_state, + PayloadSize), handle_http_put_event(Request, StateName, StateData#state{shaper_state = NewShaperState, shaper_timer = NewShaperTimer}); - %% HTTP GET: send packets to the client handle_sync_event({http_get, _Rid, _Wait, _Hold}, _From, StateName, #state{jid = JID} = StateData) - when JID /= undefined -> - %% This is a pre-bind state - {reply, {ok, {prebind, JID}}, StateName, StateData#state{jid = undefined}}; -handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> - %% setup timer + when JID /= undefined -> + {reply, {ok, {prebind, JID}}, StateName, + StateData#state{jid = undefined}}; +handle_sync_event({http_get, Rid, Wait, Hold}, From, + StateName, StateData) -> TNow = tnow(), - if - (Hold > 0) and - ((StateData#state.output == []) or (StateData#state.rid < Rid)) and - ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and - (StateData#state.rid =< Rid) and - (StateData#state.input /= cancel) and - (StateData#state.pause == 0) -> - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), - cancel_timer(StateData#state.wait_timer), - WaitTimer = erlang:start_timer(Wait * 1000, self(), []), - %% MR: Not sure we should cancel the state timer here. - cancel_timer(StateData#state.timer), - {next_state, StateName, StateData#state{ - http_receiver = From, - out_of_order_receiver = StateData#state.rid < Rid, - wait_timer = WaitTimer, - timer = undefined}}; - true -> - cancel_timer(StateData#state.timer), - Reply = {ok, StateData#state.output}, - %% save request - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = StateData#state.output - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], - if - (StateData#state.http_receiver /= undefined) and - StateData#state.out_of_order_receiver -> - {reply, Reply, StateName, StateData#state{ - output = [], - timer = undefined, - req_list = ReqList, - out_of_order_receiver = false}}; - true -> - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), - cancel_timer(StateData#state.wait_timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - {reply, Reply, StateName, - StateData#state{output = [], - http_receiver = undefined, - wait_timer = undefined, - timer = Timer, - req_list = ReqList}} - end + if (Hold > 0) and + ((StateData#state.output == []) or + (StateData#state.rid < Rid)) + and (TNow - StateData#state.ctime < Wait * 1000 * 1000) + and (StateData#state.rid =< Rid) + and (StateData#state.input /= cancel) + and (StateData#state.pause == 0) -> + send_receiver_reply(StateData#state.http_receiver, + {ok, empty}), + cancel_timer(StateData#state.wait_timer), + WaitTimer = erlang:start_timer(Wait * 1000, self(), []), + cancel_timer(StateData#state.timer), + {next_state, StateName, + StateData#state{http_receiver = From, + out_of_order_receiver = StateData#state.rid < Rid, + wait_timer = WaitTimer, timer = undefined}}; + true -> + cancel_timer(StateData#state.timer), + Reply = {ok, StateData#state.output}, + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = StateData#state.output} + | [El + || El <- StateData#state.req_list, El#hbr.rid /= Rid]], + if (StateData#state.http_receiver /= undefined) and + StateData#state.out_of_order_receiver -> + {reply, Reply, StateName, + StateData#state{output = [], timer = undefined, + req_list = ReqList, + out_of_order_receiver = false}}; + true -> + send_receiver_reply(StateData#state.http_receiver, + {ok, empty}), + cancel_timer(StateData#state.wait_timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + {reply, Reply, StateName, + StateData#state{output = [], http_receiver = undefined, + wait_timer = undefined, timer = Timer, + req_list = ReqList}} + end end; - -handle_sync_event(peername, _From, StateName, StateData) -> +handle_sync_event(peername, _From, StateName, + StateData) -> Reply = {ok, StateData#state.ip}, {reply, Reply, StateName, StateData}; - -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}. -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -%% We reached the max_inactivity timeout: handle_info({timeout, Timer, _}, _StateName, - #state{id=SID, timer = Timer} = StateData) -> - ?INFO_MSG("Session timeout. Closing the HTTP bind session: ~p", [SID]), + #state{id = SID, timer = Timer} = StateData) -> + ?INFO_MSG("Session timeout. Closing the HTTP bind " + "session: ~p", + [SID]), {stop, normal, StateData}; - handle_info({timeout, WaitTimer, _}, StateName, #state{wait_timer = WaitTimer} = StateData) -> - if - StateData#state.http_receiver /= undefined -> - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - gen_fsm:reply(StateData#state.http_receiver, {ok, empty}), - Rid = StateData#state.rid, - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = [] - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], - {next_state, StateName, - StateData#state{http_receiver = undefined, - req_list = ReqList, - wait_timer = undefined, - timer = Timer}}; - true -> - {next_state, StateName, StateData} + if StateData#state.http_receiver /= undefined -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + gen_fsm:reply(StateData#state.http_receiver, + {ok, empty}), + Rid = StateData#state.rid, + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = []} + | [El + || El <- StateData#state.req_list, El#hbr.rid /= Rid]], + {next_state, StateName, + StateData#state{http_receiver = undefined, + req_list = ReqList, wait_timer = undefined, + timer = Timer}}; + true -> {next_state, StateName, StateData} end; - handle_info({timeout, ShaperTimer, _}, StateName, #state{shaper_timer = ShaperTimer} = StateData) -> - {next_state, StateName, StateData#state{shaper_timer = undefined}}; - -handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName, + {next_state, StateName, + StateData#state{shaper_timer = undefined}}; +handle_info({'DOWN', _MRef, process, C2SPid, _}, + _StateName, #state{waiting_input = C2SPid} = StateData) -> {stop, normal, StateData}; handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate(_Reason, _StateName, StateData) -> - ?DEBUG("terminate: Deleting session ~s", [StateData#state.id]), + ?DEBUG("terminate: Deleting session ~s", + [StateData#state.id]), mnesia:dirty_delete({http_bind, StateData#state.id}), - send_receiver_reply(StateData#state.http_receiver, {ok, terminate}), + send_receiver_reply(StateData#state.http_receiver, + {ok, terminate}), case StateData#state.waiting_input of - false -> - ok; - C2SPid -> - gen_fsm:send_event(C2SPid, closed) + false -> ok; + C2SPid -> gen_fsm:send_event(C2SPid, closed) end, ok. @@ -623,738 +550,734 @@ terminate(_Reason, _StateName, StateData) -> %%% Internal functions %%%---------------------------------------------------------------------- -%% PUT / Get processing: -handle_http_put_event(#http_put{rid = Rid, attrs = Attrs, - hold = Hold} = Request, +handle_http_put_event(#http_put{rid = Rid, + attrs = Attrs, hold = Hold} = + Request, StateName, StateData) -> - ?DEBUG("New request: ~p",[Request]), - %% Check if Rid valid - RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, Hold, - StateData#state.max_pause), - - %% Check if Rid is in sequence or out of sequence: + ?DEBUG("New request: ~p", [Request]), + RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, + Hold, StateData#state.max_pause), case RidAllow of - buffer -> - ?DEBUG("Buffered request: ~p", [Request]), - %% Request is out of sequence: - PendingRequests = StateData#state.unprocessed_req_list, - %% In case an existing RID was already buffered: - Requests = lists:keydelete(Rid, 2, PendingRequests), - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = [] - } | - [El || El <- StateData#state.req_list, - El#hbr.rid > (Rid - 1 - Hold)] - ], - ?DEBUG("reqlist: ~p", [ReqList]), - UnprocessedReqList = [Request | Requests], - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(0, StateData#state.max_inactivity), - {reply, ok, StateName, - StateData#state{unprocessed_req_list = UnprocessedReqList, - req_list = ReqList, - timer = Timer}, hibernate}; - - _ -> - %% Request is in sequence: - process_http_put(Request, StateName, StateData, RidAllow) + buffer -> + ?DEBUG("Buffered request: ~p", [Request]), + PendingRequests = StateData#state.unprocessed_req_list, + Requests = lists:keydelete(Rid, 2, PendingRequests), + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = []} + | [El + || El <- StateData#state.req_list, + El#hbr.rid > Rid - 1 - Hold]], + ?DEBUG("reqlist: ~p", [ReqList]), + UnprocessedReqList = [Request | Requests], + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(0, + StateData#state.max_inactivity), + {reply, ok, StateName, + StateData#state{unprocessed_req_list = + UnprocessedReqList, + req_list = ReqList, timer = Timer}, + hibernate}; + _ -> + process_http_put(Request, StateName, StateData, + RidAllow) end. -process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload, - hold = Hold, stream = StreamTo, - ip = IP} = Request, +process_http_put(#http_put{rid = Rid, attrs = Attrs, + payload = Payload, hold = Hold, stream = StreamTo, + ip = IP} = + Request, StateName, StateData, RidAllow) -> ?DEBUG("Actually processing request: ~p", [Request]), - %% Check if key valid - Key = xml:get_attr_s("key", Attrs), - NewKey = xml:get_attr_s("newkey", Attrs), - KeyAllow = - case RidAllow of - repeat -> - true; - false -> - false; - {true, _} -> - case StateData#state.key of - "" -> - true; - OldKey -> - NextKey = sha:sha(Key), - ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", [Key, OldKey, NextKey]), - if - OldKey == NextKey -> - true; - true -> - ?DEBUG("wrong key: ~s",[Key]), - false - end - end - end, + Key = xml:get_attr_s(<<"key">>, Attrs), + NewKey = xml:get_attr_s(<<"newkey">>, Attrs), + KeyAllow = case RidAllow of + repeat -> true; + false -> false; + {true, _} -> + case StateData#state.key of + <<"">> -> true; + OldKey -> + NextKey = sha:sha(Key), + ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", + [Key, OldKey, NextKey]), + if OldKey == NextKey -> true; + true -> ?DEBUG("wrong key: ~s", [Key]), false + end + end + end, TNow = tnow(), - LastPoll = if - Payload == [] -> - TNow; - true -> - 0 + LastPoll = if Payload == [] -> TNow; + true -> 0 end, - if - (Payload == []) and - (Hold == 0) and - (TNow - StateData#state.last_poll < ?MIN_POLLING) -> - Reply = {error, polling_too_frequently}, - {reply, Reply, StateName, StateData}; - KeyAllow -> - case RidAllow of - false -> - Reply = {error, not_exists}, - {reply, Reply, StateName, StateData}; - repeat -> - ?DEBUG("REPEATING ~p", [Rid]), - case [El#hbr.out || - El <- StateData#state.req_list, - El#hbr.rid == Rid] of - [] -> - {error, not_exists}; - [Out | _XS] -> - if (Rid == StateData#state.rid) and - (StateData#state.http_receiver /= undefined) -> - {reply, ok, StateName, StateData}; - true -> - Reply = {repeat, lists:reverse(Out)}, - {reply, Reply, StateName, StateData#state{last_poll = LastPoll}} - end - end; - {true, Pause} -> - SaveKey = if - NewKey == "" -> - Key; - true -> - NewKey - end, - ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), - - %% save request - ReqList1 = - [El || El <- StateData#state.req_list, - El#hbr.rid > (Rid - 1 - Hold)], - ReqList = - case lists:keymember(Rid, #hbr.rid, ReqList1) of - true -> - ReqList1; - false -> - [#hbr{rid = Rid, - key = StateData#state.key, - out = [] - } | - ReqList1 - ] - end, - ?DEBUG("reqlist: ~p", [ReqList]), - - %% setup next timer - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(Pause, - StateData#state.max_inactivity), - case StateData#state.waiting_input of - false -> - Input = - lists:foldl( - fun queue:in/2, - StateData#state.input, Payload), - Reply = ok, - process_buffered_request(Reply, StateName, - StateData#state{input = Input, - rid = Rid, - key = SaveKey, - ctime = TNow, - timer = Timer, - pause = Pause, - last_poll = LastPoll, - req_list = ReqList, - ip = IP - }); - C2SPid -> - JID = StateData#state.jid, - case StreamTo of - {To, ""} when JID == undefined -> - gen_fsm:send_event( - C2SPid, - {xmlstreamstart, "stream:stream", - [{"to", To}, - {"xmlns", ?NS_CLIENT}, - {"xmlns:stream", ?NS_STREAM}]}); - {To, Version} when JID == undefined -> - gen_fsm:send_event( - C2SPid, - {xmlstreamstart, "stream:stream", - [{"to", To}, - {"xmlns", ?NS_CLIENT}, - {"version", Version}, - {"xmlns:stream", ?NS_STREAM}]}); - _ -> - ok - end, - - MaxInactivity = get_max_inactivity(StreamTo, StateData#state.max_inactivity), - MaxPause = get_max_inactivity(StreamTo, StateData#state.max_pause), - - ?DEBUG("really sending now: ~p", [Payload]), - lists:foreach( - fun({xmlstreamend, End}) -> - gen_fsm:send_event( - C2SPid, {xmlstreamend, End}); - (El) -> - gen_fsm:send_event( - C2SPid, {xmlstreamelement, El}) - end, Payload), - Reply = ok, - process_buffered_request(Reply, StateName, - StateData#state{input = queue:new(), - rid = Rid, - key = SaveKey, - ctime = TNow, - timer = Timer, - pause = Pause, - last_poll = LastPoll, - req_list = ReqList, - max_inactivity = MaxInactivity, - max_pause = MaxPause, - ip = IP - }) - end - end; - true -> - Reply = {error, bad_key}, - {reply, Reply, StateName, StateData} + if (Payload == []) and (Hold == 0) and + (TNow - StateData#state.last_poll < (?MIN_POLLING)) -> + Reply = {error, polling_too_frequently}, + {reply, Reply, StateName, StateData}; + KeyAllow -> + case RidAllow of + false -> + Reply = {error, not_exists}, + {reply, Reply, StateName, StateData}; + repeat -> + ?DEBUG("REPEATING ~p", [Rid]), + case [El#hbr.out + || El <- StateData#state.req_list, El#hbr.rid == Rid] + of + [] -> {error, not_exists}; + [Out | _XS] -> + if (Rid == StateData#state.rid) and + (StateData#state.http_receiver /= undefined) -> + {reply, ok, StateName, StateData}; + true -> + Reply = {repeat, lists:reverse(Out)}, + {reply, Reply, StateName, + StateData#state{last_poll = LastPoll}} + end + end; + {true, Pause} -> + SaveKey = if NewKey == <<"">> -> Key; + true -> NewKey + end, + ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), + ReqList1 = [El + || El <- StateData#state.req_list, + El#hbr.rid > Rid - 1 - Hold], + ReqList = case lists:keymember(Rid, #hbr.rid, ReqList1) + of + true -> ReqList1; + false -> + [#hbr{rid = Rid, key = StateData#state.key, + out = []} + | ReqList1] + end, + ?DEBUG("reqlist: ~p", [ReqList]), + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(Pause, + StateData#state.max_inactivity), + case StateData#state.waiting_input of + false -> + Input = lists:foldl(fun queue:in/2, + StateData#state.input, Payload), + Reply = ok, + process_buffered_request(Reply, StateName, + StateData#state{input = Input, + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + pause = Pause, + last_poll = + LastPoll, + req_list = + ReqList, + ip = IP}); + C2SPid -> + JID = StateData#state.jid, + case StreamTo of + {To, <<"">>} when JID == undefined -> + gen_fsm:send_event(C2SPid, + {xmlstreamstart, + <<"stream:stream">>, + [{<<"to">>, To}, + {<<"xmlns">>, ?NS_CLIENT}, + {<<"xmlns:stream">>, + ?NS_STREAM}]}); + {To, Version} when JID == undefined -> + gen_fsm:send_event(C2SPid, + {xmlstreamstart, + <<"stream:stream">>, + [{<<"to">>, To}, + {<<"xmlns">>, ?NS_CLIENT}, + {<<"version">>, Version}, + {<<"xmlns:stream">>, + ?NS_STREAM}]}); + _ -> ok + end, + MaxInactivity = get_max_inactivity(StreamTo, + StateData#state.max_inactivity), + MaxPause = get_max_inactivity(StreamTo, + StateData#state.max_pause), + ?DEBUG("really sending now: ~p", [Payload]), + lists:foreach(fun ({xmlstreamend, End}) -> + gen_fsm:send_event(C2SPid, + {xmlstreamend, + End}); + (El) -> + gen_fsm:send_event(C2SPid, + {xmlstreamelement, + El}) + end, + Payload), + Reply = ok, + process_buffered_request(Reply, StateName, + StateData#state{input = + queue:new(), + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + pause = Pause, + last_poll = + LastPoll, + req_list = + ReqList, + max_inactivity = + MaxInactivity, + max_pause = + MaxPause, + ip = IP}) + end + end; + true -> + Reply = {error, bad_key}, + {reply, Reply, StateName, StateData} end. process_buffered_request(Reply, StateName, StateData) -> Rid = StateData#state.rid, Requests = StateData#state.unprocessed_req_list, - case lists:keysearch(Rid+1, 2, Requests) of - {value, Request} -> - ?DEBUG("Processing buffered request: ~p", [Request]), - NewRequests = lists:keydelete(Rid+1, 2, Requests), - handle_http_put_event( - Request, StateName, - StateData#state{unprocessed_req_list = NewRequests}); - _ -> - {reply, Reply, StateName, StateData, hibernate} + case lists:keysearch(Rid + 1, 2, Requests) of + {value, Request} -> + ?DEBUG("Processing buffered request: ~p", [Request]), + NewRequests = lists:keydelete(Rid + 1, 2, Requests), + handle_http_put_event(Request, StateName, + StateData#state{unprocessed_req_list = + NewRequests}); + _ -> {reply, Reply, StateName, StateData, hibernate} end. -handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> - case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of - {error, not_exists} -> - ?DEBUG("no session associated with sid: ~p", [Sid]), - {404, ?HEADER, ""}; - {{error, Reason}, Sess} -> - ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]), - handle_http_put_error(Reason, Sess); - {{repeat, OutPacket}, Sess} -> - ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", [OutPacket]), - send_outpacket(Sess, OutPacket); - {{wait, Pause}, _Sess} -> - ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), - timer:sleep(Pause), - %{200, ?HEADER, - % xml:element_to_binary( - % {xmlelement, "body", - % [{"xmlns", ?NS_HTTP_BIND}, - % {"type", "error"}], []})}; - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, - StreamStart, IP); - {ok, Sess} -> - prepare_response(Sess, Rid, [], StreamStart) +handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP) -> + case http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP) + of + {error, not_exists} -> + ?DEBUG("no session associated with sid: ~p", [Sid]), + {404, ?HEADER, <<"">>}; + {{error, Reason}, Sess} -> + ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]), + handle_http_put_error(Reason, Sess); + {{repeat, OutPacket}, Sess} -> + ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", + [OutPacket]), + send_outpacket(Sess, OutPacket); + {{wait, Pause}, _Sess} -> + ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), + timer:sleep(Pause), + handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP); + {ok, Sess} -> + prepare_response(Sess, Rid, [], StreamStart) end. -http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> +http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP) -> ?DEBUG("Looking for session: ~p", [Sid]), case get_session(Sid) of - {error, _} -> - {error, not_exists}; - {ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}-> - NewStream = - case StreamStart of - true -> - {To, StreamVersion}; - _ -> - "" - end, - case catch {gen_fsm:sync_send_all_state_event( - FsmRef, - #http_put{rid = Rid, attrs = Attrs, payload = Payload, - payload_size = PayloadSize, hold = Hold, - stream = NewStream, ip = IP}, 30000), Sess} of - {'EXIT', _} -> - {error, not_exists}; - Res -> - Res - end + {error, _} -> {error, not_exists}; + {ok, + #http_bind{pid = FsmRef, hold = Hold, + to = {To, StreamVersion}} = + Sess} -> + NewStream = case StreamStart of + true -> {To, StreamVersion}; + _ -> <<"">> + end, + case catch {gen_fsm:sync_send_all_state_event(FsmRef, + #http_put{rid = Rid, + attrs = Attrs, + payload = + Payload, + payload_size = + PayloadSize, + hold = Hold, + stream = + NewStream, + ip = IP}, + 30000), + Sess} + of + {'EXIT', _} -> {error, not_exists}; + Res -> Res + end end. -handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version}) - when Version >= 0 -> - gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}), +handle_http_put_error(Reason, + #http_bind{pid = FsmRef, version = Version}) + when Version >= 0 -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, {put_error, Reason}}), case Reason of - not_exists -> - {200, ?HEADER, - xml:element_to_binary( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, - {"type", "terminate"}, - {"condition", "item-not-found"}], []})}; - bad_key -> - {200, ?HEADER, - xml:element_to_binary( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, - {"type", "terminate"}, - {"condition", "item-not-found"}], []})}; - polling_too_frequently -> - {200, ?HEADER, - xml:element_to_binary( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, - {"type", "terminate"}, - {"condition", "policy-violation"}], []})} + not_exists -> + {200, ?HEADER, + xml:element_to_binary(#xmlel{name = <<"body">>, + attrs = + [{<<"xmlns">>, ?NS_HTTP_BIND}, + {<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"item-not-found">>}], + children = []})}; + bad_key -> + {200, ?HEADER, + xml:element_to_binary(#xmlel{name = <<"body">>, + attrs = + [{<<"xmlns">>, ?NS_HTTP_BIND}, + {<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"item-not-found">>}], + children = []})}; + polling_too_frequently -> + {200, ?HEADER, + xml:element_to_binary(#xmlel{name = <<"body">>, + attrs = + [{<<"xmlns">>, ?NS_HTTP_BIND}, + {<<"type">>, <<"terminate">>}, + {<<"condition">>, + <<"policy-violation">>}], + children = []})} end; -handle_http_put_error(Reason, #http_bind{pid=FsmRef}) -> - gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}), +handle_http_put_error(Reason, + #http_bind{pid = FsmRef}) -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, {put_error_no_version, Reason}}), case Reason of - not_exists -> %% bad rid - ?DEBUG("Closing HTTP bind session (Bad rid).", []), - {404, ?HEADER, ""}; - bad_key -> - ?DEBUG("Closing HTTP bind session (Bad key).", []), - {404, ?HEADER, ""}; - polling_too_frequently -> - ?DEBUG("Closing HTTP bind session (User polling too frequently).", []), - {403, ?HEADER, ""} + not_exists -> %% bad rid + ?DEBUG("Closing HTTP bind session (Bad rid).", []), + {404, ?HEADER, <<"">>}; + bad_key -> + ?DEBUG("Closing HTTP bind session (Bad key).", []), + {404, ?HEADER, <<"">>}; + polling_too_frequently -> + ?DEBUG("Closing HTTP bind session (User polling " + "too frequently).", + []), + {403, ?HEADER, <<"">>} end. -%% Control RID ordering rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) -> - %% First request - nothing saved so far {true, 0}; rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) -> - ?DEBUG("Previous rid / New rid: ~p/~p", [OldRid, NewRid]), + ?DEBUG("Previous rid / New rid: ~p/~p", + [OldRid, NewRid]), if - %% We did not miss any packet, we can process it immediately: - NewRid == OldRid + 1 -> - case catch list_to_integer( - xml:get_attr_s("pause", Attrs)) of - {'EXIT', _} -> - {true, 0}; - Pause1 when Pause1 =< MaxPause -> - ?DEBUG("got pause: ~p", [Pause1]), - {true, Pause1}; - _ -> - {true, 0} - end; - %% We have missed packets, we need to cached it to process it later on: - (OldRid < NewRid) and - (NewRid =< (OldRid + Hold + 1)) -> - buffer; - (NewRid =< OldRid) and - (NewRid > OldRid - Hold - 1) -> - repeat; - true -> - false + %% We did not miss any packet, we can process it immediately: + NewRid == OldRid + 1 -> + case catch + jlib:binary_to_integer(xml:get_attr_s(<<"pause">>, + Attrs)) + of + {'EXIT', _} -> {true, 0}; + Pause1 when Pause1 =< MaxPause -> + ?DEBUG("got pause: ~p", [Pause1]), {true, Pause1}; + _ -> {true, 0} + end; + %% We have missed packets, we need to cached it to process it later on: + (OldRid < NewRid) and (NewRid =< OldRid + Hold + 1) -> + buffer; + (NewRid =< OldRid) and (NewRid > OldRid - Hold - 1) -> + repeat; + true -> false end. update_shaper(ShaperState, PayloadSize) -> - {NewShaperState, Pause} = shaper:update(ShaperState, PayloadSize), - if - Pause > 0 -> - ShaperTimer = erlang:start_timer(Pause, self(), activate), %% MR: Seems timer is not needed. Activate is not handled - {NewShaperState, ShaperTimer}; - true -> - {NewShaperState, undefined} + {NewShaperState, Pause} = shaper:update(ShaperState, + PayloadSize), + if Pause > 0 -> + ShaperTimer = erlang:start_timer(Pause, self(), + activate), + {NewShaperState, ShaperTimer}; + true -> {NewShaperState, undefined} end. prepare_response(Sess, Rid, OutputEls, StreamStart) -> - receive after Sess#http_bind.process_delay -> ok end, + receive after Sess#http_bind.process_delay -> ok end, case catch http_get(Sess, Rid) of - {ok, cancel} -> - %% actually it would be better if we could completely - %% cancel this request, but then we would have to hack - %% ejabberd_http and I'm too lazy now - {200, ?HEADER, "<body type='error' xmlns='"++?NS_HTTP_BIND++"'/>"}; - {ok, empty} -> - {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"}; - {ok, terminate} -> - {200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"}; - {ok, {prebind, JID}} -> - {200, ?HEADER, - xml:element_to_string( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, {"sid", Sess#http_bind.id}, - {"rid", integer_to_list(Rid + 1)}], - [{xmlelement, "iq", [{"id", "pre_bind"}, {"type", "result"}], - [{xmlelement, "bind", [{"xmlns", ?NS_BIND}], - [{xmlelement, "jid", [], - [{xmlcdata, jlib:jid_to_string(JID)}]}]}]}]})}; - {ok, ROutPacket} -> - OutPacket = lists:reverse(ROutPacket), - ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]), - prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart); - {'EXIT', {shutdown, _}} -> - {200, ?HEADER, "<body type='terminate' condition='system-shutdown' xmlns='"++?NS_HTTP_BIND++"'/>"}; - {'EXIT', _Reason} -> - {200, ?HEADER, "<body type='terminate' xmlns='"++?NS_HTTP_BIND++"'/>"} + {ok, cancel} -> + {200, ?HEADER, + <<"<body type='error' xmlns='", (?NS_HTTP_BIND)/binary, + "'/>">>}; + {ok, empty} -> + {200, ?HEADER, + <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>}; + {ok, terminate} -> + {200, ?HEADER, + <<"<body type='terminate' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>}; + {ok, {prebind, JID}} -> + {200, ?HEADER, + xml:element_to_binary(#xmlel{name = <<"body">>, + attrs = + [{<<"xmlns">>, ?NS_HTTP_BIND}, + {<<"sid">>, Sess#http_bind.id}, + {<<"rid">>, + iolist_to_binary(integer_to_list(Rid + + + 1))}], + children = + [#xmlel{name = <<"iq">>, + attrs = + [{<<"id">>, + <<"pre_bind">>}, + {<<"type">>, + <<"result">>}], + children = + [#xmlel{name = + <<"bind">>, + attrs = + [{<<"xmlns">>, + ?NS_BIND}], + children = + [#xmlel{name + = + <<"jid">>, + attrs + = + [], + children + = + [{xmlcdata, + jlib:jid_to_string(JID)}]}]}]}]})}; + {ok, ROutPacket} -> + OutPacket = lists:reverse(ROutPacket), + ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]), + prepare_outpacket_response(Sess, Rid, + OutputEls ++ OutPacket, StreamStart); + {'EXIT', {shutdown, _}} -> + {200, ?HEADER, + <<"<body type='terminate' condition='system-shut" + "down' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>}; + {'EXIT', _Reason} -> + {200, ?HEADER, + <<"<body type='terminate' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>} end. -%% Send output payloads on establised sessions -prepare_outpacket_response(Sess, _Rid, OutPacket, false) -> +prepare_outpacket_response(Sess, _Rid, OutPacket, + false) -> case catch send_outpacket(Sess, OutPacket) of - {'EXIT', _Reason} -> - ?DEBUG("Error in sending packet ~p ", [_Reason]), - {200, ?HEADER, - "<body type='terminate' xmlns='"++ - ?NS_HTTP_BIND++"'/>"}; - SendRes -> - SendRes + {'EXIT', _Reason} -> + ?DEBUG("Error in sending packet ~p ", [_Reason]), + {200, ?HEADER, + <<"<body type='terminate' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>}; + SendRes -> SendRes end; %% Handle a new session along with its output payload -prepare_outpacket_response(#http_bind{id=Sid, wait=Wait, - hold=Hold, to=To}=_Sess, - _Rid, OutPacket, true) -> +prepare_outpacket_response(#http_bind{id = Sid, + wait = Wait, hold = Hold, to = To} = + _Sess, + _Rid, OutPacket, true) -> case OutPacket of - [{xmlstreamstart, _, OutAttrs} | Els] -> - AuthID = xml:get_attr_s("id", OutAttrs), - From = xml:get_attr_s("from", OutAttrs), - Version = xml:get_attr_s("version", OutAttrs), - OutEls = - case Els of - [] -> - []; - [{xmlstreamelement, - {xmlelement, "stream:features", - StreamAttribs, StreamEls}} - | StreamTail] -> - TypedTail = - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail], - [{xmlelement, "stream:features", - [{"xmlns:stream", - ?NS_STREAM}] ++ - StreamAttribs, StreamEls}] ++ - TypedTail; - StreamTail -> - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail] - end, - case OutEls of - [{xmlelement, - "stream:error",_,_}] -> - {200, ?HEADER, "<body type='terminate' " - "condition='host-unknown' " - "xmlns='"++?NS_HTTP_BIND++"'/>"}; - _ -> - BOSH_attribs = - [{"authid", AuthID}, - {"xmlns:xmpp", ?NS_BOSH}, - {"xmlns:stream", ?NS_STREAM}] ++ - case OutEls of - [] -> - []; - _ -> - [{"xmpp:version", Version}] - end, - MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), - MaxPause = get_max_pause(To), - {200, ?HEADER, - xml:element_to_binary( - {xmlelement,"body", - [{"xmlns", - ?NS_HTTP_BIND}, - {"sid", Sid}, - {"wait", integer_to_list(Wait)}, - {"requests", integer_to_list(Hold+1)}, - {"inactivity", - integer_to_list( - trunc(MaxInactivity/1000))}, - {"maxpause", - integer_to_list(MaxPause)}, - {"polling", - integer_to_list( - trunc(?MIN_POLLING/1000000))}, - {"ver", ?BOSH_VERSION}, - {"from", From}, - {"secure", "true"} %% we're always being secure - ] ++ BOSH_attribs,OutEls})} - end; - _ -> - {200, ?HEADER, "<body type='terminate' " - "condition='internal-server-error' " - "xmlns='"++?NS_HTTP_BIND++"'/>"} + [{xmlstreamstart, _, OutAttrs} | Els] -> + AuthID = xml:get_attr_s(<<"id">>, OutAttrs), + From = xml:get_attr_s(<<"from">>, OutAttrs), + Version = xml:get_attr_s(<<"version">>, OutAttrs), + OutEls = case Els of + [] -> []; + [{xmlstreamelement, + #xmlel{name = <<"stream:features">>, + attrs = StreamAttribs, children = StreamEls}} + | StreamTail] -> + TypedTail = [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- StreamTail], + [#xmlel{name = <<"stream:features">>, + attrs = + [{<<"xmlns:stream">>, ?NS_STREAM}] ++ + StreamAttribs, + children = StreamEls}] + ++ TypedTail; + StreamTail -> + [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- StreamTail] + end, + case OutEls of + [#xmlel{name = <<"stream:error">>}] -> + {200, ?HEADER, + <<"<body type='terminate' condition='host-unknow" + "n' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>}; + _ -> + BOSH_attribs = [{<<"authid">>, AuthID}, + {<<"xmlns:xmpp">>, ?NS_BOSH}, + {<<"xmlns:stream">>, ?NS_STREAM}] + ++ + case OutEls of + [] -> []; + _ -> [{<<"xmpp:version">>, Version}] + end, + MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), + MaxPause = get_max_pause(To), + {200, ?HEADER, + xml:element_to_binary(#xmlel{name = <<"body">>, + attrs = + [{<<"xmlns">>, ?NS_HTTP_BIND}, + {<<"sid">>, Sid}, + {<<"wait">>, + iolist_to_binary(integer_to_list(Wait))}, + {<<"requests">>, + iolist_to_binary(integer_to_list(Hold + + + 1))}, + {<<"inactivity">>, + iolist_to_binary(integer_to_list(trunc(MaxInactivity + / + 1000)))}, + {<<"maxpause">>, + iolist_to_binary(integer_to_list(MaxPause))}, + {<<"polling">>, + iolist_to_binary(integer_to_list(trunc((?MIN_POLLING) + / + 1000000)))}, + {<<"ver">>, ?BOSH_VERSION}, + {<<"from">>, From}, + {<<"secure">>, <<"true">>}] + ++ BOSH_attribs, + children = OutEls})} + end; + _ -> + {200, ?HEADER, + <<"<body type='terminate' condition='internal-se" + "rver-error' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>} end. - -http_get(#http_bind{pid = FsmRef, wait = Wait, hold = Hold}, Rid) -> - gen_fsm:sync_send_all_state_event( - FsmRef, {http_get, Rid, Wait, Hold}, 2 * ?MAX_WAIT * 1000). +http_get(#http_bind{pid = FsmRef, wait = Wait, + hold = Hold}, + Rid) -> + gen_fsm:sync_send_all_state_event(FsmRef, + {http_get, Rid, Wait, Hold}, + 2 * (?MAX_WAIT) * 1000). send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> case OutPacket of - [] -> - {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"}; - [{xmlstreamend, _}] -> - gen_fsm:sync_send_all_state_event(FsmRef,{stop,stream_closed}), - {200, ?HEADER, "<body xmlns='"++?NS_HTTP_BIND++"'/>"}; - _ -> - %% TODO: We parse to add a default namespace to packet, - %% The spec says adding the jabber:client namespace if - %% mandatory, even if some implementation do not do that - %% change on packets. - %% I think this should be an option to avoid modifying - %% packet in most case. - AllElements = - lists:all(fun({xmlstreamelement, - {xmlelement, "stream:error", _, _}}) -> false; - ({xmlstreamelement, _}) -> true; - ({xmlstreamraw, _}) -> true; - (_) -> false - end, OutPacket), - case AllElements of - true -> - TypedEls = lists:foldl(fun({xmlstreamelement, El}, Acc) -> - Acc ++ - [xml:element_to_string( - check_default_xmlns(El) - )]; - ({xmlstreamraw, R}, Acc) -> - Acc ++ [R] - end, - [], - OutPacket), - - Body = "<body xmlns='"++?NS_HTTP_BIND++"'>" - ++ TypedEls ++ - "</body>", - ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n", - [Body]), - {200, ?HEADER, Body}; - false -> - case OutPacket of - [{xmlstreamstart, _, _} | SEls] -> - OutEls = - case SEls of - [{xmlstreamelement, - {xmlelement, - "stream:features", - StreamAttribs, StreamEls}} | - StreamTail] -> - TypedTail = - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail], - [{xmlelement, - "stream:features", - [{"xmlns:stream", - ?NS_STREAM}] ++ - StreamAttribs, StreamEls}] ++ - TypedTail; - StreamTail -> - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail] - end, - {200, ?HEADER, - xml:element_to_binary( - {xmlelement,"body", - [{"xmlns", - ?NS_HTTP_BIND}], - OutEls})}; + [] -> + {200, ?HEADER, + <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>}; + [{xmlstreamend, _}] -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, stream_closed}), + {200, ?HEADER, + <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>}; + _ -> + AllElements = lists:all(fun ({xmlstreamelement, + #xmlel{name = <<"stream:error">>}}) -> + false; + ({xmlstreamelement, _}) -> true; + ({xmlstreamraw, _}) -> true; + (_) -> false + end, + OutPacket), + case AllElements of + true -> + TypedEls = lists:foldl(fun ({xmlstreamelement, El}, + Acc) -> + Acc ++ + [xml:element_to_binary(check_default_xmlns(El))]; + ({xmlstreamraw, R}, Acc) -> + Acc ++ [R] + end, + [], OutPacket), + Body = <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'>", + (iolist_to_binary(TypedEls))/binary, "</body>">>, + ?DEBUG(" --- outgoing data --- ~n~s~n --- END " + "--- ~n", + [Body]), + {200, ?HEADER, Body}; + false -> + case OutPacket of + [{xmlstreamstart, _, _} | SEls] -> + OutEls = case SEls of + [{xmlstreamelement, + #xmlel{name = <<"stream:features">>, + attrs = StreamAttribs, + children = StreamEls}} + | StreamTail] -> + TypedTail = [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} + <- StreamTail], + [#xmlel{name = <<"stream:features">>, + attrs = + [{<<"xmlns:stream">>, + ?NS_STREAM}] + ++ StreamAttribs, + children = StreamEls}] + ++ TypedTail; + StreamTail -> + [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- StreamTail] + end, + {200, ?HEADER, + xml:element_to_binary(#xmlel{name = <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}], + children = OutEls})}; + _ -> + SErrCond = lists:filter(fun ({xmlstreamelement, + #xmlel{name = + <<"stream:error">>}}) -> + true; + (_) -> false + end, + OutPacket), + StreamErrCond = case SErrCond of + [] -> null; + [{xmlstreamelement, + #xmlel{} = StreamErrorTag} + | _] -> + [StreamErrorTag] + end, + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, + {stream_error, + OutPacket}}), + case StreamErrCond of + null -> + {200, ?HEADER, + <<"<body type='terminate' condition='internal-se" + "rver-error' xmlns='", + (?NS_HTTP_BIND)/binary, "'/>">>}; _ -> - SErrCond = - lists:filter( - fun({xmlstreamelement, - {xmlelement, "stream:error", - _, _}}) -> - true; - (_) -> false - end, OutPacket), - StreamErrCond = - case SErrCond of - [] -> - null; - [{xmlstreamelement, - {xmlelement, _, _, _Cond} = - StreamErrorTag} | _] -> - [StreamErrorTag] - end, - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, {stream_error,OutPacket}}), - case StreamErrCond of - null -> - {200, ?HEADER, - "<body type='terminate' " - "condition='internal-server-error' " - "xmlns='"++?NS_HTTP_BIND++"'/>"}; - _ -> - {200, ?HEADER, - "<body type='terminate' " - "condition='remote-stream-error' " - "xmlns='"++?NS_HTTP_BIND++"' " ++ - "xmlns:stream='"++?NS_STREAM++"'>" ++ - elements_to_string(StreamErrCond) ++ - "</body>"} - end - end - end + {200, ?HEADER, + <<"<body type='terminate' condition='remote-stre" + "am-error' xmlns='", + (?NS_HTTP_BIND)/binary, "' ", "xmlns:stream='", + (?NS_STREAM)/binary, "'>", + (elements_to_string(StreamErrCond))/binary, + "</body>">>} + end + end + end end. parse_request(Data, PayloadSize, MaxStanzaSize) -> - ?DEBUG("--- incoming data --- ~n~s~n --- END --- ", [Data]), - %% MR: I do not think it works if put put several elements in the - %% same body: + ?DEBUG("--- incoming data --- ~n~s~n --- END " + "--- ", + [Data]), case xml_stream:parse_element(Data) of - {xmlelement, "body", Attrs, Els} -> - Xmlns = xml:get_attr_s("xmlns",Attrs), - if - Xmlns /= ?NS_HTTP_BIND -> - {error, bad_request}; - true -> - case catch list_to_integer(xml:get_attr_s("rid", Attrs)) of - {'EXIT', _} -> - {error, bad_request}; - Rid -> - %% I guess this is to remove XMLCDATA: Is it really needed ? - FixedEls = - lists:filter( - fun(I) -> - case I of - {xmlelement, _, _, _} -> - true; - _ -> - false - end - end, Els), - Sid = xml:get_attr_s("sid",Attrs), - if - PayloadSize =< MaxStanzaSize -> - {ok, {Sid, Rid, Attrs, FixedEls}}; - true -> - {size_limit, Sid} - end - end - end; - {xmlelement, _Name, _Attrs, _Els} -> - {error, bad_request}; - {error, _Reason} -> - {error, bad_request} + #xmlel{name = <<"body">>, attrs = Attrs, + children = Els} -> + Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs), + if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request}; + true -> + case catch + jlib:binary_to_integer(xml:get_attr_s(<<"rid">>, + Attrs)) + of + {'EXIT', _} -> {error, bad_request}; + Rid -> + FixedEls = lists:filter(fun (I) -> + case I of + #xmlel{} -> true; + _ -> false + end + end, + Els), + Sid = xml:get_attr_s(<<"sid">>, Attrs), + if PayloadSize =< MaxStanzaSize -> + {ok, {Sid, Rid, Attrs, FixedEls}}; + true -> {size_limit, Sid} + end + end + end; + #xmlel{} -> {error, bad_request}; + {error, _Reason} -> {error, bad_request} end. -send_receiver_reply(undefined, _Reply) -> - ok; +send_receiver_reply(undefined, _Reply) -> ok; send_receiver_reply(Receiver, Reply) -> gen_fsm:reply(Receiver, Reply). - -%% Cancel timer and empty message queue. -cancel_timer(undefined) -> - ok; +cancel_timer(undefined) -> ok; cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. -%% If client asked for a pause (pause > 0), we apply the pause value -%% as inactivity timer: -set_inactivity_timer(Pause, _MaxInactivity) when Pause > 0 -> - erlang:start_timer(Pause*1000, self(), []); +set_inactivity_timer(Pause, _MaxInactivity) + when Pause > 0 -> + erlang:start_timer(Pause * 1000, self(), []); %% Otherwise, we apply the max_inactivity value as inactivity timer: set_inactivity_timer(_Pause, MaxInactivity) -> erlang:start_timer(MaxInactivity, self(), []). - -%% TODO: Use tail recursion and list reverse ? -elements_to_string([]) -> - []; +elements_to_string([]) -> []; elements_to_string([El | Els]) -> - [xml:element_to_binary(El)|elements_to_string(Els)]. + [xml:element_to_binary(El) | elements_to_string(Els)]. -%% @spec (To, Default::integer()) -> integer() -%% where To = [] | {Host::string(), Version::string()} get_max_inactivity({Host, _}, Default) -> - case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, undefined) of - Seconds when is_integer(Seconds) -> - Seconds * 1000; - undefined -> - Default + case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, + fun(I) when is_integer(I), I>0 -> I end, + undefined) + of + Seconds when is_integer(Seconds) -> Seconds * 1000; + undefined -> Default end; -get_max_inactivity(_, Default) -> - Default. +get_max_inactivity(_, Default) -> Default. get_max_pause({Host, _}) -> - gen_mod:get_module_opt(Host, mod_http_bind, max_pause, ?MAX_PAUSE); -get_max_pause(_) -> - ?MAX_PAUSE. + gen_mod:get_module_opt(Host, mod_http_bind, max_pause, + fun(I) when is_integer(I), I>0 -> I end, + ?MAX_PAUSE); +get_max_pause(_) -> ?MAX_PAUSE. -%% Current time as integer tnow() -> {TMegSec, TSec, TMSec} = now(), (TMegSec * 1000000 + TSec) * 1000000 + TMSec. -check_default_xmlns({xmlelement, Name, Attrs, Els} = El) -> - case xml:get_tag_attr_s("xmlns", El) of - "" -> {xmlelement, Name, [{"xmlns", ?NS_CLIENT} | Attrs], Els}; - _ -> El +check_default_xmlns(#xmlel{name = Name, attrs = Attrs, + children = Els} = + El) -> + case xml:get_tag_attr_s(<<"xmlns">>, El) of + <<"">> -> + #xmlel{name = Name, + attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs], + children = Els}; + _ -> El end; -check_default_xmlns(El) -> - El. +check_default_xmlns(El) -> El. -%% Check that mod_http_bind has been defined in config file. -%% Print a warning in log file if this is not the case. check_bind_module(XmppDomain) -> case gen_mod:is_loaded(XmppDomain, mod_http_bind) of - true -> ok; - false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) in host ~p," - " but the module mod_http_bind is not started in" - " that host. Configure your BOSH client to connect" - " to the correct host, or add your desired host to" - " the configuration, or check your 'modules'" - " section in your ejabberd configuration file.", - [XmppDomain]) + true -> true; + false -> + ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) " + "in host ~p, but the module mod_http_bind " + "is not started in that host. Configure " + "your BOSH client to connect to the correct " + "host, or add your desired host to the " + "configuration, or check your 'modules' " + "section in your ejabberd configuration " + "file.", + [XmppDomain]), + false end. make_sid() -> - sha:sha(term_to_binary({now(), make_ref()})) - ++ "-" ++ ejabberd_cluster:node_id(). + <<(sha:sha(term_to_binary({now(), make_ref()})))/binary, + "-", (ejabberd_cluster:node_id())/binary>>. get_session(SID) -> - case string:tokens(SID, "-") of - [_, NodeID] -> - case ejabberd_cluster:get_node_by_id(NodeID) of - Node when Node == node() -> - case mnesia:dirty_read({http_bind, SID}) of - [] -> - {error, enoent}; - [Session] -> - {ok, Session} - end; - Node -> - case catch rpc:call(Node, mnesia, dirty_read, - [{http_bind, SID}], 5000) of - [Session] -> - {ok, Session}; - _ -> - {error, enoent} - end - end; - _ -> - {error, enoent} + case str:tokens(SID, <<"-">>) of + [_, NodeID] -> + case ejabberd_cluster:get_node_by_id(NodeID) of + Node when Node == node() -> + case mnesia:dirty_read({http_bind, SID}) of + [] -> {error, enoent}; + [Session] -> {ok, Session} + end; + Node -> + case catch rpc:call(Node, mnesia, dirty_read, + [{http_bind, SID}], 5000) + of + [Session] -> {ok, Session}; + _ -> {error, enoent} + end + end; + _ -> {error, enoent} end. make_random_jid(Host) -> - %% Copied from cyrsasl_anonymous.erl - User = lists:concat([randoms:get_string() | tuple_to_list(now())]), + User = iolist_to_binary([randoms:get_string() + | tuple_to_list(now())]), jlib:make_jid(User, Host, randoms:get_string()). diff --git a/src/web/ejabberd_http_bindjson.erl b/src/web/ejabberd_http_bindjson.erl index 61f8779e8..245416142 100644 --- a/src/web/ejabberd_http_bindjson.erl +++ b/src/web/ejabberd_http_bindjson.erl @@ -7,393 +7,360 @@ %%% Id : $Id: ejabberd_http_bind.erl 953 2009-05-07 10:40:40Z alexey $ %%%---------------------------------------------------------------------- --module (ejabberd_http_bindjson). +-module(ejabberd_http_bindjson). -behaviour(gen_fsm). %% External exports --export([start_link/3, - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - send/2, - send_xml/2, - sockname/1, - peername/1, - setopts/2, - controlling_process/2, - become_controller/2, - change_controller/2, - custom_receiver/1, - reset_stream/1, - change_shaper/2, - monitor/1, - close/1, - start/4, - handle_session_start/8, - handle_http_put/7, - http_put/7, - http_get/2, - prepare_response/4, - process_request/2]). +-export([start_link/3, init/1, handle_event/3, + handle_sync_event/4, code_change/4, handle_info/3, + terminate/3, send/2, send_xml/2, sockname/1, peername/1, + setopts/2, controlling_process/2, become_controller/2, + change_controller/2, custom_receiver/1, reset_stream/1, + change_shaper/2, monitor/1, close/1, start/4, + handle_session_start/8, handle_http_put/7, http_put/7, + http_get/2, prepare_response/4, process_request/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("http_bind.hrl"). --record(http_bind, {id, pid, to, hold, wait, process_delay, version}). +-record(http_bind, +{ + id, + pid, + to, + hold, + wait, + process_delay, + version +}). -define(NULL_PEER, {{0, 0, 0, 0}, 0}). -%% http binding request --record(hbr, {rid, - key, - out}). - --record(state, {id, - rid = none, - key, - socket, - output = "", - input = queue:new(), - waiting_input = false, - shaper_state, - shaper_timer, - last_receiver, - last_poll, - http_receiver, - wait_timer, - ctime = 0, - timer, - pause=0, - unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID} - req_list = [], % list of requests (cache) - max_inactivity, - max_pause, - ip = ?NULL_PEER - }). - -%% Internal request format: --record(http_put, {rid, - attrs, - payload, - payload_size, - hold, - stream, - ip}). +-record(hbr, {rid, key, out}). + +-record(state, +{ + id, + rid = none, + key, + socket, + output = [], + input = queue:new(), + waiting_input = false, + shaper_state, + shaper_timer, + last_receiver, + last_poll, + http_receiver, + wait_timer, + ctime = 0, + timer, + pause = 0, + unprocessed_req_list = [], + req_list = [], + max_inactivity, + max_pause, + ip = ?NULL_PEER +}). + +-record(http_put, +{ + rid, + attrs, + payload, + payload_size, + hold, stream, + ip +}). %%-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. --define(BOSH_VERSION, "1.8"). --define(NS_CLIENT, "jabber:client"). --define(NS_BOSH, "urn:xmpp:xbosh"). --define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind"). - --define(MAX_REQUESTS, 2). % number of simultaneous requests --define(MIN_POLLING, 2000000). % don't poll faster than that or we will - % shoot you (time in microsec) --define(MAX_WAIT, 3600). % max num of secs to keep a request on hold --define(MAX_INACTIVITY, 30000). % msecs to wait before terminating - % idle sessions --define(MAX_PAUSE, 120). % may num of sec a client is allowed to pause - % the session - -%% Wait 100ms before continue processing, to allow the client provide more related stanzas. +-define(BOSH_VERSION, <<"1.8">>). + +-define(NS_CLIENT, <<"jabber:client">>). + +-define(NS_BOSH, <<"urn:xmpp:xbosh">>). + +-define(NS_HTTP_BIND, + <<"http://jabber.org/protocol/httpbind">>). + +-define(MAX_REQUESTS, 2). + +-define(MIN_POLLING, 2000000). + +-define(MAX_WAIT, 3600). + +-define(MAX_INACTIVITY, 30000). + +-define(MAX_PAUSE, 120). + -define(PROCESS_DELAY_DEFAULT, 100). + -define(PROCESS_DELAY_MIN, 0). --define(PROCESS_DELAY_MAX, 1000). +-define(PROCESS_DELAY_MAX, 1000). -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -%% TODO: If compile with no supervisor option, start the session without -%% supervisor start(XMPPDomain, Sid, Key, IP) -> ?DEBUG("Starting session", []), - case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of - {ok, Pid} -> - {ok, Pid}; - {error, _} = Err -> - case check_bind_module(XMPPDomain) of - false -> - {error, "Cannot start HTTP bind session"}; - true -> - ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]), - Err - end; - Exit -> - ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]), - {error, Exit} + case catch + supervisor:start_child(ejabberd_http_bind_sup, + [Sid, Key, IP]) + of + {ok, Pid} -> {ok, Pid}; + {error, _} = Err -> + case check_bind_module(XMPPDomain) of + false -> {error, <<"Cannot start HTTP bind session">>}; + true -> + ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]), + Err + end; + Exit -> + ?ERROR_MSG("Cannot start HTTP bind session: ~p", + [Exit]), + {error, Exit} end. start_link(Sid, Key, IP) -> gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS). send({http_bind, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send, Packet}). send_xml({http_bind, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send_xml, Packet}). setopts({http_bind, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - case lists:member({active, false}, Opts) of - true -> - gen_fsm:sync_send_all_state_event( - FsmRef, deactivate_socket); - _ -> - ok - end + true -> + gen_fsm:send_all_state_event(FsmRef, + {activate, self()}); + _ -> + case lists:member({active, false}, Opts) of + true -> + gen_fsm:sync_send_all_state_event(FsmRef, + deactivate_socket); + _ -> ok + end end. -controlling_process(_Socket, _Pid) -> - ok. +controlling_process(_Socket, _Pid) -> ok. custom_receiver({http_bind, FsmRef, _IP}) -> {receiver, ?MODULE, FsmRef}. become_controller(FsmRef, C2SPid) -> - gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). + gen_fsm:send_all_state_event(FsmRef, + {become_controller, C2SPid}). change_controller({http_bind, FsmRef, _IP}, C2SPid) -> become_controller(FsmRef, C2SPid). -reset_stream({http_bind, _FsmRef, _IP}) -> - ok. +reset_stream({http_bind, _FsmRef, _IP}) -> ok. change_shaper({http_bind, FsmRef, _IP}, Shaper) -> - gen_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}). + gen_fsm:send_all_state_event(FsmRef, + {change_shaper, Shaper}). monitor({http_bind, FsmRef, _IP}) -> erlang:monitor(process, FsmRef). close({http_bind, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}). + catch gen_fsm:sync_send_all_state_event(FsmRef, + {stop, close}). -sockname(_Socket) -> - {ok, ?NULL_PEER}. +sockname(_Socket) -> {ok, ?NULL_PEER}. -peername({http_bind, _FsmRef, IP}) -> - {ok, IP}. +peername({http_bind, _FsmRef, IP}) -> {ok, IP}. -%% Entry point for data coming from client through ejabberd HTTP server: process_request(Data, IP) -> Opts1 = ejabberd_c2s_config:get_c2s_limits(), Opts = [{xml_socket, true} | Opts1], - MaxStanzaSize = - case lists:keysearch(max_stanza_size, 1, Opts) of - {value, {_, Size}} -> Size; - _ -> infinity - end, + MaxStanzaSize = case lists:keysearch(max_stanza_size, 1, + Opts) + of + {value, {_, Size}} -> Size; + _ -> infinity + end, PayloadSize = iolist_size(Data), - case catch parse_request(Data, PayloadSize, MaxStanzaSize) of - %% No existing session: - {ok, {"", Rid, Attrs, Payload}} -> - case xml:get_attr_s("to",Attrs) of - "" -> - ?DEBUG("Session not created (Improper addressing)", []), - {200, ?HEADER, "{\"body\":{\"type\":\"terminate\" " - "\"condition\":\"improper-addressing\", " - "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\"}}"}; - XmppDomain -> - %% create new session - Sid = make_sid(), - case start(XmppDomain, Sid, "", IP) of - {error, _} -> - {500, ?HEADER,"{\"body\":{\"type\":\"terminate\" " - "\"condition\":\"internal-server-error\", " - "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\",\"$\":\"Internal Server Error\"}}"}; - {ok, Pid} -> - handle_session_start( - Pid, XmppDomain, Sid, Rid, Attrs, - Payload, PayloadSize, IP) - end - end; - %% Existing session - {ok, {Sid, Rid, Attrs, Payload1}} -> - StreamStart = - case xml:get_attr_s("xmpp:restart",Attrs) of - "true" -> - true; - _ -> - false - end, - Payload2 = case xml:get_attr_s("type",Attrs) of - "terminate" -> - %% close stream - Payload1 ++ [{xmlstreamend, "stream:stream"}]; - _ -> - Payload1 - end, - handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize, - StreamStart, IP); - {size_limit, Sid} -> - case get_session(Sid) of - {error, _} -> - {404, ?HEADER, ""}; - {ok, #http_bind{pid = FsmRef}} -> - gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}), - {200, ?HEADER, "{\"body\": {\"type\"=\"terminate\" " - "\"condition\":\"undefined-condition\" " - "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\", \"$\":\"Request Too Large\"}}"} - end; - _ -> - ?DEBUG("Received bad request: ~p", [Data]), - {400, ?HEADER, ""} + case catch parse_request(Data, PayloadSize, + MaxStanzaSize) + of + %% No existing session: + {ok, {<<"">>, Rid, Attrs, Payload}} -> + case xml:get_attr_s(<<"to">>, Attrs) of + <<"">> -> + ?DEBUG("Session not created (Improper addressing)", []), + {200, ?HEADER, + <<"{\"body\":{\"type\":\"terminate\" \"condition\"" + ":\"improper-addressing\", \"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\"}}">>}; + XmppDomain -> + Sid = make_sid(), + case start(XmppDomain, Sid, <<"">>, IP) of + {error, _} -> + {500, ?HEADER, + <<"{\"body\":{\"type\":\"terminate\" \"condition\"" + ":\"internal-server-error\", \"xmlns\":\"", + (?NS_HTTP_BIND)/binary, + "\",\"$\":\"Internal Server Error\"}}">>}; + {ok, Pid} -> + handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, + Payload, PayloadSize, IP) + end + end; + %% Existing session + {ok, {Sid, Rid, Attrs, Payload1}} -> + StreamStart = case xml:get_attr_s(<<"xmpp:restart">>, + Attrs) + of + <<"true">> -> true; + _ -> false + end, + Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of + <<"terminate">> -> + Payload1 ++ [{xmlstreamend, <<"stream:stream">>}]; + _ -> Payload1 + end, + handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize, + StreamStart, IP); + {size_limit, Sid} -> + case get_session(Sid) of + {error, _} -> {404, ?HEADER, <<"">>}; + {ok, #http_bind{pid = FsmRef}} -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, close}), + {200, ?HEADER, + <<"{\"body\": {\"type\"=\"terminate\" \"conditio" + "n\":\"undefined-condition\" \"xmlns\":\"", + (?NS_HTTP_BIND)/binary, + "\", \"$\":\"Request Too Large\"}}">>} + end; + _ -> + ?DEBUG("Received bad request: ~p", [Data]), + {400, ?HEADER, <<"">>} end. handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, Payload, PayloadSize, IP) -> ?DEBUG("got pid: ~p", [Pid]), - Wait = case string:to_integer(xml:get_attr_s("wait",Attrs)) of - {error, _} -> - ?MAX_WAIT; - {CWait, _} -> - if - (CWait > ?MAX_WAIT) -> - ?MAX_WAIT; - true -> - CWait - end + Wait = case str:to_integer(xml:get_attr_s(<<"wait">>, + Attrs)) + of + {error, _} -> ?MAX_WAIT; + {CWait, _} -> + if CWait > (?MAX_WAIT) -> ?MAX_WAIT; + true -> CWait + end end, - Hold = case string:to_integer(xml:get_attr_s("hold",Attrs)) of - {error, _} -> - (?MAX_REQUESTS - 1); - {CHold, _} -> - if - (CHold > (?MAX_REQUESTS - 1)) -> - (?MAX_REQUESTS - 1); - true -> - CHold - end + Hold = case str:to_integer(xml:get_attr_s(<<"hold">>, + Attrs)) + of + {error, _} -> (?MAX_REQUESTS) - 1; + {CHold, _} -> + if CHold > (?MAX_REQUESTS) - 1 -> (?MAX_REQUESTS) - 1; + true -> CHold + end end, - Pdelay = case string:to_integer(xml:get_attr_s("process-delay",Attrs)) of - {error, _} -> - ?PROCESS_DELAY_DEFAULT; - {CPdelay, _} when - (?PROCESS_DELAY_MIN =< CPdelay) and - (CPdelay =< ?PROCESS_DELAY_MAX) -> - CPdelay; - {CPdelay, _} -> - erlang:max( - erlang:min(CPdelay,?PROCESS_DELAY_MAX), - ?PROCESS_DELAY_MIN) + Pdelay = case + str:to_integer(xml:get_attr_s(<<"process-delay">>, + Attrs)) + of + {error, _} -> ?PROCESS_DELAY_DEFAULT; + {CPdelay, _} + when ((?PROCESS_DELAY_MIN) =< CPdelay) and + (CPdelay =< (?PROCESS_DELAY_MAX)) -> + CPdelay; + {CPdelay, _} -> + erlang:max(erlang:min(CPdelay, ?PROCESS_DELAY_MAX), + ?PROCESS_DELAY_MIN) end, - Version = - case catch list_to_float( - xml:get_attr_s("ver", Attrs)) of - {'EXIT', _} -> 0.0; - V -> V - end, - XmppVersion = xml:get_attr_s("xmpp:version", Attrs), + Version = case catch + list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs))) + of + {'EXIT', _} -> 0.0; + V -> V + end, + XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs), ?DEBUG("Create session: ~p", [Sid]), - mnesia:async_dirty( - fun() -> - mnesia:write( - #http_bind{id = Sid, - pid = Pid, - to = {XmppDomain, - XmppVersion}, - hold = Hold, - wait = Wait, - process_delay = Pdelay, - version = Version - }) - end), - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP). + mnesia:async_dirty(fun () -> + mnesia:write(#http_bind{id = Sid, pid = Pid, + to = + {XmppDomain, + XmppVersion}, + hold = Hold, wait = Wait, + process_delay = Pdelay, + version = Version}) + end), + handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + true, IP). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- init([Sid, Key, IP]) -> ?DEBUG("started: ~p", [{Sid, Key, IP}]), - - %% Read c2s options from the first ejabberd_c2s configuration in - %% the config file listen section - %% TODO: We should have different access and shaper values for - %% each connector. The default behaviour should be however to use - %% the default c2s restrictions if not defined for the current - %% connector. Opts1 = ejabberd_c2s_config:get_c2s_limits(), Opts = [{xml_socket, true} | Opts1], - Shaper = none, ShaperState = shaper:new(Shaper), Socket = {http_bind, self(), IP}, - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), - {ok, loop, #state{id = Sid, - key = Key, - socket = Socket, - shaper_state = ShaperState, - max_inactivity = ?MAX_INACTIVITY, - max_pause = ?MAX_PAUSE, - timer = Timer}}. - -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({become_controller, C2SPid}, StateName, StateData) -> + {ok, loop, + #state{id = Sid, key = Key, socket = Socket, + shaper_state = ShaperState, + max_inactivity = ?MAX_INACTIVITY, + max_pause = ?MAX_PAUSE, timer = Timer}}. + +handle_event({become_controller, C2SPid}, StateName, + StateData) -> erlang:monitor(process, C2SPid), case StateData#state.input of - cancel -> - {next_state, StateName, StateData#state{ - waiting_input = C2SPid}}; - Input -> - lists:foreach( - fun(Event) -> - C2SPid ! Event - end, queue:to_list(Input)), - {next_state, StateName, StateData#state{ - input = queue:new(), - waiting_input = C2SPid}} + cancel -> + {next_state, StateName, + StateData#state{waiting_input = C2SPid}}; + Input -> + lists:foreach(fun (Event) -> C2SPid ! Event end, + queue:to_list(Input)), + {next_state, StateName, + StateData#state{input = queue:new(), + waiting_input = C2SPid}} end; - -handle_event({change_shaper, Shaper}, StateName, StateData) -> +handle_event({change_shaper, Shaper}, StateName, + StateData) -> NewShaperState = shaper:new(Shaper), - {next_state, StateName, StateData#state{shaper_state = NewShaperState}}; + {next_state, StateName, + StateData#state{shaper_state = NewShaperState}}; handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- handle_sync_event({send_xml, Packet}, _From, StateName, #state{http_receiver = undefined} = StateData) -> Output = [Packet | StateData#state.output], Reply = ok, - {reply, Reply, StateName, StateData#state{output = Output}}; -handle_sync_event({send_xml, Packet}, _From, StateName, StateData) -> + {reply, Reply, StateName, + StateData#state{output = Output}}; +handle_sync_event({send_xml, Packet}, _From, StateName, + StateData) -> Output = [Packet | StateData#state.output], cancel_timer(StateData#state.timer), Timer = set_inactivity_timer(StateData#state.pause, @@ -402,188 +369,151 @@ handle_sync_event({send_xml, Packet}, _From, StateName, StateData) -> gen_fsm:reply(StateData#state.http_receiver, HTTPReply), cancel_timer(StateData#state.wait_timer), Rid = StateData#state.rid, - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = Output - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = Output} + | [El + || El <- StateData#state.req_list, El#hbr.rid /= Rid]], Reply = ok, {reply, Reply, StateName, - StateData#state{output = [], - http_receiver = undefined, - req_list = ReqList, - wait_timer = undefined, + StateData#state{output = [], http_receiver = undefined, + req_list = ReqList, wait_timer = undefined, timer = Timer}}; - -handle_sync_event({stop,close}, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; -handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; -handle_sync_event(deactivate_socket, _From, StateName, StateData) -> - %% Input = case StateData#state.input of - %% cancel -> - %% queue:new(); - %% Q -> - %% Q - %% end, - {reply, ok, StateName, StateData#state{waiting_input = false}}; -handle_sync_event({stop,Reason}, _From, _StateName, StateData) -> - ?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]), +handle_sync_event({stop, close}, _From, _StateName, + StateData) -> + Reply = ok, {stop, normal, Reply, StateData}; +handle_sync_event({stop, stream_closed}, _From, + _StateName, StateData) -> + Reply = ok, {stop, normal, Reply, StateData}; +handle_sync_event(deactivate_socket, _From, StateName, + StateData) -> + {reply, ok, StateName, + StateData#state{waiting_input = false}}; +handle_sync_event({stop, Reason}, _From, _StateName, + StateData) -> + ?DEBUG("Closing bind session ~p - Reason: ~p", + [StateData#state.id, Reason]), Reply = ok, {stop, normal, Reply, StateData}; - %% HTTP PUT: Receive packets from the client -handle_sync_event(#http_put{rid = Rid}, - _From, StateName, StateData) - when StateData#state.shaper_timer /= undefined -> - Pause = - case erlang:read_timer(StateData#state.shaper_timer) of - false -> - 0; - P -> P - end, +handle_sync_event(#http_put{rid = Rid}, _From, + StateName, StateData) + when StateData#state.shaper_timer /= undefined -> + Pause = case + erlang:read_timer(StateData#state.shaper_timer) + of + false -> 0; + P -> P + end, Reply = {wait, Pause}, ?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]), {reply, Reply, StateName, StateData}; - -handle_sync_event(#http_put{payload_size = PayloadSize} = Request, +handle_sync_event(#http_put{payload_size = + PayloadSize} = + Request, _From, StateName, StateData) -> - ?DEBUG("New request: ~p",[Request]), - %% Updating trafic shaper + ?DEBUG("New request: ~p", [Request]), {NewShaperState, NewShaperTimer} = - update_shaper(StateData#state.shaper_state, PayloadSize), - + update_shaper(StateData#state.shaper_state, + PayloadSize), handle_http_put_event(Request, StateName, StateData#state{shaper_state = NewShaperState, shaper_timer = NewShaperTimer}); - %% HTTP GET: send packets to the client -handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> - %% setup timer - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), +handle_sync_event({http_get, Rid, Wait, Hold}, From, + StateName, StateData) -> + send_receiver_reply(StateData#state.http_receiver, + {ok, empty}), cancel_timer(StateData#state.wait_timer), TNow = tnow(), - if - (Hold > 0) and - (StateData#state.output == []) and - ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and - (StateData#state.rid == Rid) and - (StateData#state.input /= cancel) and - (StateData#state.pause == 0) -> - WaitTimer = erlang:start_timer(Wait * 1000, self(), []), - %% MR: Not sure we should cancel the state timer here. - cancel_timer(StateData#state.timer), - {next_state, StateName, StateData#state{ - http_receiver = From, - wait_timer = WaitTimer, - timer = undefined}}; - (StateData#state.input == cancel) -> - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - Reply = {ok, cancel}, - {reply, Reply, StateName, StateData#state{ - input = queue:new(), - http_receiver = undefined, - wait_timer = undefined, - timer = Timer}}; - true -> - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - Reply = {ok, StateData#state.output}, - %% save request - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = StateData#state.output - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], - {reply, Reply, StateName, StateData#state{ - output = [], - http_receiver = undefined, - wait_timer = undefined, - timer = Timer, - req_list = ReqList}} + if (Hold > 0) and (StateData#state.output == []) and + (TNow - StateData#state.ctime < Wait * 1000 * 1000) + and (StateData#state.rid == Rid) + and (StateData#state.input /= cancel) + and (StateData#state.pause == 0) -> + WaitTimer = erlang:start_timer(Wait * 1000, self(), []), + cancel_timer(StateData#state.timer), + {next_state, StateName, + StateData#state{http_receiver = From, + wait_timer = WaitTimer, timer = undefined}}; + StateData#state.input == cancel -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + Reply = {ok, cancel}, + {reply, Reply, StateName, + StateData#state{input = queue:new(), + http_receiver = undefined, wait_timer = undefined, + timer = Timer}}; + true -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + Reply = {ok, StateData#state.output}, + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = StateData#state.output} + | [El + || El <- StateData#state.req_list, El#hbr.rid /= Rid]], + {reply, Reply, StateName, + StateData#state{output = [], http_receiver = undefined, + wait_timer = undefined, timer = Timer, + req_list = ReqList}} end; - -handle_sync_event(peername, _From, StateName, StateData) -> +handle_sync_event(peername, _From, StateName, + StateData) -> Reply = {ok, StateData#state.ip}, {reply, Reply, StateName, StateData}; - -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}. -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -%% We reached the max_inactivity timeout: handle_info({timeout, Timer, _}, _StateName, - #state{id=SID, timer = Timer} = StateData) -> - ?INFO_MSG("Session timeout. Closing the HTTP bind session: ~p", [SID]), + #state{id = SID, timer = Timer} = StateData) -> + ?INFO_MSG("Session timeout. Closing the HTTP bind " + "session: ~p", + [SID]), {stop, normal, StateData}; - handle_info({timeout, WaitTimer, _}, StateName, #state{wait_timer = WaitTimer} = StateData) -> - if - StateData#state.http_receiver /= undefined -> - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - gen_fsm:reply(StateData#state.http_receiver, {ok, empty}), - Rid = StateData#state.rid, - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = [] - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], - {next_state, StateName, - StateData#state{http_receiver = undefined, - req_list = ReqList, - wait_timer = undefined, - timer = Timer}}; - true -> - {next_state, StateName, StateData} + if StateData#state.http_receiver /= undefined -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + gen_fsm:reply(StateData#state.http_receiver, + {ok, empty}), + Rid = StateData#state.rid, + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = []} + | [El + || El <- StateData#state.req_list, El#hbr.rid /= Rid]], + {next_state, StateName, + StateData#state{http_receiver = undefined, + req_list = ReqList, wait_timer = undefined, + timer = Timer}}; + true -> {next_state, StateName, StateData} end; - handle_info({timeout, ShaperTimer, _}, StateName, #state{shaper_timer = ShaperTimer} = StateData) -> - {next_state, StateName, StateData#state{shaper_timer = undefined}}; - -handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName, + {next_state, StateName, + StateData#state{shaper_timer = undefined}}; +handle_info({'DOWN', _MRef, process, C2SPid, _}, + _StateName, #state{waiting_input = C2SPid} = StateData) -> {stop, normal, StateData}; handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate(_Reason, _StateName, StateData) -> - ?DEBUG("terminate: Deleting session ~s", [StateData#state.id]), + ?DEBUG("terminate: Deleting session ~s", + [StateData#state.id]), mnesia:dirty_delete({http_bind, StateData#state.id}), - send_receiver_reply(StateData#state.http_receiver, {ok, terminate}), + send_receiver_reply(StateData#state.http_receiver, + {ok, terminate}), case StateData#state.waiting_input of - false -> - ok; - C2SPid -> - gen_fsm:send_event(C2SPid, closed) + false -> ok; + C2SPid -> gen_fsm:send_event(C2SPid, closed) end, ok. @@ -591,704 +521,708 @@ terminate(_Reason, _StateName, StateData) -> %%% Internal functions %%%---------------------------------------------------------------------- -%% PUT / Get processing: -handle_http_put_event(#http_put{rid = Rid, attrs = Attrs, - hold = Hold} = Request, +handle_http_put_event(#http_put{rid = Rid, + attrs = Attrs, hold = Hold} = + Request, StateName, StateData) -> - ?DEBUG("New request: ~p",[Request]), - %% Check if Rid valid - RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, Hold, - StateData#state.max_pause), - - %% Check if Rid is in sequence or out of sequence: + ?DEBUG("New request: ~p", [Request]), + RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, + Hold, StateData#state.max_pause), case RidAllow of - buffer -> - ?DEBUG("Buffered request: ~p", [Request]), - %% Request is out of sequence: - PendingRequests = StateData#state.unprocessed_req_list, - %% In case an existing RID was already buffered: - Requests = lists:keydelete(Rid, 2, PendingRequests), - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = [] - } | - [El || El <- StateData#state.req_list, - El#hbr.rid > (Rid - 1 - Hold)] - ], - ?DEBUG("reqlist: ~p", [ReqList]), - UnprocessedReqList = [Request | Requests], - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(0, StateData#state.max_inactivity), - {reply, buffered, StateName, - StateData#state{unprocessed_req_list = UnprocessedReqList, - req_list = ReqList, - timer = Timer}}; - _ -> - %% Request is in sequence: - process_http_put(Request, StateName, StateData, RidAllow) + buffer -> + ?DEBUG("Buffered request: ~p", [Request]), + PendingRequests = StateData#state.unprocessed_req_list, + Requests = lists:keydelete(Rid, 2, PendingRequests), + ReqList = [#hbr{rid = Rid, key = StateData#state.key, + out = []} + | [El + || El <- StateData#state.req_list, + El#hbr.rid > Rid - 1 - Hold]], + ?DEBUG("reqlist: ~p", [ReqList]), + UnprocessedReqList = [Request | Requests], + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(0, + StateData#state.max_inactivity), + {reply, buffered, StateName, + StateData#state{unprocessed_req_list = + UnprocessedReqList, + req_list = ReqList, timer = Timer}}; + _ -> + process_http_put(Request, StateName, StateData, + RidAllow) end. -process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload, - hold = Hold, stream = StreamTo, - ip = IP} = Request, +process_http_put(#http_put{rid = Rid, attrs = Attrs, + payload = Payload, hold = Hold, stream = StreamTo, + ip = IP} = + Request, StateName, StateData, RidAllow) -> ?DEBUG("Actually processing request: ~p", [Request]), - %% Check if key valid - Key = xml:get_attr_s("key", Attrs), - NewKey = xml:get_attr_s("newkey", Attrs), - KeyAllow = - case RidAllow of - repeat -> - true; - false -> - false; - {true, _} -> - case StateData#state.key of - "" -> - true; - OldKey -> - NextKey = sha:sha(Key), - ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", [Key, OldKey, NextKey]), - if - OldKey == NextKey -> - true; - true -> - ?DEBUG("wrong key: ~s",[Key]), - false - end - end - end, + Key = xml:get_attr_s(<<"key">>, Attrs), + NewKey = xml:get_attr_s(<<"newkey">>, Attrs), + KeyAllow = case RidAllow of + repeat -> true; + false -> false; + {true, _} -> + case StateData#state.key of + <<"">> -> true; + OldKey -> + NextKey = sha:sha(Key), + ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", + [Key, OldKey, NextKey]), + if OldKey == NextKey -> true; + true -> ?DEBUG("wrong key: ~s", [Key]), false + end + end + end, TNow = tnow(), - LastPoll = if - Payload == [] -> - TNow; - true -> - 0 + LastPoll = if Payload == [] -> TNow; + true -> 0 end, - if - (Payload == []) and - (Hold == 0) and - (TNow - StateData#state.last_poll < ?MIN_POLLING) -> - Reply = {error, polling_too_frequently}, - {reply, Reply, StateName, StateData}; - KeyAllow -> - case RidAllow of - false -> - Reply = {error, not_exists}, - {reply, Reply, StateName, StateData}; - repeat -> - ?DEBUG("REPEATING ~p", [Rid]), - Reply = case [El#hbr.out || - El <- StateData#state.req_list, - El#hbr.rid == Rid] of - [] -> - {error, not_exists}; - [Out | _XS] -> - {repeat, lists:reverse(Out)} - end, - {reply, Reply, StateName, StateData#state{input = cancel, - last_poll = LastPoll}}; - {true, Pause} -> - SaveKey = if - NewKey == "" -> - Key; - true -> - NewKey - end, - ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), - - %% save request - ReqList1 = - [El || El <- StateData#state.req_list, - El#hbr.rid > (Rid - 1 - Hold)], - ReqList = - case lists:keymember(Rid, #hbr.rid, ReqList1) of - true -> - ReqList1; - false -> - [#hbr{rid = Rid, - key = StateData#state.key, - out = [] - } | - ReqList1 - ] - end, - ?DEBUG("reqlist: ~p", [ReqList]), - - %% setup next timer - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(Pause, - StateData#state.max_inactivity), - case StateData#state.waiting_input of - false -> - Input = - lists:foldl( - fun queue:in/2, - StateData#state.input, Payload), - Reply = ok, - process_buffered_request(Reply, StateName, - StateData#state{input = Input, - rid = Rid, - key = SaveKey, - ctime = TNow, - timer = Timer, - pause = Pause, - last_poll = LastPoll, - req_list = ReqList, - ip = IP - }); - C2SPid -> - case StreamTo of - {To, ""} -> - gen_fsm:send_event( - C2SPid, - {xmlstreamstart, "stream:stream", - [{"to", To}, - {"xmlns", ?NS_CLIENT}, - {"xmlns:stream", ?NS_STREAM}]}); - {To, Version} -> - gen_fsm:send_event( - C2SPid, - {xmlstreamstart, "stream:stream", - [{"to", To}, - {"xmlns", ?NS_CLIENT}, - {"version", Version}, - {"xmlns:stream", ?NS_STREAM}]}); - _ -> - ok - end, - - MaxInactivity = get_max_inactivity(StreamTo, StateData#state.max_inactivity), - MaxPause = get_max_inactivity(StreamTo, StateData#state.max_pause), - - ?DEBUG("really sending now: ~p", [Payload]), - lists:foreach( - fun({xmlstreamend, End}) -> - gen_fsm:send_event( - C2SPid, {xmlstreamend, End}); - (El) -> - gen_fsm:send_event( - C2SPid, {xmlstreamelement, El}) - end, Payload), - Reply = ok, - process_buffered_request(Reply, StateName, - StateData#state{input = queue:new(), - rid = Rid, - key = SaveKey, - ctime = TNow, - timer = Timer, - pause = Pause, - last_poll = LastPoll, - req_list = ReqList, - max_inactivity = MaxInactivity, - max_pause = MaxPause, - ip = IP - }) - end - end; - true -> - Reply = {error, bad_key}, - {reply, Reply, StateName, StateData} + if (Payload == []) and (Hold == 0) and + (TNow - StateData#state.last_poll < (?MIN_POLLING)) -> + Reply = {error, polling_too_frequently}, + {reply, Reply, StateName, StateData}; + KeyAllow -> + case RidAllow of + false -> + Reply = {error, not_exists}, + {reply, Reply, StateName, StateData}; + repeat -> + ?DEBUG("REPEATING ~p", [Rid]), + Reply = case [El#hbr.out + || El <- StateData#state.req_list, + El#hbr.rid == Rid] + of + [] -> {error, not_exists}; + [Out | _XS] -> {repeat, lists:reverse(Out)} + end, + {reply, Reply, StateName, + StateData#state{input = cancel, last_poll = LastPoll}}; + {true, Pause} -> + SaveKey = if NewKey == <<"">> -> Key; + true -> NewKey + end, + ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), + ReqList1 = [El + || El <- StateData#state.req_list, + El#hbr.rid > Rid - 1 - Hold], + ReqList = case lists:keymember(Rid, #hbr.rid, ReqList1) + of + true -> ReqList1; + false -> + [#hbr{rid = Rid, key = StateData#state.key, + out = []} + | ReqList1] + end, + ?DEBUG("reqlist: ~p", [ReqList]), + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(Pause, + StateData#state.max_inactivity), + case StateData#state.waiting_input of + false -> + Input = lists:foldl(fun queue:in/2, + StateData#state.input, Payload), + Reply = ok, + process_buffered_request(Reply, StateName, + StateData#state{input = Input, + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + pause = Pause, + last_poll = + LastPoll, + req_list = + ReqList, + ip = IP}); + C2SPid -> + case StreamTo of + {To, <<"">>} -> + gen_fsm:send_event(C2SPid, + {xmlstreamstart, + <<"stream:stream">>, + [{<<"to">>, To}, + {<<"xmlns">>, ?NS_CLIENT}, + {<<"xmlns:stream">>, + ?NS_STREAM}]}); + {To, Version} -> + gen_fsm:send_event(C2SPid, + {xmlstreamstart, + <<"stream:stream">>, + [{<<"to">>, To}, + {<<"xmlns">>, ?NS_CLIENT}, + {<<"version">>, Version}, + {<<"xmlns:stream">>, + ?NS_STREAM}]}); + _ -> ok + end, + MaxInactivity = get_max_inactivity(StreamTo, + StateData#state.max_inactivity), + MaxPause = get_max_inactivity(StreamTo, + StateData#state.max_pause), + ?DEBUG("really sending now: ~p", [Payload]), + lists:foreach(fun ({xmlstreamend, End}) -> + gen_fsm:send_event(C2SPid, + {xmlstreamend, + End}); + (El) -> + gen_fsm:send_event(C2SPid, + {xmlstreamelement, + El}) + end, + Payload), + Reply = ok, + process_buffered_request(Reply, StateName, + StateData#state{input = + queue:new(), + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + pause = Pause, + last_poll = + LastPoll, + req_list = + ReqList, + max_inactivity = + MaxInactivity, + max_pause = + MaxPause, + ip = IP}) + end + end; + true -> + Reply = {error, bad_key}, + {reply, Reply, StateName, StateData} end. process_buffered_request(Reply, StateName, StateData) -> Rid = StateData#state.rid, Requests = StateData#state.unprocessed_req_list, - case lists:keysearch(Rid+1, 2, Requests) of - {value, Request} -> - ?DEBUG("Processing buffered request: ~p", [Request]), - NewRequests = lists:keydelete(Rid+1, 2, Requests), - handle_http_put_event( - Request, StateName, - StateData#state{unprocessed_req_list = NewRequests}); - _ -> - {reply, Reply, StateName, StateData, hibernate} + case lists:keysearch(Rid + 1, 2, Requests) of + {value, Request} -> + ?DEBUG("Processing buffered request: ~p", [Request]), + NewRequests = lists:keydelete(Rid + 1, 2, Requests), + handle_http_put_event(Request, StateName, + StateData#state{unprocessed_req_list = + NewRequests}); + _ -> {reply, Reply, StateName, StateData, hibernate} end. -handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> - case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of - {error, not_exists} -> - ?DEBUG("no session associated with sid: ~p", [Sid]), - {404, ?HEADER, ""}; - {{error, Reason}, Sess} -> - ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]), - handle_http_put_error(Reason, Sess); - {{repeat, OutPacket}, Sess} -> - ?DEBUG("http_put said \"repeat!\" ...~nOutPacket: ~p", [OutPacket]), - send_outpacket(Sess, OutPacket); - {{wait, Pause}, _Sess} -> - ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), - timer:sleep(Pause), - %{200, ?HEADER, - % xmpp_json:to_json( - % {xmlelement, "body", - % [{"xmlns", ?NS_HTTP_BIND}, - % {"type", "error"}], []})}; - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, - StreamStart, IP); - {buffered, _Sess} -> - {200, ?HEADER, "{\"body\":{ \"xmlns\":\""++?NS_HTTP_BIND++"\"}}"}; - {ok, Sess} -> - prepare_response(Sess, Rid, [], StreamStart) +handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP) -> + case http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP) + of + {error, not_exists} -> + ?DEBUG("no session associated with sid: ~p", [Sid]), + {404, ?HEADER, <<"">>}; + {{error, Reason}, Sess} -> + ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]), + handle_http_put_error(Reason, Sess); + {{repeat, OutPacket}, Sess} -> + ?DEBUG("http_put said \"repeat!\" ...~nOutPacket: ~p", + [OutPacket]), + send_outpacket(Sess, OutPacket); + {{wait, Pause}, _Sess} -> + ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), + timer:sleep(Pause), + handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP); + {buffered, _Sess} -> + {200, ?HEADER, + <<"{\"body\":{ \"xmlns\":\"", (?NS_HTTP_BIND)/binary, + "\"}}">>}; + {ok, Sess} -> + prepare_response(Sess, Rid, [], StreamStart) end. -http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> +http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP) -> ?DEBUG("Looking for session: ~p", [Sid]), case get_session(Sid) of - {error, _} -> - {error, not_exists}; - {ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}-> - NewStream = - case StreamStart of - true -> - {To, StreamVersion}; - _ -> - "" - end, - {gen_fsm:sync_send_all_state_event( - FsmRef, #http_put{rid = Rid, attrs = Attrs, payload = Payload, - payload_size = PayloadSize, hold = Hold, - stream = NewStream, ip = IP}, 30000), Sess} + {error, _} -> {error, not_exists}; + {ok, + #http_bind{pid = FsmRef, hold = Hold, + to = {To, StreamVersion}} = + Sess} -> + NewStream = case StreamStart of + true -> {To, StreamVersion}; + _ -> <<"">> + end, + {gen_fsm:sync_send_all_state_event(FsmRef, + #http_put{rid = Rid, attrs = Attrs, + payload = Payload, + payload_size = + PayloadSize, + hold = Hold, + stream = NewStream, + ip = IP}, + 30000), + Sess} end. -handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version}) - when Version >= 0 -> - gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}), +handle_http_put_error(Reason, + #http_bind{pid = FsmRef, version = Version}) + when Version >= 0 -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, {put_error, Reason}}), case Reason of - not_exists -> - {200, ?HEADER, - mochijson2:encode( - xmpp_json:to_json( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, - {"type", "terminate"}, - {"condition", "item-not-found"}], []}))}; - bad_key -> - {200, ?HEADER, - mochijson2:encode( - xmpp_json:to_json( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, - {"type", "terminate"}, - {"condition", "item-not-found"}], []}))}; - polling_too_frequently -> - {200, ?HEADER, - mochijson2:encode( - xmpp_json:to_json( - {xmlelement, "body", - [{"xmlns", ?NS_HTTP_BIND}, - {"type", "terminate"}, - {"condition", "policy-violation"}], []}))} + not_exists -> + {200, ?HEADER, + jiffy:encode(xmpp_json:to_json(#xmlel{name = + <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}, + {<<"type">>, + <<"terminate">>}, + {<<"condition">>, + <<"item-not-found">>}], + children = []}))}; + bad_key -> + {200, ?HEADER, + jiffy:encode(xmpp_json:to_json(#xmlel{name = + <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}, + {<<"type">>, + <<"terminate">>}, + {<<"condition">>, + <<"item-not-found">>}], + children = []}))}; + polling_too_frequently -> + {200, ?HEADER, + jiffy:encode(xmpp_json:to_json(#xmlel{name = + <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}, + {<<"type">>, + <<"terminate">>}, + {<<"condition">>, + <<"policy-violation">>}], + children = []}))} end; -handle_http_put_error(Reason, #http_bind{pid=FsmRef}) -> - gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}), +handle_http_put_error(Reason, + #http_bind{pid = FsmRef}) -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, {put_error_no_version, Reason}}), case Reason of - not_exists -> %% bad rid - ?DEBUG("Closing HTTP bind session (Bad rid).", []), - {404, ?HEADER, ""}; - bad_key -> - ?DEBUG("Closing HTTP bind session (Bad key).", []), - {404, ?HEADER, ""}; - polling_too_frequently -> - ?DEBUG("Closing HTTP bind session (User polling too frequently).", []), - {403, ?HEADER, ""} + not_exists -> %% bad rid + ?DEBUG("Closing HTTP bind session (Bad rid).", []), + {404, ?HEADER, <<"">>}; + bad_key -> + ?DEBUG("Closing HTTP bind session (Bad key).", []), + {404, ?HEADER, <<"">>}; + polling_too_frequently -> + ?DEBUG("Closing HTTP bind session (User polling " + "too frequently).", + []), + {403, ?HEADER, <<"">>} end. -%% Control RID ordering rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) -> - %% First request - nothing saved so far {true, 0}; rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) -> - ?DEBUG("Previous rid / New rid: ~p/~p", [OldRid, NewRid]), + ?DEBUG("Previous rid / New rid: ~p/~p", + [OldRid, NewRid]), if - %% We did not miss any packet, we can process it immediately: - NewRid == OldRid + 1 -> - case catch list_to_integer( - xml:get_attr_s("pause", Attrs)) of - {'EXIT', _} -> - {true, 0}; - Pause1 when Pause1 =< MaxPause -> - ?DEBUG("got pause: ~p", [Pause1]), - {true, Pause1}; - _ -> - {true, 0} - end; - %% We have missed packets, we need to cached it to process it later on: - (OldRid < NewRid) and - (NewRid =< (OldRid + Hold + 1)) -> - buffer; - (NewRid =< OldRid) and - (NewRid > OldRid - Hold - 1) -> - repeat; - true -> - false + %% We did not miss any packet, we can process it immediately: + NewRid == OldRid + 1 -> + case catch + jlib:binary_to_integer(xml:get_attr_s(<<"pause">>, + Attrs)) + of + {'EXIT', _} -> {true, 0}; + Pause1 when Pause1 =< MaxPause -> + ?DEBUG("got pause: ~p", [Pause1]), {true, Pause1}; + _ -> {true, 0} + end; + %% We have missed packets, we need to cached it to process it later on: + (OldRid < NewRid) and (NewRid =< OldRid + Hold + 1) -> + buffer; + (NewRid =< OldRid) and (NewRid > OldRid - Hold - 1) -> + repeat; + true -> false end. update_shaper(ShaperState, PayloadSize) -> - {NewShaperState, Pause} = shaper:update(ShaperState, PayloadSize), - if - Pause > 0 -> - ShaperTimer = erlang:start_timer(Pause, self(), activate), %% MR: Seems timer is not needed. Activate is not handled - {NewShaperState, ShaperTimer}; - true -> - {NewShaperState, undefined} + {NewShaperState, Pause} = shaper:update(ShaperState, + PayloadSize), + if Pause > 0 -> + ShaperTimer = erlang:start_timer(Pause, self(), + activate), + {NewShaperState, ShaperTimer}; + true -> {NewShaperState, undefined} end. prepare_response(Sess, Rid, OutputEls, StreamStart) -> - receive after Sess#http_bind.process_delay -> ok end, + receive after Sess#http_bind.process_delay -> ok end, case catch http_get(Sess, Rid) of - {ok, cancel} -> - %% actually it would be better if we could completely - %% cancel this request, but then we would have to hack - %% ejabberd_http and I'm too lazy now - {200, ?HEADER, "{\"body\": {\"type\":\"error\", \"xmlns\":\""++?NS_HTTP_BIND++"\"/>"}; - {ok, empty} -> - {200, ?HEADER, "{\"body\":{ \"xmlns\":\""++?NS_HTTP_BIND++"\"}}"}; - {ok, terminate} -> - {200, ?HEADER, "{\"body\": {\"type\":\"terminate\", \"xmlns\":\""++?NS_HTTP_BIND++"\"/>"}; - {ok, ROutPacket} -> - OutPacket = lists:reverse(ROutPacket), - ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]), - prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart); - {'EXIT', {shutdown, _}} -> - {200, ?HEADER, "{\"body\": {\"type\":\"terminate\",\"condition\":\"system-shutdown\", \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"}; - {'EXIT', _Reason} -> - {200, ?HEADER, "{\"body\": {\"type\":\"terminate, \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"} + {ok, cancel} -> + {200, ?HEADER, + <<"{\"body\": {\"type\":\"error\", \"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\"/>">>}; + {ok, empty} -> + {200, ?HEADER, + <<"{\"body\":{ \"xmlns\":\"", (?NS_HTTP_BIND)/binary, + "\"}}">>}; + {ok, terminate} -> + {200, ?HEADER, + <<"{\"body\": {\"type\":\"terminate\", " + "\"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\"/>">>}; + {ok, ROutPacket} -> + OutPacket = lists:reverse(ROutPacket), + ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]), + prepare_outpacket_response(Sess, Rid, + OutputEls ++ OutPacket, StreamStart); + {'EXIT', {shutdown, _}} -> + {200, ?HEADER, + <<"{\"body\": {\"type\":\"terminate\",\"conditio" + "n\":\"system-shutdown\", \"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\"}}">>}; + {'EXIT', _Reason} -> + {200, ?HEADER, + <<"{\"body\": {\"type\":\"terminate, \"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\"}}">>} end. -%% Send output payloads on establised sessions -prepare_outpacket_response(Sess, _Rid, OutPacket, false) -> +prepare_outpacket_response(Sess, _Rid, OutPacket, + false) -> case catch send_outpacket(Sess, OutPacket) of - {'EXIT', _Reason} -> - {200, ?HEADER, - "{\"body\": {\"type\":\"terminate\", \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"}; - SendRes -> - SendRes + {'EXIT', _Reason} -> + {200, ?HEADER, + <<"{\"body\": {\"type\":\"terminate\", " + "\"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\"}}">>}; + SendRes -> SendRes end; %% Handle a new session along with its output payload -prepare_outpacket_response(#http_bind{id=Sid, wait=Wait, - hold=Hold, to=To}=Sess, - Rid, OutPacket, true) -> +prepare_outpacket_response(#http_bind{id = Sid, + wait = Wait, hold = Hold, to = To} = + Sess, + Rid, OutPacket, true) -> case OutPacket of - [{xmlstreamstart, _, OutAttrs} | Els] -> - AuthID = xml:get_attr_s("id", OutAttrs), - From = xml:get_attr_s("from", OutAttrs), - Version = xml:get_attr_s("version", OutAttrs), - OutEls = - case Els of - [] -> - []; - [{xmlstreamelement, - {xmlelement, "stream:features", - StreamAttribs, StreamEls}} - | StreamTail] -> - TypedTail = - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail], - [{xmlelement, "stream:features", - [{"xmlns:stream", - ?NS_STREAM}] ++ - StreamAttribs, StreamEls}] ++ - TypedTail; - StreamTail -> - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail] - end, - case OutEls of - [] -> - prepare_response(Sess, Rid, OutPacket, true); - [{xmlelement, - "stream:error",_,_}] -> - {200, ?HEADER, "{\"body\" : {\"type\":\"terminate\", " - "\"condition\":\"host-unknown\", " - "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"}; - _ -> - BOSH_attribs = - [{"authid", AuthID}, - {"xmlns:xmpp", ?NS_BOSH}, - {"xmlns:stream", ?NS_STREAM}] ++ - case OutEls of - [] -> - []; - _ -> - [{"xmpp:version", Version}] - end, - MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), - MaxPause = get_max_pause(To), - {200, ?HEADER, - mochijson2:encode( - xmpp_json:to_json( - {xmlelement,"body", - [{"xmlns", - ?NS_HTTP_BIND}, - {"sid", Sid}, - {"wait", integer_to_list(Wait)}, - {"requests", integer_to_list(Hold+1)}, - {"inactivity", - integer_to_list( - trunc(MaxInactivity/1000))}, - {"maxpause", - integer_to_list(MaxPause)}, - {"polling", - integer_to_list( - trunc(?MIN_POLLING/1000000))}, - {"ver", ?BOSH_VERSION}, - {"from", From}, - {"secure", "true"} %% we're always being secure - ] ++ BOSH_attribs,OutEls}))} - end; - _ -> - {200, ?HEADER, "{\"body\" : {\"type\":\"terminate\", " - "\"condition\":\"internal-server-error\", " - "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"} + [{xmlstreamstart, _, OutAttrs} | Els] -> + AuthID = xml:get_attr_s(<<"id">>, OutAttrs), + From = xml:get_attr_s(<<"from">>, OutAttrs), + Version = xml:get_attr_s(<<"version">>, OutAttrs), + OutEls = case Els of + [] -> []; + [{xmlstreamelement, + #xmlel{name = <<"stream:features">>, + attrs = StreamAttribs, children = StreamEls}} + | StreamTail] -> + TypedTail = [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- StreamTail], + [#xmlel{name = <<"stream:features">>, + attrs = + [{<<"xmlns:stream">>, ?NS_STREAM}] ++ + StreamAttribs, + children = StreamEls}] + ++ TypedTail; + StreamTail -> + [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- StreamTail] + end, + case OutEls of + [] -> prepare_response(Sess, Rid, OutPacket, true); + [#xmlel{name = <<"stream:error">>}] -> + {200, ?HEADER, + <<"{\"body\" : {\"type\":\"terminate\", " + "\"condition\":\"host-unknown\", \"xmlns\"=\"", + (?NS_HTTP_BIND)/binary, "\"}}">>}; + _ -> + BOSH_attribs = [{<<"authid">>, AuthID}, + {<<"xmlns:xmpp">>, ?NS_BOSH}, + {<<"xmlns:stream">>, ?NS_STREAM}, + {<<"xmpp:version">>, Version}], +% ++ +% case OutEls of +% [] -> []; +% _ -> [{<<"xmpp:version">>, Version}] +% end, + MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), + MaxPause = get_max_pause(To), + {200, ?HEADER, + jiffy:encode(xmpp_json:to_json(#xmlel{name = + <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}, + {<<"sid">>, + Sid}, + {<<"wait">>, + iolist_to_binary(integer_to_list(Wait))}, + {<<"requests">>, + iolist_to_binary(integer_to_list(Hold + + + 1))}, + {<<"inactivity">>, + iolist_to_binary(integer_to_list(trunc(MaxInactivity + / + 1000)))}, + {<<"maxpause">>, + iolist_to_binary(integer_to_list(MaxPause))}, + {<<"polling">>, + iolist_to_binary(integer_to_list(trunc((?MIN_POLLING) + / + 1000000)))}, + {<<"ver">>, + ?BOSH_VERSION}, + {<<"from">>, + From}, + {<<"secure">>, + <<"true">>}] + ++ + BOSH_attribs, + children = + OutEls}))} + end; + _ -> + {200, ?HEADER, + <<"{\"body\" : {\"type\":\"terminate\", " + "\"condition\":\"internal-server-error\", " + "\"xmlns\"=\"", + (?NS_HTTP_BIND)/binary, "\"}}">>} end. - -http_get(#http_bind{pid = FsmRef, wait = Wait, hold = Hold}, Rid) -> - gen_fsm:sync_send_all_state_event( - FsmRef, {http_get, Rid, Wait, Hold}, 2 * ?MAX_WAIT * 1000). +http_get(#http_bind{pid = FsmRef, wait = Wait, + hold = Hold}, + Rid) -> + gen_fsm:sync_send_all_state_event(FsmRef, + {http_get, Rid, Wait, Hold}, + 2 * (?MAX_WAIT) * 1000). send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> case OutPacket of - [] -> - {200, ?HEADER, "{\"body\": {\"xmlns\":\""++?NS_HTTP_BIND++"\"}}"}; - [{xmlstreamend, _}] -> - gen_fsm:sync_send_all_state_event(FsmRef,{stop,stream_closed}), - {200, ?HEADER, "{\"body\": {\"xmlns\":"++?NS_HTTP_BIND++"\"}}"}; - _ -> - %% TODO: We parse to add a default namespace to packet, - %% The spec says adding the jabber:client namespace if - %% mandatory, even if some implementation do not do that - %% change on packets. - %% I think this should be an option to avoid modifying - %% packet in most case. - AllElements = - lists:all(fun({xmlstreamelement, - {xmlelement, "stream:error", _, _}}) -> false; - ({xmlstreamelement, _}) -> true; - (_) -> false - end, OutPacket), - case AllElements of - true -> - TypedEls = [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- OutPacket], - Body = mochijson2:encode(xmpp_json:to_json( - {xmlelement,"body", - [{"xmlns", - ?NS_HTTP_BIND}], - TypedEls})), - ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n", - [Body]), - {200, ?HEADER, Body}; - false -> - case OutPacket of - [{xmlstreamstart, _, _} | SEls] -> - OutEls = - case SEls of - [{xmlstreamelement, - {xmlelement, - "stream:features", - StreamAttribs, StreamEls}} | - StreamTail] -> - TypedTail = - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail], - [{xmlelement, - "stream:features", - [{"xmlns:stream", - ?NS_STREAM}] ++ - StreamAttribs, StreamEls}] ++ - TypedTail; - StreamTail -> - [check_default_xmlns(OEl) || - {xmlstreamelement, OEl} <- - StreamTail] - end, - {200, ?HEADER, - mochijson2:encode( - xmpp_json:to_json( - {xmlelement,"body", - [{"xmlns", - ?NS_HTTP_BIND}], - OutEls}))}; + [] -> + {200, ?HEADER, + <<"{\"body\": {\"xmlns\":\"", (?NS_HTTP_BIND)/binary, + "\"}}">>}; + [{xmlstreamend, _}] -> + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, stream_closed}), + {200, ?HEADER, + <<"{\"body\": {\"xmlns\":", (?NS_HTTP_BIND)/binary, + "\"}}">>}; + _ -> + AllElements = lists:all(fun ({xmlstreamelement, + #xmlel{name = <<"stream:error">>}}) -> + false; + ({xmlstreamelement, _}) -> true; + (_) -> false + end, + OutPacket), + case AllElements of + true -> + TypedEls = [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- OutPacket], + Body = jiffy:encode(xmpp_json:to_json(#xmlel{name = + <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}], + children = + TypedEls})), + ?DEBUG(" --- outgoing data --- ~n~s~n --- END " + "--- ~n", + [Body]), + {200, ?HEADER, Body}; + false -> + case OutPacket of + [{xmlstreamstart, _, _} | SEls] -> + OutEls = case SEls of + [{xmlstreamelement, + #xmlel{name = <<"stream:features">>, + attrs = StreamAttribs, + children = StreamEls}} + | StreamTail] -> + TypedTail = [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} + <- StreamTail], + [#xmlel{name = <<"stream:features">>, + attrs = + [{<<"xmlns:stream">>, + ?NS_STREAM}] + ++ StreamAttribs, + children = StreamEls}] + ++ TypedTail; + StreamTail -> + [check_default_xmlns(OEl) + || {xmlstreamelement, OEl} <- StreamTail] + end, + {200, ?HEADER, + jiffy:encode(xmpp_json:to_json(#xmlel{name = + <<"body">>, + attrs = + [{<<"xmlns">>, + ?NS_HTTP_BIND}], + children = + OutEls}))}; + _ -> + SErrCond = lists:filter(fun ({xmlstreamelement, + #xmlel{name = + <<"stream:error">>}}) -> + true; + (_) -> false + end, + OutPacket), + StreamErrCond = case SErrCond of + [] -> null; + [{xmlstreamelement, + #xmlel{} = StreamErrorTag} + | _] -> + [StreamErrorTag] + end, + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, + {stream_error, + OutPacket}}), + case StreamErrCond of + null -> + {200, ?HEADER, + <<"{\"body\" : {\"\"type\"\":\"terminate\", " + "\"condition\":\"internal-server-error\", " + "\"xmlns\"=\"", + (?NS_HTTP_BIND)/binary, "\"}}">>}; _ -> - SErrCond = - lists:filter( - fun({xmlstreamelement, - {xmlelement, "stream:error", - _, _}}) -> - true; - (_) -> false - end, OutPacket), - StreamErrCond = - case SErrCond of - [] -> - null; - [{xmlstreamelement, - {xmlelement, _, _, _Cond} = - StreamErrorTag} | _] -> - [StreamErrorTag] - end, - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, {stream_error,OutPacket}}), - case StreamErrCond of - null -> - {200, ?HEADER, - "{\"body\" : {\"\"type\"\":\"terminate\", " - "\"condition\":\"internal-server-error\", " - "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"}; - _ -> - {200, ?HEADER, - "{\"body\" : {\"\"type\"\":\"terminate\", " - "\"condition\":\"remote-stream-error\", " - "\"xmlns\":\""++?NS_HTTP_BIND++"\", " ++ - "\"xmlns:stream\":\""++?NS_STREAM++"\" \"$\":" ++ - elements_to_string(StreamErrCond) ++ - "}}"} - end - end - end + {200, ?HEADER, + <<"{\"body\" : {\"\"type\"\":\"terminate\", " + "\"condition\":\"remote-stream-error\", " + "\"xmlns\":\"", + (?NS_HTTP_BIND)/binary, "\", ", + "\"xmlns:stream\":\"", (?NS_STREAM)/binary, + "\" \"$\":", + (elements_to_string(StreamErrCond))/binary, + "}}">>} + end + end + end end. parse_request(Data, PayloadSize, MaxStanzaSize) -> - ?DEBUG("--- incoming data --- ~n~p~n --- END --- ", [xmpp_json:from_json(mochijson2:decode(Data))]), - %% MR: I do not think it works if put put several elements in the - %% same body: - case xmpp_json:from_json(mochijson2:decode(Data)) of - {xmlstreamelement,{xmlelement, "body", Attrs, Els}} -> - Xmlns = xml:get_attr_s("xmlns",Attrs), - if - Xmlns /= ?NS_HTTP_BIND -> - {error, bad_request}; - true -> - case catch list_to_integer(xml:get_attr_s("rid", Attrs)) of - {'EXIT', _} -> - {error, bad_request}; - Rid -> - %% I guess this is to remove XMLCDATA: Is it really needed ? - FixedEls = - lists:filter( - fun(I) -> - case I of - {xmlelement, _, _, _} -> - true; - _ -> - false - end - end, Els), - Sid = xml:get_attr_s("sid",Attrs), - if - PayloadSize =< MaxStanzaSize -> - {ok, {Sid, Rid, Attrs, FixedEls}}; - true -> - {size_limit, Sid} - end - end - end; - {xmlstreamelement,{xmlelement, _Name, _Attrs, _Els}} -> - {error, bad_request}; - {error, _Reason} -> - {error, bad_request} + ?DEBUG("--- incoming data --- ~n~p~n --- END " + "--- ", + [xmpp_json:from_json(jiffy:decode(Data))]), + case xmpp_json:from_json(jiffy:decode(Data)) of + {xmlstreamelement, + #xmlel{name = <<"body">>, attrs = Attrs, + children = Els}} -> + Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs), + if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request}; + true -> + case catch + jlib:binary_to_integer(xml:get_attr_s(<<"rid">>, + Attrs)) + of + {'EXIT', _} -> {error, bad_request}; + Rid -> + FixedEls = lists:filter(fun (I) -> + case I of + #xmlel{} -> true; + _ -> false + end + end, + Els), + Sid = xml:get_attr_s(<<"sid">>, Attrs), + if PayloadSize =< MaxStanzaSize -> + {ok, {Sid, Rid, Attrs, FixedEls}}; + true -> {size_limit, Sid} + end + end + end; + _ -> {error, bad_request} end. -send_receiver_reply(undefined, _Reply) -> - ok; +send_receiver_reply(undefined, _Reply) -> ok; send_receiver_reply(Receiver, Reply) -> gen_fsm:reply(Receiver, Reply). - -%% Cancel timer and empty message queue. -cancel_timer(undefined) -> - ok; +cancel_timer(undefined) -> ok; cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. -%% If client asked for a pause (pause > 0), we apply the pause value -%% as inactivity timer: -set_inactivity_timer(Pause, _MaxInactivity) when Pause > 0 -> - erlang:start_timer(Pause*1000, self(), []); +set_inactivity_timer(Pause, _MaxInactivity) + when Pause > 0 -> + erlang:start_timer(Pause * 1000, self(), []); %% Otherwise, we apply the max_inactivity value as inactivity timer: set_inactivity_timer(_Pause, MaxInactivity) -> erlang:start_timer(MaxInactivity, self(), []). - -%% TODO: Use tail recursion and list reverse ? -elements_to_string([]) -> - []; +elements_to_string([]) -> []; elements_to_string([El | Els]) -> - [mochijson2:encode(xmpp_json:to_json(El))|elements_to_string(Els)]. + [jiffy:encode(xmpp_json:to_json(El)) + | elements_to_string(Els)]. -%% @spec (To, Default::integer()) -> integer() -%% where To = [] | {Host::string(), Version::string()} get_max_inactivity({Host, _}, Default) -> - case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, undefined) of - Seconds when is_integer(Seconds) -> - Seconds * 1000; - undefined -> - Default + case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, + fun(I) when is_integer(I), I>0 -> I end, + undefined) + of + Seconds when is_integer(Seconds) -> Seconds * 1000; + undefined -> Default end; -get_max_inactivity(_, Default) -> - Default. +get_max_inactivity(_, Default) -> Default. get_max_pause({Host, _}) -> - gen_mod:get_module_opt(Host, mod_http_bind, max_pause, ?MAX_PAUSE); -get_max_pause(_) -> - ?MAX_PAUSE. + gen_mod:get_module_opt(Host, mod_http_bind, max_pause, + fun(I) when is_integer(I), I>0 -> I end, + ?MAX_PAUSE); +get_max_pause(_) -> ?MAX_PAUSE. -%% Current time as integer tnow() -> {TMegSec, TSec, TMSec} = now(), (TMegSec * 1000000 + TSec) * 1000000 + TMSec. -check_default_xmlns({xmlelement, Name, Attrs, Els} = El) -> - case xml:get_tag_attr_s("xmlns", El) of - "" -> {xmlelement, Name, [{"xmlns", ?NS_CLIENT} | Attrs], Els}; - _ -> El +check_default_xmlns(#xmlel{name = Name, attrs = Attrs, + children = Els} = + El) -> + case xml:get_tag_attr_s(<<"xmlns">>, El) of + <<"">> -> + #xmlel{name = Name, + attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs], + children = Els}; + _ -> El end. -%% Check that mod_http_bind has been defined in config file. -%% Print a warning in log file if this is not the case. check_bind_module(XmppDomain) -> case gen_mod:is_loaded(XmppDomain, mod_http_bind) of - true -> true; - false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), but the module mod_http_bind is not started.~n" - "Check your 'modules' section in your ejabberd configuration file.",[]), - false + true -> true; + false -> + ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), " + "but the module mod_http_bind is not " + "started.~nCheck your 'modules' section " + "in your ejabberd configuration file.", + []), + false end. make_sid() -> - sha:sha(term_to_binary({now(), make_ref()})) - ++ "-" ++ ejabberd_cluster:node_id(). + <<(sha:sha(term_to_binary({now(), make_ref()})))/binary, + "-", (ejabberd_cluster:node_id())/binary>>. get_session(SID) -> - case string:tokens(SID, "-") of - [_, NodeID] -> - case ejabberd_cluster:get_node_by_id(NodeID) of - Node when Node == node() -> - case mnesia:dirty_read({http_bind, SID}) of - [] -> - {error, enoent}; - [Session] -> - {ok, Session} - end; - Node -> - case catch rpc:call(Node, mnesia, dirty_read, - [{http_bind, SID}], 5000) of - [Session] -> - {ok, Session}; - _ -> - {error, enoent} - end - end; - _ -> - {error, enoent} + case str:tokens(SID, <<"-">>) of + [_, NodeID] -> + case ejabberd_cluster:get_node_by_id(NodeID) of + Node when Node == node() -> + case mnesia:dirty_read({http_bind, SID}) of + [] -> {error, enoent}; + [Session] -> {ok, Session} + end; + Node -> + case catch rpc:call(Node, mnesia, dirty_read, + [{http_bind, SID}], 5000) + of + [Session] -> {ok, Session}; + _ -> {error, enoent} + end + end; + _ -> {error, enoent} end. diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl index cde8124f9..937009014 100644 --- a/src/web/ejabberd_http_poll.erl +++ b/src/web/ejabberd_http_poll.erl @@ -25,210 +25,191 @@ %%%---------------------------------------------------------------------- -module(ejabberd_http_poll). + -author('alexey@process-one.net'). -behaviour(gen_fsm). %% External exports --export([start_link/3, - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - send/2, - setopts/2, - sockname/1, peername/1, - controlling_process/2, - close/1, - process/2]). +-export([start_link/3, init/1, handle_event/3, + handle_sync_event/4, code_change/4, handle_info/3, + terminate/3, send/2, setopts/2, sockname/1, peername/1, + controlling_process/2, close/1, process/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). --record(http_poll, {id, pid}). +-record(http_poll, {id :: pid() | binary(), pid :: pid()}). + +-type poll_socket() :: #http_poll{}. +-export_type([poll_socket/0]). --record(state, {id, - key, - socket, - output = "", - input = "", - waiting_input = false, %% {ReceiverPid, Tag} - last_receiver, - http_poll_timeout, - timer}). +-record(state, + {id, key, socket, output = <<"">>, input = <<"">>, + waiting_input = false, last_receiver, http_poll_timeout, + timer}). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. --define(HTTP_POLL_TIMEOUT, 300000). --define(CT, {"Content-Type", "text/xml; charset=utf-8"}). --define(BAD_REQUEST, [?CT, {"Set-Cookie", "ID=-3:0; expires=-1"}]). +-define(HTTP_POLL_TIMEOUT, 300). +-define(CT, + {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). + +-define(BAD_REQUEST, + [?CT, {<<"Set-Cookie">>, <<"ID=-3:0; expires=-1">>}]). -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- start(ID, Key, IP) -> update_tables(), mnesia:create_table(http_poll, - [{ram_copies, [node()]}, - {local_content, true}, - {attributes, record_info(fields, http_poll)}]), + [{ram_copies, [node()]}, {local_content, true}, + {attributes, record_info(fields, http_poll)}]), mnesia:add_table_copy(http_poll, node(), ram_copies), - supervisor:start_child(ejabberd_http_poll_sup, [ID, Key, IP]). + supervisor:start_child(ejabberd_http_poll_sup, + [ID, Key, IP]). start_link(ID, Key, IP) -> gen_fsm:start_link(?MODULE, [ID, Key, IP], ?FSMOPTS). send({http_poll, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send, Packet}). setopts({http_poll, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - ok + true -> + gen_fsm:send_all_state_event(FsmRef, + {activate, self()}); + _ -> ok end. -sockname(_Socket) -> - {ok, {{0, 0, 0, 0}, 0}}. +sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. -peername({http_poll, _FsmRef, IP}) -> - {ok, IP}. +peername({http_poll, _FsmRef, IP}) -> {ok, IP}. -controlling_process(_Socket, _Pid) -> - ok. +controlling_process(_Socket, _Pid) -> ok. close({http_poll, FsmRef, _IP}) -> catch gen_fsm:sync_send_all_state_event(FsmRef, close). - -process([], #request{data = Data, - ip = IP} = _Request) -> +process([], + #request{data = Data, ip = IP} = _Request) -> case catch parse_request(Data) of - {ok, ID1, Key, NewKey, Packet} -> - ID = if - (ID1 == "0") or (ID1 == "mobile") -> - NewID = make_sid(), - {ok, Pid} = start(NewID, "", IP), - mnesia:async_dirty( - fun() -> - mnesia:write(#http_poll{id = NewID, - pid = Pid}) - end), - NewID; - true -> - ID1 - end, - case http_put(ID, Key, NewKey, Packet) of - {error, not_exists} -> - {200, ?BAD_REQUEST, ""}; - {error, bad_key} -> - {200, ?BAD_REQUEST, ""}; - ok -> - receive - after 100 -> ok - end, - case http_get(ID) of - {error, not_exists} -> - {200, ?BAD_REQUEST, ""}; - {ok, OutPacket} -> - if - ID == ID1 -> - Cookie = "ID=" ++ ID ++ "; expires=-1", - {200, [?CT, {"Set-Cookie", Cookie}], - OutPacket}; - ID1 == "mobile" -> - {200, [?CT], [ID, $\n, OutPacket]}; - true -> - Cookie = "ID=" ++ ID ++ "; expires=-1", - {200, [?CT, {"Set-Cookie", Cookie}], - OutPacket} - end - end - end; - _ -> - HumanHTMLxmlel = get_human_html_xmlel(), - {200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], HumanHTMLxmlel} + {ok, ID1, Key, NewKey, Packet} -> + ID = if (ID1 == <<"0">>) or (ID1 == <<"mobile">>) -> + NewID = make_sid(), + {ok, Pid} = start(NewID, <<"">>, IP), + mnesia:async_dirty(fun () -> + mnesia:write(#http_poll{id = + NewID, + pid = + Pid}) + end), + NewID; + true -> ID1 + end, + case http_put(ID, Key, NewKey, Packet) of + {error, not_exists} -> {200, ?BAD_REQUEST, <<"">>}; + {error, bad_key} -> {200, ?BAD_REQUEST, <<"">>}; + ok -> + receive after 100 -> ok end, + case http_get(ID) of + {error, not_exists} -> {200, ?BAD_REQUEST, <<"">>}; + {ok, OutPacket} -> + if ID == ID1 -> + Cookie = <<"ID=", ID/binary, "; expires=-1">>, + {200, [?CT, {<<"Set-Cookie">>, Cookie}], + OutPacket}; + ID1 == <<"mobile">> -> + {200, [?CT], [ID, $\n, OutPacket]}; + true -> + Cookie = <<"ID=", ID/binary, "; expires=-1">>, + {200, [?CT, {<<"Set-Cookie">>, Cookie}], OutPacket} + end + end + end; + _ -> + HumanHTMLxmlel = get_human_html_xmlel(), + {200, + [?CT, {<<"Set-Cookie">>, <<"ID=-2:0; expires=-1">>}], + HumanHTMLxmlel} end; process(_, _Request) -> - {400, [], {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}. + {400, [], + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}. -%% Code copied from mod_http_bind.erl and customized get_human_html_xmlel() -> - Heading = "ejabberd " ++ atom_to_list(?MODULE), - {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}], - [{xmlelement, "head", [], - [{xmlelement, "title", [], [{xmlcdata, Heading}]}]}, - {xmlelement, "body", [], - [{xmlelement, "h1", [], [{xmlcdata, Heading}]}, - {xmlelement, "p", [], - [{xmlcdata, "An implementation of "}, - {xmlelement, "a", - [{"href", "http://xmpp.org/extensions/xep-0025.html"}], - [{xmlcdata, "Jabber HTTP Polling (XEP-0025)"}]}]}, - {xmlelement, "p", [], - [{xmlcdata, "This web page is only informative. " - "To use HTTP-Poll you need a Jabber/XMPP client that supports it."} - ]} - ]}]}. + Heading = <<"ejabberd ", + (iolist_to_binary(atom_to_list(?MODULE)))/binary>>, + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], + children = + [#xmlel{name = <<"head">>, attrs = [], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, Heading}]}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [#xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, Heading}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, <<"An implementation of ">>}, + #xmlel{name = <<"a">>, + attrs = + [{<<"href">>, + <<"http://xmpp.org/extensions/xep-0025.html">>}], + children = + [{xmlcdata, + <<"Jabber HTTP Polling (XEP-0025)">>}]}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, + <<"This web page is only informative. To " + "use HTTP-Poll you need a Jabber/XMPP " + "client that supports it.">>}]}]}]}. %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- init([ID, Key, IP]) -> ?INFO_MSG("started: ~p", [{ID, Key, IP}]), - - %% Read c2s options from the first ejabberd_c2s configuration in - %% the config file listen section - %% TODO: We should have different access and shaper values for - %% each connector. The default behaviour should be however to use - %% the default c2s restrictions if not defined for the current - %% connector. Opts = ejabberd_c2s_config:get_c2s_limits(), - - HTTPPollTimeout = case ejabberd_config:get_local_option({http_poll_timeout, - ?MYNAME}) of - %% convert seconds of option into milliseconds - Int when is_integer(Int) -> Int*1000; - undefined -> ?HTTP_POLL_TIMEOUT - end, - + HTTPPollTimeout = ejabberd_config:get_local_option( + {http_poll_timeout, ?MYNAME}, + fun(I) when is_integer(I), I>0 -> I end, + ?HTTP_POLL_TIMEOUT) * 1000, Socket = {http_poll, self(), IP}, - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), Timer = erlang:start_timer(HTTPPollTimeout, self(), []), - {ok, loop, #state{id = ID, - key = Key, - socket = Socket, - http_poll_timeout = HTTPPollTimeout, - timer = Timer}}. + {ok, loop, + #state{id = ID, key = Key, socket = Socket, + http_poll_timeout = HTTPPollTimeout, timer = Timer}}. %%---------------------------------------------------------------------- %% Func: StateName/2 %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} +%% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -236,145 +217,105 @@ init([ID, Key, IP]) -> %% {reply, Reply, NextStateName, NextStateData} | %% {reply, Reply, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} +%% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- %state_name(Event, From, StateData) -> % Reply = ok, % {reply, Reply, state_name, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_event({activate, From}, StateName, StateData) -> case StateData#state.input of - "" -> - {next_state, StateName, - StateData#state{waiting_input = {From, ok}}}; - Input -> - Receiver = From, - Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)}, - {next_state, StateName, StateData#state{input = "", - waiting_input = false, - last_receiver = Receiver - }} + <<"">> -> + {next_state, StateName, + StateData#state{waiting_input = {From, ok}}}; + Input -> + Receiver = From, + Receiver ! + {tcp, StateData#state.socket, iolist_to_binary(Input)}, + {next_state, StateName, + StateData#state{input = <<"">>, waiting_input = false, + last_receiver = Receiver}} end; - handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({send, Packet}, _From, StateName, StateData) -> - Packet2 = if - is_binary(Packet) -> - binary_to_list(Packet); - true -> - Packet +handle_sync_event({send, Packet}, _From, StateName, + StateData) -> + Packet2 = if is_binary(Packet) -> (Packet); + true -> Packet end, - Output = StateData#state.output ++ [lists:flatten(Packet2)], + Output = StateData#state.output ++ + [lists:flatten(Packet2)], Reply = ok, - {reply, Reply, StateName, StateData#state{output = Output}}; - + {reply, Reply, StateName, + StateData#state{output = Output}}; handle_sync_event(stop, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; - + Reply = ok, {stop, normal, Reply, StateData}; handle_sync_event({http_put, Key, NewKey, Packet}, _From, StateName, StateData) -> Allow = case StateData#state.key of - "" -> - true; - OldKey -> - NextKey = jlib:encode_base64( - binary_to_list(crypto:sha(Key))), - if - OldKey == NextKey -> - true; - true -> - false - end + <<"">> -> true; + OldKey -> + NextKey = jlib:encode_base64((crypto:sha(Key))), + if OldKey == NextKey -> true; + true -> false + end end, - if - Allow -> - case StateData#state.waiting_input of - false -> - Input = [StateData#state.input|Packet], - Reply = ok, - {reply, Reply, StateName, StateData#state{input = Input, - key = NewKey}}; - {Receiver, _Tag} -> - Receiver ! {tcp, StateData#state.socket, - list_to_binary(Packet)}, - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer(StateData#state.http_poll_timeout, self(), []), - Reply = ok, - {reply, Reply, StateName, - StateData#state{waiting_input = false, - last_receiver = Receiver, - key = NewKey, - timer = Timer}} - end; - true -> - Reply = {error, bad_key}, - {reply, Reply, StateName, StateData} + if Allow -> + case StateData#state.waiting_input of + false -> + Input = [StateData#state.input | Packet], + Reply = ok, + {reply, Reply, StateName, + StateData#state{input = Input, key = NewKey}}; + {Receiver, _Tag} -> + Receiver ! + {tcp, StateData#state.socket, iolist_to_binary(Packet)}, + cancel_timer(StateData#state.timer), + Timer = + erlang:start_timer(StateData#state.http_poll_timeout, + self(), []), + Reply = ok, + {reply, Reply, StateName, + StateData#state{waiting_input = false, + last_receiver = Receiver, key = NewKey, + timer = Timer}} + end; + true -> + Reply = {error, bad_key}, + {reply, Reply, StateName, StateData} end; - -handle_sync_event(http_get, _From, StateName, StateData) -> +handle_sync_event(http_get, _From, StateName, + StateData) -> Reply = {ok, StateData#state.output}, - {reply, Reply, StateName, StateData#state{output = ""}}; - -handle_sync_event(_Event, _From, StateName, StateData) -> - Reply = ok, - {reply, Reply, StateName, StateData}. + {reply, Reply, StateName, + StateData#state{output = <<"">>}}; +handle_sync_event(_Event, _From, StateName, + StateData) -> + Reply = ok, {reply, Reply, StateName, StateData}. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_info({timeout, Timer, _}, _StateName, #state{timer = Timer} = StateData) -> {stop, normal, StateData}; - handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate(_Reason, _StateName, StateData) -> - mnesia:async_dirty( - fun() -> - mnesia:delete({http_poll, StateData#state.id}) - end), + mnesia:async_dirty(fun () -> + mnesia:delete({http_poll, StateData#state.id}) + end), case StateData#state.waiting_input of - false -> - %% We are testing this case due to "socket activation": If we pass - %% here and the "socket" is not ready to receive, the tcp_closed - %% will be lost. - case StateData#state.last_receiver of - undefined -> ok; - Receiver -> - Receiver ! {tcp_closed, StateData#state.socket} - end; - {Receiver, _Tag} -> - Receiver ! {tcp_closed, StateData#state.socket} + false -> + case StateData#state.last_receiver of + undefined -> ok; + Receiver -> + Receiver ! {tcp_closed, StateData#state.socket} + end; + {Receiver, _Tag} -> + Receiver ! {tcp_closed, StateData#state.socket} end, catch resend_messages(StateData#state.output), ok. @@ -385,109 +326,85 @@ terminate(_Reason, _StateName, StateData) -> http_put(ID, Key, NewKey, Packet) -> case get_session(ID) of - {error, _} -> - {error, not_exists}; - {ok, #http_poll{pid = FsmRef}} -> - gen_fsm:sync_send_all_state_event( - FsmRef, {http_put, Key, NewKey, Packet}) + {error, _} -> {error, not_exists}; + {ok, #http_poll{pid = FsmRef}} -> + gen_fsm:sync_send_all_state_event(FsmRef, + {http_put, Key, NewKey, Packet}) end. http_get(ID) -> case get_session(ID) of - {error, _} -> - {error, not_exists}; - {ok, #http_poll{pid = FsmRef}} -> - gen_fsm:sync_send_all_state_event(FsmRef, http_get) + {error, _} -> {error, not_exists}; + {ok, #http_poll{pid = FsmRef}} -> + gen_fsm:sync_send_all_state_event(FsmRef, http_get) end. - parse_request(Data) -> - Comma = string:chr(Data, $,), - Header = lists:sublist(Data, Comma - 1), - Packet = lists:nthtail(Comma, Data), - {ID, Key, NewKey} = - case string:tokens(Header, ";") of - [ID1] -> - {ID1, "", ""}; - [ID1, Key1] -> - {ID1, Key1, Key1}; - [ID1, Key1, NewKey1] -> - {ID1, Key1, NewKey1} - end, + Comma = str:chr(Data, $,), + Header = str:substr(Data, 1, Comma - 1), + Packet = str:substr(Data, Comma + 1, byte_size(Data)), + {ID, Key, NewKey} = case str:tokens(Header, <<";">>) of + [ID1] -> {ID1, <<"">>, <<"">>}; + [ID1, Key1] -> {ID1, Key1, Key1}; + [ID1, Key1, NewKey1] -> {ID1, Key1, NewKey1} + end, {ok, ID, Key, NewKey, Packet}. - cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. -%% Resend the polled messages resend_messages(Messages) -> - lists:foreach(fun(Packet) -> - resend_message(Packet) - end, Messages). - -%% This function is used to resend messages that have been polled but not -%% delivered. + lists:foreach(fun (Packet) -> resend_message(Packet) + end, + Messages). + resend_message(Packet) -> - {xmlelement, Name, _, _} = ParsedPacket = xml_stream:parse_element(Packet), - %% Avoid sending <stream:error> - if Name == "iq"; Name == "message"; Name == "presence" -> - From = get_jid("from", ParsedPacket), - To = get_jid("to", ParsedPacket), - ?DEBUG("Resend ~p ~p ~p~n",[From,To, ParsedPacket]), - ejabberd_router:route(From, To, ParsedPacket); - true -> - ok + #xmlel{name = Name} = ParsedPacket = + xml_stream:parse_element(Packet), + if Name == <<"iq">>; + Name == <<"message">>; + Name == <<"presence">> -> + From = get_jid(<<"from">>, ParsedPacket), + To = get_jid(<<"to">>, ParsedPacket), + ?DEBUG("Resend ~p ~p ~p~n", [From, To, ParsedPacket]), + ejabberd_router:route(From, To, ParsedPacket); + true -> ok end. -%% Type can be "from" or "to" -%% Parsed packet is a parsed Jabber packet. get_jid(Type, ParsedPacket) -> case xml:get_tag_attr(Type, ParsedPacket) of - {value, StringJid} -> - jlib:string_to_jid(StringJid); - false -> - jlib:make_jid("","","") + {value, StringJid} -> jlib:string_to_jid(StringJid); + false -> jlib:make_jid(<<"">>, <<"">>, <<"">>) end. update_tables() -> - case catch mnesia:table_info(http_poll, local_content) of - false -> - mnesia:delete_table(http_poll); - _ -> - ok + case catch mnesia:table_info(http_poll, local_content) + of + false -> mnesia:delete_table(http_poll); + _ -> ok end. make_sid() -> - sha:sha(term_to_binary({now(), make_ref()})) - ++ "-" ++ ejabberd_cluster:node_id(). + <<(sha:sha(term_to_binary({now(), make_ref()})))/binary, + "-", (ejabberd_cluster:node_id())/binary>>. get_session(SID) -> - case string:tokens(SID, "-") of - [_, NodeID] -> - case ejabberd_cluster:get_node_by_id(NodeID) of - Node when Node == node() -> - case mnesia:dirty_read({http_poll, SID}) of - [] -> - {error, enoent}; - [Session] -> - {ok, Session} - end; - Node -> - case catch rpc:call(Node, mnesia, dirty_read, - [{http_poll, SID}], 5000) of - [Session] -> - {ok, Session}; - _ -> - {error, enoent} - end - end; - _ -> - {error, enoent} + case str:tokens(SID, <<"-">>) of + [_, NodeID] -> + case ejabberd_cluster:get_node_by_id(NodeID) of + Node when Node == node() -> + case mnesia:dirty_read({http_poll, SID}) of + [] -> {error, enoent}; + [Session] -> {ok, Session} + end; + Node -> + case catch rpc:call(Node, mnesia, dirty_read, + [{http_poll, SID}], 5000) + of + [Session] -> {ok, Session}; + _ -> {error, enoent} + end + end; + _ -> {error, enoent} end. diff --git a/src/web/ejabberd_http_ws.erl b/src/web/ejabberd_http_ws.erl index f79c41ce1..97545819b 100644 --- a/src/web/ejabberd_http_ws.erl +++ b/src/web/ejabberd_http_ws.erl @@ -23,192 +23,158 @@ %%% 02111-1307 USA %%% %%%---------------------------------------------------------------------- --module (ejabberd_http_ws). +-module(ejabberd_http_ws). -author('ecestari@process-one.net'). -behaviour(gen_fsm). % External exports --export([ - start/1, - start_link/1, - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - send/2, - setopts/2, - sockname/1, peername/1, - controlling_process/2, - become_controller/2, - close/1]). +-export([start/1, start_link/1, init/1, handle_event/3, + handle_sync_event/4, code_change/4, handle_info/3, + terminate/3, send/2, setopts/2, sockname/1, peername/1, + controlling_process/2, become_controller/2, close/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). --record(state, { - socket, - timeout, - timer, - input = "", - waiting_input = false, %% {ReceiverPid, Tag} - last_receiver, - ws}). +-define(WEBSOCKET_TIMEOUT, 300). + +-record(state, + {socket :: ws_socket(), + timeout = ?WEBSOCKET_TIMEOUT :: pos_integer(), + timer = make_ref() :: reference(), + input = <<"">> :: binary(), + waiting_input = false :: false | pid(), + last_receiver :: pid(), + ws :: atom()}). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. --define(WEBSOCKET_TIMEOUT, 300000). -% -% -%%%%---------------------------------------------------------------------- -%%%% API -%%%%---------------------------------------------------------------------- +-type ws_socket() :: {http_ws, pid(), {inet:ip_address(), inet:port_number()}}. +-export_type([ws_socket/0]). + start(WS) -> supervisor:start_child(ejabberd_wsloop_sup, [WS]). start_link(WS) -> - gen_fsm:start_link(?MODULE, [WS],?FSMOPTS). + gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS). send({http_ws, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send, Packet}). setopts({http_ws, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - ok + true -> + gen_fsm:send_all_state_event(FsmRef, + {activate, self()}); + _ -> ok end. -sockname(_Socket) -> - {ok, {{0, 0, 0, 0}, 0}}. +sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. -peername({http_ws, _FsmRef, IP}) -> - {ok, IP}. +peername({http_ws, _FsmRef, IP}) -> {ok, IP}. -controlling_process(_Socket, _Pid) -> - ok. +controlling_process(_Socket, _Pid) -> ok. become_controller(FsmRef, C2SPid) -> - gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). + gen_fsm:send_all_state_event(FsmRef, + {become_controller, C2SPid}). close({http_ws, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, close). - -%%% Internal + catch gen_fsm:sync_send_all_state_event(FsmRef, close). +%%% Internal init([WS]) -> - %% Read c2s options from the first ejabberd_c2s configuration in - %% the config file listen section - %% TODO: We should have different access and shaper values for - %% each connector. The default behaviour should be however to use - %% the default c2s restrictions if not defined for the current - %% connector. Opts = ejabberd_c2s_config:get_c2s_limits(), - - WSTimeout = case ejabberd_config:get_local_option({websocket_timeout, - ?MYNAME}) of - %% convert seconds of option into milliseconds - Int when is_integer(Int) -> Int*1000; - undefined -> ?WEBSOCKET_TIMEOUT - end, - + WSTimeout = ejabberd_config:get_local_option( + {websocket_timeout, ?MYNAME}, + fun(I) when is_integer(I), I>0 -> I end, + ?WEBSOCKET_TIMEOUT) * 1000, Socket = {http_ws, self(), WS:get(ip)}, - ?DEBUG("Client connected through websocket ~p", [Socket]), - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + ?DEBUG("Client connected through websocket ~p", + [Socket]), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), Timer = erlang:start_timer(WSTimeout, self(), []), - {ok, loop, #state{ - socket = Socket, - timeout = WSTimeout, - timer = Timer, - ws = WS}}. + {ok, loop, + #state{socket = Socket, timeout = WSTimeout, + timer = Timer, ws = WS}}. handle_event({activate, From}, StateName, StateData) -> case StateData#state.input of - "" -> - {next_state, StateName, - StateData#state{waiting_input = {From, ok}}}; - Input -> - Receiver = From, - Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)}, - {next_state, StateName, StateData#state{input = "", - waiting_input = false, - last_receiver = Receiver - }} + <<"">> -> + {next_state, StateName, + StateData#state{waiting_input = From}}; + Input -> + Receiver = From, + Receiver ! + {tcp, StateData#state.socket, Input}, + {next_state, StateName, + StateData#state{input = <<"">>, waiting_input = false, + last_receiver = Receiver}} end. -handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) -> - Packet2 = if - is_binary(Packet) -> - Packet; - true -> - list_to_binary(Packet) - end, - %?DEBUG("sending on websocket : ~p ", [Packet2]), +handle_sync_event({send, Packet}, _From, StateName, + #state{ws = WS} = StateData) -> + Packet2 = if is_binary(Packet) -> Packet; + true -> iolist_to_binary(Packet) + end, WS:send(Packet2), {reply, ok, StateName, StateData}; - -handle_sync_event(close, From, _StateName, StateData)-> +handle_sync_event(close, _From, _StateName, StateData) -> {stop, normal, StateData}. handle_info(closed, _StateName, StateData) -> - {stop, normal, StateData}; - -handle_info({browser, Packet}, StateName, StateData)-> - %?DEBUG("Received on websocket : ~p ", [Packet]), - NPacket = unicode:characters_to_binary(Packet,latin1), + {stop, normal, StateData}; +handle_info({browser, Packet}, StateName, StateData) -> + NPacket = unicode:characters_to_binary(Packet, latin1), NewState = case StateData#state.waiting_input of - false -> - Input = [StateData#state.input|NPacket], - StateData#state{input = Input}; - {Receiver, _Tag} -> - Receiver ! {tcp, StateData#state.socket,NPacket}, - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer(StateData#state.timeout, self(), []), - StateData#state{waiting_input = false, - last_receiver = Receiver, - timer = Timer} - end, - {next_state, StateName, NewState}; - - + false -> + Input = <<(StateData#state.input)/binary, NPacket/binary>>, + StateData#state{input = Input}; + Receiver -> + Receiver ! {tcp, StateData#state.socket, NPacket}, + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer(StateData#state.timeout, + self(), []), + StateData#state{waiting_input = false, + last_receiver = Receiver, timer = Timer} + end, + {next_state, StateName, NewState}; handle_info({timeout, Timer, _}, _StateName, #state{timer = Timer} = StateData) -> {stop, normal, StateData}; - handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. - code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. - -terminate(_Reason, _StateName, StateData) -> + {ok, StateName, StateData}. + +terminate(_Reason, _StateName, StateData) -> case StateData#state.waiting_input of - false -> - ok; - {Receiver,_} -> - ?DEBUG("C2S Pid : ~p", [Receiver]), - Receiver ! {tcp_closed, StateData#state.socket } + false -> ok; + Receiver -> + ?DEBUG("C2S Pid : ~p", [Receiver]), + Receiver ! {tcp_closed, StateData#state.socket} end, ok. cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end.
\ No newline at end of file + receive {timeout, Timer, _} -> ok after 0 -> ok end. diff --git a/src/web/ejabberd_http_wsjson.erl b/src/web/ejabberd_http_wsjson.erl index 2abd12b3b..1a07252e8 100644 --- a/src/web/ejabberd_http_wsjson.erl +++ b/src/web/ejabberd_http_wsjson.erl @@ -24,196 +24,169 @@ %%% %%%---------------------------------------------------------------------- --module (ejabberd_http_wsjson). +-module(ejabberd_http_wsjson). + -author('ecestari@process-one.net'). -behaviour(gen_fsm). % External exports --export([ - start/1, - start_link/1, - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - send_xml/2, - setopts/2, - sockname/1, peername/1, - controlling_process/2, - become_controller/2, +-export([start/1, start_link/1, init/1, handle_event/3, + handle_sync_event/4, code_change/4, handle_info/3, + terminate/3, send_xml/2, setopts/2, sockname/1, + peername/1, controlling_process/2, become_controller/2, close/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). --record(state, { - socket, - timeout, - timer, - input = [], - waiting_input = false, %% {ReceiverPid, Tag} - last_receiver, - ws}). +-define(WEBSOCKET_TIMEOUT, 300). + +-record(state, + {socket :: ejabberd_http_ws:ws_socket(), + timeout = ?WEBSOCKET_TIMEOUT :: pos_integer(), + timer = make_ref() :: reference(), + input = [] :: list(), + waiting_input = false :: false | pid(), + last_receiver :: pid(), + ws :: atom()}). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. --define(WEBSOCKET_TIMEOUT, 300000). -% -% -%%%%---------------------------------------------------------------------- -%%%% API -%%%%---------------------------------------------------------------------- start(WS) -> supervisor:start_child(ejabberd_wsloop_sup, [WS]). start_link(WS) -> - gen_fsm:start_link(?MODULE, [WS],?FSMOPTS). + gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS). send_xml({http_ws, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + gen_fsm:sync_send_all_state_event(FsmRef, + {send, Packet}). setopts({http_ws, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - ok + true -> + gen_fsm:send_all_state_event(FsmRef, + {activate, self()}); + _ -> ok end. -sockname(_Socket) -> - {ok, {{0, 0, 0, 0}, 0}}. +sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. -peername({http_ws, _FsmRef, IP}) -> - {ok, IP}. +peername({http_ws, _FsmRef, IP}) -> {ok, IP}. -controlling_process(_Socket, _Pid) -> - ok. +controlling_process(_Socket, _Pid) -> ok. become_controller(FsmRef, C2SPid) -> - gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). + gen_fsm:send_all_state_event(FsmRef, + {become_controller, C2SPid}). close({http_ws, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, close). - -%%% Internal + catch gen_fsm:sync_send_all_state_event(FsmRef, close). +%%% Internal init([WS]) -> - %% Read c2s options from the first ejabberd_c2s configuration in - %% the config file listen section - %% TODO: We should have different access and shaper values for - %% each connector. The default behaviour should be however to use - %% the default c2s restrictions if not defined for the current - %% connector. - Opts = [{xml_socket, true}|ejabberd_c2s_config:get_c2s_limits()], - - WSTimeout = case ejabberd_config:get_local_option({websocket_timeout, - ?MYNAME}) of - %% convert seconds of option into milliseconds - Int when is_integer(Int) -> Int*1000; - undefined -> ?WEBSOCKET_TIMEOUT - end, - + Opts = [{xml_socket, true} + | ejabberd_c2s_config:get_c2s_limits()], + WSTimeout = ejabberd_config:get_local_option( + {websocket_timeout, ?MYNAME}, + fun(I) when is_integer(I), I>0 -> I end, + ?WEBSOCKET_TIMEOUT) * 1000, Socket = {http_ws, self(), WS:get(ip)}, - ?DEBUG("Client connected through websocket ~p", [Socket]), - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + ?DEBUG("Client connected through websocket ~p", + [Socket]), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + Opts), Timer = erlang:start_timer(WSTimeout, self(), []), - {ok, loop, #state{ - socket = Socket, - timeout = WSTimeout, - timer = Timer, - ws = WS}}. + {ok, loop, + #state{socket = Socket, timeout = WSTimeout, + timer = Timer, ws = WS}}. handle_event({activate, From}, StateName, StateData) -> case StateData#state.input of - [] -> - {next_state, StateName, - StateData#state{waiting_input = {From, ok}}}; - Input -> - Receiver = From, - lists:reverse(lists:map(fun(Packet)-> - Receiver ! {tcp, StateData#state.socket, [Packet]} - end, Input)), - {next_state, StateName, StateData#state{input = "", - waiting_input = false, - last_receiver = Receiver - }} + [] -> + {next_state, StateName, + StateData#state{waiting_input = From}}; + Input -> + Receiver = From, + lists:reverse(lists:map(fun (Packet) -> + Receiver ! + {tcp, StateData#state.socket, + [Packet]} + end, + Input)), + {next_state, StateName, + StateData#state{input = [], waiting_input = false, + last_receiver = Receiver}} end. -handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) -> +handle_sync_event({send, Packet}, _From, StateName, + #state{ws = WS} = StateData) -> EJson = xmpp_json:to_json(Packet), - Json = mochijson2:encode(EJson), - WS:send(iolist_to_binary(Json)), + Json = jiffy:encode(EJson), + WS:send(Json), {reply, ok, StateName, StateData}; - -handle_sync_event(close, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}. - -handle_info({browser, <<"\n">>}, StateName, StateData)-> +handle_sync_event(close, _From, _StateName, + StateData) -> + Reply = ok, {stop, normal, Reply, StateData}. + +handle_info({browser, <<"\n">>}, StateName, + StateData) -> NewState = case StateData#state.waiting_input of - false -> - ok; - {Receiver, _Tag} -> - Receiver ! {tcp, StateData#state.socket,<<"\n">>}, - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer(StateData#state.timeout, self(), []), - StateData#state{waiting_input = false, - last_receiver = Receiver, - timer = Timer} - end, - {next_state, StateName, NewState}; -handle_info({browser, JsonPacket}, StateName, StateData)-> + false -> ok; + Receiver -> + Receiver ! {tcp, StateData#state.socket, <<"\n">>}, + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer(StateData#state.timeout, + self(), []), + StateData#state{waiting_input = false, + last_receiver = Receiver, timer = Timer} + end, + {next_state, StateName, NewState}; +handle_info({browser, JsonPacket}, StateName, + StateData) -> NewState = case StateData#state.waiting_input of - false -> - EJson = mochijson2:decode(JsonPacket), - Packet = xmpp_json:from_json(EJson), - Input = [Packet | StateData#state.input], - StateData#state{input = Input}; - {Receiver, _Tag} -> - %?DEBUG("Received from browser : ~p", [JsonPacket]), - EJson = mochijson2:decode(JsonPacket), - %?DEBUG("decoded : ~p", [EJson]), - Packet = xmpp_json:from_json(EJson), - %?DEBUG("sending to c2s : ~p", [Packet]), - Receiver ! {tcp, StateData#state.socket,[Packet]}, - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer(StateData#state.timeout, self(), []), - StateData#state{waiting_input = false, - last_receiver = Receiver, - timer = Timer} - end, - {next_state, StateName, NewState}; - - + false -> + EJson = jiffy:decode(JsonPacket), + Packet = xmpp_json:from_json(EJson), + Input = [Packet | StateData#state.input], + StateData#state{input = Input}; + Receiver -> + EJson = jiffy:decode(JsonPacket), + Packet = xmpp_json:from_json(EJson), + Receiver ! {tcp, StateData#state.socket, [Packet]}, + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer(StateData#state.timeout, + self(), []), + StateData#state{waiting_input = false, + last_receiver = Receiver, timer = Timer} + end, + {next_state, StateName, NewState}; handle_info({timeout, Timer, _}, _StateName, #state{timer = Timer} = StateData) -> {stop, normal, StateData}; - handle_info(_, StateName, StateData) -> {next_state, StateName, StateData}. - code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. - + {ok, StateName, StateData}. + terminate(_Reason, _StateName, _StateData) -> ok. cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl index 932d829df..7a2ab3d84 100644 --- a/src/web/ejabberd_web.erl +++ b/src/web/ejabberd_web.erl @@ -1,7 +1,7 @@ %%%---------------------------------------------------------------------- %%% File : ejabberd_web.erl %%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : +%%% Purpose : %%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@process-one.net> %%% %%% @@ -25,56 +25,80 @@ %%%---------------------------------------------------------------------- -module(ejabberd_web). + -author('alexey@process-one.net'). %% External exports --export([make_xhtml/1, make_xhtml/2, - error/1]). +-export([make_xhtml/1, make_xhtml/2, error/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). --include("ejabberd_http.hrl"). +-include("ejabberd_http.hrl"). %% XXX bard: there are variants of make_xhtml in ejabberd_http and %% ejabberd_web_admin. It might be a good idea to centralize it here %% and also create an ejabberd_web.hrl file holding the macros, so %% that third parties can use ejabberd_web as an "utility" library. -make_xhtml(Els) -> - make_xhtml([], Els). +make_xhtml(Els) -> make_xhtml([], Els). make_xhtml(HeadEls, Els) -> - {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}, - {"xml:lang", "en"}, - {"lang", "en"}], - [{xmlelement, "head", [], - [{xmlelement, "meta", [{"http-equiv", "Content-Type"}, - {"content", "text/html; charset=utf-8"}], []} - | HeadEls]}, - {xmlelement, "body", [], Els} - ]}. - - --define(X(Name), {xmlelement, Name, [], []}). --define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}). --define(XE(Name, Els), {xmlelement, Name, [], Els}). --define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}). + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}, + {<<"xml:lang">>, <<"en">>}, {<<"lang">>, <<"en">>}], + children = + [#xmlel{name = <<"head">>, attrs = [], + children = + [#xmlel{name = <<"meta">>, + attrs = + [{<<"http-equiv">>, <<"Content-Type">>}, + {<<"content">>, + <<"text/html; charset=utf-8">>}], + children = []} + | HeadEls]}, + #xmlel{name = <<"body">>, attrs = [], children = Els}]}. + +-define(X(Name), + #xmlel{name = Name, attrs = [], children = []}). + +-define(XA(Name, Attrs), + #xmlel{name = Name, attrs = Attrs, children = []}). + +-define(XE(Name, Els), + #xmlel{name = Name, attrs = [], children = Els}). + +-define(XAE(Name, Attrs, Els), + #xmlel{name = Name, attrs = Attrs, children = Els}). + -define(C(Text), {xmlcdata, Text}). + -define(XC(Name, Text), ?XE(Name, [?C(Text)])). --define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])). --define(LI(Els), ?XE("li", Els)). --define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)). +-define(XAC(Name, Attrs, Text), + ?XAE(Name, Attrs, [?C(Text)])). + +-define(LI(Els), ?XE(<<"li">>, Els)). + +-define(A(URL, Els), + ?XAE(<<"a">>, [{<<"href">>, URL}], Els)). + -define(AC(URL, Text), ?A(URL, [?C(Text)])). --define(P, ?X("p")). --define(BR, ?X("br")). + +-define(P, ?X(<<"p">>)). + +-define(BR, ?X(<<"br">>)). + -define(INPUT(Type, Name, Value), - ?XA("input", [{"type", Type}, - {"name", Name}, - {"value", Value}])). + ?XA(<<"input">>, + [{<<"type">>, Type}, {<<"name">>, Name}, + {<<"value">>, Value}])). error(not_found) -> - {404, [], make_xhtml([?XC("h1", "404 Not Found")])}; + {404, [], + make_xhtml([?XC(<<"h1">>, <<"404 Not Found">>)])}; error(not_allowed) -> - {401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}. + {401, [], + make_xhtml([?XC(<<"h1">>, <<"401 Unauthorized">>)])}. diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index 59e9b0313..25229aaad 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -27,25 +27,27 @@ %%%% definitions -module(ejabberd_web_admin). + -author('alexey@process-one.net'). %% External exports --export([process/2, - list_users/4, - list_users_in_diapason/4, - pretty_print_xml/1, +-export([process/2, list_users/4, + list_users_in_diapason/4, pretty_print_xml/1, term_to_id/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("ejabberd_web_admin.hrl"). -define(INPUTATTRS(Type, Name, Value, Attrs), - ?XA("input", Attrs ++ - [{"type", Type}, - {"name", Name}, - {"value", Value}])). + ?XA(<<"input">>, + (Attrs ++ + [{<<"type">>, Type}, {<<"name">>, Name}, + {<<"value">>, Value}]))). %%%================================== %%%% get_acl_access @@ -53,67 +55,67 @@ %% @spec (Path::[string()], Method) -> {HostOfRule, [AccessRule]} %% where Method = 'GET' | 'POST' -%% All accounts can access those URLs -get_acl_rule([],_) -> {"localhost", [all]}; -get_acl_rule(["style.css"],_) -> {"localhost", [all]}; -get_acl_rule(["logo.png"],_) -> {"localhost", [all]}; -get_acl_rule(["logo-fill.png"],_) -> {"localhost", [all]}; -get_acl_rule(["favicon.ico"],_) -> {"localhost", [all]}; -get_acl_rule(["additions.js"],_) -> {"localhost", [all]}; +get_acl_rule([], _) -> {<<"localhost">>, [all]}; +get_acl_rule([<<"style.css">>], _) -> + {<<"localhost">>, [all]}; +get_acl_rule([<<"logo.png">>], _) -> + {<<"localhost">>, [all]}; +get_acl_rule([<<"logo-fill.png">>], _) -> + {<<"localhost">>, [all]}; +get_acl_rule([<<"favicon.ico">>], _) -> + {<<"localhost">>, [all]}; +get_acl_rule([<<"additions.js">>], _) -> + {<<"localhost">>, [all]}; %% This page only displays vhosts that the user is admin: -get_acl_rule(["vhosts"],_) -> {"localhost", [all]}; - +get_acl_rule([<<"vhosts">>], _) -> + {<<"localhost">>, [all]}; %% The pages of a vhost are only accesible if the user is admin of that vhost: -get_acl_rule(["server", VHost | _RPath], Method) - when Method=:='GET' orelse Method=:='HEAD' -> +get_acl_rule([<<"server">>, VHost | _RPath], Method) + when Method =:= 'GET' orelse Method =:= 'HEAD' -> {VHost, [configure, webadmin_view]}; -get_acl_rule(["server", VHost | _RPath], 'POST') -> {VHost, [configure]}; - +get_acl_rule([<<"server">>, VHost | _RPath], 'POST') -> + {VHost, [configure]}; %% Default rule: only global admins can access any other random page get_acl_rule(_RPath, Method) - when Method=:='GET' orelse Method=:='HEAD' -> + when Method =:= 'GET' orelse Method =:= 'HEAD' -> {global, [configure, webadmin_view]}; get_acl_rule(_RPath, 'POST') -> {global, [configure]}. is_acl_match(Host, Rules, Jid) -> - lists:any( - fun(Rule) -> - allow == acl:match_rule(Host, Rule, Jid) - end, - Rules). + lists:any(fun (Rule) -> + allow == acl:match_rule(Host, Rule, Jid) + end, + Rules). %%%================================== %%%% Menu Items Access get_jid(Auth, HostHTTP, Method) -> case get_auth_admin(Auth, HostHTTP, [], Method) of - {ok, {User, Server}} -> - jlib:make_jid(User, Server, ""); - {unauthorized, Error} -> - ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]), - throw({unauthorized, Auth}) + {ok, {User, Server}} -> + jlib:make_jid(User, Server, <<"">>); + {unauthorized, Error} -> + ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]), + throw({unauthorized, Auth}) end. get_menu_items(global, cluster, Lang, JID) -> {Base, _, Items} = make_server_menu([], [], Lang, JID), - lists:map( - fun({URI, Name}) -> - {Base++URI++"/", Name}; - ({URI, Name, _SubMenu}) -> - {Base++URI++"/", Name} - end, - Items - ); + lists:map(fun ({URI, Name}) -> + {<<Base/binary, URI/binary, "/">>, Name}; + ({URI, Name, _SubMenu}) -> + {<<Base/binary, URI/binary, "/">>, Name} + end, + Items); get_menu_items(Host, cluster, Lang, JID) -> {Base, _, Items} = make_host_menu(Host, [], Lang, JID), - lists:map( - fun({URI, Name}) -> - {Base++URI++"/", Name}; - ({URI, Name, _SubMenu}) -> - {Base++URI++"/", Name} - end, - Items - ). + lists:map(fun ({URI, Name}) -> + {<<Base/binary, URI/binary, "/">>, Name}; + ({URI, Name, _SubMenu}) -> + {<<Base/binary, URI/binary, "/">>, Name} + end, + Items). + %% get_menu_items(Host, Node, Lang, JID) -> %% {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID), %% lists:map( @@ -130,7 +132,7 @@ is_allowed_path(BasePath, {Path, _}, JID) -> is_allowed_path(BasePath, {Path, _, _}, JID) -> is_allowed_path(BasePath ++ [Path], JID). -is_allowed_path(["admin" | Path], JID) -> +is_allowed_path([<<"admin">> | Path], JID) -> is_allowed_path(Path, JID); is_allowed_path(Path, JID) -> {HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'), @@ -143,126 +145,132 @@ is_allowed_path(Path, JID) -> %%path_to_url(Path) -> %% "/" ++ string:join(Path, "/") ++ "/". -%% @spec(URL) -> Path -%% where Path = [string()] -%% URL = string() -%% Convert "admin/user/tom" -> ["admin", "user", "tom"] -url_to_path(URL) -> - string:tokens(URL, "/"). - +url_to_path(URL) -> str:tokens(URL, <<"/">>). + %%%================================== %%%% process/2 -process(["doc", LocalFile], _Request) -> +process([<<"doc">>, LocalFile], _Request) -> DocPath = case os:getenv("EJABBERD_DOC_PATH") of - P when is_list(P) -> P; - false -> "/share/doc/ejabberd/" + P when is_list(P) -> P; + false -> <<"/share/doc/ejabberd/">> end, - %% Code based in mod_http_fileserver FileName = filename:join(DocPath, LocalFile), case file:read_file(FileName) of - {ok, FileContents} -> - ?DEBUG("Delivering content.", []), - {200, - [{"Server", "ejabberd"}], - FileContents}; - {error, Error} -> - ?DEBUG("Delivering error: ~p", [Error]), - Help = " " ++ FileName ++ " - Try to specify the path to ejabberd documentation " - "with the environment variable EJABBERD_DOC_PATH. Check the ejabberd Guide for more information.", - case Error of - eacces -> {403, [], "Forbidden"++Help}; - enoent -> {404, [], "Not found"++Help}; - _Else -> {404, [], atom_to_list(Error)++Help} - end + {ok, FileContents} -> + ?DEBUG("Delivering content.", []), + {200, [{<<"Server">>, <<"ejabberd">>}], FileContents}; + {error, Error} -> + ?DEBUG("Delivering error: ~p", [Error]), + Help = <<" ", FileName/binary, + " - Try to specify the path to ejabberd " + "documentation with the environment variable " + "EJABBERD_DOC_PATH. Check the ejabberd " + "Guide for more information.">>, + case Error of + eacces -> {403, [], <<"Forbidden", Help/binary>>}; + enoent -> {404, [], <<"Not found", Help/binary>>}; + _Else -> + {404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>} + end end; - -process(["server", SHost | RPath] = Path, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) -> +process([<<"server">>, SHost | RPath] = Path, + #request{auth = Auth, lang = Lang, host = HostHTTP, + method = Method} = + Request) -> Host = jlib:nameprep(SHost), case lists:member(Host, ?MYHOSTS) of - true -> - case get_auth_admin(Auth, HostHTTP, Path, Method) of - {ok, {User, Server}} -> - AJID = get_jid(Auth, HostHTTP, Method), - process_admin(Host, Request#request{path = RPath, - auth = {auth_jid, Auth, AJID}, - us = {User, Server}}); - {unauthorized, "no-auth-provided"} -> - {401, - [{"WWW-Authenticate", "basic realm=\"ejabberd\""}], - ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])}; - {unauthorized, Error} -> - {BadUser, _BadPass} = Auth, - {IPT, _Port} = Request#request.ip, - IPS = inet_parse:ntoa(IPT), - ?WARNING_MSG("Access of ~p from ~p failed with error: ~p", - [BadUser, IPS, Error]), - {401, - [{"WWW-Authenticate", - "basic realm=\"auth error, retry login to ejabberd\""}], - ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])} - end; - false -> - ejabberd_web:error(not_found) + true -> + case get_auth_admin(Auth, HostHTTP, Path, Method) of + {ok, {User, Server}} -> + AJID = get_jid(Auth, HostHTTP, Method), + process_admin(Host, + Request#request{path = RPath, + auth = {auth_jid, Auth, AJID}, + us = {User, Server}}); + {unauthorized, <<"no-auth-provided">>} -> + {401, + [{<<"WWW-Authenticate">>, + <<"basic realm=\"ejabberd\"">>}], + ejabberd_web:make_xhtml([?XCT(<<"h1">>, + <<"Unauthorized">>)])}; + {unauthorized, Error} -> + {BadUser, _BadPass} = Auth, + {IPT, _Port} = Request#request.ip, + IPS = jlib:ip_to_list(IPT), + ?WARNING_MSG("Access of ~p from ~p failed with error: ~p", + [BadUser, IPS, Error]), + {401, + [{<<"WWW-Authenticate">>, + <<"basic realm=\"auth error, retry login " + "to ejabberd\"">>}], + ejabberd_web:make_xhtml([?XCT(<<"h1">>, + <<"Unauthorized">>)])} + end; + false -> ejabberd_web:error(not_found) end; - -process(RPath, #request{auth = Auth, lang = Lang, host = HostHTTP, method = Method} = Request) -> +process(RPath, + #request{auth = Auth, lang = Lang, host = HostHTTP, + method = Method} = + Request) -> case get_auth_admin(Auth, HostHTTP, RPath, Method) of - {ok, {User, Server}} -> - AJID = get_jid(Auth, HostHTTP, Method), - process_admin(global, Request#request{path = RPath, - auth = {auth_jid, Auth, AJID}, - us = {User, Server}}); - {unauthorized, "no-auth-provided"} -> - {401, - [{"WWW-Authenticate", "basic realm=\"ejabberd\""}], - ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])}; - {unauthorized, Error} -> - {BadUser, _BadPass} = Auth, - {IPT, _Port} = Request#request.ip, - IPS = inet_parse:ntoa(IPT), - ?WARNING_MSG("Access of ~p from ~p failed with error: ~p", - [BadUser, IPS, Error]), - {401, - [{"WWW-Authenticate", - "basic realm=\"auth error, retry login to ejabberd\""}], - ejabberd_web:make_xhtml([?XCT("h1", "Unauthorized")])} + {ok, {User, Server}} -> + AJID = get_jid(Auth, HostHTTP, Method), + process_admin(global, + Request#request{path = RPath, + auth = {auth_jid, Auth, AJID}, + us = {User, Server}}); + {unauthorized, <<"no-auth-provided">>} -> + {401, + [{<<"WWW-Authenticate">>, + <<"basic realm=\"ejabberd\"">>}], + ejabberd_web:make_xhtml([?XCT(<<"h1">>, + <<"Unauthorized">>)])}; + {unauthorized, Error} -> + {BadUser, _BadPass} = Auth, + {IPT, _Port} = Request#request.ip, + IPS = jlib:ip_to_list(IPT), + ?WARNING_MSG("Access of ~p from ~p failed with error: ~p", + [BadUser, IPS, Error]), + {401, + [{<<"WWW-Authenticate">>, + <<"basic realm=\"auth error, retry login " + "to ejabberd\"">>}], + ejabberd_web:make_xhtml([?XCT(<<"h1">>, + <<"Unauthorized">>)])} end. get_auth_admin(Auth, HostHTTP, RPath, Method) -> case Auth of - {SJID, Pass} -> - {HostOfRule, AccessRule} = get_acl_rule(RPath, Method), - case jlib:string_to_jid(SJID) of - error -> - {unauthorized, "badformed-jid"}; - #jid{user = "", server = User} -> - %% If the user only specified username, not username@server - get_auth_account(HostOfRule, AccessRule, User, HostHTTP, Pass); - #jid{user = User, server = Server} -> - get_auth_account(HostOfRule, AccessRule, User, Server, Pass) - end; - undefined -> - {unauthorized, "no-auth-provided"} + {SJID, Pass} -> + {HostOfRule, AccessRule} = get_acl_rule(RPath, Method), + case jlib:string_to_jid(SJID) of + error -> {unauthorized, <<"badformed-jid">>}; + #jid{user = <<"">>, server = User} -> + get_auth_account(HostOfRule, AccessRule, User, HostHTTP, + Pass); + #jid{user = User, server = Server} -> + get_auth_account(HostOfRule, AccessRule, User, Server, + Pass) + end; + undefined -> {unauthorized, <<"no-auth-provided">>} end. -get_auth_account(HostOfRule, AccessRule, User, Server, Pass) -> +get_auth_account(HostOfRule, AccessRule, User, Server, + Pass) -> case ejabberd_auth:check_password(User, Server, Pass) of - true -> - case is_acl_match(HostOfRule, AccessRule, - jlib:make_jid(User, Server, "")) of - false -> - {unauthorized, "unprivileged-account"}; - true -> - {ok, {User, Server}} - end; - false -> - case ejabberd_auth:is_user_exists(User, Server) of - true -> - {unauthorized, "bad-password"}; - false -> - {unauthorized, "inexistent-account"} - end + true -> + case is_acl_match(HostOfRule, AccessRule, + jlib:make_jid(User, Server, <<"">>)) + of + false -> {unauthorized, <<"unprivileged-account">>}; + true -> {ok, {User, Server}} + end; + false -> + case ejabberd_auth:is_user_exists(User, Server) of + true -> {unauthorized, <<"bad-password">>}; + false -> {unauthorized, <<"inexistent-account">>} + end end. %%%================================== @@ -271,1210 +279,1002 @@ get_auth_account(HostOfRule, AccessRule, User, Server, Pass) -> make_xhtml(Els, Host, Lang, JID) -> make_xhtml(Els, Host, cluster, Lang, JID). -%% @spec (Els, Host, Node, Lang, JID) -> {200, [html], xmlelement()} -%% where Host = global | string() -%% Node = cluster | atom() -%% JID = jid() make_xhtml(Els, Host, Node, Lang, JID) -> - Base = get_base_path(Host, cluster), %% Enforcing 'cluster' on purpose here + Base = get_base_path(Host, cluster), MenuItems = make_navigation(Host, Node, Lang, JID), {200, [html], - {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}, - {"xml:lang", Lang}, - {"lang", Lang}], - [{xmlelement, "head", [], - [?XCT("title", "ejabberd Web Admin"), - {xmlelement, "meta", [{"http-equiv", "Content-Type"}, - {"content", "text/html; charset=utf-8"}], []}, - {xmlelement, "script", [{"src", Base ++ "/additions.js"}, - {"type", "text/javascript"}], [?C(" ")]}, - {xmlelement, "link", [{"href", Base ++ "favicon.ico"}, - {"type", "image/x-icon"}, - {"rel", "shortcut icon"}], []}, - {xmlelement, "link", [{"href", Base ++ "style.css"}, - {"type", "text/css"}, - {"rel", "stylesheet"}], []}]}, - ?XE("body", - [?XAE("div", - [{"id", "container"}], - [?XAE("div", - [{"id", "header"}], - [?XE("h1", - [?ACT("/admin/", "ejabberd Web Admin")] - )]), - ?XAE("div", - [{"id", "navigation"}], - [?XE("ul", - MenuItems - )]), - ?XAE("div", - [{"id", "content"}], - Els), - ?XAE("div", - [{"id", "clearcopyright"}], - [{xmlcdata, ""}])]), - ?XAE("div", - [{"id", "copyrightouter"}], - [?XAE("div", - [{"id", "copyright"}], - [?XC("p", - "ejabberd (c) 2002-2012 ProcessOne") - ])])]) - ]}}. - -get_base_path(global, cluster) -> "/admin/"; -get_base_path(Host, cluster) -> "/admin/server/" ++ Host ++ "/"; -get_base_path(global, Node) -> "/admin/node/" ++ atom_to_list(Node) ++ "/"; -get_base_path(Host, Node) -> "/admin/server/" ++ Host ++ "/node/" ++ atom_to_list(Node) ++ "/". + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}, + {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}], + children = + [#xmlel{name = <<"head">>, attrs = [], + children = + [?XCT(<<"title">>, <<"ejabberd Web Admin">>), + #xmlel{name = <<"meta">>, + attrs = + [{<<"http-equiv">>, <<"Content-Type">>}, + {<<"content">>, + <<"text/html; charset=utf-8">>}], + children = []}, + #xmlel{name = <<"script">>, + attrs = + [{<<"src">>, + <<Base/binary, "/additions.js">>}, + {<<"type">>, <<"text/javascript">>}], + children = [?C(<<" ">>)]}, + #xmlel{name = <<"link">>, + attrs = + [{<<"href">>, + <<Base/binary, "favicon.ico">>}, + {<<"type">>, <<"image/x-icon">>}, + {<<"rel">>, <<"shortcut icon">>}], + children = []}, + #xmlel{name = <<"link">>, + attrs = + [{<<"href">>, + <<Base/binary, "style.css">>}, + {<<"type">>, <<"text/css">>}, + {<<"rel">>, <<"stylesheet">>}], + children = []}]}, + ?XE(<<"body">>, + [?XAE(<<"div">>, [{<<"id">>, <<"container">>}], + [?XAE(<<"div">>, [{<<"id">>, <<"header">>}], + [?XE(<<"h1">>, + [?ACT(<<"/admin/">>, + <<"ejabberd Web Admin">>)])]), + ?XAE(<<"div">>, [{<<"id">>, <<"navigation">>}], + [?XE(<<"ul">>, MenuItems)]), + ?XAE(<<"div">>, [{<<"id">>, <<"content">>}], Els), + ?XAE(<<"div">>, [{<<"id">>, <<"clearcopyright">>}], + [{xmlcdata, <<"">>}])]), + ?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}], + [?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}], + [?XC(<<"p">>, + <<"ejabberd (c) 2002-2012 ProcessOne">>)])])])]}}. + +get_base_path(global, cluster) -> <<"/admin/">>; +get_base_path(Host, cluster) -> + <<"/admin/server/", Host/binary, "/">>; +get_base_path(global, Node) -> + <<"/admin/node/", + (iolist_to_binary(atom_to_list(Node)))/binary, "/">>; +get_base_path(Host, Node) -> + <<"/admin/server/", Host/binary, "/node/", + (iolist_to_binary(atom_to_list(Node)))/binary, "/">>. %%%================================== %%%% css & images additions_js() -> -" -function selectAll() { - for(i=0;i<document.forms[0].elements.length;i++) - { var e = document.forms[0].elements[i]; - if(e.type == 'checkbox') - { e.checked = true; } - } -} -function unSelectAll() { - for(i=0;i<document.forms[0].elements.length;i++) - { var e = document.forms[0].elements[i]; - if(e.type == 'checkbox') - { e.checked = false; } - } -} -". + <<"\nfunction selectAll() {\n for(i=0;i<documen" + "t.forms[0].elements.length;i++)\n { " + "var e = document.forms[0].elements[i];\n " + " if(e.type == 'checkbox')\n { e.checked " + "= true; }\n }\n}\nfunction unSelectAll() " + "{\n for(i=0;i<document.forms[0].elements.len" + "gth;i++)\n { var e = document.forms[0].eleme" + "nts[i];\n if(e.type == 'checkbox')\n " + " { e.checked = false; }\n }\n}\n">>. css(Host) -> Base = get_base_path(Host, cluster), - " -html,body { - background: white; - margin: 0; - padding: 0; - height: 100%; -} - -#container { - padding: 0; - margin: 0; - min-height: 100%; - height: 100%; - margin-bottom: -30px; -} - -html>body #container { - height: auto; -} - -#header h1 { - width: 100%; - height: 55px; - padding: 0; - margin: 0; - background: transparent url(\"" ++ Base ++ "logo-fill.png\"); -} - -#header h1 a { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 55px; - padding: 0; - margin: 0; - background: transparent url(\"" ++ Base ++ "logo.png\") no-repeat; - display: block; - text-indent: -700em; -} - -#clearcopyright { - display: block; - width: 100%; - height: 30px; -} - -#copyrightouter { - display: table; - width: 100%; - height: 30px; -} - -#copyright { - display: table-cell; - vertical-align: bottom; - width: 100%; - height: 30px; -} - -#copyright p { - margin-left: 0; - margin-right: 0; - margin-top: 5px; - margin-bottom: 0; - padding-left: 0; - padding-right: 0; - padding-top: 1px; - padding-bottom: 1px; - width: 100%; - color: #ffffff; - background-color: #fe8a00; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 7pt; - font-weight: bold; - text-align: center; -} - -#navigation ul { - position: absolute; - top: 65px; - left: 0; - padding: 0 1px 1px 1px; - margin: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: bold; - border-top: 1px solid #d47911; - width: 17em; -} - -#navigation ul li { - list-style: none; - margin: 0; - text-align: left; - display: inline; -} - -#navigation ul li a { - margin: 0; - display: block; - padding: 3px 6px 3px 9px; - border-left: 1em solid #ffc78c; - border-right: 1px solid #d47911; - border-bottom: 1px solid #d47911; - background: #ffe3c9; - text-decoration: none; -} - -#navigation ul li a:link { - color: #844; -} - -#navigation ul li a:visited { - color: #766; -} - -#navigation ul li a:hover { - border-color: #fc8800; - color: #FFF; - background: #332; -} - -ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a { - text-align: center; - border-top: 1px solid #d47911; - border-bottom: 2px solid #d47911; - background: #FED6A6; -} - -#navheadsub, #navitemsub { - border-left: 7px solid white; - margin-left: 2px; -} - -#navheadsubsub, #navitemsubsub { - border-left: 14px solid white; - margin-left: 4px; -} - -#lastactivity li { - font-weight: bold; - border: 1px solid #d6760e; - background-color: #fff2e8; - padding: 2px; - margin-bottom: -1px; -} - -td.copy { - color: #ffffff; - background-color: #fe8a00; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 7pt; - font-weight: bold; - text-align: center; -} - -input { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - border: 1px solid #d6760e; - color: #723202; - background-color: #fff2e8; - vertical-align: middle; - margin-bottom: 0px; - padding: 0.1em; -} - -input[type=submit] { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: bold; - color: #ffffff; - background-color: #fe8a00; - border: 1px solid #d6760e; -} - -textarea { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - border: 1px solid #d6760e; - color: #723202; - background-color: #fff2e8; -} - -select { - border: 1px solid #d6760e; - color: #723202; - background-color: #fff2e8; - vertical-align: middle; - margin-bottom: 0px; - padding: 0.1em; -} - -thead { - color: #000000; - background-color: #ffffff; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - font-weight: bold; -} - -tr.head { - color: #ffffff; - background-color: #3b547a; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 9pt; - font-weight: bold; - text-align: center; -} - -tr.oddraw { - color: #412c75; - background-color: #ccd4df; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 9pt; - font-weight: normal; - text-align: center; -} - -tr.evenraw { - color: #412c75; - background-color: #dbe0e8; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 9pt; - font-weight: normal; - text-align: center; -} - -td.leftheader { - color: #412c75; - background-color: #ccccc1; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 9pt; - font-weight: bold; - padding-left: 5px; - padding-top: 2px; - padding-bottom: 2px; - margin-top: 0px; - margin-bottom: 0px; -} - -td.leftcontent { - color: #000044; - background-color: #e6e6df; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 7pt; - font-weight: normal; - padding-left: 5px; - padding-right: 5px; - padding-top: 2px; - padding-bottom: 2px; - margin-top: 0px; - margin-bottom: 0px; -} - -td.rightcontent { - color: #000044; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - font-weight: normal; - text-align: justify; - padding-left: 10px; - padding-right: 10px; - padding-bottom: 5px; -} - - -h1 { - color: #000044; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 14pt; - font-weight: bold; - text-align: center; - padding-top: 2px; - padding-bottom: 2px; - margin-top: 0px; - margin-bottom: 0px; -} - -h2 { - color: #000044; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 12pt; - font-weight: bold; - text-align: center; - padding-top: 2px; - padding-bottom: 2px; - margin-top: 0px; - margin-bottom: 0px; -} - -h3 { - color: #000044; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - font-weight: bold; - text-align: left; - padding-top: 20px; - padding-bottom: 2px; - margin-top: 0px; - margin-bottom: 0px; -} - -#content a:link { - color: #990000; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - font-weight: bold; - text-decoration: underline; -} -#content a:visited { - color: #990000; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - font-weight: bold; - text-decoration: underline; -} -#content a:hover { - color: #cc6600; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - font-weight: bold; - text-decoration: underline; -} - - -#content ul li { - list-style-type: disc; - font-size: 10pt; - /*font-size: 7pt;*/ - padding-left: 10px; -} - -#content ul.nolistyle>li { - list-style-type: none; -} - -#content li.big { - font-size: 10pt; -} - -#content { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10pt; - padding-left: 17em; - padding-top: 5px; -} - -div.guidelink { - text-align: right; - padding-right: 1em; -} - -table.withtextareas>tbody>tr>td { - vertical-align: top; -} - -p.result { - border: 1px; - border-style: dashed; - border-color: #FE8A02; - padding: 1em; - margin-right: 1em; - background: #FFE3C9; -} - -*.alignright { - font-size: 10pt; - text-align: right; -} - -". + <<"\nhtml,body {\n background: white;\n " + " margin: 0;\n padding: 0;\n height: " + "100%;\n}\n\n#container {\n padding: " + "0;\n margin: 0;\n min-height: 100%;\n " + " height: 100%;\n margin-bottom: -30px;\n}\n\n" + "html>body #container {\n height: auto;\n}\n\n" + "#header h1 {\n width: 100%;\n height: " + "55px;\n padding: 0;\n margin: 0;\n " + " background: transparent url(\"", + Base/binary, + "logo-fill.png\");\n}\n\n#header h1 a " + "{\n position: absolute;\n top: 0;\n " + " left: 0;\n width: 100%;\n height: " + "55px;\n padding: 0;\n margin: 0;\n " + " background: transparent url(\"", + Base/binary, + "logo.png\") no-repeat;\n display: block;\n " + " text-indent: -700em;\n}\n\n#clearcopyright " + "{\n display: block;\n width: 100%;\n " + " height: 30px;\n}\n\n#copyrightouter " + "{\n display: table;\n width: 100%;\n " + " height: 30px;\n}\n\n#copyright {\n " + " display: table-cell;\n vertical-align: " + "bottom;\n width: 100%;\n height: 30px;\n}\n\n" + "#copyright p {\n margin-left: 0;\n " + " margin-right: 0;\n margin-top: 5px;\n " + " margin-bottom: 0;\n padding-left: " + "0;\n padding-right: 0;\n padding-top: " + "1px;\n padding-bottom: 1px;\n width: " + "100%;\n color: #ffffff;\n background-color: " + "#fe8a00;\n font-family: Verdana, Arial, " + "Helvetica, sans-serif; \n font-size: " + "7pt;\n font-weight: bold;\n text-align: " + "center;\n}\n\n#navigation ul {\n position: " + "absolute;\n top: 65px;\n left: 0;\n " + " padding: 0 1px 1px 1px;\n margin: " + "0;\n font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 8pt;\n font-weigh" + "t: bold;\n border-top: 1px solid #d47911;\n " + " width: 17em;\n}\n\n#navigation ul li " + "{\n list-style: none;\n margin: 0;\n " + " text-align: left;\n display: inline;\n}\n\n" + "#navigation ul li a {\n margin: 0;\n " + " display: block;\n padding: 3px 6px " + "3px 9px;\n border-left: 1em solid #ffc78c;\n " + " border-right: 1px solid #d47911;\n " + " border-bottom: 1px solid #d47911;\n " + " background: #ffe3c9;\n text-decoration: " + "none;\n}\n\n#navigation ul li a:link " + "{\n color: #844;\n}\n\n#navigation " + "ul li a:visited {\n color: #766;\n}\n\n#navig" + "ation ul li a:hover {\n border-color: " + "#fc8800;\n color: #FFF;\n background: " + "#332;\n}\n\nul li #navhead a, ul li " + "#navheadsub a, ul li #navheadsubsub " + "a {\n text-align: center;\n border-top: " + "1px solid #d47911;\n border-bottom: " + "2px solid #d47911;\n background: #FED6A6;\n}\n\n" + "#navheadsub, #navitemsub {\n border-left: " + "7px solid white;\n margin-left: 2px;\n}\n\n#" + "navheadsubsub, #navitemsubsub {\n border-lef" + "t: 14px solid white;\n margin-left: " + "4px;\n}\n\n#lastactivity li {\n font-weight: " + "bold;\n border: 1px solid #d6760e;\n " + " background-color: #fff2e8;\n padding: " + "2px;\n margin-bottom: -1px;\n}\n\ntd.copy " + "{\n color: #ffffff;\n background-color: " + "#fe8a00;\n font-family: Verdana, Arial, " + "Helvetica, sans-serif; \n font-size: " + "7pt;\n font-weight: bold;\n text-align: " + "center;\n}\n\ninput {\n font-family: " + "Verdana, Arial, Helvetica, sans-serif; " + "\n font-size: 10pt;\n border: 1px " + "solid #d6760e;\n color: #723202;\n " + " background-color: #fff2e8;\n vertical-align" + ": middle;\n margin-bottom: 0px;\n " + "padding: 0.1em;\n}\n\ninput[type=submit] " + "{\n font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 8pt;\n font-weigh" + "t: bold;\n color: #ffffff;\n background-col" + "or: #fe8a00;\n border: 1px solid #d6760e;\n}\n\n" + "textarea {\n font-family: Verdana, " + "Arial, Helvetica, sans-serif; \n font-size: " + "10pt;\n border: 1px solid #d6760e;\n " + " color: #723202;\n background-color: " + "#fff2e8;\n}\n\nselect {\n border: 1px " + "solid #d6760e;\n color: #723202;\n " + " background-color: #fff2e8;\n vertical-align" + ": middle;\n margin-bottom: 0px; \n " + " padding: 0.1em;\n}\n\nthead {\n color: " + "#000000;\n background-color: #ffffff;\n " + " font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 10pt;\n " + "font-weight: bold;\n}\n\ntr.head {\n " + " color: #ffffff;\n background-color: " + "#3b547a;\n font-family: Verdana, Arial, " + "Helvetica, sans-serif; \n font-size: " + "9pt;\n font-weight: bold;\n text-align: " + "center;\n}\n\ntr.oddraw {\n color: " + "#412c75;\n background-color: #ccd4df;\n " + " font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 9pt;\n font-weigh" + "t: normal;\n text-align: center;\n}\n\ntr.ev" + "enraw {\n color: #412c75;\n background-colo" + "r: #dbe0e8;\n font-family: Verdana, " + "Arial, Helvetica, sans-serif; \n font-size: " + "9pt;\n font-weight: normal;\n text-align: " + "center;\n}\n\ntd.leftheader {\n color: " + "#412c75;\n background-color: #ccccc1;\n " + " font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 9pt;\n font-weigh" + "t: bold;\n padding-left: 5px;\n padding-top" + ": 2px;\n padding-bottom: 2px;\n margin-top: " + "0px;\n margin-bottom: 0px;\n}\n\ntd.leftcont" + "ent {\n color: #000044;\n background-color: " + "#e6e6df;\n font-family: Verdana, Arial, " + "Helvetica, sans-serif; \n font-size: " + "7pt;\n font-weight: normal;\n padding-left: " + "5px;\n padding-right: 5px;\n padding-top: " + "2px;\n padding-bottom: 2px;\n margin-top: " + "0px;\n margin-bottom: 0px;\n}\n\ntd.rightcon" + "tent {\n color: #000044;\n font-family: " + "Verdana, Arial, Helvetica, sans-serif; " + "\n font-size: 10pt;\n font-weight: " + "normal;\n text-align: justify;\n padding-le" + "ft: 10px;\n padding-right: 10px;\n " + " padding-bottom: 5px;\n}\n\n\nh1 {\n " + " color: #000044;\n font-family: Verdana, " + "Arial, Helvetica, sans-serif; \n font-size: " + "14pt;\n font-weight: bold;\n text-align: " + "center;\n padding-top: 2px;\n padding-botto" + "m: 2px;\n margin-top: 0px;\n margin-bottom: " + "0px;\n}\n\nh2 {\n color: #000044;\n " + " font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 12pt;\n " + "font-weight: bold;\n text-align: center;\n " + " padding-top: 2px;\n padding-bottom: " + "2px;\n margin-top: 0px;\n margin-bottom: " + "0px;\n}\n\nh3 {\n color: #000044;\n " + " font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 10pt;\n " + "font-weight: bold;\n text-align: left;\n " + " padding-top: 20px;\n padding-bottom: " + "2px;\n margin-top: 0px;\n margin-bottom: " + "0px;\n}\n\n#content a:link {\n color: " + "#990000; \n font-family: Verdana, Arial, " + "Helvetica, sans-serif; \n font-size: " + "10pt;\n font-weight: bold;\n text-decoratio" + "n: underline;\n}\n#content a:visited " + "{\n color: #990000; \n font-family: " + "Verdana, Arial, Helvetica, sans-serif; " + "\n font-size: 10pt;\n font-weight: " + "bold;\n text-decoration: underline;\n}\n#con" + "tent a:hover {\n color: #cc6600; \n " + " font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 10pt;\n " + "font-weight: bold;\n text-decoration: " + "underline;\n}\n\n\n#content ul li {\n " + " list-style-type: disc;\n font-size: " + "10pt;\n /*font-size: 7pt;*/\n padding-left: " + "10px;\n}\n\n#content ul.nolistyle>li " + "{\n list-style-type: none;\n}\n\n#content " + "li.big {\n font-size: 10pt;\n}\n\n#content " + "{\n font-family: Verdana, Arial, Helvetica, " + "sans-serif; \n font-size: 10pt;\n " + "padding-left: 17em;\n padding-top: " + "5px;\n}\n\ndiv.guidelink {\n text-align: " + "right;\n padding-right: 1em;\n}\n\ntable.wit" + "htextareas>tbody>tr>td {\n vertical-align: " + "top;\n}\n\np.result {\n border: 1px;\n " + " border-style: dashed;\n border-color: " + "#FE8A02;\n padding: 1em;\n margin-right: " + "1em;\n background: #FFE3C9;\n}\n\n*.alignrig" + "ht {\n font-size: 10pt;\n text-align: " + "right;\n}\n\n">>. favicon() -> - jlib:decode_base64( - "AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAJf+cAAIPsAAGC8gAVhecAAIr8ACiR7wBBmO" - "cAUKPsAFun8ABhqeoAgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhI" - "CAkJCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkXrRCQkJCMgI7" - "kiAjICAUFF2swkFBQRQUXazCQUFBAgI7kiAgICAkJF60QkJCQgICOpiHkyA" - "gJCRevdvlQkICAjdndnMgICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAA"). + jlib:decode_base64(<<"AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAA" + "AEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJf+cAAI" + "PsAAGC8gAVhecAAIr8ACiR7wBBmOcAUKPsAFun8ABhqeo" + "AgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhICAk" + "JCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkX" + "rRCQkJCMgI7kiAjICAUFF2swkFBQRQUXazCQUFBAgI7ki" + "AgICAkJF60QkJCQgICOpiHkyAgJCRevdvlQkICAjdndnM" + "gICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAA">>). logo() -> - jlib:decode_base64( - "iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEAAAAAXNSR0IArs4c" - "6QAAAEtQTFRFcTIA1XcE/YsA/40E/pIH/JYc/5kg/54i/KIu/6U6/apE/61H" - "/61P/bFX/7Vh/bda/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA" - "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3tqO/DhxihMg33VJ7" - "JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcMUVV1xxLXIlRfPAZptYrbf5YeW618PW" - "yvG8w/g9ZwquuJ6Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/" - "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmVpb5NN9LUyddu7nn" - "LYkrrrjiimuVK6mZB+6VuFbiXJk8v/bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xx" - "xRXXKldSMw+8KPGgxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx" - "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8jO4Ot9uTEq8Krrji" - "iiuuZa6kZh74UFpli3sO61btMfyHyWGv/RMs7wB67ne32/BdwRVXXHHFtcyV" - "1MwDn0qrbHHvyPT/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI" - "F5dWoNvcLcs/AAAAAElFTkSuQmCC"). + jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEA" + "AAAAXNSR0IArs4c6QAAAEtQTFRFcTIA1XcE/YsA/40E/p" + "IH/JYc/5kg/54i/KIu/6U6/apE/61H/61P/bFX/7Vh/bd" + "a/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA" + "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3t" + "qO/DhxihMg33VJ7JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcM" + "UVV1xxLXIlRfPAZptYrbf5YeW618PWyvG8w/g9ZwquuJ6" + "Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/" + "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmV" + "pb5NN9LUyddu7nnLYkrrrjiimuVK6mZB+6VuFbiXJk8v/" + "bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xxxRXXKldSMw+8KPG" + "gxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx" + "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8j" + "O4Ot9uTEq8KrrjiiiuuZa6kZh74UFpli3sO61btMfyHyW" + "Gv/RMs7wB67ne32/BdwRVXXHHFtcyV1MwDn0qrbHHvyPT" + "/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI" + "F5dWoNvcLcs/AAAAAElFTkSuQmCC">>). logo_fill() -> - jlib:decode_base64( - "iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzAAAAAXNSR0IArs4c" - "6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p4q/q5K/rpq/sqM/tam/ubGzn/S/AAA" - "AEFJREFUCNdlw0sRwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr" - "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAAElFTkSuQmCC"). + jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA" + "AAAAXNSR0IArs4c6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p" + "4q/q5K/rpq/sqM/tam/ubGzn/S/AAAAEFJREFUCNdlw0s" + "RwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr" + "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAA" + "ElFTkSuQmCC">>). %%%================================== %%%% process_admin process_admin(global, - #request{path = [], - auth = {_, _, AJID}, + #request{path = [], auth = {_, _, AJID}, lang = Lang}) -> - %%Base = get_base_path(global, cluster), - make_xhtml(?H1GL(?T("Administration"), "toc", "Contents") ++ - [?XE("ul", - [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(global, cluster, Lang, AJID)] - ) - ], global, Lang, AJID); - + make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>, + <<"Contents">>)) + ++ + [?XE(<<"ul">>, + [?LI([?ACT(MIU, MIN)]) + || {MIU, MIN} + <- get_menu_items(global, cluster, Lang, AJID)])], + global, Lang, AJID); process_admin(Host, - #request{path = [], - auth = {_, _Auth, AJID}, + #request{path = [], auth = {_, _Auth, AJID}, lang = Lang}) -> - %%Base = get_base_path(Host, cluster), - make_xhtml([?XCT("h1", "Administration"), - ?XE("ul", - [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- get_menu_items(Host, cluster, Lang, AJID)] - ) - ], Host, Lang, AJID); - -process_admin(Host, #request{path = ["style.css"]}) -> - {200, [{"Content-Type", "text/css"}, last_modified(), cache_control_public()], css(Host)}; - -process_admin(_Host, #request{path = ["favicon.ico"]}) -> - {200, [{"Content-Type", "image/x-icon"}, last_modified(), cache_control_public()], favicon()}; - -process_admin(_Host, #request{path = ["logo.png"]}) -> - {200, [{"Content-Type", "image/png"}, last_modified(), cache_control_public()], logo()}; - -process_admin(_Host, #request{path = ["logo-fill.png"]}) -> - {200, [{"Content-Type", "image/png"}, last_modified(), cache_control_public()], logo_fill()}; - -process_admin(_Host, #request{path = ["additions.js"]}) -> - {200, [{"Content-Type", "text/javascript"}, last_modified(), cache_control_public()], additions_js()}; - + make_xhtml([?XCT(<<"h1">>, <<"Administration">>), + ?XE(<<"ul">>, + [?LI([?ACT(MIU, MIN)]) + || {MIU, MIN} + <- get_menu_items(Host, cluster, Lang, AJID)])], + Host, Lang, AJID); process_admin(Host, - #request{path = ["acls-raw"], - q = Query, - auth = {_, _Auth, AJID}, - lang = Lang}) -> - - Res = case lists:keysearch("acls", 1, Query) of - {value, {_, String}} -> - case erl_scan:string(String) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, NewACLs} -> - case acl:add_list(Host, NewACLs, true) of - ok -> - ok; - _ -> - error - end; - _ -> - error - end; - _ -> - error - end; - _ -> - nothing + #request{path = [<<"style.css">>]}) -> + {200, + [{<<"Content-Type">>, <<"text/css">>}, last_modified(), + cache_control_public()], + css(Host)}; +process_admin(_Host, + #request{path = [<<"favicon.ico">>]}) -> + {200, + [{<<"Content-Type">>, <<"image/x-icon">>}, + last_modified(), cache_control_public()], + favicon()}; +process_admin(_Host, + #request{path = [<<"logo.png">>]}) -> + {200, + [{<<"Content-Type">>, <<"image/png">>}, last_modified(), + cache_control_public()], + logo()}; +process_admin(_Host, + #request{path = [<<"logo-fill.png">>]}) -> + {200, + [{<<"Content-Type">>, <<"image/png">>}, last_modified(), + cache_control_public()], + logo_fill()}; +process_admin(_Host, + #request{path = [<<"additions.js">>]}) -> + {200, + [{<<"Content-Type">>, <<"text/javascript">>}, + last_modified(), cache_control_public()], + additions_js()}; +process_admin(Host, + #request{path = [<<"acls-raw">>], q = Query, + auth = {_, _Auth, AJID}, lang = Lang}) -> + Res = case lists:keysearch(<<"acls">>, 1, Query) of + {value, {_, String}} -> + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, NewACLs} -> + case acl:add_list(Host, NewACLs, true) of + ok -> ok; + _ -> error + end; + _ -> error + end; + _ -> error + end; + _ -> nothing end, - ACLs = lists:keysort(2, ets:select(acl, [{{acl, {'$1', Host}, '$2'}, - [], [{{acl, '$1', '$2'}}]}])), + ACLs = lists:keysort(2, + ets:select(acl, + [{{acl, {'$1', Host}, '$2'}, [], + [{{acl, '$1', '$2'}}]}])), {NumLines, ACLsP} = term_to_paragraph(ACLs, 80), - make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; + make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), + <<"ACLDefinition">>, <<"ACL Definition">>)) + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?TEXTAREA("acls", integer_to_list(lists:max([16, NumLines])), "80", ACLsP++"."), - ?BR, - ?INPUTT("submit", "submit", "Submit") - ]) - ], Host, Lang, AJID); - + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?TEXTAREA(<<"acls">>, + (iolist_to_binary(integer_to_list(lists:max([16, + NumLines])))), + <<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>), + ?BR, + ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], + Host, Lang, AJID); process_admin(Host, - #request{method = Method, - path = ["acls"], - auth = {_, _Auth, AJID}, - q = Query, - lang = Lang}) -> + #request{method = Method, path = [<<"acls">>], + auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> ?DEBUG("query: ~p", [Query]), Res = case Method of - 'POST' -> - case catch acl_parse_query(Host, Query) of - {'EXIT', _} -> - error; - NewACLs -> - ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]), - case acl:add_list(Host, NewACLs, true) of - ok -> - ?INFO_MSG("NewACLs: ok", []), - ok; - _ -> - error - end - end; - _ -> - nothing + 'POST' -> + case catch acl_parse_query(Host, Query) of + {'EXIT', _} -> error; + NewACLs -> + ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]), + case acl:add_list(Host, NewACLs, true) of + ok -> ?INFO_MSG("NewACLs: ok", []), ok; + _ -> error + end + end; + _ -> nothing end, - ACLs = lists:keysort( - 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'}, - [], [{{acl, '$1', '$2'}}]}])), - make_xhtml(?H1GL(?T("Access Control Lists"), "ACLDefinition", "ACL Definition") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; + ACLs = lists:keysort(2, + ets:select(acl, + [{{acl, {'$1', Host}, '$2'}, [], + [{{acl, '$1', '$2'}}]}])), + make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), + <<"ACLDefinition">>, <<"ACL Definition">>)) + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; nothing -> [] - end ++ - [?XE("p", [?ACT("../acls-raw/", "Raw")])] ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [acls_to_xhtml(ACLs), - ?BR, - ?INPUTT("submit", "delete", "Delete Selected"), - ?C(" "), - ?INPUTT("submit", "submit", "Submit") - ]) - ], Host, Lang, AJID); - + end + ++ + [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [acls_to_xhtml(ACLs), ?BR, + ?INPUTT(<<"submit">>, <<"delete">>, + <<"Delete Selected">>), + ?C(<<" ">>), + ?INPUTT(<<"submit">>, <<"submit">>, + <<"Submit">>)])], + Host, Lang, AJID); process_admin(Host, - #request{path = ["access-raw"], - auth = {_, _Auth, AJID}, - q = Query, - lang = Lang}) -> - SetAccess = - fun(Rs) -> - mnesia:transaction( - fun() -> - Os = mnesia:select( - config, - [{{config, {access, '$1', Host}, '$2'}, - [], - ['$_']}]), - lists:foreach(fun(O) -> - mnesia:delete_object(O) - end, Os), - lists:foreach( - fun({access, Name, Rules}) -> - mnesia:write({config, - {access, Name, Host}, - Rules}) - end, Rs) - end) - end, - Res = case lists:keysearch("access", 1, Query) of - {value, {_, String}} -> - case erl_scan:string(String) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Rs} -> - case SetAccess(Rs) of - {atomic, _} -> - ok; - _ -> - error - end; - _ -> - error - end; - _ -> - error - end; - _ -> - nothing + #request{path = [<<"access-raw">>], + auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> + SetAccess = fun (Rs) -> + mnesia:transaction(fun () -> + Os = mnesia:select(config, + [{{config, + {access, + '$1', + Host}, + '$2'}, + [], + ['$_']}]), + lists:foreach(fun (O) -> + mnesia:delete_object(O) + end, + Os), + lists:foreach(fun ({access, + Name, + Rules}) -> + mnesia:write({config, + {access, + Name, + Host}, + Rules}) + end, + Rs) + end) + end, + Res = case lists:keysearch(<<"access">>, 1, Query) of + {value, {_, String}} -> + case erl_scan:string(binary_to_list(String)) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, Rs} -> + case SetAccess(Rs) of + {atomic, _} -> ok; + _ -> error + end; + _ -> error + end; + _ -> error + end; + _ -> nothing end, - Access = - ets:select(config, - [{{config, {access, '$1', Host}, '$2'}, - [], - [{{access, '$1', '$2'}}]}]), - {NumLines, AccessP} = term_to_paragraph(Access, 80), - make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; + Access = ets:select(config, + [{{config, {access, '$1', Host}, '$2'}, [], + [{{access, '$1', '$2'}}]}]), + {NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80), + make_xhtml((?H1GL((?T(<<"Access Rules">>)), + <<"AccessRights">>, <<"Access Rights">>)) + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?TEXTAREA("access", integer_to_list(lists:max([16, NumLines])), "80", AccessP++"."), - ?BR, - ?INPUTT("submit", "submit", "Submit") - ]) - ], Host, Lang, AJID); - + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?TEXTAREA(<<"access">>, + (iolist_to_binary(integer_to_list(lists:max([16, + NumLines])))), + <<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>), + ?BR, + ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], + Host, Lang, AJID); process_admin(Host, - #request{method = Method, - path = ["access"], - q = Query, - auth = {_, _Auth, AJID}, - lang = Lang}) -> + #request{method = Method, path = [<<"access">>], + q = Query, auth = {_, _Auth, AJID}, lang = Lang}) -> ?DEBUG("query: ~p", [Query]), Res = case Method of - 'POST' -> - case catch access_parse_query(Host, Query) of - {'EXIT', _} -> - error; - ok -> - ok - end; - _ -> - nothing + 'POST' -> + case catch access_parse_query(Host, Query) of + {'EXIT', _} -> error; + ok -> ok + end; + _ -> nothing end, - AccessRules = - ets:select(config, - [{{config, {access, '$1', Host}, '$2'}, - [], - [{{access, '$1', '$2'}}]}]), - make_xhtml(?H1GL(?T("Access Rules"), "AccessRights", "Access Rights") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; + AccessRules = ets:select(config, + [{{config, {access, '$1', Host}, '$2'}, [], + [{{access, '$1', '$2'}}]}]), + make_xhtml((?H1GL((?T(<<"Access Rules">>)), + <<"AccessRights">>, <<"Access Rights">>)) + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; nothing -> [] - end ++ - [?XE("p", [?ACT("../access-raw/", "Raw")])] ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [access_rules_to_xhtml(AccessRules, Lang), - ?BR, - ?INPUTT("submit", "delete", "Delete Selected") - ]) - ], Host, Lang, AJID); - + end + ++ + [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])] + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [access_rules_to_xhtml(AccessRules, Lang), ?BR, + ?INPUTT(<<"submit">>, <<"delete">>, + <<"Delete Selected">>)])], + Host, Lang, AJID); process_admin(Host, - #request{path = ["access", SName], - q = Query, - auth = {_, _Auth, AJID}, - lang = Lang}) -> + #request{path = [<<"access">>, SName], q = Query, + auth = {_, _Auth, AJID}, lang = Lang}) -> ?DEBUG("query: ~p", [Query]), - Name = list_to_atom(SName), - Res = case lists:keysearch("rules", 1, Query) of - {value, {_, String}} -> - case parse_access_rule(String) of - {ok, Rs} -> - ejabberd_config:add_global_option( - {access, Name, Host}, Rs), - ok; - _ -> - error - end; - _ -> - nothing + Name = jlib:binary_to_atom(SName), + Res = case lists:keysearch(<<"rules">>, 1, Query) of + {value, {_, String}} -> + case parse_access_rule(String) of + {ok, Rs} -> + ejabberd_config:add_global_option({access, Name, Host}, + Rs), + ok; + _ -> error + end; + _ -> nothing end, - Rules = case ejabberd_config:get_global_option({access, Name, Host}) of - undefined -> - []; - Rs1 -> - Rs1 + Rules = case ejabberd_config:get_global_option( + {access, Name, Host}, fun(V) -> V end) + of + undefined -> []; + Rs1 -> Rs1 end, - make_xhtml([?XC("h1", - io_lib:format(?T("~s access rule configuration"), [SName]))] ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; + make_xhtml([?XC(<<"h1">>, + list_to_binary(io_lib:format( + ?T(<<"~s access rule configuration">>), + [SName])))] + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [access_rule_to_xhtml(Rules), - ?BR, - ?INPUTT("submit", "submit", "Submit") - ]) - ], Host, Lang, AJID); - + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [access_rule_to_xhtml(Rules), ?BR, + ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], + Host, Lang, AJID); process_admin(global, - #request{path = ["vhosts"], - auth = {_, _Auth, AJID}, + #request{path = [<<"vhosts">>], auth = {_, _Auth, AJID}, lang = Lang}) -> Res = list_vhosts(Lang, AJID), - make_xhtml(?H1GL(?T("Virtual Hosts"), "virtualhost", "Virtual Hosting") ++ Res, global, Lang, AJID); - + make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)), + <<"virtualhost">>, <<"Virtual Hosting">>)) + ++ Res, + global, Lang, AJID); process_admin(Host, - #request{path = ["users"], - q = Query, - auth = {_, _Auth, AJID}, - lang = Lang}) when is_list(Host) -> + #request{path = [<<"users">>], q = Query, + auth = {_, _Auth, AJID}, lang = Lang}) + when is_binary(Host) -> Res = list_users(Host, Query, Lang, fun url_func/1), - make_xhtml([?XCT("h1", "Users")] ++ Res, Host, Lang, AJID); - + make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host, + Lang, AJID); process_admin(Host, - #request{path = ["users", Diap], - auth = {_, _Auth, AJID}, - lang = Lang}) when is_list(Host) -> - Res = list_users_in_diapason(Host, Diap, Lang, fun url_func/1), - make_xhtml([?XCT("h1", "Users")] ++ Res, Host, Lang, AJID); - + #request{path = [<<"users">>, Diap], + auth = {_, _Auth, AJID}, lang = Lang}) + when is_binary(Host) -> + Res = list_users_in_diapason(Host, Diap, Lang, + fun url_func/1), + make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host, + Lang, AJID); process_admin(Host, - #request{ - path = ["online-users"], - auth = {_, _Auth, AJID}, - lang = Lang}) when is_list(Host) -> + #request{path = [<<"online-users">>], + auth = {_, _Auth, AJID}, lang = Lang}) + when is_binary(Host) -> Res = list_online_users(Host, Lang), - make_xhtml([?XCT("h1", "Online Users")] ++ Res, Host, Lang, AJID); - + make_xhtml([?XCT(<<"h1">>, <<"Online Users">>)] ++ Res, + Host, Lang, AJID); process_admin(Host, - #request{path = ["last-activity"], - auth = {_, _Auth, AJID}, - q = Query, - lang = Lang}) when is_list(Host) -> + #request{path = [<<"last-activity">>], + auth = {_, _Auth, AJID}, q = Query, lang = Lang}) + when is_binary(Host) -> ?DEBUG("query: ~p", [Query]), - Month = case lists:keysearch("period", 1, Query) of - {value, {_, Val}} -> - Val; - _ -> - "month" + Month = case lists:keysearch(<<"period">>, 1, Query) of + {value, {_, Val}} -> Val; + _ -> <<"month">> end, - Res = case lists:keysearch("ordinary", 1, Query) of - {value, {_, _}} -> - list_last_activity(Host, Lang, false, Month); - _ -> - list_last_activity(Host, Lang, true, Month) + Res = case lists:keysearch(<<"ordinary">>, 1, Query) of + {value, {_, _}} -> + list_last_activity(Host, Lang, false, Month); + _ -> list_last_activity(Host, Lang, true, Month) end, - make_xhtml([?XCT("h1", "Users Last Activity")] ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?CT("Period: "), - ?XAE("select", [{"name", "period"}], - lists:map( - fun({O, V}) -> - Sel = if - O == Month -> [{"selected", "selected"}]; - true -> [] - end, - ?XAC("option", - Sel ++ [{"value", O}], V) - end, [{"month", ?T("Last month")}, - {"year", ?T("Last year")}, - {"all", ?T("All activity")}])), - ?C(" "), - ?INPUTT("submit", "ordinary", "Show Ordinary Table"), - ?C(" "), - ?INPUTT("submit", "integral", "Show Integral Table") - ])] ++ - Res, Host, Lang, AJID); - + make_xhtml([?XCT(<<"h1">>, <<"Users Last Activity">>)] + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?CT(<<"Period: ">>), + ?XAE(<<"select">>, [{<<"name">>, <<"period">>}], + (lists:map(fun ({O, V}) -> + Sel = if O == Month -> + [{<<"selected">>, + <<"selected">>}]; + true -> [] + end, + ?XAC(<<"option">>, + (Sel ++ + [{<<"value">>, O}]), + V) + end, + [{<<"month">>, ?T(<<"Last month">>)}, + {<<"year">>, ?T(<<"Last year">>)}, + {<<"all">>, + ?T(<<"All activity">>)}]))), + ?C(<<" ">>), + ?INPUTT(<<"submit">>, <<"ordinary">>, + <<"Show Ordinary Table">>), + ?C(<<" ">>), + ?INPUTT(<<"submit">>, <<"integral">>, + <<"Show Integral Table">>)])] + ++ Res, + Host, Lang, AJID); process_admin(Host, - #request{path = ["stats"], - auth = {_, _Auth, AJID}, + #request{path = [<<"stats">>], auth = {_, _Auth, AJID}, lang = Lang}) -> Res = get_stats(Host, Lang), - make_xhtml([?XCT("h1", "Statistics")] ++ Res, Host, Lang, AJID); - + make_xhtml([?XCT(<<"h1">>, <<"Statistics">>)] ++ Res, + Host, Lang, AJID); process_admin(Host, - #request{path = ["user", U], - auth = {_, _Auth, AJID}, - q = Query, - lang = Lang}) -> + #request{path = [<<"user">>, U], + auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> case ejabberd_auth:is_user_exists(U, Host) of - true -> - Res = user_info(U, Host, Query, Lang), - make_xhtml(Res, Host, Lang, AJID); - false -> - make_xhtml([?XCT("h1", "Not Found")], Host, Lang, AJID) + true -> + Res = user_info(U, Host, Query, Lang), + make_xhtml(Res, Host, Lang, AJID); + false -> + make_xhtml([?XCT(<<"h1">>, <<"Not Found">>)], Host, + Lang, AJID) end; - process_admin(Host, - #request{path = ["nodes"], - auth = {_, _Auth, AJID}, + #request{path = [<<"nodes">>], auth = {_, _Auth, AJID}, lang = Lang}) -> Res = get_nodes(Lang), make_xhtml(Res, Host, Lang, AJID); - process_admin(Host, - #request{path = ["node", SNode | NPath], - auth = {_, _Auth, AJID}, - q = Query, - lang = Lang}) -> + #request{path = [<<"node">>, SNode | NPath], + auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> case search_running_node(SNode) of - false -> - make_xhtml([?XCT("h1", "Node not found")], Host, Lang, AJID); - Node -> - Res = get_node(Host, Node, NPath, Query, Lang), - make_xhtml(Res, Host, Node, Lang, AJID) + false -> + make_xhtml([?XCT(<<"h1">>, <<"Node not found">>)], Host, + Lang, AJID); + Node -> + Res = get_node(Host, Node, NPath, Query, Lang), + make_xhtml(Res, Host, Node, Lang, AJID) end; - %%%================================== %%%% process_admin default case - -process_admin(Host, #request{lang = Lang, - auth = {_, _Auth, AJID} - } = Request) -> +process_admin(Host, + #request{lang = Lang, auth = {_, _Auth, AJID}} = + Request) -> {Hook, Opts} = case Host of - global -> {webadmin_page_main, [Request]}; - Host -> {webadmin_page_host, [Host, Request]} + global -> {webadmin_page_main, [Request]}; + Host -> {webadmin_page_host, [Host, Request]} end, case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of - [] -> setelement(1, make_xhtml([?XC("h1", "Not Found")], Host, Lang, AJID), 404); - Res -> make_xhtml(Res, Host, Lang, AJID) + [] -> + setelement(1, + make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang, + AJID), + 404); + Res -> make_xhtml(Res, Host, Lang, AJID) end. %%%================================== %%%% acl acls_to_xhtml(ACLs) -> - ?XAE("table", [], - [?XE("tbody", - lists:map( - fun({acl, Name, Spec} = ACL) -> - SName = atom_to_list(Name), - ID = term_to_id(ACL), - ?XE("tr", - [?XE("td", [?INPUT("checkbox", "selected", ID)]), - ?XC("td", SName)] ++ - acl_spec_to_xhtml(ID, Spec) - ) - end, ACLs) ++ - [?XE("tr", - [?X("td"), - ?XE("td", [?INPUT("text", "namenew", "")]) - ] ++ - acl_spec_to_xhtml("new", {user, ""}) - )] - )]). - -acl_spec_to_text({user, U}) -> - {user, U}; - -acl_spec_to_text({server, S}) -> - {server, S}; - + ?XAE(<<"table">>, [], + [?XE(<<"tbody">>, + (lists:map(fun ({acl, Name, Spec} = ACL) -> + SName = iolist_to_binary(atom_to_list(Name)), + ID = term_to_id(ACL), + ?XE(<<"tr">>, + ([?XE(<<"td">>, + [?INPUT(<<"checkbox">>, + <<"selected">>, ID)]), + ?XC(<<"td">>, SName)] + ++ acl_spec_to_xhtml(ID, Spec))) + end, + ACLs) + ++ + [?XE(<<"tr">>, + ([?X(<<"td">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"namenew">>, <<"">>)])] + ++ acl_spec_to_xhtml(<<"new">>, {user, <<"">>})))]))]). + +acl_spec_to_text({user, U}) -> {user, U}; +acl_spec_to_text({server, S}) -> {server, S}; acl_spec_to_text({user, U, S}) -> - {user, U ++ "@" ++ S}; - + {user, <<U/binary, "@", S/binary>>}; acl_spec_to_text({user_regexp, RU}) -> {user_regexp, RU}; - acl_spec_to_text({user_regexp, RU, S}) -> - {user_regexp, RU ++ "@" ++ S}; - + {user_regexp, <<RU/binary, "@", S/binary>>}; acl_spec_to_text({server_regexp, RS}) -> {server_regexp, RS}; - acl_spec_to_text({node_regexp, RU, RS}) -> - {node_regexp, RU ++ "@" ++ RS}; - -acl_spec_to_text({user_glob, RU}) -> - {user_glob, RU}; - + {node_regexp, <<RU/binary, "@", RS/binary>>}; +acl_spec_to_text({user_glob, RU}) -> {user_glob, RU}; acl_spec_to_text({user_glob, RU, S}) -> - {user_glob, RU ++ "@" ++ S}; - + {user_glob, <<RU/binary, "@", S/binary>>}; acl_spec_to_text({server_glob, RS}) -> {server_glob, RS}; - acl_spec_to_text({node_glob, RU, RS}) -> - {node_glob, RU ++ "@" ++ RS}; - -acl_spec_to_text(all) -> - {all, ""}; - -acl_spec_to_text(Spec) -> - {raw, term_to_string(Spec)}. + {node_glob, <<RU/binary, "@", RS/binary>>}; +acl_spec_to_text(all) -> {all, <<"">>}; +acl_spec_to_text(Spec) -> {raw, term_to_string(Spec)}. acl_spec_to_xhtml(ID, Spec) -> {Type, Str} = acl_spec_to_text(Spec), [acl_spec_select(ID, Type), ?ACLINPUT(Str)]. acl_spec_select(ID, Opt) -> - ?XE("td", - [?XAE("select", [{"name", "type" ++ ID}], - lists:map( - fun(O) -> - Sel = if - O == Opt -> [{"selected", "selected"}]; - true -> [] - end, - ?XAC("option", - Sel ++ [{"value", atom_to_list(O)}], - atom_to_list(O)) - end, [user, server, user_regexp, server_regexp, - node_regexp, user_glob, server_glob, node_glob, all, raw]))]). - + ?XE(<<"td">>, + [?XAE(<<"select">>, + [{<<"name">>, <<"type", ID/binary>>}], + (lists:map(fun (O) -> + Sel = if O == Opt -> + [{<<"selected">>, + <<"selected">>}]; + true -> [] + end, + ?XAC(<<"option">>, + (Sel ++ + [{<<"value">>, + iolist_to_binary(atom_to_list(O))}]), + (iolist_to_binary(atom_to_list(O)))) + end, + [user, server, user_regexp, server_regexp, node_regexp, + user_glob, server_glob, node_glob, all, raw])))]). -%% @spec (T::any()) -> StringLine::string() term_to_string(T) -> - StringParagraph = lists:flatten(io_lib:format("~1000000p", [T])), - %% Remove from the string all the carriage returns characters - ejabberd_regexp:greplace(StringParagraph, "\\n ", ""). + StringParagraph = + iolist_to_binary(io_lib:format("~1000000p", [T])), + ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>, + <<"">>). -%% @spec (T::any(), Cols::integer()) -> {NumLines::integer(), Paragraph::string()} term_to_paragraph(T, Cols) -> - Paragraph = erl_prettypr:format(erl_syntax:abstract(T), [{paper, Cols}]), - FieldList = ejabberd_regexp:split(Paragraph, "\n"), + Paragraph = list_to_binary(erl_prettypr:format(erl_syntax:abstract(T), + [{paper, Cols}])), + FieldList = ejabberd_regexp:split(Paragraph, <<"\n">>), NumLines = length(FieldList), {NumLines, Paragraph}. -term_to_id(T) -> - jlib:encode_base64(binary_to_list(term_to_binary(T))). - +term_to_id(T) -> jlib:encode_base64((term_to_binary(T))). acl_parse_query(Host, Query) -> - ACLs = ets:select(acl, [{{acl, {'$1', Host}, '$2'}, - [], [{{acl, '$1', '$2'}}]}]), - case lists:keysearch("submit", 1, Query) of - {value, _} -> - acl_parse_submit(ACLs, Query); - _ -> - case lists:keysearch("delete", 1, Query) of - {value, _} -> - acl_parse_delete(ACLs, Query) - end + ACLs = ets:select(acl, + [{{acl, {'$1', Host}, '$2'}, [], + [{{acl, '$1', '$2'}}]}]), + case lists:keysearch(<<"submit">>, 1, Query) of + {value, _} -> acl_parse_submit(ACLs, Query); + _ -> + case lists:keysearch(<<"delete">>, 1, Query) of + {value, _} -> acl_parse_delete(ACLs, Query) + end end. acl_parse_submit(ACLs, Query) -> - NewACLs = - lists:map( - fun({acl, Name, Spec} = ACL) -> - %%SName = atom_to_list(Name), - ID = term_to_id(ACL), - case {lists:keysearch("type" ++ ID, 1, Query), - lists:keysearch("value" ++ ID, 1, Query)} of - {{value, {_, T}}, {value, {_, V}}} -> - {Type, Str} = acl_spec_to_text(Spec), - case {atom_to_list(Type), Str} of - {T, V} -> - ACL; - _ -> - NewSpec = string_to_spec(T, V), - {acl, Name, NewSpec} - end; - _ -> - ACL - end - end, ACLs), - NewACL = case {lists:keysearch("namenew", 1, Query), - lists:keysearch("typenew", 1, Query), - lists:keysearch("valuenew", 1, Query)} of - {{value, {_, ""}}, _, _} -> - []; - {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} -> - NewName = list_to_atom(N), - NewSpec = string_to_spec(T, V), - [{acl, NewName, NewSpec}]; - _ -> - [] + NewACLs = lists:map(fun ({acl, Name, Spec} = ACL) -> + ID = term_to_id(ACL), + case {lists:keysearch(<<"type", ID/binary>>, 1, + Query), + lists:keysearch(<<"value", ID/binary>>, 1, + Query)} + of + {{value, {_, T}}, {value, {_, V}}} -> + {Type, Str} = acl_spec_to_text(Spec), + case + {iolist_to_binary(atom_to_list(Type)), + Str} + of + {T, V} -> ACL; + _ -> + NewSpec = string_to_spec(T, V), + {acl, Name, NewSpec} + end; + _ -> ACL + end + end, + ACLs), + NewACL = case {lists:keysearch(<<"namenew">>, 1, Query), + lists:keysearch(<<"typenew">>, 1, Query), + lists:keysearch(<<"valuenew">>, 1, Query)} + of + {{value, {_, <<"">>}}, _, _} -> []; + {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} -> + NewName = jlib:binary_to_atom(N), + NewSpec = string_to_spec(T, V), + [{acl, NewName, NewSpec}]; + _ -> [] end, NewACLs ++ NewACL. -string_to_spec("user", Val) -> +string_to_spec(<<"user">>, Val) -> string_to_spec2(user, Val); -string_to_spec("server", Val) -> - {server, Val}; -string_to_spec("user_regexp", Val) -> +string_to_spec(<<"server">>, Val) -> {server, Val}; +string_to_spec(<<"user_regexp">>, Val) -> string_to_spec2(user_regexp, Val); -string_to_spec("server_regexp", Val) -> +string_to_spec(<<"server_regexp">>, Val) -> {server_regexp, Val}; -string_to_spec("node_regexp", Val) -> - #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val), +string_to_spec(<<"node_regexp">>, Val) -> + #jid{luser = U, lserver = S, resource = <<"">>} = + jlib:string_to_jid(Val), {node_regexp, U, S}; -string_to_spec("user_glob", Val) -> +string_to_spec(<<"user_glob">>, Val) -> string_to_spec2(user_glob, Val); -string_to_spec("server_glob", Val) -> +string_to_spec(<<"server_glob">>, Val) -> {server_glob, Val}; -string_to_spec("node_glob", Val) -> - #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val), +string_to_spec(<<"node_glob">>, Val) -> + #jid{luser = U, lserver = S, resource = <<"">>} = + jlib:string_to_jid(Val), {node_glob, U, S}; -string_to_spec("all", _) -> - all; -string_to_spec("raw", Val) -> - {ok, Tokens, _} = erl_scan:string(Val ++ "."), +string_to_spec(<<"all">>, _) -> all; +string_to_spec(<<"raw">>, Val) -> + {ok, Tokens, _} = erl_scan:string(binary_to_list(<<Val/binary, ".">>)), {ok, NewSpec} = erl_parse:parse_term(Tokens), NewSpec. string_to_spec2(ACLName, Val) -> - #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val), + #jid{luser = U, lserver = S, resource = <<"">>} = + jlib:string_to_jid(Val), case U of - "" -> - {ACLName, S}; - _ -> - {ACLName, U, S} + <<"">> -> {ACLName, S}; + _ -> {ACLName, U, S} end. - acl_parse_delete(ACLs, Query) -> - NewACLs = - lists:filter( - fun({acl, _Name, _Spec} = ACL) -> - ID = term_to_id(ACL), - not lists:member({"selected", ID}, Query) - end, ACLs), + NewACLs = lists:filter(fun ({acl, _Name, _Spec} = + ACL) -> + ID = term_to_id(ACL), + not lists:member({"selected", ID}, Query) + end, + ACLs), NewACLs. - access_rules_to_xhtml(AccessRules, Lang) -> - ?XAE("table", [], - [?XE("tbody", - lists:map( - fun({access, Name, Rules} = Access) -> - SName = atom_to_list(Name), - ID = term_to_id(Access), - ?XE("tr", - [?XE("td", [?INPUT("checkbox", "selected", ID)]), - ?XE("td", [?AC(SName ++ "/", SName)]), - ?XC("td", term_to_string(Rules)) - ] - ) - end, AccessRules) ++ - [?XE("tr", - [?X("td"), - ?XE("td", [?INPUT("text", "namenew", "")]), - ?XE("td", [?INPUTT("submit", "addnew", "Add New")]) - ] - )] - )]). + ?XAE(<<"table">>, [], + [?XE(<<"tbody">>, + (lists:map(fun ({access, Name, Rules} = Access) -> + SName = iolist_to_binary(atom_to_list(Name)), + ID = term_to_id(Access), + ?XE(<<"tr">>, + [?XE(<<"td">>, + [?INPUT(<<"checkbox">>, + <<"selected">>, ID)]), + ?XE(<<"td">>, + [?AC(<<SName/binary, "/">>, SName)]), + ?XC(<<"td">>, (term_to_string(Rules)))]) + end, + lists:keysort(2,AccessRules)) + ++ + [?XE(<<"tr">>, + [?X(<<"td">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"namenew">>, <<"">>)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"addnew">>, + <<"Add New">>)])])]))]). access_parse_query(Host, Query) -> - AccessRules = - ets:select(config, - [{{config, {access, '$1', Host}, '$2'}, - [], - [{{access, '$1', '$2'}}]}]), - case lists:keysearch("addnew", 1, Query) of - {value, _} -> - access_parse_addnew(AccessRules, Host, Query); - _ -> - case lists:keysearch("delete", 1, Query) of - {value, _} -> - access_parse_delete(AccessRules, Host, Query) - end + AccessRules = ets:select(config, + [{{config, {access, '$1', Host}, '$2'}, [], + [{{access, '$1', '$2'}}]}]), + case lists:keysearch(<<"addnew">>, 1, Query) of + {value, _} -> + access_parse_addnew(AccessRules, Host, Query); + _ -> + case lists:keysearch(<<"delete">>, 1, Query) of + {value, _} -> + access_parse_delete(AccessRules, Host, Query) + end end. access_parse_addnew(_AccessRules, Host, Query) -> - case lists:keysearch("namenew", 1, Query) of - {value, {_, String}} when String /= "" -> - Name = list_to_atom(String), - ejabberd_config:add_global_option({access, Name, Host}, []), - ok + case lists:keysearch(<<"namenew">>, 1, Query) of + {value, {_, String}} when String /= <<"">> -> + Name = jlib:binary_to_atom(String), + ejabberd_config:add_global_option({access, Name, Host}, + []), + ok end. access_parse_delete(AccessRules, Host, Query) -> - lists:foreach( - fun({access, Name, _Rules} = AccessRule) -> - ID = term_to_id(AccessRule), - case lists:member({"selected", ID}, Query) of - true -> - mnesia:transaction( - fun() -> - mnesia:delete({config, {access, Name, Host}}) - end); - _ -> - ok - end - end, AccessRules), + lists:foreach(fun ({access, Name, _Rules} = + AccessRule) -> + ID = term_to_id(AccessRule), + case lists:member({<<"selected">>, ID}, Query) of + true -> + mnesia:transaction(fun () -> + mnesia:delete({config, + {access, + Name, + Host}}) + end); + _ -> ok + end + end, + AccessRules), ok. - - - access_rule_to_xhtml(Rules) -> - Text = lists:flatmap( - fun({Access, ACL} = _Rule) -> - SAccess = element_to_list(Access), - SACL = atom_to_list(ACL), - SAccess ++ "\s\t" ++ SACL ++ "\n" - end, Rules), - ?XAC("textarea", [{"name", "rules"}, - {"rows", "16"}, - {"cols", "80"}], - Text). + Text = lists:flatmap(fun ({Access, ACL} = _Rule) -> + SAccess = element_to_list(Access), + SACL = atom_to_list(ACL), + [SAccess, " \t", SACL, "\n"] + end, + Rules), + ?XAC(<<"textarea">>, + [{<<"name">>, <<"rules">>}, {<<"rows">>, <<"16">>}, + {<<"cols">>, <<"80">>}], + list_to_binary(Text)). parse_access_rule(Text) -> - Strings = string:tokens(Text, "\r\n"), - case catch lists:flatmap( - fun(String) -> - case string:tokens(String, "\s\t") of - [Access, ACL] -> - [{list_to_element(Access), list_to_atom(ACL)}]; - [] -> - [] - end - end, Strings) of - {'EXIT', _Reason} -> - error; - Rs -> - {ok, Rs} + Strings = str:tokens(Text, <<"\r\n">>), + case catch lists:flatmap(fun (String) -> + case str:tokens(String, <<" \t">>) of + [Access, ACL] -> + [{list_to_element(Access), + jlib:binary_to_atom(ACL)}]; + [] -> [] + end + end, + Strings) + of + {'EXIT', _Reason} -> error; + Rs -> {ok, Rs} end. %%%================================== %%%% list_vhosts list_vhosts(Lang, JID) -> - Hosts = ?MYHOSTS, - HostsAllowed = lists:filter( - fun(Host) -> - is_acl_match(Host, [configure, webadmin_view], JID) - end, - Hosts - ), + Hosts = (?MYHOSTS), + HostsAllowed = lists:filter(fun (Host) -> + is_acl_match(Host, + [configure, webadmin_view], + JID) + end, + Hosts), list_vhosts2(Lang, HostsAllowed). list_vhosts2(Lang, Hosts) -> SHosts = lists:sort(Hosts), - [?XE("table", - [?XE("thead", - [?XE("tr", - [?XCT("td", "Host"), - ?XCT("td", "Registered Users"), - ?XCT("td", "Online Users") - ])]), - ?XE("tbody", - lists:map( - fun(Host) -> - OnlineUsers = - length(ejabberd_sm:get_vh_session_list(Host)), - RegisteredUsers = - ejabberd_auth:get_vh_registered_users_number(Host), - ?XE("tr", - [?XE("td", [?AC("../server/" ++ Host ++ "/", Host)]), - ?XC("td", pretty_string_int(RegisteredUsers)), - ?XC("td", pretty_string_int(OnlineUsers)) - ]) - end, SHosts) - )])]. + [?XE(<<"table">>, + [?XE(<<"thead">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Host">>), + ?XCT(<<"td">>, <<"Registered Users">>), + ?XCT(<<"td">>, <<"Online Users">>)])]), + ?XE(<<"tbody">>, + (lists:map(fun (Host) -> + OnlineUsers = + length(ejabberd_sm:get_vh_session_list(Host)), + RegisteredUsers = + ejabberd_auth:get_vh_registered_users_number(Host), + ?XE(<<"tr">>, + [?XE(<<"td">>, + [?AC(<<"../server/", Host/binary, + "/">>, + Host)]), + ?XC(<<"td">>, + (pretty_string_int(RegisteredUsers))), + ?XC(<<"td">>, + (pretty_string_int(OnlineUsers)))]) + end, + SHosts)))])]. %%%================================== %%%% list_users @@ -1483,387 +1283,424 @@ list_users(Host, Query, Lang, URLFunc) -> Res = list_users_parse_query(Query, Host), Users = ejabberd_auth:get_vh_registered_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), - FUsers = - case length(SUsers) of - N when N =< 100 -> - [list_given_users(Host, SUsers, "../", Lang, URLFunc)]; - N -> - NParts = trunc(math:sqrt(N * 0.618)) + 1, - M = trunc(N / NParts) + 1, - lists:flatmap( - fun(K) -> - L = K + M - 1, - %%Node = integer_to_list(K) ++ "-" ++ integer_to_list(L), - Last = if L < N -> su_to_list(lists:nth(L, SUsers)); - true -> su_to_list(lists:last(SUsers)) + FUsers = case length(SUsers) of + N when N =< 100 -> + [list_given_users(Host, SUsers, <<"../">>, Lang, + URLFunc)]; + N -> + NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + + 1, + M = trunc(N / NParts) + 1, + lists:flatmap(fun (K) -> + L = K + M - 1, + Last = if L < N -> + su_to_list(lists:nth(L, + SUsers)); + true -> + su_to_list(lists:last(SUsers)) + end, + Name = <<(su_to_list(lists:nth(K, + SUsers)))/binary, + $\s, 226, 128, 148, $\s, + Last/binary>>, + [?AC((URLFunc({user_diapason, K, L})), + Name), + ?BR] end, - Name = - su_to_list(lists:nth(K, SUsers)) ++ - [$\s, 226, 128, 148, $\s] ++ - Last, - [?AC(URLFunc({user_diapason, K, L}), Name), ?BR] - end, lists:seq(1, N, M)) - end, + lists:seq(1, N, M)) + end, case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?XE("table", - [?XE("tr", - [?XC("td", ?T("User") ++ ":"), - ?XE("td", [?INPUT("text", "newusername", "")]), - ?XE("td", [?C([" @ ", Host])]) - ]), - ?XE("tr", - [?XC("td", ?T("Password") ++ ":"), - ?XE("td", [?INPUT("password", "newuserpassword", "")]), - ?X("td") - ]), - ?XE("tr", - [?X("td"), - ?XAE("td", [{"class", "alignright"}], - [?INPUTT("submit", "addnewuser", "Add User")]), - ?X("td") - ])]), - ?P] ++ - FUsers)]. - -%% Parse user creation query and try register: + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; + nothing -> [] + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + ([?XE(<<"table">>, + [?XE(<<"tr">>, + [?XC(<<"td">>, <<(?T(<<"User">>))/binary, ":">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"newusername">>, <<"">>)]), + ?XE(<<"td">>, [?C(<<" @ ", Host/binary>>)])]), + ?XE(<<"tr">>, + [?XC(<<"td">>, <<(?T(<<"Password">>))/binary, ":">>), + ?XE(<<"td">>, + [?INPUT(<<"password">>, <<"newuserpassword">>, + <<"">>)]), + ?X(<<"td">>)]), + ?XE(<<"tr">>, + [?X(<<"td">>), + ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}], + [?INPUTT(<<"submit">>, <<"addnewuser">>, + <<"Add User">>)]), + ?X(<<"td">>)])]), + ?P] + ++ FUsers))]. + list_users_parse_query(Query, Host) -> - case lists:keysearch("addnewuser", 1, Query) of - {value, _} -> - {value, {_, Username}} = - lists:keysearch("newusername", 1, Query), - {value, {_, Password}} = - lists:keysearch("newuserpassword", 1, Query), - case jlib:string_to_jid(Username++"@"++Host) of - error -> - error; - #jid{user = User, server = Server} -> - case ejabberd_auth:try_register(User, Server, Password) of - {error, _Reason} -> - error; - _ -> - ok - end - end; - false -> - nothing + case lists:keysearch(<<"addnewuser">>, 1, Query) of + {value, _} -> + {value, {_, Username}} = + lists:keysearch(<<"newusername">>, 1, Query), + {value, {_, Password}} = + lists:keysearch(<<"newuserpassword">>, 1, Query), + case jlib:string_to_jid(<<Username/binary, "@", + Host/binary>>) + of + error -> error; + #jid{user = User, server = Server} -> + case ejabberd_auth:try_register(User, Server, Password) + of + {error, _Reason} -> error; + _ -> ok + end + end; + false -> nothing end. - list_users_in_diapason(Host, Diap, Lang, URLFunc) -> Users = ejabberd_auth:get_vh_registered_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), - [S1, S2] = ejabberd_regexp:split(Diap, "-"), - N1 = list_to_integer(S1), - N2 = list_to_integer(S2), + [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), + N1 = jlib:binary_to_integer(S1), + N2 = jlib:binary_to_integer(S2), Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), - [list_given_users(Host, Sub, "../../", Lang, URLFunc)]. + [list_given_users(Host, Sub, <<"../../">>, Lang, + URLFunc)]. list_given_users(Host, Users, Prefix, Lang, URLFunc) -> ModOffline = get_offlinemsg_module(Host), - ?XE("table", - [?XE("thead", - [?XE("tr", - [?XCT("td", "User"), - ?XCT("td", "Offline Messages"), - ?XCT("td", "Last Activity")])]), - ?XE("tbody", - lists:map( - fun(_SU = {Server, User}) -> - US = {User, Server}, - QueueLenStr = get_offlinemsg_length(ModOffline, User, Server), - FQueueLen = [?AC(URLFunc({users_queue, Prefix, - User, Server}), - QueueLenStr)], - FLast = - case ejabberd_sm:get_user_resources(User, Server) of - [] -> - case mod_last:get_last_info(User, Server) of - not_found -> - ?T("Never"); - {ok, Shift, _Status} -> - TimeStamp = {Shift div 1000000, - Shift rem 1000000, - 0}, - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - lists:flatten( - io_lib:format( - "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, Hour, Minute, Second])) - end; - _ -> - ?T("Online") - end, - ?XE("tr", - [?XE("td", - [?AC(URLFunc({user, Prefix, - ejabberd_http:url_encode(User), - Server}), - us_to_list(US))]), - ?XE("td", FQueueLen), - ?XC("td", FLast)]) - end, Users) - )]). + ?XE(<<"table">>, + [?XE(<<"thead">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"User">>), + ?XCT(<<"td">>, <<"Offline Messages">>), + ?XCT(<<"td">>, <<"Last Activity">>)])]), + ?XE(<<"tbody">>, + (lists:map(fun (_SU = {Server, User}) -> + US = {User, Server}, + QueueLenStr = get_offlinemsg_length(ModOffline, + User, + Server), + FQueueLen = [?AC((URLFunc({users_queue, Prefix, + User, Server})), + QueueLenStr)], + FLast = case + ejabberd_sm:get_user_resources(User, + Server) + of + [] -> + case mod_last:get_last_info(User, + Server) + of + not_found -> ?T(<<"Never">>); + {ok, Shift, _Status} -> + TimeStamp = {Shift div + 1000000, + Shift rem + 1000000, + 0}, + {{Year, Month, Day}, + {Hour, Minute, Second}} = + calendar:now_to_local_time(TimeStamp), + iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [Year, + Month, + Day, + Hour, + Minute, + Second])) + end; + _ -> ?T(<<"Online">>) + end, + ?XE(<<"tr">>, + [?XE(<<"td">>, + [?AC((URLFunc({user, Prefix, + ejabberd_http:url_encode(User), + Server})), + (us_to_list(US)))]), + ?XE(<<"td">>, FQueueLen), + ?XC(<<"td">>, FLast)]) + end, + Users)))]). get_offlinemsg_length(ModOffline, User, Server) -> case ModOffline of - none -> "disabled"; - _ -> pretty_string_int(ModOffline:get_queue_length(User, Server)) + none -> <<"disabled">>; + _ -> + pretty_string_int(ModOffline:get_queue_length(User, + Server)) end. get_offlinemsg_module(Server) -> case gen_mod:is_loaded(Server, mod_offline) of - true -> - mod_offline; - false -> - none + true -> mod_offline; + false -> none end. get_lastactivity_menuitem_list(Server) -> case gen_mod:db_type(Server, mod_last) of - mnesia -> [{"last-activity", "Last Activity"}]; - _ -> [] + mnesia -> [{<<"last-activity">>, <<"Last Activity">>}]; + _ -> [] end. us_to_list({User, Server}) -> - jlib:jid_to_string({User, Server, ""}). + jlib:jid_to_string({User, Server, <<"">>}). su_to_list({Server, User}) -> - jlib:jid_to_string({User, Server, ""}). + jlib:jid_to_string({User, Server, <<"">>}). %%%================================== %%%% get_stats get_stats(global, Lang) -> OnlineUsers = mnesia:table_info(session, size), - RegisteredUsers = lists:foldl( - fun(Host, Total) -> - ejabberd_auth:get_vh_registered_users_number(Host) + Total - end, 0, ejabberd_config:get_global_option(hosts)), + RegisteredUsers = lists:foldl(fun (Host, Total) -> + ejabberd_auth:get_vh_registered_users_number(Host) + + Total + end, + 0, ?MYHOSTS), S2SConns = ejabberd_s2s:dirty_get_connections(), S2SConnections = length(S2SConns), - S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])), - [?XAE("table", [], - [?XE("tbody", - [?XE("tr", [?XCT("td", "Registered Users:"), - ?XC("td", pretty_string_int(RegisteredUsers))]), - ?XE("tr", [?XCT("td", "Online Users:"), - ?XC("td", pretty_string_int(OnlineUsers))]), - ?XE("tr", [?XCT("td", "Outgoing s2s Connections:"), - ?XC("td", pretty_string_int(S2SConnections))]), - ?XE("tr", [?XCT("td", "Outgoing s2s Servers:"), - ?XC("td", pretty_string_int(S2SServers))]) - ]) - ])]; - + S2SServers = length(lists:usort([element(2, C) + || C <- S2SConns])), + [?XAE(<<"table">>, [], + [?XE(<<"tbody">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Registered Users:">>), + ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Online Users:">>), + ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Outgoing s2s Connections:">>), + ?XC(<<"td">>, (pretty_string_int(S2SConnections)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Outgoing s2s Servers:">>), + ?XC(<<"td">>, (pretty_string_int(S2SServers)))])])])]; get_stats(Host, Lang) -> - OnlineUsers = length(ejabberd_sm:get_vh_session_list(Host)), - RegisteredUsers = ejabberd_auth:get_vh_registered_users_number(Host), - [?XAE("table", [], - [?XE("tbody", - [?XE("tr", [?XCT("td", "Registered Users:"), - ?XC("td", pretty_string_int(RegisteredUsers))]), - ?XE("tr", [?XCT("td", "Online Users:"), - ?XC("td", pretty_string_int(OnlineUsers))]) - ]) - ])]. - + OnlineUsers = + length(ejabberd_sm:get_vh_session_list(Host)), + RegisteredUsers = + ejabberd_auth:get_vh_registered_users_number(Host), + [?XAE(<<"table">>, [], + [?XE(<<"tbody">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Registered Users:">>), + ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Online Users:">>), + ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))])])])]. list_online_users(Host, _Lang) -> - Users = [{S, U} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)], + Users = [{S, U} + || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)], SUsers = lists:usort(Users), - lists:flatmap( - fun({_S, U} = SU) -> - [?AC("../user/" ++ ejabberd_http:url_encode(U) ++ "/", - su_to_list(SU)), - ?BR] - end, SUsers). + lists:flatmap(fun ({_S, U} = SU) -> + [?AC(<<"../user/", + (ejabberd_http:url_encode(U))/binary, "/">>, + (su_to_list(SU))), + ?BR] + end, + SUsers). user_info(User, Server, Query, Lang) -> LServer = jlib:nameprep(Server), US = {jlib:nodeprep(User), LServer}, Res = user_parse_query(User, Server, Query), - Resources = ejabberd_sm:get_user_resources(User, Server), + Resources = ejabberd_sm:get_user_resources(User, + Server), FResources = - case Resources of - [] -> - [?CT("None")]; - _ -> - [?XE("ul", - lists:map(fun(R) -> - FIP = case ejabberd_sm:get_user_info( - User, Server, R) of - offline -> - ""; - Info when is_list(Info) -> - Node = proplists:get_value(node, Info), - Conn = proplists:get_value(conn, Info), - {IP, Port} = proplists:get_value(ip, Info), - ConnS = case Conn of - c2s -> "plain"; - c2s_tls -> "tls"; - c2s_compressed -> "zlib"; - c2s_compressed_tls -> "tls+zlib"; - http_bind -> "http-bind"; - http_poll -> "http-poll" - end, - " (" ++ - ConnS ++ "://" ++ - inet_parse:ntoa(IP) ++ - ":" ++ - integer_to_list(Port) - ++ "#" ++ atom_to_list(Node) - ++ ")" - end, - ?LI([?C(R ++ FIP)]) - end, lists:sort(Resources)))] - end, + case Resources of + [] -> [?CT(<<"None">>)]; + _ -> + [?XE(<<"ul">>, + (lists:map( + fun (R) -> + FIP = case + ejabberd_sm:get_user_info(User, + Server, + R) + of + offline -> <<"">>; + Info + when + is_list(Info) -> + Node = + proplists:get_value(node, + Info), + Conn = + proplists:get_value(conn, + Info), + {IP, Port} = + proplists:get_value(ip, + Info), + ConnS = case Conn of + c2s -> + <<"plain">>; + c2s_tls -> + <<"tls">>; + c2s_compressed -> + <<"zlib">>; + c2s_compressed_tls -> + <<"tls+zlib">>; + http_bind -> + <<"http-bind">>; + http_poll -> + <<"http-poll">> + end, + <<" (", ConnS/binary, + "://", + (jlib:ip_to_list(IP))/binary, + ":", + (jlib:integer_to_binary(Port))/binary, + "#", + (jlib:atom_to_binary(Node))/binary, + ")">> + end, + ?LI([?C((<<R/binary, FIP/binary>>))]) + end, + lists:sort(Resources))))] + end, Password = ejabberd_auth:get_password_s(User, Server), - FPassword = [?INPUT("password", "password", Password), ?C(" "), - ?INPUTT("submit", "chpassword", "Change Password")], - UserItems = ejabberd_hooks:run_fold(webadmin_user, LServer, [], - [User, Server, Lang]), - %% Code copied from list_given_users/5: - LastActivity = case ejabberd_sm:get_user_resources(User, Server) of - [] -> - case mod_last:get_last_info(User, Server) of - not_found -> - ?T("Never"); - {ok, Shift, _Status} -> - TimeStamp = {Shift div 1000000, - Shift rem 1000000, - 0}, - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - lists:flatten( - io_lib:format( - "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, Hour, Minute, Second])) - end; - _ -> - ?T("Online") + FPassword = [?INPUT(<<"password">>, <<"password">>, + Password), + ?C(<<" ">>), + ?INPUTT(<<"submit">>, <<"chpassword">>, + <<"Change Password">>)], + UserItems = ejabberd_hooks:run_fold(webadmin_user, + LServer, [], [User, Server, Lang]), + LastActivity = case ejabberd_sm:get_user_resources(User, + Server) + of + [] -> + case mod_last:get_last_info(User, Server) of + not_found -> ?T(<<"Never">>); + {ok, Shift, _Status} -> + TimeStamp = {Shift div 1000000, + Shift rem 1000000, 0}, + {{Year, Month, Day}, {Hour, Minute, Second}} = + calendar:now_to_local_time(TimeStamp), + iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [Year, Month, Day, + Hour, Minute, + Second])) + end; + _ -> ?T(<<"Online">>) end, - [?XC("h1", ?T("User ") ++ us_to_list(US))] ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?XCT("h3", "Connected Resources:")] ++ FResources ++ - [?XCT("h3", "Password:")] ++ FPassword ++ - [?XCT("h3", "Last Activity")] ++ [?C(LastActivity)] ++ - UserItems ++ - [?P, ?INPUTT("submit", "removeuser", "Remove User")])]. - + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>), + [us_to_list(US)])))] + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; + nothing -> [] + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + ([?XCT(<<"h3">>, <<"Connected Resources:">>)] ++ + FResources ++ + [?XCT(<<"h3">>, <<"Password:">>)] ++ + FPassword ++ + [?XCT(<<"h3">>, <<"Last Activity">>)] ++ + [?C(LastActivity)] ++ + UserItems ++ + [?P, + ?INPUTT(<<"submit">>, <<"removeuser">>, + <<"Remove User">>)]))]. user_parse_query(User, Server, Query) -> - lists:foldl(fun({Action, _Value}, Acc) when Acc == nothing -> - user_parse_query1(Action, User, Server, Query); - ({_Action, _Value}, Acc) -> - Acc - end, nothing, Query). - -user_parse_query1("password", _User, _Server, _Query) -> + lists:foldl(fun ({Action, _Value}, Acc) + when Acc == nothing -> + user_parse_query1(Action, User, Server, Query); + ({_Action, _Value}, Acc) -> Acc + end, + nothing, Query). + +user_parse_query1(<<"password">>, _User, _Server, + _Query) -> nothing; -user_parse_query1("chpassword", User, Server, Query) -> - case lists:keysearch("password", 1, Query) of - {value, {_, undefined}} -> - error; - {value, {_, Password}} -> - ejabberd_auth:set_password(User, Server, Password), - ok; - _ -> - error +user_parse_query1(<<"chpassword">>, User, Server, + Query) -> + case lists:keysearch(<<"password">>, 1, Query) of + {value, {_, Password}} -> + ejabberd_auth:set_password(User, Server, Password), ok; + _ -> error end; -user_parse_query1("removeuser", User, Server, _Query) -> - ejabberd_auth:remove_user(User, Server), - ok; +user_parse_query1(<<"removeuser">>, User, Server, + _Query) -> + ejabberd_auth:remove_user(User, Server), ok; user_parse_query1(Action, User, Server, Query) -> - case ejabberd_hooks:run_fold(webadmin_user_parse_query, Server, [], [Action, User, Server, Query]) of - [] -> nothing; - Res -> Res + case ejabberd_hooks:run_fold(webadmin_user_parse_query, + Server, [], [Action, User, Server, Query]) + of + [] -> nothing; + Res -> Res end. - - list_last_activity(Host, Lang, Integral, Period) -> {MegaSecs, Secs, _MicroSecs} = now(), TimeStamp = MegaSecs * 1000000 + Secs, case Period of - "all" -> - TS = 0, - Days = infinity; - "year" -> - TS = TimeStamp - 366 * 86400, - Days = 366; - _ -> - TS = TimeStamp - 31 * 86400, - Days = 31 + <<"all">> -> TS = 0, Days = infinity; + <<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366; + _ -> TS = TimeStamp - 31 * 86400, Days = 31 end, - case catch mnesia:dirty_select( - last_activity, [{{last_activity, {'_', Host}, '$1', '_'}, - [{'>', '$1', TS}], - [{'trunc', {'/', - {'-', TimeStamp, '$1'}, - 86400}}]}]) of - {'EXIT', _Reason} -> - []; - Vals -> - Hist = histogram(Vals, Integral), - if - Hist == [] -> - [?CT("No Data")]; - true -> - Left = if - Days == infinity -> - 0; - true -> - Days - length(Hist) - end, - Tail = if - Integral -> - lists:duplicate(Left, lists:last(Hist)); - true -> - lists:duplicate(Left, 0) - end, - Max = lists:max(Hist), - [?XAE("ol", - [{"id", "lastactivity"}, {"start", "0"}], - [?XAE("li", - [{"style", - "width:" ++ integer_to_list( - trunc(90 * V / Max)) ++ "%;"}], - [{xmlcdata, pretty_string_int(V)}]) - || V <- Hist ++ Tail])] - end + case catch mnesia:dirty_select(last_activity, + [{{last_activity, {'_', Host}, '$1', '_'}, + [{'>', '$1', TS}], + [{trunc, + {'/', {'-', TimeStamp, '$1'}, 86400}}]}]) + of + {'EXIT', _Reason} -> []; + Vals -> + Hist = histogram(Vals, Integral), + if Hist == [] -> [?CT(<<"No Data">>)]; + true -> + Left = if Days == infinity -> 0; + true -> Days - length(Hist) + end, + Tail = if Integral -> + lists:duplicate(Left, lists:last(Hist)); + true -> lists:duplicate(Left, 0) + end, + Max = lists:max(Hist), + [?XAE(<<"ol">>, + [{<<"id">>, <<"lastactivity">>}, + {<<"start">>, <<"0">>}], + [?XAE(<<"li">>, + [{<<"style">>, + <<"width:", + (iolist_to_binary(integer_to_list(trunc(90 * V + / + Max))))/binary, + "%;">>}], + [{xmlcdata, pretty_string_int(V)}]) + || V <- Hist ++ Tail])] + end end. histogram(Values, Integral) -> histogram(lists:sort(Values), Integral, 0, 0, []). -histogram([H | T], Integral, Current, Count, Hist) when Current == H -> +histogram([H | T], Integral, Current, Count, Hist) + when Current == H -> histogram(T, Integral, Current, Count + 1, Hist); - -histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H -> - if - Integral -> - histogram(Values, Integral, Current + 1, Count, [Count | Hist]); - true -> - histogram(Values, Integral, Current + 1, 0, [Count | Hist]) +histogram([H | _] = Values, Integral, Current, Count, + Hist) + when Current < H -> + if Integral -> + histogram(Values, Integral, Current + 1, Count, + [Count | Hist]); + true -> + histogram(Values, Integral, Current + 1, 0, + [Count | Hist]) end; - histogram([], _Integral, _Current, Count, Hist) -> - if - Count > 0 -> - lists:reverse([Count | Hist]); - true -> - lists:reverse(Hist) + if Count > 0 -> lists:reverse([Count | Hist]); + true -> lists:reverse(Hist) end. %%%================================== @@ -1871,989 +1708,1162 @@ histogram([], _Integral, _Current, Count, Hist) -> get_nodes(Lang) -> RunningNodes = mnesia:system_info(running_db_nodes), - StoppedNodes = lists:usort(mnesia:system_info(db_nodes) ++ - mnesia:system_info(extra_db_nodes)) -- - RunningNodes, - FRN = if - RunningNodes == [] -> - ?CT("None"); - true -> - ?XE("ul", - lists:map( - fun(N) -> - S = atom_to_list(N), - ?LI([?AC("../node/" ++ S ++ "/", S)]) - end, lists:sort(RunningNodes))) + StoppedNodes = lists:usort(mnesia:system_info(db_nodes) + ++ mnesia:system_info(extra_db_nodes)) + -- RunningNodes, + FRN = if RunningNodes == [] -> ?CT(<<"None">>); + true -> + ?XE(<<"ul">>, + (lists:map(fun (N) -> + S = iolist_to_binary(atom_to_list(N)), + ?LI([?AC(<<"../node/", S/binary, "/">>, + S)]) + end, + lists:sort(RunningNodes)))) end, - FSN = if - StoppedNodes == [] -> - ?CT("None"); - true -> - ?XE("ul", - lists:map( - fun(N) -> - S = atom_to_list(N), - ?LI([?C(S)]) - end, lists:sort(StoppedNodes))) + FSN = if StoppedNodes == [] -> ?CT(<<"None">>); + true -> + ?XE(<<"ul">>, + (lists:map(fun (N) -> + S = iolist_to_binary(atom_to_list(N)), + ?LI([?C(S)]) + end, + lists:sort(StoppedNodes)))) end, - [?XCT("h1", "Nodes"), - ?XCT("h3", "Running Nodes"), - FRN, - ?XCT("h3", "Stopped Nodes"), - FSN]. + [?XCT(<<"h1">>, <<"Nodes">>), + ?XCT(<<"h3">>, <<"Running Nodes">>), FRN, + ?XCT(<<"h3">>, <<"Stopped Nodes">>), FSN]. search_running_node(SNode) -> - search_running_node(SNode, mnesia:system_info(running_db_nodes)). + search_running_node(SNode, + mnesia:system_info(running_db_nodes)). -search_running_node(_, []) -> - false; +search_running_node(_, []) -> false; search_running_node(SNode, [Node | Nodes]) -> - case atom_to_list(Node) of - SNode -> - Node; - _ -> - search_running_node(SNode, Nodes) + case iolist_to_binary(atom_to_list(Node)) of + SNode -> Node; + _ -> search_running_node(SNode, Nodes) end. get_node(global, Node, [], Query, Lang) -> Res = node_parse_query(Node, Query), Base = get_base_path(global, Node), MenuItems2 = make_menu_items(global, Node, Base, Lang), - [?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XE("ul", - [?LI([?ACT(Base ++ "db/", "Database")]), - ?LI([?ACT(Base ++ "backup/", "Backup")]), - ?LI([?ACT(Base ++ "ports/", "Listened Ports")]), - ?LI([?ACT(Base ++ "stats/", "Statistics")]), - ?LI([?ACT(Base ++ "update/", "Update")]) - ] ++ MenuItems2), - ?XAE("form", [{"action", ""}, {"method", "post"}], - [?INPUTT("submit", "restart", "Restart"), - ?C(" "), - ?INPUTT("submit", "stop", "Stop")]) - ]; - + [?XC(<<"h1">>, + list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))] + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; + nothing -> [] + end + ++ + [?XE(<<"ul">>, + ([?LI([?ACT(<<Base/binary, "db/">>, <<"Database">>)]), + ?LI([?ACT(<<Base/binary, "backup/">>, <<"Backup">>)]), + ?LI([?ACT(<<Base/binary, "ports/">>, + <<"Listened Ports">>)]), + ?LI([?ACT(<<Base/binary, "stats/">>, + <<"Statistics">>)]), + ?LI([?ACT(<<Base/binary, "update/">>, <<"Update">>)])] + ++ MenuItems2)), + ?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?INPUTT(<<"submit">>, <<"restart">>, <<"Restart">>), + ?C(<<" ">>), + ?INPUTT(<<"submit">>, <<"stop">>, <<"Stop">>)])]; get_node(Host, Node, [], _Query, Lang) -> Base = get_base_path(Host, Node), MenuItems2 = make_menu_items(Host, Node, Base, Lang), - [?XC("h1", ?T("Node ") ++ atom_to_list(Node)), - ?XE("ul", - [?LI([?ACT(Base ++ "modules/", "Modules")])] ++ MenuItems2) - ]; - -get_node(global, Node, ["db"], Query, Lang) -> + [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))), + ?XE(<<"ul">>, + ([?LI([?ACT(<<Base/binary, "modules/">>, + <<"Modules">>)])] + ++ MenuItems2))]; +get_node(global, Node, [<<"db">>], Query, Lang) -> case rpc:call(Node, mnesia, system_info, [tables]) of - {badrpc, _Reason} -> - [?XCT("h1", "RPC Call Error")]; - Tables -> - ResS = case node_db_parse_query(Node, Tables, Query) of - nothing -> []; - ok -> [?XREST("Submitted")] - end, - STables = lists:sort(Tables), - Rows = lists:map( - fun(Table) -> - STable = atom_to_list(Table), - TInfo = - case rpc:call(Node, - mnesia, - table_info, - [Table, all]) of - {badrpc, _} -> - []; - I -> - I - end, - {Type, Size, Memory} = - case {lists:keysearch(storage_type, 1, TInfo), - lists:keysearch(size, 1, TInfo), - lists:keysearch(memory, 1, TInfo)} of - {{value, {storage_type, T}}, - {value, {size, S}}, - {value, {memory, M}}} -> - {T, S, M}; - _ -> - {unknown, 0, 0} - end, - ?XE("tr", - [?XC("td", STable), - ?XE("td", [db_storage_select( - STable, Type, Lang)]), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(Size)), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(Memory)) - ]) - end, STables), - [?XC("h1", ?T("Database Tables at ") ++ atom_to_list(Node))] ++ - ResS ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?XAE("table", [], - [?XE("thead", - [?XE("tr", - [?XCT("td", "Name"), - ?XCT("td", "Storage Type"), - ?XCT("td", "Elements"), %% Elements/items/records inserted in the table - ?XCT("td", "Memory") %% Words or Bytes allocated to the table on this node - ])]), - ?XE("tbody", - Rows ++ - [?XE("tr", - [?XAE("td", [{"colspan", "4"}, - {"class", "alignright"}], - [?INPUTT("submit", "submit", - "Submit")]) - ])] - )])])] + {badrpc, _Reason} -> + [?XCT(<<"h1">>, <<"RPC Call Error">>)]; + Tables -> + ResS = case node_db_parse_query(Node, Tables, Query) of + nothing -> []; + ok -> [?XREST(<<"Submitted">>)] + end, + STables = lists:sort(Tables), + Rows = lists:map(fun (Table) -> + STable = + iolist_to_binary(atom_to_list(Table)), + TInfo = case rpc:call(Node, mnesia, + table_info, + [Table, all]) + of + {badrpc, _} -> []; + I -> I + end, + {Type, Size, Memory} = case + {lists:keysearch(storage_type, + 1, + TInfo), + lists:keysearch(size, + 1, + TInfo), + lists:keysearch(memory, + 1, + TInfo)} + of + {{value, + {storage_type, + T}}, + {value, {size, S}}, + {value, + {memory, M}}} -> + {T, S, M}; + _ -> {unknown, 0, 0} + end, + ?XE(<<"tr">>, + [?XC(<<"td">>, STable), + ?XE(<<"td">>, + [db_storage_select(STable, Type, + Lang)]), + ?XAC(<<"td">>, + [{<<"class">>, <<"alignright">>}], + (pretty_string_int(Size))), + ?XAC(<<"td">>, + [{<<"class">>, <<"alignright">>}], + (pretty_string_int(Memory)))]) + end, + STables), + [?XC(<<"h1">>, + list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>), + [Node])) + )] + ++ + ResS ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XAE(<<"table">>, [], + [?XE(<<"thead">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Name">>), + ?XCT(<<"td">>, <<"Storage Type">>), + ?XCT(<<"td">>, <<"Elements">>), + ?XCT(<<"td">>, <<"Memory">>)])]), + ?XE(<<"tbody">>, + (Rows ++ + [?XE(<<"tr">>, + [?XAE(<<"td">>, + [{<<"colspan">>, <<"4">>}, + {<<"class">>, <<"alignright">>}], + [?INPUTT(<<"submit">>, + <<"submit">>, + <<"Submit">>)])])]))])])] end; - -get_node(global, Node, ["backup"], Query, Lang) -> +get_node(global, Node, [<<"backup">>], Query, Lang) -> HomeDirRaw = case {os:getenv("HOME"), os:type()} of - {EnvHome, _} when is_list(EnvHome) -> EnvHome; - {false, {win32, _Osname}} -> "C:/"; - {false, _} -> "/tmp/" - end, + {EnvHome, _} when is_list(EnvHome) -> list_to_binary(EnvHome); + {false, {win32, _Osname}} -> <<"C:/">>; + {false, _} -> <<"/tmp/">> + end, HomeDir = filename:nativename(HomeDirRaw), ResS = case node_backup_parse_query(Node, Query) of - nothing -> []; - ok -> [?XREST("Submitted")]; - {error, Error} -> [?XRES(?T("Error") ++": " ++ io_lib:format("~p", [Error]))] + nothing -> []; + ok -> [?XREST(<<"Submitted">>)]; + {error, Error} -> + [?XRES(<<(?T(<<"Error">>))/binary, ": ", + (list_to_binary(io_lib:format("~p", [Error])))/binary>>)] end, - ?H1GL(?T("Backup of ") ++ atom_to_list(Node), "list-eja-commands", "List of ejabberd Commands") ++ - ResS ++ - [?XCT("p", "Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately."), - ?XAE("form", [{"action", ""}, {"method", "post"}], - [?XAE("table", [], - [?XE("tbody", - [?XE("tr", - [?XCT("td", "Store binary backup:"), - ?XE("td", [?INPUT("text", "storepath", - filename:join(HomeDir, "ejabberd.backup"))]), - ?XE("td", [?INPUTT("submit", "store", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Restore binary backup immediately:"), - ?XE("td", [?INPUT("text", "restorepath", - filename:join(HomeDir, "ejabberd.backup"))]), - ?XE("td", [?INPUTT("submit", "restore", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", - "Restore binary backup after next ejabberd restart (requires less memory):"), - ?XE("td", [?INPUT("text", "fallbackpath", - filename:join(HomeDir, "ejabberd.backup"))]), - ?XE("td", [?INPUTT("submit", "fallback", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Store plain text backup:"), - ?XE("td", [?INPUT("text", "dumppath", - filename:join(HomeDir, "ejabberd.dump"))]), - ?XE("td", [?INPUTT("submit", "dump", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Restore plain text backup immediately:"), - ?XE("td", [?INPUT("text", "loadpath", - filename:join(HomeDir, "ejabberd.dump"))]), - ?XE("td", [?INPUTT("submit", "load", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Import users data from a PIEFXIS file (XEP-0227):"), - ?XE("td", [?INPUT("text", "import_piefxis_filepath", - filename:join(HomeDir, "users.xml"))]), - ?XE("td", [?INPUTT("submit", "import_piefxis_file", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Export data of all users in the server to PIEFXIS files (XEP-0227):"), - ?XE("td", [?INPUT("text", "export_piefxis_dirpath", - HomeDir)]), - ?XE("td", [?INPUTT("submit", "export_piefxis_dir", - "OK")]) - ]), - ?XE("tr", - [?XE("td", [?CT("Export data of users in a host to PIEFXIS files (XEP-0227):"), - ?C(" "), - ?INPUT("text", "export_piefxis_host_dirhost", ?MYNAME)]), - ?XE("td", [?INPUT("text", "export_piefxis_host_dirpath", HomeDir)]), - ?XE("td", [?INPUTT("submit", "export_piefxis_host_dir", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Import user data from jabberd14 spool file:"), - ?XE("td", [?INPUT("text", "import_filepath", - filename:join(HomeDir, "user1.xml"))]), - ?XE("td", [?INPUTT("submit", "import_file", - "OK")]) - ]), - ?XE("tr", - [?XCT("td", "Import users data from jabberd14 spool directory:"), - ?XE("td", [?INPUT("text", "import_dirpath", - "/var/spool/jabber/")]), - ?XE("td", [?INPUTT("submit", "import_dir", - "OK")]) - ]) - ]) - ])])]; - -get_node(global, Node, ["ports"], Query, Lang) -> - Ports = rpc:call(Node, ejabberd_config, get_local_option, [listen]), - Res = case catch node_ports_parse_query(Node, Ports, Query) of - submitted -> - ok; - {'EXIT', _Reason} -> - error; - {is_added, ok} -> - ok; - {is_added, {error, Reason}} -> - {error, io_lib:format("~p", [Reason])}; - _ -> - nothing + (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])), + <<"list-eja-commands">>, + <<"List of ejabberd Commands">>)) + ++ + ResS ++ + [?XCT(<<"p">>, + <<"Please note that these options will " + "only backup the builtin Mnesia database. " + "If you are using the ODBC module, you " + "also need to backup your SQL database " + "separately.">>), + ?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XAE(<<"table">>, [], + [?XE(<<"tbody">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Store binary backup:">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"storepath">>, + (filename:join(HomeDir, + "ejabberd.backup")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"store">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Restore binary backup immediately:">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"restorepath">>, + (filename:join(HomeDir, + "ejabberd.backup")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"restore">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Restore binary backup after next ejabberd " + "restart (requires less memory):">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"fallbackpath">>, + (filename:join(HomeDir, + "ejabberd.backup")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"fallback">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Store plain text backup:">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"dumppath">>, + (filename:join(HomeDir, + "ejabberd.dump")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"dump">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Restore plain text backup immediately:">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"loadpath">>, + (filename:join(HomeDir, + "ejabberd.dump")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"load">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Import users data from a PIEFXIS file " + "(XEP-0227):">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, + <<"import_piefxis_filepath">>, + (filename:join(HomeDir, + "users.xml")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"import_piefxis_file">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Export data of all users in the server " + "to PIEFXIS files (XEP-0227):">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, + <<"export_piefxis_dirpath">>, + HomeDir)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"export_piefxis_dir">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XE(<<"td">>, + [?CT(<<"Export data of users in a host to PIEFXIS " + "files (XEP-0227):">>), + ?C(<<" ">>), + ?INPUT(<<"text">>, + <<"export_piefxis_host_dirhost">>, + (?MYNAME))]), + ?XE(<<"td">>, + [?INPUT(<<"text">>, + <<"export_piefxis_host_dirpath">>, + HomeDir)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"export_piefxis_host_dir">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XE(<<"td">>, + [?CT(<<"Export all tables as SQL queries " + "to a file:">>), + ?C(<<" ">>), + ?INPUT(<<"text">>, + <<"export_sql_filehost">>, + (?MYNAME))]), + ?XE(<<"td">>, + [?INPUT(<<"text">>, + <<"export_sql_filepath">>, + (filename:join(HomeDir, + "db.sql")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"export_sql_file">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Import user data from jabberd14 spool " + "file:">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"import_filepath">>, + (filename:join(HomeDir, + "user1.xml")))]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"import_file">>, + <<"OK">>)])]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, + <<"Import users data from jabberd14 spool " + "directory:">>), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"import_dirpath">>, + <<"/var/spool/jabber/">>)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, <<"import_dir">>, + <<"OK">>)])])])])])]; +get_node(global, Node, [<<"ports">>], Query, Lang) -> + Ports = rpc:call(Node, ejabberd_config, + get_local_option, [listen, + {ejabberd_listener, validate_cfg}, + []]), + Res = case catch node_ports_parse_query(Node, Ports, + Query) + of + submitted -> ok; + {'EXIT', _Reason} -> error; + {is_added, ok} -> ok; + {is_added, {error, Reason}} -> + {error, iolist_to_binary(io_lib:format("~p", [Reason]))}; + _ -> nothing end, - %% TODO: This sorting does not work when [{{Port, IP}, Module, Opts}] - NewPorts = lists:sort( - rpc:call(Node, ejabberd_config, get_local_option, [listen])), - H1String = ?T("Listened Ports at ") ++ atom_to_list(Node), - ?H1GL(H1String, "listened", "Listening Ports") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - {error, ReasonT} -> [?XRES(?T("Error") ++ ": " ++ ReasonT)]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [node_ports_to_xhtml(NewPorts, Lang)]) - ]; - -get_node(Host, Node, ["modules"], Query, Lang) when is_list(Host) -> - Modules = rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host]), - Res = case catch node_modules_parse_query(Host, Node, Modules, Query) of - submitted -> - ok; - {'EXIT', Reason} -> - ?INFO_MSG("~p~n", [Reason]), - error; - _ -> - nothing + NewPorts = lists:sort(rpc:call(Node, ejabberd_config, + get_local_option, + [listen, + {ejabberd_listener, validate_cfg}, + []])), + H1String = <<(?T(<<"Listened Ports at ">>))/binary, + (iolist_to_binary(atom_to_list(Node)))/binary>>, + (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>)) + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; + {error, ReasonT} -> + [?XRES(<<(?T(<<"Error">>))/binary, ": ", + ReasonT/binary>>)]; + nothing -> [] + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [node_ports_to_xhtml(NewPorts, Lang)])]; +get_node(Host, Node, [<<"modules">>], Query, Lang) + when is_binary(Host) -> + Modules = rpc:call(Node, gen_mod, + loaded_modules_with_opts, [Host]), + Res = case catch node_modules_parse_query(Host, Node, + Modules, Query) + of + submitted -> ok; + {'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error; + _ -> nothing end, - NewModules = lists:sort( - rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host])), - H1String = ?T("Modules at ") ++ atom_to_list(Node), - ?H1GL(H1String, "modoverview", "Modules Overview") ++ - case Res of - ok -> [?XREST("Submitted")]; - error -> [?XREST("Bad format")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [node_modules_to_xhtml(NewModules, Lang)]) - ]; - -get_node(global, Node, ["stats"], _Query, Lang) -> - UpTime = rpc:call(Node, erlang, statistics, [wall_clock]), - UpTimeS = io_lib:format("~.3f", [element(1, UpTime)/1000]), + NewModules = lists:sort(rpc:call(Node, gen_mod, + loaded_modules_with_opts, [Host])), + H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])), + (?H1GL(H1String, <<"modoverview">>, + <<"Modules Overview">>)) + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + error -> [?XREST(<<"Bad format">>)]; + nothing -> [] + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [node_modules_to_xhtml(NewModules, Lang)])]; +get_node(global, Node, [<<"stats">>], _Query, Lang) -> + UpTime = rpc:call(Node, erlang, statistics, + [wall_clock]), + UpTimeS = list_to_binary(io_lib:format("~.3f", + [element(1, UpTime) / 1000])), CPUTime = rpc:call(Node, erlang, statistics, [runtime]), - CPUTimeS = io_lib:format("~.3f", [element(1, CPUTime)/1000]), + CPUTimeS = list_to_binary(io_lib:format("~.3f", + [element(1, CPUTime) / 1000])), OnlineUsers = mnesia:table_info(session, size), - TransactionsCommitted = - rpc:call(Node, mnesia, system_info, [transaction_commits]), - TransactionsAborted = - rpc:call(Node, mnesia, system_info, [transaction_failures]), - TransactionsRestarted = - rpc:call(Node, mnesia, system_info, [transaction_restarts]), - TransactionsLogged = - rpc:call(Node, mnesia, system_info, [transaction_log_writes]), - - [?XC("h1", io_lib:format(?T("Statistics of ~p"), [Node])), - ?XAE("table", [], - [?XE("tbody", - [?XE("tr", [?XCT("td", "Uptime:"), - ?XAC("td", [{"class", "alignright"}], - UpTimeS)]), - ?XE("tr", [?XCT("td", "CPU Time:"), - ?XAC("td", [{"class", "alignright"}], - CPUTimeS)]), - ?XE("tr", [?XCT("td", "Online Users:"), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(OnlineUsers))]), - ?XE("tr", [?XCT("td", "Transactions Committed:"), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(TransactionsCommitted))]), - ?XE("tr", [?XCT("td", "Transactions Aborted:"), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(TransactionsAborted))]), - ?XE("tr", [?XCT("td", "Transactions Restarted:"), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(TransactionsRestarted))]), - ?XE("tr", [?XCT("td", "Transactions Logged:"), - ?XAC("td", [{"class", "alignright"}], - pretty_string_int(TransactionsLogged))]) - ]) - ])]; - -get_node(global, Node, ["update"], Query, Lang) -> + TransactionsCommitted = rpc:call(Node, mnesia, + system_info, [transaction_commits]), + TransactionsAborted = rpc:call(Node, mnesia, + system_info, [transaction_failures]), + TransactionsRestarted = rpc:call(Node, mnesia, + system_info, [transaction_restarts]), + TransactionsLogged = rpc:call(Node, mnesia, system_info, + [transaction_log_writes]), + [?XC(<<"h1">>, + list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))), + ?XAE(<<"table">>, [], + [?XE(<<"tbody">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Uptime:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + UpTimeS)]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"CPU Time:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + CPUTimeS)]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Online Users:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + (pretty_string_int(OnlineUsers)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Transactions Committed:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + (pretty_string_int(TransactionsCommitted)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Transactions Aborted:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + (pretty_string_int(TransactionsAborted)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Transactions Restarted:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + (pretty_string_int(TransactionsRestarted)))]), + ?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Transactions Logged:">>), + ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], + (pretty_string_int(TransactionsLogged)))])])])]; +get_node(global, Node, [<<"update">>], Query, Lang) -> rpc:call(Node, code, purge, [ejabberd_update]), Res = node_update_parse_query(Node, Query), rpc:call(Node, code, load_file, [ejabberd_update]), - {ok, _Dir, UpdatedBeams, Script, LowLevelScript, Check} = + {ok, _Dir, UpdatedBeams, Script, LowLevelScript, + Check} = rpc:call(Node, ejabberd_update, update_info, []), - Mods = - case UpdatedBeams of - [] -> - ?CT("None"); - _ -> - BeamsLis = - lists:map( - fun(Beam) -> - BeamString = atom_to_list(Beam), - ?LI([ - ?INPUT("checkbox", "selected", BeamString), - %%?XA("input", [{"checked", ""}, %% Selected by default - %% {"type", "checkbox"}, - %% {"name", "selected"}, - %% {"value", BeamString}]), - ?C(BeamString)]) - end, - UpdatedBeams), - SelectButtons = - [?BR, - ?INPUTATTRS("button", "selectall", "Select All", - [{"onClick", "selectAll()"}]), - ?C(" "), - ?INPUTATTRS("button", "unselectall", "Unselect All", - [{"onClick", "unSelectAll()"}])], - %%?XE("ul", BeamsLis) - ?XAE("ul", [{"class", "nolistyle"}], BeamsLis ++ SelectButtons) - end, - FmtScript = ?XC("pre", io_lib:format("~p", [Script])), - FmtLowLevelScript = ?XC("pre", io_lib:format("~p", [LowLevelScript])), - [?XC("h1", ?T("Update ") ++ atom_to_list(Node))] ++ - case Res of - ok -> [?XREST("Submitted")]; - {error, ErrorText} -> [?XREST("Error: " ++ ErrorText)]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [ - ?XCT("h2", "Update plan"), - ?XCT("h3", "Modified modules"), Mods, - ?XCT("h3", "Update script"), FmtScript, - ?XCT("h3", "Low level update script"), FmtLowLevelScript, - ?XCT("h3", "Script check"), ?XC("pre", atom_to_list(Check)), + Mods = case UpdatedBeams of + [] -> ?CT(<<"None">>); + _ -> + BeamsLis = lists:map(fun (Beam) -> + BeamString = + iolist_to_binary(atom_to_list(Beam)), + ?LI([?INPUT(<<"checkbox">>, + <<"selected">>, + BeamString), + ?C(BeamString)]) + end, + UpdatedBeams), + SelectButtons = [?BR, + ?INPUTATTRS(<<"button">>, <<"selectall">>, + <<"Select All">>, + [{<<"onClick">>, + <<"selectAll()">>}]), + ?C(<<" ">>), + ?INPUTATTRS(<<"button">>, <<"unselectall">>, + <<"Unselect All">>, + [{<<"onClick">>, + <<"unSelectAll()">>}])], + ?XAE(<<"ul">>, [{<<"class">>, <<"nolistyle">>}], + (BeamsLis ++ SelectButtons)) + end, + FmtScript = (?XC(<<"pre">>, + list_to_binary(io_lib:format("~p", [Script])))), + FmtLowLevelScript = (?XC(<<"pre">>, + list_to_binary(io_lib:format("~p", [LowLevelScript])))), + [?XC(<<"h1">>, + list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))] + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + {error, ErrorText} -> + [?XREST(<<"Error: ", ErrorText/binary>>)]; + nothing -> [] + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XCT(<<"h2">>, <<"Update plan">>), + ?XCT(<<"h3">>, <<"Modified modules">>), Mods, + ?XCT(<<"h3">>, <<"Update script">>), FmtScript, + ?XCT(<<"h3">>, <<"Low level update script">>), + FmtLowLevelScript, ?XCT(<<"h3">>, <<"Script check">>), + ?XC(<<"pre">>, (iolist_to_binary(Check))), ?BR, - ?INPUTT("submit", "update", "Update") - ]) - ]; - + ?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])]; get_node(Host, Node, NPath, Query, Lang) -> {Hook, Opts} = case Host of - global -> {webadmin_page_node, [Node, NPath, Query, Lang]}; - Host -> {webadmin_page_hostnode, [Host, Node, NPath, Query, Lang]} + global -> + {webadmin_page_node, [Node, NPath, Query, Lang]}; + Host -> + {webadmin_page_hostnode, + [Host, Node, NPath, Query, Lang]} end, case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of - [] -> [?XC("h1", "Not Found")]; - Res -> Res + [] -> [?XC(<<"h1">>, <<"Not Found">>)]; + Res -> Res end. %%%================================== %%%% node parse node_parse_query(Node, Query) -> - case lists:keysearch("restart", 1, Query) of - {value, _} -> - case rpc:call(Node, init, restart, []) of - {badrpc, _Reason} -> - error; - _ -> - ok - end; - _ -> - case lists:keysearch("stop", 1, Query) of - {value, _} -> - case rpc:call(Node, init, stop, []) of - {badrpc, _Reason} -> - error; - _ -> - ok - end; - _ -> - nothing - end + case lists:keysearch(<<"restart">>, 1, Query) of + {value, _} -> + case rpc:call(Node, init, restart, []) of + {badrpc, _Reason} -> error; + _ -> ok + end; + _ -> + case lists:keysearch(<<"stop">>, 1, Query) of + {value, _} -> + case rpc:call(Node, init, stop, []) of + {badrpc, _Reason} -> error; + _ -> ok + end; + _ -> nothing + end end. - db_storage_select(ID, Opt, Lang) -> - ?XAE("select", [{"name", "table" ++ ID}], - lists:map( - fun({O, Desc}) -> - Sel = if - O == Opt -> [{"selected", "selected"}]; - true -> [] - end, - ?XACT("option", - Sel ++ [{"value", atom_to_list(O)}], - Desc) - end, [{ram_copies, "RAM copy"}, - {disc_copies, "RAM and disc copy"}, - {disc_only_copies, "Disc only copy"}, - {unknown, "Remote copy"}, - {delete_content, "Delete content"}, - {delete_table, "Delete table"}])). - -node_db_parse_query(_Node, _Tables, [{nokey,[]}]) -> + ?XAE(<<"select">>, + [{<<"name">>, <<"table", ID/binary>>}], + (lists:map(fun ({O, Desc}) -> + Sel = if O == Opt -> + [{<<"selected">>, <<"selected">>}]; + true -> [] + end, + ?XACT(<<"option">>, + (Sel ++ + [{<<"value">>, + iolist_to_binary(atom_to_list(O))}]), + Desc) + end, + [{ram_copies, <<"RAM copy">>}, + {disc_copies, <<"RAM and disc copy">>}, + {disc_only_copies, <<"Disc only copy">>}, + {unknown, <<"Remote copy">>}, + {delete_content, <<"Delete content">>}, + {delete_table, <<"Delete table">>}]))). + +node_db_parse_query(_Node, _Tables, [{nokey, <<>>}]) -> nothing; node_db_parse_query(Node, Tables, Query) -> - lists:foreach( - fun(Table) -> - STable = atom_to_list(Table), - case lists:keysearch("table" ++ STable, 1, Query) of - {value, {_, SType}} -> - Type = case SType of - "unknown" -> unknown; - "ram_copies" -> ram_copies; - "disc_copies" -> disc_copies; - "disc_only_copies" -> disc_only_copies; - "delete_content" -> delete_content; - "delete_table" -> delete_table; - _ -> false - end, - if - Type == false -> - ok; - Type == delete_content -> - mnesia:clear_table(Table); - Type == delete_table -> - mnesia:delete_table(Table); - Type == unknown -> - mnesia:del_table_copy(Table, Node); - true -> - case mnesia:add_table_copy(Table, Node, Type) of - {aborted, _} -> - mnesia:change_table_copy_type( - Table, Node, Type); - _ -> - ok - end - end; - _ -> - ok - end - end, Tables), + lists:foreach(fun (Table) -> + STable = iolist_to_binary(atom_to_list(Table)), + case lists:keysearch(<<"table", STable/binary>>, 1, + Query) + of + {value, {_, SType}} -> + Type = case SType of + <<"unknown">> -> unknown; + <<"ram_copies">> -> ram_copies; + <<"disc_copies">> -> disc_copies; + <<"disc_only_copies">> -> + disc_only_copies; + <<"delete_content">> -> delete_content; + <<"delete_table">> -> delete_table; + _ -> false + end, + if Type == false -> ok; + Type == delete_content -> + mnesia:clear_table(Table); + Type == delete_table -> + mnesia:delete_table(Table); + Type == unknown -> + mnesia:del_table_copy(Table, Node); + true -> + case mnesia:add_table_copy(Table, Node, + Type) + of + {aborted, _} -> + mnesia:change_table_copy_type(Table, + Node, + Type); + _ -> ok + end + end; + _ -> ok + end + end, + Tables), ok. -node_backup_parse_query(_Node, [{nokey,[]}]) -> +node_backup_parse_query(_Node, [{nokey, <<>>}]) -> nothing; node_backup_parse_query(Node, Query) -> - lists:foldl( - fun(Action, nothing) -> - case lists:keysearch(Action, 1, Query) of - {value, _} -> - case lists:keysearch(Action ++ "path", 1, Query) of - {value, {_, Path}} -> - Res = - case Action of - "store" -> - rpc:call(Node, mnesia, - backup, [Path]); - "restore" -> - rpc:call(Node, ejabberd_admin, - restore, [Path]); - "fallback" -> - rpc:call(Node, mnesia, - install_fallback, [Path]); - "dump" -> - rpc:call(Node, ejabberd_admin, - dump_to_textfile, [Path]); - "load" -> - rpc:call(Node, mnesia, - load_textfile, [Path]); - "import_piefxis_file" -> - rpc:call(Node, ejabberd_piefxis, - import_file, [Path]); - "export_piefxis_dir" -> - rpc:call(Node, ejabberd_piefxis, - export_server, [Path]); - "export_piefxis_host_dir" -> - {value, {_, Host}} = lists:keysearch(Action ++ "host", 1, Query), - rpc:call(Node, ejabberd_piefxis, - export_host, [Path, Host]); - "import_file" -> - rpc:call(Node, ejabberd_admin, - import_file, [Path]); - "import_dir" -> - rpc:call(Node, ejabberd_admin, - import_dir, [Path]) - end, - case Res of - {error, Reason} -> - {error, Reason}; - {badrpc, Reason} -> - {badrpc, Reason}; - _ -> - ok + lists:foldl(fun (Action, nothing) -> + case lists:keysearch(Action, 1, Query) of + {value, _} -> + case lists:keysearch(<<Action/binary, "path">>, 1, + Query) + of + {value, {_, Path}} -> + Res = case Action of + <<"store">> -> + rpc:call(Node, mnesia, backup, + [binary_to_list(Path)]); + <<"restore">> -> + rpc:call(Node, ejabberd_admin, + restore, [Path]); + <<"fallback">> -> + rpc:call(Node, mnesia, + install_fallback, + [binary_to_list(Path)]); + <<"dump">> -> + rpc:call(Node, ejabberd_admin, + dump_to_textfile, + [Path]); + <<"load">> -> + rpc:call(Node, mnesia, + load_textfile, + [binary_to_list(Path)]); + <<"import_piefxis_file">> -> + rpc:call(Node, ejabberd_piefxis, + import_file, [Path]); + <<"export_piefxis_dir">> -> + rpc:call(Node, ejabberd_piefxis, + export_server, [Path]); + <<"export_piefxis_host_dir">> -> + {value, {_, Host}} = + lists:keysearch(<<Action/binary, + "host">>, + 1, Query), + rpc:call(Node, ejabberd_piefxis, + export_host, + [Path, Host]); + <<"export_sql_file">> -> + {value, {_, Host}} = + lists:keysearch(<<Action/binary, + "host">>, + 1, Query), + rpc:call(Node, ejd2odbc, + export, [Host, Path]); + <<"import_file">> -> + rpc:call(Node, ejabberd_admin, + import_file, [Path]); + <<"import_dir">> -> + rpc:call(Node, ejabberd_admin, + import_dir, [Path]) + end, + case Res of + {error, Reason} -> {error, Reason}; + {badrpc, Reason} -> {badrpc, Reason}; + _ -> ok + end; + OtherError -> {error, OtherError} end; - OtherError -> - {error, OtherError} - end; - _ -> - nothing - end; - (_Action, Res) -> - Res - end, nothing, ["store", "restore", "fallback", "dump", "load", "import_file", "import_dir", - "import_piefxis_file", "export_piefxis_dir", "export_piefxis_host_dir"]). + _ -> nothing + end; + (_Action, Res) -> Res + end, + nothing, + [<<"store">>, <<"restore">>, <<"fallback">>, <<"dump">>, + <<"load">>, <<"import_file">>, <<"import_dir">>, + <<"import_piefxis_file">>, <<"export_piefxis_dir">>, + <<"export_piefxis_host_dir">>, <<"export_sql_file">>]). node_ports_to_xhtml(Ports, Lang) -> - ?XAE("table", [{"class", "withtextareas"}], - [?XE("thead", - [?XE("tr", - [?XCT("td", "Port"), - ?XCT("td", "IP"), - ?XCT("td", "Protocol"), - ?XCT("td", "Module"), - ?XCT("td", "Options") - ])]), - ?XE("tbody", - lists:map( - fun({PortIP, Module, Opts} = _E) -> - {_Port, SPort, _TIP, SIP, SSPort, NetProt, OptsClean} = - get_port_data(PortIP, Opts), - SModule = atom_to_list(Module), - {NumLines, SOptsClean} = term_to_paragraph(OptsClean, 40), - %%ID = term_to_id(E), - ?XE("tr", - [?XAE("td", [{"size", "6"}], [?C(SPort)]), - ?XAE("td", [{"size", "15"}], [?C(SIP)]), - ?XAE("td", [{"size", "4"}], [?C(atom_to_list(NetProt))]), - ?XE("td", [?INPUTS("text", "module" ++ SSPort, - SModule, "15")]), - ?XE("td", [?TEXTAREA("opts" ++ SSPort, integer_to_list(NumLines), "35", SOptsClean)]), - ?XE("td", [?INPUTT("submit", "add" ++ SSPort, - "Update")]), - ?XE("td", [?INPUTT("submit", "delete" ++ SSPort, - "Delete")]) - ] - ) - end, Ports) ++ - [?XE("tr", - [?XE("td", [?INPUTS("text", "portnew", "", "6")]), - ?XE("td", [?INPUTS("text", "ipnew", "0.0.0.0", "15")]), - ?XE("td", [make_netprot_html("tcp")]), - ?XE("td", [?INPUTS("text", "modulenew", "", "15")]), - ?XE("td", [?TEXTAREA("optsnew", "2", "35", "[]")]), - ?XAE("td", [{"colspan", "2"}], - [?INPUTT("submit", "addnew", "Add New")]) - ] - )] - )]). + ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}], + [?XE(<<"thead">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Port">>), ?XCT(<<"td">>, <<"IP">>), + ?XCT(<<"td">>, <<"Protocol">>), + ?XCT(<<"td">>, <<"Module">>), + ?XCT(<<"td">>, <<"Options">>)])]), + ?XE(<<"tbody">>, + (lists:map(fun ({PortIP, Module, Opts} = _E) -> + {_Port, SPort, _TIP, SIP, SSPort, NetProt, + OptsClean} = + get_port_data(PortIP, Opts), + SModule = + iolist_to_binary(atom_to_list(Module)), + {NumLines, SOptsClean} = + term_to_paragraph(OptsClean, 40), + ?XE(<<"tr">>, + [?XAE(<<"td">>, [{<<"size">>, <<"6">>}], + [?C(SPort)]), + ?XAE(<<"td">>, [{<<"size">>, <<"15">>}], + [?C(SIP)]), + ?XAE(<<"td">>, [{<<"size">>, <<"4">>}], + [?C((iolist_to_binary(atom_to_list(NetProt))))]), + ?XE(<<"td">>, + [?INPUTS(<<"text">>, + <<"module", SSPort/binary>>, + SModule, <<"15">>)]), + ?XE(<<"td">>, + [?TEXTAREA(<<"opts", SSPort/binary>>, + (iolist_to_binary(integer_to_list(NumLines))), + <<"35">>, SOptsClean)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"add", SSPort/binary>>, + <<"Update">>)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"delete", SSPort/binary>>, + <<"Delete">>)])]) + end, + Ports) + ++ + [?XE(<<"tr">>, + [?XE(<<"td">>, + [?INPUTS(<<"text">>, <<"portnew">>, <<"">>, + <<"6">>)]), + ?XE(<<"td">>, + [?INPUTS(<<"text">>, <<"ipnew">>, <<"0.0.0.0">>, + <<"15">>)]), + ?XE(<<"td">>, [make_netprot_html(<<"tcp">>)]), + ?XE(<<"td">>, + [?INPUTS(<<"text">>, <<"modulenew">>, <<"">>, + <<"15">>)]), + ?XE(<<"td">>, + [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>, + <<"[]">>)]), + ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], + [?INPUTT(<<"submit">>, <<"addnew">>, + <<"Add New">>)])])]))]). make_netprot_html(NetProt) -> - ?XAE("select", [{"name", "netprotnew"}], - lists:map( - fun(O) -> - Sel = if - O == NetProt -> [{"selected", "selected"}]; - true -> [] - end, - ?XAC("option", - Sel ++ [{"value", O}], - O) - end, ["tcp", "udp"])). + ?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}], + (lists:map(fun (O) -> + Sel = if O == NetProt -> + [{<<"selected">>, <<"selected">>}]; + true -> [] + end, + ?XAC(<<"option">>, (Sel ++ [{<<"value">>, O}]), O) + end, + [<<"tcp">>, <<"udp">>]))). get_port_data(PortIP, Opts) -> - {Port, IPT, IPS, _IPV, NetProt, OptsClean} = ejabberd_listener:parse_listener_portip(PortIP, Opts), - SPort = io_lib:format("~p", [Port]), - - SSPort = lists:flatten( - lists:map( - fun(N) -> io_lib:format("~.16b", [N]) end, - binary_to_list(crypto:md5(SPort++IPS++atom_to_list(NetProt))))), + {Port, IPT, IPS, _IPV, NetProt, OptsClean} = + ejabberd_listener:parse_listener_portip(PortIP, Opts), + SPort = jlib:integer_to_binary(Port), + SSPort = list_to_binary( + lists:map(fun (N) -> + io_lib:format("~.16b", [N]) + end, + binary_to_list( + crypto:md5( + [SPort, IPS, atom_to_list(NetProt)])))), {Port, SPort, IPT, IPS, SSPort, NetProt, OptsClean}. - node_ports_parse_query(Node, Ports, Query) -> - lists:foreach( - fun({PortIpNetp, Module1, Opts1}) -> - {Port, _SPort, TIP, _SIP, SSPort, NetProt, _OptsClean} = - get_port_data(PortIpNetp, Opts1), - case lists:keysearch("add" ++ SSPort, 1, Query) of - {value, _} -> - PortIpNetp2 = {Port, TIP, NetProt}, - {{value, {_, SModule}}, {value, {_, SOpts}}} = - {lists:keysearch("module" ++ SSPort, 1, Query), - lists:keysearch("opts" ++ SSPort, 1, Query)}, - Module = list_to_atom(SModule), - {ok, Tokens, _} = erl_scan:string(SOpts ++ "."), - {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, ejabberd_listener, delete_listener, - [PortIpNetp2, Module1]), - R=rpc:call(Node, ejabberd_listener, add_listener, - [PortIpNetp2, Module, Opts]), - throw({is_added, R}); - _ -> - case lists:keysearch("delete" ++ SSPort, 1, Query) of - {value, _} -> - rpc:call(Node, ejabberd_listener, delete_listener, - [PortIpNetp, Module1]), - throw(submitted); - _ -> - ok - end - end - end, Ports), - case lists:keysearch("addnew", 1, Query) of - {value, _} -> - {{value, {_, SPort}}, - {value, {_, STIP}}, %% It is a string that may represent a tuple - {value, {_, SNetProt}}, - {value, {_, SModule}}, - {value, {_, SOpts}}} = - {lists:keysearch("portnew", 1, Query), - lists:keysearch("ipnew", 1, Query), - lists:keysearch("netprotnew", 1, Query), - lists:keysearch("modulenew", 1, Query), - lists:keysearch("optsnew", 1, Query)}, - {ok, Toks, _} = erl_scan:string(SPort ++ "."), - {ok, Port2} = erl_parse:parse_term(Toks), - {ok, ToksIP, _} = erl_scan:string(STIP ++ "."), - STIP2 = case erl_parse:parse_term(ToksIP) of - {ok, IPTParsed} -> IPTParsed; - {error, _} -> STIP - end, - Module = list_to_atom(SModule), - NetProt2 = list_to_atom(SNetProt), - {ok, Tokens, _} = erl_scan:string(SOpts ++ "."), - {ok, Opts} = erl_parse:parse_term(Tokens), - {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2, OptsClean} = - get_port_data({Port2, STIP2, NetProt2}, Opts), - R=rpc:call(Node, ejabberd_listener, add_listener, + lists:foreach(fun ({PortIpNetp, Module1, Opts1}) -> + {Port, _SPort, TIP, _SIP, SSPort, NetProt, + _OptsClean} = + get_port_data(PortIpNetp, Opts1), + case lists:keysearch(<<"add", SSPort/binary>>, 1, + Query) + of + {value, _} -> + PortIpNetp2 = {Port, TIP, NetProt}, + {{value, {_, SModule}}, {value, {_, SOpts}}} = + {lists:keysearch(<<"module", + SSPort/binary>>, + 1, Query), + lists:keysearch(<<"opts", SSPort/binary>>, + 1, Query)}, + Module = jlib:binary_to_atom(SModule), + {ok, Tokens, _} = + erl_scan:string(binary_to_list(SOpts) ++ "."), + {ok, Opts} = erl_parse:parse_term(Tokens), + rpc:call(Node, ejabberd_listener, + delete_listener, + [PortIpNetp2, Module1]), + R = rpc:call(Node, ejabberd_listener, + add_listener, + [PortIpNetp2, Module, Opts]), + throw({is_added, R}); + _ -> + case lists:keysearch(<<"delete", + SSPort/binary>>, + 1, Query) + of + {value, _} -> + rpc:call(Node, ejabberd_listener, + delete_listener, + [PortIpNetp, Module1]), + throw(submitted); + _ -> ok + end + end + end, + Ports), + case lists:keysearch(<<"addnew">>, 1, Query) of + {value, _} -> + {{value, {_, SPort}}, {value, {_, STIP}}, + {value, {_, SNetProt}}, {value, {_, SModule}}, + {value, {_, SOpts}}} = + {lists:keysearch(<<"portnew">>, 1, Query), + lists:keysearch(<<"ipnew">>, 1, Query), + lists:keysearch(<<"netprotnew">>, 1, Query), + lists:keysearch(<<"modulenew">>, 1, Query), + lists:keysearch(<<"optsnew">>, 1, Query)}, + {ok, Toks, _} = erl_scan:string(binary_to_list(<<SPort/binary, ".">>)), + {ok, Port2} = erl_parse:parse_term(Toks), + {ok, ToksIP, _} = erl_scan:string(binary_to_list(<<STIP/binary, ".">>)), + STIP2 = case erl_parse:parse_term(ToksIP) of + {ok, IPTParsed} -> IPTParsed; + {error, _} -> STIP + end, + Module = jlib:binary_to_atom(SModule), + NetProt2 = jlib:binary_to_atom(SNetProt), + {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)), + {ok, Opts} = erl_parse:parse_term(Tokens), + {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2, + OptsClean} = + get_port_data({Port2, STIP2, NetProt2}, Opts), + R = rpc:call(Node, ejabberd_listener, add_listener, [{Port2, IP2, NetProt2}, Module, OptsClean]), - throw({is_added, R}); - _ -> - ok + throw({is_added, R}); + _ -> ok end. node_modules_to_xhtml(Modules, Lang) -> - ?XAE("table", [{"class", "withtextareas"}], - [?XE("thead", - [?XE("tr", - [?XCT("td", "Module"), - ?XCT("td", "Options") - ])]), - ?XE("tbody", - lists:map( - fun({Module, Opts} = _E) -> - SModule = atom_to_list(Module), - {NumLines, SOpts} = term_to_paragraph(Opts, 40), - %%ID = term_to_id(E), - ?XE("tr", - [?XC("td", SModule), - ?XE("td", [?TEXTAREA("opts" ++ SModule, integer_to_list(NumLines), "40", SOpts)]), - ?XE("td", [?INPUTT("submit", "restart" ++ SModule, - "Restart")]), - ?XE("td", [?INPUTT("submit", "stop" ++ SModule, - "Stop")]) - ] - ) - end, Modules) ++ - [?XE("tr", - [?XE("td", [?INPUT("text", "modulenew", "")]), - ?XE("td", [?TEXTAREA("optsnew", "2", "40", "[]")]), - ?XAE("td", [{"colspan", "2"}], - [?INPUTT("submit", "start", "Start")]) - ] - )] - )]). + ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}], + [?XE(<<"thead">>, + [?XE(<<"tr">>, + [?XCT(<<"td">>, <<"Module">>), + ?XCT(<<"td">>, <<"Options">>)])]), + ?XE(<<"tbody">>, + (lists:map(fun ({Module, Opts} = _E) -> + SModule = + iolist_to_binary(atom_to_list(Module)), + {NumLines, SOpts} = term_to_paragraph(Opts, + 40), + ?XE(<<"tr">>, + [?XC(<<"td">>, SModule), + ?XE(<<"td">>, + [?TEXTAREA(<<"opts", SModule/binary>>, + (iolist_to_binary(integer_to_list(NumLines))), + <<"40">>, SOpts)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"restart", + SModule/binary>>, + <<"Restart">>)]), + ?XE(<<"td">>, + [?INPUTT(<<"submit">>, + <<"stop", SModule/binary>>, + <<"Stop">>)])]) + end, + Modules) + ++ + [?XE(<<"tr">>, + [?XE(<<"td">>, + [?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]), + ?XE(<<"td">>, + [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>, + <<"[]">>)]), + ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], + [?INPUTT(<<"submit">>, <<"start">>, + <<"Start">>)])])]))]). node_modules_parse_query(Host, Node, Modules, Query) -> - lists:foreach( - fun({Module, _Opts1}) -> - SModule = atom_to_list(Module), - case lists:keysearch("restart" ++ SModule, 1, Query) of - {value, _} -> - {value, {_, SOpts}} = - lists:keysearch("opts" ++ SModule, 1, Query), - {ok, Tokens, _} = erl_scan:string(SOpts ++ "."), - {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, gen_mod, stop_module, [Host, Module]), - rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]), - throw(submitted); - _ -> - case lists:keysearch("stop" ++ SModule, 1, Query) of - {value, _} -> - rpc:call(Node, gen_mod, stop_module, [Host, Module]), - throw(submitted); - _ -> - ok - end - end - end, Modules), - case lists:keysearch("start", 1, Query) of - {value, _} -> - {{value, {_, SModule}}, - {value, {_, SOpts}}} = - {lists:keysearch("modulenew", 1, Query), - lists:keysearch("optsnew", 1, Query)}, - Module = list_to_atom(SModule), - {ok, Tokens, _} = erl_scan:string(SOpts ++ "."), - {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]), - throw(submitted); - _ -> - ok + lists:foreach(fun ({Module, _Opts1}) -> + SModule = iolist_to_binary(atom_to_list(Module)), + case lists:keysearch(<<"restart", SModule/binary>>, 1, + Query) + of + {value, _} -> + {value, {_, SOpts}} = lists:keysearch(<<"opts", + SModule/binary>>, + 1, Query), + {ok, Tokens, _} = + erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)), + {ok, Opts} = erl_parse:parse_term(Tokens), + rpc:call(Node, gen_mod, stop_module, + [Host, Module]), + rpc:call(Node, gen_mod, start_module, + [Host, Module, Opts]), + throw(submitted); + _ -> + case lists:keysearch(<<"stop", SModule/binary>>, + 1, Query) + of + {value, _} -> + rpc:call(Node, gen_mod, stop_module, + [Host, Module]), + throw(submitted); + _ -> ok + end + end + end, + Modules), + case lists:keysearch(<<"start">>, 1, Query) of + {value, _} -> + {{value, {_, SModule}}, {value, {_, SOpts}}} = + {lists:keysearch(<<"modulenew">>, 1, Query), + lists:keysearch(<<"optsnew">>, 1, Query)}, + Module = jlib:binary_to_atom(SModule), + {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)), + {ok, Opts} = erl_parse:parse_term(Tokens), + rpc:call(Node, gen_mod, start_module, + [Host, Module, Opts]), + throw(submitted); + _ -> ok end. - node_update_parse_query(Node, Query) -> - case lists:keysearch("update", 1, Query) of - {value, _} -> - ModulesToUpdateStrings = proplists:get_all_values("selected",Query), - ModulesToUpdate = [list_to_atom(M) || M <- ModulesToUpdateStrings], - case rpc:call(Node, ejabberd_update, update, [ModulesToUpdate]) of - {ok, _} -> - ok; - {error, Error} -> - ?ERROR_MSG("~p~n", [Error]), - {error, io_lib:format("~p", [Error])}; - {badrpc, Error} -> - ?ERROR_MSG("Bad RPC: ~p~n", [Error]), - {error, "Bad RPC: " ++ io_lib:format("~p", [Error])} - end; - _ -> - nothing + case lists:keysearch(<<"update">>, 1, Query) of + {value, _} -> + ModulesToUpdateStrings = + proplists:get_all_values(<<"selected">>, Query), + ModulesToUpdate = [jlib:binary_to_atom(M) + || M <- ModulesToUpdateStrings], + case rpc:call(Node, ejabberd_update, update, + [ModulesToUpdate]) + of + {ok, _} -> ok; + {error, Error} -> + ?ERROR_MSG("~p~n", [Error]), + {error, iolist_to_binary(io_lib:format("~p", [Error]))}; + {badrpc, Error} -> + ?ERROR_MSG("Bad RPC: ~p~n", [Error]), + {error, + <<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>} + end; + _ -> nothing end. - pretty_print_xml(El) -> - lists:flatten(pretty_print_xml(El, "")). + list_to_binary(pretty_print_xml(El, <<"">>)). pretty_print_xml({xmlcdata, CData}, Prefix) -> - [Prefix, CData, $\n]; -pretty_print_xml({xmlelement, Name, Attrs, Els}, Prefix) -> + IsBlankCData = lists:all( + fun($\f) -> true; + ($\r) -> true; + ($\n) -> true; + ($\t) -> true; + ($\v) -> true; + ($ ) -> true; + (_) -> false + end, binary_to_list(CData)), + if IsBlankCData -> + []; + true -> + [Prefix, CData, $\n] + end; +pretty_print_xml(#xmlel{name = Name, attrs = Attrs, + children = Els}, + Prefix) -> [Prefix, $<, Name, case Attrs of - [] -> - []; - [{Attr, Val} | RestAttrs] -> - AttrPrefix = [Prefix, - string:copies(" ", length(Name) + 2)], - [$\s, Attr, $=, $', xml:crypt(Val), $' | - lists:map(fun({Attr1, Val1}) -> - [$\n, AttrPrefix, - Attr1, $=, $', xml:crypt(Val1), $'] - end, RestAttrs)] + [] -> []; + [{Attr, Val} | RestAttrs] -> + AttrPrefix = [Prefix, + str:copies(<<" ">>, byte_size(Name) + 2)], + [$\s, Attr, $=, $', xml:crypt(Val) | [$', + lists:map(fun ({Attr1, + Val1}) -> + [$\n, + AttrPrefix, + Attr1, $=, + $', + xml:crypt(Val1), + $'] + end, + RestAttrs)]] end, - if - Els == [] -> - "/>\n"; - true -> - OnlyCData = lists:all(fun({xmlcdata, _}) -> true; - ({xmlelement, _, _, _}) -> false - end, Els), - if - OnlyCData -> - [$>, - xml:get_cdata(Els), - $<, $/, Name, $>, $\n - ]; - true -> - [$>, $\n, - lists:map(fun(E) -> - pretty_print_xml(E, [Prefix, " "]) - end, Els), - Prefix, $<, $/, Name, $>, $\n - ] - end + if Els == [] -> <<"/>\n">>; + true -> + OnlyCData = lists:all(fun ({xmlcdata, _}) -> true; + (#xmlel{}) -> false + end, + Els), + if OnlyCData -> + [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n]; + true -> + [$>, $\n, + lists:map(fun (E) -> + pretty_print_xml(E, [Prefix, <<" ">>]) + end, + Els), + Prefix, $<, $/, Name, $>, $\n] + end end]. -element_to_list(X) when is_atom(X) -> atom_to_list(X); -element_to_list(X) when is_integer(X) -> integer_to_list(X). +element_to_list(X) when is_atom(X) -> + iolist_to_binary(atom_to_list(X)); +element_to_list(X) when is_integer(X) -> + iolist_to_binary(integer_to_list(X)). -list_to_element(List) -> - {ok, Tokens, _} = erl_scan:string(List), +list_to_element(Bin) -> + {ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)), [{_, _, Element}] = Tokens, Element. url_func({user_diapason, From, To}) -> - integer_to_list(From) ++ "-" ++ integer_to_list(To) ++ "/"; + <<(iolist_to_binary(integer_to_list(From)))/binary, "-", + (iolist_to_binary(integer_to_list(To)))/binary, "/">>; url_func({users_queue, Prefix, User, _Server}) -> - Prefix ++ "user/" ++ User ++ "/queue/"; + <<Prefix/binary, "user/", User/binary, "/queue/">>; url_func({user, Prefix, User, _Server}) -> - Prefix ++ "user/" ++ User ++ "/". + <<Prefix/binary, "user/", User/binary, "/">>. last_modified() -> - {"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}. + {<<"Last-Modified">>, + <<"Mon, 25 Feb 2008 13:23:30 GMT">>}. + cache_control_public() -> - {"Cache-Control", "public"}. + {<<"Cache-Control">>, <<"public">>}. %% Transform 1234567890 into "1,234,567,890" pretty_string_int(Integer) when is_integer(Integer) -> - pretty_string_int(integer_to_list(Integer)); -pretty_string_int(String) when is_list(String) -> - {_, Result} = lists:foldl( - fun(NewNumber, {3, Result}) -> - {1, [NewNumber, $, | Result]}; - (NewNumber, {CountAcc, Result}) -> - {CountAcc+1, [NewNumber | Result]} - end, - {0, ""}, - lists:reverse(String)), + pretty_string_int(iolist_to_binary(integer_to_list(Integer))); +pretty_string_int(String) when is_binary(String) -> + {_, Result} = lists:foldl(fun (NewNumber, {3, Result}) -> + {1, <<NewNumber, $,, Result/binary>>}; + (NewNumber, {CountAcc, Result}) -> + {CountAcc + 1, <<NewNumber, Result/binary>>} + end, + {0, <<"">>}, lists:reverse(binary_to_list(String))), Result. %%%================================== %%%% navigation menu -%% @spec (Host, Node, Lang, JID::jid()) -> [LI] make_navigation(Host, Node, Lang, JID) -> Menu = make_navigation_menu(Host, Node, Lang, JID), make_menu_items(Lang, Menu). -%% @spec (Host, Node, Lang, JID::jid()) -> Menu -%% where Host = global | string() -%% Node = cluster | string() -%% Lang = string() -%% Menu = {URL, Title} | {URL, Title, [Menu]} -%% URL = string() -%% Title = string() make_navigation_menu(Host, Node, Lang, JID) -> - HostNodeMenu = make_host_node_menu(Host, Node, Lang, JID), - HostMenu = make_host_menu(Host, HostNodeMenu, Lang, JID), + HostNodeMenu = make_host_node_menu(Host, Node, Lang, + JID), + HostMenu = make_host_menu(Host, HostNodeMenu, Lang, + JID), NodeMenu = make_node_menu(Host, Node, Lang), make_server_menu(HostMenu, NodeMenu, Lang, JID). -%% @spec (Host, Node, Base, Lang) -> [LI] make_menu_items(global, cluster, Base, Lang) -> HookItems = get_menu_items_hook(server, Lang), - make_menu_items(Lang, {Base, "", HookItems}); - + make_menu_items(Lang, {Base, <<"">>, HookItems}); make_menu_items(global, Node, Base, Lang) -> HookItems = get_menu_items_hook({node, Node}, Lang), - make_menu_items(Lang, {Base, "", HookItems}); - + make_menu_items(Lang, {Base, <<"">>, HookItems}); make_menu_items(Host, cluster, Base, Lang) -> HookItems = get_menu_items_hook({host, Host}, Lang), - make_menu_items(Lang, {Base, "", HookItems}); - + make_menu_items(Lang, {Base, <<"">>, HookItems}); make_menu_items(Host, Node, Base, Lang) -> - HookItems = get_menu_items_hook({hostnode, Host, Node}, Lang), - make_menu_items(Lang, {Base, "", HookItems}). - + HookItems = get_menu_items_hook({hostnode, Host, Node}, + Lang), + make_menu_items(Lang, {Base, <<"">>, HookItems}). make_host_node_menu(global, _, _Lang, _JID) -> - {"", "", []}; + {<<"">>, <<"">>, []}; make_host_node_menu(_, cluster, _Lang, _JID) -> - {"", "", []}; + {<<"">>, <<"">>, []}; make_host_node_menu(Host, Node, Lang, JID) -> HostNodeBase = get_base_path(Host, Node), - HostNodeFixed = [{"modules/", "Modules"}] - ++ get_menu_items_hook({hostnode, Host, Node}, Lang), + HostNodeFixed = [{<<"modules/">>, <<"Modules">>}] ++ + get_menu_items_hook({hostnode, Host, Node}, Lang), HostNodeBasePath = url_to_path(HostNodeBase), - HostNodeFixed2 = [Tuple || Tuple <- HostNodeFixed, is_allowed_path(HostNodeBasePath, Tuple, JID)], - {HostNodeBase, atom_to_list(Node), HostNodeFixed2}. + HostNodeFixed2 = [Tuple + || Tuple <- HostNodeFixed, + is_allowed_path(HostNodeBasePath, Tuple, JID)], + {HostNodeBase, iolist_to_binary(atom_to_list(Node)), + HostNodeFixed2}. make_host_menu(global, _HostNodeMenu, _Lang, _JID) -> - {"", "", []}; + {<<"">>, <<"">>, []}; make_host_menu(Host, HostNodeMenu, Lang, JID) -> HostBase = get_base_path(Host, cluster), - HostFixed = [{"acls", "Access Control Lists"}, - {"access", "Access Rules"}, - {"users", "Users"}, - {"online-users", "Online Users"}] - ++ get_lastactivity_menuitem_list(Host) ++ - [{"nodes", "Nodes", HostNodeMenu}, - {"stats", "Statistics"}] - ++ get_menu_items_hook({host, Host}, Lang), + HostFixed = [{<<"acls">>, <<"Access Control Lists">>}, + {<<"access">>, <<"Access Rules">>}, + {<<"users">>, <<"Users">>}, + {<<"online-users">>, <<"Online Users">>}] + ++ + get_lastactivity_menuitem_list(Host) ++ + [{<<"nodes">>, <<"Nodes">>, HostNodeMenu}, + {<<"stats">>, <<"Statistics">>}] + ++ get_menu_items_hook({host, Host}, Lang), HostBasePath = url_to_path(HostBase), - HostFixed2 = [Tuple || Tuple <- HostFixed, is_allowed_path(HostBasePath, Tuple, JID)], + HostFixed2 = [Tuple + || Tuple <- HostFixed, + is_allowed_path(HostBasePath, Tuple, JID)], {HostBase, Host, HostFixed2}. make_node_menu(_Host, cluster, _Lang) -> - {"", "", []}; + {<<"">>, <<"">>, []}; make_node_menu(global, Node, Lang) -> NodeBase = get_base_path(global, Node), - NodeFixed = [{"db/", "Database"}, - {"backup/", "Backup"}, - {"ports/", "Listened Ports"}, - {"stats/", "Statistics"}, - {"update/", "Update"}] - ++ get_menu_items_hook({node, Node}, Lang), - {NodeBase, atom_to_list(Node), NodeFixed}; + NodeFixed = [{<<"db/">>, <<"Database">>}, + {<<"backup/">>, <<"Backup">>}, + {<<"ports/">>, <<"Listened Ports">>}, + {<<"stats/">>, <<"Statistics">>}, + {<<"update/">>, <<"Update">>}] + ++ get_menu_items_hook({node, Node}, Lang), + {NodeBase, iolist_to_binary(atom_to_list(Node)), + NodeFixed}; make_node_menu(_Host, _Node, _Lang) -> - {"", "", []}. + {<<"">>, <<"">>, []}. make_server_menu(HostMenu, NodeMenu, Lang, JID) -> Base = get_base_path(global, cluster), - Fixed = [{"acls", "Access Control Lists"}, - {"access", "Access Rules"}, - {"vhosts", "Virtual Hosts", HostMenu}, - {"nodes", "Nodes", NodeMenu}, - {"stats", "Statistics"}] - ++ get_menu_items_hook(server, Lang), + Fixed = [{<<"acls">>, <<"Access Control Lists">>}, + {<<"access">>, <<"Access Rules">>}, + {<<"vhosts">>, <<"Virtual Hosts">>, HostMenu}, + {<<"nodes">>, <<"Nodes">>, NodeMenu}, + {<<"stats">>, <<"Statistics">>}] + ++ get_menu_items_hook(server, Lang), BasePath = url_to_path(Base), - Fixed2 = [Tuple || Tuple <- Fixed, is_allowed_path(BasePath, Tuple, JID)], - {Base, "ejabberd", Fixed2}. - + Fixed2 = [Tuple + || Tuple <- Fixed, + is_allowed_path(BasePath, Tuple, JID)], + {Base, <<"ejabberd">>, Fixed2}. get_menu_items_hook({hostnode, Host, Node}, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]); + ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, + [], [Host, Node, Lang]); get_menu_items_hook({host, Host}, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]); + ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], + [Host, Lang]); get_menu_items_hook({node, Node}, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node, Lang]); + ejabberd_hooks:run_fold(webadmin_menu_node, [], + [Node, Lang]); get_menu_items_hook(server, Lang) -> ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]). - -%% @spec (Lang::string(), Menu) -> [LI] -%% where Menu = {MURI::string(), MName::string(), Items::[Item]} -%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu} make_menu_items(Lang, Menu) -> lists:reverse(make_menu_items2(Lang, 1, Menu)). make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) -> Res = case MName of - "" -> []; - _ -> [make_menu_item(header, Deep, MURI, MName, Lang) ] + <<"">> -> []; + _ -> [make_menu_item(header, Deep, MURI, MName, Lang)] end, make_menu_items2(Lang, Deep, Menu, Res). -make_menu_items2(_, _Deep, {_, _, []}, Res) -> - Res; - -make_menu_items2(Lang, Deep, {MURI, MName, [Item | Items]}, Res) -> +make_menu_items2(_, _Deep, {_, _, []}, Res) -> Res; +make_menu_items2(Lang, Deep, + {MURI, MName, [Item | Items]}, Res) -> Res2 = case Item of - {IURI, IName} -> - [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res]; - {IURI, IName, SubMenu} -> - %%ResTemp = [?LI([?ACT(MURI ++ IURI ++ "/", IName)]) | Res], - ResTemp = [make_menu_item(item, Deep, MURI++IURI++"/", IName, Lang) | Res], - ResSubMenu = make_menu_items2(Lang, Deep+1, SubMenu), - ResSubMenu ++ ResTemp + {IURI, IName} -> + [make_menu_item(item, Deep, + <<MURI/binary, IURI/binary, "/">>, IName, Lang) + | Res]; + {IURI, IName, SubMenu} -> + ResTemp = [make_menu_item(item, Deep, + <<MURI/binary, IURI/binary, "/">>, + IName, Lang) + | Res], + ResSubMenu = make_menu_items2(Lang, Deep + 1, SubMenu), + ResSubMenu ++ ResTemp end, - make_menu_items2(Lang, Deep, {MURI, MName, Items}, Res2). + make_menu_items2(Lang, Deep, {MURI, MName, Items}, + Res2). make_menu_item(header, 1, URI, Name, _Lang) -> - ?LI([?XAE("div", [{"id", "navhead"}], [?AC(URI, Name)] )]); + ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navhead">>}], + [?AC(URI, Name)])]); make_menu_item(header, 2, URI, Name, _Lang) -> - ?LI([?XAE("div", [{"id", "navheadsub"}], [?AC(URI, Name)] )]); + ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsub">>}], + [?AC(URI, Name)])]); make_menu_item(header, 3, URI, Name, _Lang) -> - ?LI([?XAE("div", [{"id", "navheadsubsub"}], [?AC(URI, Name)] )]); + ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsubsub">>}], + [?AC(URI, Name)])]); make_menu_item(item, 1, URI, Name, Lang) -> - ?LI([?XAE("div", [{"id", "navitem"}], [?ACT(URI, Name)] )]); + ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitem">>}], + [?ACT(URI, Name)])]); make_menu_item(item, 2, URI, Name, Lang) -> - ?LI([?XAE("div", [{"id", "navitemsub"}], [?ACT(URI, Name)] )]); + ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsub">>}], + [?ACT(URI, Name)])]); make_menu_item(item, 3, URI, Name, Lang) -> - ?LI([?XAE("div", [{"id", "navitemsubsub"}], [?ACT(URI, Name)] )]). + ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}], + [?ACT(URI, Name)])]). %%%================================== %%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: + diff --git a/src/web/ejabberd_web_admin.hrl b/src/web/ejabberd_web_admin.hrl index 73f792e2d..46f002fbf 100644 --- a/src/web/ejabberd_web_admin.hrl +++ b/src/web/ejabberd_web_admin.hrl @@ -19,58 +19,84 @@ %%% %%%---------------------------------------------------------------------- --define(X(Name), {xmlelement, Name, [], []}). --define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}). --define(XE(Name, Els), {xmlelement, Name, [], Els}). --define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}). +-define(X(Name), + #xmlel{name = Name, attrs = [], children = []}). + +-define(XA(Name, Attrs), + #xmlel{name = Name, attrs = Attrs, children = []}). + +-define(XE(Name, Els), + #xmlel{name = Name, attrs = [], children = Els}). + +-define(XAE(Name, Attrs, Els), + #xmlel{name = Name, attrs = Attrs, children = Els}). + -define(C(Text), {xmlcdata, Text}). + -define(XC(Name, Text), ?XE(Name, [?C(Text)])). --define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])). + +-define(XAC(Name, Attrs, Text), + ?XAE(Name, Attrs, [?C(Text)])). -define(T(Text), translate:translate(Lang, Text)). --define(CT(Text), ?C(?T(Text))). --define(XCT(Name, Text), ?XC(Name, ?T(Text))). --define(XACT(Name, Attrs, Text), ?XAC(Name, Attrs, ?T(Text))). --define(LI(Els), ?XE("li", Els)). --define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)). +-define(CT(Text), ?C((?T(Text)))). + +-define(XCT(Name, Text), ?XC(Name, (?T(Text)))). + +-define(XACT(Name, Attrs, Text), + ?XAC(Name, Attrs, (?T(Text)))). + +-define(LI(Els), ?XE(<<"li">>, Els)). + +-define(A(URL, Els), + ?XAE(<<"a">>, [{<<"href">>, URL}], Els)). + -define(AC(URL, Text), ?A(URL, [?C(Text)])). --define(ACT(URL, Text), ?AC(URL, ?T(Text))). --define(P, ?X("p")). --define(BR, ?X("br")). + +-define(ACT(URL, Text), ?AC(URL, (?T(Text)))). + +-define(P, ?X(<<"p">>)). + +-define(BR, ?X(<<"br">>)). + -define(INPUT(Type, Name, Value), - ?XA("input", [{"type", Type}, - {"name", Name}, - {"value", Value}])). --define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))). + ?XA(<<"input">>, + [{<<"type">>, Type}, {<<"name">>, Name}, + {<<"value">>, Value}])). + +-define(INPUTT(Type, Name, Value), + ?INPUT(Type, Name, (?T(Value)))). + -define(INPUTS(Type, Name, Value, Size), - ?XA("input", [{"type", Type}, - {"name", Name}, - {"value", Value}, - {"size", Size}])). --define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)). --define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])). + ?XA(<<"input">>, + [{<<"type">>, Type}, {<<"name">>, Name}, + {<<"value">>, Value}, {<<"size">>, Size}])). + +-define(INPUTST(Type, Name, Value, Size), + ?INPUT(Type, Name, (?T(Value)), Size)). + +-define(ACLINPUT(Text), + ?XE(<<"td">>, + [?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])). -define(TEXTAREA(Name, Rows, Cols, Value), - ?XAC("textarea", [{"name", Name}, - {"rows", Rows}, - {"cols", Cols}], + ?XAC(<<"textarea">>, + [{<<"name">>, Name}, {<<"rows">>, Rows}, + {<<"cols">>, Cols}], Value)). -%% Build an xmlelement for result --define(XRES(Text), ?XAC("p", [{"class", "result"}], Text)). --define(XREST(Text), ?XRES(?T(Text))). +-define(XRES(Text), + ?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)). -%% Guide Link --define(GL(Ref, Title), - ?XAE("div", - [{"class", "guidelink"}], - [?XAE("a", - [{"href", "/admin/doc/guide.html#"++ Ref}, - {"target", "_blank"}], - [?C("[Guide: " ++ Title ++ "]")]) - ])). +-define(XREST(Text), ?XRES((?T(Text)))). +-define(GL(Ref, Title), + ?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}], + [?XAE(<<"a">>, + [{<<"href">>, <<"/admin/doc/guide.html#", Ref/binary>>}, + {<<"target">>, <<"_blank">>}], + [?C(<<"[Guide: ", Title/binary, "]">>)])])). -%% h1 with a Guide Link --define(H1GL(Name, Ref, Title), [?XC("h1", Name), ?GL(Ref, Title)]). +-define(H1GL(Name, Ref, Title), + [?XC(<<"h1">>, Name), ?GL(Ref, Title)]). diff --git a/src/web/ejabberd_websocket.erl b/src/web/ejabberd_websocket.erl index 8030842a0..fde8baff6 100644 --- a/src/web/ejabberd_websocket.erl +++ b/src/web/ejabberd_websocket.erl @@ -8,12 +8,12 @@ %%% (http://github.com/ostinelli/misultin/blob/master/src/misultin_websocket.erl) %%% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Joe Armstrong. %%% All rights reserved. -%%% +%%% %%% Code portions from Joe Armstrong have been originally taken under MIT license at the address: %%% <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html> %%% %%% BSD License -%%% +%%% %%% Redistribution and use in source and binary forms, with or without modification, are permitted provided %%% that the following conditions are met: %%% @@ -36,400 +36,446 @@ %%% ejabberd, Copyright (C) 2002-2010 ProcessOne %%%---------------------------------------------------------------------- --module (ejabberd_websocket). +-module(ejabberd_websocket). + -author('ecestari@process-one.net'). + -export([connect/2, check/2, is_acceptable/1]). + -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). -check(_Path, Headers)-> - % set supported websocket protocols, order does matter - VsnSupported = [{'draft-hybi', 8}, {'draft-hybi', 13}, {'draft-hixie', 0}, {'draft-hixie', 68}], - % checks - check_websockets(VsnSupported, Headers). +check(_Path, Headers) -> + VsnSupported = [{'draft-hybi', 8}, {'draft-hybi', 13}, + {'draft-hixie', 0}, {'draft-hixie', 68}], + check_websockets(VsnSupported, Headers). -% Checks if websocket can be access by client -% If origins are set in configuration, check if it belongs -% If origins not set, access is open. -is_acceptable(#ws{origin=Origin, protocol=Protocol, - headers = Headers, acceptable_origins = Origins, auth_module=undefined})-> - ClientProtocol = lists:keyfind("Sec-Websocket-Protocol",1, Headers), - case {(Origins == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of - {false, _, _} -> - ?INFO_MSG("client does not come from authorized origin", []), - false; - {_, false, _} -> - true; - {_, {_, P}, P} -> - true; - _ = E-> - ?INFO_MSG("Wrong protocol requested : ~p", [E]), - false - end; -is_acceptable(#ws{local_path=LocalPath, origin=Origin, ip=IP, q=Q, protocol=Protocol, headers = Headers,auth_module=Module})-> - Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP, Headers). +is_acceptable(#ws{origin = Origin, protocol = Protocol, + headers = Headers, acceptable_origins = Origins, + auth_module = undefined}) -> + ClientProtocol = + lists:keyfind(<<"Sec-Websocket-Protocol">>, 1, Headers), + case {(Origins == []) or lists:member(Origin, Origins), + ClientProtocol, Protocol} + of + {false, _, _} -> + ?INFO_MSG("client does not come from authorized " + "origin", + []), + false; + {_, false, _} -> true; + {_, {_, P}, P} -> true; + _ = E -> + ?INFO_MSG("Wrong protocol requested : ~p", [E]), false + end; +is_acceptable(#ws{local_path = LocalPath, + origin = Origin, ip = IP, q = Q, protocol = Protocol, + headers = Headers, auth_module = Module}) -> + Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP, + Headers). -% Connect and handshake with Websocket. -connect(#ws{vsn = Vsn, socket = Socket, q=Q,origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) -> - % build handshake - HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Q, Origin, Host, Port}), - % send handshake back - SockMod:send(Socket, HandshakeServer), - ?DEBUG("Sent handshake response : ~p", [HandshakeServer]), - Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, host = Host}, self()), - %?DEBUG("Ws0 : ~p",[Ws0]), - % add data to ws record and spawn controlling process - {ok, WsHandleLoopPid} = WsLoop:start_link(Ws0), - erlang:monitor(process, WsHandleLoopPid), - % set opts - case SockMod of - gen_tcp -> - inet:setopts(Socket, [{packet, 0}, {active, true}]); - _ -> - SockMod:setopts(Socket, [{packet, 0}, {active, true}]) - end, - % start listening for incoming data - ws_loop(Vsn, none, Socket, WsHandleLoopPid, SockMod, WsAutoExit). +connect(#ws{vsn = Vsn, socket = Socket, q = Q, + origin = Origin, host = Host, port = Port, + sockmod = SockMod, path = Path, headers = Headers, + ws_autoexit = WsAutoExit} = + Ws, + WsLoop) -> + HandshakeServer = handshake(Vsn, Socket, SockMod, + Headers, {Path, Q, Origin, Host, Port}), + SockMod:send(Socket, HandshakeServer), + ?DEBUG("Sent handshake response : ~p", + [HandshakeServer]), + Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, + host = Host}, + self()), + {ok, WsHandleLoopPid} = WsLoop:start_link(Ws0), + erlang:monitor(process, WsHandleLoopPid), + case SockMod of + gen_tcp -> + inet:setopts(Socket, [{packet, 0}, {active, true}]); + _ -> + SockMod:setopts(Socket, [{packet, 0}, {active, true}]) + end, + ws_loop(Vsn, none, Socket, WsHandleLoopPid, SockMod, + WsAutoExit). - check_websockets([], _Headers) -> false; -check_websockets([Vsn|T], Headers) -> - case check_websocket(Vsn, Headers) of - false -> check_websockets(T, Headers); - Value -> Value - end. +check_websockets([Vsn | T], Headers) -> + case check_websocket(Vsn, Headers) of + false -> check_websockets(T, Headers); + Value -> Value + end. -% Function: {true, Vsn} | false -% Description: Check if the incoming request is a websocket request. check_websocket({'draft-hixie', 0} = Vsn, Headers) -> - %?DEBUG("testing for websocket protocol ~p", [Vsn]), - % set required headers - RequiredHeaders = [ - {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore}, - {"Sec-Websocket-Key1", ignore}, {"Sec-Websocket-Key2", ignore} - ], - % check for headers existance - case check_headers(Headers, RequiredHeaders) of - true -> - % return - {true, Vsn}; - _RemainingHeaders -> - %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]), - false - end; + RequiredHeaders = [{'Upgrade', <<"WebSocket">>}, + {'Connection', <<"Upgrade">>}, {'Host', ignore}, + {<<"Origin">>, ignore}, + {<<"Sec-Websocket-Key1">>, ignore}, + {<<"Sec-Websocket-Key2">>, ignore}], + case check_headers(Headers, RequiredHeaders) of + true -> {true, Vsn}; + _RemainingHeaders -> + %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]), + false + end; check_websocket({'draft-hixie', 68} = Vsn, Headers) -> - %?DEBUG("testing for websocket protocol ~p", [Vsn]), - % set required headers - RequiredHeaders = [ - {'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore} - ], - % check for headers existance - case check_headers(Headers, RequiredHeaders) of - true -> {true, Vsn}; - _RemainingHeaders -> - %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]), - false - end; + RequiredHeaders = [{'Upgrade', <<"WebSocket">>}, + {'Connection', <<"Upgrade">>}, {'Host', ignore}, + {<<"Origin">>, ignore}], + case check_headers(Headers, RequiredHeaders) of + true -> {true, Vsn}; + _RemainingHeaders -> + %?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]), + false + end; check_websocket({'draft-hybi', 8} = Vsn, Headers) -> - %?DEBUG("testing for websocket protocol ~p", [Vsn]), - % set required headers - RequiredHeaders = [ - {'Upgrade', "websocket"}, {'Connection', ignore}, {'Host', ignore}, - {"Sec-Websocket-Key", ignore}, {"Sec-Websocket-Version", "8"} - ], - % check for headers existance - case check_headers(Headers, RequiredHeaders) of - true -> {true, Vsn}; - RemainingHeaders -> - %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]), - false - end; + RequiredHeaders = [{'Upgrade', <<"websocket">>}, + {'Connection', ignore}, {'Host', ignore}, + {<<"Sec-Websocket-Key">>, ignore}, + {<<"Sec-Websocket-Version">>, <<"8">>}], + case check_headers(Headers, RequiredHeaders) of + true -> {true, Vsn}; + _RemainingHeaders -> + %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]), + false + end; check_websocket({'draft-hybi', 13} = Vsn, Headers) -> - %?DEBUG("testing for websocket protocol ~p", [Vsn]), - % set required headers - RequiredHeaders = [ - {'Upgrade', "websocket"}, {'Connection', ignore}, {'Host', ignore}, - {"Sec-Websocket-Key", ignore}, {"Sec-Websocket-Version", "13"} - ], - % check for headers existance - case check_headers(Headers, RequiredHeaders) of - true -> {true, Vsn}; - RemainingHeaders -> - %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]), - false - end; -check_websocket(_Vsn, _Headers) -> false. % not implemented + RequiredHeaders = [{'Upgrade', <<"websocket">>}, + {'Connection', ignore}, {'Host', ignore}, + {<<"Sec-Websocket-Key">>, ignore}, + {<<"Sec-Websocket-Version">>, <<"13">>}], + case check_headers(Headers, RequiredHeaders) of + true -> {true, Vsn}; + _RemainingHeaders -> + %%?INFO_MSG("not protocol ~p, remaining headers: ~p", [Vsn, RemainingHeaders]), + false + end. -% Function: true | [{RequiredTag, RequiredVal}, ..] -% Description: Check if headers correspond to headers requirements. check_headers(Headers, RequiredHeaders) -> - F = fun({Tag, Val}) -> - % see if the required Tag is in the Headers - case lists:keyfind(Tag, 1, Headers) of - false -> true; % header not found, keep in list - {_, HVal} -> - %?DEBUG("check: ~p", [{Tag, HVal,Val }]), - case Val of - ignore -> false; % ignore value -> ok, remove from list - HVal -> false; % expected val -> ok, remove from list - _ -> true % val is different, keep in list - end + F = fun ({Tag, Val}) -> + case lists:keyfind(Tag, 1, Headers) of + false -> true; % header not found, keep in list + {_, HVal} -> + case Val of + ignore -> false; % ignore value -> ok, remove from list + HVal -> false; % expected val -> ok, remove from list + _ -> + true % val is different, keep in list + end end end, - case lists:filter(F, RequiredHeaders) of - [] -> true; - MissingHeaders -> MissingHeaders - end. + case lists:filter(F, RequiredHeaders) of + [] -> true; + MissingHeaders -> MissingHeaders + end. -% Function: List -% Description: Builds the server handshake response. -handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) -> - % build data - {_, Key1} = lists:keyfind("Sec-Websocket-Key1",1, Headers), - {_, Key2} = lists:keyfind("Sec-Websocket-Key2",1, Headers), - HostPort = case lists:keyfind('Host', 1, Headers) of - {_, Value} -> Value; - _ -> string:join([Host, integer_to_list(Port)],":") - end, - % handshake needs body of the request, still need to read it [TODO: default recv timeout hard set, will be exported when WS protocol is final] - case SocketMod of - gen_tcp -> - inet:setopts(Sock, [{packet, raw}, {active, false}]); - _ -> - SocketMod:setopts(Sock, [{packet, raw}, {active, false}]) - end, - Body = case SocketMod:recv(Sock, 8, 30*1000) of - {ok, Bin} -> Bin; - {error, timeout} -> - ?WARNING_MSG("timeout in reading websocket body", []), - <<>>; - _Other -> - ?ERROR_MSG("tcp error treating data: ~p", [_Other]), - <<>> - end, - QParams = lists:map( - fun({nokey,[]})-> - none; - ({K, V})-> - K ++ "=" ++ V - end, Q), - QString = case QParams of - [none]-> ""; - QParams-> "?" ++ string:join(QParams, "&") - end, - %?DEBUG("got content in body of websocket request: ~p, ~p", [Body,string:join([Host, Path],"/")]), - % prepare handhsake response - ["HTTP/1.1 101 WebSocket Protocol Handshake\r\n", - "Upgrade: WebSocket\r\n", - "Connection: Upgrade\r\n", - "Sec-WebSocket-Origin: ", Origin, "\r\n", - "Sec-WebSocket-Location: ws://", HostPort, "/", string:join(Path,"/"), - QString, "\r\n\r\n", - build_challenge({'draft-hixie', 0}, {Key1, Key2, Body}) - ]; -handshake({'draft-hixie', 68}, _Sock,_SocketMod, Headers, {Path, Origin, Host, Port}) -> - HostPort = case lists:keyfind('Host', 1, Headers) of - {_, Value} -> Value; - _ -> string:join([Host, integer_to_list(Port)],":") - end, - ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n", - "Upgrade: WebSocket\r\n", - "Connection: Upgrade\r\n", - "WebSocket-Origin: ", Origin , "\r\n", - "WebSocket-Location: ws://", HostPort, "/", string:join(Path,"/"),"\r\n\r\n" - ]; -handshake({'draft-hybi', _}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) -> - {_, Key} = lists:keyfind("Sec-Websocket-Key",1, Headers), - Hash = jlib:encode_base64(binary_to_list(sha:sha1(Key++"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))), - ["HTTP/1.1 101 Switching Protocols\r\n", - "Upgrade: websocket\r\n", - "Connection: Upgrade\r\n", - "Sec-WebSocket-Accept: ", Hash, "\r\n\r\n" - ]. +handshake({'draft-hixie', 0}, Sock, SocketMod, Headers, + {Path, Q, Origin, Host, Port}) -> + {_, Key1} = lists:keyfind(<<"Sec-Websocket-Key1">>, 1, + Headers), + {_, Key2} = lists:keyfind(<<"Sec-Websocket-Key2">>, 1, + Headers), + HostPort = case lists:keyfind('Host', 1, Headers) of + {_, Value} -> Value; + _ -> + str:join([Host, + jlib:integer_to_binary(Port)], + <<":">>) + end, + case SocketMod of + gen_tcp -> + inet:setopts(Sock, [{packet, raw}, {active, false}]); + _ -> + SocketMod:setopts(Sock, + [{packet, raw}, {active, false}]) + end, + Body = case SocketMod:recv(Sock, 8, 30 * 1000) of + {ok, Bin} -> Bin; + {error, timeout} -> + ?WARNING_MSG("timeout in reading websocket body", []), + <<>>; + _Other -> + ?ERROR_MSG("tcp error treating data: ~p", [_Other]), + <<>> + end, + QParams = lists:map(fun ({nokey, <<>>}) -> none; + ({K, V}) -> <<K/binary, "=", V/binary>> + end, + Q), + QString = case QParams of + [none] -> <<"">>; + QParams -> <<"?", (str:join(QParams, <<"&">>))/binary>> + end, + [<<"HTTP/1.1 101 WebSocket Protocol Handshake\r\n">>, + <<"Upgrade: WebSocket\r\n">>, + <<"Connection: Upgrade\r\n">>, + <<"Sec-WebSocket-Origin: ">>, Origin, <<"\r\n">>, + <<"Sec-WebSocket-Location: ws://">>, HostPort, <<"/">>, + str:join(Path, <<"/">>), QString, <<"\r\n\r\n">>, + build_challenge({'draft-hixie', 0}, + {Key1, Key2, Body})]; +handshake({'draft-hixie', 68}, _Sock, _SocketMod, + Headers, {Path, _Q, Origin, Host, Port}) -> + HostPort = case lists:keyfind('Host', 1, Headers) of + {_, Value} -> Value; + _ -> + str:join([Host, + iolist_to_binary(integer_to_list(Port))], + <<":">>) + end, + [<<"HTTP/1.1 101 Web Socket Protocol Handshake\r\n">>, + <<"Upgrade: WebSocket\r\n">>, + <<"Connection: Upgrade\r\n">>, <<"WebSocket-Origin: ">>, + Origin, <<"\r\n">>, <<"WebSocket-Location: ws://">>, + HostPort, <<"/">>, str:join(Path, <<"/">>), + <<"\r\n\r\n">>]; +handshake({'draft-hybi', _}, _Sock, _SocketMod, Headers, + {_Path, _Q, _Origin, _Host, _Port}) -> + {_, Key} = lists:keyfind(<<"Sec-Websocket-Key">>, 1, + Headers), + Hash = jlib:encode_base64( + sha:sha1(<<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)), + [<<"HTTP/1.1 101 Switching Protocols\r\n">>, + <<"Upgrade: websocket\r\n">>, + <<"Connection: Upgrade\r\n">>, + <<"Sec-WebSocket-Accept: ">>, Hash, <<"\r\n\r\n">>]. -% Function: List -% Description: Builds the challenge for a handshake response. -% Code portions from Sergio Veiga <http://sergioveiga.com/index.php/2010/06/17/websocket-handshake-76-in-erlang/> -build_challenge({'draft-hixie', 0}, {Key1, Key2, Key3}) -> - Ikey1 = [D || D <- Key1, $0 =< D, D =< $9], - Ikey2 = [D || D <- Key2, $0 =< D, D =< $9], - Blank1 = length([D || D <- Key1, D =:= 32]), - Blank2 = length([D || D <- Key2, D =:= 32]), - Part1 = list_to_integer(Ikey1) div Blank1, - Part2 = list_to_integer(Ikey2) div Blank2, - Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Key3/binary>>, - erlang:md5(Ckey). - - -ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) -> - receive - {tcp, Socket, Data} -> - %?ERROR_MSG("[WS recv] ~p~n[Buffer state] ~p", [Data, Buffer]), - {NewHandlerState, ToSend} = handle_data(Vsn, HandlerState, Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit), - lists:foreach(fun(Pkt) -> - SocketMode:send(Socket, Pkt) - end, ToSend), - ws_loop(Vsn, NewHandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit); - {tcp_closed, Socket} -> - ?DEBUG("tcp connection was closed, exit", []), - % close websocket and custom controlling loop - websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit); - {'DOWN', Ref, process, WsHandleLoopPid, Reason} -> - case Reason of - normal -> - %?DEBUG("linked websocket controlling loop stopped.", []); - ok; - _ -> - ?ERROR_MSG("linked websocket controlling loop crashed with reason: ~p", [Reason]) +build_challenge({'draft-hixie', 0}, + {Key1, Key2, Key3}) -> + Ikey1 = << <<D>> || <<D>> <= Key1, $0 =< D, D =< $9>>, + Ikey2 = << <<D>> || <<D>> <= Key2, $0 =< D, D =< $9>>, + Blank1 = byte_size(<< <<D>> || <<D>> <= Key1, D =:= 32>>), + Blank2 = byte_size(<< <<D>> || <<D>> <= Key2, D =:= 32>>), + Part1 = jlib:binary_to_integer(Ikey1) div Blank1, + Part2 = jlib:binary_to_integer(Ikey2) div Blank2, + Ckey = <<Part1:4/big-unsigned-integer-unit:8, + Part2:4/big-unsigned-integer-unit:8, Key3/binary>>, + erlang:md5(Ckey). + +ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, + SocketMode, WsAutoExit) -> + receive + {tcp, Socket, Data} -> + {NewHandlerState, ToSend} = handle_data(Vsn, + HandlerState, Data, Socket, + WsHandleLoopPid, SocketMode, + WsAutoExit), + lists:foreach(fun (Pkt) -> SocketMode:send(Socket, Pkt) end, - % demonitor - erlang:demonitor(Ref), - % close websocket and custom controlling loop - websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit); - {send, Data} -> - %?DEBUG("sending data to websocket: ~p", [Data]), - SocketMode:send(Socket, encode_frame(Vsn, Data)), - ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit); - shutdown -> - ?DEBUG("shutdown request received, closing websocket with pid ~p", [self()]), - % close websocket and custom controlling loop - websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit); - _Ignored -> - ?WARNING_MSG("received unexpected message, ignoring: ~p", [_Ignored]), - ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) - end. + ToSend), + ws_loop(Vsn, NewHandlerState, Socket, WsHandleLoopPid, + SocketMode, WsAutoExit); + {tcp_closed, Socket} -> + ?DEBUG("tcp connection was closed, exit", []), + websocket_close(Socket, WsHandleLoopPid, SocketMode, + WsAutoExit); + {'DOWN', Ref, process, WsHandleLoopPid, Reason} -> + case Reason of + normal -> + %?DEBUG("linked websocket controlling loop stopped.", []); + ok; + _ -> + ?ERROR_MSG("linked websocket controlling loop crashed " + "with reason: ~p", + [Reason]) + end, + erlang:demonitor(Ref), + websocket_close(Socket, WsHandleLoopPid, SocketMode, + WsAutoExit); + {send, Data} -> + SocketMode:send(Socket, encode_frame(Vsn, Data)), + ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, + SocketMode, WsAutoExit); + shutdown -> + ?DEBUG("shutdown request received, closing websocket " + "with pid ~p", + [self()]), + websocket_close(Socket, WsHandleLoopPid, SocketMode, + WsAutoExit); + _Ignored -> + ?WARNING_MSG("received unexpected message, ignoring: ~p", + [_Ignored]), + ws_loop(Vsn, HandlerState, Socket, WsHandleLoopPid, + SocketMode, WsAutoExit) + end. encode_frame({'draft-hybi', _}, Data, Opcode) -> case byte_size(Data) of - S1 when S1 < 126 -> - <<1:1, 0:3, Opcode:4, 0:1, S1:7, Data/binary>>; - S2 when S2 < 65536 -> - <<1:1, 0:3, Opcode:4, 0:1, 126:7, S2:16, Data/binary>>; - S3 -> - <<1:1, 0:3, Opcode:4, 0:1, 127:7, S3:64, Data/binary>> + S1 when S1 < 126 -> + <<1:1, 0:3, Opcode:4, 0:1, S1:7, Data/binary>>; + S2 when S2 < 65536 -> + <<1:1, 0:3, Opcode:4, 0:1, 126:7, S2:16, Data/binary>>; + S3 -> + <<1:1, 0:3, Opcode:4, 0:1, 127:7, S3:64, Data/binary>> end. -encode_frame({'draft-hybi', _}=Vsn, Data) -> +encode_frame({'draft-hybi', _} = Vsn, Data) -> encode_frame(Vsn, Data, 1); -encode_frame(_, Data) -> - <<0, Data/binary, 255>>. +encode_frame(_, Data) -> <<0, Data/binary, 255>>. process_hixie_68(none, Data) -> process_hixie_68({false, <<>>}, Data); -process_hixie_68({false, <<>>}, <<0,T/binary>>) -> +process_hixie_68({false, <<>>}, <<0, T/binary>>) -> process_hixie_68({true, <<>>}, T); -process_hixie_68(L, <<>>) -> - {L, [], []}; -process_hixie_68({_, L}, <<255,T/binary>>) -> +process_hixie_68(L, <<>>) -> {L, [], []}; +process_hixie_68({_, L}, <<255, T/binary>>) -> {L2, Recv, Send} = process_hixie_68({false, <<>>}, T), - {L2, [L|Recv], Send}; + {L2, [L | Recv], Send}; process_hixie_68({true, L}, <<H/utf8, T/binary>>) -> process_hixie_68({true, <<L/binary, H>>}, T). --record(hybi_8_state, {mask=none, offset=0, left, final_frame=true, opcode, unprocessed = <<>>, unmasked = <<>>, unmasked_msg = <<>>}). +-record(hybi_8_state, + {mask = none, offset = 0, left, final_frame = true, + opcode, unprocessed = <<>>, unmasked = <<>>, + unmasked_msg = <<>>}). -decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, Len:7, Data/binary>>) when Len < 126 -> +decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, + Len:7, Data/binary>>) + when Len < 126 -> {Len, Final, Opcode, none, Data}; -decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, 126:7, Len:16/integer, Data/binary>>) -> +decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, + 126:7, Len:16/integer, Data/binary>>) -> {Len, Final, Opcode, none, Data}; -decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, 127:7, Len:64/integer, Data/binary>>) -> +decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 0:1, + 127:7, Len:64/integer, Data/binary>>) -> {Len, Final, Opcode, none, Data}; -decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, Len:7, Mask:4/binary, Data/binary>>) when Len < 126 -> +decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, + Len:7, Mask:4/binary, Data/binary>>) + when Len < 126 -> {Len, Final, Opcode, Mask, Data}; -decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, 126:7, Len:16/integer, Mask:4/binary, Data/binary>>) -> +decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, + 126:7, Len:16/integer, Mask:4/binary, Data/binary>>) -> {Len, Final, Opcode, Mask, Data}; -decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, 127:7, Len:64/integer, Mask:4/binary, Data/binary>>) -> +decode_hybi_8_header(<<Final:1, _:3, Opcode:4, 1:1, + 127:7, Len:64/integer, Mask:4/binary, Data/binary>>) -> {Len, Final, Opcode, Mask, Data}; -decode_hybi_8_header(_) -> - none. +decode_hybi_8_header(_) -> none. unmask_hybi_8_int(Offset, _, <<>>, Acc) -> {Acc, Offset}; -unmask_hybi_8_int(0, <<M:32>>=Mask, <<N:32, Rest/binary>>, Acc) -> - unmask_hybi_8_int(0, Mask, Rest, <<Acc/binary, (M bxor N):32>>); -unmask_hybi_8_int(0, <<M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) -> - unmask_hybi_8_int(1, Mask, Rest, <<Acc/binary, (M bxor N):8>>); -unmask_hybi_8_int(1, <<_:8, M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) -> - unmask_hybi_8_int(2, Mask, Rest, <<Acc/binary, (M bxor N):8>>); -unmask_hybi_8_int(2, <<_:16, M:8, _/binary>>=Mask, <<N:8, Rest/binary>>, Acc) -> - unmask_hybi_8_int(3, Mask, Rest, <<Acc/binary, (M bxor N):8>>); -unmask_hybi_8_int(3, <<_:24, M:8>>=Mask, <<N:8, Rest/binary>>, Acc) -> - unmask_hybi_8_int(0, Mask, Rest, <<Acc/binary, (M bxor N):8>>). +unmask_hybi_8_int(0, <<M:32>> = Mask, + <<N:32, Rest/binary>>, Acc) -> + unmask_hybi_8_int(0, Mask, Rest, + <<Acc/binary, (M bxor N):32>>); +unmask_hybi_8_int(0, <<M:8, _/binary>> = Mask, + <<N:8, Rest/binary>>, Acc) -> + unmask_hybi_8_int(1, Mask, Rest, + <<Acc/binary, (M bxor N):8>>); +unmask_hybi_8_int(1, <<_:8, M:8, _/binary>> = Mask, + <<N:8, Rest/binary>>, Acc) -> + unmask_hybi_8_int(2, Mask, Rest, + <<Acc/binary, (M bxor N):8>>); +unmask_hybi_8_int(2, <<_:16, M:8, _/binary>> = Mask, + <<N:8, Rest/binary>>, Acc) -> + unmask_hybi_8_int(3, Mask, Rest, + <<Acc/binary, (M bxor N):8>>); +unmask_hybi_8_int(3, <<_:24, M:8>> = Mask, + <<N:8, Rest/binary>>, Acc) -> + unmask_hybi_8_int(0, Mask, Rest, + <<Acc/binary, (M bxor N):8>>). -unmask_hybi_8(#hybi_8_state{mask=none}=State, Data) -> +unmask_hybi_8(#hybi_8_state{mask = none} = State, + Data) -> {State, Data}; -unmask_hybi_8(#hybi_8_state{mask=Mask, offset=Offset}=State, Data) -> - {Unmasked, NewOffset} = unmask_hybi_8_int(Offset, Mask, Data, <<>>), - {State#hybi_8_state{offset=NewOffset}, Unmasked}. - +unmask_hybi_8(#hybi_8_state{mask = Mask, + offset = Offset} = + State, + Data) -> + {Unmasked, NewOffset} = unmask_hybi_8_int(Offset, Mask, + Data, <<>>), + {State#hybi_8_state{offset = NewOffset}, Unmasked}. process_hybi_8(none, Data) -> process_hybi_8(#hybi_8_state{}, Data); -process_hybi_8(State, <<>>) -> - {State, [], []}; -process_hybi_8(#hybi_8_state{unprocessed=none, unmasked=UnmaskedPre, left=Left}=State, - Data) when byte_size(Data) < Left -> +process_hybi_8(State, <<>>) -> {State, [], []}; +process_hybi_8(#hybi_8_state{unprocessed = none, + unmasked = UnmaskedPre, left = Left} = + State, + Data) + when byte_size(Data) < Left -> {State2, Unmasked} = unmask_hybi_8(State, Data), - {State2#hybi_8_state{left=Left-byte_size(Data), unmasked=[UnmaskedPre, Unmasked]}, [], []}; -process_hybi_8(#hybi_8_state{unprocessed=none, unmasked=UnmaskedPre, opcode=Opcode, - final_frame=Final, left=Left, unmasked_msg=UnmaskedMsg}=State, Data) -> - {_State, Unmasked} = unmask_hybi_8(State, binary_part(Data, 0, Left)), - Unprocessed = binary_part(Data, Left, byte_size(Data)-Left), + {State2#hybi_8_state{left = Left - byte_size(Data), + unmasked = [UnmaskedPre, Unmasked]}, + [], []}; +process_hybi_8(#hybi_8_state{unprocessed = none, + unmasked = UnmaskedPre, opcode = Opcode, + final_frame = Final, left = Left, + unmasked_msg = UnmaskedMsg} = + State, + Data) -> + <<ToProcess:(Left)/binary, Unprocessed/binary>> = Data, + {_State, Unmasked} = unmask_hybi_8(State, ToProcess), case Final of - true -> - {State3, Recv, Send} = process_hybi_8(#hybi_8_state{}, Unprocessed), - case Opcode of - 9 -> - Frame = encode_frame({'draft-hybi', 8}, Unprocessed, 10), - {State3#hybi_8_state{unmasked_msg=UnmaskedMsg}, Recv, [Frame|Send]}; - X when X < 3 -> - {State3, [iolist_to_binary([UnmaskedMsg, UnmaskedPre, Unmasked])|Recv], Send}; - _ -> - {State3#hybi_8_state{unmasked_msg=UnmaskedMsg}, Recv, Send} - end; - _ -> - process_hybi_8(#hybi_8_state{unmasked_msg=[UnmaskedMsg, UnmaskedPre, Unmasked]}, Unprocessed) + true -> + {State3, Recv, Send} = process_hybi_8(#hybi_8_state{}, + Unprocessed), + case Opcode of + 9 -> + Frame = encode_frame({'draft-hybi', 8}, Unprocessed, + 10), + {State3#hybi_8_state{unmasked_msg = UnmaskedMsg}, Recv, + [Frame | Send]}; + X when X < 3 -> + {State3, + [iolist_to_binary([UnmaskedMsg, UnmaskedPre, Unmasked]) + | Recv], + Send}; + _ -> + {State3#hybi_8_state{unmasked_msg = UnmaskedMsg}, Recv, + Send} + end; + _ -> + process_hybi_8(#hybi_8_state{unmasked_msg = + [UnmaskedMsg, UnmaskedPre, + Unmasked]}, + Unprocessed) end; -process_hybi_8(#hybi_8_state{unprocessed= <<>>}=State, Data) -> +process_hybi_8(#hybi_8_state{unprocessed = <<>>} = + State, + Data) -> case decode_hybi_8_header(Data) of - none -> - {State#hybi_8_state{unprocessed=Data}, [], []}; - {Len, Final, Opcode, Mask, Rest} -> - process_hybi_8(State#hybi_8_state{mask=Mask, final_frame=Final==1, - left=Len, opcode=Opcode, - unprocessed=none}, Rest) + none -> + {State#hybi_8_state{unprocessed = Data}, [], []}; + {Len, Final, Opcode, Mask, Rest} -> + process_hybi_8(State#hybi_8_state{mask = Mask, + final_frame = Final == 1, + left = Len, opcode = Opcode, + unprocessed = none}, + Rest) end; -process_hybi_8(#hybi_8_state{unprocessed=UnprocessedPre}=State, Data) -> - process_hybi_8(State#hybi_8_state{unprocessed = <<>>}, <<UnprocessedPre/binary, Data/binary>>). +process_hybi_8(#hybi_8_state{unprocessed = + UnprocessedPre} = + State, + Data) -> + process_hybi_8(State#hybi_8_state{unprocessed = <<>>}, + <<UnprocessedPre/binary, Data/binary>>). - -% Buffering and data handling -handle_data({'draft-hybi', _}, State, Data, _Socket, WsHandleLoopPid, _SocketMode, _WsAutoExit) -> +handle_data({'draft-hybi', _}, State, Data, _Socket, + WsHandleLoopPid, _SocketMode, _WsAutoExit) -> {NewState, Recv, Send} = process_hybi_8(State, Data), - lists:foreach(fun(El) -> - WsHandleLoopPid ! {browser, El} - end, Recv), + lists:foreach(fun (El) -> + WsHandleLoopPid ! {browser, El} + end, + Recv), {NewState, Send}; -handle_data(_Vsn, State, Data, _Socket, WsHandleLoopPid, _SocketMode, _WsAutoExit) -> +handle_data(_Vsn, State, Data, _Socket, WsHandleLoopPid, + _SocketMode, _WsAutoExit) -> {NewState, Recv, Send} = process_hixie_68(State, Data), - lists:foreach(fun(El) -> - WsHandleLoopPid ! {browser, El} - end, Recv), - {NewState, Send}; -%% Invalid input -handle_data(_Vsn, _State, _Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) -> - websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit). + lists:foreach(fun (El) -> + WsHandleLoopPid ! {browser, El} + end, + Recv), + {NewState, Send}. -% Close socket and custom handling loop dependency -websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit) -> - case WsAutoExit of - true -> - % kill custom handling loop process - exit(WsHandleLoopPid, kill); - false -> - % the killing of the custom handling loop process is handled in the loop itself -> send event - WsHandleLoopPid ! closed - end, - % close main socket - SocketMode:close(Socket). +websocket_close(Socket, WsHandleLoopPid, SocketMode, + WsAutoExit) -> + case WsAutoExit of + true -> + % kill custom handling loop process + exit(WsHandleLoopPid, kill); + false -> WsHandleLoopPid ! closed + end, + SocketMode:close(Socket). diff --git a/src/web/ejabberd_ws.erl b/src/web/ejabberd_ws.erl index 5ccaadf34..2e47fe375 100644 --- a/src/web/ejabberd_ws.erl +++ b/src/web/ejabberd_ws.erl @@ -8,12 +8,12 @@ % MISULTIN - Websocket Request % % >-|-|-(°> -% +% % Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>. % All rights reserved. % % BSD License -% +% % Redistribution and use in source and binary forms, with or without modification, are permitted provided % that the following conditions are met: % @@ -34,6 +34,7 @@ % POSSIBILITY OF SUCH DAMAGE. % ========================================================================================================== -module(ejabberd_ws, [Ws, SocketPid]). + -vsn("0.6.1"). % API @@ -42,39 +43,24 @@ % includes -include("ejabberd_http.hrl"). - % ============================ \/ API ====================================================================== -% Description: Returns raw websocket content. -raw() -> - Ws. +raw() -> Ws. -% Description: Get websocket info. -get(socket) -> - Ws#ws.socket; -get(socket_mode) -> - Ws#ws.sockmod; -get(ip) -> - Ws#ws.ip; -get(vsn) -> - Ws#ws.vsn; -get(origin) -> - Ws#ws.origin; -get(host) -> - Ws#ws.host; -get(path) -> - Ws#ws.path; -get(headers) -> - Ws#ws.headers. - -% send data -send(Data) -> - SocketPid ! {send, Data}. - -% ============================ /\ API ====================================================================== +get(socket) -> Ws#ws.socket; +get(socket_mode) -> Ws#ws.sockmod; +get(ip) -> Ws#ws.ip; +get(vsn) -> Ws#ws.vsn; +get(origin) -> Ws#ws.origin; +get(host) -> Ws#ws.host; +get(path) -> Ws#ws.path; +get(headers) -> Ws#ws.headers. +send(Data) -> SocketPid ! {send, Data}. +% ============================ /\ API ====================================================================== % ============================ \/ INTERNAL FUNCTIONS ======================================================= % ============================ /\ INTERNAL FUNCTIONS ======================================================= + diff --git a/src/web/http_bind.hrl b/src/web/http_bind.hrl index 433703a5b..409f765a7 100644 --- a/src/web/http_bind.hrl +++ b/src/web/http_bind.hrl @@ -19,14 +19,29 @@ %%% %%%---------------------------------------------------------------------- --define(CT_XML, {"Content-Type", "text/xml; charset=utf-8"}). --define(CT_PLAIN, {"Content-Type", "text/plain"}). +-define(CT_XML, + {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). --define(AC_ALLOW_ORIGIN, {"Access-Control-Allow-Origin", "*"}). --define(AC_ALLOW_METHODS, {"Access-Control-Allow-Methods", "GET, POST, OPTIONS"}). --define(AC_ALLOW_HEADERS, {"Access-Control-Allow-Headers", "Content-Type"}). --define(AC_MAX_AGE, {"Access-Control-Max-Age", "86400"}). +-define(CT_PLAIN, + {<<"Content-Type">>, <<"text/plain">>}). --define(OPTIONS_HEADER, [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, - ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). --define(HEADER, [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]). +-define(AC_ALLOW_ORIGIN, + {<<"Access-Control-Allow-Origin">>, <<"*">>}). + +-define(AC_ALLOW_METHODS, + {<<"Access-Control-Allow-Methods">>, + <<"GET, POST, OPTIONS">>}). + +-define(AC_ALLOW_HEADERS, + {<<"Access-Control-Allow-Headers">>, + <<"Content-Type">>}). + +-define(AC_MAX_AGE, + {<<"Access-Control-Max-Age">>, <<"86400">>}). + +-define(OPTIONS_HEADER, + [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, + ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). + +-define(HEADER, + [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]). diff --git a/src/web/mochijson2.erl b/src/web/mochijson2.erl deleted file mode 100644 index 710ae9bce..000000000 --- a/src/web/mochijson2.erl +++ /dev/null @@ -1,782 +0,0 @@ -%% @author Bob Ippolito <bob@mochimedia.com> -%% @copyright 2007 Mochi Media, Inc. - -%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works -%% with binaries as strings, arrays as lists (without an {array, _}) -%% wrapper and it only knows how to decode UTF-8 (and ASCII). - --module(mochijson2). --author('bob@mochimedia.com'). --export([encoder/1, encode/1]). --export([decoder/1, decode/1]). - -% This is a macro to placate syntax highlighters.. --define(Q, $\"). --define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, - column=N+S#decoder.column}). --define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, - column=1+S#decoder.column}). --define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, - column=1, - line=1+S#decoder.line}). --define(INC_CHAR(S, C), - case C of - $\n -> - S#decoder{column=1, - line=1+S#decoder.line, - offset=1+S#decoder.offset}; - _ -> - S#decoder{column=1+S#decoder.column, - offset=1+S#decoder.offset} - end). --define(IS_WHITESPACE(C), - (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). - -%% @type iolist() = [char() | binary() | iolist()] -%% @type iodata() = iolist() | binary() -%% @type json_string() = atom | binary() -%% @type json_number() = integer() | float() -%% @type json_array() = [json_term()] -%% @type json_object() = {struct, [{json_string(), json_term()}]} -%% @type json_iolist() = {json, iolist()} -%% @type json_term() = json_string() | json_number() | json_array() | -%% json_object() | json_iolist() - --record(encoder, {handler=null, - utf8=false}). - --record(decoder, {object_hook=null, - offset=0, - line=1, - column=1, - state=null}). - -%% @spec encoder([encoder_option()]) -> function() -%% @doc Create an encoder/1 with the given options. -%% @type encoder_option() = handler_option() | utf8_option() -%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false) -encoder(Options) -> - State = parse_encoder_options(Options, #encoder{}), - fun (O) -> json_encode(O, State) end. - -%% @spec encode(json_term()) -> iolist() -%% @doc Encode the given as JSON to an iolist. -encode(Any) -> - json_encode(Any, #encoder{}). - -%% @spec decoder([decoder_option()]) -> function() -%% @doc Create a decoder/1 with the given options. -decoder(Options) -> - State = parse_decoder_options(Options, #decoder{}), - fun (O) -> json_decode(O, State) end. - -%% @spec decode(iolist()) -> json_term() -%% @doc Decode the given iolist to Erlang terms. -decode(S) -> - json_decode(S, #decoder{}). - -%% Internal API - -parse_encoder_options([], State) -> - State; -parse_encoder_options([{handler, Handler} | Rest], State) -> - parse_encoder_options(Rest, State#encoder{handler=Handler}); -parse_encoder_options([{utf8, Switch} | Rest], State) -> - parse_encoder_options(Rest, State#encoder{utf8=Switch}). - -parse_decoder_options([], State) -> - State; -parse_decoder_options([{object_hook, Hook} | Rest], State) -> - parse_decoder_options(Rest, State#decoder{object_hook=Hook}). - -json_encode(true, _State) -> - <<"true">>; -json_encode(false, _State) -> - <<"false">>; -json_encode(null, _State) -> - <<"null">>; -json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 -> - %% Anything outside of 32-bit integers should be encoded as a float - integer_to_list(I); -json_encode(I, _State) when is_integer(I) -> - mochinum:digits(float(I)); -json_encode(F, _State) when is_float(F) -> - mochinum:digits(F); -json_encode(S, State) when is_binary(S); is_atom(S) -> - json_encode_string(S, State); -json_encode(Array, State) when is_list(Array) -> - json_encode_array(Array, State); -json_encode({struct, Props}, State) when is_list(Props) -> - json_encode_proplist(Props, State); -json_encode({json, IoList}, _State) -> - IoList; -json_encode(Bad, #encoder{handler=null}) -> - exit({json_encode, {bad_term, Bad}}); -json_encode(Bad, State=#encoder{handler=Handler}) -> - json_encode(Handler(Bad), State). - -json_encode_array([], _State) -> - <<"[]">>; -json_encode_array(L, State) -> - F = fun (O, Acc) -> - [$,, json_encode(O, State) | Acc] - end, - [$, | Acc1] = lists:foldl(F, "[", L), - lists:reverse([$\] | Acc1]). - -json_encode_proplist([], _State) -> - <<"{}">>; -json_encode_proplist(Props, State) -> - F = fun ({K, V}, Acc) -> - KS = json_encode_string(K, State), - VS = json_encode(V, State), - [$,, VS, $:, KS | Acc] - end, - [$, | Acc1] = lists:foldl(F, "{", Props), - lists:reverse([$\} | Acc1]). - -json_encode_string(A, State) when is_atom(A) -> - L = atom_to_list(A), - case json_string_is_safe(L) of - true -> - [?Q, L, ?Q]; - false -> - json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) - end; -json_encode_string(B, State) when is_binary(B) -> - case json_bin_is_safe(B) of - true -> - [?Q, B, ?Q]; - false -> - json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) - end; -json_encode_string(I, _State) when is_integer(I) -> - [?Q, integer_to_list(I), ?Q]; -json_encode_string(L, State) when is_list(L) -> - case json_string_is_safe(L) of - true -> - [?Q, L, ?Q]; - false -> - json_encode_string_unicode(L, State, [?Q]) - end. - -json_string_is_safe([]) -> - true; -json_string_is_safe([C | Rest]) -> - case C of - ?Q -> - false; - $\\ -> - false; - $\b -> - false; - $\f -> - false; - $\n -> - false; - $\r -> - false; - $\t -> - false; - C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> - false; - C when C < 16#7f -> - json_string_is_safe(Rest); - _ -> - false - end. - -json_bin_is_safe(<<>>) -> - true; -json_bin_is_safe(<<C, Rest/binary>>) -> - case C of - ?Q -> - false; - $\\ -> - false; - $\b -> - false; - $\f -> - false; - $\n -> - false; - $\r -> - false; - $\t -> - false; - C when C >= 0, C < $\s; C >= 16#7f -> - false; - C when C < 16#7f -> - json_bin_is_safe(Rest) - end. - -json_encode_string_unicode([], _State, Acc) -> - lists:reverse([$\" | Acc]); -json_encode_string_unicode([C | Cs], State, Acc) -> - Acc1 = case C of - ?Q -> - [?Q, $\\ | Acc]; - %% Escaping solidus is only useful when trying to protect - %% against "</script>" injection attacks which are only - %% possible when JSON is inserted into a HTML document - %% in-line. mochijson2 does not protect you from this, so - %% if you do insert directly into HTML then you need to - %% uncomment the following case or escape the output of encode. - %% - %% $/ -> - %% [$/, $\\ | Acc]; - %% - $\\ -> - [$\\, $\\ | Acc]; - $\b -> - [$b, $\\ | Acc]; - $\f -> - [$f, $\\ | Acc]; - $\n -> - [$n, $\\ | Acc]; - $\r -> - [$r, $\\ | Acc]; - $\t -> - [$t, $\\ | Acc]; - C when C >= 0, C < $\s -> - [unihex(C) | Acc]; - C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> - [xmerl_ucs:to_utf8(C) | Acc]; - C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> - [unihex(C) | Acc]; - C when C < 16#7f -> - [C | Acc]; - _ -> - exit({json_encode, {bad_char, C}}) - end, - json_encode_string_unicode(Cs, State, Acc1). - -hexdigit(C) when C >= 0, C =< 9 -> - C + $0; -hexdigit(C) when C =< 15 -> - C + $a - 10. - -unihex(C) when C < 16#10000 -> - <<D3:4, D2:4, D1:4, D0:4>> = <<C:16>>, - Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], - [$\\, $u | Digits]; -unihex(C) when C =< 16#10FFFF -> - N = C - 16#10000, - S1 = 16#d800 bor ((N bsr 10) band 16#3ff), - S2 = 16#dc00 bor (N band 16#3ff), - [unihex(S1), unihex(S2)]. - -json_decode(L, S) when is_list(L) -> - json_decode(iolist_to_binary(L), S); -json_decode(B, S) -> - {Res, S1} = decode1(B, S), - {eof, _} = tokenize(B, S1#decoder{state=trim}), - Res. - -decode1(B, S=#decoder{state=null}) -> - case tokenize(B, S#decoder{state=any}) of - {{const, C}, S1} -> - {C, S1}; - {start_array, S1} -> - decode_array(B, S1); - {start_object, S1} -> - decode_object(B, S1) - end. - -make_object(V, #decoder{object_hook=null}) -> - V; -make_object(V, #decoder{object_hook=Hook}) -> - Hook(V). - -decode_object(B, S) -> - decode_object(B, S#decoder{state=key}, []). - -decode_object(B, S=#decoder{state=key}, Acc) -> - case tokenize(B, S) of - {end_object, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, S1#decoder{state=null}}; - {{const, K}, S1} -> - {colon, S2} = tokenize(B, S1), - {V, S3} = decode1(B, S2#decoder{state=null}), - decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) - end; -decode_object(B, S=#decoder{state=comma}, Acc) -> - case tokenize(B, S) of - {end_object, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, S1#decoder{state=null}}; - {comma, S1} -> - decode_object(B, S1#decoder{state=key}, Acc) - end. - -decode_array(B, S) -> - decode_array(B, S#decoder{state=any}, []). - -decode_array(B, S=#decoder{state=any}, Acc) -> - case tokenize(B, S) of - {end_array, S1} -> - {lists:reverse(Acc), S1#decoder{state=null}}; - {start_array, S1} -> - {Array, S2} = decode_array(B, S1), - decode_array(B, S2#decoder{state=comma}, [Array | Acc]); - {start_object, S1} -> - {Array, S2} = decode_object(B, S1), - decode_array(B, S2#decoder{state=comma}, [Array | Acc]); - {{const, Const}, S1} -> - decode_array(B, S1#decoder{state=comma}, [Const | Acc]) - end; -decode_array(B, S=#decoder{state=comma}, Acc) -> - case tokenize(B, S) of - {end_array, S1} -> - {lists:reverse(Acc), S1#decoder{state=null}}; - {comma, S1} -> - decode_array(B, S1#decoder{state=any}, Acc) - end. - -tokenize_string(B, S=#decoder{offset=O}) -> - case tokenize_string_fast(B, O) of - {escape, O1} -> - Length = O1 - O, - S1 = ?ADV_COL(S, Length), - <<_:O/binary, Head:Length/binary, _/binary>> = B, - tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); - O1 -> - Length = O1 - O, - <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, - {{const, String}, ?ADV_COL(S, Length + 1)} - end. - -tokenize_string_fast(B, O) -> - case B of - <<_:O/binary, ?Q, _/binary>> -> - O; - <<_:O/binary, $\\, _/binary>> -> - {escape, O}; - <<_:O/binary, C1, _/binary>> when C1 < 128 -> - tokenize_string_fast(B, 1 + O); - <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, - C2 >= 128, C2 =< 191 -> - tokenize_string_fast(B, 2 + O); - <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, - C2 >= 128, C2 =< 191, - C3 >= 128, C3 =< 191 -> - tokenize_string_fast(B, 3 + O); - <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, - C2 >= 128, C2 =< 191, - C3 >= 128, C3 =< 191, - C4 >= 128, C4 =< 191 -> - tokenize_string_fast(B, 4 + O); - _ -> - throw(invalid_utf8) - end. - -tokenize_string(B, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, ?Q, _/binary>> -> - {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; - <<_:O/binary, "\\\"", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); - <<_:O/binary, "\\\\", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); - <<_:O/binary, "\\/", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); - <<_:O/binary, "\\b", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); - <<_:O/binary, "\\f", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); - <<_:O/binary, "\\n", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); - <<_:O/binary, "\\r", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); - <<_:O/binary, "\\t", _/binary>> -> - tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); - <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> - C = erlang:list_to_integer([C3, C2, C1, C0], 16), - if C > 16#D7FF, C < 16#DC00 -> - %% coalesce UTF-16 surrogate pair - <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, - D = erlang:list_to_integer([D3,D2,D1,D0], 16), - [CodePoint] = xmerl_ucs:from_utf16be(<<C:16/big-unsigned-integer, - D:16/big-unsigned-integer>>), - Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), - tokenize_string(B, ?ADV_COL(S, 12), Acc1); - true -> - Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), - tokenize_string(B, ?ADV_COL(S, 6), Acc1) - end; - <<_:O/binary, C, _/binary>> -> - tokenize_string(B, ?INC_CHAR(S, C), [C | Acc]) - end. - -tokenize_number(B, S) -> - case tokenize_number(B, sign, S, []) of - {{int, Int}, S1} -> - {{const, list_to_integer(Int)}, S1}; - {{float, Float}, S1} -> - {{const, list_to_float(Float)}, S1} - end. - -tokenize_number(B, sign, S=#decoder{offset=O}, []) -> - case B of - <<_:O/binary, $-, _/binary>> -> - tokenize_number(B, int, ?INC_COL(S), [$-]); - _ -> - tokenize_number(B, int, S, []) - end; -tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, $0, _/binary>> -> - tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); - <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> - tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) - end; -tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); - _ -> - tokenize_number(B, frac, S, Acc) - end; -tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> - tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); - <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> - tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); - _ -> - {{int, lists:reverse(Acc)}, S} - end; -tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); - <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> - tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); - _ -> - {{float, lists:reverse(Acc)}, S} - end; -tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> - tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); - _ -> - tokenize_number(B, eint, S, Acc) - end; -tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) - end; -tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> - case B of - <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> - tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); - _ -> - {{float, lists:reverse(Acc)}, S} - end. - -tokenize(B, S=#decoder{offset=O}) -> - case B of - <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> - tokenize(B, ?INC_CHAR(S, C)); - <<_:O/binary, "{", _/binary>> -> - {start_object, ?INC_COL(S)}; - <<_:O/binary, "}", _/binary>> -> - {end_object, ?INC_COL(S)}; - <<_:O/binary, "[", _/binary>> -> - {start_array, ?INC_COL(S)}; - <<_:O/binary, "]", _/binary>> -> - {end_array, ?INC_COL(S)}; - <<_:O/binary, ",", _/binary>> -> - {comma, ?INC_COL(S)}; - <<_:O/binary, ":", _/binary>> -> - {colon, ?INC_COL(S)}; - <<_:O/binary, "null", _/binary>> -> - {{const, null}, ?ADV_COL(S, 4)}; - <<_:O/binary, "true", _/binary>> -> - {{const, true}, ?ADV_COL(S, 4)}; - <<_:O/binary, "false", _/binary>> -> - {{const, false}, ?ADV_COL(S, 5)}; - <<_:O/binary, "\"", _/binary>> -> - tokenize_string(B, ?INC_COL(S)); - <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) - orelse C =:= $- -> - tokenize_number(B, S); - <<_:O/binary>> -> - trim = S#decoder.state, - {eof, S} - end. -%% -%% Tests -%% --include_lib("eunit/include/eunit.hrl"). --ifdef(TEST). - - -%% testing constructs borrowed from the Yaws JSON implementation. - -%% Create an object from a list of Key/Value pairs. - -obj_new() -> - {struct, []}. - -is_obj({struct, Props}) -> - F = fun ({K, _}) when is_binary(K) -> true end, - lists:all(F, Props). - -obj_from_list(Props) -> - Obj = {struct, Props}, - ?assert(is_obj(Obj)), - Obj. - -%% Test for equivalence of Erlang terms. -%% Due to arbitrary order of construction, equivalent objects might -%% compare unequal as erlang terms, so we need to carefully recurse -%% through aggregates (tuples and objects). - -equiv({struct, Props1}, {struct, Props2}) -> - equiv_object(Props1, Props2); -equiv(L1, L2) when is_list(L1), is_list(L2) -> - equiv_list(L1, L2); -equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; -equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; -equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. - -%% Object representation and traversal order is unknown. -%% Use the sledgehammer and sort property lists. - -equiv_object(Props1, Props2) -> - L1 = lists:keysort(1, Props1), - L2 = lists:keysort(1, Props2), - Pairs = lists:zip(L1, L2), - true = lists:all(fun({{K1, V1}, {K2, V2}}) -> - equiv(K1, K2) and equiv(V1, V2) - end, Pairs). - -%% Recursively compare tuple elements for equivalence. - -equiv_list([], []) -> - true; -equiv_list([V1 | L1], [V2 | L2]) -> - equiv(V1, V2) andalso equiv_list(L1, L2). - -decode_test() -> - [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), - <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). - -e2j_vec_test() -> - test_one(e2j_test_vec(utf8), 1). - -test_one([], _N) -> - %% io:format("~p tests passed~n", [N-1]), - ok; -test_one([{E, J} | Rest], N) -> - %% io:format("[~p] ~p ~p~n", [N, E, J]), - true = equiv(E, decode(J)), - true = equiv(E, decode(encode(E))), - test_one(Rest, 1+N). - -e2j_test_vec(utf8) -> - [ - {1, "1"}, - {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes - {-1, "-1"}, - {-3.1416, "-3.14160"}, - {12.0e10, "1.20000e+11"}, - {1.234E+10, "1.23400e+10"}, - {-1.234E-10, "-1.23400e-10"}, - {10.0, "1.0e+01"}, - {123.456, "1.23456E+2"}, - {10.0, "1e1"}, - {<<"foo">>, "\"foo\""}, - {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, - {<<"">>, "\"\""}, - {<<"\n\n\n">>, "\"\\n\\n\\n\""}, - {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, - {obj_new(), "{}"}, - {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, - {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), - "{\"foo\":\"bar\",\"baz\":123}"}, - {[], "[]"}, - {[[]], "[[]]"}, - {[1, <<"foo">>], "[1,\"foo\"]"}, - - %% json array in a json object - {obj_from_list([{<<"foo">>, [123]}]), - "{\"foo\":[123]}"}, - - %% json object in a json object - {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), - "{\"foo\":{\"bar\":true}}"}, - - %% fold evaluation order - {obj_from_list([{<<"foo">>, []}, - {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, - {<<"alice">>, <<"bob">>}]), - "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, - - %% json object in a json array - {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], - "[-123,\"foo\",{\"bar\":[]},null]"} - ]. - -%% test utf8 encoding -encoder_utf8_test() -> - %% safe conversion case (default) - [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = - encode(<<1,"\321\202\320\265\321\201\321\202">>), - - %% raw utf8 output (optional) - Enc = mochijson2:encoder([{utf8, true}]), - [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = - Enc(<<1,"\321\202\320\265\321\201\321\202">>). - -input_validation_test() -> - Good = [ - {16#00A3, <<?Q, 16#C2, 16#A3, ?Q>>}, %% pound - {16#20AC, <<?Q, 16#E2, 16#82, 16#AC, ?Q>>}, %% euro - {16#10196, <<?Q, 16#F0, 16#90, 16#86, 16#96, ?Q>>} %% denarius - ], - lists:foreach(fun({CodePoint, UTF8}) -> - Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), - Expect = decode(UTF8) - end, Good), - - Bad = [ - %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte - <<?Q, 16#80, ?Q>>, - %% missing continuations, last byte in each should be 80-BF - <<?Q, 16#C2, 16#7F, ?Q>>, - <<?Q, 16#E0, 16#80,16#7F, ?Q>>, - <<?Q, 16#F0, 16#80, 16#80, 16#7F, ?Q>>, - %% we don't support code points > 10FFFF per RFC 3629 - <<?Q, 16#F5, 16#80, 16#80, 16#80, ?Q>> - ], - lists:foreach( - fun(X) -> - ok = try decode(X) catch invalid_utf8 -> ok end, - %% could be {ucs,{bad_utf8_character_code}} or - %% {json_encode,{bad_char,_}} - {'EXIT', _} = (catch encode(X)) - end, Bad). - -inline_json_test() -> - ?assertEqual(<<"\"iodata iodata\"">>, - iolist_to_binary( - encode({json, [<<"\"iodata">>, " iodata\""]}))), - ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, - decode( - encode({struct, - [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), - ok. - -big_unicode_test() -> - UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), - ?assertEqual( - <<"\"\\ud834\\udd20\"">>, - iolist_to_binary(encode(UTF8Seq))), - ?assertEqual( - UTF8Seq, - decode(iolist_to_binary(encode(UTF8Seq)))), - ok. - -custom_decoder_test() -> - ?assertEqual( - {struct, [{<<"key">>, <<"value">>}]}, - (decoder([]))("{\"key\": \"value\"}")), - F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, - ?assertEqual( - win, - (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), - ok. - -atom_test() -> - %% JSON native atoms - [begin - ?assertEqual(A, decode(atom_to_list(A))), - ?assertEqual(iolist_to_binary(atom_to_list(A)), - iolist_to_binary(encode(A))) - end || A <- [true, false, null]], - %% Atom to string - ?assertEqual( - <<"\"foo\"">>, - iolist_to_binary(encode(foo))), - ?assertEqual( - <<"\"\\ud834\\udd20\"">>, - iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), - ok. - -key_encode_test() -> - %% Some forms are accepted as keys that would not be strings in other - %% cases - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode({struct, [{foo, 1}]}))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), - ?assertEqual( - <<"{\"foo\":1}">>, - iolist_to_binary(encode({struct, [{"foo", 1}]}))), - ?assertEqual( - <<"{\"\\ud834\\udd20\":1}">>, - iolist_to_binary( - encode({struct, [{[16#0001d120], 1}]}))), - ?assertEqual( - <<"{\"1\":1}">>, - iolist_to_binary(encode({struct, [{1, 1}]}))), - ok. - -unsafe_chars_test() -> - Chars = "\"\\\b\f\n\r\t", - [begin - ?assertEqual(false, json_string_is_safe([C])), - ?assertEqual(false, json_bin_is_safe(<<C>>)), - ?assertEqual(<<C>>, decode(encode(<<C>>))) - end || C <- Chars], - ?assertEqual( - false, - json_string_is_safe([16#0001d120])), - ?assertEqual( - false, - json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), - ?assertEqual( - [16#0001d120], - xmerl_ucs:from_utf8( - binary_to_list( - decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), - ?assertEqual( - false, - json_string_is_safe([16#110000])), - ?assertEqual( - false, - json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), - %% solidus can be escaped but isn't unsafe by default - ?assertEqual( - <<"/">>, - decode(<<"\"\\/\"">>)), - ok. - -int_test() -> - ?assertEqual(0, decode("0")), - ?assertEqual(1, decode("1")), - ?assertEqual(11, decode("11")), - ok. - -float_fallback_test() -> - ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649))), - ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648))), - ok. - -handler_test() -> - ?assertEqual( - {'EXIT',{json_encode,{bad_term,{}}}}, - catch encode({})), - F = fun ({}) -> [] end, - ?assertEqual( - <<"[]">>, - iolist_to_binary((encoder([{handler, F}]))({}))), - ok. - --endif.
\ No newline at end of file diff --git a/src/web/mod_bosh.erl b/src/web/mod_bosh.erl index 64b85357b..70f8fce05 100644 --- a/src/web/mod_bosh.erl +++ b/src/web/mod_bosh.erl @@ -26,144 +26,139 @@ %%% %%%------------------------------------------------------------------- -module(mod_bosh). + -author('steve@zeank.in-berlin.de'). %%-define(ejabberd_debug, true). -behaviour(gen_mod). --export([ - start/2, - stop/1, - process/2, - open_session/2, - close_session/1, - find_session/1, - node_up/1, - node_down/1, - migrate/3 - ]). +-export([start/2, stop/1, process/2, open_session/2, + close_session/1, find_session/1, node_up/1, node_down/1, + migrate/3]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("bosh.hrl"). --record(bosh, {sid, pid}). +-record(bosh, {sid = <<"">> :: binary() | '$1', + pid = self() :: pid() | '$2'}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -process([], #request{method = 'POST', - data = []}) -> +process([], #request{method = 'POST', data = <<>>}) -> ?DEBUG("Bad Request: no data", []), - {400, ?HEADER, {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}; -process([], #request{method = 'POST', - data = Data, - ip = IP}) -> - ?DEBUG("Incoming data: ~s", [Data]), - ejabberd_bosh:process_request(Data, IP); -process([], #request{method = 'GET', - data = []}) -> - {200, ?HEADER, get_human_html_xmlel()}; -process([], #request{method = 'OPTIONS', - data = []}) -> + {400, ?HEADER(?CT_XML), + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}; +process([], + #request{method = 'POST', data = Data, ip = IP, headers = Hdrs}) -> + ?DEBUG("Incoming data: ~p", [Data]), + Type = get_type(Hdrs), + ejabberd_bosh:process_request(Data, IP, Type); +process([], #request{method = 'GET', data = <<>>}) -> + {200, ?HEADER(?CT_XML), get_human_html_xmlel()}; +process([], #request{method = 'OPTIONS', data = <<>>}) -> {200, ?OPTIONS_HEADER, []}; process(_Path, _Request) -> ?DEBUG("Bad Request: ~p", [_Request]), - {400, ?HEADER, {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}. + {400, ?HEADER(?CT_XML), + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}. get_human_html_xmlel() -> - Heading = "ejabberd " ++ atom_to_list(?MODULE), - {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}], - [{xmlelement, "head", [], - [{xmlelement, "title", [], [{xmlcdata, Heading}]}]}, - {xmlelement, "body", [], - [{xmlelement, "h1", [], [{xmlcdata, Heading}]}, - {xmlelement, "p", [], - [{xmlcdata, "An implementation of "}, - {xmlelement, "a", - [{"href", "http://xmpp.org/extensions/xep-0206.html"}], - [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]}, - {xmlelement, "p", [], - [{xmlcdata, "This web page is only informative. " - "To use HTTP-Bind you need a Jabber/XMPP client that supports it."} - ]} - ]}]}. + Heading = <<"ejabberd ", (jlib:atom_to_binary(?MODULE))/binary>>, + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], + children = + [#xmlel{name = <<"head">>, attrs = [], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, Heading}]}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [#xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, Heading}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, <<"An implementation of ">>}, + #xmlel{name = <<"a">>, + attrs = + [{<<"href">>, + <<"http://xmpp.org/extensions/xep-0206.html">>}], + children = + [{xmlcdata, + <<"XMPP over BOSH (XEP-0206)">>}]}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, + <<"This web page is only informative. To " + "use HTTP-Bind you need a Jabber/XMPP " + "client that supports it.">>}]}]}]}. open_session(SID, Pid) -> mnesia:dirty_write(#bosh{sid = SID, pid = Pid}). -close_session(SID) -> - mnesia:dirty_delete(bosh, SID). +close_session(SID) -> mnesia:dirty_delete(bosh, SID). find_session(SID) -> Node = ejabberd_cluster:get_node(SID), - case rpc:call(Node, mnesia, dirty_read, [bosh, SID], 5000) of - [#bosh{pid = Pid}] -> - {ok, Pid}; - _ -> - error + case rpc:call(Node, mnesia, dirty_read, [bosh, SID], + 5000) + of + [#bosh{pid = Pid}] -> {ok, Pid}; + _ -> error end. migrate(_Node, _UpOrDown, After) -> - Rs = mnesia:dirty_select( - bosh, - [{#bosh{sid = '$1', pid = '$2', _ = '_'}, - [], - ['$$']}]), - lists:foreach( - fun([SID, Pid]) -> - case ejabberd_cluster:get_node(SID) of - Node when Node /= node() -> - ejabberd_bosh:migrate(Pid, Node, random:uniform(After)); - _ -> - ok - end - end, Rs). + Rs = mnesia:dirty_select(bosh, + [{#bosh{sid = '$1', pid = '$2', _ = '_'}, [], + ['$$']}]), + lists:foreach(fun ([SID, Pid]) -> + case ejabberd_cluster:get_node(SID) of + Node when Node /= node() -> + ejabberd_bosh:migrate(Pid, Node, + random:uniform(After)); + _ -> ok + end + end, + Rs). node_up(_Node) -> copy_entries(mnesia:dirty_first(bosh)). node_down(Node) when Node == node() -> copy_entries(mnesia:dirty_first(bosh)); -node_down(_) -> - ok. +node_down(_) -> ok. -copy_entries('$end_of_table') -> - ok; +copy_entries('$end_of_table') -> ok; copy_entries(Key) -> case mnesia:dirty_read(bosh, Key) of - [#bosh{sid = SID} = Entry] -> - case ejabberd_cluster:get_node_new(SID) of - Node when node() /= Node -> - rpc:cast(Node, mnesia, dirty_write, [Entry]); - _ -> - ok - end; - _ -> - ok + [#bosh{sid = SID} = Entry] -> + case ejabberd_cluster:get_node_new(SID) of + Node when node() /= Node -> + rpc:cast(Node, mnesia, dirty_write, [Entry]); + _ -> ok + end; + _ -> ok end, copy_entries(mnesia:dirty_next(bosh, Key)). -%%%---------------------------------------------------------------------- -%%% BEHAVIOUR CALLBACKS -%%%---------------------------------------------------------------------- -start(Host, _Opts) -> +start(Host, Opts) -> start_hook_handler(), setup_database(), + start_jiffy(Opts), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, ejabberd_bosh]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, + ChildSpec = {Proc, + {ejabberd_tmp_sup, start_link, [Proc, ejabberd_bosh]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop(Host) -> @@ -173,40 +168,63 @@ stop(Host) -> setup_database() -> mnesia:create_table(bosh, - [{ram_copies, [node()]}, - {local_content, true}, - {attributes, record_info(fields, bosh)}]), + [{ram_copies, [node()]}, {local_content, true}, + {attributes, record_info(fields, bosh)}]), mnesia:add_table_copy(bosh, node(), ram_copies). -start_hook_handler() -> - %% HACK: We need this in order to avoid - %% hooks processing more than once - %% when many vhosts are involved - %% TODO: get rid of this stuff - spawn(fun hook_handler/0). +start_jiffy(Opts) -> + case gen_mod:get_opt(json, Opts, + fun(false) -> false; + (true) -> true + end, false) of + false -> + ok; + true -> + case application:start(jiffy) of + ok -> + ok; + {error, {already_started, _}} -> + ok; + Err -> + ?WARNING_MSG("Failed to start JSON codec (jiffy): ~p. " + "JSON support will be disabled", [Err]) + end + end. + +start_hook_handler() -> spawn(fun hook_handler/0). hook_handler() -> - case catch register(ejabberd_bosh_hook_handler, self()) of - true -> - ejabberd_hooks:add(node_up, ?MODULE, node_up, 100), - ejabberd_hooks:add(node_down, ?MODULE, node_down, 100), - ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100), - %% Stop if ejabberd_sup goes down - %% (i.e. the whole ejabberd goes down) - MRef = erlang:monitor(process, ejabberd_sup), - hook_handler_loop(MRef); - _ -> - ok + case catch register(ejabberd_bosh_hook_handler, self()) + of + true -> + ejabberd_hooks:add(node_up, ?MODULE, node_up, 100), + ejabberd_hooks:add(node_down, ?MODULE, node_down, 100), + ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, + 100), + MRef = erlang:monitor(process, ejabberd_sup), + hook_handler_loop(MRef); + _ -> ok end. hook_handler_loop(MRef) -> receive - {'DOWN', MRef, _Type, _Object, _Info} -> - %% Unregister the hooks. I think this is useless, thus 'catch' - catch ejabberd_hooks:delete(node_up, ?MODULE, node_up, 100), - catch ejabberd_hooks:delete(node_down, ?MODULE, node_down, 100), - catch ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100), - ok; - _ -> - hook_handler_loop(MRef) + {'DOWN', MRef, _Type, _Object, _Info} -> + catch ejabberd_hooks:delete(node_up, ?MODULE, node_up, + 100), + catch ejabberd_hooks:delete(node_down, ?MODULE, + node_down, 100), + catch ejabberd_hooks:delete(node_hash_update, ?MODULE, + migrate, 100), + ok; + _ -> hook_handler_loop(MRef) + end. + +get_type(Hdrs) -> + try + {_, S} = lists:keyfind('Content-Type', 1, Hdrs), + [T|_] = str:tokens(S, <<";">>), + [_, <<"json">>] = str:tokens(T, <<"/">>), + json + catch _:_ -> + xml end. diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl index 341e9fa7d..c7de9c329 100644 --- a/src/web/mod_http_bind.erl +++ b/src/web/mod_http_bind.erl @@ -31,88 +31,90 @@ %%%---------------------------------------------------------------------- -module(mod_http_bind). + -author('steve@zeank.in-berlin.de'). %%-define(ejabberd_debug, true). -behaviour(gen_mod). --export([ - start/2, - stop/1, - process/2 - ]). +-export([start/2, stop/1, process/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("http_bind.hrl"). -define(PROCNAME_MHB, ejabberd_mod_http_bind). -%% Duplicated from ejabberd_http_bind. -%% TODO: move to hrl file. --record(http_bind, {id, pid, to, hold, wait, process_delay, version}). +-record(http_bind, + {id, pid, to, hold, wait, process_delay, version}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -process([], #request{method = 'POST', - data = []}) -> +process([], #request{method = 'POST', data = <<>>}) -> ?DEBUG("Bad Request: no data", []), - {400, ?HEADER, {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}; -process([], #request{method = 'POST', - data = Data, - ip = IP}) -> + {400, ?HEADER, + #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}; +process([], + #request{method = 'POST', data = Data, ip = IP}) -> ?DEBUG("Incoming data: ~s", [Data]), ejabberd_http_bind:process_request(Data, IP); -process([], #request{method = 'GET', - data = []}) -> +process([], #request{method = 'GET', data = <<>>}) -> {200, ?HEADER, get_human_html_xmlel()}; -process([], #request{method = 'OPTIONS', - data = []}) -> - {200, ?OPTIONS_HEADER, []}; +process([], #request{method = 'OPTIONS', data = <<>>}) -> + {200, ?OPTIONS_HEADER, <<>>}; process([], #request{method = 'HEAD'}) -> - {200, ?HEADER, []}; + {200, ?HEADER, <<>>}; process(_Path, _Request) -> ?DEBUG("Bad Request: ~p", [_Request]), - {400, ?HEADER, {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}. + {400, ?HEADER, + #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}. get_human_html_xmlel() -> - Heading = "ejabberd " ++ atom_to_list(?MODULE), - {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}], - [{xmlelement, "head", [], - [{xmlelement, "title", [], [{xmlcdata, Heading}]}]}, - {xmlelement, "body", [], - [{xmlelement, "h1", [], [{xmlcdata, Heading}]}, - {xmlelement, "p", [], - [{xmlcdata, "An implementation of "}, - {xmlelement, "a", - [{"href", "http://xmpp.org/extensions/xep-0206.html"}], - [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]}, - {xmlelement, "p", [], - [{xmlcdata, "This web page is only informative. " - "To use HTTP-Bind you need a Jabber/XMPP client that supports it."} - ]} - ]}]}. + Heading = <<"ejabberd ", + (iolist_to_binary(atom_to_list(?MODULE)))/binary>>, + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], + children = + [#xmlel{name = <<"head">>, + children = + [#xmlel{name = <<"title">>, + children = [{xmlcdata, Heading}]}]}, + #xmlel{name = <<"body">>, + children = + [#xmlel{name = <<"h1">>, + children = [{xmlcdata, Heading}]}, + #xmlel{name = <<"p">>, + children = + [{xmlcdata, <<"An implementation of ">>}, + #xmlel{name = <<"a">>, + attrs = + [{<<"href">>, + <<"http://xmpp.org/extensions/xep-0206.html">>}], + children = + [{xmlcdata, + <<"XMPP over BOSH (XEP-0206)">>}]}]}, + #xmlel{name = <<"p">>, + children = + [{xmlcdata, + <<"This web page is only informative. To " + "use HTTP-Bind you need a Jabber/XMPP " + "client that supports it.">>}]}]}]}. -%%%---------------------------------------------------------------------- -%%% BEHAVIOUR CALLBACKS -%%%---------------------------------------------------------------------- start(Host, _Opts) -> setup_database(), Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, ejabberd_http_bind]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, + ChildSpec = {Proc, + {ejabberd_tmp_sup, start_link, + [Proc, ejabberd_http_bind]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop(Host) -> @@ -123,23 +125,17 @@ stop(Host) -> setup_database() -> migrate_database(), mnesia:create_table(http_bind, - [{ram_copies, [node()]}, - {local_content, true}, + [{ram_copies, [node()]}, {local_content, true}, {attributes, record_info(fields, http_bind)}]), mnesia:add_table_copy(http_bind, node(), ram_copies). migrate_database() -> case catch mnesia:table_info(http_bind, attributes) of - [id, pid, to, hold, wait, process_delay, version] -> - ok; - _ -> - %% Since the stored information is not important, instead - %% of actually migrating data, let's just destroy the table - mnesia:delete_table(http_bind) + [id, pid, to, hold, wait, process_delay, version] -> ok; + _ -> mnesia:delete_table(http_bind) end, - case catch mnesia:table_info(http_bind, local_content) of - false -> - mnesia:delete_table(http_bind); - _ -> - ok + case catch mnesia:table_info(http_bind, local_content) + of + false -> mnesia:delete_table(http_bind); + _ -> ok end. diff --git a/src/web/mod_http_bindjson.erl b/src/web/mod_http_bindjson.erl index 6090ee458..30f841c99 100644 --- a/src/web/mod_http_bindjson.erl +++ b/src/web/mod_http_bindjson.erl @@ -28,129 +28,126 @@ %%% the real stuff, this is to handle the new pluggable architecture for %%% extending ejabberd's http service. %%%---------------------------------------------------------------------- -%%% I will probable kill and merge code with the original mod_http_bind +%%% I will probable kill and merge code with the original mod_http_bind %%% if this feature gains traction. %%% Eric Cestari -module(mod_http_bindjson). + -author('steve@zeank.in-berlin.de'). %%-define(ejabberd_debug, true). -behaviour(gen_mod). --export([ - start/2, - stop/1, - process/2 - ]). +-export([start/2, stop/1, process/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("http_bind.hrl"). -%% Duplicated from ejabberd_http_bind. -%% TODO: move to hrl file. --record(http_bind, {id, pid, to, hold, wait, process_delay, version}). +-record(http_bind, + {id, pid, to, hold, wait, process_delay, version}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -process([], #request{method = 'POST', - data = []}) -> +process([], #request{method = 'POST', data = <<>>}) -> ?DEBUG("Bad Request: no data", []), - {400, ?HEADER, {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}; -process([], #request{method = 'POST', - data = Data, - ip = IP}) -> + {400, ?HEADER, + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}; +process([], + #request{method = 'POST', data = Data, ip = IP}) -> ?DEBUG("Incoming data: ~s", [Data]), - %NOTE the whole point of this file is this line. ejabberd_http_bindjson:process_request(Data, IP); -process([], #request{method = 'GET', - data = []}) -> +process([], #request{method = 'GET', data = <<>>}) -> {200, ?HEADER, get_human_html_xmlel()}; -process([], #request{method = 'OPTIONS', - data = []}) -> +process([], #request{method = 'OPTIONS', data = <<>>}) -> {200, ?OPTIONS_HEADER, []}; process(_Path, _Request) -> ?DEBUG("Bad Request: ~p", [_Request]), - {400, ?HEADER, {xmlelement, "h1", [], - [{xmlcdata, "400 Bad Request"}]}}. + {400, ?HEADER, + #xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, <<"400 Bad Request">>}]}}. get_human_html_xmlel() -> - Heading = "ejabberd " ++ atom_to_list(?MODULE), - {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}], - [{xmlelement, "head", [], - [{xmlelement, "title", [], [{xmlcdata, Heading}]}]}, - {xmlelement, "body", [], - [{xmlelement, "h1", [], [{xmlcdata, Heading}]}, - {xmlelement, "p", [], - [{xmlcdata, "An implementation of "}, - {xmlelement, "a", - [{"href", "http://xmpp.org/extensions/xep-0206.html"}], - [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]}, - {xmlelement, "p", [], - [{xmlcdata, "This web page is only informative. " - "To use HTTP-Bind you need a Jabber/XMPP client that supports it."} - ]} - ]}]}. + Heading = <<"ejabberd ", + (iolist_to_binary(atom_to_list(?MODULE)))/binary>>, + #xmlel{name = <<"html">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], + children = + [#xmlel{name = <<"head">>, attrs = [], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, Heading}]}]}, + #xmlel{name = <<"body">>, attrs = [], + children = + [#xmlel{name = <<"h1">>, attrs = [], + children = [{xmlcdata, Heading}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, <<"An implementation of ">>}, + #xmlel{name = <<"a">>, + attrs = + [{<<"href">>, + <<"http://xmpp.org/extensions/xep-0206.html">>}], + children = + [{xmlcdata, + <<"XMPP over BOSH (XEP-0206)">>}]}]}, + #xmlel{name = <<"p">>, attrs = [], + children = + [{xmlcdata, + <<"This web page is only informative. To " + "use HTTP-Bind you need a Jabber/XMPP " + "client that supports it.">>}]}]}]}. -%%%---------------------------------------------------------------------- -%%% BEHAVIOUR CALLBACKS -%%%---------------------------------------------------------------------- start(_Host, _Opts) -> setup_database(), - HTTPBindSupervisor = - {ejabberd_http_bind_sup, - {ejabberd_tmp_sup, start_link, - [ejabberd_http_bind_sup, ejabberd_http_bind]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, - case supervisor:start_child(ejabberd_sup, HTTPBindSupervisor) of - {ok, _Pid} -> - ok; - {ok, _Pid, _Info} -> - ok; - {error, {already_started, _PidOther}} -> - % mod_http_bind is already started so it will not be started again - ok; - {error, Error} -> - {'EXIT', {start_child_error, Error}} + HTTPBindSupervisor = {ejabberd_http_bind_sup, + {ejabberd_tmp_sup, start_link, + [ejabberd_http_bind_sup, ejabberd_http_bind]}, + permanent, infinity, supervisor, [ejabberd_tmp_sup]}, + case supervisor:start_child(ejabberd_sup, + HTTPBindSupervisor) + of + {ok, _Pid} -> ok; + {ok, _Pid, _Info} -> ok; + {error, {already_started, _PidOther}} -> + % mod_http_bind is already started so it will not be started again + ok; + {error, Error} -> {'EXIT', {start_child_error, Error}} end. stop(_Host) -> - case supervisor:terminate_child(ejabberd_sup, ejabberd_http_bind_sup) of - ok -> - ok; - {error, Error} -> - {'EXIT', {terminate_child_error, Error}} + case supervisor:terminate_child(ejabberd_sup, + ejabberd_http_bind_sup) + of + ok -> ok; + {error, Error} -> + {'EXIT', {terminate_child_error, Error}} end. setup_database() -> migrate_database(), mnesia:create_table(http_bind, - [{ram_copies, [node()]}, - {local_content, true}, + [{ram_copies, [node()]}, {local_content, true}, {attributes, record_info(fields, http_bind)}]), mnesia:add_table_copy(http_bind, node(), ram_copies). migrate_database() -> case catch mnesia:table_info(http_bind, attributes) of - [id, pid, to, hold, wait, process_delay, version] -> - ok; - _ -> - %% Since the stored information is not important, instead - %% of actually migrating data, let's just destroy the table - mnesia:delete_table(http_bind) + [id, pid, to, hold, wait, process_delay, version] -> ok; + _ -> mnesia:delete_table(http_bind) end, - case catch mnesia:table_info(http_bind, local_content) of - false -> - mnesia:delete_table(http_bind); - _ -> - ok + case catch mnesia:table_info(http_bind, local_content) + of + false -> mnesia:delete_table(http_bind); + _ -> ok end. diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl index b7c16f1b5..b47d8c34d 100644 --- a/src/web/mod_http_fileserver.erl +++ b/src/web/mod_http_fileserver.erl @@ -25,8 +25,11 @@ %%%---------------------------------------------------------------------- -module(mod_http_fileserver). + -author('mmirra@process-one.net'). + -author('ecestari@process-one.net'). + -behaviour(gen_mod). %% gen_mod callbacks @@ -36,113 +39,119 @@ -export([process/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include_lib("kernel/include/file.hrl"). -include("ejabberd_http.hrl"). --ifdef(SSL40). --define(STRING2LOWER, string). --else. --ifdef(SSL39). --define(STRING2LOWER, string). --else. --define(STRING2LOWER, httpd_util). --endif. --endif. - -%% Response is {DataSize, Code, [{HeaderKey, HeaderValue}], Data} --define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}). --define(HTTP_ERR_FORBIDDEN, {-1, 403, [], "Forbidden"}). - --define(DEFAULT_CONTENT_TYPE, "application/octet-stream"). --define(DEFAULT_CONTENT_TYPES, [{".css", "text/css"}, - {".gif", "image/gif"}, - {".html", "text/html"}, - {".jar", "application/java-archive"}, - {".jpeg", "image/jpeg"}, - {".jpg", "image/jpeg"}, - {".js", "text/javascript"}, - {".png", "image/png"}, - {".svg", "image/svg+xml"}, - {".txt", "text/plain"}, - {".xml", "application/xml"}, - {".xpi", "application/x-xpinstall"}, - {".xul", "application/vnd.mozilla.xul+xml"}]). - --compile(export_all). +-define(HTTP_ERR_FILE_NOT_FOUND, + {-1, 404, [], <<"Not found">>}). + +-define(HTTP_ERR_FORBIDDEN, + {-1, 403, [], <<"Forbidden">>}). +-define(DEFAULT_CONTENT_TYPE, + <<"application/octet-stream">>). + +-define(DEFAULT_CONTENT_TYPES, + [{<<".css">>, <<"text/css">>}, + {<<".gif">>, <<"image/gif">>}, + {<<".html">>, <<"text/html">>}, + {<<".jar">>, <<"application/java-archive">>}, + {<<".jpeg">>, <<"image/jpeg">>}, + {<<".jpg">>, <<"image/jpeg">>}, + {<<".js">>, <<"text/javascript">>}, + {<<".png">>, <<"image/png">>}, + {<<".svg">>, <<"image/svg+xml">>}, + {<<".txt">>, <<"text/plain">>}, + {<<".xml">>, <<"application/xml">>}, + {<<".xpi">>, <<"application/x-xpinstall">>}, + {<<".xul">>, <<"application/vnd.mozilla.xul+xml">>}]). start(Host, Opts) -> - DocRoot = gen_mod:get_opt(docroot, Opts, undefined), + DocRoot = gen_mod:get_opt(docroot, Opts, + fun iolist_to_binary/1, + undefined), set_default_host(Host, Opts), conf_store(Host, docroot, DocRoot), check_docroot_defined(DocRoot, Host), DRInfo = check_docroot_exists(DocRoot), check_docroot_is_dir(DRInfo, DocRoot), check_docroot_is_readable(DRInfo, DocRoot), - AccessLog = gen_mod:get_opt(accesslog, Opts, undefined), + AccessLog = gen_mod:get_opt(accesslog, Opts, + fun iolist_to_binary/1, + undefined), start_log(Host, AccessLog), - DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []), + DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, + fun(L) when is_list(L) -> L end, + []), conf_store(Host, directory_indices, DirectoryIndices), - ServeStaticGzip = gen_mod:get_opt(serve_gzip, Opts, false), + ServeStaticGzip = gen_mod:get_opt(serve_gzip, Opts, + fun(B) when is_boolean(B) -> B end, + false), conf_store(Host, serve_gzip, ServeStaticGzip), - CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []), + CustomHeaders = gen_mod:get_opt(custom_headers, Opts, + fun(L) when is_list(L) -> L end, + []), conf_store(Host, custom_headers, CustomHeaders), - DefaultContentType = gen_mod:get_opt(default_content_type, Opts, - ?DEFAULT_CONTENT_TYPE), - conf_store(Host, default_content_type, DefaultContentType), - ContentTypes = build_list_content_types(gen_mod:get_opt(content_types, Opts, []), ?DEFAULT_CONTENT_TYPES), + DefaultContentType = + gen_mod:get_opt(default_content_type, Opts, + fun iolist_to_binary/1, + ?DEFAULT_CONTENT_TYPE), + conf_store(Host, default_content_type, + DefaultContentType), + ContentTypes = build_list_content_types( + gen_mod:get_opt(content_types, Opts, + fun(L) when is_list(L) -> L end, + []), + ?DEFAULT_CONTENT_TYPES), conf_store(Host, content_types, ContentTypes), ?INFO_MSG("initialize: ~n ~p", [ContentTypes]), ok. -% Defines host that will answer request if hostname is not recognized. -% The first configured host will be used. -set_default_host(Host, _Opts)-> +set_default_host(Host, _Opts) -> case mochiglobal:get(http_default_host) of - undefined -> - ?DEBUG("Setting default host to ~p", [Host]), - mochiglobal:put(http_default_host, Host); - _ -> - ok + undefined -> + ?DEBUG("Setting default host to ~p", [Host]), + mochiglobal:put(http_default_host, Host); + _ -> ok end. -% Returns current host if it exists or default host -get_host(Host)-> + +get_host(Host) -> DCT = mochiglobal:get(default_content_type), case lists:keymember(Host, 1, DCT) of - true -> Host; - false -> mochiglobal:get(http_default_host) + true -> Host; + false -> mochiglobal:get(http_default_host) end. - -conf_store(Host, Key, Value)-> + +conf_store(Host, Key, Value) -> R = case mochiglobal:get(Key) of - undefined -> [{Host, Value}]; - A -> - case lists:keymember(Host, 1, A) of - true -> lists:keyreplace(Host, 1, A,{Host, Value}); - false -> [{Host, Value}|A] - end - end, + undefined -> [{Host, Value}]; + A -> + case lists:keymember(Host, 1, A) of + true -> lists:keyreplace(Host, 1, A, {Host, Value}); + false -> [{Host, Value} | A] + end + end, mochiglobal:put(Key, R). - + conf_get(Host, Key) -> case mochiglobal:get(Key) of - undefined-> undefined; - A -> - case lists:keyfind(Host, 1, A) of - {Host, Val} -> Val; - false -> - case mochiglobal:get(http_default_host) of - Host -> % stop recursion here - undefined; - DefaultHost -> - conf_get(DefaultHost, Key) - end - end + undefined -> undefined; + A -> + case lists:keyfind(Host, 1, A) of + {Host, Val} -> Val; + false -> + case mochiglobal:get(http_default_host) of + Host -> % stop recursion here + undefined; + DefaultHost -> conf_get(DefaultHost, Key) + end + end end. - %% @spec (AdminCTs::[CT], Default::[CT]) -> [CT] %% where CT = {Extension::string(), Value} %% Value = string() | undefined @@ -150,45 +159,47 @@ conf_get(Host, Key) -> %% Elements of AdminCTs have more priority. %% If a CT is declared as 'undefined', then it is not included in the result. -start_log(_Host, undefined)-> - ok; +start_log(_Host, undefined) -> ok; start_log(Host, FileName) -> mod_http_fileserver_log:start(Host, FileName). - -build_list_content_types(AdminCTsUnsorted, DefaultCTsUnsorted) -> + +build_list_content_types(AdminCTsUnsorted, + DefaultCTsUnsorted) -> AdminCTs = lists:ukeysort(1, AdminCTsUnsorted), DefaultCTs = lists:ukeysort(1, DefaultCTsUnsorted), - CTsUnfiltered = lists:ukeymerge(1, AdminCTs, DefaultCTs), - [{Extension, Value} || {Extension, Value} <- CTsUnfiltered, Value /= undefined]. + CTsUnfiltered = lists:ukeymerge(1, AdminCTs, + DefaultCTs), + [{Extension, Value} + || {Extension, Value} <- CTsUnfiltered, + Value /= undefined]. check_docroot_defined(DocRoot, Host) -> case DocRoot of - undefined -> throw({undefined_docroot_option, Host}); - _ -> ok + undefined -> throw({undefined_docroot_option, Host}); + _ -> ok end. check_docroot_exists(DocRoot) -> case file:read_file_info(DocRoot) of - {error, Reason} -> throw({error_access_docroot, DocRoot, Reason}); - {ok, FI} -> FI + {error, Reason} -> + throw({error_access_docroot, DocRoot, Reason}); + {ok, FI} -> FI end. check_docroot_is_dir(DRInfo, DocRoot) -> case DRInfo#file_info.type of - directory -> ok; - _ -> throw({docroot_not_directory, DocRoot}) + directory -> ok; + _ -> throw({docroot_not_directory, DocRoot}) end. check_docroot_is_readable(DRInfo, DocRoot) -> case DRInfo#file_info.access of - read -> ok; - read_write -> ok; - _ -> throw({docroot_not_readable, DocRoot}) + read -> ok; + read_write -> ok; + _ -> throw({docroot_not_readable, DocRoot}) end. - -stop(_Host) -> - ok. +stop(_Host) -> ok. %%==================================================================== %% request_handlers callbacks @@ -205,141 +216,161 @@ process(LocalPath, Request) -> ClientHeaders = Request#request.headers, DirectoryIndices = conf_get(Host, directory_indices), CustomHeaders = conf_get(Host, custom_headers), - DefaultContentType = conf_get(Host, default_content_type), + DefaultContentType = conf_get(Host, + default_content_type), ContentTypes = conf_get(Host, content_types), Encoding = conf_get(Host, serve_gzip), Static = select_encoding(ClientHeaders, Encoding), DocRoot = conf_get(Host, docroot), - FileName = filename:join(filename:split(DocRoot) ++ LocalPath), - {FileSize, Code, Headers, Contents} = case file:read_file_info(FileName) of - {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, eacces} -> ?HTTP_ERR_FORBIDDEN; - {ok, #file_info{type = directory}} -> serve_index(FileName, - DirectoryIndices, - CustomHeaders, - DefaultContentType, - ContentTypes, Static); - {ok, FileInfo} -> - case should_serve(FileInfo, ClientHeaders) of - true ->serve_file(FileInfo, FileName, - CustomHeaders, - DefaultContentType, - ContentTypes, Static); - false -> - {0, 304, [], []} - end - end, - mod_http_fileserver_log:add_to_log(Host,FileSize, Code, Request), + FileName = filename:join(filename:split(DocRoot) ++ + LocalPath), + {FileSize, Code, Headers, Contents} = case + file:read_file_info(FileName) + of + {error, enoent} -> + ?HTTP_ERR_FILE_NOT_FOUND; + {error, eacces} -> + ?HTTP_ERR_FORBIDDEN; + {ok, + #file_info{type = directory}} -> + serve_index(FileName, + DirectoryIndices, + CustomHeaders, + DefaultContentType, + ContentTypes, + Static); + {ok, FileInfo} -> + case should_serve(FileInfo, + ClientHeaders) + of + true -> + serve_file(FileInfo, + FileName, + CustomHeaders, + DefaultContentType, + ContentTypes, + Static); + false -> {0, 304, [], []} + end + end, + mod_http_fileserver_log:add_to_log(Host, FileSize, Code, + Request), {Code, Headers, Contents}. should_serve(FileInfo, Headers) -> - lists:foldl(fun({Header, Fun}, Acc)-> - case lists:keyfind(Header, 1, Headers) of - {_, Val} -> - Fun(FileInfo,Val); - _O -> - Acc - end - end, true, [{'If-None-Match',fun etag/2} - ]). -etag(FileInfo, Etag)-> + lists:foldl(fun ({Header, Fun}, Acc) -> + case lists:keyfind(Header, 1, Headers) of + {_, Val} -> Fun(FileInfo, Val); + _O -> Acc + end + end, + true, [{'If-None-Match', fun etag/2}]). + +etag(FileInfo, Etag) -> case httpd_util:create_etag(FileInfo) of - Etag -> - false; - _ -> - true + Etag -> false; + _ -> true end. -modified(FileInfo, LastModified)-> - AfterDate = calendar:datetime_to_gregorian_seconds( - httpd_util:convert_request_date(LastModified)), - Mtime = calendar:datetime_to_gregorian_seconds(FileInfo#file_info.mtime), - ?DEBUG("Modified : ~p > ~p (serving: ~p)", [Mtime, AfterDate,Mtime > AfterDate]), - Mtime > AfterDate. - -select_encoding(_Headers, false)-> - false; -select_encoding(Headers, Configuration)-> - Value = find_header('Accept-Encoding', Headers, ""), - Schemes = string:tokens(Value, ","), - case lists:member("gzip",Schemes) of - true -> Configuration; - false -> false + +select_encoding(_Headers, false) -> false; +select_encoding(Headers, Configuration) -> + Value = find_header('Accept-Encoding', Headers, <<"">>), + Schemes = str:tokens(Value, <<",">>), + case lists:member(<<"gzip">>, Schemes) of + true -> Configuration; + false -> false end. -%% Troll through the directory indices attempting to find one which -%% works, if none can be found, return a 404. -serve_index(_FileName, [], _CH, _DefaultContentType, _ContentTypes, _Static) -> +serve_index(_FileName, [], _CH, _DefaultContentType, + _ContentTypes, _Static) -> ?HTTP_ERR_FILE_NOT_FOUND; -serve_index(FileName, [Index | T], CH, DefaultContentType, ContentTypes, Static) -> +serve_index(FileName, [Index | T], CH, + DefaultContentType, ContentTypes, Static) -> IndexFileName = filename:join([FileName] ++ [Index]), case file:read_file_info(IndexFileName) of - {error, _Error} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes, Static); - {ok, #file_info{type = directory}} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes, Static); - {ok, FileInfo} -> serve_file(FileInfo, IndexFileName, CH, DefaultContentType, ContentTypes, Static) + {error, _Error} -> + serve_index(FileName, T, CH, DefaultContentType, + ContentTypes, Static); + {ok, #file_info{type = directory}} -> + serve_index(FileName, T, CH, DefaultContentType, + ContentTypes, Static); + {ok, FileInfo} -> + serve_file(FileInfo, IndexFileName, CH, + DefaultContentType, ContentTypes, Static) end. %% Assume the file exists if we got this far and attempt to read it in %% and serve it up. - -serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes, false) -> + +serve_file(FileInfo, FileName, CustomHeaders, + DefaultContentType, ContentTypes, false) -> ?DEBUG("Delivering: ~s", [FileName]), - ContentType = content_type(FileName, DefaultContentType, ContentTypes), + ContentType = content_type(FileName, DefaultContentType, + ContentTypes), {ok, FileContents} = file:read_file(FileName), - {FileInfo#file_info.size, - 200, [{"Server", "ejabberd"}, - {"Last-Modified", last_modified(FileInfo)}, - {"Content-Type", ContentType} | CustomHeaders], + {FileInfo#file_info.size, 200, + [{<<"Server">>, <<"ejabberd">>}, + {<<"Last-Modified">>, last_modified(FileInfo)}, + {<<"Content-Type">>, ContentType} + | CustomHeaders], FileContents}; - -serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes, Gzip) -> +serve_file(FileInfo, FileName, CustomHeaders, + DefaultContentType, ContentTypes, Gzip) -> ?DEBUG("Delivering: ~s", [FileName]), - ContentType = content_type(FileName, DefaultContentType, ContentTypes), - CompressedFileName = FileName ++ ".gz", + ContentType = content_type(FileName, DefaultContentType, + ContentTypes), + CompressedFileName = <<FileName/binary, ".gz">>, case file:read_file_info(CompressedFileName) of - {ok, FileInfoCompressed} -> %Found compressed - ?INFO_MSG("Found compressed: ~s", [FileName]), - {ok, FileContents} = file:read_file(CompressedFileName), - {FileInfoCompressed#file_info.size, - 200, [{"Server", "ejabberd"}, - {"Last-Modified", last_modified(FileInfoCompressed)}, - {"Content-Type", ContentType}, - {"Etag", httpd_util:create_etag(FileInfoCompressed)}, - {"Content-Encoding", "gzip"} | CustomHeaders], - FileContents}; - {error, _} -> - {FileContents, Size} = case Gzip of - static -> - {ok, Content} = file:read_file(FileName), - {Content, FileInfo#file_info.size}; - always -> - {ok, Content} = file:read_file(FileName), - Compressed = zlib:gzip(Content), - {Compressed, size(Compressed)} - end, - {Size, - 200, [{"Server", "ejabberd"}, - {"Last-Modified", last_modified(FileInfo)}, - {"Etag", httpd_util:create_etag(FileInfo)}, - {"Content-Type", ContentType}, - {"Content-Encoding", "gzip"} | CustomHeaders], - FileContents} + {ok, FileInfoCompressed} -> + ?INFO_MSG("Found compressed: ~s", [FileName]), + {ok, FileContents} = file:read_file(CompressedFileName), + {FileInfoCompressed#file_info.size, 200, + [{<<"Server">>, <<"ejabberd">>}, + {<<"Last-Modified">>, + last_modified(FileInfoCompressed)}, + {<<"Content-Type">>, ContentType}, + {<<"Etag">>, + httpd_util:create_etag(FileInfoCompressed)}, + {<<"Content-Encoding">>, <<"gzip">>} + | CustomHeaders], + FileContents}; + {error, _} -> + {FileContents, Size} = case Gzip of + static -> + {ok, Content} = file:read_file(FileName), + {Content, FileInfo#file_info.size}; + always -> + {ok, Content} = file:read_file(FileName), + Compressed = zlib:gzip(Content), + {Compressed, byte_size(Compressed)} + end, + {Size, 200, + [{<<"Server">>, <<"ejabberd">>}, + {<<"Last-Modified">>, last_modified(FileInfo)}, + {<<"Etag">>, httpd_util:create_etag(FileInfo)}, + {<<"Content-Type">>, ContentType}, + {<<"Content-Encoding">>, <<"gzip">>} + | CustomHeaders], + FileContents} end. find_header(Header, Headers, Default) -> case lists:keysearch(Header, 1, Headers) of - {value, {_, Value}} -> Value; - false -> Default + {value, {_, Value}} -> Value; + false -> Default end. %%---------------------------------------------------------------------- %% Utilities %%---------------------------------------------------------------------- -content_type(Filename, DefaultContentType, ContentTypes) -> - Extension = ?STRING2LOWER:to_lower(filename:extension(Filename)), +content_type(Filename, DefaultContentType, + ContentTypes) -> + Extension = + str:to_lower(filename:extension(Filename)), case lists:keysearch(Extension, 1, ContentTypes) of - {value, {_, ContentType}} -> ContentType; - false -> DefaultContentType + {value, {_, ContentType}} -> ContentType; + false -> DefaultContentType end. last_modified(FileInfo) -> diff --git a/src/web/mod_http_fileserver_log.erl b/src/web/mod_http_fileserver_log.erl index b76f947c8..8f634f487 100644 --- a/src/web/mod_http_fileserver_log.erl +++ b/src/web/mod_http_fileserver_log.erl @@ -1,30 +1,35 @@ --module (mod_http_fileserver_log). +-module(mod_http_fileserver_log). --behaviour (gen_server). +-behaviour(gen_server). --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]). --export ([start_link/2,start/2, stop/1, add_to_log/4,reopen_log/1]). +-export([start_link/2, start/2, stop/1, add_to_log/4, + reopen_log/1]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include_lib("kernel/include/file.hrl"). -define(PROCNAME, ejabberd_mod_http_fileserver_log). --record(state, {host,accesslog, accesslogfd}). +-record(state, {host = <<"">>, + accesslog :: binary(), + accesslogfd :: file:io_device()}). + %% Public API start(Host, Filename) -> - Proc =gen_mod:get_module_proc(Host, ?PROCNAME), - ChildSpec = - {Proc, - {?MODULE, start_link, [Host, Filename]}, - transient, % if process crashes abruptly, it gets restarted - 1000, - worker, - [?MODULE]}, + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + ChildSpec = {Proc, + {?MODULE, start_link, [Host, Filename]}, + transient, % if process crashes abruptly, it gets restarted + 1000, worker, [?MODULE]}, supervisor:start_child(ejabberd_sup, ChildSpec). stop(Host) -> @@ -35,133 +40,139 @@ stop(Host) -> start_link(Host, Filename) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - gen_server:start_link({local, Proc}, ?MODULE, [Host, Filename], []). + gen_server:start_link({local, Proc}, ?MODULE, + [Host, Filename], []). -add_to_log(Host,FileSize, Code, Request) -> - gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME), +-spec add_to_log(binary(), non_neg_integer(), non_neg_integer(), + http_request()) -> ok. + +add_to_log(Host, FileSize, Code, Request) -> + gen_server:cast(gen_mod:get_module_proc(Host, + ?PROCNAME), {add_to_log, FileSize, Code, Request}). - + +-spec reopen_log(binary()) -> ok. + reopen_log(Host) -> - gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME), reopen_log). + gen_server:cast(gen_mod:get_module_proc(Host, + ?PROCNAME), + reopen_log). %% Server implementation, a.k.a.: callbacks init([Host, Filename]) -> try try_open_log(Filename, Host) of - AccessLogFD -> - ?DEBUG("File opened !", []), - {ok, #state{host = Host, - accesslog = Filename, - accesslogfd = AccessLogFD}} + AccessLogFD -> + ?DEBUG("File opened !", []), + {ok, + #state{host = Host, accesslog = Filename, + accesslogfd = AccessLogFD}} catch - throw:Reason -> - {stop, Reason} + Reason -> {stop, Reason} end. try_open_log(FN, Host) -> FD = try open_log(FN) of - FD1 -> FD1 + FD1 -> FD1 catch - throw:{cannot_open_accesslog, FN, Reason} -> - ?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", [FN, Reason]), - undefined + {cannot_open_accesslog, FN, Reason} -> + ?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", + [FN, Reason]), + undefined end, - %HostB = list_to_binary(Host), - ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE, reopen_log, 50), + ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE, + reopen_log, 50), FD. - + handle_call(_Request, _From, State) -> - {reply, ok, State}. + {reply, ok, State}. -handle_cast({add_to_log, FileSize, Code, Request}, State) -> - add_to_log2(State#state.accesslogfd, FileSize, Code, Request), +handle_cast({add_to_log, FileSize, Code, Request}, + State) -> + add_to_log2(State#state.accesslogfd, FileSize, Code, + Request), {noreply, State}; handle_cast(reopen_log, State) -> - FD2 = reopen_log(State#state.accesslog, State#state.accesslogfd), + FD2 = reopen_log(State#state.accesslog, + State#state.accesslogfd), {noreply, State#state{accesslogfd = FD2}}; -handle_cast(_Msg, State) -> - {noreply, State}. +handle_cast(_Msg, State) -> {noreply, State}. -handle_info(_Info, State) -> - {noreply, State}. +handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, State) -> close_log(State#state.accesslogfd), - ejabberd_hooks:delete(reopen_log_hook, State#state.host, ?MODULE, reopen_log, 50), + ejabberd_hooks:delete(reopen_log_hook, State#state.host, + ?MODULE, reopen_log, 50), ok. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - - +code_change(_OldVsn, State, _Extra) -> {ok, State}. + %%---------------------------------------------------------------------- %% Log file %%---------------------------------------------------------------------- +-spec open_log(binary()) -> file:io_device(). + open_log(FN) -> case file:open(FN, [append]) of - {ok, FD} -> - FD; - {error, Reason} -> - throw({cannot_open_accesslog, FN, Reason}) + {ok, FD} -> FD; + {error, Reason} -> + throw({cannot_open_accesslog, FN, Reason}) end. -close_log(FD) -> - file:close(FD). +close_log(FD) -> file:close(FD). -reopen_log(undefined, undefined) -> - ok; +reopen_log(undefined, undefined) -> ok; reopen_log(FN, FD) -> ?DEBUG("reopening logs", []), close_log(FD), open_log(FN). - +-spec add_to_log2(file:io_device(), non_neg_integer(), non_neg_integer(), + http_request()) -> ok. add_to_log2(undefined, _FileSize, _Code, _Request) -> ok; add_to_log2(File, FileSize, Code, Request) -> - {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), - IP = ip_to_string(element(1, Request#request.ip)), - Path = join(Request#request.path, "/"), - Query = case join(lists:map(fun(E) -> lists:concat([element(1, E), "=", element(2, E)]) end, - Request#request.q), "&") of - [] -> - ""; - String -> - [$? | String] + {{Year, Month, Day}, {Hour, Minute, Second}} = + calendar:local_time(), + IP = jlib:ip_to_list(Request#request.ip), + Path = str:join(Request#request.path, <<"/">>), + Query = case str:join( + lists:flatmap(fun ({nokey, []}) -> + []; + ({K, V}) -> + [<<K/binary, $=, V/binary>>] + end, Request#request.q), + <<"&">>) of + <<"">> -> <<"">>; + String -> <<$?, String/binary>> end, - UserAgent = find_header('User-Agent', Request#request.headers, "-"), - Referer = find_header('Referer', Request#request.headers, "-"), - %% Pseudo Combined Apache log format: - %% 127.0.0.1 - - [28/Mar/2007:18:41:55 +0200] "GET / HTTP/1.1" 302 303 "-" "tsung" - %% TODO some fields are harcoded/missing: - %% The date/time integers should have always 2 digits. For example day "7" should be "07" - %% Month should be 3*letter, not integer 1..12 - %% Missing time zone = (`+' | `-') 4*digit - %% Missing protocol version: HTTP/1.1 - %% For reference: http://httpd.apache.org/docs/2.2/logs.html - io:format(File, "~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" ~p ~p ~p ~p~n", - [IP, Day, Month, Year, Hour, Minute, Second, Request#request.method, Path, Query, Code, - FileSize, Referer, UserAgent]). - + UserAgent = find_header('User-Agent', + Request#request.headers, <<"-">>), + Referer = find_header('Referer', + Request#request.headers, <<"-">>), + io:format(File, + <<"~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" " + "~p ~p \"~s\" \"~s\"~n">>, + [IP, Day, Month, Year, Hour, Minute, Second, + Request#request.method, Path, Query, Code, FileSize, + escape_quote(Referer), escape_quote(UserAgent)]). + find_header(Header, Headers, Default) -> case lists:keysearch(Header, 1, Headers) of - {value, {_, Value}} -> Value; - false -> Default + {value, {_, Value}} -> Value; + false -> Default end. - -join([], _) -> - ""; -join([E], _) -> - E; -join([H | T], Separator) -> - lists:foldl(fun(E, Acc) -> lists:concat([Acc, Separator, E]) end, H, T). - -%% Convert IP address tuple to string representation. Accepts either -%% IPv4 or IPv6 address tuples. -ip_to_string(Address) when size(Address) == 4 -> - join(tuple_to_list(Address), "."); -ip_to_string(Address) when size(Address) == 8 -> - Parts = lists:map(fun (Int) -> io_lib:format("~.16B", [Int]) end, tuple_to_list(Address)), - string:to_lower(lists:flatten(join(Parts, ":"))).
\ No newline at end of file + + +escape_quote(B) -> + escape_quote(B, <<>>). + +escape_quote(<<$", Rest/binary>>, Acc) -> + escape_quote(Rest, <<Acc/binary, $\\, $">>); +escape_quote(<<C, Rest/binary>>, Acc) -> + escape_quote(Rest, <<Acc/binary, C>>); +escape_quote(<<>>, Acc) -> + Acc. diff --git a/src/web/mod_register_web.erl b/src/web/mod_register_web.erl index b5c1f3801..b020052d6 100644 --- a/src/web/mod_register_web.erl +++ b/src/web/mod_register_web.erl @@ -51,18 +51,19 @@ %%% * Optionally register request is forwarded to admin, no account created. -module(mod_register_web). + -author('badlop@process-one.net'). -behaviour(gen_mod). --export([start/2, - stop/1, - process/2 - ]). +-export([start/2, stop/1, process/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). + -include("ejabberd_web_admin.hrl"). %%%---------------------------------------------------------------------- @@ -73,8 +74,7 @@ start(_Host, _Opts) -> %% case gen_mod:get_opt(docroot, Opts, undefined) of ok. -stop(_Host) -> - ok. +stop(_Host) -> ok. %%%---------------------------------------------------------------------- %%% HTTP handlers @@ -82,55 +82,64 @@ stop(_Host) -> process([], #request{method = 'GET', lang = Lang}) -> index_page(Lang); - -process(["register.css"], #request{method = 'GET'}) -> +process([<<"register.css">>], + #request{method = 'GET'}) -> serve_css(); - -process(["new"], #request{method = 'GET', lang = Lang, host = Host, ip = IP}) -> - {Addr, _Port} = IP, - form_new_get(Host, Lang, Addr); - -process(["delete"], #request{method = 'GET', lang = Lang, host = Host}) -> +process([<<"new">>], + #request{method = 'GET', lang = Lang, host = Host, + ip = IP}) -> + {Addr, _Port} = IP, form_new_get(Host, Lang, Addr); +process([<<"delete">>], + #request{method = 'GET', lang = Lang, host = Host}) -> form_del_get(Host, Lang); - -process(["change_password"], #request{method = 'GET', lang = Lang, host = Host}) -> +process([<<"change_password">>], + #request{method = 'GET', lang = Lang, host = Host}) -> form_changepass_get(Host, Lang); - -process(["new"], #request{method = 'POST', q = Q, ip = {Ip,_Port}, lang = Lang, host = Host}) -> +process([<<"new">>], + #request{method = 'POST', q = Q, ip = {Ip, _Port}, + lang = Lang, host = Host}) -> case form_new_post(Q, Host) of - {success, ok, {Username, Host, _Password}} -> - Jid = jlib:make_jid(Username, Host, ""), - send_registration_notifications(Jid, Ip), - Text = ?T("Your Jabber account was successfully created."), - {200, [], Text}; - Error -> - ErrorText = ?T("There was an error creating the account: ") ++ - ?T(get_error_text(Error)), - {404, [], ErrorText} + {success, ok, {Username, Host, _Password}} -> + Jid = jlib:make_jid(Username, Host, <<"">>), + mod_register:send_registration_notifications(?MODULE, Jid, Ip), + Text = (?T(<<"Your Jabber account was successfully " + "created.">>)), + {200, [], Text}; + Error -> + ErrorText = + list_to_binary([?T(<<"There was an error creating the account: ">>), + ?T(get_error_text(Error))]), + {404, [], ErrorText} end; - -process(["delete"], #request{method = 'POST', q = Q, lang = Lang, host = Host}) -> +process([<<"delete">>], + #request{method = 'POST', q = Q, lang = Lang, + host = Host}) -> case form_del_post(Q, Host) of - {atomic, ok} -> - Text = ?T("Your Jabber account was successfully deleted."), - {200, [], Text}; - Error -> - ErrorText = ?T("There was an error deleting the account: ") ++ - ?T(get_error_text(Error)), - {404, [], ErrorText} + {atomic, ok} -> + Text = (?T(<<"Your Jabber account was successfully " + "deleted.">>)), + {200, [], Text}; + Error -> + ErrorText = + list_to_binary([?T(<<"There was an error deleting the account: ">>), + ?T(get_error_text(Error))]), + {404, [], ErrorText} end; - %% TODO: Currently only the first vhost is usable. The web request record %% should include the host where the POST was sent. -process(["change_password"], #request{method = 'POST', q = Q, lang = Lang, host = Host}) -> +process([<<"change_password">>], + #request{method = 'POST', q = Q, lang = Lang, + host = Host}) -> case form_changepass_post(Q, Host) of - {atomic, ok} -> - Text = ?T("The password of your Jabber account was successfully changed."), - {200, [], Text}; - Error -> - ErrorText = ?T("There was an error changing the password: ") ++ - ?T(get_error_text(Error)), - {404, [], ErrorText} + {atomic, ok} -> + Text = (?T(<<"The password of your Jabber account " + "was successfully changed.">>)), + {200, [], Text}; + Error -> + ErrorText = + list_to_binary([?T(<<"There was an error changing the password: ">>), + ?T(get_error_text(Error))]), + {404, [], ErrorText} end. %%%---------------------------------------------------------------------- @@ -138,48 +147,48 @@ process(["change_password"], #request{method = 'POST', q = Q, lang = Lang, host %%%---------------------------------------------------------------------- serve_css() -> - {200, [{"Content-Type", "text/css"}, - last_modified(), cache_control_public()], css()}. + {200, + [{<<"Content-Type">>, <<"text/css">>}, last_modified(), + cache_control_public()], + css()}. last_modified() -> - {"Last-Modified", "Mon, 25 Feb 2008 13:23:30 GMT"}. + {<<"Last-Modified">>, + <<"Mon, 25 Feb 2008 13:23:30 GMT">>}. + cache_control_public() -> - {"Cache-Control", "public"}. + {<<"Cache-Control">>, <<"public">>}. css() -> - "html,body { -background: white; -margin: 0; -padding: 0; -height: 100%; -}". + <<"html,body {\nbackground: white;\nmargin: " + "0;\npadding: 0;\nheight: 100%;\n}">>. %%%---------------------------------------------------------------------- %%% Index page %%%---------------------------------------------------------------------- index_page(Lang) -> - HeadEls = [ - ?XCT("title", "Jabber Account Registration"), - ?XA("link", - [{"href", "/register/register.css"}, - {"type", "text/css"}, - {"rel", "stylesheet"}]) - ], - Els=[ - ?XACT("h1", - [{"class", "title"}, {"style", "text-align:center;"}], - "Jabber Account Registration"), - ?XE("ul", [ - ?XE("li", [?ACT("new", "Register a Jabber account")]), - ?XE("li", [?ACT("change_password", "Change Password")]), - ?XE("li", [?ACT("delete", "Unregister a Jabber account")]) - ] - ) - ], + HeadEls = [?XCT(<<"title">>, + <<"Jabber Account Registration">>), + ?XA(<<"link">>, + [{<<"href">>, <<"/register/register.css">>}, + {<<"type">>, <<"text/css">>}, + {<<"rel">>, <<"stylesheet">>}])], + Els = [?XACT(<<"h1">>, + [{<<"class">>, <<"title">>}, + {<<"style">>, <<"text-align:center;">>}], + <<"Jabber Account Registration">>), + ?XE(<<"ul">>, + [?XE(<<"li">>, + [?ACT(<<"new">>, <<"Register a Jabber account">>)]), + ?XE(<<"li">>, + [?ACT(<<"change_password">>, <<"Change Password">>)]), + ?XE(<<"li">>, + [?ACT(<<"delete">>, + <<"Unregister a Jabber account">>)])])], {200, - [{"Server", "ejabberd"}, - {"Content-Type", "text/html"}], + [{<<"Server">>, <<"ejabberd">>}, + {<<"Content-Type">>, <<"text/html">>}], ejabberd_web:make_xhtml(HeadEls, Els)}. %%%---------------------------------------------------------------------- @@ -188,149 +197,112 @@ index_page(Lang) -> form_new_get(Host, Lang, IP) -> CaptchaEls = build_captcha_li_list(Lang, IP), - HeadEls = [ - ?XCT("title", "Register a Jabber account"), - ?XA("link", - [{"href", "/register/register.css"}, - {"type", "text/css"}, - {"rel", "stylesheet"}]) - ], - Els=[ - ?XACT("h1", - [{"class", "title"}, {"style", "text-align:center;"}], - "Register a Jabber account"), - ?XCT("p", - "This page allows to create a Jabber account in this Jabber server. " - "Your JID (Jabber IDentifier) will be of the form: username@server. " - "Please read carefully the instructions to fill correctly the fields."), - %% <!-- JID's take the form of 'username@server.com'. For example, my JID is 'kirjava@jabber.org'. - %% The maximum length for a JID is 255 characters. --> - ?XAE("form", [{"action", ""}, {"method", "post"}], - [ - ?XE("ol", [ - ?XE("li", [ - ?CT("Username:"), - ?C(" "), - ?INPUTS("text", "username", "", "20"), - ?BR, - ?XE("ul", [ - ?XCT("li", "This is case insensitive: macbeth is the same that MacBeth and Macbeth."), - ?XC("li", ?T("Characters not allowed:") ++ " \" & ' / : < > @ ") - ]) - ]), - ?XE("li", [ - ?CT("Server:"), - ?C(" "), - ?C(Host) - ]), - ?XE("li", [ - ?CT("Password:"), - ?C(" "), - ?INPUTS("password", "password", "", "20"), - ?BR, - ?XE("ul", [ - ?XCT("li", "Don't tell your password to anybody, " - "not even the administrators of the Jabber server."), - ?XCT("li", "You can later change your password using a Jabber client."), - ?XCT("li", "Some Jabber clients can store your password in your computer. " - "Use that feature only if you trust your computer is safe."), - ?XCT("li", "Memorize your password, or write it in a paper placed in a safe place. " - "In Jabber there isn't an automated way to recover your password if you forget it.") - ]) - ]), - ?XE("li", [ - ?CT("Password Verification:"), - ?C(" "), - ?INPUTS("password", "password2", "", "20") - ])] ++ CaptchaEls ++ [ - %% Nombre</b> (opcional)<b>:</b> <input type="text" size="20" name="name" maxlength="255"> <br /> <br /> --> - %% - %% Dirección de correo</b> (opcional)<b>:</b> <input type="text" size="20" name="email" maxlength="255"> <br /> <br /> --> - ?XE("li", [ - ?INPUTT("submit", "register", "Register") - ]) - ]) - ]) - ], + HeadEls = [?XCT(<<"title">>, + <<"Register a Jabber account">>), + ?XA(<<"link">>, + [{<<"href">>, <<"/register/register.css">>}, + {<<"type">>, <<"text/css">>}, + {<<"rel">>, <<"stylesheet">>}])], + Els = [?XACT(<<"h1">>, + [{<<"class">>, <<"title">>}, + {<<"style">>, <<"text-align:center;">>}], + <<"Register a Jabber account">>), + ?XCT(<<"p">>, + <<"This page allows to create a Jabber " + "account in this Jabber server. Your " + "JID (Jabber IDentifier) will be of the " + "form: username@server. Please read carefully " + "the instructions to fill correctly the " + "fields.">>), + ?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XE(<<"ol">>, + ([?XE(<<"li">>, + [?CT(<<"Username:">>), ?C(<<" ">>), + ?INPUTS(<<"text">>, <<"username">>, <<"">>, + <<"20">>), + ?BR, + ?XE(<<"ul">>, + [?XCT(<<"li">>, + <<"This is case insensitive: macbeth is " + "the same that MacBeth and Macbeth.">>), + ?XC(<<"li">>, + <<(?T(<<"Characters not allowed:">>))/binary, + " \" & ' / : < > @ ">>)])]), + ?XE(<<"li">>, + [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]), + ?XE(<<"li">>, + [?CT(<<"Password:">>), ?C(<<" ">>), + ?INPUTS(<<"password">>, <<"password">>, <<"">>, + <<"20">>), + ?BR, + ?XE(<<"ul">>, + [?XCT(<<"li">>, + <<"Don't tell your password to anybody, " + "not even the administrators of the Jabber " + "server.">>), + ?XCT(<<"li">>, + <<"You can later change your password using " + "a Jabber client.">>), + ?XCT(<<"li">>, + <<"Some Jabber clients can store your password " + "in your computer. Use that feature only " + "if you trust your computer is safe.">>), + ?XCT(<<"li">>, + <<"Memorize your password, or write it " + "in a paper placed in a safe place. In " + "Jabber there isn't an automated way " + "to recover your password if you forget " + "it.">>)])]), + ?XE(<<"li">>, + [?CT(<<"Password Verification:">>), ?C(<<" ">>), + ?INPUTS(<<"password">>, <<"password2">>, <<"">>, + <<"20">>)])] + ++ + CaptchaEls ++ + [?XE(<<"li">>, + [?INPUTT(<<"submit">>, <<"register">>, + <<"Register">>)])]))])], {200, - [{"Server", "ejabberd"}, - {"Content-Type", "text/html"}], + [{<<"Server">>, <<"ejabberd">>}, + {<<"Content-Type">>, <<"text/html">>}], ejabberd_web:make_xhtml(HeadEls, Els)}. -%% Copied from mod_register.erl -send_registration_notifications(UJID, Source) -> - Host = UJID#jid.lserver, - case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of - [] -> ok; - JIDs when is_list(JIDs) -> - Body = lists:flatten( - io_lib:format( - "[~s] The account ~s was registered from IP address ~s " - "on node ~w using ~p.", - [get_time_string(), jlib:jid_to_string(UJID), - ip_to_string(Source), node(), ?MODULE])), - lists:foreach( - fun(S) -> - case jlib:string_to_jid(S) of - error -> ok; - JID -> - ejabberd_router:route( - jlib:make_jid("", Host, ""), - JID, - {xmlelement, "message", [{"type", "chat"}], - [{xmlelement, "body", [], - [{xmlcdata, Body}]}]}) - end - end, JIDs); - _ -> - ok - end. -ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source); -ip_to_string(undefined) -> "undefined"; -ip_to_string(_) -> "unknown". -get_time_string() -> write_time(erlang:localtime()). -%% Function copied from ejabberd_logger_h.erl and customized -write_time({{Y,Mo,D},{H,Mi,S}}) -> - io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Y, Mo, D, H, Mi, S]). - %%%---------------------------------------------------------------------- %%% Formulary new POST %%%---------------------------------------------------------------------- form_new_post(Q, Host) -> case catch get_register_parameters(Q) of - [Username, Password, Password, Id, Key] -> - form_new_post(Username, Host, Password, {Id, Key}); - [_Username, _Password, _Password2, false, false] -> - {error, passwords_not_identical}; - [_Username, _Password, _Password2, Id, Key] -> - ejabberd_captcha:check_captcha(Id, Key), %% This deletes the captcha - {error, passwords_not_identical}; - _ -> - {error, wrong_parameters} + [Username, Password, Password, Id, Key] -> + form_new_post(Username, Host, Password, {Id, Key}); + [_Username, _Password, _Password2, false, false] -> + {error, passwords_not_identical}; + [_Username, _Password, _Password2, Id, Key] -> + ejabberd_captcha:check_captcha(Id, Key), + {error, passwords_not_identical}; + _ -> {error, wrong_parameters} end. get_register_parameters(Q) -> - lists:map( - fun(Key) -> - case lists:keysearch(Key, 1, Q) of - {value, {_Key, Value}} -> Value; - false -> false - end - end, - ["username", "password", "password2", "id", "key"]). - -form_new_post(Username, Host, Password, {false, false}) -> + lists:map(fun (Key) -> + case lists:keysearch(Key, 1, Q) of + {value, {_Key, Value}} -> Value; + false -> false + end + end, + [<<"username">>, <<"password">>, <<"password2">>, + <<"id">>, <<"key">>]). + +form_new_post(Username, Host, Password, + {false, false}) -> register_account(Username, Host, Password); form_new_post(Username, Host, Password, {Id, Key}) -> case ejabberd_captcha:check_captcha(Id, Key) of - captcha_valid -> - register_account(Username, Host, Password); - captcha_non_valid -> - {error, captcha_non_valid}; - captcha_not_found -> - {error, captcha_non_valid} + captcha_valid -> + register_account(Username, Host, Password); + captcha_non_valid -> {error, captcha_non_valid}; + captcha_not_found -> {error, captcha_non_valid} end. %%%---------------------------------------------------------------------- @@ -339,28 +311,26 @@ form_new_post(Username, Host, Password, {Id, Key}) -> build_captcha_li_list(Lang, IP) -> case ejabberd_captcha:is_feature_available() of - true -> build_captcha_li_list2(Lang, IP); - false -> [] + true -> build_captcha_li_list2(Lang, IP); + false -> [] end. build_captcha_li_list2(Lang, IP) -> - SID = "", - From = #jid{user = "", server = "test", resource = ""}, - To = #jid{user = "", server = "test", resource = ""}, + SID = <<"">>, + From = #jid{user = <<"">>, server = <<"test">>, + resource = <<"">>}, + To = #jid{user = <<"">>, server = <<"test">>, + resource = <<"">>}, Args = [], - case ejabberd_captcha:create_captcha(SID, From, To, Lang, IP, Args) of - {ok, Id, _} -> - {_, {CImg,CText,CId,CKey}} = - ejabberd_captcha:build_captcha_html(Id, Lang), - [?XE("li", [CText, - ?C(" "), - CId, - CKey, - ?BR, - CImg] - )]; - _ -> - [] + case ejabberd_captcha:create_captcha(SID, From, To, + Lang, IP, Args) + of + {ok, Id, _} -> + {_, {CImg, CText, CId, CKey}} = + ejabberd_captcha:build_captcha_html(Id, Lang), + [?XE(<<"li">>, + [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])]; + _ -> [] end. %%%---------------------------------------------------------------------- @@ -368,54 +338,42 @@ build_captcha_li_list2(Lang, IP) -> %%%---------------------------------------------------------------------- form_changepass_get(Host, Lang) -> - HeadEls = [ - ?XCT("title", "Change Password"), - ?XA("link", - [{"href", "/register/register.css"}, - {"type", "text/css"}, - {"rel", "stylesheet"}]) - ], - Els=[ - ?XACT("h1", - [{"class", "title"}, {"style", "text-align:center;"}], - "Change Password"), - ?XAE("form", [{"action", ""}, {"method", "post"}], - [ - ?XE("ol", [ - ?XE("li", [ - ?CT("Username:"), - ?C(" "), - ?INPUTS("text", "username", "", "20") - ]), - ?XE("li", [ - ?CT("Server:"), - ?C(" "), - ?C(Host) - ]), - ?XE("li", [ - ?CT("Old Password:"), - ?C(" "), - ?INPUTS("password", "passwordold", "", "20") - ]), - ?XE("li", [ - ?CT("New Password:"), - ?C(" "), - ?INPUTS("password", "password", "", "20") - ]), - ?XE("li", [ - ?CT("Password Verification:"), - ?C(" "), - ?INPUTS("password", "password2", "", "20") - ]), - ?XE("li", [ - ?INPUTT("submit", "changepass", "Change Password") - ]) - ]) - ]) - ], + HeadEls = [?XCT(<<"title">>, <<"Change Password">>), + ?XA(<<"link">>, + [{<<"href">>, <<"/register/register.css">>}, + {<<"type">>, <<"text/css">>}, + {<<"rel">>, <<"stylesheet">>}])], + Els = [?XACT(<<"h1">>, + [{<<"class">>, <<"title">>}, + {<<"style">>, <<"text-align:center;">>}], + <<"Change Password">>), + ?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XE(<<"ol">>, + [?XE(<<"li">>, + [?CT(<<"Username:">>), ?C(<<" ">>), + ?INPUTS(<<"text">>, <<"username">>, <<"">>, + <<"20">>)]), + ?XE(<<"li">>, + [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]), + ?XE(<<"li">>, + [?CT(<<"Old Password:">>), ?C(<<" ">>), + ?INPUTS(<<"password">>, <<"passwordold">>, <<"">>, + <<"20">>)]), + ?XE(<<"li">>, + [?CT(<<"New Password:">>), ?C(<<" ">>), + ?INPUTS(<<"password">>, <<"password">>, <<"">>, + <<"20">>)]), + ?XE(<<"li">>, + [?CT(<<"Password Verification:">>), ?C(<<" ">>), + ?INPUTS(<<"password">>, <<"password2">>, <<"">>, + <<"20">>)]), + ?XE(<<"li">>, + [?INPUTT(<<"submit">>, <<"changepass">>, + <<"Change Password">>)])])])], {200, - [{"Server", "ejabberd"}, - {"Content-Type", "text/html"}], + [{<<"Server">>, <<"ejabberd">>}, + {<<"Content-Type">>, <<"text/html">>}], ejabberd_web:make_xhtml(HeadEls, Els)}. %%%---------------------------------------------------------------------- @@ -424,64 +382,56 @@ form_changepass_get(Host, Lang) -> form_changepass_post(Q, Host) -> case catch get_changepass_parameters(Q) of - [Username, PasswordOld, Password, Password] -> - try_change_password(Username, Host, PasswordOld, Password); - [_Username, _PasswordOld, _Password, _Password2] -> - {error, passwords_not_identical}; - _ -> - {error, wrong_parameters} + [Username, PasswordOld, Password, Password] -> + try_change_password(Username, Host, PasswordOld, + Password); + [_Username, _PasswordOld, _Password, _Password2] -> + {error, passwords_not_identical}; + _ -> {error, wrong_parameters} end. get_changepass_parameters(Q) -> - lists:map( - fun(Key) -> - {value, {_Key, Value}} = lists:keysearch(Key, 1, Q), - Value - end, - ["username", "passwordold", "password", "password2"]). - -%% @spec(Username,Host,PasswordOld,Password) -> {atomic, ok} | -%% {error, account_doesnt_exist} | -%% {error, password_not_changed} | -%% {error, password_incorrect} -try_change_password(Username, Host, PasswordOld, Password) -> - try change_password(Username, Host, PasswordOld, Password) of - {atomic, ok} -> - {atomic, ok} + lists:map(fun (Key) -> + {value, {_Key, Value}} = lists:keysearch(Key, 1, Q), + Value + end, + [<<"username">>, <<"passwordold">>, <<"password">>, + <<"password2">>]). + +try_change_password(Username, Host, PasswordOld, + Password) -> + try change_password(Username, Host, PasswordOld, + Password) + of + {atomic, ok} -> {atomic, ok} catch - error:{badmatch, Error} -> - {error, Error} + error:{badmatch, Error} -> {error, Error} end. -change_password(Username, Host, PasswordOld, Password) -> - %% Check the account exists +change_password(Username, Host, PasswordOld, + Password) -> account_exists = check_account_exists(Username, Host), - - %% Check the old password is correct - password_correct = check_password(Username, Host, PasswordOld), - - %% This function always returns: ok - %% Change the password - ok = ejabberd_auth:set_password(Username, Host, Password), - - %% Check the new password is correct + password_correct = check_password(Username, Host, + PasswordOld), + ok = ejabberd_auth:set_password(Username, Host, + Password), case check_password(Username, Host, Password) of - password_correct -> - {atomic, ok}; - password_incorrect -> - {error, password_not_changed} + password_correct -> {atomic, ok}; + password_incorrect -> {error, password_not_changed} end. check_account_exists(Username, Host) -> case ejabberd_auth:is_user_exists(Username, Host) of - true -> account_exists; - false -> account_doesnt_exist + true -> account_exists; + false -> account_doesnt_exist end. check_password(Username, Host, Password) -> - case ejabberd_auth:check_password(Username, Host, Password) of - true -> password_correct; - false -> password_incorrect + case ejabberd_auth:check_password(Username, Host, + Password) + of + true -> password_correct; + false -> password_incorrect end. %%%---------------------------------------------------------------------- @@ -489,63 +439,53 @@ check_password(Username, Host, Password) -> %%%---------------------------------------------------------------------- form_del_get(Host, Lang) -> - HeadEls = [ - ?XCT("title", "Unregister a Jabber account"), - ?XA("link", - [{"href", "/register/register.css"}, - {"type", "text/css"}, - {"rel", "stylesheet"}]) - ], - Els=[ - ?XACT("h1", - [{"class", "title"}, {"style", "text-align:center;"}], - "Unregister a Jabber account"), - ?XCT("p", - "This page allows to unregister a Jabber account in this Jabber server."), - ?XAE("form", [{"action", ""}, {"method", "post"}], - [ - ?XE("ol", [ - ?XE("li", [ - ?CT("Username:"), - ?C(" "), - ?INPUTS("text", "username", "", "20") - ]), - ?XE("li", [ - ?CT("Server:"), - ?C(" "), - ?C(Host) - ]), - ?XE("li", [ - ?CT("Password:"), - ?C(" "), - ?INPUTS("password", "password", "", "20") - ]), - ?XE("li", [ - ?INPUTT("submit", "unregister", "Unregister") - ]) - ]) - ]) - ], + HeadEls = [?XCT(<<"title">>, + <<"Unregister a Jabber account">>), + ?XA(<<"link">>, + [{<<"href">>, <<"/register/register.css">>}, + {<<"type">>, <<"text/css">>}, + {<<"rel">>, <<"stylesheet">>}])], + Els = [?XACT(<<"h1">>, + [{<<"class">>, <<"title">>}, + {<<"style">>, <<"text-align:center;">>}], + <<"Unregister a Jabber account">>), + ?XCT(<<"p">>, + <<"This page allows to unregister a Jabber " + "account in this Jabber server.">>), + ?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XE(<<"ol">>, + [?XE(<<"li">>, + [?CT(<<"Username:">>), ?C(<<" ">>), + ?INPUTS(<<"text">>, <<"username">>, <<"">>, + <<"20">>)]), + ?XE(<<"li">>, + [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]), + ?XE(<<"li">>, + [?CT(<<"Password:">>), ?C(<<" ">>), + ?INPUTS(<<"password">>, <<"password">>, <<"">>, + <<"20">>)]), + ?XE(<<"li">>, + [?INPUTT(<<"submit">>, <<"unregister">>, + <<"Unregister">>)])])])], {200, - [{"Server", "ejabberd"}, - {"Content-Type", "text/html"}], + [{<<"Server">>, <<"ejabberd">>}, + {<<"Content-Type">>, <<"text/html">>}], ejabberd_web:make_xhtml(HeadEls, Els)}. -%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} | -%% {success, exists, {Username, Host, Password}} | -%% {error, not_allowed} | -%% {error, invalid_jid} register_account(Username, Host, Password) -> - case jlib:make_jid(Username, Host, "") of - error -> {error, invalid_jid}; - _ -> register_account2(Username, Host, Password) + case jlib:make_jid(Username, Host, <<"">>) of + error -> {error, invalid_jid}; + _ -> register_account2(Username, Host, Password) end. + register_account2(Username, Host, Password) -> - case ejabberd_auth:try_register(Username, Host, Password) of - {atomic, Res} -> - {success, Res, {Username, Host, Password}}; - Other -> - Other + case ejabberd_auth:try_register(Username, Host, + Password) + of + {atomic, Res} -> + {success, Res, {Username, Host, Password}}; + Other -> Other end. %%%---------------------------------------------------------------------- @@ -554,47 +494,33 @@ register_account2(Username, Host, Password) -> form_del_post(Q, Host) -> case catch get_unregister_parameters(Q) of - [Username, Password] -> - try_unregister_account(Username, Host, Password); - _ -> - {error, wrong_parameters} + [Username, Password] -> + try_unregister_account(Username, Host, Password); + _ -> {error, wrong_parameters} end. get_unregister_parameters(Q) -> - lists:map( - fun(Key) -> - {value, {_Key, Value}} = lists:keysearch(Key, 1, Q), - Value - end, - ["username", "password"]). - -%% @spec(Username, Host, Password) -> {atomic, ok} | -%% {error, account_doesnt_exist} | -%% {error, account_exists} | -%% {error, password_incorrect} + lists:map(fun (Key) -> + {value, {_Key, Value}} = lists:keysearch(Key, 1, Q), + Value + end, + [<<"username">>, <<"password">>]). + try_unregister_account(Username, Host, Password) -> try unregister_account(Username, Host, Password) of - {atomic, ok} -> - {atomic, ok} + {atomic, ok} -> {atomic, ok} catch - error:{badmatch, Error} -> - {error, Error} + error:{badmatch, Error} -> {error, Error} end. unregister_account(Username, Host, Password) -> - %% Check the account exists account_exists = check_account_exists(Username, Host), - - %% Check the password is correct - password_correct = check_password(Username, Host, Password), - - %% This function always returns: ok - ok = ejabberd_auth:remove_user(Username, Host, Password), - - %% Check the account does not exist anymore - account_doesnt_exist = check_account_exists(Username, Host), - - %% If we reached this point, return success + password_correct = check_password(Username, Host, + Password), + ok = ejabberd_auth:remove_user(Username, Host, + Password), + account_doesnt_exist = check_account_exists(Username, + Host), {atomic, ok}. %%%---------------------------------------------------------------------- @@ -602,24 +528,24 @@ unregister_account(Username, Host, Password) -> %%%---------------------------------------------------------------------- get_error_text({error, captcha_non_valid}) -> - "The captcha you entered is wrong"; + <<"The captcha you entered is wrong">>; get_error_text({success, exists, _}) -> get_error_text({atomic, exists}); get_error_text({atomic, exists}) -> - "The account already exists"; + <<"The account already exists">>; get_error_text({error, password_incorrect}) -> - "Incorrect password"; + <<"Incorrect password">>; get_error_text({error, invalid_jid}) -> - "The username is not valid"; + <<"The username is not valid">>; get_error_text({error, not_allowed}) -> - "Not allowed"; + <<"Not allowed">>; get_error_text({error, account_doesnt_exist}) -> - "Account doesn't exist"; + <<"Account doesn't exist">>; get_error_text({error, account_exists}) -> - "The account was not deleted"; + <<"The account was not deleted">>; get_error_text({error, password_not_changed}) -> - "The password was not changed"; + <<"The password was not changed">>; get_error_text({error, passwords_not_identical}) -> - "The passwords are different"; + <<"The passwords are different">>; get_error_text({error, wrong_parameters}) -> - "Wrong parameters in the web formulary". + <<"Wrong parameters in the web formulary">>. diff --git a/src/web/pshb_http.erl b/src/web/pshb_http.erl index 02eb0a4cc..6f1a7a52c 100644 --- a/src/web/pshb_http.erl +++ b/src/web/pshb_http.erl @@ -25,7 +25,7 @@ %%%---------------------------------------------------------------------- %%% %%% {5280, ejabberd_http, [ -%%% http_poll, +%%% http_poll, %%% web_admin, %%% {request_handlers, [{["pshb"], pshb_http}]} % this should be added %%% ]} @@ -34,383 +34,465 @@ %%% curl -u cstar@localhost:encore -i -X POST http://localhost:5280/pshb/localhost/foo -d @sam.atom %%% +-module(pshb_http). --module (pshb_http). -author('ecestari@process-one.net'). --compile({no_auto_import,[error/1]}). +-compile({no_auto_import, [{error, 1}]}). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("ejabberd_http.hrl"). -include("mod_pubsub/pubsub.hrl"). --export([process/2]). - -process([Domain | _Rest] = LocalPath, #request{auth = Auth} = Request)-> - UD = get_auth(Auth), - Module = backend(Domain), - case catch out(Module, Request, Request#request.method, LocalPath,UD) of - {'EXIT', Error} -> - ?ERROR_MSG("Error while processing ~p : ~n~p", [LocalPath, Error]), - error(500); - Result -> - Result - end. - +-export([process/2]). + +process([Domain | _Rest] = LocalPath, + #request{auth = Auth} = Request) -> + UD = get_auth(Auth), + Module = backend(Domain), + case catch out(Module, Request, Request#request.method, + LocalPath, UD) + of + {'EXIT', Error} -> + ?ERROR_MSG("Error while processing ~p : ~n~p", + [LocalPath, Error]), + error(500); + Result -> Result + end. + get_auth(Auth) -> case Auth of - {SJID, P} -> - case jlib:string_to_jid(SJID) of - error -> - undefined; - #jid{user = U, server = S} -> - case ejabberd_auth:check_password(U, S, P) of - true -> - {U, S}; - false -> - undefined - end - end; - _ -> - undefined + {SJID, P} -> + case jlib:string_to_jid(SJID) of + error -> undefined; + #jid{user = U, server = S} -> + case ejabberd_auth:check_password(U, S, P) of + true -> {U, S}; + false -> undefined + end + end; + _ -> undefined end. -out(Module, Args, 'GET', [Domain,Node]=Uri, _User) -> - case Module:tree_action(get_host(Uri), get_node, [get_host(Uri),get_collection(Uri)]) of - {error, Error} -> - error(Error); - #pubsub_node{options = Options}-> +out(Module, Args, 'GET', [Domain, Node] = Uri, _User) -> + case Module:tree_action(get_host(Uri), get_node, + [get_host(Uri), get_collection(Uri)]) + of + {error, Error} -> error(Error); + #pubsub_node{options = Options} -> AccessModel = lists:keyfind(access_model, 1, Options), case AccessModel of {access_model, open} -> - Items = lists:sort(fun(X,Y)-> - {DateX, _} = X#pubsub_item.modification, - {DateY, _} = Y#pubsub_item.modification, - DateX > DateY - end, Module:get_items( - get_host(Uri), - get_collection(Uri))), - case Items of - [] -> ?DEBUG("Items : ~p ~n", [collection(get_collection(Uri), - collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])]), - {200, [{"Content-Type", "application/atom+xml"}], - collection(get_collection(Uri), - collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])}; - _ -> - #pubsub_item{modification = {LastDate, _JID}} = LastItem = hd(Items), - Etag =generate_etag(LastItem), - IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers), - if IfNoneMatch==Etag - -> - success(304); - true -> - XMLEntries= [item_to_entry(Args,Domain, Node,Entry)||Entry <- Items], - {200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - ++ xml:element_to_string( - collection(get_collection(Uri), collection_uri(Args,Domain,Node), - calendar:now_to_universal_time(LastDate), "", XMLEntries))} - end - end; - {access_model, Access} -> - ?INFO_MSG("Uri ~p requested. access_model is ~p. HTTP access denied unless access_model =:= open", - [Uri, Access]), - error(?ERR_FORBIDDEN) - end - end; - -out(Module, Args, 'POST', [_D, _Node]=Uri, {_User, _Domain} = UD) -> - publish_item(Module, Args, Uri, uniqid(false), UD); - -out(Module, Args, 'PUT', [_D, _Node, Slug]=Uri, {_User, _Domain} = UD) -> - publish_item(Module, Args, Uri, Slug, UD); - -out(Module, _Args, 'DELETE', [_D, Node, Id]= Uri, {User, UDomain}) -> - Jid = jlib:make_jid({User, UDomain, ""}), - case Module:delete_item(get_host(Uri), list_to_binary(Node), Jid, Id) of - {error, Error} -> error(Error); - {result, _Res} -> success(200) - end; - - -out(Module, Args, 'PUT', [_Domain, Node]= Uri, {User, UDomain}) -> - Host = get_host(Uri), - Jid = jlib:make_jid({User, UDomain, ""}), - Payload = xml_stream:parse_element(Args#request.data), - ConfigureElement = case xml:get_subtag(Payload, "configure") of - false ->[]; - {xmlelement, _, _, SubEls}->SubEls - end, - case Module:set_configure(Host, list_to_binary(Node), Jid, ConfigureElement, Args#request.lang) of - {result, []} -> success(200); - {error, Error} -> error(Error) - end; - -out(Module, Args, 'GET', [Domain]=Uri, From)-> - Host = get_host(Uri), - ?DEBUG("Host = ~p", [Host]), - case Module:tree_action(Host, get_subnodes, [Host, <<>>, From ]) of - [] -> - ?DEBUG("Error getting URI ~p : ~p",[Uri, From]), - error(?ERR_ITEM_NOT_FOUND); - Collections -> - {200, [{"Content-Type", "application/atomsvc+xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" - ++ xml:element_to_string(service(Args,Domain, Collections))} - end; - -out(Module, Args, 'POST', [Domain]=Uri, {User, UDomain})-> - Host = get_host(Uri), - Payload = xml_stream:parse_element(Args#request.data), - {Node, Type} = case xml:get_subtag(Payload, "create") of - false -> {<<>>,"flat"}; - E -> - {list_to_binary(get_tag_attr_or_default("node", E,"")), - get_tag_attr_or_default("type", E,"flat")} - end, - ConfigureElement = case xml:get_subtag(Payload, "configure") of - false ->[]; - {xmlelement, _, _, SubEls}->SubEls - end, - Jid = jlib:make_jid({User, UDomain, ""}), - case Module:create_node(Host, Domain, Node, Jid, Type, all, ConfigureElement) of - {error, Error} -> - ?ERROR_MSG("Error create node via HTTP : ~p",[Error]), - error(Error); % will probably detail more - {result, [Result]} -> - {200, [{"Content-Type", "application/xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" - ++ xml:element_to_string(Result)} - end; - -out(Module,_Args, 'DELETE', [_Domain, Node] = Uri, {User, UDomain})-> - Host = get_host(Uri), - Jid = jlib:make_jid({User, UDomain, ""}), - BinNode = list_to_binary(Node), - case Module:delete_node(Host, BinNode, Jid) of - {error, Error} -> error(Error); - {result, []} -> - {200, [],[]} - end; - - - -out(Module, Args, 'GET', [Domain, Node, _Item]=URI, _) -> - Failure = fun(Error)-> - ?DEBUG("Error getting URI ~p : ~p",[URI, Error]), - error(Error) - end, - Success = fun(Item)-> - Etag =generate_etag(Item), - IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers), - if IfNoneMatch==Etag - -> - success(304); - true -> - {200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" - ++ xml:element_to_string(item_to_entry(Args, Domain,Node, Item))} - end - end, - get_item(Module, URI, Failure, Success); - -out(_Module,_,Method,Uri,undefined) -> - ?DEBUG("Error, ~p not authorized for ~p : ~p",[ Method,Uri]), - error(?ERR_FORBIDDEN). - -get_item(Module, Uri, Failure, Success)-> - ?DEBUG(" Module:get_item(~p, ~p,~p)", [get_host(Uri), get_collection(Uri), get_member(Uri)]), - case Module:get_item(get_host(Uri), get_collection(Uri), get_member(Uri)) of - {error, Reason} -> - Failure(Reason); - #pubsub_item{}=Item -> - Success(Item) - end. - -publish_item(Module, Args, [Domain, Node | _R] = Uri, Slug, {User, Domain})-> - - Payload = xml_stream:parse_element(Args#request.data), - [FilteredPayload]=xml:remove_cdata([Payload]), - - %FilteredPayload2 = case xml:get_subtag(FilteredPayload, "app:edited") -> - % {xmlelement, Name, Attrs, [{cdata, }]} - case Module:publish_item(get_host(Uri), - Domain, - get_collection(Uri), - jlib:make_jid(User,Domain, ""), - Slug, - [FilteredPayload]) of - {result, [_]} -> - ?DEBUG("Publishing to ~p~n",[entry_uri(Args, Domain, Node,Slug)]), - {201, [{"location", entry_uri(Args, Domain,Node,Slug)}], Payload}; - {error, Error} -> - error(Error) - end. - -generate_etag(#pubsub_item{modification={{_, D2, D3}, _JID}})->integer_to_list(D3+D2). -get_host([Domain|_Rest])-> "pubsub."++Domain. -get_collection([_Domain, Node|_Rest])->list_to_binary(Node). -get_member([_Domain, _Node, Member])-> - Member. + Items = lists:sort(fun (X, Y) -> + {DateX, _} = + X#pubsub_item.modification, + {DateY, _} = + Y#pubsub_item.modification, + DateX > DateY + end, + Module:get_items(get_host(Uri), + get_collection(Uri))), + case Items of + [] -> + ?DEBUG("Items : ~p ~n", + [collection(get_collection(Uri), + collection_uri(Args, Domain, Node), + calendar:now_to_universal_time(erlang:now()), + <<"">>, [])]), + {200, + [{<<"Content-Type">>, <<"application/atom+xml">>}], + collection(get_collection(Uri), + collection_uri(Args, Domain, Node), + calendar:now_to_universal_time(erlang:now()), + <<"">>, [])}; + _ -> + #pubsub_item{modification = {LastDate, _JID}} = + LastItem = hd(Items), + Etag = generate_etag(LastItem), + IfNoneMatch = proplists:get_value('If-None-Match', + Args#request.headers), + if IfNoneMatch == Etag -> success(304); + true -> + XMLEntries = [item_to_entry(Args, Domain, Node, + Entry) + || Entry <- Items], + {200, + [{<<"Content-Type">>, <<"application/atom+xml">>}, + {<<"Etag">>, Etag}], + <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n", + (xml:element_to_binary(collection(get_collection(Uri), + collection_uri(Args, + Domain, + Node), + calendar:now_to_universal_time(LastDate), + <<"">>, + XMLEntries)))/binary>>} + end + end; + {access_model, Access} -> + ?INFO_MSG("Uri ~p requested. access_model is ~p. " + "HTTP access denied unless access_model " + "=:= open", + [Uri, Access]), + error(?ERR_FORBIDDEN) + end + end; +out(Module, Args, 'POST', [_D, _Node] = Uri, + {_User, _Domain} = UD) -> + publish_item(Module, Args, Uri, uniqid(false), UD); +out(Module, Args, 'PUT', [_D, _Node, Slug] = Uri, + {_User, _Domain} = UD) -> + publish_item(Module, Args, Uri, Slug, UD); +out(Module, _Args, 'DELETE', [_D, Node, Id] = Uri, + {User, UDomain}) -> + Jid = jlib:make_jid({User, UDomain, <<"">>}), + case Module:delete_item(get_host(Uri), + iolist_to_binary(Node), Jid, Id) + of + {error, Error} -> error(Error); + {result, _Res} -> success(200) + end; +out(Module, Args, 'PUT', [_Domain, Node] = Uri, + {User, UDomain}) -> + Host = get_host(Uri), + Jid = jlib:make_jid({User, UDomain, <<"">>}), + Payload = xml_stream:parse_element(Args#request.data), + ConfigureElement = case xml:get_subtag(Payload, + <<"configure">>) + of + false -> []; + #xmlel{children = SubEls} -> SubEls + end, + case Module:set_configure(Host, iolist_to_binary(Node), + Jid, ConfigureElement, Args#request.lang) + of + {result, []} -> success(200); + {error, Error} -> error(Error) + end; +out(Module, Args, 'GET', [Domain] = Uri, From) -> + Host = get_host(Uri), + ?DEBUG("Host = ~p", [Host]), + case Module:tree_action(Host, get_subnodes, + [Host, <<>>, From]) + of + [] -> + ?DEBUG("Error getting URI ~p : ~p", [Uri, From]), + error(?ERR_ITEM_NOT_FOUND); + Collections -> + {200, + [{<<"Content-Type">>, <<"application/atomsvc+xml">>}], + <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>", + (xml:element_to_binary(service(Args, Domain, + Collections)))/binary>>} + end; +out(Module, Args, 'POST', [Domain] = Uri, + {User, UDomain}) -> + Host = get_host(Uri), + Payload = xml_stream:parse_element(Args#request.data), + {Node, Type} = case xml:get_subtag(Payload, + <<"create">>) + of + false -> {<<>>, <<"flat">>}; + E -> + {get_tag_attr_or_default(<<"node">>, E, <<"">>), + get_tag_attr_or_default(<<"type">>, E, <<"flat">>)} + end, + ConfigureElement = case xml:get_subtag(Payload, + <<"configure">>) + of + false -> []; + #xmlel{children = SubEls} -> SubEls + end, + Jid = jlib:make_jid({User, UDomain, <<"">>}), + case Module:create_node(Host, Domain, Node, Jid, Type, + all, ConfigureElement) + of + {error, Error} -> + ?ERROR_MSG("Error create node via HTTP : ~p", [Error]), + error(Error); + {result, [Result]} -> + {200, [{<<"Content-Type">>, <<"application/xml">>}], + <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>", + (xml:element_to_binary(Result))/binary>>} + end; +out(Module, _Args, 'DELETE', [_Domain, Node] = Uri, + {User, UDomain}) -> + Host = get_host(Uri), + Jid = jlib:make_jid({User, UDomain, <<"">>}), + case Module:delete_node(Host, Node, Jid) of + {error, Error} -> error(Error); + {result, []} -> {200, [], []} + end; +out(Module, Args, 'GET', [Domain, Node, _Item] = URI, + _) -> + Failure = fun (Error) -> + ?DEBUG("Error getting URI ~p : ~p", [URI, Error]), + error(Error) + end, + Success = fun (Item) -> + Etag = generate_etag(Item), + IfNoneMatch = proplists:get_value('If-None-Match', + Args#request.headers), + if IfNoneMatch == Etag -> success(304); + true -> + {200, + [{<<"Content-Type">>, <<"application/atom+xml">>}, + {<<"Etag">>, Etag}], + <<"<?xml version=\"1.0\" encoding=\"utf-8\"?>", + (xml:element_to_binary(item_to_entry(Args, + Domain, + Node, + Item)))/binary>>} + end + end, + get_item(Module, URI, Failure, Success); +out(_Module, _, Method, Uri, undefined) -> + ?DEBUG("Error, ~p not authorized for ~p : ~p", + [Method, Uri]), + error(?ERR_FORBIDDEN). + +get_item(Module, Uri, Failure, Success) -> + ?DEBUG(" Module:get_item(~p, ~p,~p)", + [get_host(Uri), get_collection(Uri), get_member(Uri)]), + case Module:get_item(get_host(Uri), get_collection(Uri), + get_member(Uri)) + of + {error, Reason} -> Failure(Reason); + #pubsub_item{} = Item -> Success(Item) + end. + +publish_item(Module, Args, [Domain, Node | _R] = Uri, + Slug, {User, Domain}) -> + Payload = xml_stream:parse_element(Args#request.data), + [FilteredPayload] = xml:remove_cdata([Payload]), + case Module:publish_item(get_host(Uri), Domain, + get_collection(Uri), + jlib:make_jid(User, Domain, <<"">>), Slug, + [FilteredPayload]) + of + {result, [_]} -> + ?DEBUG("Publishing to ~p~n", + [entry_uri(Args, Domain, Node, Slug)]), + {201, + [{<<"location">>, entry_uri(Args, Domain, Node, Slug)}], + Payload}; + {error, Error} -> error(Error) + end. + +generate_etag(#pubsub_item{modification = + {{_, D2, D3}, _JID}}) -> + jlib:integer_to_binary(D3 + D2). + +get_host([Domain | _Rest]) -> + <<"pubsub.", Domain/binary>>. + +get_collection([_Domain, Node | _Rest]) -> + Node. + +get_member([_Domain, _Node, Member]) -> Member. collection_uri(R, Domain, Node) -> - base_uri(R, Domain)++ "/" ++ b2l(Node). - -entry_uri(R,Domain, Node, Id)-> - collection_uri(R,Domain, Node)++"/"++b2l(Id). - -base_uri(#request{host=Host, port=Port}, Domain)-> - "http://"++Host++":"++i2l(Port)++"/pshb/" ++ Domain. - -item_to_entry(Args,Domain, Node,#pubsub_item{itemid={Id,_}, payload=Entry}=Item)-> - [R]=xml:remove_cdata(Entry), - item_to_entry(Args, Domain, Node, Id, R, Item). - -item_to_entry(Args,Domain, Node, Id,{xmlelement, "entry", Attrs, SubEl}, - #pubsub_item{modification={ Secs, JID} }) -> - Date = calendar:now_to_local_time(Secs), - {_User, Domain, _}=jlib:jid_tolower(JID), - SubEl2=[{xmlelement, "app:edited", [], [{xmlcdata, w3cdtf(Date)}]}, - {xmlelement, "updated", [],[{xmlcdata, w3cdtf(Date)}]}, - {xmlelement, "author", [],[{xmlelement, "name", [], [{xmlcdata, list_to_binary(jlib:jid_to_string(JID))}]}]}, - {xmlelement, "link",[{"rel", "edit"}, - {"href", entry_uri(Args,Domain,Node, Id)}],[] }, - {xmlelement, "id", [],[{xmlcdata, entry_uri(Args, Domain, Node, Id)}]} - | SubEl], - {xmlelement, "entry", [{"xmlns:app","http://www.w3.org/2007/app"}|Attrs], SubEl2}; - + <<(base_uri(R, Domain))/binary, "/", + Node/binary>>. + +entry_uri(R, Domain, Node, Id) -> + <<(collection_uri(R, Domain, Node))/binary, "/", + Id/binary>>. + +base_uri(#request{host = Host, port = Port}, Domain) -> + <<"http://", Host/binary, ":", (i2l(Port))/binary, + "/pshb/", Domain/binary>>. + +item_to_entry(Args, Domain, Node, + #pubsub_item{itemid = {Id, _}, payload = Entry} = + Item) -> + [R] = xml:remove_cdata(Entry), + item_to_entry(Args, Domain, Node, Id, R, Item). + +item_to_entry(Args, Domain, Node, Id, + #xmlel{name = <<"entry">>, attrs = Attrs, + children = SubEl}, + #pubsub_item{modification = {Secs, JID}}) -> + Date = calendar:now_to_local_time(Secs), + {_User, Domain, _} = jlib:jid_tolower(JID), + SubEl2 = [#xmlel{name = <<"app:edited">>, attrs = [], + children = [{xmlcdata, w3cdtf(Date)}]}, + #xmlel{name = <<"updated">>, attrs = [], + children = [{xmlcdata, w3cdtf(Date)}]}, + #xmlel{name = <<"author">>, attrs = [], + children = + [#xmlel{name = <<"name">>, attrs = [], + children = + [{xmlcdata, + jlib:jid_to_string(JID)}]}]}, + #xmlel{name = <<"link">>, + attrs = + [{<<"rel">>, <<"edit">>}, + {<<"href">>, entry_uri(Args, Domain, Node, Id)}], + children = []}, + #xmlel{name = <<"id">>, attrs = [], + children = + [{xmlcdata, entry_uri(Args, Domain, Node, Id)}]} + | SubEl], + #xmlel{name = <<"entry">>, + attrs = + [{<<"xmlns:app">>, <<"http://www.w3.org/2007/app">>} + | Attrs], + children = SubEl2}; % Don't do anything except adding xmlns -item_to_entry(_Args,_Domain, Node, _Id, {xmlelement, Name, Attrs, Subels}=Element, _Item)-> - case proplists:is_defined("xmlns",Attrs) of - true -> Element; - false -> {xmlelement, Name, [{"xmlns", Node}|Attrs], Subels} - end. - -collection(Title, Link, Updated, _Id, Entries)-> - {xmlelement, "feed", [{"xmlns", "http://www.w3.org/2005/Atom"}, - {"xmlns:app", "http://www.w3.org/2007/app"}], [ - {xmlelement, "title", [],[{xmlcdata, Title}]}, - {xmlelement, "generator", [],[{xmlcdata, <<"ejabberd">>}]}, - {xmlelement, "updated", [],[{xmlcdata, w3cdtf(Updated)}]}, - {xmlelement, "link", [{"href", Link}, {"rel", "self"}], []}, - {xmlelement, "id", [], [{xmlcdata, list_to_binary(Link)}]}, - {xmlelement, "title", [],[{xmlcdata, Title}]} | - Entries - ]}. - -service(Args, Domain,Collections)-> - {xmlelement, "service", [{"xmlns", "http://www.w3.org/2007/app"}, - {"xmlns:atom", "http://www.w3.org/2005/Atom"}, - {"xmlns:app", "http://www.w3.org/2007/app"}],[ - {xmlelement, "workspace", [],[ - {xmlelement, "atom:title", [],[{xmlcdata,"Pubsub node Feed for " ++Domain}]} | - lists:map(fun(#pubsub_node{nodeid={_Server, Id}, type=_Type})-> - {xmlelement, "collection", [{"href", collection_uri(Args,Domain, Id)}], [ - {xmlelement, "atom:title", [], [{xmlcdata, Id}]} - ]} - end, Collections) - ]} - ]}. - -%% simple output functions -error({xmlelement, "error", Attrs, _}=Error) -> - Value = list_to_integer(xml:get_attr_s("code", Attrs)), - {Value, [{"Content-type", "application/xml"}], xml:element_to_string(Error)}; -error(404)-> - {404, [], "Not Found"}; -error(403)-> - {403, [], "Forbidden"}; -error(500)-> - {500, [], "Internal server error"}; -error(401)-> - {401, [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],"Unauthorized"}; -error(Code)-> - {Code, [], ""}. -success(200)-> - {200, [], ""}; -success(Code)-> - {Code, [], ""}. - -backend(Domain)-> - Modules = gen_mod:loaded_modules(Domain), - case lists:member(mod_pubsub_odbc, Modules) of - true -> mod_pubsub_odbc; - _ -> mod_pubsub - end. - - -% Code below is taken (with some modifications) from the yaws webserver, which -% is distributed under the folowing license: -% -% This software (the yaws webserver) is free software. -% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org> -% Any use or misuse of the source code is hereby freely allowed. -% -% 1. Redistributions of source code must retain the above copyright -% notice as well as this list of conditions. -% -% 2. Redistributions in binary form must reproduce the above copyright -% notice as well as this list of conditions. -%%% Create W3CDTF (http://www.w3.org/TR/NOTE-datetime) formatted date -%%% w3cdtf(GregSecs) -> "YYYY-MM-DDThh:mm:ssTZD" -%%% -uniqid(false)-> - {T1, T2, T3} = now(), - lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3])); -uniqid(Slug) -> - Slut = string:to_lower(Slug), - S = string:substr(Slut, 1, 9), - {_T1, T2, T3} = now(), - lists:flatten(io_lib:fwrite("~s-~.16B~.16B", [S, T2, T3])). - -w3cdtf(Date) -> %1 Date = calendar:gregorian_seconds_to_datetime(GregSecs), - {{Y, Mo, D},{H, Mi, S}} = Date, - [UDate|_] = calendar:local_time_to_universal_time_dst(Date), - {DiffD,{DiffH,DiffMi,_}}=calendar:time_difference(UDate,Date), - w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi). - -%%% w3cdtf's helper function -w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) when DiffH < 12, DiffH /= 0 -> - i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++ - add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++ - add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++ add_zero(DiffMi); - -w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD == 0 -> - i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++ - add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++ - add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++ - add_zero(DiffMi); - -w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi /= 0 -> - i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++ - add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++ - add_zero(S) ++ "-" ++ add_zero(23-DiffH) ++ - ":" ++ add_zero(60-DiffMi); - -w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi == 0 -> - i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++ - add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++ - add_zero(S) ++ "-" ++ add_zero(24-DiffH) ++ - ":" ++ add_zero(DiffMi); - -w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) when DiffH == 0 -> - i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++ - add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++ - add_zero(S) ++ "Z". +item_to_entry(_Args, _Domain, Node, _Id, + #xmlel{name = Name, attrs = Attrs, children = Subels} = + Element, + _Item) -> + case proplists:is_defined(<<"xmlns">>, Attrs) of + true -> Element; + false -> + #xmlel{name = Name, + attrs = [{<<"xmlns">>, Node} | Attrs], + children = Subels} + end. -add_zero(I) when is_integer(I) -> add_zero(i2l(I)); -add_zero([A]) -> [$0,A]; -add_zero(L) when is_list(L) -> L. +collection(Title, Link, Updated, _Id, Entries) -> + #xmlel{name = <<"feed">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/2005/Atom">>}, + {<<"xmlns:app">>, <<"http://www.w3.org/2007/app">>}], + children = + [#xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, Title}]}, + #xmlel{name = <<"generator">>, attrs = [], + children = [{xmlcdata, <<"ejabberd">>}]}, + #xmlel{name = <<"updated">>, attrs = [], + children = [{xmlcdata, w3cdtf(Updated)}]}, + #xmlel{name = <<"link">>, + attrs = [{<<"href">>, Link}, {<<"rel">>, <<"self">>}], + children = []}, + #xmlel{name = <<"id">>, attrs = [], + children = [{xmlcdata, iolist_to_binary(Link)}]}, + #xmlel{name = <<"title">>, attrs = [], + children = [{xmlcdata, Title}]} + | Entries]}. + +service(Args, Domain, Collections) -> + #xmlel{name = <<"service">>, + attrs = + [{<<"xmlns">>, <<"http://www.w3.org/2007/app">>}, + {<<"xmlns:atom">>, <<"http://www.w3.org/2005/Atom">>}, + {<<"xmlns:app">>, <<"http://www.w3.org/2007/app">>}], + children = + [#xmlel{name = <<"workspace">>, attrs = [], + children = + [#xmlel{name = <<"atom:title">>, attrs = [], + children = + [{xmlcdata, + <<"Pubsub node Feed for ", + Domain/binary>>}]} + | lists:map(fun (#pubsub_node{nodeid = + {_Server, Id}, + type = _Type}) -> + #xmlel{name = <<"collection">>, + attrs = + [{<<"href">>, + collection_uri(Args, + Domain, + Id)}], + children = + [#xmlel{name = + <<"atom:title">>, + attrs = [], + children = + [{xmlcdata, + Id}]}]} + end, + Collections)]}]}. + +error(#xmlel{name = <<"error">>, attrs = Attrs} = + Error) -> + Value = + jlib:binary_to_integer(xml:get_attr_s(<<"code">>, + Attrs)), + {Value, [{<<"Content-type">>, <<"application/xml">>}], + xml:element_to_binary(Error)}; +error(404) -> {404, [], <<"Not Found">>}; +error(403) -> {403, [], <<"Forbidden">>}; +error(500) -> {500, [], <<"Internal server error">>}; +error(401) -> + {401, + [{<<"WWW-Authenticate">>, + <<"basic realm=\"ejabberd\"">>}], + <<"Unauthorized">>}; +error(Code) -> {Code, [], <<"">>}. + +success(200) -> {200, [], <<"">>}; +success(Code) -> {Code, [], <<"">>}. + +backend(Domain) -> + Modules = gen_mod:loaded_modules(Domain), + case lists:member(mod_pubsub_odbc, Modules) of + true -> mod_pubsub_odbc; + _ -> mod_pubsub + end. +uniqid(false) -> + {T1, T2, T3} = now(), + list_to_binary(io_lib:fwrite("~.16B~.16B~.16B", + [T1, T2, T3])). +w3cdtf(Date) -> %1 Date = calendar:gregorian_seconds_to_datetime(GregSecs), + {{Y, Mo, D}, {H, Mi, S}} = Date, + [UDate | _] = + calendar:local_time_to_universal_time_dst(Date), + {DiffD, {DiffH, DiffMi, _}} = + calendar:time_difference(UDate, Date), + w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi). + +w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) + when DiffH < 12, DiffH /= 0 -> + <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-", + (add_zero(D))/binary, "T", (add_zero(H))/binary, ":", + (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "+", + (add_zero(DiffH))/binary, ":", + (add_zero(DiffMi))/binary>>; +w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) + when DiffH > 12, DiffD == 0 -> + <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-", + (add_zero(D))/binary, "T", (add_zero(H))/binary, ":", + (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "+", + (add_zero(DiffH))/binary, ":", + (add_zero(DiffMi))/binary>>; +w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) + when DiffH > 12, DiffD /= 0, DiffMi /= 0 -> + <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-", + (add_zero(D))/binary, "T", (add_zero(H))/binary, ":", + (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "-", + (add_zero(23 - DiffH))/binary, ":", + (add_zero(60 - DiffMi))/binary>>; +w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) + when DiffH > 12, DiffD /= 0, DiffMi == 0 -> + <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-", + (add_zero(D))/binary, "T", (add_zero(H))/binary, ":", + (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "-", + (add_zero(24 - DiffH))/binary, ":", + (add_zero(DiffMi))/binary>>; +w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) + when DiffH == 0 -> + <<(i2l(Y))/binary, "-", (add_zero(Mo))/binary, "-", + (add_zero(D))/binary, "T", (add_zero(H))/binary, ":", + (add_zero(Mi))/binary, ":", (add_zero(S))/binary, "Z">>. -i2l(I) when is_integer(I) -> integer_to_list(I); -i2l(L) when is_list(L) -> L. +add_zero(I) when is_integer(I) -> add_zero(i2l(I)); +add_zero(<<A>>) -> <<$0, A>>; +add_zero(L) when is_binary(L) -> L. -b2l(B) when is_binary(B) -> binary_to_list(B); -b2l(L) when is_list(L) -> L. +i2l(I) when is_integer(I) -> + jlib:integer_to_binary(I). -get_tag_attr_or_default(AttrName, Element, Default)-> - case xml:get_tag_attr_s(AttrName, Element) of - "" -> Default; - Val -> Val - end. +get_tag_attr_or_default(AttrName, Element, Default) -> + case xml:get_tag_attr_s(AttrName, Element) of + <<"">> -> Default; + Val -> Val + end. diff --git a/src/web/simple_ws_check.erl b/src/web/simple_ws_check.erl index 8ef160980..d897b1ecc 100644 --- a/src/web/simple_ws_check.erl +++ b/src/web/simple_ws_check.erl @@ -1,11 +1,20 @@ --module (simple_ws_check). --export ([is_acceptable/6]). +-module(simple_ws_check). + +-export([is_acceptable/6]). + -include("ejabberd.hrl"). -is_acceptable(["true"]=Path, Q, Origin, Protocol, IP, Headers)-> - ?INFO_MSG("Authorized Websocket ~p with: ~n Q = ~p~n Origin = ~p~n Protocol = ~p~n IP = ~p~n Headers = ~p~n", - [Path, Q, Origin, Protocol, IP, Headers]), + +is_acceptable([<<"true">>] = Path, Q, Origin, Protocol, + IP, Headers) -> + ?INFO_MSG("Authorized Websocket ~p with: ~n Q = " + "~p~n Origin = ~p~n Protocol = ~p~n IP " + "= ~p~n Headers = ~p~n", + [Path, Q, Origin, Protocol, IP, Headers]), true; -is_acceptable(["false"]=Path, Q, Origin, Protocol, IP, Headers)-> - ?INFO_MSG("Failed Websocket ~p with: ~n Q = ~p~n Origin = ~p~n Protocol = ~p~n IP = ~p~n Headers = ~p~n", - [Path, Q, Origin, Protocol, IP, Headers]), - false.
\ No newline at end of file +is_acceptable([<<"false">>] = Path, Q, Origin, Protocol, + IP, Headers) -> + ?INFO_MSG("Failed Websocket ~p with: ~n Q = ~p~n " + "Origin = ~p~n Protocol = ~p~n IP = ~p~n " + "Headers = ~p~n", + [Path, Q, Origin, Protocol, IP, Headers]), + false. diff --git a/src/web/websocket_test.erl b/src/web/websocket_test.erl index b5491bc08..cbe65392a 100644 --- a/src/web/websocket_test.erl +++ b/src/web/websocket_test.erl @@ -1,19 +1,14 @@ --module (websocket_test). +-module(websocket_test). + -export([start_link/1, loop/1]). -% callback on received websockets data start_link(Ws) -> - Pid = spawn_link(?MODULE, loop, [Ws]), - {ok, Pid}. + Pid = spawn_link(?MODULE, loop, [Ws]), {ok, Pid}. loop(Ws) -> - receive - {browser, Data} -> - Ws:send(["received '", Data, "'"]), - loop(Ws); - _Ignore -> - loop(Ws) - after 5000 -> - Ws:send("pushing!"), - loop(Ws) - end. + receive + {browser, Data} -> + Ws:send([<<"received '">>, Data, <<"'">>]), loop(Ws); + _Ignore -> loop(Ws) + after 5000 -> Ws:send(<<"pushing!">>), loop(Ws) + end. diff --git a/src/web/xmpp_json.erl b/src/web/xmpp_json.erl index 6fae2ab08..258d573cc 100644 --- a/src/web/xmpp_json.erl +++ b/src/web/xmpp_json.erl @@ -1,8 +1,8 @@ %%%---------------------------------------------------------------------- %%% File : xmpp_json.erl %%% Author : Eric Cestari <ecestari@process-one.net> -%%% Purpose : Converts {xmlelement,Name, A, Sub} to/from JSON as per protoxep -%%% Created : 09-20-2010 +%%% Purpose : Converts {xmlelement,Name, A, Sub} to/from JSON as per protoxep +%%% Created : 09-20-2010 %%% %%% %%% ejabberd, Copyright (C) 2002-2010 ProcessOne @@ -22,341 +22,396 @@ %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA %%% 02111-1307 USA --module (xmpp_json). +-module(xmpp_json). -export([to_json/1, from_json/1]). +-include("jlib.hrl"). - %%% FROM JSON TO XML -from_json({struct, [{<<"stream">>, _Attr}]=Elems}) -> - parse_start(Elems); - -from_json({struct, Elems}) -> - {xmlstreamelement, hd(from_json2({struct, Elems}))}. - -from_json2({struct, Elems}) -> - lists:map(fun parse_json/1, Elems). - -parse_start([{BinName, {struct, JAttrs}}]) -> - Name = binary_to_list(BinName), - {FullName, Attrs} = lists:foldl( - fun({<<"xml">>, {struct, XML}}, {N, Attrs}) -> - XmlAttrs = parse_json_special_attrs("xml", XML), - {N, lists:merge(Attrs, XmlAttrs)}; - ({<<"xmlns">>, {struct, XMLNS}}, {N, Attrs}) -> - XmlNsAttrs = parse_json_special_attrs("xmlns", XMLNS), - {N, lists:merge(Attrs, XmlNsAttrs)}; - ({<<"$$">>, BaseNS}, {N, Attrs})-> - {binary_to_list(BaseNS)++":"++N, Attrs}; - ({Key, Value}, {N, Attrs})-> - {N, [{ib2tol(Key), ib2tol(Value)}|Attrs]} - end, {Name, []}, JAttrs), - {xmlstreamstart, FullName, Attrs}. - -parse_json({Name, CData}) when is_binary(CData)-> - {xmlelement, binary_to_list(Name), [], [{xmlcdata, CData}]}; - -parse_json({Name, CDatas}) when is_list(CDatas)-> - lists:map(fun(CData)-> - {xmlelement, binary_to_list(Name), [], [{xmlcdata, CData}]} - end, CDatas); - -parse_json({BinName, {struct, JAttrs}}) -> - Name = binary_to_list(BinName), - {FullName, Attrs, SubEls} = lists:foldl( - fun({<<"$">>, Cdata}, {N, Attrs, _SubEls}) when is_binary(Cdata)-> - {N, Attrs, [{xmlcdata, Cdata}]}; - ({<<"$">>, {struct, Elems}}, {N, Attrs, _SubEls}) -> - SE = lists:map(fun parse_json/1, Elems), - {N, Attrs, lists:flatten(SE)}; % due to 4.2.3.3 - ({<<"xml">>, {struct, XML}}, {N, Attrs, SubEls}) -> - XmlAttrs = parse_json_special_attrs("xml", XML), - {N, lists:merge(Attrs, XmlAttrs), SubEls}; - ({<<"xmlns">>, {struct, XMLNS}}, {N, Attrs, SubEls}) -> - XmlNsAttrs = parse_json_special_attrs("xmlns", XMLNS), - {N, lists:merge(Attrs, XmlNsAttrs), SubEls}; - ({Key, {struct, []}}, {N, Attrs, SubEls})-> - {N, Attrs, [{xmlelement, ib2tol(Key), [], []}|SubEls]}; - ({Key, Value}, {N, Attrs, SubEls})-> - {N, [{binary_to_list(Key), ib2tol(Value)}|Attrs], SubEls} - end, {Name, [], []}, JAttrs), - {xmlelement, FullName, Attrs, SubEls}. - -parse_json_special_attrs(Prefix, XMLNS)-> - lists:reverse(lists:map( - fun({<<"$">>, Value})-> - {Prefix, ib2tol(Value)}; - ({<<"@",NS/binary>>, Value})-> - {Prefix ++ ":"++binary_to_list(NS), ib2tol(Value)} - end, XMLNS)). - -%%% FROM XML TO JSON -to_json({xmlstreamelement, XMLElement})-> - to_json(XMLElement); -to_json({xmlelement, _Name, [], []})-> - {struct, []}; -to_json({xmlelement, Name, [], [{xmlcdata, Cdata}]})-> - {SName, JsonAttrs2} = parse_namespace(Name, []), - {struct, [{SName, Cdata}|JsonAttrs2]}; -to_json({xmlstreamstart, Name, Attrs})-> - JsonAttrs = parse_attrs(Attrs), - {SName, Members2} = parse_namespace(Name, JsonAttrs), - {struct, [{SName, {struct, Members2}}]}; -to_json({xmlelement, Name, Attrs, SubEls})-> - JsonAttrs = parse_attrs(Attrs), - Members = case parse_subels(SubEls) of - [] -> - JsonAttrs; - [Elem] -> - [{<<"$">>,Elem}|JsonAttrs]; - Elems -> - [{<<"$">>,Elems}|JsonAttrs] - end, - {SName, Members2} = parse_namespace(Name, Members), - {struct, [{SName, {struct, Members2}}]}. - -parse_namespace(Name, AttrsList)-> - {l2b(Name), AttrsList}. - -parse_subels([{xmlcdata, Cdata}])-> - l2b(Cdata); -parse_subels([])-> - []; -parse_subels(SubEls)-> - {struct, lists:reverse(lists:foldl( - fun({xmlelement, SName, [], [{xmlcdata, UCdata}]}, Acc)-> - Cdata = l2b(UCdata), - Name = l2b(SName), - case lists:keyfind(Name, 1, Acc) of - {Name, PrevCdata} when is_binary(PrevCdata) -> - Acc1 = lists:keydelete(Name, 1, Acc), - [{Name,[PrevCdata, Cdata]} | Acc1]; - {Name, CDatas} when is_list(CDatas) -> - Acc1 = lists:keydelete(Name, 1, Acc), - [{Name,lists:append(CDatas, [Cdata])} | Acc1]; - _ -> - [{Name, Cdata}| Acc] - end; - ({xmlelement, SName, _, _} = Elem, Acc) -> - E = case to_json(Elem) of %TODO There could be a better way to iterate - {struct, [{_, ToKeep}]} -> ToKeep; - {struct, []} = Empty -> Empty - end, - [{l2b(SName), E}|Acc]; - ({xmlcdata,<<"\n">>}, Acc) -> - Acc - end,[], SubEls))}. - - -parse_attrs(XmlAttrs)-> - {Normal, XMLNS} = lists:foldl( - fun({"xmlns", NS}, {Attrs, XMLNS}) -> - {Attrs,[{<<"$">>, l2b(NS)}| XMLNS]}; - ({"xmlns:" ++ Short, NS}, {Attrs, XMLNS})-> - AttrName = iolist_to_binary([<<"@">>,l2b(Short)]), - {Attrs,[{AttrName, list_to_binary(NS)}| XMLNS]}; - ({"xml:" ++ Short, Val}, {Attrs, XMLNS})-> - % TODO currently tolerates only one xml:* attr per element - AttrName = iolist_to_binary([<<"@">>,l2b(Short)]), - {[{<<"xml">>,{struct, [{AttrName, l2b(Val)}]}}|Attrs], XMLNS}; - ({K, V}, {Attrs, XMLNS})-> - {[{l2b(K), l2b(V)}|Attrs], XMLNS} - end,{[], []}, XmlAttrs), - - case XMLNS of - [{<<"$">>, NS}]-> - [{<<"xmlns">>, NS}|Normal]; - []-> - Normal; - _ -> - [{<<"xmlns">>,{struct, XMLNS} }| Normal] - end. - -l2b(List) when is_list(List) -> list_to_binary(List); -l2b(Bin) when is_binary(Bin) -> Bin. - -ib2tol(Bin) when is_binary(Bin) -> binary_to_list(Bin ); -ib2tol(Integer) when is_integer(Integer) -> integer_to_list(Integer); -ib2tol(List) when is_list(List) -> List. +from_json({[{<<"stream">>, _Attr}] = Elems}) -> + parse_start(Elems); +from_json({Elems}) -> + {xmlstreamelement, hd(from_json2({Elems}))}. + +from_json2({Elems}) -> + lists:map(fun parse_json/1, Elems). + +parse_start([{BinName, {JAttrs}}]) -> + Name = (BinName), + {FullName, Attrs} = lists:foldl(fun ({<<"xml">>, + {XML}}, + {N, Attrs}) -> + XmlAttrs = + parse_json_special_attrs(<<"xml">>, + XML), + {N, lists:merge(Attrs, XmlAttrs)}; + ({<<"xmlns">>, {XMLNS}}, + {N, Attrs}) -> + XmlNsAttrs = + parse_json_special_attrs(<<"xmlns">>, + XMLNS), + {N, lists:merge(Attrs, XmlNsAttrs)}; + ({<<"$$">>, BaseNS}, {N, Attrs}) -> + {<<((BaseNS))/binary, ":", + N/binary>>, + Attrs}; + ({Key, Value}, {N, Attrs}) -> + {N, + [{ib2tol(Key), ib2tol(Value)} + | Attrs]} + end, + {Name, []}, JAttrs), + {xmlstreamstart, FullName, Attrs}. + +parse_json({Name, CData}) when is_binary(CData) -> + #xmlel{name = (Name), attrs = [], + children = [{xmlcdata, CData}]}; +parse_json({Name, CDatas}) when is_list(CDatas) -> + lists:map(fun (CData) -> + #xmlel{name = (Name), attrs = [], + children = [{xmlcdata, CData}]} + end, + CDatas); +parse_json({BinName, {JAttrs}}) -> + Name = (BinName), + {FullName, Attrs, SubEls} = lists:foldl(fun ({<<"$">>, + Cdata}, + {N, Attrs, _SubEls}) + when is_binary(Cdata) -> + {N, Attrs, + [{xmlcdata, Cdata}]}; + ({<<"$">>, {Elems}}, + {N, Attrs, _SubEls}) -> + SE = + lists:map(fun parse_json/1, + Elems), + {N, Attrs, + lists:flatten(SE)}; + ({<<"xml">>, {XML}}, + {N, Attrs, SubEls}) -> + XmlAttrs = + parse_json_special_attrs(<<"xml">>, + XML), + {N, + lists:merge(Attrs, + XmlAttrs), + SubEls}; + ({<<"xmlns">>, {XMLNS}}, + {N, Attrs, SubEls}) -> + XmlNsAttrs = + parse_json_special_attrs(<<"xmlns">>, + XMLNS), + {N, + lists:merge(Attrs, + XmlNsAttrs), + SubEls}; + ({Key, {[]}}, + {N, Attrs, SubEls}) -> + {N, Attrs, + [#xmlel{name = ib2tol(Key), + attrs = [], + children = []} + | SubEls]}; + ({Key, Value}, + {N, Attrs, SubEls}) -> + {N, + [{(Key), ib2tol(Value)} + | Attrs], + SubEls} + end, + {Name, [], []}, JAttrs), + #xmlel{name = FullName, attrs = Attrs, + children = SubEls}. + +parse_json_special_attrs(Prefix, XMLNS) -> + lists:reverse(lists:map(fun ({<<"$">>, Value}) -> + {Prefix, ib2tol(Value)}; + ({<<"@", NS/binary>>, Value}) -> + {<<Prefix/binary, ":", ((NS))/binary>>, + ib2tol(Value)} + end, + XMLNS)). + +to_json({xmlstreamelement, XMLElement}) -> + to_json(XMLElement); +to_json(#xmlel{attrs = [], children = []}) -> + {[]}; +to_json(#xmlel{name = Name, attrs = [], + children = [{xmlcdata, Cdata}]}) -> + {SName, JsonAttrs2} = parse_namespace(Name, []), + {[{SName, Cdata} | JsonAttrs2]}; +to_json({xmlstreamstart, Name, Attrs}) -> + JsonAttrs = parse_attrs(Attrs), + {SName, Members2} = parse_namespace(Name, JsonAttrs), + {[{SName, {Members2}}]}; +to_json(#xmlel{name = Name, attrs = Attrs, + children = SubEls}) -> + JsonAttrs = parse_attrs(Attrs), + Members = case parse_subels(SubEls) of + [] -> JsonAttrs; + Elems -> [{<<"$">>, Elems} | JsonAttrs] + end, + {SName, Members2} = parse_namespace(Name, Members), + {[{SName, {Members2}}]}. + +parse_namespace(Name, AttrsList) -> + {Name, AttrsList}. + +parse_subels([{xmlcdata, Cdata}]) -> Cdata; +parse_subels([]) -> []; +parse_subels(SubEls) -> + {lists:reverse(lists:foldl(fun (#xmlel{name = SName, + attrs = [], + children = [{xmlcdata, UCdata}]}, + Acc) -> + Cdata = UCdata, + Name = SName, + case lists:keyfind(Name, 1, Acc) of + {Name, PrevCdata} + when is_binary(PrevCdata) -> + Acc1 = lists:keydelete(Name, 1, + Acc), + [{Name, [PrevCdata, Cdata]} + | Acc1]; + {Name, CDatas} + when is_list(CDatas) -> + Acc1 = lists:keydelete(Name, 1, + Acc), + [{Name, + lists:append(CDatas, [Cdata])} + | Acc1]; + _ -> [{Name, Cdata} | Acc] + end; + (#xmlel{name = SName} = Elem, Acc) -> + E = case to_json(Elem) of + {[{_, ToKeep}]} -> ToKeep; + {[]} = Empty -> Empty + end, + [{SName, E} | Acc]; + ({xmlcdata, <<"\n">>}, Acc) -> Acc + end, + [], SubEls))}. + +parse_attrs(XmlAttrs) -> + {Normal, XMLNS} = lists:foldl(fun ({<<"xmlns">>, NS}, + {Attrs, XMLNS}) -> + {Attrs, [{<<"$">>, NS} | XMLNS]}; + ({<<"xmlns:", Short/binary>>, NS}, + {Attrs, XMLNS}) -> + AttrName = <<$@, Short/binary>>, + {Attrs, + [{AttrName, NS} + | XMLNS]}; + ({<<"xml:", Short/binary>>, Val}, + {Attrs, XMLNS}) -> + AttrName = <<$@, Short/binary>>, + {[{<<"xml">>, + {[{AttrName, Val}]}} + | Attrs], + XMLNS}; + ({K, V}, {Attrs, XMLNS}) -> + {[{K, V} | Attrs], XMLNS} + end, + {[], []}, XmlAttrs), + case XMLNS of + [{<<"$">>, NS}] -> [{<<"xmlns">>, NS} | Normal]; + [] -> Normal; + _ -> [{<<"xmlns">>, {XMLNS}} | Normal] + end. + +ib2tol(Bin) when is_binary(Bin) -> Bin; +ib2tol(Integer) when is_integer(Integer) -> + jlib:integer_to_binary(Integer). %% %% Tests %% erlc -DTEST web/xmpp_json.erl && erl -pa web/ -run xmpp_json test -run init stop -noshell -include_lib("eunit/include/eunit.hrl"). + -ifdef(TEST). -% 4.2.3.1 Tag with text value -to_text_value_test()-> - In = {xmlstreamelement, {xmlelement, "tag", [], [{xmlcdata, <<"txt-value">>}]}}, - Out = {struct, [{<<"tag">>, <<"txt-value">>}]}, - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -% 4.2.3.2 Tag with recursive tags -to_tag_with_recursive_tags_test()-> - In = {xmlstreamelement, {xmlelement, "tag", [], - [{xmlelement,"tag2",[], [{xmlcdata, <<"txt-value">>}]}, - {xmlelement,"tag3",[], [ - {xmlelement,"tag4",[], [{xmlcdata, <<"txt2-value">>}]}]}]}}, - Out = {struct, [{<<"tag">>, - {struct, [{<<"$">>, - {struct, [ - {<<"tag2">>,<<"txt-value">>}, - {<<"tag3">>,{struct, [{<<"$">>,{struct, [{<<"tag4">>,<<"txt2-value">>}]}}]}} - ]} - }]} - }] - }, - %io:format("~n~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - io:format("~n~p", [from_json(Out)]), - io:format("~n~p", [to_json(In)]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -% 4.2.3.3 Multiple text value tags as array -multiple_text_value_tags_as_array_test()-> - In = {xmlstreamelement, {xmlelement, "tag", [], [ - {xmlelement,"tag2",[], [ - {xmlcdata, <<"txt-value">>}]}, - {xmlelement,"tag2",[], [ - {xmlcdata, <<"txt-value2">>}]}]}}, - Out = {struct, [{<<"tag">>, - {struct, [{<<"$">>, - {struct, [{<<"tag2">>, - [<<"txt-value">>, <<"txt-value2">>]}]} - }]} - }] - }, - io:format("~p~n", [to_json(In)]), - io:format("~p~n", [from_json(Out)]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -% 4.2.3.4 Tag with attribute, no value +to_text_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, attrs = [], + children = [{xmlcdata, <<"txt-value">>}]}}, + Out = {[{<<"tag">>, <<"txt-value">>}]}, + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +to_tag_with_recursive_tags_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, attrs = [], + children = + [#xmlel{name = <<"tag2">>, attrs = [], + children = [{xmlcdata, <<"txt-value">>}]}, + #xmlel{name = <<"tag3">>, attrs = [], + children = + [#xmlel{name = <<"tag4">>, attrs = [], + children = + [{xmlcdata, + <<"txt2-value">>}]}]}]}}, + Out = {[{<<"tag">>, + {[{<<"$">>, + {[{<<"tag2">>, <<"txt-value">>}, + {<<"tag3">>, + {[{<<"$">>, + {[{<<"tag4">>, <<"txt2-value">>}]}}]}}]}}]}}]}, + io:format("~n~p", [from_json(Out)]), + io:format("~n~p", [to_json(In)]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +multiple_text_value_tags_as_array_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, attrs = [], + children = + [#xmlel{name = <<"tag2">>, attrs = [], + children = [{xmlcdata, <<"txt-value">>}]}, + #xmlel{name = <<"tag2">>, attrs = [], + children = [{xmlcdata, <<"txt-value2">>}]}]}}, + Out = {[{<<"tag">>, + {[{<<"$">>, + {[{<<"tag2">>, + [<<"txt-value">>, <<"txt-value2">>]}]}}]}}]}, + io:format("~p~n", [to_json(In)]), + io:format("~p~n", [from_json(Out)]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + tag_attr_no_value_test() -> - In = {xmlstreamelement, {xmlelement, "tag", [{"attr", "attr-value"}], []}}, - Out = {struct, [{<<"tag">>, {struct, [ - {<<"attr">>,<<"attr-value">>} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - io:format("~p", [from_json(Out)]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, + attrs = [{<<"attr">>, <<"attr-value">>}], + children = []}}, + Out = {[{<<"tag">>, + {[{<<"attr">>, <<"attr-value">>}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + io:format("~p", [from_json(Out)]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + % 4.2.3.5 Tag with multiple attributes as array, no value % Not wellformed XML. % 4.2.3.6 Tags as array with unique attributes, no value +tag_with_namespace_no_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, + attrs = [{<<"xmlns:ns">>, <<"ns-value">>}], + children = []}}, + Out = {[{<<"tag">>, + {[{<<"xmlns">>, + {[{<<"@ns">>, <<"ns-value">>}]}}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +two_namespaces_tag_no_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, + attrs = + [{<<"xmlns:ns">>, <<"ns-value">>}, + {<<"xmlns">>, <<"root-value">>}], + children = []}}, + Out = {[{<<"tag">>, + {[{<<"xmlns">>, + {[{<<"$">>, <<"root-value">>}, + {<<"@ns">>, <<"ns-value">>}]}}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +namespaced_tag_no_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"ns:tag">>, + attrs = [{<<"attr">>, <<"attr-value">>}], + children = []}}, + Out = {[{<<"ns:tag">>, + {[{<<"attr">>, <<"attr-value">>}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). -% 4.2.3.7 Tag with namespace attribute, no value -tag_with_namespace_no_value_test()-> - In = {xmlstreamelement, {xmlelement, "tag", [{"xmlns:ns", "ns-value"}], []}}, - Out = {struct, [{<<"tag">>, {struct, [ - {<<"xmlns">>,{struct, [{<<"@ns">>, <<"ns-value">>}]}} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - - -% 4.2.3.8 Tag with many attributes to namespace, no value -two_namespaces_tag_no_value_test()-> - In = {xmlstreamelement,{xmlelement, "tag", [{"xmlns:ns", "ns-value"}, - {"xmlns", "root-value"}], []}}, - Out = {struct, [{<<"tag">>, {struct, [ - {<<"xmlns">>,{struct, [ - {<<"$">>, <<"root-value">>}, - {<<"@ns">>, <<"ns-value">>}]}} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -% 4.2.3.9 Tag with namespace attribute, no value -% Removed namespace handling. More complex on both sides. -namespaced_tag_no_value_test()-> - In = {xmlstreamelement,{xmlelement, "ns:tag", [{"attr", "attr-value"}], []}}, - Out = {struct, [{<<"ns:tag">>, {struct, [ - {<<"attr">>,<<"attr-value">>} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -% 4.2.3.10 Tag with attribute and text value -tag_with_attribute_and_value_test()-> - In = {xmlstreamelement,{xmlelement, "tag", [{"attr", "attr-value"}], - [{xmlcdata, <<"txt-value">>}]}}, - Out = {struct, [{<<"tag">>, {struct, [ - {<<"$">>, <<"txt-value">>}, - {<<"attr">>,<<"attr-value">>} - ]}}]}, - %io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -% 4.2.3.11 Namespace tag with attribute and text value -% Removed namespace handling. More complex on both sides -namespaced_tag_with_value_test()-> - In = {xmlstreamelement,{xmlelement, "ns:tag", [{"attr", "attr-value"}], [{xmlcdata, <<"txt-value">>}]}}, - Out = {struct, [{<<"ns:tag">>, {struct, [ - {<<"$">>,<<"txt-value">>}, - {<<"attr">>,<<"attr-value">>} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -xml_lang_attr_test()-> - In = {xmlstreamelement,{xmlelement, "tag", [{"xml:lang", "en"}], []}}, - Out = {struct, [{<<"tag">>, {struct, [ - {<<"xml">>,{struct,[{<<"@lang">>,<<"en">>}]}} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -xmlns_tag_with_value_test()-> - Out = {struct,[{<<"response">>, - {struct,[{<<"$">>,<<"dXNlcm5hbWU9I">>}, - {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}} - ]}, - Out2 = {struct,[{<<"response">>, - {struct,[{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}, - {<<"$">>,<<"dXNlcm5hbWU9I">>} - ]}} - ]}, - In = {xmlstreamelement,{xmlelement,"response", - [{"xmlns","urn:ietf:params:xml:ns:xmpp-sasl"}], - [{xmlcdata, <<"dXNlcm5hbWU9I">>}]}}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)), - ?assertEqual(In, from_json(Out2)). - -no_attr_no_value_test()-> - In = {xmlstreamelement, {xmlelement,"failure", - [{"xmlns","urn:ietf:params:xml:ns:xmpp-sasl"}], - [{xmlelement,"not-authorized",[],[]}]}}, - Out = {struct, [{<<"failure">>,{struct, [ - {<<"$">>, {struct, [{<<"not-authorized">>, {struct, []}}]}}, - {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), +tag_with_attribute_and_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, + attrs = [{<<"attr">>, <<"attr-value">>}], + children = [{xmlcdata, <<"txt-value">>}]}}, + Out = {[{<<"tag">>, + {[{<<"$">>, <<"txt-value">>}, + {<<"attr">>, <<"attr-value">>}]}}]}, + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +namespaced_tag_with_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"ns:tag">>, + attrs = [{<<"attr">>, <<"attr-value">>}], + children = [{xmlcdata, <<"txt-value">>}]}}, + Out = {[{<<"ns:tag">>, + {[{<<"$">>, <<"txt-value">>}, + {<<"attr">>, <<"attr-value">>}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +xml_lang_attr_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"tag">>, + attrs = [{<<"xml:lang">>, <<"en">>}], children = []}}, + Out = {[{<<"tag">>, + {[{<<"xml">>, {[{<<"@lang">>, <<"en">>}]}}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +xmlns_tag_with_value_test() -> + Out = {[{<<"response">>, + {[{<<"$">>, <<"dXNlcm5hbWU9I">>}, + {<<"xmlns">>, + <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}}]}, + Out2 = {[{<<"response">>, + {[{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}, + {<<"$">>, <<"dXNlcm5hbWU9I">>}]}}]}, + In = {xmlstreamelement, + #xmlel{name = <<"response">>, + attrs = + [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}], + children = [{xmlcdata, <<"dXNlcm5hbWU9I">>}]}}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))), + ?assertEqual(In, (from_json(Out2))). + +no_attr_no_value_test() -> + In = {xmlstreamelement, + #xmlel{name = <<"failure">>, + attrs = + [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}], + children = + [#xmlel{name = <<"not-authorized">>, attrs = [], + children = []}]}}, + Out = {[{<<"failure">>, + {[{<<"$">>, + {[{<<"not-authorized">>, {[]}}]}}, + {<<"xmlns">>, + <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), io:format("~p~n", [to_json(In)]), io:format("~p~n", [from_json(Out)]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). - -xmlstream_test()-> - In = {xmlstreamstart, "stream", [{"xml:lang", "en"}]}, - Out = {struct, [{<<"stream">>, {struct, [ - {<<"xml">>,{struct,[{<<"@lang">>,<<"en">>}]}} - ]}}]}, - io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), - ?assertEqual(Out, to_json(In)), - ?assertEqual(In, from_json(Out)). --endif.
\ No newline at end of file + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +xmlstream_test() -> + In = {xmlstreamstart, <<"stream">>, + [{<<"xml:lang">>, <<"en">>}]}, + Out = {[{<<"stream">>, + {[{<<"xml">>, {[{<<"@lang">>, <<"en">>}]}}]}}]}, + io:format("~s", [jiffy:encode(to_json(In))]), + ?assertEqual(Out, (to_json(In))), + ?assertEqual(In, (from_json(Out))). + +-endif. |