From 769975f6d7a3c8bd74b071d28767f090be967ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Mon, 2 Jan 2017 15:53:25 +0100 Subject: [PATCH] Remove mod_http_bind and migration code to mod_bosh --- src/ejabberd_config.erl | 1 + src/ejabberd_http.erl | 13 +- src/ejabberd_http_bind.erl | 1211 ------------------------------------ src/ejabberd_socket.erl | 7 +- src/mod_bosh.erl | 158 ++++- src/mod_http_bind.erl | 239 ------- 6 files changed, 139 insertions(+), 1490 deletions(-) delete mode 100644 src/ejabberd_http_bind.erl delete mode 100644 src/mod_http_bind.erl diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index e930e36b1..541706383 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -1041,6 +1041,7 @@ replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql}; replace_module(mod_vcard_odbc) -> {mod_vcard, sql}; replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql}; replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql}; +replace_module(mod_http_bind) -> mod_bosh; replace_module(Module) -> case is_elixir_module(Module) of true -> expand_elixir_module(Module); diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index c0c7bbbd6..513559b94 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -135,7 +135,7 @@ init({SockMod, Socket}, Opts) -> false -> [] end, Bind = case proplists:get_bool(http_bind, Opts) of - true -> [{[<<"http-bind">>], mod_http_bind}]; + true -> [{[<<"http-bind">>], mod_bosh}]; false -> [] end, XMLRPC = case proplists:get_bool(xmlrpc, Opts) of @@ -150,9 +150,14 @@ init({SockMod, Socket}, Opts) -> ({Path, Mod}) -> {Path, Mod} end, Hs), - [{str:tokens( - iolist_to_binary(Path), <<"/">>), - Mod} || {Path, Mod} <- Hs1] + Hs2 = [{str:tokens( + iolist_to_binary(Path), <<"/">>), + Mod} || {Path, Mod} <- Hs1], + [{Path, + case Mod of + mod_http_bind -> mod_bosh; + _ -> Mod + end} || {Path, Mod} <- Hs2] end, []), RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ Admin ++ Bind ++ XMLRPC, diff --git a/src/ejabberd_http_bind.erl b/src/ejabberd_http_bind.erl deleted file mode 100644 index ea64b3cdf..000000000 --- a/src/ejabberd_http_bind.erl +++ /dev/null @@ -1,1211 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_http_bind.erl -%%% Author : Stefan Strigler -%%% Purpose : Implements XMPP over BOSH (XEP-0206) -%%% Created : 21 Sep 2005 by Stefan Strigler -%%% Modified: may 2009 by Mickael Remond, Alexey Schepin -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2016 ProcessOne -%%% -%%% This program is free software; you can redistribute it and/or -%%% modify it under the terms of the GNU General Public License as -%%% published by the Free Software Foundation; either version 2 of the -%%% License, or (at your option) any later version. -%%% -%%% This program is distributed in the hope that it will be useful, -%%% but WITHOUT ANY WARRANTY; without even the implied warranty of -%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%%% General Public License for more details. -%%% -%%% You should have received a copy of the GNU General Public License along -%%% with this program; if not, write to the Free Software Foundation, Inc., -%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_http_bind). - --protocol({xep, 124, '1.11'}). --protocol({xep, 206, '1.4'}). - --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, - custom_receiver/1, - reset_stream/1, - change_shaper/2, - monitor/1, - close/1, - start/5, - handle_session_start/8, - handle_http_put/7, - http_put/7, - http_get/2, - prepare_response/4, - process_request/3]). - --include("ejabberd.hrl"). --include("logger.hrl"). - --include("xmpp.hrl"). - --include("ejabberd_http.hrl"). - --include("http_bind.hrl"). - --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, - 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}). - -%%-define(DBGFSM, true). --ifdef(DBGFSM). - --define(FSMOPTS, [{debug, [trace]}]). - --else. - --define(FSMOPTS, []). - --endif. - -%% Wait 100ms before continue processing, to allow the client provide more related stanzas. --define(BOSH_VERSION, <<"1.8">>). - --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(PROCNAME_MHB, ejabberd_mod_http_bind). - -start(XMPPDomain, Sid, Key, IP, HOpts) -> - ?DEBUG("Starting session", []), - case catch gen_fsm:start(?MODULE, - [Sid, Key, IP, HOpts], - ?FSMOPTS) - of - {ok, Pid} -> {ok, Pid}; - _ -> check_bind_module(XMPPDomain), - {error, "Cannot start HTTP bind session"} - end. - -start_link(Sid, Key, IP, HOpts) -> - gen_fsm:start_link(?MODULE, [Sid, Key, IP, HOpts], ?FSMOPTS). - -send({http_bind, FsmRef, _IP}, 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}). - -setopts({http_bind, FsmRef, _IP}, Opts) -> - case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - ok - end. - -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}). - -reset_stream({http_bind, _FsmRef, _IP}) -> - ok. - -change_shaper({http_bind, FsmRef, _IP}, 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}). - -sockname(_Socket) -> {ok, ?NULL_PEER}. - -peername({http_bind, _FsmRef, IP}) -> {ok, IP}. - - -%% Entry point for data coming from client through ejabberd HTTP server: -process_request(Data, IP, HOpts) -> - 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, - PayloadSize = iolist_size(Data), - case catch parse_request(Data, PayloadSize, - MaxStanzaSize) - of - %% No existing session: - {ok, {<<"">>, Rid, Attrs, Payload}} -> - case fxml:get_attr_s(<<"to">>, Attrs) of - <<"">> -> - ?DEBUG("Session not created (Improper addressing)", []), - {200, ?HEADER, - <<"">>}; - XmppDomain -> - NXmppDomain = jid:nameprep(XmppDomain), - Sid = p1_sha:sha(term_to_binary({p1_time_compat:monotonic_time(), make_ref()})), - case start(NXmppDomain, Sid, <<"">>, IP, HOpts) of - {error, _} -> - {500, ?HEADER, - <<"Internal Server Error">>}; - {ok, Pid} -> - handle_session_start(Pid, NXmppDomain, Sid, Rid, Attrs, - Payload, PayloadSize, IP) - end - end; - %% Existing session - {ok, {Sid, Rid, Attrs, Payload1}} -> - StreamStart = case fxml:get_attr_s(<<"xmpp:restart">>, - Attrs) - of - <<"true">> -> true; - _ -> false - end, - Payload2 = case fxml: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 mnesia:dirty_read({http_bind, Sid}) of - {error, _} -> {404, ?HEADER, <<"">>}; - {ok, #http_bind{pid = FsmRef}} -> - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, close}), - {200, ?HEADER, - <<"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 str:to_integer(fxml:get_attr_s(<<"wait">>, - Attrs)) - of - {error, _} -> ?MAX_WAIT; - {CWait, _} -> - if CWait > (?MAX_WAIT) -> ?MAX_WAIT; - true -> CWait - end - end, - Hold = case str:to_integer(fxml: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 - str:to_integer(fxml: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(binary_to_list(fxml:get_attr_s(<<"ver">>, Attrs))) - of - {'EXIT', _} -> 0.0; - V -> V - end, - XmppVersion = fxml:get_attr_s(<<"xmpp:version">>, Attrs), - ?DEBUG("Create session: ~p", [Sid]), - mnesia:dirty_write( - #http_bind{id = Sid, - pid = Pid, - to = {XmppDomain, - XmppVersion}, - hold = Hold, - wait = Wait, - process_delay = Pdelay, - version = Version - }), - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP). - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_fsm -%%%---------------------------------------------------------------------- - -init([Sid, Key, IP, HOpts]) -> - ?DEBUG("started: ~p", [{Sid, Key, IP}]), - Opts1 = ejabberd_c2s_config:get_c2s_limits(), - SOpts = lists:filtermap(fun({stream_management, _}) -> true; - ({max_ack_queue, _}) -> true; - ({ack_timeout, _}) -> true; - ({resume_timeout, _}) -> true; - ({max_resume_timeout, _}) -> true; - ({resend_on_timeout, _}) -> true; - (_) -> false - end, HOpts), - - Opts = [{xml_socket, true} | SOpts ++ Opts1], - Shaper = none, - ShaperState = shaper:new(Shaper), - Socket = {http_bind, self(), IP}, - 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}}. - -handle_event({become_controller, C2SPid}, StateName, StateData) -> - 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}} - end; -handle_event({change_shaper, Shaper}, StateName, - StateData) -> - NewShaperState = shaper:new(Shaper), - {next_state, StateName, - StateData#state{shaper_state = NewShaperState}}; -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -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, - #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) -> - Output = [Packet | StateData#state.output], - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - HTTPReply = {ok, Output}, - 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]], - Reply = ok, - {reply, Reply, StateName, - 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({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, - 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, - _From, StateName, StateData) -> - ?DEBUG("New request: ~p", [Request]), - {NewShaperState, NewShaperTimer} = - 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) -> - TNow = p1_time_compat:system_time(micro_seconds), - 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.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) -> - Reply = {ok, StateData#state.ip}, - {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}. - -handle_info({timeout, Timer, _}, _StateName, - #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} - end; -handle_info({timeout, ShaperTimer, _}, StateName, - #state{shaper_timer = ShaperTimer} = StateData) -> - {next_state, StateName, StateData#state{shaper_timer = undefined}}; - -handle_info(_, StateName, StateData) -> - {next_state, StateName, StateData}. - -terminate(_Reason, _StateName, StateData) -> - ?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}), - case StateData#state.waiting_input of - false -> ok; - C2SPid -> gen_fsm:send_event(C2SPid, closed) - end, - ok. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -%% PUT / Get processing: -handle_http_put_event(#http_put{rid = Rid, - attrs = Attrs, hold = Hold} = - Request, - StateName, StateData) -> - ?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]), - 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, - StateName, StateData, RidAllow) -> - ?DEBUG("Actually processing request: ~p", [Request]), - Key = fxml:get_attr_s(<<"key">>, Attrs), - NewKey = fxml:get_attr_s(<<"newkey">>, Attrs), - KeyAllow = case RidAllow of - repeat -> true; - false -> false; - {true, _} -> - case StateData#state.key of - <<"">> -> true; - OldKey -> - NextKey = p1_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 = p1_time_compat:system_time(micro_seconds), - 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]), - 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} - 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), - 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) -> - ?DEBUG("Looking for session: ~p", [Sid]), - case mnesia:dirty_read({http_bind, Sid}) of - [] -> - {error, not_exists}; - [#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}}), - case Reason of - not_exists -> - {200, ?HEADER, - fxml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"type">>, <<"terminate">>}, - {<<"condition">>, - <<"item-not-found">>}], - children = []})}; - bad_key -> - {200, ?HEADER, - fxml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"type">>, <<"terminate">>}, - {<<"condition">>, - <<"item-not-found">>}], - children = []})}; - polling_too_frequently -> - {200, ?HEADER, - fxml: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}}), - 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, <<"">>} - end. - -%% Control RID ordering -rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) -> - {true, 0}; -rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) -> - ?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 - binary_to_integer(fxml: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), - {NewShaperState, ShaperTimer}; - true -> {NewShaperState, undefined} - end. - -prepare_response(Sess, Rid, OutputEls, StreamStart) -> - receive after Sess#http_bind.process_delay -> ok end, - case catch http_get(Sess, Rid) of - {ok, cancel} -> - {200, ?HEADER, - <<"">>}; - {ok, empty} -> - {200, ?HEADER, - <<"">>}; - {ok, terminate} -> - {200, ?HEADER, - <<"">>}; - {ok, ROutPacket} -> - OutPacket = lists:reverse(ROutPacket), - ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]), - prepare_outpacket_response(Sess, Rid, - OutputEls ++ OutPacket, StreamStart); - {'EXIT', {shutdown, _}} -> - {200, ?HEADER, - <<"">>}; - {'EXIT', _Reason} -> - {200, ?HEADER, - <<"">>} - end. - -%% Send output payloads on establised sessions -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, - <<"">>}; - 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) -> - case OutPacket of - [{xmlstreamstart, _, OutAttrs} | Els] -> - AuthID = fxml:get_attr_s(<<"id">>, OutAttrs), - From = fxml:get_attr_s(<<"from">>, OutAttrs), - Version = fxml: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, - <<"">>}; - _ -> - 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, - fxml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"sid">>, Sid}, - {<<"wait">>, - integer_to_binary(Wait)}, - {<<"requests">>, - integer_to_binary(Hold + 1)}, - {<<"inactivity">>, - integer_to_binary( - trunc(MaxInactivity / 1000))}, - {<<"maxpause">>, - integer_to_binary(MaxPause)}, - {<<"polling">>, - integer_to_binary( - trunc((?MIN_POLLING) / 1000000))}, - {<<"ver">>, ?BOSH_VERSION}, - {<<"from">>, From}, - {<<"secure">>, <<"true">>}] - ++ BOSH_attribs, - children = OutEls})} - end; - _ -> - {200, ?HEADER, - <<"">>} - 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). - -send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> - case OutPacket of - [] -> - {200, ?HEADER, - <<"">>}; - [{xmlstreamend, _}] -> - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, stream_closed}), - {200, ?HEADER, - <<"">>}; - _ -> - 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 ++ - [fxml:element_to_binary(check_default_xmlns(El))]; - ({xmlstreamraw, R}, Acc) -> - Acc ++ [R] - end, - [], OutPacket), - Body = <<"", - (iolist_to_binary(TypedEls))/binary, "">>, - ?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, - fxml: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, - <<"">>}; - _ -> - {200, ?HEADER, - <<"", - (elements_to_string(StreamErrCond))/binary, - "">>} - end - end - end - end. - -parse_request(Data, PayloadSize, MaxStanzaSize) -> - ?DEBUG("--- incoming data --- ~n~s~n --- END " - "--- ", - [Data]), - case fxml_stream:parse_element(Data) of - #xmlel{name = <<"body">>, attrs = Attrs, - children = Els} -> - Xmlns = fxml:get_attr_s(<<"xmlns">>, Attrs), - if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request}; - true -> - case catch - binary_to_integer(fxml: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 = fxml: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(Receiver, Reply) -> - gen_fsm:reply(Receiver, Reply). - -%% Cancel timer and empty message queue. -cancel_timer(undefined) -> ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - 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(), []); -%% Otherwise, we apply the max_inactivity value as inactivity timer: -set_inactivity_timer(_Pause, MaxInactivity) -> - erlang:start_timer(MaxInactivity, self(), []). - -elements_to_string([]) -> []; -elements_to_string([El | Els]) -> - [fxml: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, - 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_pause({Host, _}) -> - 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. - -check_default_xmlns(#xmlel{name = Name, attrs = Attrs, - children = Els} = - El) -> - case fxml: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 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) " - "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. diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index e83fd3f31..f49f768cb 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -51,16 +51,14 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --type sockmod() :: ejabberd_http_bind | - ejabberd_bosh | +-type sockmod() :: ejabberd_bosh | ejabberd_http_ws | gen_tcp | fast_tls | ezlib. -type receiver() :: pid () | atom(). -type socket() :: pid() | inet:socket() | fast_tls:tls_socket() | ezlib:zlib_socket() | - ejabberd_bosh:bind_socket() | - ejabberd_http_bind:bind_socket(). + ejabberd_bosh:bind_socket(). -record(socket_state, {sockmod = gen_tcp :: sockmod(), socket = self() :: socket(), @@ -231,7 +229,6 @@ get_transport(#socket_state{sockmod = SockMod, fast_tls -> tls_zlib end; ejabberd_bosh -> http_bind; - ejabberd_http_bind -> http_bind; ejabberd_http_ws -> websocket end. diff --git a/src/mod_bosh.erl b/src/mod_bosh.erl index 038218739..d3eda4734 100644 --- a/src/mod_bosh.erl +++ b/src/mod_bosh.erl @@ -82,37 +82,6 @@ process(_Path, _Request) -> #xmlel{name = <<"h1">>, attrs = [], children = [{xmlcdata, <<"400 Bad Request">>}]}}. -get_human_html_xmlel() -> - 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) -> Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid}, lists:foreach( @@ -294,3 +263,130 @@ mod_opt_type(prebind) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(_) -> [json, max_concat, max_inactivity, max_pause, prebind]. + + +%%%---------------------------------------------------------------------- +%%% Help Web Page +%%%---------------------------------------------------------------------- + +get_human_html_xmlel() -> + 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 = <<"style">>, + children = [{xmlcdata, get_style_cdata()}]}]}, + #xmlel{name = <<"body">>, + children = + [#xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"container">>}], + children = get_container_children(Heading)}]}]}. + +get_container_children(Heading) -> + [#xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"section">>}], + children = + [#xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"block">>}], + children = + [#xmlel{name = <<"a">>, + attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}], + children = + [#xmlel{name = <<"img">>, + attrs = [{<<"height">>, <<"32">>}, + {<<"src">>, get_image_src()}]}]}]}]}, + #xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"white section">>}], + children = + [#xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"block">>}], + 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.">>}]}]}]}, + #xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"section">>}], + children = + [#xmlel{name = <<"div">>, + attrs = [{<<"class">>, <<"block">>}], + children = + [#xmlel{name = <<"a">>, + attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}, + {<<"title">>, <<"ejabberd XMPP server">>}], + children = [{xmlcdata, <<"ejabberd">>}]}, + {xmlcdata, <<" is maintained by ">>}, + #xmlel{name = <<"a">>, + attrs = [{<<"href">>, <<"https://www.process-one.net">>}, + {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}], + children = [{xmlcdata, <<"ProcessOne">>}]} ]}]} + ]. + +get_style_cdata() -> + <<" + body { + margin: 0; + padding: 0; + font-family: sans-serif; + color: #fff; + } + h1 { + font-size: 3em; + color: #444; + } + p { + line-height: 1.5em; + color: #888; + } + a { + color: #fff; + } + a:hover, + a:active { + text-decoration: underline; + } + .container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #424A55; + background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%); + background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%); + } + .section { + padding: 3em; + } + .white.section { + background: #fff; + border-bottom: 4px solid #41AFCA; + } + .white.section a { + text-decoration: none; + color: #41AFCA; + } + .white.section a:hover, + .white.section a:active { + text-decoration: underline; + } + .block { + margin: 0 auto; + max-width: 900px; + width: 100%; + }">>. + +get_image_src() -> + <<"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABACAYAAACgPErgAAAVtklEQVR42uydeXhU1d2ADzuyCcii4FZBZdEqVYtaUGqlikVwaRWsrWBRKIh8KgXcEAX9VECtoljcURAJICJWQLaKLAEjIHtBSNhC9oQkk2SW+36/52P+iIE5d5kzk4Fn3ud5H/7gIbnD3PPOveeeuVedygD9xLlQMh9yJ0D+GeokJAg1xcEWzOWYj0DBaQm0fXdaMCu8bROgpJVKksQj0+F84OXw/jRd7KVOdbbCFI6jdDNktlAnESVQNwSzOI6yhZBaV1UzAXiB4wj8CBtbqiRJXDIILtgL+zmOb8aoU5Xh0MMiEr5x6iSiAIZYROLo/aoayYMeocjbNlklSeKC9VAjBDM5IcEQfNZFnYq8AM8SkcJV6iSiCGYRkT1LVDWSA08Tkfy9MLaeOsUA2ot9xJHiW2KK+LW4Mvxnijg1/Pd9xQvFGiqJLUOhcT5kE5Etg0/VYI3TBOvbkyxYKURk2dpqDZb2gyEjC1SjUyBQdcQe4mtimujDHWXiD+Lr4g1iXZXkhISgBZBLZIaoU5GR0I2IVDxxkgVrNhH5z+pqDZb2g+FAJjQ4aYMFtBAfEzdils3iP8TkhYkqlMEZFuQQmcHqVGUBTOI40lLhz02TwUoGKxJAI/FxMYPYckB8Smyikvw/vlM/WHqAP4ifiHPF0fBsYyUkg5UM1okAbhV/JL5sE29TSZLBOhHJYCWDVRXgNPFVqpfXxQbJYCWDlQxWMlgRAc4VV5EYrBZ/kQxWMljJYFVLsBomdLCAS8RdJBZ7xMuSwTIcLKCO2EG8VrxdvEfsLV4F1vkJstq9BlidwN8hVleSLLgUuFzsEv6zs9g4EYIFNBAvFi87tm3WZWI7oHZ8glWnkWbb6ortCG/bsT+tDsBpcYrVpeIhzBHAHJni5XGOd5PwvnKJif0DaCh2/Pn7y/linbgFKx/qrjkWpffEHZo3qVhMBWuC2EVVA4ehay6sAYJgBeDocuh9iYqCL+F04DbxLTFNPMzxWGI6WCuBJ8Rfxi5YK34WLKC5BX3FVzj22n8S/fwcX/i9+1wcLraLVbAqr8OqgNOA31nwPLBc3HWCNU1+cbe4WBwjXhbD08A9eOcn8SNxmHij+EuxQ3iwdxP/Jr4pbsU7+2J5ehiCNuI9FkwN78sZol+0xDSYd6OH75R2BcZasALYKwZO0IWd4fd3LIS6g1XHeLBWQV1gsLgJ9wTBWgSBm1ScuBYuAvI4jrx9cOEZyiXzoUUIxhbDPm+fvFlL4PPe5oO14FslvAjnrYeXgYO4p0T8UrzJbLB2HlbCZMqbfwOjgrDD41HLCrE/LK1pKFYNxFS8sVDsK1Y5ctQeRf42HLcy3JMmNjIc6+ss+ETMRUuRH9Zd7SBS9QMwCFiDNzZBYAT4G5dTUcuC7KiClQ3dj8B6osYSAzOgoq2KMatgChF5e5RyQQXcVwL7MMNXsKOTuWAd2FBG9uhyyMMI6XPBd7GZYOUd8pP+UCn8hBEOr4KCbgYG7Ju4Z43YU0VB+LRoLu6ZpgyQAhflw2e4Y7lYS0XADzdZ8ANGsLaX88PgACHvwfLBY4Afo4T2Q9lNMZ68W0lE/J8oB5RDkxDMwDhF+XC4v6tgxZdcKLov+mBhYR4/FD8VRTRu93CE97RYTxkCuFfMxR13qSjIhoE+Tx9qPh9Mb6WqcBRqBuBli5hguQ4WUGsPvGsRKyw/hAaoGJEPy4lI4Uxlw3XHzu/XEVNKHk3QYAmWWDEecmraBqtaCE6HrCYeJpX34Jwc8eYYXp3cjHPSxWbKA9/Da3im+CeY1kBV4gNoUQILiB/6YD0JNYFP4jMoGFINwZqhNHwKLbfB5ji9/kdMBss8vk9MBcs8pasht7GLSIzHOQfFX6kYApyFuw/FFz38jveJitxBqhI3QMsS2IiQMMFaDFOJHyF45fZECdYOqA0sIX5YMPkOk8Eyz96xpoJlnvIZDgduazEfZ+SJVyoNhqO1DWcUim1dfJf2DTxTVgxFY1Ql+kOtivDYSJhgTYX7cIx1EJgmDhSvEbuK12XAiHyYCxThCKsQHmiXCMHa5+5T+BBY74qVX39vcUIhbLBwSkUO7DonBsEqBCtFfBjoHt6+34mjLfg34HO4feL8mw0HqwKsr8XRwA2E9x3xYY693jzHvWfZCAdheAZnWG6+1wecKd4oDhFHicPFvm6WI4SXRBTijOcdzi3+BefkifPAehLoI14Pqcdt/1x3YyNb/BisYUC38PvbU3wivOQhGHWw+sK5ASjAltBB8D8MQe05NXCBBZOBCmxJXwbTa1RnsDbCL4Pgx5ajeeJjYDVXEZgDNSugVwhW44j1sw0GKwjrX4El2kGzHDqVwzvO1x3d2dhMsPbMgjnaRZGroW0OjHcY1VIYeZHNMoZ0nPFP5YDwIJwp5miWinwt3u7w5z3g4lS1sc2c1Tl+Z0eTRXBkLKy1PWp7Ga4EAthSXAhHn4BQa5vX2zUIX1rRBKvU0fluYDaUt3F5xa5rNmzFlsfurs5grYP52JK7AVZcpBxSCrU3wvPOPtgzuxsI1gG3l9/90Cfg6Ihm/ONRBssn3udyceNVftiBLXNnaAZHbxcLNZs6uPXMFJdHCF84OeICluIIfQTzHI3jrI0Q7Kwc4myaxEqDtR2VC9bCCMDvOliZ0NGCCv1pwb7XlEdmQCtgOVp2bYU69aojWB/AVUEIoWcpfNlMeQD4q/0bs/GL6IK1JR3Wd/T4/lxZDJloKTgMrzXzFqyDJfBNT+WB2dD2kO1Eb9AP/zrhAAQ+xhl/d7A6fm0U977qbvPzr3YYwlkqArnQ2f4swb8SZjZXDtkG3SxsWSZ6HRt9RJ+rYP0H/omWdVNUlGyDxpm2b/i4P1VHsPbYnhrl7IQ/NlNRAPzDfqnH0o4eg1UEEy+N8mkl3e1P378e4CFYFnzRR0VBVzgP26BumqyqANQX92JPhm5FOXC2uJXoKBR7KA3AQuzZLzaMcJbwqv2pfXFL5YKV8BFaCvfCH1uqKDgKA0NOgzUAGuRp7664eaUyxD3Qpkz7u0oWxDtY/aBhFhzUD7hBN6goeRVq7IRVNkEY4y1YRx5VBiiHV9ByaK77YOW+pwxQCn9DS/4W2Fy7SgCuEEPYM1ETkXriSsxwRLxQe7RhjyV2VVW4E+ofhj1oSe2rXDAaGhTBAbQM72vo6VDzHQXrX9r7nxeXwqrLYWcNoFa0hj8F7rQiTuAV5cHIFvEM1jvQ3UJH+QJliN9Dj5B2APkWuw/Wrm3QrJ6hZyC20q/C9qfDsgbOg1WYDxe2NbRtdfTfZbXKIbW9x8ns30QISA1xEmbZLDbRzJEdwp6hqgqvwVWWdt9avVy5ZDL8Wj9VstbYwUwpdALKbIP1HxhJRAJlwPfhHWWzATeJafpz9bd/H89grYBRaHn+RmWQn2CdZtsyYWhjd8HaPVQZZKl2HV7AgvEXOw9W1pvKIDNhGFrevbVKACY6vPLWSHN0NUp8ThxnyJfECzRHWSlermaugfvRcuQu5ZLv4F60pN+lDLIZUmyDtRRmklAUPBzPYK2GT3Vf4IX+jZRBPoYniUioHA62dx6sQAVkt1MGSYXeFjoC3ZwFKyRm/14Z5Bu4OKidZwsOqjL4P8eeZSqBAMZgz0JVhQ3wOhEpLoLxZyqXHIAJRKS0CF48UxnkWehnG6wj8BUJxfK3VZQUuAiW/ntRwa+UYR6BP+gH+f6eLoK1CQprKoMchIv82itNGfc5DFY+FLUyvG0NS2E/Ecl8qcrg/w573kuwYP0Re1JVFd7RfvAWbYIU1/vJ2/AhETm6HlJqKIOshA4hKNMGK5RwwZrygYFHqC9zGqygNlgrP4vBjQW7hcCKHKy9tzh/kGrqd8ow+6CVX3tPovShKkw2PKuJx2G4tKEyyF6o4YPvicjhN1UlHD5TcGKCBetG7Nkq/ixA+doPNv9i5YFp2iuE1mJlmBxoEbR7kKqVcMGaOU1FyTpYq7tbg/NgbZ+tDLMLrrcJVi9ViTLtKfsq48E6CK392p0m/e8qTDE8RUQOHobmRoN1AGqWQZomWFMq33FE/C/2vJBgwboee/aKdVzMfXk6U3hLGyyMBysXWga1i5jTBqmchAvWY31VlGRqbyrmn64qka8NVsj4/MZg6IPgNFj7tV9iLfsRVtdSBsmATgEIaoJ/mwqzEwYTkWABbDhLGSQdGpdp12OVPF7lmQO7HU1gJxDAI9izs+oRVlAfrH8rDyzQXoAp3gxv1zJ8UaVTSDtHec9f1bqEmXQPiuUfwjdR/Se8AG0sOEpEUidVmax8n4iU58OKFoaPsJ5xE6x/wwT9pPuTRifdv4K79Nv3QZdK8yb90PLazcogH8HlIQgQkfl3ewjWpwn2yLH92JOmhFgHqwSGE5EyH7x/vjJICtyjX362s6f6Cf6hX9tSOB8Q+SJGfimmQPogZYCXoD9avh1WZYAORcvwfsogpbDeTbBmwW1oKR6mDJIGs4iIvxAeOLPSpGxn/dGYb6rhJRdPod2jn7nGQ7DWaQJSQzxLbC22iqFtxDvE7ThjRTyCNQ1uRkvqYGWQQzBH/yV3+R7zFOhhab8u8smt6iThN1AjB1brd+rnuqhKvAFX6hdz/rgBLqqpDJAHPS2w3ARrIpzth2IiEtgBM+spA2RBB/33unZ9qyrxDNTLg52a11MAS842dDrYRH/XhfxDMLyhh2AViK0jBKum+K5YKObE0CLc8WE8gvUStC7XbtuWrXBeHWWA7tDJp933dq+F8TXVHmgS1M4LFByE/q3VScB826OrwG7IqqsqcQvU3Q+70OJ7SEVJO6iVARsQHAcrTBosR8vrI5UBNsBctBQ+raowD6ag5ev3lQFmw3i0FM9SgiZYOiJ+KHMs4iUkFk+aC5aeLbAULYERygBB+BIt+8ZV3lHfQ8uRRaDqqASmF7Sxf8xV6SR1AjbBi+gphPIuUcb0aQQvwfoWBqDFKoEXuqooWAID0RLyw56OJ7j319WAhZZp/aOMVTegDC0ZfaII1sdKA9CPxOLWeAVrMgy0Hxu9ukS5NvEh+33v6Q6VT4uusCCElpnz4Ie6KgFZAWctgTS0lPjg2osiLDRtFwAfWoKHIfRrj/NCI0oAr8EqgAZ+2IOew7DkWuWBAPQvBT9ayuZo5uUWo8cH6z1FKwTXFkEOWio2Q1ndKIJVILa1XYGeGBSJbeIVrObQKBPS0VJ6ABZ7itY2eLAIO1JmqKqUwzzsWQyB9soFwGliQxUj/HCNs/tiH5hqM2hfxxarCPwPughBI80dEBwHK3wUOMDCjtKjbp5GlAn1vodxgIWeoO6hDGvhmqDtzygXA0/BN3VcPF/yAYfzO3crQRcsE+uxgIfEUqqXVUowGyw9I2EY9uRCxV0uls803AwTsacC3uisqjIEOgMV2GLlAeMgdK7NbW47Aq+Ke8UD4jsw4yyDj9ruIE6yoAxbyo/AjjOVhnehVQFk4ojQIgjeDNSNsGO3FP9mwVYEE8FqAbUOwwoccWAh+HuCVS/C9jUNwZ+BNByx9A0Hd4+ciiOOpELx3WBFumNB/RD0smARjti5FPrVNBCsQvECB9G6SvwaZ/xX/F/xf8S5YojoGRXvYM2C+j7nT8uZC9ZvI42NhdA8AAOALTgi5TkVic/gSRxjFQDzxKFiH7G7eJv4WAhWBqCc49i/Gya2US7hWPzuFf8iTrBgGVCOY4r/5HCupF8AV2wDazrwiDhEfEFcKGYRxkiwwlRAeyAb5+wQPxFHioPEseLn4gEcU7gdLm3q4PubTVw+ay9DTBGfCm/bKPFTcSfOyYe+FyrBQLAQPnfzyHdxSvg1F4hlYrGYHn5d94hNTnC/qxK8Uya2Mx8se0rhCsCHc7aCVXnfG2fB/Ao4hGPSV8O59VQkfgs1N8IiYkrBK8oFW+E5wIdndkx2eVXkeYxgPljh7bs1CCHigr8Ull7u4r26pBgKiBtb7lCCqWCFeVi5AKgtthE7iL8QGykNwGi8s0AJcQ9WmOnwIHEjlAFp5yg7noNmQCoxo2yNizmWoUG8Yon73oOzayiXAG8mZLDC/BkGlIBFTMkvg1v6eFhPc31mzKNVIf59hBJiECy/2FvFCOCcKObBesQjWNV/8aFiPxR0cbNRrcVVxITgauWAR6H+IdiNZxZPinJdyKSEDFaY5dAXyCY2HIJPeyqPvAlXANuJDT74/n4leA6WPUfFXjEKVjMxD/css/m5c7TBMsgmGBGI2VG+9T0UtvdyI7cme+EzjPPhROWACmiJp0/q8lzY/RdlgFUwDCjGGFZYXbDSb3Fx4aGzH77DKBmrIPdCFSUcWyU9B6Pk7ICMbkowFiz9fNFAZRjgGjGIOwLi1UrDBv0R1hJlmCLoFYIMs2Mj8D74m6poAIZbkGtmg/gIbmumHLID5uOK0GzIb294B7skCAssoiYLsh6FdWIk/D7Ycp7LZ7vV2Qejifo9CmYBo+Hb2oY/jQcC+4iKUAnwCmxvrgTjwdLzttjS0L7U3uOjwqYoG3ZqF19WvKVigA/aBGEaUEFUWFsh+CeTa50uCMAUC3LwRioEb1MueRw62a/DsXLE9yH4GxVD/HCjBfPEYlxxdDsUjgPrbCXMprTF/oiPnip9QnnEgnND8KwFu3DHdigfC4vaqhgBNA3CwxZsxB37xFdhUSelQRssM+wVh4iNPb7+RhwLyhHcs8PJM/+OHLv1ziaOw5cNmReoGAL8KgRvBWC/5e4IZh2EBomnqVgQgrYWDBUXApkRlhaUillgLRVfFm8AaiqPAN3ENWJ+2ExxJVjTIHQXBM9UcSQI51twLzBNXMSxQOSIeeEdchNYC469dq6HH+qd4DYpvwA+FPeIeeF/86Ch9+g0C34HTBC/FLeK2eHfkyWmiZ+LT4rXhddqxYUQ1LLgGmCMOF/8QcwKb1tOeFsXhNcu3QyWLhDmg2XPLvFZ8UpRO8iAVuL14nPiTrxRLnZVDjkC54TveJAfdjEcuUzFia/h9FLoDbwifhV+3XmVxsZ6cQ5Yj0LoChVPkI0TO4q9wLoDuFW8TrxAbGr4d9WwoAVwhni6SiCA+mIzsXn4/6S2i3/bIPzvasdw++pW2r6mYi2VIAC1wtvUPLyNdZUGz8EyTyj8s78KnzK+LL4kviPOE9eJeUTP/R5f+xlgnZEIY8OC5pXGRg2VJEkSTbBOXsaoJEmSJIN1EvC0SvJ/7dSxigEAHIDxf5FisAilbNbbWGSi5OZ7BgZPcS/hAZTNwFtYSVltit2iU/c9gxv8u75ffa/wSQ4ruR+ahySHldyZJiHJYSW3pnZIcliJHekrJDmsxMM60JQqIUkJh3WhJX1SKSTpzcN60J1udKQNfdOIaiFJLwxrTwua0Zj61PtjXfqgDtWpEJL04rCetKKBM5GUeVgnGoYkJR1Wka60o0ZIUuJhVWlLzZCk5MMqUyv0r/wCSDD/4sxS1q8AAAAASUVORK5CYII=">>. diff --git a/src/mod_http_bind.erl b/src/mod_http_bind.erl deleted file mode 100644 index 68500f2c4..000000000 --- a/src/mod_http_bind.erl +++ /dev/null @@ -1,239 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_http_bind.erl -%%% Author : Stefan Strigler -%%% Purpose : Implementation of XMPP over BOSH (XEP-0206) -%%% Created : Tue Feb 20 13:15:52 CET 2007 -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2016 ProcessOne -%%% -%%% This program is free software; you can redistribute it and/or -%%% modify it under the terms of the GNU General Public License as -%%% published by the Free Software Foundation; either version 2 of the -%%% License, or (at your option) any later version. -%%% -%%% This program is distributed in the hope that it will be useful, -%%% but WITHOUT ANY WARRANTY; without even the implied warranty of -%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%%% General Public License for more details. -%%% -%%% You should have received a copy of the GNU General Public License along -%%% with this program; if not, write to the Free Software Foundation, Inc., -%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -%%% -%%%---------------------------------------------------------------------- - -%%%---------------------------------------------------------------------- -%%% This module acts as a bridge to ejabberd_http_bind which implements -%%% the real stuff, this is to handle the new pluggable architecture for -%%% extending ejabberd's http service. -%%%---------------------------------------------------------------------- - --module(mod_http_bind). - --author('steve@zeank.in-berlin.de'). - -%%-define(ejabberd_debug, true). - --behaviour(gen_mod). - --export([start/2, stop/1, process/2, mod_opt_type/1, depends/2]). - --include("ejabberd.hrl"). --include("logger.hrl"). - --include("xmpp.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}). - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- - -process([], #request{method = 'POST', data = <<>>}) -> - ?DEBUG("Bad Request: no data", []), - {400, ?HEADER, - #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}; -process([], - #request{method = 'POST', data = Data, ip = IP, opts = Opts}) -> - ?DEBUG("Incoming data: ~s", [Data]), - ejabberd_http_bind:process_request(Data, IP, Opts); -process([], #request{method = 'GET', data = <<>>}) -> - {200, ?HEADER, get_human_html_xmlel()}; -process([], #request{method = 'OPTIONS', data = <<>>}) -> - {200, ?OPTIONS_HEADER, <<>>}; -process([], #request{method = 'HEAD'}) -> - {200, ?HEADER, <<>>}; -process(_Path, _Request) -> - ?DEBUG("Bad Request: ~p", [_Request]), - {400, ?HEADER, - #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}. - -%%%---------------------------------------------------------------------- -%%% BEHAVIOUR CALLBACKS -%%%---------------------------------------------------------------------- -start(_Host, _Opts) -> - setup_database(). - -stop(_Host) -> - ok. - -setup_database() -> - migrate_database(), - ejabberd_mnesia:create(?MODULE, http_bind, - [{ram_copies, [node()]}, - {attributes, record_info(fields, http_bind)}]). - -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) - end. - -mod_opt_type(max_inactivity) -> - fun (I) when is_integer(I), I > 0 -> I end; -mod_opt_type(max_pause) -> - fun (I) when is_integer(I), I > 0 -> I end; -mod_opt_type(_) -> [max_inactivity, max_pause]. - -depends(_Host, _Opts) -> - []. - -%%%---------------------------------------------------------------------- -%%% Help Web Page -%%%---------------------------------------------------------------------- - -get_human_html_xmlel() -> - 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 = <<"style">>, - children = [{xmlcdata, get_style_cdata()}]}]}, - #xmlel{name = <<"body">>, - children = - [#xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"container">>}], - children = get_container_children(Heading)}]}]}. - -get_container_children(Heading) -> - [#xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"section">>}], - children = - [#xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"block">>}], - children = - [#xmlel{name = <<"a">>, - attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}], - children = - [#xmlel{name = <<"img">>, - attrs = [{<<"height">>, <<"32">>}, - {<<"src">>, get_image_src()}]}]}]}]}, - #xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"white section">>}], - children = - [#xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"block">>}], - 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.">>}]}]}]}, - #xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"section">>}], - children = - [#xmlel{name = <<"div">>, - attrs = [{<<"class">>, <<"block">>}], - children = - [#xmlel{name = <<"a">>, - attrs = [{<<"href">>, <<"https://www.ejabberd.im">>}, - {<<"title">>, <<"ejabberd XMPP server">>}], - children = [{xmlcdata, <<"ejabberd">>}]}, - {xmlcdata, <<" is maintained by ">>}, - #xmlel{name = <<"a">>, - attrs = [{<<"href">>, <<"https://www.process-one.net">>}, - {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}], - children = [{xmlcdata, <<"ProcessOne">>}]} ]}]} - ]. - -get_style_cdata() -> - <<" - body { - margin: 0; - padding: 0; - font-family: sans-serif; - color: #fff; - } - h1 { - font-size: 3em; - color: #444; - } - p { - line-height: 1.5em; - color: #888; - } - a { - color: #fff; - } - a:hover, - a:active { - text-decoration: underline; - } - .container { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: #424A55; - background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%); - background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%); - } - .section { - padding: 3em; - } - .white.section { - background: #fff; - border-bottom: 4px solid #41AFCA; - } - .white.section a { - text-decoration: none; - color: #41AFCA; - } - .white.section a:hover, - .white.section a:active { - text-decoration: underline; - } - .block { - margin: 0 auto; - max-width: 900px; - width: 100%; - }">>. - -get_image_src() -> - <<"data:image/png;base64,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">>.