From ec182cbd60786487c6843e60981dabc03a4e78bb Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Tue, 2 Mar 2004 21:16:55 +0000 Subject: [PATCH] * src/web/: Small HTTP server and admin web-interface to ejabberd (not completed yet) * src/ejabberd_sup.erl: Added HTTP processes supervisor * src/ejabberd_c2s.erl: Added API to ask presence (thanks to Mickael Remond) * src/msgs/ru.msg: Updated (thanks to Sergei Golovan) * src/mod_muc/mod_muc_room.erl: Updated date parser (thanks to Sergei Golovan) * src/mod_muc/mod_muc.erl: Added error descriptions (thanks to Sergei Golovan) * src/mod_muc/mod_muc_room.erl: Likewise * src/mod_vcard.erl: Fixed vCard tag (thanks to Sergei Golovan) * src/mod_irc/mod_irc.erl: Likewise * src/mod_pubsub/mod_pubsub.erl: Likewise * src/jlib.hrl: Added macros for errors with (thanks to Sergei Golovan) SVN Revision: 206 --- ChangeLog | 25 +++ src/ejabberd_c2s.erl | 23 +- src/ejabberd_sup.erl | 9 + src/jlib.hrl | 50 +++++ src/mod_irc/mod_irc.erl | 2 +- src/mod_muc/mod_muc.erl | 26 ++- src/mod_muc/mod_muc_room.erl | 374 +++++++++++++++++-------------- src/mod_pubsub/mod_pubsub.erl | 2 +- src/mod_vcard.erl | 8 +- src/msgs/ru.msg | 39 ++++ src/web/Makefile.in | 33 +++ src/web/ejabberd_http.erl | 401 ++++++++++++++++++++++++++++++++++ src/web/ejabberd_web.erl | 104 +++++++++ 13 files changed, 923 insertions(+), 173 deletions(-) create mode 100644 src/web/Makefile.in create mode 100644 src/web/ejabberd_http.erl create mode 100644 src/web/ejabberd_web.erl diff --git a/ChangeLog b/ChangeLog index 906dd7a9f..472fbdebf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,28 @@ +2004-03-02 Alexey Shchepin + + * src/web/: Small HTTP server and admin web-interface to ejabberd + (not completed yet) + * src/ejabberd_sup.erl: Added HTTP processes supervisor + + * src/ejabberd_c2s.erl: Added API to ask presence (thanks to + Mickael Remond) + + * src/msgs/ru.msg: Updated (thanks to Sergei Golovan) + + * src/mod_muc/mod_muc_room.erl: Updated date parser (thanks to + Sergei Golovan) + + * src/mod_muc/mod_muc.erl: Added error descriptions (thanks to + Sergei Golovan) + * src/mod_muc/mod_muc_room.erl: Likewise + + * src/mod_vcard.erl: Fixed vCard tag (thanks to Sergei Golovan) + * src/mod_irc/mod_irc.erl: Likewise + * src/mod_pubsub/mod_pubsub.erl: Likewise + + * src/jlib.hrl: Added macros for errors with (thanks to + Sergei Golovan) + 2004-02-26 Alexey Shchepin * src/msgs/ru.msg: Updated (thanks to Sergei Golovan) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 93ac7d41b..b2bd9924f 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -16,7 +16,8 @@ -export([start/2, start_link/2, send_text/2, - send_element/2]). + send_element/2, + get_presence/1]). %% gen_fsm callbacks -export([init/1, @@ -90,6 +91,10 @@ start(SockData, Opts) -> start_link(SockData, Opts) -> gen_fsm:start_link(ejabberd_c2s, [SockData, Opts], ?FSMOPTS). +%% Return Username, Resource and presence information +get_presence(FsmRef) -> + gen_fsm:sync_send_all_state_event(FsmRef, {get_presence}, 1000). + %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- @@ -661,6 +666,22 @@ handle_event(_Event, StateName, StateData) -> %% {stop, Reason, NewStateData} | %% {stop, Reason, Reply, NewStateData} %%---------------------------------------------------------------------- +handle_sync_event({get_presence}, _From, StateName, StateData) -> + User = StateData#state.user, + Status = + case StateData#state.pres_last of + undefined -> + "unavailable"; + PresLast -> + case xml:get_path_s(PresLast, [{elem, "status"}, cdata]) of + "" -> + "available"; + Stat -> + Stat + end + end, + Reply = {User, Status}, + {reply, Reply, StateName, StateData}; handle_sync_event(_Event, _From, StateName, StateData) -> Reply = ok, {reply, Reply, StateName, StateData}. diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 76dedc1ee..0d4d1816d 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -87,6 +87,14 @@ init([]) -> infinity, supervisor, [ejabberd_tmp_sup]}, + HTTPSupervisor = + {ejabberd_http_sup, + {ejabberd_tmp_sup, start_link, + [ejabberd_http_sup, ejabberd_http]}, + permanent, + infinity, + supervisor, + [ejabberd_tmp_sup]}, IQSupervisor = {ejabberd_iq_sup, {ejabberd_tmp_sup, start_link, @@ -102,6 +110,7 @@ init([]) -> S2SInSupervisor, S2SOutSupervisor, ServiceSupervisor, + HTTPSupervisor, IQSupervisor, Listener]}}. diff --git a/src/jlib.hrl b/src/jlib.hrl index 045207cb2..29afc3dc4 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -97,6 +97,56 @@ %-define(ERR_, % ?STANZA_ERROR("", "", "")). +-define(STANZA_ERRORT(Code, Type, Condition, Lang, Text), + {xmlelement, "error", + [{"code", Code}, {"type", Type}], + [{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}, + {xmlelement, "text", [{"xmlns", ?NS_STANZAS}], + [{xmlcdata, translate:translate(Lang, Text)}]}]}). + +-define(ERRT_BAD_REQUEST(Lang, Text), + ?STANZA_ERRORT("400", "modify", "bad-request", Lang, Text)). +-define(ERRT_CONFLICT(Lang, Text), + ?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)). +-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text), + ?STANZA_ERRORT("501", "cancel", "feature-not-implemented", Lang, Text)). +-define(ERRT_FORBIDDEN(Lang, Text), + ?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)). +-define(ERRT_GONE(Lang, Text), + ?STANZA_ERRORT("302", "modify", "gone", Lang, Text)). +-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text), + ?STANZA_ERRORT("500", "wait", "internal-server-error", Lang, Text)). +-define(ERRT_ITEM_NOT_FOUND(Lang, Text), + ?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)). +-define(ERRT_JID_MALFORMED(Lang, Text), + ?STANZA_ERRORT("400", "modify", "jid-malformed", Lang, Text)). +-define(ERRT_NOT_ACCEPTABLE(Lang, Text), + ?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)). +-define(ERRT_NOT_ALLOWED(Lang, Text), + ?STANZA_ERRORT("405", "cancel", "not-allowed", Lang, Text)). +-define(ERRT_NOT_AUTHORIZED(Lang, Text), + ?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)). +-define(ERRT_PAYMENT_REQUIRED(Lang, Text), + ?STANZA_ERRORT("402", "auth", "payment-required", Lang, Text)). +-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text), + ?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)). +-define(ERRT_REDIRECT(Lang, Text), + ?STANZA_ERRORT("302", "modify", "redirect", Lang, Text)). +-define(ERRT_REGISTRATION_REQUIRED(Lang, Text), + ?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)). +-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text), + ?STANZA_ERRORT("404", "cancel", "remote-server-not-found", Lang, Text)). +-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text), + ?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)). +-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text), + ?STANZA_ERRORT("500", "wait", "resource-constraint", Lang, Text)). +-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text), + ?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)). +-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text), + ?STANZA_ERRORT("407", "auth", "subscription-required", Lang, Text)). +-define(ERRT_UNEXPECTED_REQUEST(Lang, Text), + ?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)). + % TODO: update to new-style % Application-specific stanza errors -define(AUTH_STANZA_ERROR(Condition), diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index 1bb712575..a7afd5f06 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -79,7 +79,7 @@ do_route(Host, From, To, Packet) -> lang = Lang} = IQ -> Res = IQ#iq{type = result, sub_el = - [{xmlelement, "query", + [{xmlelement, "vCard", [{"xmlns", XMLNS}], iq_get_vcard(Lang)}]}, ejabberd_router:route(To, diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index 8fee7a291..551182b3d 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -113,8 +113,9 @@ do_route(Host, From, To, Packet) -> jlib:iq_to_xml(Res)); #iq{type = set, xmlns = ?NS_REGISTER = XMLNS, + lang = Lang, sub_el = SubEl} = IQ -> - case process_iq_register_set(From, SubEl) of + case process_iq_register_set(From, SubEl, Lang) of {result, IQRes} -> Res = IQ#iq{type = result, sub_el = @@ -135,7 +136,7 @@ do_route(Host, From, To, Packet) -> sub_el = SubEl} = IQ -> Res = IQ#iq{type = result, sub_el = - [{xmlelement, "query", + [{xmlelement, "vCard", [{"xmlns", XMLNS}], iq_get_vcard(Lang)}]}, ejabberd_router:route(To, @@ -161,9 +162,12 @@ do_route(Host, From, To, Packet) -> [{elem, "body"}, cdata]), broadcast_service_message(Msg); _ -> + Lang = xml:get_attr_s("xml:lang", Attrs), + ErrText = "Only service administrators " + "are allowed to send service messages", Err = jlib:make_error_reply( Packet, - ?ERR_NOT_ALLOWED), + ?ERRT_FORBIDDEN(Lang, ErrText)), ejabberd_router:route( To, From, Err) end @@ -198,8 +202,10 @@ do_route(Host, From, To, Packet) -> mod_muc_room:route(Pid, From, Nick, Packet), ok; _ -> + Lang = xml:get_attr_s("xml:lang", Attrs), + ErrText = "Conference room does not exist", Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), + Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), ejabberd_router:route(To, From, Err) end; [R] -> @@ -332,12 +338,13 @@ iq_get_register_info(From, Host, Lang) -> Lang, "Enter nickname you want to register")}]}, ?XFIELD("text-single", "Nickname", "nick", Nick)]}]. -iq_set_register_info(From, XData) -> +iq_set_register_info(From, XData, Lang) -> {LUser, LServer, _} = jlib:jid_tolower(From), LUS = {LUser, LServer}, case lists:keysearch("nick", 1, XData) of false -> - {error, ?ERR_BAD_REQUEST}; + ErrText = "You must fill in field \"nick\" in the form", + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; {value, {_, [Nick]}} -> F = fun() -> case Nick of @@ -369,13 +376,14 @@ iq_set_register_info(From, XData) -> {atomic, ok} -> {result, []}; {atomic, false} -> - {error, ?ERR_CONFLICT}; + ErrText = "Specified nickname is already registered", + {error, ?ERRT_CONFLICT(Lang, ErrText)}; _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} end end. -process_iq_register_set(From, SubEl) -> +process_iq_register_set(From, SubEl, Lang) -> {xmlelement, Name, Attrs, Els} = SubEl, case xml:remove_cdata(Els) of [{xmlelement, "x", Attrs1, Els1} = XEl] -> @@ -389,7 +397,7 @@ process_iq_register_set(From, SubEl) -> invalid -> {error, ?ERR_BAD_REQUEST}; _ -> - iq_set_register_info(From, XData) + iq_set_register_info(From, XData, Lang) end; _ -> {error, ?ERR_BAD_REQUEST} diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index 312938287..2aca20b1b 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -119,6 +119,7 @@ init([Host, Room, Opts]) -> normal_state({route, From, "", {xmlelement, "message", Attrs, Els} = Packet}, StateData) -> + Lang = xml:get_attr_s("xml:lang", Attrs), case is_user_online(From, StateData) of true -> case xml:get_attr_s("type", Attrs) of @@ -172,16 +173,26 @@ normal_state({route, From, "", NewStateData1), {next_state, normal_state, NewStateData2}; _ -> + ErrText = + case (StateData#state.config)#config.allow_change_subj of + true -> + "Only moderators and participants " + "are allowed to change subject in this room"; + _ -> + "Only moderators " + "are allowed to change subject in this room" + end, Err = jlib:make_error_reply( - Packet, ?ERR_FORBIDDEN), + Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), ejabberd_router:route( StateData#state.jid, From, Err), {next_state, normal_state, StateData} end; true -> + ErrText = "Visitors are not allowed to send messages to all occupants", Err = jlib:make_error_reply( - Packet, ?ERR_FORBIDDEN), + Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), ejabberd_router:route( StateData#state.jid, From, Err), @@ -202,11 +213,20 @@ normal_state({route, From, "", _ -> {next_state, normal_state, StateData} end; + "chat" -> + ErrText = "It is not allowed to send private messages to the conference", + Err = jlib:make_error_reply( + Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + ejabberd_router:route( + StateData#state.jid, + From, Err), + {next_state, normal_state, StateData}; Type when (Type == "") or (Type == "normal") -> case check_invitation(From, Els, StateData) of error -> + ErrText = "It is not allowed to send normal messages to the conference", Err = jlib:make_error_reply( - Packet, ?ERR_NOT_ALLOWED), + Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), ejabberd_router:route( StateData#state.jid, From, Err), @@ -231,8 +251,9 @@ normal_state({route, From, "", end end; _ -> + ErrText = "Improper message type", Err = jlib:make_error_reply( - Packet, ?ERR_NOT_ALLOWED), + Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), ejabberd_router:route( StateData#state.jid, From, Err), @@ -243,8 +264,9 @@ normal_state({route, From, "", "error" -> ok; _ -> + ErrText = "Only occupants are allowed to send messages to the conference", Err = jlib:make_error_reply( - Packet, ?ERR_NOT_ACCEPTABLE), + Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), ejabberd_router:route(StateData#state.jid, From, Err) end, {next_state, normal_state, StateData} @@ -305,6 +327,7 @@ normal_state({route, From, Nick, {xmlelement, "presence", Attrs, Els} = Packet}, StateData) -> Type = xml:get_attr_s("type", Attrs), + Lang = xml:get_attr_s("xml:lang", Attrs), StateData1 = case Type of "unavailable" -> @@ -336,17 +359,32 @@ normal_state({route, From, Nick, true -> case is_nick_change(From, Nick, StateData) of true -> - case is_nick_exists(Nick, StateData) of - true -> + case {is_nick_exists(Nick, StateData), + mod_muc:can_use_nick(From, Nick)} of + {true, _} -> + Lang = xml:get_attr_s("xml:lang", Attrs), + ErrText = "Nickname is already in use by another occupant", Err = jlib:make_error_reply( Packet, - ?ERR_CONFLICT), + ?ERRT_CONFLICT(Lang, ErrText)), ejabberd_router:route( jlib:jid_replace_resource( StateData#state.jid, Nick), % TODO: s/Nick/""/ From, Err), StateData; + {_, false} -> + ErrText = "Nickname is registered by another person", + Err = jlib:make_error_reply( + Packet, + ?ERRT_CONFLICT(Lang, ErrText)), + ejabberd_router:route( + % TODO: s/Nick/""/ + jlib:jid_replace_resource( + StateData#state.jid, + Nick), + From, Err), + StateData; _ -> change_nick(From, Nick, StateData) end; @@ -376,6 +414,7 @@ normal_state({route, From, ToNick, {xmlelement, "message", Attrs, Els} = Packet}, StateData) -> Type = xml:get_attr_s("type", Attrs), + Lang = xml:get_attr_s("xml:lang", Attrs), case Type of "error" -> case is_user_online(From, StateData) of @@ -398,8 +437,10 @@ normal_state({route, From, ToNick, true -> case Type of "groupchat" -> + ErrText = "It is not allowed to send private " + "messages of type \"groupchat\"", Err = jlib:make_error_reply( - Packet, ?ERR_BAD_REQUEST), + Packet, ?ERRT_BAD_REQUEST(Lang, ErrText)), ejabberd_router:route( jlib:jid_replace_resource( StateData#state.jid, @@ -408,8 +449,9 @@ normal_state({route, From, ToNick, _ -> case find_jid_by_nick(ToNick, StateData) of false -> + ErrText = "Recipient is not in the conference room", Err = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND), + Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), ejabberd_router:route( jlib:jid_replace_resource( StateData#state.jid, @@ -427,8 +469,9 @@ normal_state({route, From, ToNick, end end; _ -> + ErrText = "Only occupants are allowed to send messages to the conference", Err = jlib:make_error_reply( - Packet, ?ERR_NOT_ALLOWED), + Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), ejabberd_router:route( jlib:jid_replace_resource( StateData#state.jid, @@ -441,17 +484,19 @@ normal_state({route, From, ToNick, normal_state({route, From, ToNick, {xmlelement, "iq", Attrs, Els} = Packet}, StateData) -> - case (StateData#state.config)#config.allow_query_users - andalso is_user_online(From, StateData) of - true -> + Lang = xml:get_attr_s("xml:lang", Attrs), + case {(StateData#state.config)#config.allow_query_users, + is_user_online(From, StateData)} of + {true, true} -> case find_jid_by_nick(ToNick, StateData) of false -> case jlib:iq_query_info(Packet) of reply -> ok; _ -> + ErrText = "Recipient is not in the conference room", Err = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND), + Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), ejabberd_router:route( jlib:jid_replace_resource( StateData#state.jid, ToNick), @@ -465,13 +510,26 @@ normal_state({route, From, ToNick, jlib:jid_replace_resource(StateData#state.jid, FromNick), ToJID, Packet) end; + {_, false} -> + case jlib:iq_query_info(Packet) of + reply -> + ok; + _ -> + ErrText = "Only occupants are allowed to send queries to the conference", + Err = jlib:make_error_reply( + Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + ejabberd_router:route( + jlib:jid_replace_resource(StateData#state.jid, ToNick), + From, Err) + end; _ -> case jlib:iq_query_info(Packet) of reply -> ok; _ -> + ErrText = "Queries to the conference members are not allowed in this room", Err = jlib:make_error_reply( - Packet, ?ERR_NOT_ALLOWED), + Packet, ?ERRT_NOT_ALLOWED(Lang, ErrText)), ejabberd_router:route( jlib:jid_replace_resource(StateData#state.jid, ToNick), From, Err) @@ -798,10 +856,20 @@ is_nick_change(JID, Nick, StateData) -> end. add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> - case is_nick_exists(Nick, StateData) or - not mod_muc:can_use_nick(From, Nick) of - true -> - Err = jlib:make_error_reply(Packet, ?ERR_CONFLICT), + Lang = xml:get_attr_s("xml:lang", Attrs), + case {is_nick_exists(Nick, StateData), + mod_muc:can_use_nick(From, Nick)} of + {true, _} -> + ErrText = "Nickname is already in use by another occupant", + Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)), + ejabberd_router:route( + % TODO: s/Nick/""/ + jlib:jid_replace_resource(StateData#state.jid, Nick), + From, Err), + StateData; + {_, false} -> + ErrText = "Nickname is registered by another person", + Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)), ejabberd_router:route( % TODO: s/Nick/""/ jlib:jid_replace_resource(StateData#state.jid, Nick), @@ -815,8 +883,12 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> Err = jlib:make_error_reply( Packet, case Affiliation of - outcast -> ?ERR_FORBIDDEN; - _ -> ?ERR_REGISTRATION_REQUIRED + outcast -> + ErrText = "You have been banned from this room", + ?ERRT_FORBIDDEN(Lang, ErrText); + _ -> + ErrText = "Membership required to enter this room", + ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText) end), ejabberd_router:route( % TODO: s/Nick/""/ jlib:jid_replace_resource(StateData#state.jid, Nick), @@ -836,13 +908,22 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> true -> ok; _ -> - Lang = xml:get_attr_s("xml:lang", Attrs), send_subject(From, Lang, StateData) end, NewState; - _ -> + nopass -> + ErrText = "Password required to enter this room", Err = jlib:make_error_reply( - Packet, ?ERR_NOT_AUTHORIZED), + Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)), + ejabberd_router:route( % TODO: s/Nick/""/ + jlib:jid_replace_resource( + StateData#state.jid, Nick), + From, Err), + StateData; + _ -> + ErrText = "Incorrect password", + Err = jlib:make_error_reply( + Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)), ejabberd_router:route( % TODO: s/Nick/""/ jlib:jid_replace_resource( StateData#state.jid, Nick), @@ -860,20 +941,30 @@ check_password(_Affiliation, Els, StateData) -> true; true -> Pass = extract_password(Els), - case (StateData#state.config)#config.password of - Pass -> - true; + case Pass of + false -> + nopass; _ -> - false + case (StateData#state.config)#config.password of + Pass -> + true; + _ -> + false + end end end. extract_password([]) -> - ""; -extract_password([{xmlelement, Name, Attrs, SubEls} = El | Els]) -> + false; +extract_password([{xmlelement, _Name, Attrs, _SubEls} = El | Els]) -> case xml:get_attr_s("xmlns", Attrs) of ?NS_MUC -> - xml:get_path_s(El, [{elem, "password"}, cdata]); + case xml:get_subtag(El, "password") of + false -> + false; + SubEl -> + xml:get_tag_cdata(SubEl) + end; _ -> extract_password(Els) end; @@ -913,6 +1004,7 @@ count_stanza_shift(Nick, Els, StateData) -> _ -> count_maxchars_shift(Nick, MaxChars, HL) end, + lists:max([Shift0, Shift1, Shift2, Shift3]). count_seconds_shift(Seconds, HistoryList) -> @@ -968,7 +1060,12 @@ extract_history([{xmlelement, Name, Attrs, SubEls} = El | Els], Type) -> [{elem, "history"}, {attr, Type}]), case Type of "since" -> - parse_datetime(AttrVal); + case catch parse_datetime(AttrVal) of + {'EXIT', Err} -> + false; + Res -> + Res + end; _ -> case catch list_to_integer(AttrVal) of {'EXIT', _} -> @@ -991,50 +1088,20 @@ extract_history([_ | Els], Type) -> % JEP-0082 % yyyy-mm-ddThh:mm:ss[.sss]{Z|{+|-}hh:mm} -> {{yyyy, mm, dd}, {hh, mm, ss}} (UTC) parse_datetime(TimeStr) -> - DateTime = string:tokens(TimeStr, "T"), - case DateTime of - [Date, Time] -> - case parse_date(Date) of - false -> - false; - D -> - case parse_time(Time) of - false -> - false; - {T, TZH, TZM} -> - S = calendar:datetime_to_gregorian_seconds( - {D, T}), - calendar:gregorian_seconds_to_datetime( - S - TZH * 60 * 60 - TZM * 60 * 30) - end - end; - _ -> - false - end. + [Date, Time] = string:tokens(TimeStr, "T"), + D = parse_date(Date), + {T, TZH, TZM} = parse_time(Time), + S = calendar:datetime_to_gregorian_seconds({D, T}), + calendar:gregorian_seconds_to_datetime(S - TZH * 60 * 60 - TZM * 60). % yyyy-mm-dd parse_date(Date) -> YearMonthDay = string:tokens(Date, "-"), - case length(YearMonthDay) of - 3 -> - [Y, M, D] = lists:map( - fun(L)-> - case catch list_to_integer(L) of - {'EXIT', _} -> - false; - Int -> - Int - end - end, YearMonthDay), - case catch calendar:valid_date(Y, M, D) of - true -> - {Y, M, D}; - _ -> - false - end; - _ -> - false - end. + [Y, M, D] = lists:map( + fun(L)-> + list_to_integer(L) + end, YearMonthDay), + {Y, M, D}. % hh:mm:ss[.sss]TZD parse_time(Time) -> @@ -1043,12 +1110,7 @@ parse_time(Time) -> parse_time_with_timezone(Time); _ -> [T | _] = string:tokens(Time, "Z"), - case parse_time1(T) of - false -> - false; - TT -> - {TT, 0, 0} - end + {parse_time1(T), 0, 0} end. parse_time_with_timezone(Time) -> @@ -1065,71 +1127,35 @@ parse_time_with_timezone(Time) -> end. parse_time_with_timezone(Time, Delim) -> - TTZ = string:tokens(Time, Delim), - case TTZ of - [T, TZ] -> - case parse_timezone(TZ) of - false -> - false; - {TZH, TZM} -> - case parse_time1(T) of - false -> - false; - TT -> - case Delim of - "-" -> - {TT, -TZH, -TZM}; - "+" -> - {TT, TZH, TZM}; - _ -> - false - end - end - end; - _ -> - false + [T, TZ] = string:tokens(Time, Delim), + {TZH, TZM} = parse_timezone(TZ), + TT = parse_time1(T), + case Delim of + "-" -> + {TT, -TZH, -TZM}; + "+" -> + {TT, TZH, TZM} end. parse_timezone(TZ) -> - case string:tokens(TZ, ":") of - [H, M] -> - case check_list([{H, 12}, {M, 60}]) of - {[H, M], true} -> - {H, M}; - _ -> - false - end; - _ -> - false - end. + [H, M] = string:tokens(TZ, ":"), + {[H1, M1], true} = check_list([{H, 12}, {M, 60}]), + {H1, M1}. parse_time1(Time) -> - case string:tokens(Time, ".") of - [HMS | _] -> - case string:tokens(HMS, ":") of - [H, M, S] -> - case check_list([{H, 24}, {M, 60}, {S, 60}]) of - {[H1, M1, S1], true} -> - {H1, M1, S1}; - _ -> - false - end; - _ -> - false - end; - _ -> - false - end. + [HMS | _] = string:tokens(Time, "."), + [H, M, S] = string:tokens(HMS, ":"), + {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, {S, 60}]), + {H1, M1, S1}. check_list(List) -> lists:mapfoldl( fun({L, N}, B)-> - case catch list_to_integer(L) of - {'EXIT', _} -> - {false, false}; - Int when (Int >= 0) and (Int =< N) -> - {Int, B}; - _ -> + V = list_to_integer(L), + if + (V >= 0) and (V =< N) -> + {V, B}; + true -> {false, false} end end, true, List). @@ -1405,7 +1431,7 @@ can_change_subject(Role, StateData) -> process_iq_admin(From, set, Lang, SubEl, StateData) -> {xmlelement, _, _, Items} = SubEl, - process_admin_items_set(From, Items, StateData); + process_admin_items_set(From, Items, Lang, StateData); process_iq_admin(From, get, Lang, SubEl, StateData) -> case xml:get_subtag(SubEl, "item") of @@ -1431,7 +1457,8 @@ process_iq_admin(From, get, Lang, SubEl, StateData) -> SAffiliation, StateData), {result, Items, StateData}; true -> - {error, ?ERR_NOT_ALLOWED} + ErrText = "Administrator privileges required", + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end end end; @@ -1445,7 +1472,8 @@ process_iq_admin(From, get, Lang, SubEl, StateData) -> Items = items_with_role(SRole, StateData), {result, Items, StateData}; true -> - {error, ?ERR_NOT_ALLOWED} + ErrText = "Moderator privileges required", + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end end end @@ -1492,10 +1520,10 @@ search_affiliation(Affiliation, StateData) -> end, ?DICT:to_list(StateData#state.affiliations)). -process_admin_items_set(UJID, Items, StateData) -> +process_admin_items_set(UJID, Items, Lang, StateData) -> UAffiliation = get_affiliation(UJID, StateData), URole = get_role(UJID, StateData), - case find_changed_items(UJID, UAffiliation, URole, Items, StateData, []) of + case find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, []) of {result, Res} -> NSD = lists:foldl( @@ -1555,19 +1583,23 @@ process_admin_items_set(UJID, Items, StateData) -> end. -find_changed_items(UJID, UAffiliation, URole, [], StateData, Res) -> +find_changed_items(UJID, UAffiliation, URole, [], Lang, StateData, Res) -> {result, Res}; find_changed_items(UJID, UAffiliation, URole, [{xmlcdata, _} | Items], - StateData, Res) -> - find_changed_items(UJID, UAffiliation, URole, Items, StateData, Res); + Lang, StateData, Res) -> + find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res); find_changed_items(UJID, UAffiliation, URole, [{xmlelement, "item", Attrs, Els} = Item | Items], - StateData, Res) -> + Lang, StateData, Res) -> TJID = case xml:get_attr("jid", Attrs) of {value, S} -> case jlib:string_to_jid(S) of error -> - {error, ?ERR_BAD_REQUEST}; + ErrText = io_lib:format( + translate:translate( + Lang, + "JID ~s is invalid"), [S]), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; J -> {value, J} end; @@ -1576,7 +1608,13 @@ find_changed_items(UJID, UAffiliation, URole, {value, N} -> case find_jid_by_nick(N, StateData) of false -> - {error, ?ERR_NOT_ALLOWED}; + ErrText = + io_lib:format( + translate:translate( + Lang, + "Nickname ~s does not exist in the room"), + [N]), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; J -> {value, J} end; @@ -1596,7 +1634,13 @@ find_changed_items(UJID, UAffiliation, URole, {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; + ErrText1 = + io_lib:format( + translate:translate( + Lang, + "Invalid affiliation: ~s"), + [StrAffiliation]), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; SAffiliation -> case can_change_ra( UAffiliation, URole, @@ -1606,13 +1650,13 @@ find_changed_items(UJID, UAffiliation, URole, find_changed_items( UJID, UAffiliation, URole, - Items, StateData, + Items, Lang, StateData, Res); true -> find_changed_items( UJID, UAffiliation, URole, - Items, StateData, + Items, Lang, StateData, [{jlib:jid_remove_resource(JID), affiliation, SAffiliation, @@ -1627,7 +1671,13 @@ find_changed_items(UJID, UAffiliation, URole, {value, StrRole} -> case catch list_to_role(StrRole) of {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; + ErrText1 = + io_lib:format( + translate:translate( + Lang, + "Invalid role: ~s"), + [StrRole]), + {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; SRole -> case can_change_ra( UAffiliation, URole, @@ -1637,13 +1687,13 @@ find_changed_items(UJID, UAffiliation, URole, find_changed_items( UJID, UAffiliation, URole, - Items, StateData, + Items, Lang, StateData, Res); true -> find_changed_items( UJID, UAffiliation, URole, - Items, StateData, + Items, Lang, StateData, [{JID, role, SRole, xml:get_path_s( Item, [{elem, "reason"}, @@ -1656,7 +1706,7 @@ find_changed_items(UJID, UAffiliation, URole, Err -> Err end; -find_changed_items(UJID, UAffiliation, URole, Items, StateData, Res) -> +find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, Res) -> {error, ?ERR_BAD_REQUEST}. @@ -1851,10 +1901,11 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> [{xmlelement, "destroy", Attrs1, Els1}] -> destroy_room(Els1, StateData); Items -> - process_admin_items_set(From, Items, StateData) + process_admin_items_set(From, Items, Lang, StateData) end; _ -> - {error, ?ERR_FORBIDDEN} + ErrText = "Owner privileges required", + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end; process_iq_owner(From, get, Lang, SubEl, StateData) -> @@ -1872,7 +1923,13 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> {value, StrAffiliation} -> case catch list_to_affiliation(StrAffiliation) of {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; + ErrText = + io_lib:format( + translate:translate( + Lang, + "Invalid affiliation: ~s"), + [StrAffiliation]), + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; SAffiliation -> Items = items_with_affiliation( SAffiliation, StateData), @@ -1883,7 +1940,8 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} end; _ -> - {error, ?ERR_NOT_ALLOWED} + ErrText = "Owner privileges required", + {error, ?ERRT_FORBIDDEN(Lang, ErrText)} end. @@ -2191,7 +2249,7 @@ process_iq_disco_items(From, get, Lang, StateData) -> ?DICT:to_list(StateData#state.users)), {result, UList, StateData}; _ -> - {error, ?ERR_NOT_ALLOWED} + {error, ?ERR_FORBIDDEN} end. get_title(StateData) -> diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index c56edf948..3acd0a76d 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -162,7 +162,7 @@ do_route(Host, From, To, Packet) -> #iq{type = get, xmlns = ?NS_VCARD = XMLNS, lang = Lang, sub_el = SubEl} = IQ -> Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", + sub_el = [{xmlelement, "vCard", [{"xmlns", XMLNS}], iq_get_vcard(Lang)}]}, ejabberd_router:route(To, diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index c58d97e20..e56a4454d 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -336,7 +336,7 @@ do_route(From, To, Packet) -> ResIQ = IQ#iq{type = result, sub_el = [{xmlelement, - "query", + "vCard", [{"xmlns", ?NS_VCARD}], iq_get_vcard(Lang)}]}, ejabberd_router:route(To, @@ -356,8 +356,10 @@ iq_get_vcard(Lang) -> [{xmlcdata, "http://ejabberd.jabberstudio.org/"}]}, {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd vCard module\n" - "Copyright (c) 2003-2004 Alexey Shchepin")}]}]. + [{xmlcdata, translate:translate( + Lang, + "ejabberd vCard module\n" + "Copyright (c) 2003-2004 Alexey Shchepin")}]}]. find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> find_xdata_el1(SubEls). diff --git a/src/msgs/ru.msg b/src/msgs/ru.msg index 2bd4749ff..09c2c06ca 100644 --- a/src/msgs/ru.msg +++ b/src/msgs/ru.msg @@ -102,6 +102,12 @@ {"Enter nickname you want to register", "Введите псевдоним, который Вы хотели бы зарегистрировать"}. {"ejabberd MUC module\nCopyright (c) 2003-2004 Alexey Shchepin", "ejabberd MUC модуль\nCopyright (c) 2003-2004 Алексей Щепин"}. +{"Only service administrators are allowed to send service messages", + "Только администратор службы может посылать служебные сообщения"}. +{"Conference room does not exist", "Конференция не существует"}. +{"You must fill in field \"nick\" in the form", + "Вы должны заполнить поле \"nick\" в форме"}. +{"Specified nickname is already registered", "Указанный псевдоним уже зарегистрирован"}. % mod_muc/mod_muc_room.erl {" has set the subject to: ", " установил(а) тему: "}. @@ -128,6 +134,39 @@ {"Password", "Пароль"}. {"Make room anonymous?", "Сделать комнату анонимной?"}. {"Enable logging?", "Включить журналирование?"}. +{"Only moderators and participants are allowed to change subject in this room", + "Только модераторы и участники могут изменять тему в этой комнате"}. +{"Only moderators are allowed to change subject in this room", + "Только модераторы могут изменять тему в этой комнате"}. +{"Visitors are not allowed to send messages to all occupants", + "Посетителям не разрешается посылать сообщения всем присутствующим"}. +{"Only occupants are allowed to send messages to the conference", + "Только присутствующим разрешается посылать сообщения в конференцию"}. +{"It is not allowed to send normal messages to the conference", + "Нельзя посылать обычные сообщения в конференцию"}. +{"It is not allowed to send private messages to the conference", + "Не разрешается посылать частные сообщения прямо в конференцию"}. +{"Improper message type", "Неправильный тип сообщения"}. +{"Nickname is already in use by another occupant", "Псевдоним занят кем-то из присутствующих"}. +{"Nickname is registered by another person", "Псевдоним зарегистирован кем-то другим"}. +{"It is not allowed to send private messages of type \"groupchat\"", + "Нельзя посылать частные сообщения типа \"groupchat\""}. +{"Recipient is not in the conference room", "Адресата нет в конференции"}. +{"Only occupants are allowed to send queries to the conference", + "Только присутствующим разрешается посылать запросы в конференцию"}. +{"Queries to the conference members are not allowed in this room", + "Запросы к пользователям в этой конференции запрещены"}. +{"You have been banned from this room", "Вам запрещено входить в эту конференцию"}. +{"Membership required to enter this room", "В эту конференцию могут входить только её члены"}. +{"Password required to enter this room", "Чтобы войти в эту конференцию, нужен пароль"}. +{"Incorrect password", "Неправильный пароль"}. +{"Administrator privileges required", "Требуются права администратора"}. +{"Moderator privileges required", "Требуются права модератора"}. +{"JID ~s is invalid", "JID ~s недопустимый"}. +{"Nickname ~s does not exist in the room", "Псевдоним ~s в комнате отсутствует"}. +{"Invalid affiliation: ~s", "Недопустимый ранг: ~s"}. +{"Invalid role: ~s", "Недопустимая роль: ~s"}. +{"Owner privileges required", "Требуются права владельца"}. % mod_irc/mod_irc.erl {"ejabberd IRC module\nCopyright (c) 2003-2004 Alexey Shchepin", diff --git a/src/web/Makefile.in b/src/web/Makefile.in new file mode 100644 index 000000000..806bbed1a --- /dev/null +++ b/src/web/Makefile.in @@ -0,0 +1,33 @@ +# $Id$ + +CC = @CC@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ + +INCLUDES = @ERLANG_CFLAGS@ + +LIBDIRS = @ERLANG_LIBS@ + +SUBDIRS = + + +OUTDIR = .. +EFLAGS = -I .. -pz .. +OBJS = \ + $(OUTDIR)/ejabberd_http.beam \ + $(OUTDIR)/ejabberd_web.beam + +all: $(OBJS) + +$(OUTDIR)/%.beam: %.erl + erlc -W $(EFLAGS) -o $(OUTDIR) $< + + +clean: + rm -f $(OBJS) + +TAGS: + etags *.erl + diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl new file mode 100644 index 000000000..cbae8b3bf --- /dev/null +++ b/src/web/ejabberd_http.erl @@ -0,0 +1,401 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_http.erl +%%% Author : Alexey Shchepin +%%% Purpose : +%%% Created : 27 Feb 2004 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(ejabberd_http). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +%% External exports +-export([start/2, + start_link/2, + receive_headers/1]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). + +-record(state, {sockmod, + socket, + request_method, + request_path, + request_auth, + request_content_length + }). + + +-define(XHTML_DOCTYPE, + "\n"). + + +start(SockData, Opts) -> + supervisor:start_child(ejabberd_http_sup, [SockData, Opts]). + +start_link({SockMod, Socket}, Opts) -> + ?INFO_MSG("started: ~p", [{SockMod, Socket}]), + case SockMod of + gen_tcp -> + inet:setopts(Socket, [{packet, http}]); + ssl -> + ssl:setopts(Socket, [{packet, http}]) + end, + {ok, proc_lib:spawn_link(ejabberd_http, + receive_headers, + [#state{sockmod = SockMod, socket = Socket}])}. + + +send_text(State, Text) -> + (State#state.sockmod):send(State#state.socket, Text). + + +receive_headers(State) -> + Data = (State#state.sockmod):recv(State#state.socket, 0, 300000), + ?INFO_MSG("recv: ~p~n", [Data]), + case Data of + {ok, {http_request, Method, Path, _Version}} -> + receive_headers(State#state{request_method = Method, + request_path = Path}); + {ok, {http_header, _, 'Authorization', _, Auth}} -> + receive_headers(State#state{request_auth = parse_auth(Auth)}); + {ok, {http_header, _, 'Content-Length', _, SLen}} -> + case catch list_to_integer(SLen) of + Len when is_integer(Len) -> + receive_headers(State#state{request_content_length = Len}); + _ -> + receive_headers(State) + end; + {ok, {http_header, _, _, _, _}} -> + receive_headers(State); + {ok, http_eoh} -> + Out = process_request(State), + send_text(State, Out), + ok; + {error, _Reason} -> + ok; + _ -> + ok + end. + + +process_request(#state{request_method = 'GET', + request_path = {abs_path, Path}, + request_auth = undefined}) -> + make_xhtml_output( + 401, + [{"WWW-Authenticate", "basic realm=\"ejabberd\""}], + ejabberd_web:make_xhtml([{xmlelement, "h1", [], + [{xmlcdata, "401 Unauthorized"}]}])); + +process_request(#state{request_method = 'GET', + request_path = {abs_path, Path}, + request_auth = {User, Pass}}) -> + case ejabberd_auth:check_password(User, Pass) of + true -> + case (catch url_decode_q_split(Path)) of + {'EXIT', _} -> + process_request(false); + {NPath, Query} -> + LQuery = parse_urlencoded(Query), + ?INFO_MSG("path: ~p, query: ~p~n", [NPath, LQuery]), + LPath = string:tokens(NPath, "/"), + case ejabberd_web:process_get(User, LPath, LQuery) of + El when element(1, El) == xmlelement -> + make_xhtml_output(200, [], El); + {Status, Headers, El} -> + make_xhtml_output(Status, Headers, El) + end + end; + _ -> + make_xhtml_output( + 401, + [{"WWW-Authenticate", "basic realm=\"ejabberd\""}], + ejabberd_web:make_xhtml([{xmlelement, "h1", [], + [{xmlcdata, "401 Unauthorized"}]}])) + end; + +process_request(#state{request_method = 'POST', + request_path = {abs_path, Path}, + request_auth = {User, Pass}, + request_content_length = Len, + sockmod = SockMod, + socket = Socket} = State) when is_integer(Len) -> + case ejabberd_auth:check_password(User, Pass) of + true -> + case SockMod of + gen_tcp -> + inet:setopts(Socket, [{packet, 0}]); + ssl -> + ssl:setopts(Socket, [{packet, 0}]) + end, + Data = recv_data(State, Len), + ?INFO_MSG("client data: ~p~n", [Data]), + case (catch url_decode_q_split(Path)) of + {'EXIT', _} -> + process_request(false); + {NPath, Query} -> + ?INFO_MSG("path: ~p, query: ~p~n", [NPath, Query]), + LPath = string:tokens(NPath, "/"), + LQuery = parse_urlencoded(Data), + ?INFO_MSG("client query: ~p~n", [LQuery]), + case ejabberd_web:process_get(User, LPath, LQuery) of + El when element(1, El) == xmlelement -> + make_xhtml_output(200, [], El); + {Status, Headers, El} -> + make_xhtml_output(Status, Headers, El) + end + end; + _ -> + make_xhtml_output( + 401, + [{"WWW-Authenticate", "basic realm=\"ejabberd\""}], + ejabberd_web:make_xhtml([{xmlelement, "h1", [], + [{xmlcdata, "401 Unauthorized"}]}])) + end; + +process_request(State) -> + make_xhtml_output( + 400, + [], + ejabberd_web:make_xhtml([{xmlelement, "h1", [], + [{xmlcdata, "400 Bad Request"}]}])). + + +recv_data(State, Len) -> + recv_data(State, Len, []). + +recv_data(State, 0, Acc) -> + binary_to_list(list_to_binary(Acc)); +recv_data(State, Len, Acc) -> + case (State#state.sockmod):recv(State#state.socket, Len, 300000) of + {ok, Data} -> + recv_data(State, Len - size(Data), [Acc | Data]); + _ -> + "" + end. + + +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 +% 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. + + +parse_urlencoded(S) -> + parse_urlencoded(S, nokey, [], key). + +parse_urlencoded([$%, Hi, Lo | Tail], Last, Cur, State) -> + Hex = hex_to_integer([Hi, Lo]), + parse_urlencoded(Tail, Last, [Hex | Cur], State); + +parse_urlencoded([$& | Tail], _Last, Cur, key) -> + [{lists:reverse(Cur), ""} | + parse_urlencoded(Tail, nokey, [], key)]; %% cont keymode + +parse_urlencoded([$& | Tail], Last, Cur, value) -> + V = {Last, lists:reverse(Cur)}, + [V | parse_urlencoded(Tail, nokey, [], key)]; + +parse_urlencoded([$+ | Tail], Last, Cur, State) -> + parse_urlencoded(Tail, Last, [$\s | Cur], State); + +parse_urlencoded([$= | Tail], _Last, Cur, key) -> + parse_urlencoded(Tail, lists:reverse(Cur), [], value); %% change mode + +parse_urlencoded([H | Tail], Last, Cur, State) -> + parse_urlencoded(Tail, Last, [H|Cur], State); + +parse_urlencoded([], Last, Cur, _State) -> + [{Last, lists:reverse(Cur)}]; + +parse_urlencoded(undefined, _, _, _) -> + []. + + diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl new file mode 100644 index 000000000..c83494399 --- /dev/null +++ b/src/web/ejabberd_web.erl @@ -0,0 +1,104 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_web.erl +%%% Author : Alexey Shchepin +%%% Purpose : +%%% Created : 28 Feb 2004 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(ejabberd_web). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +%% External exports +-export([make_xhtml/1, + process_get/3]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). + + +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} + ]}. + + +-define(X(Name), {xmlelement, Name, [], []}). +-define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}). +-define(XE(Name, Els), {xmlelement, Name, [], Els}). +-define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}). +-define(C(Text), {xmlcdata, Text}). +-define(XC(Name, Text), ?XE(Name, [?C(Text)])). +-define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])). + +-define(LI(Els), ?XE("li", Els)). +-define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)). +-define(AC(URL, Text), ?A(URL, [?C(Text)])). +-define(P, ?X("p")). +-define(BR, ?X("br")). + +process_get(User, [], Query) -> + make_xhtml([?XC("h1", "ejabberd configuration"), + ?XE("ul", + [?LI([?AC("acls/", "Access Control Lists")]), + ?LI([?AC("access/", "Access Rules")]), + ?LI([?AC("users/", "Users")]), + ?LI([?AC("nodes/", "Nodes")]) + ]) + ]); + +process_get(User, ["acls"], Query) -> + case acl:match_rule(configure, jlib:make_jid(User, ?MYNAME, "")) of + deny -> + {401, [], make_xhtml([?XC("h1", "Not Allowed")])}; + allow -> + Res = case lists:keysearch("acls", 1, Query) of + {value, {_, String}} -> + case erl_scan:string(String) of + {ok, Tokens, _} -> + case erl_parse:parse_term(Tokens) of + {ok, NewACLs} -> + case acl:add_list(NewACLs, true) of + ok -> + ok; + _ -> + error + end; + _ -> + error + end; + _ -> + error + end; + _ -> + nothing + end, + ACLs = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])), + make_xhtml([?XC("h1", "ejabberd ACLs configuration")] ++ + case Res of + ok -> [?C("submited"), ?P]; + error -> [?C("bad format"), ?P]; + nothing -> [] + end ++ + [?XAE("form", [{"method", "post"}], + [?XAC("textarea", [{"name", "acls"}, + {"rows", "16"}, + {"cols", "80"}], + ACLs), + ?BR, + ?XA("input", [{"type", "submit"}]) + ]) + ]) + end; + +process_get(User, Path, Query) -> + {404, [], make_xhtml([?XC("h1", "Not found")])}. + + +