From 197639a34b7b1aac2703f046896a1a68d674b6c6 Mon Sep 17 00:00:00 2001
From: Badlop
Date: Tue, 16 Jun 2009 18:24:14 +0000
Subject: [PATCH] Minor changes (thanks to Stefan Strigler)
SVN Revision: 2269
---
src/web/ejabberd_http_bind.erl | 952 ++++++++++++++++-----------------
src/web/mod_http_bind.erl | 18 +-
2 files changed, 469 insertions(+), 501 deletions(-)
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, "
"
- ++ 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, ""
+ ++ 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
%%%----------------------------------------------------------------------