25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-02 15:27:09 +01:00

* 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 <text/> (thanks to
Sergei Golovan)

SVN Revision: 206
This commit is contained in:
Alexey Shchepin 2004-03-02 21:16:55 +00:00
parent 21c4b65610
commit ec182cbd60
13 changed files with 923 additions and 173 deletions

View File

@ -1,3 +1,28 @@
2004-03-02 Alexey Shchepin <alexey@sevcom.net>
* 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 <text/> (thanks to
Sergei Golovan)
2004-02-26 Alexey Shchepin <alexey@sevcom.net>
* src/msgs/ru.msg: Updated (thanks to Sergei Golovan)

View File

@ -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}.

View File

@ -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]}}.

View File

@ -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),

View File

@ -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,

View File

@ -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}

View File

@ -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) ->

View File

@ -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,

View File

@ -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).

View File

@ -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",

33
src/web/Makefile.in Normal file
View File

@ -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

401
src/web/ejabberd_http.erl Normal file
View File

@ -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(<