SVN Revision: 309
This commit is contained in:
Alexey Shchepin 2005-04-17 21:39:41 +00:00
parent 2d9b9956b5
commit e46b28f27f
12 changed files with 16 additions and 3874 deletions

View File

@ -1,3 +0,0 @@
jabber://aleksey@jabber.ru
jabber://sgolovan@nes.ru
ryazanov@gmail.com

View File

@ -1,3 +1,7 @@
2005-04-17 Alexey Shchepin <alexey@sevcom.net>
* (all): Merged virtual hosting support
2005-04-09 Alexey Shchepin <alexey@sevcom.net>
* src/ejabberd_c2s.erl: Send new id for each new stream inside one

86
TODO-vh
View File

@ -1,86 +0,0 @@
check remove_user hook
check all usages of ejabberd_auth
+ src/ejabberd.hrl -- add MYHOSTS macro to return a list of virtual domains,
MYNAME should return first defined host
? src/acl.erl -- "user", "user_regexp", "user_regexp" should match all virtual
domains
+ src/cyrsasl_digest.erl -- need to use both "username" and "authzid", or better
to use callback function
+ src/cyrsasl_plain.erl -- likewise
+ src/cyrsasl.erl -- likewise
+ src/ejabberd_auth.erl -- add "Server" argument to almost all exported
functions
(workaround) src/ejabberd_auth_external.erl -- likewise
+ src/ejabberd_auth_internal.erl -- likewise
(workaround) src/ejabberd_auth_ldap.erl -- likewise
(workaround) src/ejabberd_auth_odbc.erl -- likewise
+ src/ejabberd_c2s.erl -- add validation of a server field
+ src/ejabberd_config.erl -- support for "hosts" option
src/ejabberd_ctl.erl -- add server argument to "register", "unregister", and
"registered-users" commands
+ src/ejabberd_local.erl -- register all virtual hosts in router
+ src/ejabberd_router.erl -- update dirty_get_all_routes/0
src/ejabberd_service.erl -- probably minor update in stream header
+ src/ejabberd_sm.erl -- update "session" and "presence" tables to contain
server name in first two fields
- src/extauth.erl -- same as for src/ejabberd_auth_external.erl
src/jd2ejd.erl -- anyway need to be rewriten :)
+ src/mod_announce.erl -- update to ejabberd_auth changes, (-) probably update
to send different server names in "from" attribute to users on different
virtual hosts
+ src/mod_configure.erl -- update users part
+ src/mod_disco.erl -- likewise
+ src/mod_last.erl -- update db table to store server part of jid
src/mod_last_odbc.erl -- likewise
+ src/mod_offline.erl -- likewise
src/mod_offline_odbc.erl -- likewise
+ src/mod_privacy.erl -- likewise
+ src/mod_private.erl -- likewise
+ src/mod_register.erl -- need to check server field in sender jid and pass it
to ejabberd_auth
(not tested) src/mod_roster.erl -- update db table to store server part of jid
src/mod_roster_odbc.erl -- update roster_in_subscription,
roster_out_subscription, roster_get_subscription_lists hooks
+ src/mod_vcard.erl -- update db table to store server part of jid
src/mod_vcard_ldap.erl -- update db table to store server part of jid
src/mod_pubsub/mod_pubsub.erl -- update defining of "ServedHosts" variable
+ src/web/ejabberd_web_admin.erl -- update user listing, roster editing, acl
setting parts
+ src/web/ejabberd_web.erl -- update user authorization

View File

@ -17,6 +17,7 @@
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
@ -118,7 +119,7 @@ dirty_get_registered_users() ->
[]
end.
dirty_get_registered_users(Server) ->
get_vh_registered_users(Server) ->
dirty_get_registered_users().
get_password(User, _Server) ->

View File

@ -445,7 +445,8 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
?INFO_MSG("(~w) Accepted authentication for ~s",
[StateData#state.socket, U]),
{next_state, wait_for_stream,
StateData#state{authentificated = true,
StateData#state{streamid = new_id(),
authentificated = true,
user = U
}};
{continue, ServerOut, NewSASLState} ->
@ -475,6 +476,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
{next_state, wait_for_stream,
StateData#state{sockmod = tls,
socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
}};
_ ->
@ -533,7 +535,8 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
?INFO_MSG("(~w) Accepted authentication for ~s",
[StateData#state.socket, U]),
{next_state, wait_for_stream,
StateData#state{authentificated = true,
StateData#state{streamid = new_id(),
authentificated = true,
user = U
}};
{continue, ServerOut, NewSASLState} ->

View File

@ -147,7 +147,7 @@ process(Node, ["load", Path]) ->
process(Node, ["restore", Path]) ->
case rpc:call(Node,
mnesia, restore, [Path, [{default_op, keep_tables}]]) of
{atomic, ok} ->
{atomic, _} ->
?STATUS_SUCCESS;
{error, Reason} ->
io:format("Can't restore backup from ~p on node ~p: ~p~n",

View File

@ -146,7 +146,8 @@ clean_table_from_bad_node(Node) ->
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
mnesia:delete_object(E),
mnesia:delete({presence, E#session.usr})
end, Es)
end,
mnesia:transaction(F).

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +0,0 @@
%% Generated by the Erlang ASN.1 compiler version:1.3.2
%% Purpose: Erlang record definitions for each named and unnamed
%% SEQUENCE and SET, and macro definitions for each value
%% definition,in module ELDAPv3
-record('LDAPMessage',{
messageID, protocolOp, controls = asn1_NOVALUE}).
-record('AttributeValueAssertion',{
attributeDesc, assertionValue}).
-record('Attribute',{
type, vals}).
-record('LDAPResult',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE}).
-record('Control',{
controlType, criticality = asn1_DEFAULT, controlValue = asn1_NOVALUE}).
-record('BindRequest',{
version, name, authentication}).
-record('SaslCredentials',{
mechanism, credentials = asn1_NOVALUE}).
-record('BindResponse',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, serverSaslCreds = asn1_NOVALUE}).
-record('SearchRequest',{
baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributes}).
-record('SubstringFilter',{
type, substrings}).
-record('MatchingRuleAssertion',{
matchingRule = asn1_NOVALUE, type = asn1_NOVALUE, matchValue, dnAttributes = asn1_DEFAULT}).
-record('SearchResultEntry',{
objectName, attributes}).
-record('PartialAttributeList_SEQOF',{
type, vals}).
-record('ModifyRequest',{
object, modification}).
-record('ModifyRequest_modification_SEQOF',{
operation, modification}).
-record('AttributeTypeAndValues',{
type, vals}).
-record('AddRequest',{
entry, attributes}).
-record('AttributeList_SEQOF',{
type, vals}).
-record('ModifyDNRequest',{
entry, newrdn, deleteoldrdn, newSuperior = asn1_NOVALUE}).
-record('CompareRequest',{
entry, ava}).
-record('ExtendedRequest',{
requestName, requestValue = asn1_NOVALUE}).
-record('ExtendedResponse',{
resultCode, matchedDN, errorMessage, referral = asn1_NOVALUE, responseName = asn1_NOVALUE, response = asn1_NOVALUE}).
-define('maxInt', 2147483647).

View File

@ -808,8 +808,8 @@ filter_presence({xmlelement, "presence", Attrs, Els}) ->
case El of
{xmlcdata, _} ->
false;
{xmlelement, Name1, _Attrs1, _Els1} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
{xmlelement, Name1, Attrs1, _Els1} ->
XMLNS = xml:get_attr_s("xmlns", Attrs1),
case {Name1, XMLNS} of
{"show", ""} ->
true;

View File

@ -1,404 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_http.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_http).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start/2,
start_link/2]).
%% gen_fsm callbacks
-export([init/1,
wait_for_headers/2,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(DICT, dict).
-record(state, {socket,
request_method,
request_path,
request_auth
}).
-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(XHTML_DOCTYPE,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n").
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(SockData, Opts) ->
supervisor:start_child(ejabberd_http_sup, [SockData, Opts]).
start_link(SockData, Opts) ->
gen_fsm:start_link(ejabberd_http, [SockData, Opts], ?FSMOPTS).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
?INFO_MSG("started: ~p", [{SockMod, Socket}]),
case SockMod of
gen_tcp ->
inet:setopts(Socket, [{packet, http}, {active, true}, {recbuf, 0}]);
ssl ->
ssl:setopts(Socket, [{packet, http}, {active, true}])
end,
{ok, wait_for_headers, #state{socket = Socket}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_headers(_, StateData) ->
{next_state, wait_for_headers, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event(_Event, _From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({http_request, _Socket, Method, Path, _Version},
StateName, StateData) ->
{next_state, StateName, StateData#state{request_method = Method,
request_path = Path}};
handle_info({http_header, _Socket, _, 'Authorization', _, Auth},
StateName, StateData) ->
{next_state, StateName,
StateData#state{request_auth = parse_auth(Auth)}};
handle_info({http_eoh, _Socket}, StateName, StateData) ->
process_request(StateData),
{stop, normal, StateData};
handle_info({tcp_closed, _Socket}, StateName, StateData) ->
{stop, normal, StateData};
handle_info({tcp_error, _Socket, _Reason}, StateName, StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("terminated: ~p", [Reason]),
gen_tcp:close(StateData#state.socket),
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
send_text(State, Text) ->
gen_tcp:send(State#state.socket, Text).
process_request(#state{request_method = 'GET',
request_path = {abs_path, Path},
request_auth = undefined} = State) ->
Out = make_xhtml_output(
401,
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "403 Unauthorized"}]}])),
send_text(State, Out),
ok;
process_request(#state{request_method = 'GET',
request_path = {abs_path, Path},
request_auth = {User, Pass}} = State) ->
Out = make_xhtml_output(
200,
[],
make_xhtml([{xmlelement, "h1", [],
[{xmlcdata, "Welcome " ++ User}]}])),
send_text(State, Out),
ok;
process_request(State) ->
todo.
make_xhtml(Els) ->
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", "en"},
{"lang", "en"}],
[{xmlelement, "head", [],
[{xmlelement, "meta", [{"http-equiv", "Content-Type"},
{"content", "text/html; charset=utf-8"}], []}]},
{xmlelement, "body", [], Els}
]}.
make_xhtml_output(Status, Headers, XHTML) ->
Data = list_to_binary([?XHTML_DOCTYPE, xml:element_to_string(XHTML)]),
Headers1 = [{"Content-Type", "text/html; charset=utf-8"},
{"Content-Length", integer_to_list(size(Data))} | Headers],
H = lists:map(fun({Attr, Val}) ->
[Attr, ": ", Val, "\r\n"]
end, Headers1),
SL = ["HTTP/1.1 ", integer_to_list(Status), " ",
code_to_phrase(Status), "\r\n"],
[SL, H, "\r\n", Data].
% Code below is taken (with some modifications) from the yaws webserver, which
% is distributed under the folowing license:
%
%This software (the yaws webserver) is free software.
%Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
%Any use or misuse of the source code is hereby freely allowed.
%
%1. Redistributions of source code must retain the above copyright
% notice as well as this list of conditions.
%
%2. Redistributions in binary form must reproduce the above copyright
% notice as well as this list of conditions.
%% url decode the path and return {Path, QueryPart}
url_decode_q_split(Path) ->
url_decode_q_split(Path, []).
url_decode_q_split([$%, $C, $2, $%, Hi, Lo | Tail], Ack) ->
Hex = hex_to_integer([Hi, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi > $9 ->
Hex = hex_to_integer([Hi+4, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi < $A ->
Hex = hex_to_integer([Hi+4+7, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$%, Hi, Lo | Tail], Ack) ->
Hex = hex_to_integer([Hi, Lo]),
url_decode_q_split(Tail, [Hex|Ack]);
url_decode_q_split([$?|T], Ack) ->
%% Don't decode the query string here, that is parsed separately.
{path_norm_reverse(Ack), T};
url_decode_q_split([H|T], Ack) ->
url_decode_q_split(T, [H|Ack]);
url_decode_q_split([], Ack) ->
{path_norm_reverse(Ack), []}.
path_norm_reverse("/" ++ T) -> start_dir(0, "/", T);
path_norm_reverse( T) -> start_dir(0, "", T).
start_dir(N, Path, ".." ) -> rest_dir(N, Path, "");
start_dir(N, Path, "/" ++ T ) -> start_dir(N , Path, T);
start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T);
start_dir(N, Path, "../" ++ T ) -> start_dir(N + 1, Path, T);
start_dir(N, Path, T ) -> rest_dir (N , Path, T).
rest_dir (_N, Path, [] ) -> case Path of
[] -> "/";
_ -> Path
end;
rest_dir (0, Path, [ $/ | T ] ) -> start_dir(0 , [ $/ | Path ], T);
rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T);
rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T);
rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T).
%% hex_to_integer
hex_to_integer(Hex) ->
case catch erlang:list_to_integer(Hex, 16) of
{'EXIT', _} ->
old_hex_to_integer(Hex);
X ->
X
end.
old_hex_to_integer(Hex) ->
DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10;
(H) when H >= $A, H =< $F -> H - $A + 10;
(H) when H >= $0, H =< $9 -> H - $0
end,
lists:foldl(fun(E, Acc) -> Acc*16+DEHEX(E) end, 0, Hex).
code_to_phrase(100) -> "Continue";
code_to_phrase(101) -> "Switching Protocols ";
code_to_phrase(200) -> "OK";
code_to_phrase(201) -> "Created";
code_to_phrase(202) -> "Accepted";
code_to_phrase(203) -> "Non-Authoritative Information";
code_to_phrase(204) -> "No Content";
code_to_phrase(205) -> "Reset Content";
code_to_phrase(206) -> "Partial Content";
code_to_phrase(300) -> "Multiple Choices";
code_to_phrase(301) -> "Moved Permanently";
code_to_phrase(302) -> "Found";
code_to_phrase(303) -> "See Other";
code_to_phrase(304) -> "Not Modified";
code_to_phrase(305) -> "Use Proxy";
code_to_phrase(306) -> "(Unused)";
code_to_phrase(307) -> "Temporary Redirect";
code_to_phrase(400) -> "Bad Request";
code_to_phrase(401) -> "Unauthorized";
code_to_phrase(402) -> "Payment Required";
code_to_phrase(403) -> "Forbidden";
code_to_phrase(404) -> "Not Found";
code_to_phrase(405) -> "Method Not Allowed";
code_to_phrase(406) -> "Not Acceptable";
code_to_phrase(407) -> "Proxy Authentication Required";
code_to_phrase(408) -> "Request Timeout";
code_to_phrase(409) -> "Conflict";
code_to_phrase(410) -> "Gone";
code_to_phrase(411) -> "Length Required";
code_to_phrase(412) -> "Precondition Failed";
code_to_phrase(413) -> "Request Entity Too Large";
code_to_phrase(414) -> "Request-URI Too Long";
code_to_phrase(415) -> "Unsupported Media Type";
code_to_phrase(416) -> "Requested Range Not Satisfiable";
code_to_phrase(417) -> "Expectation Failed";
code_to_phrase(500) -> "Internal Server Error";
code_to_phrase(501) -> "Not Implemented";
code_to_phrase(502) -> "Bad Gateway";
code_to_phrase(503) -> "Service Unavailable";
code_to_phrase(504) -> "Gateway Timeout";
code_to_phrase(505) -> "HTTP Version Not Supported".
parse_auth(Orig = "Basic " ++ Auth64) ->
case decode_base64(Auth64) of
{error, _Err} ->
undefined;
Auth ->
case string:tokens(Auth, ":") of
[User, Pass] ->
{User, Pass};
_ ->
undefined
end
end;
parse_auth(_) ->
undefined.
decode_base64([]) ->
[];
decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
Bits2x6=
(d(Sextet1) bsl 18) bor
(d(Sextet2) bsl 12),
Octet1=Bits2x6 bsr 16,
[Octet1|decode_base64(Rest)];
decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
Bits3x6=
(d(Sextet1) bsl 18) bor
(d(Sextet2) bsl 12) bor
(d(Sextet3) bsl 6),
Octet1=Bits3x6 bsr 16,
Octet2=(Bits3x6 bsr 8) band 16#ff,
[Octet1,Octet2|decode_base64(Rest)];
decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
Bits4x6=
(d(Sextet1) bsl 18) bor
(d(Sextet2) bsl 12) bor
(d(Sextet3) bsl 6) bor
d(Sextet4),
Octet1=Bits4x6 bsr 16,
Octet2=(Bits4x6 bsr 8) band 16#ff,
Octet3=Bits4x6 band 16#ff,
[Octet1,Octet2,Octet3|decode_base64(Rest)];
decode_base64(_CatchAll) ->
{error, bad_base64}.
d(X) when X >= $A, X =<$Z ->
X-65;
d(X) when X >= $a, X =<$z ->
X-71;
d(X) when X >= $0, X =<$9 ->
X+4;
d($+) -> 62;
d($/) -> 63;
d(_) -> 63.

View File

@ -1,328 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_wcs.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Web Client Service
%%% Created : 13 Jul 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_wcs).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_fsm).
%% External exports
-export([start_link/2,
init/1,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3,
send/2,
recv/3,
close/1,
process_request/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
-record(http_poll, {id, pid}).
-record(state, {id,
key,
output = "",
input = "",
waiting_input = false,
timer}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(HTTP_POLL_TIMEOUT, 300000).
-define(CT, {"Content-Type", "text/xml; charset=utf-8"}).
-define(BAD_REQUEST, [?CT, {"Set-Cookie", "ID=-3:0; expires=-1"}]).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(ID, Key) ->
mnesia:create_table(http_poll,
[{ram_copies, [node()]},
{attributes, record_info(fields, http_poll)}]),
supervisor:start_child(ejabberd_http_poll_sup, [ID, Key]).
start_link(ID, Key) ->
gen_fsm:start_link(?MODULE, [ID, Key], ?FSMOPTS).
send({http_poll, FsmRef}, Packet) ->
gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
recv({http_poll, FsmRef}, _Length, Timeout) ->
gen_fsm:sync_send_all_state_event(FsmRef, recv, Timeout).
close({http_poll, FsmRef}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
process_request(#request{path = [],
data = Data} = Request) ->
case catch parse_request(Data) of
{ok, ID1, Key, NewKey, Packet} ->
ID = if
(ID1 == "0") or (ID1 == "mobile") ->
NewID = sha:sha(term_to_binary({now(), make_ref()})),
{ok, Pid} = start(NewID, ""),
mnesia:transaction(
fun() ->
mnesia:write(#http_poll{id = NewID,
pid = Pid})
end),
NewID;
true ->
ID1
end,
case http_put(ID, Key, NewKey, Packet) of
{error, not_exists} ->
{200, ?BAD_REQUEST, ""};
{error, bad_key} ->
{200, ?BAD_REQUEST, ""};
ok ->
receive
after 100 -> ok
end,
case http_get(ID) of
{error, not_exists} ->
{200, [?BAD_REQUEST], ""};
{ok, OutPacket} ->
if
ID == ID1 ->
{200, [?CT], OutPacket};
ID1 == "mobile" ->
{200, [?CT], [ID, $\n, OutPacket]};
true ->
Cookie = "ID=" ++ ID ++ "; expires=-1",
{200, [?CT, {"Set-Cookie", Cookie}],
OutPacket}
end
end
end;
_ ->
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
end.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, StateName, StateData} |
%% {ok, StateName, StateData, Timeout} |
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([ID, Key]) ->
?INFO_MSG("started: ~p", [{ID, Key}]),
Opts = [], % TODO
ejabberd_c2s:start({?MODULE, {http_poll, self()}}, Opts),
Timer = erlang:start_timer(?HTTP_POLL_TIMEOUT, self(), []),
{ok, loop, #state{id = ID,
key = Key,
timer = Timer}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: StateName/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
%state_name(Event, From, StateData) ->
% Reply = ok,
% {reply, Reply, state_name, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {reply, Reply, NextStateName, NextStateData} |
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event({send, Packet}, From, StateName, StateData) ->
Output = [StateData#state.output | Packet],
Reply = ok,
{reply, Reply, StateName, StateData#state{output = Output}};
handle_sync_event(recv, From, StateName, StateData) ->
case StateData#state.input of
"" ->
{next_state, StateName, StateData#state{waiting_input = From}};
Input ->
Reply = {ok, list_to_binary(Input)},
{reply, Reply, StateName, StateData#state{input = "",
waiting_input = false}}
end;
handle_sync_event(stop, From, StateName, StateData) ->
Reply = ok,
{stop, normal, Reply, StateData};
handle_sync_event({http_put, Key, NewKey, Packet},
From, StateName, StateData) ->
Allow = case StateData#state.key of
"" ->
true;
OldKey ->
NextKey = jlib:encode_base64(
binary_to_list(crypto:sha(Key))),
if
OldKey == NextKey ->
true;
true ->
false
end
end,
if
Allow ->
case StateData#state.waiting_input of
false ->
Input = [StateData#state.input | Packet],
Reply = ok,
{reply, Reply, StateName, StateData#state{input = Input,
key = NewKey}};
Receiver ->
gen_fsm:reply(Receiver, {ok, list_to_binary(Packet)}),
cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(?HTTP_POLL_TIMEOUT, self(), []),
Reply = ok,
{reply, Reply, StateName,
StateData#state{waiting_input = false,
key = NewKey,
timer = Timer}}
end;
true ->
Reply = {error, bad_key},
{reply, Reply, StateName, StateData}
end;
handle_sync_event(http_get, From, StateName, StateData) ->
Reply = {ok, StateData#state.output},
{reply, Reply, StateName, StateData#state{output = ""}};
handle_sync_event(Event, From, StateName, StateData) ->
Reply = ok,
{reply, Reply, StateName, StateData}.
code_change(OldVsn, StateName, StateData, Extra) ->
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({timeout, Timer, _}, StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate(Reason, StateName, StateData) ->
mnesia:transaction(
fun() ->
mnesia:delete({http_poll, StateData#state.id})
end),
case StateData#state.waiting_input of
false ->
ok;
Receiver ->
gen_fsm:reply(Receiver, {error, closed})
end,
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
http_put(ID, Key, NewKey, Packet) ->
case mnesia:dirty_read({http_poll, ID}) of
[] ->
{error, not_exists};
[#http_poll{pid = FsmRef}] ->
gen_fsm:sync_send_all_state_event(
FsmRef, {http_put, Key, NewKey, Packet})
end.
http_get(ID) ->
case mnesia:dirty_read({http_poll, ID}) of
[] ->
{error, not_exists};
[#http_poll{pid = FsmRef}] ->
gen_fsm:sync_send_all_state_event(FsmRef, http_get)
end.
parse_request(Data) ->
Comma = string:chr(Data, $,),
Header = lists:sublist(Data, Comma - 1),
Packet = lists:nthtail(Comma, Data),
{ID, Key, NewKey} =
case string:tokens(Header, ";") of
[ID1] ->
{ID1, "", ""};
[ID1, Key1] ->
{ID1, Key1, Key1};
[ID1, Key1, NewKey1] ->
{ID1, Key1, NewKey1}
end,
{ok, ID, Key, NewKey, Packet}.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
receive
{timeout, Timer, _} ->
ok
after 0 ->
ok
end.