summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_c2s.erl23
-rw-r--r--src/ejabberd_sup.erl9
-rw-r--r--src/jlib.hrl50
-rw-r--r--src/mod_irc/mod_irc.erl2
-rw-r--r--src/mod_muc/mod_muc.erl26
-rw-r--r--src/mod_muc/mod_muc_room.erl372
-rw-r--r--src/mod_pubsub/mod_pubsub.erl2
-rw-r--r--src/mod_vcard.erl8
-rw-r--r--src/msgs/ru.msg39
-rw-r--r--src/web/Makefile.in33
-rw-r--r--src/web/ejabberd_http.erl401
-rw-r--r--src/web/ejabberd_web.erl104
12 files changed, 897 insertions, 172 deletions
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 93ac7d41..b2bd9924 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 76dedc1e..0d4d1816 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 045207cb..29afc3dc 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 1bb71257..a7afd5f0 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 8fee7a29..551182b3 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 31293828..2aca20b1 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, ?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, ?ERR_NOT_AUTHORIZED),
+ 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 c56edf94..3acd0a76 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 c58d97e2..e56a4454 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 2bd4749f..09c2c06c 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 00000000..806bbed1
--- /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 00000000..cbae8b3b
--- /dev/null
+++ b/src/web/ejabberd_http.erl
@@ -0,0 +1,401 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_http.erl
+%%% Author : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose :
+%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id : $Id$
+%%%----------------------------------------------------------------------
+
+-module(ejabberd_http).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+%% 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,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\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 <klacke@hyber.org>
+% Any use or misuse of the source code is hereby freely allowed.
+%
+% 1. Redistributions of source code must retain the above copyright
+% notice as well as this list of conditions.
+%
+% 2. Redistributions in binary form must reproduce the above copyright
+% notice as well as this list of conditions.
+
+
+%% url decode the path and return {Path, QueryPart}
+
+url_decode_q_split(Path) ->
+ url_decode_q_split(Path, []).
+
+url_decode_q_split([$%, $C, $2, $%, Hi, Lo | Tail], Ack) ->
+ Hex = hex_to_integer([Hi, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi > $9 ->
+ Hex = hex_to_integer([Hi+4, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi < $A ->
+ Hex = hex_to_integer([Hi+4+7, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$%, Hi, Lo | Tail], Ack) ->
+ Hex = hex_to_integer([Hi, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$?|T], Ack) ->
+ %% Don't decode the query string here, that is parsed separately.
+ {path_norm_reverse(Ack), T};
+url_decode_q_split([H|T], Ack) ->
+ url_decode_q_split(T, [H|Ack]);
+url_decode_q_split([], Ack) ->
+ {path_norm_reverse(Ack), []}.
+
+path_norm_reverse("/" ++ T) -> start_dir(0, "/", T);
+path_norm_reverse( T) -> start_dir(0, "", T).
+
+start_dir(N, Path, ".." ) -> rest_dir(N, Path, "");
+start_dir(N, Path, "/" ++ T ) -> start_dir(N , Path, T);
+start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T);
+start_dir(N, Path, "../" ++ T ) -> start_dir(N + 1, Path, T);
+start_dir(N, Path, T ) -> rest_dir (N , Path, T).
+
+rest_dir (_N, Path, [] ) -> case Path of
+ [] -> "/";
+ _ -> Path
+ end;
+rest_dir (0, Path, [ $/ | T ] ) -> start_dir(0 , [ $/ | Path ], T);
+rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T);
+rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T);
+rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T).
+
+
+%% hex_to_integer
+
+
+hex_to_integer(Hex) ->
+ case catch erlang:list_to_integer(Hex, 16) of
+ {'EXIT', _} ->
+ old_hex_to_integer(Hex);
+ X ->
+ X
+ end.
+
+
+old_hex_to_integer(Hex) ->
+ DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10;
+ (H) when H >= $A, H =< $F -> H - $A + 10;
+ (H) when H >= $0, H =< $9 -> H - $0
+ end,
+ lists:foldl(fun(E, Acc) -> Acc*16+DEHEX(E) end, 0, Hex).
+
+code_to_phrase(100) -> "Continue";
+code_to_phrase(101) -> "Switching Protocols ";
+code_to_phrase(200) -> "OK";
+code_to_phrase(201) -> "Created";
+code_to_phrase(202) -> "Accepted";
+code_to_phrase(203) -> "Non-Authoritative Information";
+code_to_phrase(204) -> "No Content";
+code_to_phrase(205) -> "Reset Content";
+code_to_phrase(206) -> "Partial Content";
+code_to_phrase(300) -> "Multiple Choices";
+code_to_phrase(301) -> "Moved Permanently";
+code_to_phrase(302) -> "Found";
+code_to_phrase(303) -> "See Other";
+code_to_phrase(304) -> "Not Modified";
+code_to_phrase(305) -> "Use Proxy";
+code_to_phrase(306) -> "(Unused)";
+code_to_phrase(307) -> "Temporary Redirect";
+code_to_phrase(400) -> "Bad Request";
+code_to_phrase(401) -> "Unauthorized";
+code_to_phrase(402) -> "Payment Required";
+code_to_phrase(403) -> "Forbidden";
+code_to_phrase(404) -> "Not Found";
+code_to_phrase(405) -> "Method Not Allowed";
+code_to_phrase(406) -> "Not Acceptable";
+code_to_phrase(407) -> "Proxy Authentication Required";
+code_to_phrase(408) -> "Request Timeout";
+code_to_phrase(409) -> "Conflict";
+code_to_phrase(410) -> "Gone";
+code_to_phrase(411) -> "Length Required";
+code_to_phrase(412) -> "Precondition Failed";
+code_to_phrase(413) -> "Request Entity Too Large";
+code_to_phrase(414) -> "Request-URI Too Long";
+code_to_phrase(415) -> "Unsupported Media Type";
+code_to_phrase(416) -> "Requested Range Not Satisfiable";
+code_to_phrase(417) -> "Expectation Failed";
+code_to_phrase(500) -> "Internal Server Error";
+code_to_phrase(501) -> "Not Implemented";
+code_to_phrase(502) -> "Bad Gateway";
+code_to_phrase(503) -> "Service Unavailable";
+code_to_phrase(504) -> "Gateway Timeout";
+code_to_phrase(505) -> "HTTP Version Not Supported".
+
+
+parse_auth(Orig = "Basic " ++ Auth64) ->
+ case decode_base64(Auth64) of
+ {error, _Err} ->
+ undefined;
+ Auth ->
+ case string:tokens(Auth, ":") of
+ [User, Pass] ->
+ {User, Pass};
+ _ ->
+ undefined
+ end
+ end;
+parse_auth(_) ->
+ undefined.
+
+
+
+decode_base64([]) ->
+ [];
+decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
+ Bits2x6=
+ (d(Sextet1) bsl 18) bor
+ (d(Sextet2) bsl 12),
+ Octet1=Bits2x6 bsr 16,
+ [Octet1|decode_base64(Rest)];
+decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
+ Bits3x6=
+ (d(Sextet1) bsl 18) bor
+ (d(Sextet2) bsl 12) bor
+ (d(Sextet3) bsl 6),
+ Octet1=Bits3x6 bsr 16,
+ Octet2=(Bits3x6 bsr 8) band 16#ff,
+ [Octet1,Octet2|decode_base64(Rest)];
+decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
+ Bits4x6=
+ (d(Sextet1) bsl 18) bor
+ (d(Sextet2) bsl 12) bor
+ (d(Sextet3) bsl 6) bor
+ d(Sextet4),
+ Octet1=Bits4x6 bsr 16,
+ Octet2=(Bits4x6 bsr 8) band 16#ff,
+ Octet3=Bits4x6 band 16#ff,
+ [Octet1,Octet2,Octet3|decode_base64(Rest)];
+decode_base64(_CatchAll) ->
+ {error, bad_base64}.
+
+d(X) when X >= $A, X =<$Z ->
+ X-65;
+d(X) when X >= $a, X =<$z ->
+ X-71;
+d(X) when X >= $0, X =<$9 ->
+ X+4;
+d($+) -> 62;
+d($/) -> 63;
+d(_) -> 63.
+
+
+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 00000000..c8349439
--- /dev/null
+++ b/src/web/ejabberd_web.erl
@@ -0,0 +1,104 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_web.erl
+%%% Author : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose :
+%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
+%%% 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")])}.
+
+
+