mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-26 16:26:24 +01:00
* src/web/*: Plugin architecture for HTTP modules (thanks to
Massimiliano Mirra) SVN Revision: 713
This commit is contained in:
parent
456185d0d3
commit
00807235c4
@ -1,3 +1,8 @@
|
|||||||
|
2007-01-25 Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
|
||||||
|
* src/web/*: Plugin architecture for HTTP modules (thanks to
|
||||||
|
Massimiliano Mirra)
|
||||||
|
|
||||||
2007-01-24 Mickael Remond <mickael.remond@process-one.net>
|
2007-01-24 Mickael Remond <mickael.remond@process-one.net>
|
||||||
|
|
||||||
* doc/guide.tex: Documentation for the
|
* doc/guide.tex: Documentation for the
|
||||||
|
@ -31,8 +31,15 @@
|
|||||||
request_keepalive,
|
request_keepalive,
|
||||||
request_content_length,
|
request_content_length,
|
||||||
request_lang = "en",
|
request_lang = "en",
|
||||||
use_http_poll = false,
|
%% XXX bard: request handlers are configured in
|
||||||
use_web_admin = false,
|
%% ejabberd.cfg under the HTTP service. For example,
|
||||||
|
%% to have the module test_web handle requests with
|
||||||
|
%% paths starting with "/test/module":
|
||||||
|
%%
|
||||||
|
%% {5280, ejabberd_http, [http_poll, web_admin,
|
||||||
|
%% {request_handlers, [{["test", "module"], mod_test_web}]}]}
|
||||||
|
%%
|
||||||
|
request_handlers = [],
|
||||||
end_of_request = false,
|
end_of_request = false,
|
||||||
trail = ""
|
trail = ""
|
||||||
}).
|
}).
|
||||||
@ -71,16 +78,32 @@ start_link({SockMod, Socket}, Opts) ->
|
|||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
UseHTTPPoll = lists:member(http_poll, Opts),
|
|
||||||
UseWebAdmin = lists:member(web_admin, Opts),
|
%% XXX bard: for backward compatibility: expand web_admin and
|
||||||
?DEBUG("S: ~p~n", [{UseHTTPPoll, UseWebAdmin}]),
|
%% http_poll in Opts respectively to {["admin"],
|
||||||
|
%% ejabberd_web_admin} and {["http-poll"], ejabberd_http_poll}
|
||||||
|
|
||||||
|
RequestHandlers =
|
||||||
|
case lists:keysearch(request_handlers, 1, Opts) of
|
||||||
|
{value, {request_handlers, H}} -> H;
|
||||||
|
false -> []
|
||||||
|
end ++
|
||||||
|
case lists:member(web_admin, Opts) of
|
||||||
|
true -> [{["admin"], ejabberd_web_admin}];
|
||||||
|
false -> []
|
||||||
|
end ++
|
||||||
|
case lists:member(http_poll, Opts) of
|
||||||
|
true -> [{["http-poll"], ejabberd_http_poll}];
|
||||||
|
false -> []
|
||||||
|
end,
|
||||||
|
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||||
|
|
||||||
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
|
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
|
||||||
{ok, proc_lib:spawn_link(ejabberd_http,
|
{ok, proc_lib:spawn_link(ejabberd_http,
|
||||||
receive_headers,
|
receive_headers,
|
||||||
[#state{sockmod = SockMod1,
|
[#state{sockmod = SockMod1,
|
||||||
socket = Socket1,
|
socket = Socket1,
|
||||||
use_http_poll = UseHTTPPoll,
|
request_handlers = RequestHandlers}])}.
|
||||||
use_web_admin = UseWebAdmin}])}.
|
|
||||||
|
|
||||||
|
|
||||||
become_controller(_Pid) ->
|
become_controller(_Pid) ->
|
||||||
@ -192,23 +215,44 @@ process_header(State, Data) ->
|
|||||||
end,
|
end,
|
||||||
#state{sockmod = SockMod,
|
#state{sockmod = SockMod,
|
||||||
socket = Socket,
|
socket = Socket,
|
||||||
use_http_poll = State#state.use_http_poll,
|
request_handlers = State#state.request_handlers};
|
||||||
use_web_admin = State#state.use_web_admin};
|
|
||||||
_ ->
|
_ ->
|
||||||
#state{end_of_request = true}
|
#state{end_of_request = true,
|
||||||
|
request_handlers = State#state.request_handlers}
|
||||||
end;
|
end;
|
||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
#state{end_of_request = true};
|
#state{end_of_request = true,
|
||||||
|
request_handlers = State#state.request_handlers};
|
||||||
_ ->
|
_ ->
|
||||||
#state{end_of_request = true}
|
#state{end_of_request = true,
|
||||||
|
request_handlers = State#state.request_handlers}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% XXX bard: search through request handlers looking for one that
|
||||||
|
%% matches the requested URL path, and pass control to it. If none is
|
||||||
|
%% found, answer with HTTP 404.
|
||||||
|
process([], _) ->
|
||||||
|
ejabberd_web:error(not_found);
|
||||||
|
process(Handlers, Request) ->
|
||||||
|
[{PathPattern, HandlerModule} | HandlersLeft] = Handlers,
|
||||||
|
|
||||||
|
case lists:prefix(PathPattern, Request#request.path) of
|
||||||
|
true ->
|
||||||
|
%% LocalPath is the path "local to the handler", i.e. if
|
||||||
|
%% the handler was registered to handle "/test/" and the
|
||||||
|
%% requested path is "/test/foo/bar", the local path is
|
||||||
|
%% ["foo", "bar"]
|
||||||
|
LocalPath = lists:nthtail(length(PathPattern), Request#request.path),
|
||||||
|
HandlerModule:process(LocalPath, Request);
|
||||||
|
false ->
|
||||||
|
process(HandlersLeft, Request)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
process_request(#state{request_method = 'GET',
|
process_request(#state{request_method = 'GET',
|
||||||
request_path = {abs_path, Path},
|
request_path = {abs_path, Path},
|
||||||
request_auth = Auth,
|
request_auth = Auth,
|
||||||
request_lang = Lang,
|
request_lang = Lang,
|
||||||
use_http_poll = UseHTTPPoll,
|
request_handlers = RequestHandlers} = State) ->
|
||||||
use_web_admin = UseWebAdmin} = State) ->
|
|
||||||
case (catch url_decode_q_split(Path)) of
|
case (catch url_decode_q_split(Path)) of
|
||||||
{'EXIT', _} ->
|
{'EXIT', _} ->
|
||||||
process_request(false);
|
process_request(false);
|
||||||
@ -225,8 +269,11 @@ process_request(#state{request_method = 'GET',
|
|||||||
q = LQuery,
|
q = LQuery,
|
||||||
auth = Auth,
|
auth = Auth,
|
||||||
lang = Lang},
|
lang = Lang},
|
||||||
case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
|
%% XXX bard: This previously passed control to
|
||||||
Request) of
|
%% ejabberd_web:process_get, now passes it to a local
|
||||||
|
%% procedure (process) that handles dispatching based on
|
||||||
|
%% URL path prefix.
|
||||||
|
case process(RequestHandlers, Request) of
|
||||||
El when element(1, El) == xmlelement ->
|
El when element(1, El) == xmlelement ->
|
||||||
make_xhtml_output(State, 200, [], El);
|
make_xhtml_output(State, 200, [], El);
|
||||||
{Status, Headers, El} when
|
{Status, Headers, El} when
|
||||||
@ -247,8 +294,7 @@ process_request(#state{request_method = 'POST',
|
|||||||
request_lang = Lang,
|
request_lang = Lang,
|
||||||
sockmod = SockMod,
|
sockmod = SockMod,
|
||||||
socket = Socket,
|
socket = Socket,
|
||||||
use_http_poll = UseHTTPPoll,
|
request_handlers = RequestHandlers} = State)
|
||||||
use_web_admin = UseWebAdmin} = State)
|
|
||||||
when is_integer(Len) ->
|
when is_integer(Len) ->
|
||||||
case SockMod of
|
case SockMod of
|
||||||
gen_tcp ->
|
gen_tcp ->
|
||||||
@ -275,8 +321,7 @@ process_request(#state{request_method = 'POST',
|
|||||||
auth = Auth,
|
auth = Auth,
|
||||||
data = Data,
|
data = Data,
|
||||||
lang = Lang},
|
lang = Lang},
|
||||||
case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
|
case process(RequestHandlers, Request) of
|
||||||
Request) of
|
|
||||||
El when element(1, El) == xmlelement ->
|
El when element(1, El) == xmlelement ->
|
||||||
make_xhtml_output(State, 200, [], El);
|
make_xhtml_output(State, 200, [], El);
|
||||||
{Status, Headers, El} when
|
{Status, Headers, El} when
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
setopts/2,
|
setopts/2,
|
||||||
controlling_process/2,
|
controlling_process/2,
|
||||||
close/1,
|
close/1,
|
||||||
process_request/1]).
|
process/2]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
@ -83,8 +83,7 @@ close({http_poll, FsmRef}) ->
|
|||||||
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
|
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
|
||||||
|
|
||||||
|
|
||||||
process_request(#request{path = [],
|
process([], #request{data = Data} = Request) ->
|
||||||
data = Data} = Request) ->
|
|
||||||
case catch parse_request(Data) of
|
case catch parse_request(Data) of
|
||||||
{ok, ID1, Key, NewKey, Packet} ->
|
{ok, ID1, Key, NewKey, Packet} ->
|
||||||
ID = if
|
ID = if
|
||||||
@ -130,7 +129,7 @@ process_request(#request{path = [],
|
|||||||
_ ->
|
_ ->
|
||||||
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
|
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
|
||||||
end;
|
end;
|
||||||
process_request(_Request) ->
|
process(_, _Request) ->
|
||||||
{400, [], {xmlelement, "h1", [],
|
{400, [], {xmlelement, "h1", [],
|
||||||
[{xmlcdata, "400 Bad Request"}]}}.
|
[{xmlcdata, "400 Bad Request"}]}}.
|
||||||
|
|
||||||
|
@ -12,13 +12,18 @@
|
|||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
-export([make_xhtml/1,
|
-export([make_xhtml/1,
|
||||||
process_get/2]).
|
error/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
-include("ejabberd_http.hrl").
|
-include("ejabberd_http.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
%% XXX bard: there are variants of make_xhtml in ejabberd_http and
|
||||||
|
%% ejabberd_web_admin. It might be a good idea to centralize it here
|
||||||
|
%% and also create an ejabberd_web.hrl file holding the macros, so
|
||||||
|
%% that third parties can use ejabberd_web as an "utility" library.
|
||||||
|
|
||||||
make_xhtml(Els) ->
|
make_xhtml(Els) ->
|
||||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||||
{"xml:lang", "en"},
|
{"xml:lang", "en"},
|
||||||
@ -48,98 +53,7 @@ make_xhtml(Els) ->
|
|||||||
{"name", Name},
|
{"name", Name},
|
||||||
{"value", Value}])).
|
{"value", Value}])).
|
||||||
|
|
||||||
|
error(not_found) ->
|
||||||
process_get({_, true},
|
{404, [], make_xhtml([?XC("h1", "404 Not Found")])};
|
||||||
#request{auth = Auth,
|
error(not_allowed) ->
|
||||||
path = ["admin", "server", SHost | RPath],
|
{401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.
|
||||||
q = Query,
|
|
||||||
lang = Lang} = Request) ->
|
|
||||||
Host = jlib:nameprep(SHost),
|
|
||||||
case lists:member(Host, ?MYHOSTS) of
|
|
||||||
true ->
|
|
||||||
US = case Auth of
|
|
||||||
{SJID, P} ->
|
|
||||||
case jlib:string_to_jid(SJID) of
|
|
||||||
error ->
|
|
||||||
unauthorized;
|
|
||||||
#jid{user = U, server = S} ->
|
|
||||||
case ejabberd_auth:check_password(U, S, P) of
|
|
||||||
true ->
|
|
||||||
{U, S};
|
|
||||||
false ->
|
|
||||||
unauthorized
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
unauthorized
|
|
||||||
end,
|
|
||||||
case US of
|
|
||||||
{User, Server} ->
|
|
||||||
case acl:match_rule(
|
|
||||||
Host, configure, jlib:make_jid(User, Server, "")) of
|
|
||||||
deny ->
|
|
||||||
{401, [], make_xhtml([?XC("h1", "Not Allowed")])};
|
|
||||||
allow ->
|
|
||||||
ejabberd_web_admin:process_admin(
|
|
||||||
Host, Request#request{path = RPath,
|
|
||||||
us = US})
|
|
||||||
end;
|
|
||||||
unauthorized ->
|
|
||||||
{401,
|
|
||||||
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
|
|
||||||
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
|
|
||||||
[{xmlcdata, "401 Unauthorized"}]}])}
|
|
||||||
end;
|
|
||||||
false ->
|
|
||||||
{404, [], make_xhtml([?XC("h1", "Not found")])}
|
|
||||||
end;
|
|
||||||
|
|
||||||
process_get({_, true},
|
|
||||||
#request{auth = Auth,
|
|
||||||
path = ["admin" | RPath],
|
|
||||||
q = Query,
|
|
||||||
lang = Lang} = Request) ->
|
|
||||||
US = case Auth of
|
|
||||||
{SJID, P} ->
|
|
||||||
case jlib:string_to_jid(SJID) of
|
|
||||||
error ->
|
|
||||||
unauthorized;
|
|
||||||
#jid{user = U, server = S} ->
|
|
||||||
case ejabberd_auth:check_password(U, S, P) of
|
|
||||||
true ->
|
|
||||||
{U, S};
|
|
||||||
false ->
|
|
||||||
unauthorized
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
unauthorized
|
|
||||||
end,
|
|
||||||
case US of
|
|
||||||
{User, Server} ->
|
|
||||||
case acl:match_rule(
|
|
||||||
global, configure, jlib:make_jid(User, Server, "")) of
|
|
||||||
deny ->
|
|
||||||
{401, [], make_xhtml([?XC("h1", "Not Allowed")])};
|
|
||||||
allow ->
|
|
||||||
ejabberd_web_admin:process_admin(
|
|
||||||
global, Request#request{path = RPath,
|
|
||||||
us = US})
|
|
||||||
end;
|
|
||||||
unauthorized ->
|
|
||||||
{401,
|
|
||||||
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
|
|
||||||
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
|
|
||||||
[{xmlcdata, "401 Unauthorized"}]}])}
|
|
||||||
end;
|
|
||||||
|
|
||||||
process_get({true, _},
|
|
||||||
#request{path = ["http-poll" | RPath],
|
|
||||||
q = _Query,
|
|
||||||
lang = _Lang} = Request) ->
|
|
||||||
ejabberd_http_poll:process_request(Request#request{path = RPath});
|
|
||||||
|
|
||||||
process_get(_, _Request) ->
|
|
||||||
{404, [], make_xhtml([?XC("h1", "Not found")])}.
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,9 @@
|
|||||||
-vsn('$Revision$ ').
|
-vsn('$Revision$ ').
|
||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
-export([process_admin/2,
|
-export([process/2,
|
||||||
|
%% XXX bard: unexported, since it is only called from process/2 now
|
||||||
|
%% process_admin/2,
|
||||||
list_users/4,
|
list_users/4,
|
||||||
list_users_in_diapason/4]).
|
list_users_in_diapason/4]).
|
||||||
|
|
||||||
@ -54,6 +56,75 @@
|
|||||||
{"size", Size}])).
|
{"size", Size}])).
|
||||||
-define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
|
-define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
|
||||||
|
|
||||||
|
|
||||||
|
process(["server", SHost | RPath], #request{auth = Auth,
|
||||||
|
q = Query,
|
||||||
|
lang = Lang} = Request) ->
|
||||||
|
Host = jlib:nameprep(SHost),
|
||||||
|
case lists:member(Host, ?MYHOSTS) of
|
||||||
|
true ->
|
||||||
|
case get_auth(Auth) of
|
||||||
|
{User, Server} ->
|
||||||
|
case acl:match_rule(
|
||||||
|
Host, configure, jlib:make_jid(User, Server, "")) of
|
||||||
|
deny ->
|
||||||
|
ejabberd_web:error(not_allowed);
|
||||||
|
allow ->
|
||||||
|
process_admin(
|
||||||
|
Host, Request#request{path = RPath,
|
||||||
|
us = {User, Server}})
|
||||||
|
end;
|
||||||
|
unauthorized ->
|
||||||
|
{401,
|
||||||
|
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
|
||||||
|
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
|
||||||
|
[{xmlcdata, "401 Unauthorized"}]}])}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ejabberd_web:error(not_found)
|
||||||
|
end;
|
||||||
|
|
||||||
|
process(RPath, #request{auth = Auth,
|
||||||
|
q = Query,
|
||||||
|
lang = Lang} = Request) ->
|
||||||
|
case get_auth(Auth) of
|
||||||
|
{User, Server} ->
|
||||||
|
case acl:match_rule(
|
||||||
|
global, configure, jlib:make_jid(User, Server, "")) of
|
||||||
|
deny ->
|
||||||
|
ejabberd_web:error(not_allowed);
|
||||||
|
allow ->
|
||||||
|
process_admin(
|
||||||
|
global, Request#request{path = RPath,
|
||||||
|
us = {User, Server}})
|
||||||
|
end;
|
||||||
|
unauthorized ->
|
||||||
|
%% XXX bard: any reason to send this data now and not
|
||||||
|
%% always in case of an 401? ought to check http specs...
|
||||||
|
{401,
|
||||||
|
[{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
|
||||||
|
ejabberd_web:make_xhtml([{xmlelement, "h1", [],
|
||||||
|
[{xmlcdata, "401 Unauthorized"}]}])}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_auth(Auth) ->
|
||||||
|
case Auth of
|
||||||
|
{SJID, P} ->
|
||||||
|
case jlib:string_to_jid(SJID) of
|
||||||
|
error ->
|
||||||
|
unauthorized;
|
||||||
|
#jid{user = U, server = S} ->
|
||||||
|
case ejabberd_auth:check_password(U, S, P) of
|
||||||
|
true ->
|
||||||
|
{U, S};
|
||||||
|
false ->
|
||||||
|
unauthorized
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
unauthorized
|
||||||
|
end.
|
||||||
|
|
||||||
make_xhtml(Els, global, Lang) ->
|
make_xhtml(Els, global, Lang) ->
|
||||||
{200, [html],
|
{200, [html],
|
||||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
|
||||||
|
Loading…
Reference in New Issue
Block a user