diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index d16a5d0c4..0cbe40e86 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -3,17 +3,16 @@ %%% 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('Revision: 1.4'). +-vsn('1.9'). -behaviour(gen_fsm). %% External exports --export([start_link/2, +-export([start_link/3, init/1, handle_event/3, handle_sync_event/4, @@ -26,7 +25,7 @@ close/1, process_request/1]). -%%-define(ejabberd_debug, true). +-define(ejabberd_debug, true). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -35,10 +34,7 @@ -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, @@ -53,8 +49,7 @@ req_list = [] % list of requests }). - -%%-define(DBGFSM, true). +-define(DBGFSM, true). -ifdef(DBGFSM). -define(FSMOPTS, [{debug, [trace]}]). @@ -62,6 +57,8 @@ -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 @@ -69,18 +66,17 @@ -define(CT, {"Content-Type", "text/xml; charset=utf-8"}). -define(HEADER, [?CT,{"X-Sponsored-By", "http://mabber.com"}]). - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- -start(Sid, Key) -> +start(Sid, Rid, Key) -> mnesia:create_table(http_bind, [{ram_copies, [node()]}, {attributes, record_info(fields, http_bind)}]), - supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key]). + supervisor:start_child(ejabberd_http_bind_sup, [Sid, Rid, Key]). -start_link(Sid, Key) -> - gen_fsm:start_link(?MODULE, [Sid, Key], ?FSMOPTS). +start_link(Sid, Rid, Key) -> + gen_fsm:start_link(?MODULE, [Sid, Rid, Key], ?FSMOPTS). send({http_bind, FsmRef}, Packet) -> gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). @@ -99,294 +95,58 @@ 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, {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; + {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; _ -> {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 %%%---------------------------------------------------------------------- @@ -398,7 +158,7 @@ send_outpacket(Sid, OutPacket) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([Sid, Key]) -> +init([Sid, Rid, Key]) -> ?INFO_MSG("started: ~p", [{Sid, Key}]), Opts = [], % TODO ejabberd_socket:start(ejabberd_c2s, ?MODULE, {http_bind, self()}, Opts), @@ -406,6 +166,7 @@ init([Sid, Key]) -> % ejabberd_c2s:become_controller(C2SPid), Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), {ok, loop, #state{id = Sid, + rid = Rid, key = Key, timer = Timer}}. @@ -471,57 +232,73 @@ handle_sync_event(stop, _From, _StateName, StateData) -> Reply = ok, {stop, normal, Reply, StateData}; -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, +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", []), {_,TSec,TMSec} = now(), TNow = TSec*1000*1000 + TMSec, LastPoll = if @@ -535,98 +312,75 @@ handle_sync_event({http_put, Rid, Key, NewKey, Hold, Packet, StartTo}, (Packet == "") and (TNow - StateData#state.last_poll < MinPoll*1000*1000) -> 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]), - [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} + {reply, Reply, send2server, StateData}; + true -> + {next_state, send2server, StateData#state{last_poll = LastPoll}} 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, @@ -714,27 +468,251 @@ 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. -http_put(Sid, Rid, Key, NewKey, Hold, Packet, Restart) -> - ?DEBUG("http-put",[]), +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,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 + [#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) -> + ?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}) end. -http_get(Sid,Rid) -> +http_get(Sid, Rid) -> case mnesia:dirty_read({http_bind, Sid}) of [] -> {error, not_exists}; @@ -747,43 +725,39 @@ http_get(Sid,Rid) -> parse_request(Data) -> ?DEBUG("--- incoming data --- ~n~s~n --- END --- ", [Data]), - 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} -> + 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; + _ -> {error, bad_request} end. diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl index 1cebd0f5e..989d025e3 100644 --- a/src/web/mod_http_bind.erl +++ b/src/web/mod_http_bind.erl @@ -3,7 +3,6 @@ %%% Author : Stefan Strigler %%% Purpose : Implementation of XMPP over BOSH (XEP-0206) %%% Created : Tue Feb 20 13:15:52 CET 2007 -%%% Id : $Id: $ %%%---------------------------------------------------------------------- %%%---------------------------------------------------------------------- @@ -15,7 +14,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). @@ -48,7 +47,7 @@ process([], #request{method = 'POST', ejabberd_http_bind:process_request(Data); process([], #request{method = 'GET', data = []}) -> - Heading = "Ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ ?MOD_HTTP_BIND_VERSION, + Heading = "Ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ lists:concat([?MOD_HTTP_BIND_VERSION]), {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}], [{xmlelement, "head", [], [{xmlelement, "title", [], [{xmlcdata, Heading}]}]}, @@ -57,21 +56,16 @@ 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, "XMPP over BOSH (XEP-0206)"}]}, + {xmlcdata, "."}]}, {xmlelement, "p", [], - [{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, "."}]} - ]}]}; + [{xmlcdata, integer_to_list(mnesia:table_info(http_bind, size)) ++ " sessions found."}]} + ]}]}; process(_Path, _Request) -> ?DEBUG("Bad Request: ~p", [_Request]), {400, [], {xmlelement, "h1", [], [{xmlcdata, "400 Bad Request"}]}}. - %%%---------------------------------------------------------------------- %%% BEHAVIOUR CALLBACKS %%%----------------------------------------------------------------------