diff --git a/ChangeLog b/ChangeLog index d55aae069..ae8dce874 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2004-02-15 Alexey Shchepin + + * src/mod_muc/mod_muc_room.erl: Support for history management + (thanks to Sergei Golovan) + + * src/mod_stats.erl: Updated error codes (thanks to Sergei + Golovan) + * src/mod_irc/mod_irc.erl: Likewise + + * src/mod_configure.erl: "jabber:iq:data" replaced with + "ejabber:config" namespace (thanks to Sergei Golovan) + * src/mod_disco.erl: Likewise + 2004-02-12 Alexey Shchepin * src/ejabberd_c2s.erl: Added to stream features diff --git a/src/mod_configure.erl b/src/mod_configure.erl index edf552023..fba0b31de 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -23,15 +23,15 @@ start(Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_IQDATA, + gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_EJABBERD_CONFIG, ?MODULE, process_local_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_IQDATA, + gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_EJABBERD_CONFIG, ?MODULE, process_sm_iq, IQDisc), ok. stop() -> - gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_IQDATA), - gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_IQDATA). + gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_EJABBERD_CONFIG), + gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_EJABBERD_CONFIG). process_local_iq(From, _To, #iq{id = ID, type = Type, @@ -43,38 +43,44 @@ process_local_iq(From, _To, #iq{id = ID, type = Type, Lang = xml:get_tag_attr_s("xml:lang", SubEl), case Type of set -> - case xml:get_tag_attr_s("type", SubEl) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(SubEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = - string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_form(Node, Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> + XDataEl = find_xdata_el(SubEl), + case XDataEl of + false -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; + {xmlelement, _Name, Attrs, SubEls} -> + case xml:get_attr_s("type", Attrs) of + "cancel" -> + IQ#iq{type = result, + sub_el = [{xmlelement, "query", + [{"xmlns", XMLNS}], []}]}; + "submit" -> + XData = jlib:parse_xdata_submit(XDataEl), + case XData of + invalid -> IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + _ -> + Node = + string:tokens( + xml:get_tag_attr_s("node", SubEl), + "/"), + case set_form(Node, Lang, XData) of + {result, Res} -> + IQ#iq{type = result, + sub_el = + [{xmlelement, "query", + [{"xmlns", XMLNS}], + Res + }]}; + {error, Error} -> + IQ#iq{type = error, + sub_el = [SubEl, Error]} + end + end; + _ -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end end; get -> Node = @@ -137,27 +143,28 @@ get_form(["running nodes", ENode, "DB"], Lang) -> {error, ?ERR_INTERNAL_SERVER_ERROR}; Tables -> STables = lists:sort(Tables), - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "DB Tables Configuration")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Choose storage type of tables")}]} | - lists:map( - fun(Table) -> - case rpc:call(Node, - mnesia, - table_info, - [Table, storage_type]) of - {badrpc, _} -> - ?TABLEFIELD(Table, unknown); - Type -> - ?TABLEFIELD(Table, Type) - end - end, STables) - ]} + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "DB Tables Configuration")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Choose storage type of tables")}]} | + lists:map( + fun(Table) -> + case rpc:call(Node, + mnesia, + table_info, + [Table, storage_type]) of + {badrpc, _} -> + ?TABLEFIELD(Table, unknown); + Type -> + ?TABLEFIELD(Table, Type) + end + end, STables) + ]}]} end end; @@ -171,217 +178,228 @@ get_form(["running nodes", ENode, "modules", "stop"], Lang) -> {error, ?ERR_INTERNAL_SERVER_ERROR}; Modules -> SModules = lists:sort(Modules), - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Stop Modules")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Choose modules to stop")}]} | - lists:map(fun(M) -> - S = atom_to_list(M), - ?XFIELD("boolean", S, S, "0") - end, SModules) - ]} + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Stop Modules")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Choose modules to stop")}]} | + lists:map(fun(M) -> + S = atom_to_list(M), + ?XFIELD("boolean", S, S, "0") + end, SModules) + ]}]} end end; get_form(["running nodes", ENode, "modules", "start"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Start Modules")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter list of {Module, [Options]}")}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - translate:translate( - Lang, "List of modules to start")}, - {"var", "modules"}], - [{xmlelement, "value", [], [{xmlcdata, "[]."}]}] - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Start Modules")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Enter list of {Module, [Options]}")}]}, + {xmlelement, "field", [{"type", "text-multi"}, + {"label", + translate:translate( + Lang, "List of modules to start")}, + {"var", "modules"}], + [{xmlelement, "value", [], [{xmlcdata, "[]."}]}] + } + ]}]}; get_form(["running nodes", ENode, "backup", "backup"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Backup to File")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter path to backup file")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "Path to File")}, - {"var", "path"}], - [{xmlelement, "value", [], [{xmlcdata, ""}]}] - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Backup to File")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Enter path to backup file")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate( + Lang, "Path to File")}, + {"var", "path"}], + [{xmlelement, "value", [], [{xmlcdata, ""}]}] + } + ]}]}; get_form(["running nodes", ENode, "backup", "restore"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Restore Backup from File")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter path to backup file")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "Path to File")}, - {"var", "path"}], - [{xmlelement, "value", [], [{xmlcdata, ""}]}] - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Restore Backup from File")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Enter path to backup file")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate( + Lang, "Path to File")}, + {"var", "path"}], + [{xmlelement, "value", [], [{xmlcdata, ""}]}] + } + ]}]}; get_form(["running nodes", ENode, "backup", "textfile"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Dump Backup to Text File")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter path to text file")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "Path to File")}, - {"var", "path"}], - [{xmlelement, "value", [], [{xmlcdata, ""}]}] - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Dump Backup to Text File")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Enter path to text file")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate( + Lang, "Path to File")}, + {"var", "path"}], + [{xmlelement, "value", [], [{xmlcdata, ""}]}] + } + ]}]}; get_form(["running nodes", ENode, "import", "file"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Import User from File")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter path to jabberd1.4 spool file")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "Path to File")}, - {"var", "path"}], - [{xmlelement, "value", [], [{xmlcdata, ""}]}] - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Import User from File")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Enter path to jabberd1.4 spool file")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate( + Lang, "Path to File")}, + {"var", "path"}], + [{xmlelement, "value", [], [{xmlcdata, ""}]}] + } + ]}]}; get_form(["running nodes", ENode, "import", "dir"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Import User from Dir")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter path to jabberd1.4 spool dir")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate( - Lang, "Path to Dir")}, - {"var", "path"}], - [{xmlelement, "value", [], [{xmlcdata, ""}]}] - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Import User from Dir")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Enter path to jabberd1.4 spool dir")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate( + Lang, "Path to Dir")}, + {"var", "path"}], + [{xmlelement, "value", [], [{xmlcdata, ""}]}] + } + ]}]}; get_form(["config", "hostname"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Hostname Configuration")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Choose host name")}]}, - {xmlelement, "field", [{"type", "text-single"}, - {"label", - translate:translate(Lang, "Host name")}, - {"var", "hostname"}], - [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]} - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Hostname Configuration")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Choose host name")}]}, + {xmlelement, "field", [{"type", "text-single"}, + {"label", + translate:translate(Lang, "Host name")}, + {"var", "hostname"}], + [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]} + ]}]}; get_form(["config", "acls"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "ACLs Configuration")}]}, - %{xmlelement, "instructions", [], - % [{xmlcdata, - % translate:translate( - % Lang, "")}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - translate:translate(Lang, "ACLs")}, - {"var", "acls"}], - lists:map(fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten(io_lib:format("~p.", - [ets:tab2list(acl)])), - "\n")) - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "ACLs Configuration")}]}, + %{xmlelement, "instructions", [], + % [{xmlcdata, + % translate:translate( + % Lang, "")}]}, + {xmlelement, "field", [{"type", "text-multi"}, + {"label", + translate:translate(Lang, "ACLs")}, + {"var", "acls"}], + lists:map(fun(S) -> + {xmlelement, "value", [], [{xmlcdata, S}]} + end, + string:tokens( + lists:flatten(io_lib:format("~p.", + [ets:tab2list(acl)])), + "\n")) + } + ]}]}; get_form(["config", "access"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Access Configuration")}]}, - %{xmlelement, "instructions", [], - % [{xmlcdata, - % translate:translate( - % Lang, "")}]}, - {xmlelement, "field", [{"type", "text-multi"}, - {"label", - translate:translate( - Lang, "Access Rules")}, - {"var", "access"}], - lists:map(fun(S) -> - {xmlelement, "value", [], [{xmlcdata, S}]} - end, - string:tokens( - lists:flatten( - io_lib:format( - "~p.", - [ets:select(config, - [{{config, {access, '$1'}, '$2'}, - [], - [{{access, '$1', '$2'}}]}]) - ])), - "\n")) - } - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Access Configuration")}]}, + %{xmlelement, "instructions", [], + % [{xmlcdata, + % translate:translate( + % Lang, "")}]}, + {xmlelement, "field", [{"type", "text-multi"}, + {"label", + translate:translate( + Lang, "Access Rules")}, + {"var", "access"}], + lists:map(fun(S) -> + {xmlelement, "value", [], [{xmlcdata, S}]} + end, + string:tokens( + lists:flatten( + io_lib:format( + "~p.", + [ets:select(config, + [{{config, {access, '$1'}, '$2'}, + [], + [{{access, '$1', '$2'}}]}]) + ])), + "\n")) + } + ]}]}; get_form(["config", "remusers"], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Remove Users")}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Choose users to remove")}]}] ++ - case catch ejabberd_auth:dirty_get_registered_users() of - {'EXIT', Reason} -> - []; - Users -> - lists:map(fun(U) -> - ?XFIELD("boolean", U, U, "0") - end, lists:sort(Users)) - end - }; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Remove Users")}]}, + {xmlelement, "instructions", [], + [{xmlcdata, + translate:translate( + Lang, "Choose users to remove")}]}] ++ + case catch ejabberd_auth:dirty_get_registered_users() of + {'EXIT', Reason} -> + []; + Users -> + lists:map(fun(U) -> + ?XFIELD("boolean", U, U, "0") + end, lists:sort(Users)) + end + }]}; get_form(_, Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. @@ -712,39 +730,45 @@ process_sm_iq(From, To, Lang = xml:get_tag_attr_s("xml:lang", SubEl), case Type of set -> - case xml:get_tag_attr_s("type", SubEl) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(SubEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = - string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_sm_form( - User, Node, Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> + XDataEl = find_xdata_el(SubEl), + case XDataEl of + false -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; + {xmlelement, _Name, Attrs, SubEls} -> + case xml:get_attr_s("type", Attrs) of + "cancel" -> + IQ#iq{type = result, + sub_el = [{xmlelement, "query", + [{"xmlns", XMLNS}], []}]}; + "submit" -> + XData = jlib:parse_xdata_submit(XDataEl), + case XData of + invalid -> IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_NOT_ALLOWED]} + sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + _ -> + Node = + string:tokens( + xml:get_tag_attr_s("node", SubEl), + "/"), + case set_sm_form( + User, Node, Lang, XData) of + {result, Res} -> + IQ#iq{type = result, + sub_el = + [{xmlelement, "query", + [{"xmlns", XMLNS}], + Res + }]}; + {error, Error} -> + IQ#iq{type = error, + sub_el = [SubEl, Error]} + end + end; + _ -> + IQ#iq{type = error, + sub_el = [SubEl, ?ERR_BAD_REQUEST]} + end end; get -> Node = @@ -764,34 +788,35 @@ process_sm_iq(From, To, get_sm_form(User, [], Lang) -> - {result, [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Administration of " ++ User)}]}, - %{xmlelement, "instructions", [], - % [{xmlcdata, - % translate:translate( - % Lang, "Choose host name")}]}, - {xmlelement, "field", - [{"type", "list-single"}, - {"label", translate:translate(Lang, "Action on user")}, - {"var", "action"}], - [{xmlelement, "value", [], [{xmlcdata, "edit"}]}, - {xmlelement, "option", - [{"label", translate:translate(Lang, "Edit Properties")}], - [{xmlelement, "value", [], [{xmlcdata, "edit"}]}]}, - {xmlelement, "option", - [{"label", translate:translate(Lang, "Remove User")}], - [{xmlelement, "value", [], [{xmlcdata, "remove"}]}]} - ]}, - ?XFIELD("text-private", "Password", "password", - ejabberd_auth:get_password_s(User)) - %{xmlelement, "field", [{"type", "text-single"}, - % {"label", - % translate:translate(Lang, "Host name")}, - % {"var", "hostname"}], - % [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]} - ]}; + {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], + [{xmlelement, "title", [], + [{xmlcdata, + translate:translate( + Lang, "Administration of " ++ User)}]}, + %{xmlelement, "instructions", [], + % [{xmlcdata, + % translate:translate( + % Lang, "Choose host name")}]}, + {xmlelement, "field", + [{"type", "list-single"}, + {"label", translate:translate(Lang, "Action on user")}, + {"var", "action"}], + [{xmlelement, "value", [], [{xmlcdata, "edit"}]}, + {xmlelement, "option", + [{"label", translate:translate(Lang, "Edit Properties")}], + [{xmlelement, "value", [], [{xmlcdata, "edit"}]}]}, + {xmlelement, "option", + [{"label", translate:translate(Lang, "Remove User")}], + [{xmlelement, "value", [], [{xmlcdata, "remove"}]}]} + ]}, + ?XFIELD("text-private", "Password", "password", + ejabberd_auth:get_password_s(User)) + %{xmlelement, "field", [{"type", "text-single"}, + % {"label", + % translate:translate(Lang, "Host name")}, + % {"var", "hostname"}], + % [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]} + ]}]}; get_sm_form(_, _, Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. @@ -820,3 +845,20 @@ set_sm_form(User, [], Lang, XData) -> end; set_sm_form(_, _, Lang, XData) -> {error, ?ERR_SERVICE_UNAVAILABLE}. + +find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> + find_xdata_el1(SubEls). + +find_xdata_el1([]) -> + false; + +find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) -> + case xml:get_attr_s("xmlns", Attrs) of + ?NS_XDATA -> + {xmlelement, Name, Attrs, SubEls}; + _ -> + find_xdata_el1(Els) + end; + +find_xdata_el1([_ | Els]) -> + find_xdata_el1(Els). diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 1c7f8d0b9..0dbf759e8 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -157,7 +157,7 @@ process_local_iq_info(From, _To, #iq{type = Type, xmlns = XMLNS, "query", [{"xmlns", XMLNS}, {"node", SNode}], - [feature_to_xml({?NS_IQDATA})]}]}; + [feature_to_xml({?NS_EJABBERD_CONFIG})]}]}; {allow, ["running nodes", ENode, "modules"]} -> ?EMPTY_INFO_RESULT; {allow, ["running nodes", ENode, "modules", _]} -> @@ -165,7 +165,7 @@ process_local_iq_info(From, _To, #iq{type = Type, xmlns = XMLNS, sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}, {"node", SNode}], - [feature_to_xml({?NS_IQDATA})]}]}; + [feature_to_xml({?NS_EJABBERD_CONFIG})]}]}; {allow, ["running nodes", ENode, "backup"]} -> ?EMPTY_INFO_RESULT; {allow, ["running nodes", ENode, "backup", _]} -> @@ -173,7 +173,7 @@ process_local_iq_info(From, _To, #iq{type = Type, xmlns = XMLNS, sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}, {"node", SNode}], - [feature_to_xml({?NS_IQDATA})]}]}; + [feature_to_xml({?NS_EJABBERD_CONFIG})]}]}; {allow, ["running nodes", ENode, "import"]} -> ?EMPTY_INFO_RESULT; {allow, ["running nodes", ENode, "import", _]} -> @@ -181,13 +181,13 @@ process_local_iq_info(From, _To, #iq{type = Type, xmlns = XMLNS, sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}, {"node", SNode}], - [feature_to_xml({?NS_IQDATA})]}]}; + [feature_to_xml({?NS_EJABBERD_CONFIG})]}]}; {allow, ["config", _]} -> IQ#iq{type = result, sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}, {"node", SNode}], - [feature_to_xml({?NS_IQDATA})]}]}; + [feature_to_xml({?NS_EJABBERD_CONFIG})]}]}; _ -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]} end @@ -489,7 +489,7 @@ process_sm_iq_info(From, To, #iq{type = Type, xmlns = XMLNS, "" -> IQ#iq{type = result, sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}], - [feature_to_xml({?NS_IQDATA})]}]}; + [feature_to_xml({?NS_EJABBERD_CONFIG})]}]}; _ -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]} end diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index 8a5a35672..cd8616b8e 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -358,16 +358,16 @@ set_form(From, [], Lang, XData) -> {atomic, _} -> {result, []}; _ -> - {error, "406", "Not Acceptable"} + {error, ?ERR_NOT_ACCEPTABLE} end; _ -> - {error, "406", "Not Acceptable"} + {error, ?ERR_NOT_ACCEPTABLE} end; _ -> - {error, "406", "Not Acceptable"} + {error, ?ERR_NOT_ACCEPTABLE} end; _ -> - {error, "406", "Not Acceptable"} + {error, ?ERR_NOT_ACCEPTABLE} end; diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index f62880c4b..7f7b89574 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -63,7 +63,7 @@ config = #config{}, users = ?DICT:new(), affiliations = ?DICT:new(), - history = lqueue_new(10), + history = lqueue_new(20), subject = "", subject_author = ""}). @@ -831,7 +831,8 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> add_online_user(From, Nick, Role, StateData)), send_new_presence(From, NewState), send_existing_presences(From, NewState), - case send_history(From, NewState) of + Shift = count_stanza_shift(Nick, Els, NewState), + case send_history(From, Shift, NewState) of true -> ok; _ -> @@ -879,7 +880,259 @@ extract_password([{xmlelement, Name, Attrs, SubEls} = El | Els]) -> extract_password([_ | Els]) -> extract_password(Els). +count_stanza_shift(Nick, Els, StateData) -> + HL = lqueue_to_list(StateData#state.history), + Since = extract_history(Els, "since"), + Shift0 = case Since of + false -> + 0; + _ -> + Sin = calendar:datetime_to_gregorian_seconds(Since), + count_seconds_shift(Sin, HL) + end, + Seconds = extract_history(Els, "seconds"), + Shift1 = case Seconds of + false -> + 0; + _ -> + Sec = calendar:datetime_to_gregorian_seconds( + calendar:now_to_universal_time(now())) - Seconds, + count_seconds_shift(Sec, HL) + end, + MaxStanzas = extract_history(Els, "maxstanzas"), + Shift2 = case MaxStanzas of + false -> + 0; + _ -> + count_maxstanzas_shift(MaxStanzas, HL) + end, + MaxChars = extract_history(Els, "maxchars"), + Shift3 = case MaxChars of + false -> + 0; + _ -> + count_maxchars_shift(Nick, MaxChars, HL) + end, + lists:max([Shift0, Shift1, Shift2, Shift3]). +count_seconds_shift(Seconds, HistoryList) -> + lists:sum( + lists:map( + fun({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) -> + T = calendar:datetime_to_gregorian_seconds(TimeStamp), + if + T < Seconds -> + 1; + true -> + 0 + end + end, HistoryList)). + +count_maxstanzas_shift(MaxStanzas, HistoryList) -> + S = length(HistoryList) - MaxStanzas, + if + S =< 0 -> + 0; + true -> + S + end. + +count_maxchars_shift(Nick, MaxSize, HistoryList) -> + NLen = string:len(Nick) + 1, + Sizes = lists:map( + fun({_Nick, _Packet, _HaveSubject, _TimeStamp, Size}) -> + Size + NLen + end, HistoryList), + calc_shift(MaxSize, Sizes). + +calc_shift(MaxSize, Sizes) -> + Total = lists:sum(Sizes), + calc_shift(MaxSize, Total, 0, Sizes). + +calc_shift(_MaxSize, _Size, Shift, []) -> + Shift; +calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> + if + MaxSize >= Size -> + Shift; + true -> + calc_shift(MaxSize, Size - S, Shift + 1, TSizes) + end. + +extract_history([], Type) -> + false; +extract_history([{xmlelement, Name, Attrs, SubEls} = El | Els], Type) -> + case xml:get_attr_s("xmlns", Attrs) of + ?NS_MUC -> + AttrVal = xml:get_path_s(El, + [{elem, "history"}, {attr, Type}]), + case Type of + "since" -> + parse_datetime(AttrVal); + _ -> + case catch list_to_integer(AttrVal) of + {'EXIT', _} -> + false; + IntVal -> + if + IntVal >= 0 -> + IntVal; + true -> + false + end + end + end; + _ -> + extract_history(Els, Type) + 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) -> + 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. + +% 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. + +% 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"), + case parse_time1(T) of + false -> + false; + TT -> + {TT, 0, 0} + end + 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) -> + 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 + 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. + +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. + +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}; + _ -> + {false, false} + end + end, true, List). send_update_presence(JID, StateData) -> LJID = jlib:jid_tolower(JID), @@ -1087,22 +1340,28 @@ add_message_to_history(FromNick, Packet, StateData) -> _ -> true end, + TimeStamp = calendar:now_to_universal_time(now()), TSPacket = append_subtags(Packet, - [jlib:timestamp_to_xml( - calendar:now_to_universal_time( - now()))]), - Q1 = lqueue_in({FromNick, TSPacket, HaveSubject}, StateData#state.history), + [jlib:timestamp_to_xml(TimeStamp)]), + {xmlelement, Name, Attrs, Els} = TSPacket, + SPacket = jlib:replace_from_to( + jlib:jid_replace_resource(StateData#state.jid, FromNick), + StateData#state.jid, + TSPacket), + Size = string:len(xml:element_to_string(SPacket)), + Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size}, + StateData#state.history), StateData#state{history = Q1}. -send_history(JID, StateData) -> +send_history(JID, Shift, StateData) -> lists:foldl( - fun({Nick, Packet, HaveSubject}, B) -> + fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) -> ejabberd_router:route( jlib:jid_replace_resource(StateData#state.jid, Nick), JID, Packet), B or HaveSubject - end, false, lqueue_to_list(StateData#state.history)). + end, false, lists:nthtail(Shift, lqueue_to_list(StateData#state.history))). send_subject(JID, StateData) -> diff --git a/src/mod_stats.erl b/src/mod_stats.erl index ab76021f6..629e92e7b 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -89,7 +89,7 @@ get_local_stats(["running nodes", _], []) -> get_local_stats(["running nodes", ENode], Names) -> case search_running_node(ENode) of false -> - {error, "404", "Not Found"}; + {error, ?ERR_ITEM_NOT_FOUND}; Node -> {result, lists:map(fun(Name) -> get_node_stat(Node, Name) end, Names)}