diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index 0cbe40e86..d16a5d0c4 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -3,16 +3,17 @@ %%% Author : Stefan Strigler %%% Purpose : HTTP Binding support (JEP-0124) %%% Created : 21 Sep 2005 by Stefan Strigler +%%% Id : $Id: $ %%%---------------------------------------------------------------------- -module(ejabberd_http_bind). -author('steve@zeank.in-berlin.de'). --vsn('1.9'). +-vsn('Revision: 1.4'). -behaviour(gen_fsm). %% External exports --export([start_link/3, +-export([start_link/2, init/1, handle_event/3, handle_sync_event/4, @@ -25,7 +26,7 @@ close/1, process_request/1]). --define(ejabberd_debug, true). +%%-define(ejabberd_debug, true). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -34,7 +35,10 @@ -record(http_bind, {id, pid, to, hold, wait}). %% http binding request --record(hbr, {rid, key, in, out}). +-record(hbr, {rid, + key, + in, + out}). -record(state, {id, rid = error, @@ -49,7 +53,8 @@ req_list = [] % list of requests }). --define(DBGFSM, true). + +%%-define(DBGFSM, true). -ifdef(DBGFSM). -define(FSMOPTS, [{debug, [trace]}]). @@ -57,8 +62,6 @@ -define(FSMOPTS, []). -endif. --define(BOSH_VERSION, "1.6"). - -define(MAX_REQUESTS, 2). % number of simultaneous requests -define(MIN_POLLING, "2"). % don't poll faster than that or we will shoot you -define(MAX_WAIT, 3600). % max num of secs to keep a request on hold @@ -66,17 +69,18 @@ -define(CT, {"Content-Type", "text/xml; charset=utf-8"}). -define(HEADER, [?CT,{"X-Sponsored-By", "http://mabber.com"}]). + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(Sid, Rid, Key) -> +start(Sid, Key) -> mnesia:create_table(http_bind, [{ram_copies, [node()]}, {attributes, record_info(fields, http_bind)}]), - supervisor:start_child(ejabberd_http_bind_sup, [Sid, Rid, Key]). + supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key]). -start_link(Sid, Rid, Key) -> - gen_fsm:start_link(?MODULE, [Sid, Rid, Key], ?FSMOPTS). +start_link(Sid, Key) -> + gen_fsm:start_link(?MODULE, [Sid, Key], ?FSMOPTS). send({http_bind, FsmRef}, Packet) -> gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). @@ -95,58 +99,294 @@ controlling_process(_Socket, _Pid) -> close({http_bind, FsmRef}) -> catch gen_fsm:sync_send_all_state_event(FsmRef, close). + process_request(Data) -> case catch parse_request(Data) of - {ok, {[], Attrs, Packet}} -> %% no session id - create a new one! - ?DEBUG("no sid given. create a new session?", []), - case xml:get_attr_s("to",Attrs) of - [] -> %% missing 'to' - can't proceed - ?DEBUG("missing 'to' attribute, can't create session", []), - {200, ?HEADER, ""}; - XmppDomain -> - create_session(XmppDomain, Attrs, Packet) - end; - {ok, {Sid, Attrs, Packet}} -> - case check_request(Sid, Attrs, Packet) of - {error, not_exists} -> - ?DEBUG("no session associated with sid: ~p", [Sid]), - {404, ?HEADER, ""}; - {error, bad_key} -> - %%?DEBUG("bad key: ~s", [Key]), - case mnesia:dirty_read({http_bind, Sid}) of - [] -> - {404, ?HEADER, ""}; - [#http_bind{pid = FsmRef}] -> - gen_fsm:sync_send_all_state_event(FsmRef,stop), - {404, ?HEADER, ""} - end; - {repeat, OutPacket} -> - ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", - [OutPacket]), - send_outpacket(Sid, OutPacket); - {ok, Rid} -> - case http_put(Sid, Attrs, Packet) of - {error, polling_too_frequently} -> - ?DEBUG("polling too frequently: ~p", [Sid]), - case mnesia:dirty_read({http_bind, Sid}) of - [] -> %% unlikely! (?) - {404, ?HEADER, ""}; - [#http_bind{pid = FsmRef}] -> - gen_fsm:sync_send_all_state_event(FsmRef,stop), - {403, ?HEADER, ""} - end; - ok -> - receive_loop(Sid, Rid); - _ -> - {400, ?HEADER, ""} - end - end; + {ok, {ParsedSid, Rid, Key, NewKey, Attrs, Packet}} -> + XmppDomain = xml:get_attr_s("to",Attrs), + if + (ParsedSid == "") and (XmppDomain == "") -> + {200, ?HEADER, ""}; + true -> + Sid = if + (ParsedSid == "") -> + %% create new session + NewSid = sha:sha(term_to_binary({now(), make_ref()})), + {ok, Pid} = start(NewSid, Key), + ?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 + 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 + end, + mnesia:transaction( + fun() -> + mnesia:write(#http_bind{id = NewSid, + pid = Pid, + to = XmppDomain, + wait = Wait, + hold = Hold}) + end), + StreamStart = if + (XmppDomain /= "") -> + true; + true -> + false + end, + InPacket = Packet, + NewSid; + true -> + %% old session + Type = xml:get_attr_s("type",Attrs), + StreamStart = + case xml:get_attr_s("xmpp:restart",Attrs) of + "true" -> + true; + _ -> + false + end, + Wait = ?MAX_WAIT, + Hold = (?MAX_REQUESTS - 1), + if + (Type == "terminate") -> + %% terminate session + InPacket = Packet ++ ""; + true -> + InPacket = Packet + end, + ParsedSid + end, +%% ?DEBUG("~n InPacket: ~s ~n", [InPacket]), + case http_put(Sid, Rid, Key, NewKey, Hold, InPacket, StreamStart) of + {error, not_exists} -> + ?DEBUG("no session associated with sid: ~p", [Sid]), + {404, ?HEADER, ""}; + {error, bad_key} -> + ?DEBUG("bad key: ~s", [Key]), + case mnesia:dirty_read({http_bind, Sid}) of + [] -> + {404, ?HEADER, ""}; + [#http_bind{pid = FsmRef}] -> + gen_fsm:sync_send_all_state_event(FsmRef,stop), + {404, ?HEADER, ""} + end; + {error, polling_too_frequently} -> + ?DEBUG("polling too frequently: ~p", [Sid]), + case mnesia:dirty_read({http_bind, Sid}) of + [] -> %% unlikely! (?) + {404, ?HEADER, ""}; + [#http_bind{pid = FsmRef}] -> + gen_fsm:sync_send_all_state_event(FsmRef,stop), + {403, ?HEADER, ""} + end; + {repeat, OutPacket} -> + ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", + [OutPacket]), + send_outpacket(Sid, OutPacket); + ok -> + receive_loop(Sid,ParsedSid,Rid,Wait,Hold,Attrs) + end + end; _ -> {400, ?HEADER, ""} end. +receive_loop(Sid,ParsedSid,Rid,Wait,Hold,Attrs) -> + receive + after 100 -> ok + end, + prepare_response(Sid,ParsedSid,Rid,Wait,Hold,Attrs). + +prepare_response(Sid,ParsedSid,Rid,Wait,Hold,Attrs) -> + case http_get(Sid,Rid) of + {error, not_exists} -> + ?DEBUG("no session associated with sid: ~s", Sid), + {404, ?HEADER, ""}; + {ok, keep_on_hold} -> + receive_loop(Sid,ParsedSid,Rid,Wait,Hold,Attrs); + {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 + {404, ?HEADER, ""}; + {ok, OutPacket} -> + ?DEBUG("OutPacket: ~s", [OutPacket]), + if + Sid == ParsedSid -> + send_outpacket(Sid, OutPacket); + true -> + To = xml:get_attr_s("to",Attrs), + OutEls = case xml_stream:parse_element( + OutPacket++"") of + El when element(1, El) == xmlelement -> + {xmlelement, _, OutAttrs, Els} = El, + AuthID = xml:get_attr_s("id", OutAttrs), + StreamError = false, + case Els of + [] -> + []; + [{xmlelement, "stream:features", StreamAttribs, StreamEls} | StreamTail] -> + [{xmlelement, "stream:features", [{"xmlns:stream","http://etherx.jabber.org/streams"}] ++ StreamAttribs, StreamEls}] ++ StreamTail; + Xml -> + Xml + end; + {error, _} -> + AuthID = "", + StreamError = true, + [] + end, + if + To == "" -> + {200, ?HEADER, ""}; + StreamError == true -> + {200, ?HEADER, ""}; + true -> + {200, ?HEADER, + xml:element_to_string( + {xmlelement,"body", + [{"xmlns", + "http://jabber.org/protocol/httpbind"}, + {"sid",Sid}, + {"wait", integer_to_list(Wait)}, + {"requests", integer_to_list(Hold+1)}, + {"inactivity", + integer_to_list(trunc(?MAX_INACTIVITY/1000))}, + {"polling", ?MIN_POLLING}, + {"authid", AuthID} + ],OutEls})} + end + end + end. + +send_outpacket(Sid, OutPacket) -> + case OutPacket of + "" -> + {200, ?HEADER, ""}; + "" -> + case mnesia:dirty_read({http_bind, Sid}) of + [#http_bind{pid = FsmRef}] -> + gen_fsm:sync_send_all_state_event(FsmRef,stop) + end, + {200, ?HEADER, ""}; + _ -> + case xml_stream:parse_element("" + ++ OutPacket + ++ "") + of + El when element(1, El) == xmlelement -> + {xmlelement, _, _, OEls} = El, + TypedEls = [xml:replace_tag_attr("xmlns", + "jabber:client",OEl) || + OEl <- OEls], + ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n", + [xml:element_to_string( + {xmlelement,"body", + [{"xmlns", + "http://jabber.org/protocol/httpbind"}], + TypedEls})] + ), + {200, ?HEADER, + xml:element_to_string( + {xmlelement,"body", + [{"xmlns", + "http://jabber.org/protocol/httpbind"}], + TypedEls})}; + {error, _E} -> + OutEls = case xml_stream:parse_element( + OutPacket++"") of + SEl when element(1, SEl) == xmlelement -> + {xmlelement, _, _OutAttrs, SEls} = SEl, + StreamError = false, + case SEls of + [] -> + []; + [{xmlelement, "stream:features", StreamAttribs, StreamEls} | StreamTail] -> + TypedTail = [xml:replace_tag_attr("xmlns", + "jabber:client",OEl) || + OEl <- StreamTail], + [{xmlelement, "stream:features", [{"xmlns:stream","http://etherx.jabber.org/streams"}] ++ StreamAttribs, StreamEls}] ++ TypedTail; + Xml -> + Xml + end; + {error, _} -> + StreamError = true, + [] + end, + if + StreamError -> + StreamErrCond = case xml_stream:parse_element( + ""++OutPacket) of + El when element(1, El) == xmlelement -> + {xmlelement, _Tag, _Attr, Els} = El, + [{xmlelement, SE, _, Cond} | _] = Els, + if + SE == "stream:error" -> + Cond; + true -> + null + end; + {error, _E} -> + null + end, + case mnesia:dirty_read({http_bind, Sid}) of + [#http_bind{pid = FsmRef}] -> + gen_fsm:sync_send_all_state_event(FsmRef,stop); + _ -> + err %% hu? + end, + case StreamErrCond of + null -> + {200, ?HEADER, + ""}; + _ -> + {200, ?HEADER, + "" ++ + elements_to_string(StreamErrCond) ++ + ""} + end; + true -> + {200, ?HEADER, + xml:element_to_string( + {xmlelement,"body", + [{"xmlns", + "http://jabber.org/protocol/httpbind"}], + OutEls})} + end + end + end. + %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- @@ -158,7 +398,7 @@ process_request(Data) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([Sid, Rid, Key]) -> +init([Sid, Key]) -> ?INFO_MSG("started: ~p", [{Sid, Key}]), Opts = [], % TODO ejabberd_socket:start(ejabberd_c2s, ?MODULE, {http_bind, self()}, Opts), @@ -166,7 +406,6 @@ init([Sid, Rid, Key]) -> % ejabberd_c2s:become_controller(C2SPid), Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), {ok, loop, #state{id = Sid, - rid = Rid, key = Key, timer = Timer}}. @@ -232,73 +471,57 @@ handle_sync_event(stop, _From, _StateName, StateData) -> Reply = ok, {stop, normal, Reply, StateData}; -handle_sync_event({check_request, Attrs, Packet, _Hold}, - _From, check_key, StateData) -> - Key = xml:get_attr_s("key", Attrs), - case StateData#state.key of - "" -> - NewKey = xml:get_attr_s("newkey", Attrs), - {next_state, check_rid, StateData#state{key = NewKey}}; - StateKey -> - case httpd_util:to_lower( - hex(binary_to_list( - crypto:sha(Key)))) of - StateKey -> - case xml:get_attr_s("newkey", Attrs) of - "" -> - {next_state, check_rid, StateData#state{key = Key}}; - NewKey -> - {next_state, check_rid, StateData#state{key = NewKey}} - end; - _ -> - Reply = {error, bad_key}, - {reply, Reply, check_key, StateData} - end - end; - -handle_sync_event({check_request, Attrs, Packet, Hold}, - _From, check_rid = StateName, StateData) -> - case string:to_integer(xml:get_attr_s("rid", Attrs)) of - {error, _} -> - Reply = {error, not_exists}, - {reply, Reply, StateName, StateData}; - {Rid, _} -> - case StateData#state.rid of - error -> - {reply, ok, request_checked, StateData#state{rid = Rid}}; - StateRid -> - if - (StateRid < Rid) and - (Rid =< StateRid + Hold + 1) -> - {reply, {ok, Rid}, request_checked, StateData#state{rid = Rid}}; - ((StateRid-Hold-1) < Rid )and - (Rid =< StateRid) -> - %% Repeat request - [Out | _XS] = [El#hbr.out || - El <- StateData#state.req_list, - El#hbr.rid == Rid], - Reply = case Out of - [[] | OutPacket] -> - {repeat, OutPacket}; - _ -> - {repeat, Out} - end, - {reply, Reply, StateName, - StateData#state{input = "cancel"}}; - true -> - Reply = {error, not_exists}, - {reply, Reply, StateName, StateData} - end - end - end; - -handle_sync_event({check_request, Attrs, Packet, Hold}, - _From, _StateName, StateData) -> - {next_state, check_key, StateData}; - -handle_sync_event({http_put, _Rid, Attrs, Packet, Hold}, - _From, check_activity, StateData) -> - ?DEBUG("check activity", []), +handle_sync_event({http_put, Rid, Key, NewKey, Hold, Packet, StartTo}, + _From, StateName, StateData) -> + %% check if Rid valid + RidAllow = case Rid of + error -> + false; + _ -> + case StateData#state.rid of + error -> + %% first request - nothing saved so far + true; + OldRid -> + ?DEBUG("state.rid/cur rid: ~p/~p", + [OldRid, Rid]), + if + (OldRid < Rid) and + (Rid =< (OldRid + Hold + 1)) -> + true; + (Rid =< OldRid) and + (Rid > OldRid - Hold - 1) -> + repeat; + true -> + false + end + end + end, + %% check if key valid + KeyAllow = case RidAllow of + repeat -> + true; + false -> + false; + true -> + case StateData#state.key of + "" -> + true; + OldKey -> + NextKey = httpd_util:to_lower( + hex(binary_to_list( + crypto: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, {_,TSec,TMSec} = now(), TNow = TSec*1000*1000 + TMSec, LastPoll = if @@ -312,75 +535,98 @@ handle_sync_event({http_put, _Rid, Attrs, Packet, Hold}, (Packet == "") and (TNow - StateData#state.last_poll < MinPoll*1000*1000) -> Reply = {error, polling_too_frequently}, - {reply, Reply, send2server, StateData}; - true -> - {next_state, send2server, StateData#state{last_poll = LastPoll}} + {reply, Reply, StateName, StateData}; + KeyAllow -> + case RidAllow of + false -> + Reply = {error, not_exists}, + {reply, Reply, StateName, StateData}; + repeat -> + ?DEBUG("REPEATING ~p", [Rid]), + [Out | _XS] = [El#hbr.out || + El <- StateData#state.req_list, + El#hbr.rid == Rid], + case Out of + [[] | OutPacket] -> + Reply = {repeat, OutPacket}; + _ -> + Reply = {repeat, Out} + end, + {reply, Reply, StateName, + StateData#state{input = "cancel", last_poll = LastPoll}}; + true -> + SaveKey = if + NewKey == "" -> + Key; + true -> + NewKey + end, + ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), + + %% save request + ReqList = [#hbr{rid=Rid, + key=StateData#state.key, + in=StateData#state.input, + out=StateData#state.output + } | + [El || El <- StateData#state.req_list, + El#hbr.rid < Rid, + El#hbr.rid > (Rid - 1 - Hold)] + ], +%% ?DEBUG("reqlist: ~p", [ReqList]), + case StateData#state.waiting_input of + false -> + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer( + ?MAX_INACTIVITY, self(), []), + Input = Packet ++ [StateData#state.input], + Reply = ok, + {reply, Reply, StateName, + StateData#state{input = Input, + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + last_poll = LastPoll, + req_list = ReqList + }}; + {Receiver, _Tag} -> + SendPacket = + if + StartTo /= "" -> + [""] ++ Packet; + true -> + Packet + end, + ?DEBUG("really sending now: ~s", [SendPacket]), + Receiver ! {tcp, {http_bind, self()}, + list_to_binary(SendPacket)}, + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer( + ?MAX_INACTIVITY, self(), []), + Reply = ok, + {reply, Reply, StateName, + StateData#state{waiting_input = false, + last_receiver = Receiver, + input = "", + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + last_poll = LastPoll, + req_list = ReqList + }} + end + end; + true -> + Reply = {error, bad_key}, + {reply, Reply, StateName, StateData} end; -handle_sync_event({http_put, Rid, Packet, StartTo, Hold}, - _From, send2server = StateName, StateData) -> - - %% save request - ReqList = [#hbr{rid=Rid, - key=StateData#state.key, - in=StateData#state.input, - out=StateData#state.output - } | - [El || El <- StateData#state.req_list, - El#hbr.rid < Rid, - El#hbr.rid > (Rid - 1 - Hold)] - ], - - {_,TSec,TMSec} = now(), - TNow = TSec*1000*1000 + TMSec, - - case StateData#state.waiting_input of - false -> - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer( - ?MAX_INACTIVITY, self(), []), - Input = Packet ++ [StateData#state.input], - Reply = ok, - {reply, Reply, StateName, - StateData#state{input = Input, - ctime = TNow, - timer = Timer, - req_list = ReqList - }}; - {Receiver, _Tag} -> - SendPacket = - if - StartTo /= "" -> - [""] ++ Packet; - true -> - Packet - end, - ?DEBUG("really sending now: ~s", [SendPacket]), - Receiver ! {tcp, {http_bind, self()}, - list_to_binary(SendPacket)}, - cancel_timer(StateData#state.timer), - Timer = erlang:start_timer( - ?MAX_INACTIVITY, self(), []), - Reply = ok, - {reply, Reply, StateName, - StateData#state{waiting_input = false, - last_receiver = Receiver, - input = "", - ctime = TNow, - timer = Timer, - req_list = ReqList - }} - end; -handle_sync_event({http_put, Rid, Packet, StartTo, Hold}, - _From, StateName, StateData) -> - ?DEBUG("http-put checking acitivtiy", []), - {next_state, check_activity, StateData}; - - handle_sync_event({http_get, Rid, Wait, Hold}, _From, StateName, StateData) -> {_,TSec,TMSec} = now(), TNow = TSec*1000*1000 + TMSec, @@ -468,251 +714,27 @@ terminate(_Reason, _StateName, StateData) -> %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- -limit_val_max(Val, Max) when is_list(Val) -> - case string:to_integer(Val) of - {error, _} -> Max; - {IntVal, _} -> limit_val_max(IntVal, Max) - end; -limit_val_max(Val, Max) when is_integer(Val) and (Val =< Max) -> - Val; -limit_val_max(Val, Max) when is_integer(Val) and (Val > Max) -> - Max; -limit_val_max(_, Max) -> Max. -create_session(XmppDomain, Attrs, Packet) -> - case string:to_integer(xml:get_attr_s("rid", Attrs)) of - {error, _} -> - ?DEBUG("'rid' invalid", []), - {400, ?HEADER, ""}; - {Rid, _} -> - Sid = sha:sha(term_to_binary({now(), make_ref()})), - Key = xml:get_attr_s("key", Attrs), - {ok, Pid} = start(Sid, Rid, Key), - Wait = limit_val_max(xml:get_attr_s("wait",Attrs), ?MAX_WAIT), - Hold = limit_val_max(xml:get_attr_s("hold",Attrs), (?MAX_REQUESTS-1)), - F = fun() -> - mnesia:write( - #http_bind{id = Sid, - pid = Pid, - to = XmppDomain, - wait = Wait, - hold = Hold}) - end, - case catch mnesia:transaction(F) of - {atomic, ok} -> - ?DEBUG("created session with sid: ~s", [Sid]), - case http_put(Sid, Attrs, Packet) of - ok -> - receive_loop(Sid, Rid); - _ -> - {400, ?HEADER, ""} - end; - _E -> - ?DEBUG("error creating session: ~p", [_E]), - close({http_bind, Pid}), - {200, ?HEADER, - ""} - end - end. -receive_loop(Sid, Rid) -> - receive - after 100 -> ok - end, - prepare_response(Sid, Rid). - -prepare_response(Sid, Rid) -> - case http_get(Sid,Rid) of - {error, not_exists} -> - ?DEBUG("no session associated with sid: ~s", Sid), - {404, ?HEADER, ""}; - {ok, keep_on_hold} -> - receive_loop(Sid, Rid); - {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 - {404, ?HEADER, ""}; - {ok, OutPacket} -> - ?DEBUG("OutPacket: ~s", [OutPacket]), - send_outpacket(Sid, OutPacket); - {ok, stream_start, OutPacket} -> - ?DEBUG("OutPacket: ~s", [OutPacket]), - OutEls = case xml_stream:parse_element( - OutPacket++"") of - El when element(1, El) == xmlelement -> - {xmlelement, _, OutAttrs, Els} = El, - AuthID = xml:get_attr_s("id", OutAttrs), - StreamError = false, - case Els of - [] -> - []; - [{xmlelement, "stream:features", StreamAttribs, StreamEls} | StreamTail] -> - [{xmlelement, "stream:features", [{"xmlns:stream","http://etherx.jabber.org/streams"}] ++ StreamAttribs, StreamEls}] ++ StreamTail; - Xml -> - Xml - end; - {error, _} -> - AuthID = "", - StreamError = true, - [] - end, -% To = xml:get_attr_s("to",Attrs), - if -% To == "" -> -% {200, ?HEADER, ""}; - StreamError == true -> - {200, ?HEADER, ""}; - true -> - case mnesia:dirty_read({http_bind, Sid}) of - [#http_bind{wait = Wait, hold = Hold}] -> - {200, ?HEADER, - xml:element_to_string( - {xmlelement,"body", - [{"xmlns", - "http://jabber.org/protocol/httpbind"}, - {"sid",Sid}, - {"wait", integer_to_list(Wait)}, - {"requests", integer_to_list(Hold+1)}, - {"inactivity", - integer_to_list(trunc(?MAX_INACTIVITY/1000))}, - {"polling", ?MIN_POLLING}, - {"authid", AuthID} - ],OutEls})}; - _ -> - {404, ?HEADER, ""} - end - end - end. - -send_outpacket(Sid, []) -> - {200, ?HEADER, ""}; -send_outpacket(Sid, "") -> - case mnesia:dirty_read({http_bind, Sid}) of - [#http_bind{pid = FsmRef}] -> - gen_fsm:sync_send_all_state_event(FsmRef,stop) - end, - {200, ?HEADER, ""}; -send_outpacket(Sid, OutPacket) -> - case xml_stream:parse_element("" - ++ OutPacket - ++ "") of - El when element(1, El) == xmlelement -> - {xmlelement, _, _, OEls} = El, - TypedEls = [xml:replace_tag_attr("xmlns", - "jabber:client",OEl) || - OEl <- OEls], - ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n", - [xml:element_to_string( - {xmlelement,"body", - [{"xmlns", - "http://jabber.org/protocol/httpbind"}], - TypedEls})] - ), - {200, ?HEADER, - xml:element_to_string( - {xmlelement,"body", - [{"xmlns", - "http://jabber.org/protocol/httpbind"}], - TypedEls})}; - {error, _E} -> - OutEls = case xml_stream:parse_element( - OutPacket++"") of - SEl when element(1, SEl) == xmlelement -> - {xmlelement, _, _OutAttrs, SEls} = SEl, - StreamError = false, - case SEls of - [] -> - []; - [{xmlelement, "stream:features", StreamAttribs, StreamEls} | StreamTail] -> - TypedTail = [xml:replace_tag_attr("xmlns", - "jabber:client",OEl) || - OEl <- StreamTail], - [{xmlelement, "stream:features", [{"xmlns:stream","http://etherx.jabber.org/streams"}] ++ StreamAttribs, StreamEls}] ++ TypedTail; - Xml -> - Xml - end; - {error, _} -> - StreamError = true, - [] - end, - if - StreamError -> - StreamErrCond = case xml_stream:parse_element( - ""++OutPacket) of - El when element(1, El) == xmlelement -> - {xmlelement, _Tag, _Attr, Els} = El, - [{xmlelement, SE, _, Cond} | _] = Els, - if - SE == "stream:error" -> - Cond; - true -> - null - end; - {error, _E} -> - null - end, - case mnesia:dirty_read({http_bind, Sid}) of - [#http_bind{pid = FsmRef}] -> - gen_fsm:sync_send_all_state_event(FsmRef,stop); - _ -> - err %% hu? - end, - case StreamErrCond of - null -> - {200, ?HEADER, - ""}; - _ -> - {200, ?HEADER, - "" ++ - elements_to_string(StreamErrCond) ++ - ""} - end; - true -> - {200, ?HEADER, - xml:element_to_string( - {xmlelement,"body", - [{"xmlns", - "http://jabber.org/protocol/httpbind"}], - OutEls})} - end - end. - -check_request(Sid, Attrs, Packet) -> - case mnesia:dirty_read({http_bind, Sid}) of - [] -> - ?DEBUG("not found",[]), - {error, not_exists}; - [#http_bind{pid = FsmRef, hold = Hold}] -> - gen_fsm:sync_send_all_state_event( - FsmRef, {http_put, Attrs, Packet, Hold}) - end. - - -http_put(Sid, Attrs, Packet) -> +http_put(Sid, Rid, Key, NewKey, Hold, Packet, Restart) -> ?DEBUG("http-put",[]), - {Rid, _} = string:to_integer(xml:get_attr_s("rid", Attrs)), - To = xml:get_attr_s("to", Attrs), case mnesia:dirty_read({http_bind, Sid}) of [] -> ?DEBUG("not found",[]), {error, not_exists}; - [#http_bind{pid = FsmRef, to = To, hold = Hold}] -> - gen_fsm:sync_send_all_state_event( - FsmRef, {http_put, Rid, Packet, To, Hold}) + [#http_bind{pid = FsmRef,to=To}] -> + case Restart of + true -> + ?DEBUG("restart requested for ~s", [To]), + gen_fsm:sync_send_all_state_event( + FsmRef, {http_put, Rid, Key, NewKey, Hold, Packet, To}); + _ -> + gen_fsm:sync_send_all_state_event( + FsmRef, {http_put, Rid, Key, NewKey, Hold, Packet, ""}) + end end. -http_get(Sid, Rid) -> +http_get(Sid,Rid) -> case mnesia:dirty_read({http_bind, Sid}) of [] -> {error, not_exists}; @@ -725,39 +747,43 @@ http_get(Sid, Rid) -> parse_request(Data) -> ?DEBUG("--- incoming data --- ~n~s~n --- END --- ", [Data]), - case catch xml_stream:parse_element(Data) of - {xmlelement, "body", Attrs, Els} -> - case xml:get_attr_s("xmlns",Attrs) of - "http://jabber.org/protocol/httpbind" -> - Sid = xml:get_attr_s("sid",Attrs), - %% normalize tree - actually not needed by XEP but - %% where playing nicely here - FixedEls = - lists:filter( - fun(I) -> - case I of - {xmlelement, _, _, _} -> - true; - _ -> - false - end - end, Els), - %% fix namespace of child element - lists:map(fun(E) -> - case xml:get_tag_attr_s("xmlns",E) of - "jabber:client" -> - remove_tag_attr("xmlns",E); - true -> - ok - end - end, FixedEls), - %% revert to string - Packet = [xml:element_to_string(E) || E <- FixedEls], - {ok, {Sid, Attrs, Packet}}; - _ -> %% bad namespace - {error, bad_request} - end; - _ -> + case xml_stream:parse_element(Data) of + El when element(1, El) == xmlelement -> + {xmlelement, Name, Attrs, Els} = El, + FixedEls = + lists:filter( + fun(I) -> + case I of + {xmlelement, _, _, _} -> + true; + _ -> + false + end + end, Els), + Sid = xml:get_attr_s("sid",Attrs), + {Rid,_X} = string:to_integer(xml:get_attr_s("rid",Attrs)), + Key = xml:get_attr_s("key",Attrs), + NewKey = xml:get_attr_s("newkey",Attrs), + Xmlns = xml:get_attr_s("xmlns",Attrs), + lists:map(fun(E) -> + EXmlns = xml:get_tag_attr_s("xmlns",E), + if + EXmlns == "jabber:client" -> + remove_tag_attr("xmlns",E); + true -> + ok + end + end, FixedEls), + Packet = [xml:element_to_string(E) || E <- FixedEls], + if + Name /= "body" -> + {error, bad_request}; + Xmlns /= "http://jabber.org/protocol/httpbind" -> + {error, bad_request}; + true -> + {ok, {Sid, Rid, Key, NewKey, Attrs, Packet}} + end; + {error, _Reason} -> {error, bad_request} end. diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl index 989d025e3..1cebd0f5e 100644 --- a/src/web/mod_http_bind.erl +++ b/src/web/mod_http_bind.erl @@ -3,6 +3,7 @@ %%% Author : Stefan Strigler %%% Purpose : Implementation of XMPP over BOSH (XEP-0206) %%% Created : Tue Feb 20 13:15:52 CET 2007 +%%% Id : $Id: $ %%%---------------------------------------------------------------------- %%%---------------------------------------------------------------------- @@ -14,7 +15,7 @@ -module(mod_http_bind). -author('steve@zeank.in-berlin.de'). --define(MOD_HTTP_BIND_VERSION, '1.0'). +-define(MOD_HTTP_BIND_VERSION, "1.0"). -vsn(?MOD_HTTP_BIND_VERSION). %%-define(ejabberd_debug, true). @@ -47,7 +48,7 @@ process([], #request{method = 'POST', ejabberd_http_bind:process_request(Data); process([], #request{method = 'GET', data = []}) -> - Heading = "Ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ lists:concat([?MOD_HTTP_BIND_VERSION]), + Heading = "Ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ ?MOD_HTTP_BIND_VERSION, {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}], [{xmlelement, "head", [], [{xmlelement, "title", [], [{xmlcdata, Heading}]}]}, @@ -56,16 +57,21 @@ process([], #request{method = 'GET', {xmlelement, "p", [], [{xmlcdata, "An implementation of "}, {xmlelement, "a", [{"href", "http://www.xmpp.org/extensions/xep-0206.html"}], - [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}, - {xmlcdata, "."}]}, + [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]}, {xmlelement, "p", [], - [{xmlcdata, integer_to_list(mnesia:table_info(http_bind, size)) ++ " sessions found."}]} - ]}]}; + [{xmlcdata, integer_to_list(mnesia:table_info(http_bind, size)) ++ " sessions found."}]}, + {xmlelement, "p", [], + [{xmlcdata, "Sponsored by "}, + {xmlelement, "a", [{"href", "http://mabber.com"}], + [{xmlcdata, "mabber"}]}, + {xmlcdata, "."}]} + ]}]}; process(_Path, _Request) -> ?DEBUG("Bad Request: ~p", [_Request]), {400, [], {xmlelement, "h1", [], [{xmlcdata, "400 Bad Request"}]}}. + %%%---------------------------------------------------------------------- %%% BEHAVIOUR CALLBACKS %%%----------------------------------------------------------------------