aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Shchepin <alexey@process-one.net>2007-01-25 05:53:58 +0000
committerAlexey Shchepin <alexey@process-one.net>2007-01-25 05:53:58 +0000
commit00807235c4b42f760b4d26f9720396638c8bb60b (patch)
treef1dfd96e206b7cc156b9a19321cbfae8b5aef0f4
parent* Generated updated documentation. (diff)
* src/web/*: Plugin architecture for HTTP modules (thanks to
Massimiliano Mirra) SVN Revision: 713
-rw-r--r--ChangeLog5
-rw-r--r--src/web/ejabberd_http.erl85
-rw-r--r--src/web/ejabberd_http_poll.erl7
-rw-r--r--src/web/ejabberd_web.erl106
-rw-r--r--src/web/ejabberd_web_admin.erl73
5 files changed, 155 insertions, 121 deletions
diff --git a/ChangeLog b/ChangeLog
index 299093426..19ae31380 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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>
* doc/guide.tex: Documentation for the
diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl
index 3220925ec..24913de58 100644
--- a/src/web/ejabberd_http.erl
+++ b/src/web/ejabberd_http.erl
@@ -31,8 +31,15 @@
request_keepalive,
request_content_length,
request_lang = "en",
- use_http_poll = false,
- use_web_admin = false,
+ %% XXX bard: request handlers are configured in
+ %% 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,
trail = ""
}).
@@ -71,16 +78,32 @@ start_link({SockMod, Socket}, Opts) ->
_ ->
ok
end,
- UseHTTPPoll = lists:member(http_poll, Opts),
- UseWebAdmin = lists:member(web_admin, Opts),
- ?DEBUG("S: ~p~n", [{UseHTTPPoll, UseWebAdmin}]),
+
+ %% XXX bard: for backward compatibility: expand web_admin and
+ %% 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}]),
{ok, proc_lib:spawn_link(ejabberd_http,
receive_headers,
[#state{sockmod = SockMod1,
socket = Socket1,
- use_http_poll = UseHTTPPoll,
- use_web_admin = UseWebAdmin}])}.
+ request_handlers = RequestHandlers}])}.
become_controller(_Pid) ->
@@ -192,23 +215,44 @@ process_header(State, Data) ->
end,
#state{sockmod = SockMod,
socket = Socket,
- use_http_poll = State#state.use_http_poll,
- use_web_admin = State#state.use_web_admin};
+ request_handlers = State#state.request_handlers};
_ ->
- #state{end_of_request = true}
+ #state{end_of_request = true,
+ request_handlers = State#state.request_handlers}
end;
{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.
process_request(#state{request_method = 'GET',
request_path = {abs_path, Path},
request_auth = Auth,
request_lang = Lang,
- use_http_poll = UseHTTPPoll,
- use_web_admin = UseWebAdmin} = State) ->
+ request_handlers = RequestHandlers} = State) ->
case (catch url_decode_q_split(Path)) of
{'EXIT', _} ->
process_request(false);
@@ -225,8 +269,11 @@ process_request(#state{request_method = 'GET',
q = LQuery,
auth = Auth,
lang = Lang},
- case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
- Request) of
+ %% XXX bard: This previously passed control to
+ %% 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 ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
@@ -247,8 +294,7 @@ process_request(#state{request_method = 'POST',
request_lang = Lang,
sockmod = SockMod,
socket = Socket,
- use_http_poll = UseHTTPPoll,
- use_web_admin = UseWebAdmin} = State)
+ request_handlers = RequestHandlers} = State)
when is_integer(Len) ->
case SockMod of
gen_tcp ->
@@ -275,8 +321,7 @@ process_request(#state{request_method = 'POST',
auth = Auth,
data = Data,
lang = Lang},
- case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
- Request) of
+ case process(RequestHandlers, Request) of
El when element(1, El) == xmlelement ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl
index d12277449..01268b817 100644
--- a/src/web/ejabberd_http_poll.erl
+++ b/src/web/ejabberd_http_poll.erl
@@ -24,7 +24,7 @@
setopts/2,
controlling_process/2,
close/1,
- process_request/1]).
+ process/2]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@@ -83,8 +83,7 @@ close({http_poll, FsmRef}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
-process_request(#request{path = [],
- data = Data} = Request) ->
+process([], #request{data = Data} = Request) ->
case catch parse_request(Data) of
{ok, ID1, Key, NewKey, Packet} ->
ID = if
@@ -130,7 +129,7 @@ process_request(#request{path = [],
_ ->
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
end;
-process_request(_Request) ->
+process(_, _Request) ->
{400, [], {xmlelement, "h1", [],
[{xmlcdata, "400 Bad Request"}]}}.
diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl
index 143fbf407..a1d6b6592 100644
--- a/src/web/ejabberd_web.erl
+++ b/src/web/ejabberd_web.erl
@@ -12,13 +12,18 @@
%% External exports
-export([make_xhtml/1,
- process_get/2]).
+ error/1]).
-include("ejabberd.hrl").
-include("jlib.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) ->
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", "en"},
@@ -48,98 +53,7 @@ make_xhtml(Els) ->
{"name", Name},
{"value", Value}])).
-
-process_get({_, true},
- #request{auth = Auth,
- path = ["admin", "server", SHost | RPath],
- 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")])}.
-
-
+error(not_found) ->
+ {404, [], make_xhtml([?XC("h1", "404 Not Found")])};
+error(not_allowed) ->
+ {401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.
diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl
index 1c03e50e1..61183f736 100644
--- a/src/web/ejabberd_web_admin.erl
+++ b/src/web/ejabberd_web_admin.erl
@@ -14,7 +14,9 @@
-vsn('$Revision$ ').
%% 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_in_diapason/4]).
@@ -54,6 +56,75 @@
{"size", 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) ->
{200, [html],
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},