+2004-09-17 Alexey Shchepin <alexey@sevcom.net>
+
+ * src/mod_muc/mod_muc_room.erl: Send password in room invitation
+ (thanks to Sergei Golovan)
+
+ * src/mod_disco.erl: Added registration of sm features and nodes
+ (thanks to Sergei Golovan)
+ * src/mod_vcard.erl: Register vcard-temp feature (thanks to Sergei
+ Golovan)
+
+ * src/jlib.erl: Added functions now_to_utc_string/1,
+ now_to_local_string/1, and datetime_string_to_timestamp/1 (thanks
+ to Sergei Golovan)
+ * src/mod_muc/mod_muc_room.erl: Use time parsing functions from
+ jlib (thanks to Sergei Golovan)
+
+2004-09-16 Alexey Shchepin <alexey@sevcom.net>
+
+ * ejabberd/src/mod_pubsub/mod_pubsub.erl: Bugfix (thanks to
+ Mickael Remond)
+
+2004-09-15 Alexey Shchepin <alexey@sevcom.net>
+
+ * src/mod_pubsub/mod_pubsub.erl: Bugfix
+
2004-09-10 Alexey Shchepin <alexey@sevcom.net>
* tools/ejabberdctl: Added call to "exec" (thanks to Sergei
ejabberd_sm:set_presence(StateData#state.user,
StateData#state.resource,
Pri).
-
+
process_privacy_iq(From, To,
parse_xdata_submit/1,
timestamp_to_iso/1,
timestamp_to_xml/1,
+ now_to_utc_string/1,
+ now_to_local_string/1,
+ datetime_string_to_timestamp/1,
decode_base64/1,
encode_base64/1]).
[Year, Month, Day, Hour, Minute, Second]))}],
[]}.
+now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}),
+ lists:flatten(
+ io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0wZ",
+ [Year, Month, Day, Hour, Minute, Second, MicroSecs])).
+
+now_to_local_string({MegaSecs, Secs, MicroSecs}) ->
+ LocalTime = calendar:now_to_local_time({MegaSecs, Secs, MicroSecs}),
+ UTCTime = calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}),
+ Seconds = calendar:datetime_to_gregorian_seconds(LocalTime) -
+ calendar:datetime_to_gregorian_seconds(UTCTime),
+ {{H, M, _}, Sign} = if
+ Seconds < 0 ->
+ {calendar:seconds_to_time(-Seconds), "-"};
+ true ->
+ {calendar:seconds_to_time(Seconds), "+"}
+ end,
+ {{Year, Month, Day}, {Hour, Minute, Second}} = LocalTime,
+ lists:flatten(
+ io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6..0w~s~2..0w:~2..0w",
+ [Year, Month, Day, Hour, Minute, Second, MicroSecs, Sign, H, M])).
+
+
+% yyyy-mm-ddThh:mm:ss[.sss]{Z|{+|-}hh:mm} -> {MegaSecs, Secs, MicroSecs}
+datetime_string_to_timestamp(TimeStr) ->
+ case catch parse_datetime(TimeStr) of
+ {'EXIT', _Err} ->
+ undefined;
+ TimeStamp ->
+ TimeStamp
+ end.
+
+parse_datetime(TimeStr) ->
+ [Date, Time] = string:tokens(TimeStr, "T"),
+ D = parse_date(Date),
+ {T, MS, TZH, TZM} = parse_time(Time),
+ S = calendar:datetime_to_gregorian_seconds({D, T}),
+ S1 = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+ Seconds = (S - S1) - TZH * 60 * 60 - TZM * 60,
+ {Seconds div 1000000, Seconds rem 1000000, MS}.
+
+% yyyy-mm-dd
+parse_date(Date) ->
+ [Y, M, D] = string:tokens(Date, "-"),
+ Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
+ case calendar:valid_date(Date1) of
+ true ->
+ Date1;
+ _ ->
+ false
+ end.
+
+% hh:mm:ss[.sss]TZD
+parse_time(Time) ->
+ case string:str(Time, "Z") of
+ 0 ->
+ parse_time_with_timezone(Time);
+ _ ->
+ [T | _] = string:tokens(Time, "Z"),
+ {TT, MS} = parse_time1(T),
+ {TT, MS, 0, 0}
+ end.
+
+parse_time_with_timezone(Time) ->
+ case string:str(Time, "+") of
+ 0 ->
+ case string:str(Time, "-") of
+ 0 ->
+ false;
+ _ ->
+ parse_time_with_timezone(Time, "-")
+ end;
+ _ ->
+ parse_time_with_timezone(Time, "+")
+ end.
+
+parse_time_with_timezone(Time, Delim) ->
+ [T, TZ] = string:tokens(Time, Delim),
+ {TZH, TZM} = parse_timezone(TZ),
+ {TT, MS} = parse_time1(T),
+ case Delim of
+ "-" ->
+ {TT, MS, -TZH, -TZM};
+ "+" ->
+ {TT, MS, TZH, TZM}
+ end.
+
+parse_timezone(TZ) ->
+ [H, M] = string:tokens(TZ, ":"),
+ {[H1, M1], true} = check_list([{H, 12}, {M, 60}]),
+ {H1, M1}.
+
+parse_time1(Time) ->
+ [HMS | T] = string:tokens(Time, "."),
+ MS = case T of
+ [] ->
+ 0;
+ [Val] ->
+ list_to_integer(string:left(Val, 6, $0))
+ end,
+ [H, M, S] = string:tokens(HMS, ":"),
+ {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, {S, 60}]),
+ {{H1, M1, S1}, MS}.
+
+check_list(List) ->
+ lists:mapfoldl(
+ fun({L, N}, B)->
+ V = list_to_integer(L),
+ if
+ (V >= 0) and (V =< N) ->
+ {V, B};
+ true ->
+ {false, false}
+ end
+ end, true, List).
+
%
% Base64 stuff (based on httpd_util.erl)
register_feature/1,
unregister_feature/1,
register_extra_domain/1,
- unregister_extra_domain/1]).
+ unregister_extra_domain/1,
+ register_sm_feature/1,
+ unregister_sm_feature/1,
+ register_sm_node/4,
+ unregister_sm_node/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
ExtraDomains = gen_mod:get_opt(extra_domains, Opts, []),
lists:foreach(fun register_extra_domain/1, ExtraDomains),
+ catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
+ catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
ok.
stop() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-process_sm_iq_items(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
- #jid{user = User} = To,
- case {acl:match_rule(configure, From), Type} of
- {deny, _} ->
+register_sm_feature(Feature) ->
+ catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
+ ets:insert(disco_sm_features, {Feature}).
+
+unregister_sm_feature(Feature) ->
+ catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
+ ets:delete(disco_sm_features, Feature).
+
+register_sm_node(Node, Name, Module, Function) ->
+ catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
+ ets:insert(disco_sm_nodes, {Node, Name, Module, Function}).
+
+unregister_sm_node(Node) ->
+ catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
+ ets:delete(disco_sm_nodes, Node).
+
+process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
+ #jid{user = User, luser = LTo} = To,
+ #jid{luser = LFrom, lserver = LServer} = From,
+ Self = (LTo == LFrom) andalso (LServer == ?MYNAME),
+ Node = xml:get_tag_attr_s("node", SubEl),
+ case {acl:match_rule(configure, From), Type, Self, Node} of
+ {_, set, _, _} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- {allow, set} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- {allow, get} ->
- case xml:get_tag_attr_s("node", SubEl) of
- "" ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query",
- [{"xmlns", ?NS_DISCO_ITEMS}],
- get_user_resources(User)
- }]};
- _ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
- end
+ {_, get, true, []} ->
+ Nodes = lists:map(fun({Nod, Name, _, _}) ->
+ node_to_xml(User,
+ Nod,
+ translate:translate(Lang, Name))
+ end, ets:tab2list(disco_sm_nodes)),
+ IQ#iq{type = result,
+ sub_el = [{xmlelement, "query",
+ [{"xmlns", ?NS_DISCO_ITEMS}],
+ get_user_resources(User) ++ Nodes}]};
+ {allow, get, _, []} ->
+ Nodes = lists:map(fun({Nod, Name, _, _}) ->
+ node_to_xml(User,
+ Nod,
+ translate:translate(Lang, Name))
+ end, ets:tab2list(disco_sm_nodes)),
+ IQ#iq{type = result,
+ sub_el = [{xmlelement, "query",
+ [{"xmlns", ?NS_DISCO_ITEMS}],
+ get_user_resources(User) ++ Nodes}]};
+ {A, get, S, _} when (A == allow) or (S == true) ->
+ case ets:lookup(disco_sm_nodes, Node) of
+ [] ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
+ [{Node, _Name, Module, Function}] ->
+ case Module:Function(From, To, IQ) of
+ {error, Err} ->
+ IQ#iq{type = error, sub_el = [SubEl, Err]};
+ {result, Res} ->
+ IQ#iq{type = result,
+ sub_el = [{xmlelement, "query",
+ [{"xmlns", ?NS_DISCO_ITEMS},
+ {"node", Node}],
+ Res}]}
+ end
+ end;
+ {_, get, _, _} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
+ _ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end.
process_sm_iq_info(From, To, #iq{type = Type, xmlns = XMLNS,
sub_el = SubEl} = IQ) ->
- case {acl:match_rule(configure, From), Type} of
- {deny, _} ->
+ #jid{luser = LTo} = To,
+ #jid{luser = LFrom, lserver = LServer} = From,
+ Self = (LTo == LFrom) andalso (LServer == ?MYNAME),
+ Node = xml:get_tag_attr_s("node", SubEl),
+ case {acl:match_rule(configure, From), Type, Self, Node} of
+ {_, set, _, _} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- {allow, set} ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
- {allow, get} ->
- case xml:get_tag_attr_s("node", SubEl) of
- "" ->
- IQ#iq{type = result,
- sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}],
- [feature_to_xml({?NS_EJABBERD_CONFIG})]}]};
+ {allow, get, _, []} ->
+ Features = lists:map(fun feature_to_xml/1,
+ ets:tab2list(disco_sm_features)),
+ IQ#iq{type = result,
+ sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}],
+ [feature_to_xml({?NS_EJABBERD_CONFIG})] ++
+ Features}]};
+ {_, get, _, []} ->
+ Features = lists:map(fun feature_to_xml/1,
+ ets:tab2list(disco_sm_features)),
+ IQ#iq{type = result,
+ sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}],
+ Features}]};
+ {A, get, S, _} when (A == allow) or (S == true) ->
+ case ets:lookup(disco_sm_nodes, Node) of
+ [] ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
_ ->
- IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}
- end
+ IQ#iq{type = result, sub_el = [{xmlelement, "query",
+ [{"xmlns", XMLNS},
+ {"node", Node}], []}]}
+ end;
+ {_, get, _, _} ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
+ _ ->
+ IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end.
{"name", User}], []}
end, lists:sort(Rs)).
+node_to_xml(User, Node, Name) ->
+ {xmlelement, "item", [{"jid", User ++ "@" ++ ?MYNAME},
+ {"node", Node},
+ {"name", Name}], []}.
+
[{elem, "history"}, {attr, Type}]),
case Type of
"since" ->
- case catch parse_datetime(AttrVal) of
- {'EXIT', _Err} ->
+ case jlib:datetime_string_to_timestamp(AttrVal) of
+ undefined ->
false;
- Res ->
- Res
+ TS ->
+ calendar:now_to_universal_time(TS)
end;
_ ->
case catch list_to_integer(AttrVal) of
- {'EXIT', _} ->
- false;
- IntVal ->
- if
- IntVal >= 0 ->
- IntVal;
- true ->
- false
- end
+ IntVal when is_integer(IntVal) and IntVal >= 0 ->
+ IntVal;
+ _ ->
+ false
end
end;
_ ->
extract_history([_ | Els], Type) ->
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) ->
- [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, "-"),
- [Y, M, D] = lists:map(
- fun(L)->
- list_to_integer(L)
- end, YearMonthDay),
- {Y, M, D}.
-
-% hh:mm:ss[.sss]TZD
-parse_time(Time) ->
- case string:str(Time, "Z") of
- 0 ->
- parse_time_with_timezone(Time);
- _ ->
- [T | _] = string:tokens(Time, "Z"),
- {parse_time1(T), 0, 0}
- end.
-
-parse_time_with_timezone(Time) ->
- case string:str(Time, "+") of
- 0 ->
- case string:str(Time, "-") of
- 0 ->
- false;
- _ ->
- parse_time_with_timezone(Time, "-")
- end;
- _ ->
- parse_time_with_timezone(Time, "+")
- end.
-
-parse_time_with_timezone(Time, Delim) ->
- [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) ->
- [H, M] = string:tokens(TZ, ":"),
- {[H1, M1], true} = check_list([{H, 12}, {M, 60}]),
- {H1, M1}.
-
-parse_time1(Time) ->
- [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)->
- V = list_to_integer(L),
- if
- (V >= 0) and (V =< N) ->
- {V, B};
- true ->
- {false, false}
- end
- end, true, List).
send_update_presence(JID, StateData) ->
LJID = jlib:jid_tolower(JID),
jlib:jid_to_string(From)}],
[{xmlelement, "reason", [],
[{xmlcdata, Reason}]}]}],
- PasswdEl = [],
+ PasswdEl =
+ case (StateData#state.config)#config.password_protected of
+ true ->
+ [{xmlelement, "password", [],
+ [{xmlcdata, (StateData#state.config)#config.password}]}];
+ _ ->
+ []
+ end,
Msg =
{xmlelement, "message",
[{"type", "normal"}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
+%% Create new pubsub nodes
+%% This function is used during init to create the first bootstrap nodes
create_new_node(Host, Node, Owner) ->
case Node of
[] ->
(Subscription == false) ->
error;
true ->
- [{JID,
+ [{jlib:jid_tolower(JID),
#entity{
affiliation = Affiliation,
subscription = Subscription}} |
check_create_permission(Host, Node, Owner) ->
if
- #jid{lserver = Host} == Owner ->
+ Owner#jid.lserver == Host ->
true;
true ->
#jid{luser = User, lserver = Server} = Owner,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_VCARD,
?MODULE, process_sm_iq, IQDisc),
+ catch mod_disco:register_sm_feature(?NS_VCARD),
Host = gen_mod:get_opt(host, Opts, "vjud." ++ ?MYNAME),
Search = gen_mod:get_opt(search, Opts, true),
register(ejabberd_mod_vcard, spawn(?MODULE, init, [Host, Search])).
stop() ->
gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_VCARD),
+ catch mod_disco:unregister_sm_feature(?NS_VCARD),
ejabberd_mod_vcard ! stop,
ok.