* src/web/ejabberd_http_poll.erl: Properly handle bad requests

* src/web/ejabberd_web_admin.erl: Ported features from J-EAI

SVN Revision: 272
This commit is contained in:
Alexey Shchepin 2004-10-05 19:31:17 +00:00
parent 34de660c44
commit b5e8850de9
3 changed files with 438 additions and 49 deletions

View File

@ -1,3 +1,9 @@
2004-10-05 Alexey Shchepin <alexey@sevcom.net>
* src/web/ejabberd_http_poll.erl: Properly handle bad requests
* src/web/ejabberd_web_admin.erl: Ported features from J-EAI
2004-09-30 Alexey Shchepin <alexey@sevcom.net> 2004-09-30 Alexey Shchepin <alexey@sevcom.net>
* src/web/ejabberd_http.erl: Fixed processing of POST body for * src/web/ejabberd_http.erl: Fixed processing of POST body for

View File

@ -119,8 +119,10 @@ process_request(#request{path = [],
end; end;
_ -> _ ->
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""} {200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
end. end;
process_request(_Request) ->
{400, [], {xmlelement, "h1", [],
[{xmlcdata, "400 Bad Request"}]}}.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm %%% Callback functions from gen_fsm

View File

@ -5,6 +5,9 @@
%%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@sevcom.net> %%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$ %%% Id : $Id$
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Copyright (c) 2004 Alexey Shchepin
%%% Copyright (c) 2004 Process One
%%%----------------------------------------------------------------------
-module(ejabberd_web_admin). -module(ejabberd_web_admin).
-author('alexey@sevcom.net'). -author('alexey@sevcom.net').
@ -101,6 +104,7 @@ make_xhtml(Els, Lang) ->
[?LI([?ACT("/admin/acls/", "Access Control Lists")]), [?LI([?ACT("/admin/acls/", "Access Control Lists")]),
?LI([?ACT("/admin/access/", "Access Rules")]), ?LI([?ACT("/admin/access/", "Access Rules")]),
?LI([?ACT("/admin/users/", "Users")]), ?LI([?ACT("/admin/users/", "Users")]),
?LI([?ACT("/admin/online-users/", "Online Users")]),
?LI([?ACT("/admin/nodes/", "Nodes")]), ?LI([?ACT("/admin/nodes/", "Nodes")]),
?LI([?ACT("/admin/stats/", "Statistics")]) ?LI([?ACT("/admin/stats/", "Statistics")])
])]), ])]),
@ -115,7 +119,7 @@ make_xhtml(Els, Lang) ->
[?XE("tbody", [?XE("tbody",
[?XE("tr", [?XE("tr",
[?XCT("td", [?XCT("td",
"ejabberd (c) 2002-2004 Alexey Shchepin") "ejabberd (c) 2002-2004 Alexey Shchepin, 2004 Process One")
])]) ])])
])])])])])]) ])])])])])])
]}}. ]}}.
@ -494,6 +498,7 @@ process_admin(#request{user = User,
?LI([?ACT("access/", "Access Rules"), ?C(" "), ?LI([?ACT("access/", "Access Rules"), ?C(" "),
?ACT("access-raw/", "(raw)")]), ?ACT("access-raw/", "(raw)")]),
?LI([?ACT("users/", "Users")]), ?LI([?ACT("users/", "Users")]),
?LI([?ACT("online-users/", "Online Users")]),
?LI([?ACT("nodes/", "Nodes")]), ?LI([?ACT("nodes/", "Nodes")]),
?LI([?ACT("stats/", "Statistics")]) ?LI([?ACT("stats/", "Statistics")])
]) ])
@ -753,14 +758,21 @@ process_admin(#request{user = User,
path = ["users"], path = ["users"],
q = Query, q = Query,
lang = Lang} = Request) -> lang = Lang} = Request) ->
Res = list_users(), Res = list_users(Query, Lang),
make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang); make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang);
process_admin(#request{user = User, process_admin(#request{user = User,
path = ["users", Diap], path = ["users", Diap],
q = Query, q = Query,
lang = Lang} = Request) -> lang = Lang} = Request) ->
Res = list_users_in_diapason(Diap), Res = list_users_in_diapason(Diap, Lang),
make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang);
process_admin(#request{user = User,
path = ["online-users"],
q = Query,
lang = Lang} = Request) ->
Res = list_online_users(Lang),
make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang); make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang);
process_admin(#request{user = User, process_admin(#request{user = User,
@ -777,11 +789,18 @@ process_admin(#request{user = User,
Res = user_info(U, Query, Lang), Res = user_info(U, Query, Lang),
make_xhtml(Res, Lang); make_xhtml(Res, Lang);
process_admin(#request{user = User,
path = ["user", U, "queue"],
q = Query,
lang = Lang} = Request) ->
Res = user_queue(U, Query, Lang),
make_xhtml(Res, Lang);
process_admin(#request{user = User, process_admin(#request{user = User,
path = ["user", U, "roster"], path = ["user", U, "roster"],
q = Query, q = Query,
lang = Lang} = Request) -> lang = Lang} = Request) ->
Res = user_roster(U, Query, Lang), Res = user_roster(U, Query, Lang, true),
make_xhtml(Res, Lang); make_xhtml(Res, Lang);
process_admin(#request{user = User, process_admin(#request{user = User,
@ -1036,44 +1055,123 @@ parse_access_rule(Text) ->
list_users() -> list_users(Query, Lang) ->
Res = list_users_parse_query(Query),
Users = ejabberd_auth:dirty_get_registered_users(), Users = ejabberd_auth:dirty_get_registered_users(),
SUsers = lists:sort(Users), SUsers = lists:sort(Users),
case length(SUsers) of FUsers =
N when N =< 100 -> case length(SUsers) of
lists:flatmap( N when N =< 100 ->
fun(U) -> [list_given_users(SUsers, Lang)];
[?AC("../user/" ++ U ++ "/", U), ?BR] N ->
end, SUsers); NParts = trunc(math:sqrt(N * 0.618)) + 1,
N -> M = trunc(N / NParts) + 1,
NParts = trunc(math:sqrt(N * 0.618)) + 1, lists:flatmap(
M = trunc(N / NParts) + 1, fun(K) ->
lists:flatmap( L = K + M - 1,
fun(K) -> Node = integer_to_list(K) ++ "-" ++ integer_to_list(L),
L = K + M - 1, Last = if L < N -> lists:nth(L, SUsers);
Node = integer_to_list(K) ++ "-" ++ integer_to_list(L), true -> lists:last(SUsers)
Last = if L < N -> lists:nth(L, SUsers); end,
true -> lists:last(SUsers) Name =
end, lists:nth(K, SUsers) ++ [$\s, 226, 128, 148, $\s] ++
Name = Last,
lists:nth(K, SUsers) ++ [$\s, 226, 128, 148, $\s] ++ [?AC(Node ++ "/", Name), ?BR]
Last, end, lists:seq(1, N, M))
[?AC(Node ++ "/", Name), ?BR] end,
end, lists:seq(1, N, M)) case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
nothing -> []
end ++
[?XAE("form", [{"method", "post"}],
[?XE("table",
[?XE("tr",
[?XC("td", ?T("User") ++ ":"),
?XE("td", [?INPUT("text", "newusername", "")])
]),
?XE("tr",
[?XC("td", ?T("Password") ++ ":"),
?XE("td", [?INPUT("password", "newuserpassword", "")])
]),
?XE("tr",
[?X("td"),
?XAE("td", [{"class", "alignright"}],
[?INPUTT("submit", "addnewuser", "Add User")])
])]),
?P] ++
FUsers)].
list_users_parse_query(Query) ->
case lists:keysearch("addnewuser", 1, Query) of
{value, _} ->
{value, {_, User}} =
lists:keysearch("newusername", 1, Query),
{value, {_, Password}} =
lists:keysearch("newuserpassword", 1, Query),
case jlib:nodeprep(User) of
error ->
error;
"" ->
error;
_ ->
ejabberd_auth:try_register(User, Password),
ok
end;
false ->
nothing
end. end.
list_users_in_diapason(Diap) -> list_users_in_diapason(Diap, Lang) ->
Users = ejabberd_auth:dirty_get_registered_users(), Users = ejabberd_auth:dirty_get_registered_users(),
SUsers = lists:sort(Users), SUsers = lists:sort(Users),
{ok, [S1, S2]} = regexp:split(Diap, "-"), {ok, [S1, S2]} = regexp:split(Diap, "-"),
N1 = list_to_integer(S1), N1 = list_to_integer(S1),
N2 = list_to_integer(S2), N2 = list_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
lists:flatmap( [list_given_users(Sub, Lang)].
fun(U) ->
[?AC("../../user/" ++ U ++ "/", U), ?BR]
end, Sub).
list_given_users(Users, Lang) ->
?XE("table",
[?XE("thead",
[?XE("tr",
[?XCT("td", "User"),
?XCT("td", "Offline messages"),
?XCT("td", "Last Activity")])]),
?XE("tbody",
lists:map(
fun(User) ->
QueueLen = length(mnesia:dirty_read({offline_msg, User})),
FQueueLen = [?AC("../../user/" ++ User ++ "/queue/",
integer_to_list(QueueLen))],
FLast =
case ejabberd_sm:get_user_resources(User) of
[] ->
case mnesia:dirty_read({last_activity, User}) of
[] ->
?T("Never");
[E] ->
Shift = element(3, E),
TimeStamp = {Shift div 1000000,
Shift rem 1000000,
0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
lists:flatten(
io_lib:format(
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour, Minute, Second]))
end;
_ ->
?T("Online")
end,
?XE("tr",
[?XE("td", [?AC("../../user/" ++ User ++ "/",
User)]),
?XE("td", FQueueLen),
?XC("td", FLast)])
end, Users)
)]).
get_stats(Lang) -> get_stats(Lang) ->
@ -1100,6 +1198,15 @@ get_stats(Lang) ->
])]. ])].
list_online_users(_Lang) ->
Users = lists:map(fun({U, R}) -> U end,
ejabberd_sm:dirty_get_sessions_list()),
SUsers = lists:usort(Users),
lists:flatmap(
fun(U) ->
[?AC("../user/" ++ U ++ "/", U), ?BR]
end, SUsers).
user_info(User, Query, Lang) -> user_info(User, Query, Lang) ->
Res = user_parse_query(User, Query), Res = user_parse_query(User, Query),
Resources = ejabberd_sm:get_user_resources(User), Resources = ejabberd_sm:get_user_resources(User),
@ -1116,6 +1223,9 @@ user_info(User, Query, Lang) ->
Password = ejabberd_auth:get_password_s(User), Password = ejabberd_auth:get_password_s(User),
FPassword = [?INPUT("text", "password", Password), ?C(" "), FPassword = [?INPUT("text", "password", Password), ?C(" "),
?INPUTT("submit", "chpassword", "Change Password")], ?INPUTT("submit", "chpassword", "Change Password")],
QueueLen = length(mnesia:dirty_read({offline_msg, User})),
FQueueLen = [?AC("queue/",
integer_to_list(QueueLen))],
[?XC("h1", "User: " ++ User)] ++ [?XC("h1", "User: " ++ User)] ++
case Res of case Res of
ok -> [?CT("submitted"), ?P]; ok -> [?CT("submitted"), ?P];
@ -1124,13 +1234,18 @@ user_info(User, Query, Lang) ->
end ++ end ++
[?XAE("form", [{"method", "post"}], [?XAE("form", [{"method", "post"}],
[?XCT("h3", "Connected Resources:")] ++ FResources ++ [?XCT("h3", "Connected Resources:")] ++ FResources ++
[?XCT("h3", "Password:")] ++ FPassword)]. [?XCT("h3", "Password:")] ++ FPassword ++
[?XCT("h3", "Offline messages:")] ++ FQueueLen ++
[?XE("h3", [?ACT("roster/", "Roster")])] ++
[?BR, ?INPUTT("submit", "removeuser", "Remove User")])].
user_parse_query(User, Query) -> user_parse_query(User, Query) ->
case lists:keysearch("chpassword", 1, Query) of case lists:keysearch("chpassword", 1, Query) of
{value, _} -> {value, _} ->
case lists:keysearch("password", 1, Query) of case lists:keysearch("password", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, Password}} -> {value, {_, Password}} ->
ejabberd_auth:set_password(User, Password), ejabberd_auth:set_password(User, Password),
ok; ok;
@ -1138,10 +1253,90 @@ user_parse_query(User, Query) ->
error error
end; end;
_ -> _ ->
case lists:keysearch("removeuser", 1, Query) of
{value, _} ->
ejabberd_auth:remove_user(User),
ok;
false ->
nothing
end
end.
user_queue(User, Query, Lang) ->
Res = user_queue_parse_query(User, Query),
Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, User})),
FMsgs =
lists:map(
fun({offline_msg, _User, TimeStamp, _Expire, From, To,
{xmlelement, Name, Attrs, Els}} = Msg) ->
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
Time = lists:flatten(
io_lib:format(
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour, Minute, Second])),
SFrom = jlib:jid_to_string(From),
STo = jlib:jid_to_string(To),
Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
Packet = jlib:remove_attr(
"jeai-id", {xmlelement, Name, Attrs2, Els}),
FPacket = pretty_print(Packet),
?XE("tr",
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
?XAC("td", [{"class", "valign"}], Time),
?XAC("td", [{"class", "valign"}], SFrom),
?XAC("td", [{"class", "valign"}], STo),
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
)
end, Msgs),
[?XC("h1", User ++ " offline messages queue")] ++
case Res of
ok -> [?CT("submitted"), ?P];
error -> [?CT("bad format"), ?P];
nothing -> []
end ++
[?XAE("form", [{"method", "post"}],
[?XE("table",
[?XE("thead",
[?XE("tr",
[?X("td"),
?XCT("td", "Time"),
?XCT("td", "From"),
?XCT("td", "To"),
?XCT("td", "Packet")
])]),
?XE("tbody", FMsgs)]),
?BR,
?INPUTT("submit", "delete", "Delete Selected")
])].
user_queue_parse_query(User, Query) ->
case lists:keysearch("delete", 1, Query) of
{value, _} ->
Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, User})),
F = fun() ->
lists:foreach(
fun(Msg) ->
ID = jlib:encode_base64(
binary_to_list(term_to_binary(Msg))),
case lists:member({"selected", ID}, Query) of
true ->
mnesia:delete_object(Msg);
false ->
ok
end
end, Msgs)
end,
mnesia:transaction(F),
ok;
false ->
nothing nothing
end. end.
-record(roster, {uj, -record(roster, {uj,
user, user,
jid, jid,
@ -1156,10 +1351,11 @@ ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none; ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask. ask_to_pending(Ask) -> Ask.
user_roster(User, Query, Lang) ->
%Res = user_parse_query(User, Query), user_roster(User, Query, Lang, Admin) ->
Res = nothing, % TODO
LUser = jlib:nameprep(User), LUser = jlib:nameprep(User),
Items1 = mnesia:dirty_index_read(roster, LUser, #roster.user),
Res = user_roster_parse_query(User, Items1, Query, Admin),
Items = mnesia:dirty_index_read(roster, LUser, #roster.user), Items = mnesia:dirty_index_read(roster, LUser, #roster.user),
SItems = lists:sort(Items), SItems = lists:sort(Items),
FItems = FItems =
@ -1173,22 +1369,43 @@ user_roster(User, Query, Lang) ->
[?XCT("td", "JID"), [?XCT("td", "JID"),
?XCT("td", "Name"), ?XCT("td", "Name"),
?XCT("td", "Subscription"), ?XCT("td", "Subscription"),
?XCT("td", "Pending") ?XCT("td", "Pending"),
?XCT("td", "Groups")
])]), ])]),
?XE("tbody", ?XE("tbody",
lists:map( lists:map(
fun(R) -> fun(R) ->
Groups =
lists:flatmap(
fun(Group) ->
[?C(Group), ?BR]
end, R#roster.groups),
Pending = ask_to_pending(R#roster.ask),
?XE("tr", ?XE("tr",
[?XE("td", [?C(jlib:jid_to_string( [?XAC("td", [{"class", "valign"}],
R#roster.jid))]), jlib:jid_to_string(R#roster.jid)),
?XE("td", [?C(R#roster.name)]), ?XAC("td", [{"class", "valign"}],
?XE("td", R#roster.name),
[?C(atom_to_list( ?XAC("td", [{"class", "valign"}],
R#roster.subscription))]), atom_to_list(R#roster.subscription)),
?XE("td", ?XAC("td", [{"class", "valign"}],
[?C(atom_to_list( atom_to_list(Pending)),
ask_to_pending( ?XAE("td", [{"class", "valign"}], Groups),
R#roster.ask)))])]) if
Pending == in ->
?XAE("td", [{"class", "valign"}],
[?INPUTT("submit",
"validate" ++
term_to_id(R#roster.jid),
"Validate")]);
true ->
?X("td")
end,
?XAE("td", [{"class", "valign"}],
[?INPUTT("submit",
"remove" ++
term_to_id(R#roster.jid),
"Remove")])])
end, SItems))])] end, SItems))])]
end, end,
[?XC("h1", "Roster of " ++ User)] ++ [?XC("h1", "Roster of " ++ User)] ++
@ -1198,7 +1415,129 @@ user_roster(User, Query, Lang) ->
nothing -> [] nothing -> []
end ++ end ++
[?XAE("form", [{"method", "post"}], [?XAE("form", [{"method", "post"}],
FItems)]. FItems ++
[?P,
?INPUT("text", "newjid", ""), ?C(" "),
?INPUTT("submit", "addjid", "Add JID")
])].
user_roster_parse_query(User, Items, Query, Admin) ->
case lists:keysearch("addjid", 1, Query) of
{value, _} ->
case lists:keysearch("newjid", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, SJID}} ->
case jlib:string_to_jid(SJID) of
JID when is_record(JID, jid) ->
user_roster_subscribe_jid(User, JID),
ok;
error ->
error
end;
false ->
error
end;
false ->
case lists:keysearch("adduser", 1, Query) of
{value, _} ->
case lists:keysearch("newuser", 1, Query) of
{value, {_, undefined}} ->
error;
{value, {_, U}} ->
if
Admin ->
user_roster_subscribe_users(User, U);
true ->
case jlib:make_jid(U, ?MYNAME, "") of
JID when is_record(JID, jid) ->
user_roster_subscribe_jid(
User, JID),
ok;
false ->
error
end
end;
false ->
error
end;
false ->
case catch user_roster_item_parse_query(
User, Items, Query) of
submitted ->
ok;
{'EXIT', _Reason} ->
error;
_ ->
nothing
end
end
end.
user_roster_subscribe_users(User1, User2) ->
case jlib:make_jid(User1, ?MYNAME, "") of
JID1 when is_record(JID1, jid) ->
case jlib:make_jid(User2, ?MYNAME, "") of
JID2 when is_record(JID2, jid) ->
mod_roster:out_subscription(User1, JID2, subscribe),
mod_roster:in_subscription(User2, JID1, subscribe),
mod_roster:out_subscription(User2, JID1, subscribe),
mod_roster:in_subscription(User1, JID2, subscribe),
mod_roster:out_subscription(User1, JID2, subscribed),
mod_roster:in_subscription(User2, JID1, subscribed),
mod_roster:out_subscription(User2, JID1, subscribed),
mod_roster:in_subscription(User1, JID2, subscribed),
ok;
false ->
error
end;
false ->
error
end.
user_roster_subscribe_jid(User, JID) ->
mod_roster:out_subscription(User, JID, subscribe),
UJID = jlib:make_jid(User, ?MYNAME, ""),
ejabberd_router:route(
UJID, JID, {xmlelement, "presence", [{"type", "subscribe"}], []}).
user_roster_item_parse_query(User, Items, Query) ->
lists:foreach(
fun(R) ->
JID = R#roster.jid,
case lists:keysearch(
"validate" ++ term_to_id(JID), 1, Query) of
{value, _} ->
JID1 = jlib:make_jid(JID),
mod_roster:out_subscription(User, JID1, subscribed),
UJID = jlib:make_jid(User, ?MYNAME, ""),
ejabberd_router:route(
UJID, JID1, {xmlelement, "presence",
[{"type", "subscribed"}], []}),
throw(submitted);
false ->
case lists:keysearch(
"remove" ++ term_to_id(JID), 1, Query) of
{value, _} ->
UJID = jlib:make_jid(User, ?MYNAME, ""),
mod_roster:process_iq(
UJID, UJID,
#iq{type = set,
sub_el = {xmlelement, "query",
[{"xmlns", ?NS_ROSTER}],
[{xmlelement, "item",
[{"jid", jlib:jid_to_string(JID)},
{"subscription", "remove"}],
[]}]}}),
throw(submitted);
false ->
ok
end
end
end, Items),
nothing.
get_nodes(Lang) -> get_nodes(Lang) ->
@ -1650,3 +1989,45 @@ node_ports_parse_query(Node, Ports, Query) ->
pretty_print(El) ->
lists:flatten(pretty_print(El, "")).
pretty_print({xmlcdata, CData}, Prefix) ->
[Prefix, CData, $\n];
pretty_print({xmlelement, Name, Attrs, Els}, Prefix) ->
[Prefix, $<, Name,
case Attrs of
[] ->
[];
[{Attr, Val} | RestAttrs] ->
AttrPrefix = [Prefix,
string:copies(" ", length(Name) + 2)],
[$\s, Attr, $=, $', xml:crypt(Val), $' |
lists:map(fun({Attr1, Val1}) ->
[$\n, AttrPrefix,
Attr1, $=, $', xml:crypt(Val1), $']
end, RestAttrs)]
end,
if
Els == [] ->
"/>\n";
true ->
OnlyCData = lists:all(fun({xmlcdata, _}) -> true;
({xmlelement, _, _, _}) -> false
end, Els),
if
OnlyCData ->
[$>,
xml:get_cdata(Els),
$<, $/, Name, $>, $\n
];
true ->
[$>, $\n,
lists:map(fun(E) ->
pretty_print(E, [Prefix, " "])
end, Els),
Prefix, $<, $/, Name, $>, $\n
]
end
end].