diff --git a/ChangeLog b/ChangeLog index c1db8a3f6..b4537022c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,18 @@ +2008-11-04 Pablo Polvorin + + * src/mod_muc/mod_muc_log.erl: Convert to exmpp, fix recursive + directory creation. + + * src/mod_muc/mod_muc_room.erl: Default values as binary() instead of + list(). + 2008-11-03 Pablo Polvorin * src/ejabberd_app.erl: Start the exmpp application. + * src/mod_muc/mod_muc.erl, src/mod_muc/mod_muc_room.erl: Convert to + exmpp. + 2008-10-31 Jean-Sébastien Pédron * src/mod_last_odbc.erl (store_last_info/4): Fix a bug where the diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index 812c8de97..e83bedeba 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -45,8 +45,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-include_lib("exmpp/include/exmpp.hrl"). + -include("ejabberd.hrl"). --include("jlib.hrl"). -record(muc_room, {name_host, opts}). @@ -123,20 +124,19 @@ forget_room(Host, Name) -> end, mnesia:transaction(F). -process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_DISCO_ITEMS}], - iq_disco_items(Host, From, Lang)}]}, +process_iq_disco_items(Host, From, To, #iq{} = IQ) -> + Lang = exmpp_stanza:get_lang(IQ), + Res = exmpp_iq:result(IQ, #xmlel{ns = ?NS_DISCO_ITEMS, + name = 'query', + children = iq_disco_items(Host, From, Lang)}), ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)). + exmpp_iq:iq_to_xmlel(Res)). can_use_nick(_Host, _JID, "") -> false; can_use_nick(Host, JID, Nick) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), - LUS = {LUser, LServer}, + LUS = {JID#jid.lnode, JID#jid.ldomain}, case catch mnesia:dirty_select( muc_registered, [{#muc_registered{us_host = '$1', @@ -300,11 +300,11 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper, do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts); _ -> - {xmlelement, _Name, Attrs, _Els} = Packet, - Lang = xml:get_attr_s("xml:lang", Attrs), - ErrText = "Access denied by service policy", - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), + Lang = exmpp_stanza:get_lang(Packet), + ErrText = "Access denied by service policy", + Err = exmpp_iq:error(Packet,exmpp_stanza:error(Packet#xmlel.ns, + 'forbidden', + {Lang,ErrText})), ejabberd_router:route(To, From, Err) end. @@ -312,124 +312,109 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper, do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) -> {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access, - {Room, _, Nick} = jlib:jid_tolower(To), - {xmlelement, Name, Attrs, _Els} = Packet, + Room = To#jid.lnode, + Nick = To#jid.lresource, + #xmlel{name = Name} = Packet, case Room of - "" -> + 'undefined' -> case Nick of - "" -> + 'undefined' -> case Name of - "iq" -> - case jlib:iq_query_info(Packet) of - #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, - sub_el = _SubEl, lang = Lang} = IQ -> - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - iq_disco_info(Lang)}]}, + 'iq' -> + case exmpp_iq:xmlel_to_iq(Packet) of + #iq{type = get, ns = ?NS_DISCO_INFO = XMLNS, + payload = _SubEl, lang = Lang} = IQ -> + + ResPayload = #xmlel{ns = XMLNS, name = 'query', + children = iq_disco_info(Lang)}, + Res = exmpp_iq:result(IQ, ResPayload), ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); + exmpp_iq:iq_to_xmlel(Res)); #iq{type = get, - xmlns = ?NS_DISCO_ITEMS} = IQ -> + ns = ?NS_DISCO_ITEMS} = IQ -> spawn(?MODULE, process_iq_disco_items, [Host, From, To, IQ]); #iq{type = get, - xmlns = ?NS_REGISTER = XMLNS, - lang = Lang, - sub_el = _SubEl} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - iq_get_register_info( - Host, From, Lang)}]}, + ns = ?NS_INBAND_REGISTER = XMLNS, + lang = Lang} = IQ -> + ResPayload = #xmlel{ns = XMLNS, name = 'query', + children = iq_get_register_info(Host, + From, + Lang)}, + Res = exmpp_iq:result(IQ,ResPayload), ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); + exmpp_iq:iq_to_xmlel(Res)); #iq{type = set, - xmlns = ?NS_REGISTER = XMLNS, + ns = ?NS_INBAND_REGISTER , lang = Lang, - sub_el = SubEl} = IQ -> + payload = SubEl} = IQ -> case process_iq_register_set(Host, From, SubEl, Lang) of - {result, IQRes} -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - IQRes}]}, + ok -> + Res = exmpp_iq:result(IQ), ejabberd_router:route( - To, From, jlib:iq_to_xml(Res)); + To, From, exmpp_iq:iq_to_xmlel(Res)); {error, Error} -> - Err = jlib:make_error_reply( - Packet, Error), + Err = exmpp_iq:error(IQ,Error), ejabberd_router:route( - To, From, Err) + To, From, exmpp_iq:iq_to_xmlel(Err)) end; #iq{type = get, - xmlns = ?NS_VCARD = XMLNS, - lang = Lang, - sub_el = _SubEl} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [{xmlelement, "vCard", - [{"xmlns", XMLNS}], - iq_get_vcard(Lang)}]}, + ns = ?NS_VCARD, + lang = Lang} = IQ -> + Res = exmpp_iq:result(IQ,iq_get_vcard(Lang)), ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{} -> - Err = jlib:make_error_reply( - Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), + exmpp_iq:iq_to_xmlel(Res)); + #iq{} = IQ -> + Err = exmpp_iq:error(IQ,'feature-not-implemented'), ejabberd_router:route(To, From, Err); _ -> ok end; - "message" -> - case xml:get_attr_s("type", Attrs) of + 'message' -> + case exmpp_xml:get_attribute(Packet,type, "chat") of "error" -> ok; _ -> case acl:match_rule(ServerHost, AccessAdmin, From) of allow -> - Msg = xml:get_path_s( - Packet, - [{elem, "body"}, cdata]), + Msg = exmpp_xml:get_path(Packet, + [{element,'body'},cdata]), broadcast_service_message(Host, Msg); _ -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), ErrText = "Only service administrators " "are allowed to send service messages", - Err = jlib:make_error_reply( - Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), + Err = exmpp_iq:error(Packet,exmpp_stanza:error(Packet#xmlel.ns, + 'forbidden', + {Lang,ErrText})), ejabberd_router:route( To, From, Err) end end; - "presence" -> + 'presence' -> ok end; _ -> - case xml:get_attr_s("type", Attrs) of + case exmpp_stanza:get_type(Packet) of "error" -> ok; "result" -> ok; _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_ITEM_NOT_FOUND), + Err = exmpp_iq:error(Packet,'item-not-found'), ejabberd_router:route(To, From, Err) end end; _ -> case mnesia:dirty_read(muc_online_room, {Room, Host}) of [] -> - Type = xml:get_attr_s("type", Attrs), + Type = exmpp_stanza:get_type(Packet), case {Name, Type} of - {"presence", ""} -> + {'presence', 'undefined'} -> case acl:match_rule(ServerHost, AccessCreate, From) of allow -> ?DEBUG("MUC: open new room '~s'~n", [Room]), @@ -442,17 +427,20 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, mod_muc_room:route(Pid, From, Nick, Packet), ok; _ -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), ErrText = "Room creation is denied by service policy", - Err = jlib:make_error_reply( - Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet,exmpp_stanza:error(Packet#xmlel.ns, + 'forbidden', + {Lang,ErrText})), ejabberd_router:route(To, From, Err) end; _ -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), ErrText = "Conference room does not exist", - Err = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, + 'item-not-found', + {Lang,ErrText})), ejabberd_router:route(To, From, Err) end; [R] -> @@ -504,13 +492,23 @@ register_room(Host, Room, Pid) -> iq_disco_info(Lang) -> - [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "text"}, - {"name", translate:translate(Lang, "Chatrooms")}], []}, - {xmlelement, "feature", [{"var", ?NS_MUC}], []}, - {xmlelement, "feature", [{"var", ?NS_REGISTER}], []}, - {xmlelement, "feature", [{"var", ?NS_VCARD}], []}]. + [#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', + attrs = [#xmlattr{name = 'category', + value = "conference"}, + #xmlattr{name = 'type', + value = "text"}, + #xmlattr{name = 'name', + value = translate:translate(Lang, "Chatrooms")}]}, + #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = + [#xmlattr{name = 'var', + value = ?NS_MUC_s}]}, + #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = + [#xmlattr{name = 'var', + value = ?NS_INBAND_REGISTER_s}]}, + #xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = + [#xmlattr{name = 'var', + value = ?NS_VCARD_s}]}]. + iq_disco_items(Host, From, Lang) -> @@ -520,9 +518,13 @@ iq_disco_items(Host, From, Lang) -> {item, Desc} -> flush(), {true, - {xmlelement, "item", - [{"jid", jlib:jid_to_string({Name, Host, ""})}, - {"name", Desc}], []}}; + #xmlel{name = 'item', + attrs = [#xmlattr{name = 'jid', + value = exmpp_jid:jid_to_list(Name, + Host, + "")}, + #xmlattr{name = 'name', + value = Desc}]}}; _ -> false end @@ -537,13 +539,17 @@ flush() -> end. -define(XFIELD(Type, Label, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = "field", + attrs = [#xmlattr{name = 'type', value = Type}, + #xmlattr{name = 'label', + value = translate:translate(Lang, Label)}, + #xmlattr{name = 'var', value = Var}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = Val}]}]}). iq_get_register_info(Host, From, Lang) -> - {LUser, LServer, _} = jlib:jid_tolower(From), + LUser = From#jid.lnode, + LServer = From#jid.ldomain, LUS = {LUser, LServer}, {Nick, Registered} = case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of @@ -552,27 +558,28 @@ iq_get_register_info(Host, From, Lang) -> [] -> {"", []}; [#muc_registered{nick = N}] -> - {N, [{xmlelement, "registered", [], []}]} + {N, [#xmlel{name = 'registered'}]} end, Registered ++ - [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "You need an x:data capable client to register nickname")}]}, - {xmlelement, "x", - [{"xmlns", ?NS_XDATA}], - [{xmlelement, "title", [], - [{xmlcdata, - translate:translate( - Lang, "Nickname Registration at ") ++ Host}]}, - {xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "Enter nickname you want to register")}]}, + [#xmlel{name = 'instructions' , + children = [#xmlcdata{cdata = + translate:translate(Lang, + "You need an x:data capable client to register nickname")}]}, + #xmlel{ns = ?NS_DATA_FORMS, name = 'x', + children = [ + #xmlel{ns = ?NS_DATA_FORMS, name = 'title', + children = [#xmlcdata{cdata = + translate:translate(Lang, "Nickname Registration at ") ++ Host}]}, + #xmlel{ns = ?NS_DATA_FORMS, name = 'instructions', + children = [#xmlcdata{cdata = + translate:translate(Lang, "Enter nickname you want to register")}]}, ?XFIELD("text-single", "Nickname", "nick", Nick)]}]. + + iq_set_register_info(Host, From, Nick, Lang) -> - {LUser, LServer, _} = jlib:jid_tolower(From), + LUser = From#jid.lnode, + LServer = From#jid.ldomain, LUS = {LUser, LServer}, F = fun() -> case Nick of @@ -606,56 +613,64 @@ iq_set_register_info(Host, From, Nick, Lang) -> end, case mnesia:transaction(F) of {atomic, ok} -> - {result, []}; + ok; {atomic, false} -> ErrText = "Specified nickname is already registered", - {error, ?ERRT_CONFLICT(Lang, ErrText)}; + %%TODO: Always in the jabber:client namespace? + {error,exmpp_stanza:error(?NS_JABBER_CLIENT, + 'conflict', + {Lang, ErrText})}; _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} + {error, 'internal-server-error'} end. process_iq_register_set(Host, From, SubEl, Lang) -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - case xml:get_subtag(SubEl, "remove") of - false -> - case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> - case {xml:get_tag_attr_s("xmlns", XEl), - xml:get_tag_attr_s("type", XEl)} of - {?NS_XDATA, "cancel"} -> - {result, []}; - {?NS_XDATA, "submit"} -> +% {xmlelement, _Name, _Attrs, Els} = SubEl, + case exmpp_xml:get_element(SubEl,'remove') of + undefined -> + case exmpp_xml:get_child_elements(SubEl) of + [#xmlel{ns= NS, name = 'x'} = XEl] -> + case {NS, exmpp_stanza:get_type(XEl)} of + {?NS_DATA_FORMS, "cancel"} -> + ok; + {?NS_DATA_FORMS, "submit"} -> XData = jlib:parse_xdata_submit(XEl), case XData of invalid -> - {error, ?ERR_BAD_REQUEST}; + {error, 'bad-request'}; _ -> case lists:keysearch("nick", 1, XData) of false -> ErrText = "You must fill in field \"Nickname\" in the form", - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + Err = exmpp_stanza:error(SubEl#xmlel.ns, + 'not-acceptable', + {Lang, translate:translate(Lang,ErrText)}), + {error, Err}; {value, {_, [Nick]}} -> iq_set_register_info(Host, From, Nick, Lang) end end; _ -> - {error, ?ERR_BAD_REQUEST} + {error, 'bad-request'} end; _ -> - {error, ?ERR_BAD_REQUEST} + {error, 'bad-request'} end; _ -> iq_set_register_info(Host, From, "", Lang) end. iq_get_vcard(Lang) -> - [{xmlelement, "FN", [], - [{xmlcdata, "ejabberd/mod_muc"}]}, - {xmlelement, "URL", [], - [{xmlcdata, ?EJABBERD_URI}]}, - {xmlelement, "DESC", [], - [{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++ - "\nCopyright (c) 2003-2008 Alexey Shchepin"}]}]. + #xmlel{ns = ?NS_VCARD, name = 'vCard', + children = + [#xmlel{ns = ?NS_VCARD, name = 'FN', + children = [#xmlcdata{cdata = <<"ejabberd/mod_muc">>}]}, + #xmlel{ns = ?NS_VCARD, name = 'URL', + children = [#xmlcdata{cdata = ?EJABBERD_URI}]}, + #xmlel{ns = ?NS_VCARD, name = 'DESC', + children = [#xmlcdata{cdata = + translate:translate(Lang, "ejabberd MUC module") ++ + "\nCopyright (c) 2003-2008 Alexey Shchepin"}]}]}. broadcast_service_message(Host, Msg) -> diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl index e76904193..2c4dc8c70 100644 --- a/src/mod_muc/mod_muc_log.erl +++ b/src/mod_muc/mod_muc_log.erl @@ -41,8 +41,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-include_lib("exmpp/include/exmpp.hrl"). + -include("ejabberd.hrl"). --include("jlib.hrl"). -define(T(Text), translate:translate(Lang, Text)). -define(PROCNAME, ejabberd_mod_muc_log). @@ -204,14 +205,15 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- add_to_log2(text, {Nick, Packet}, Room, Opts, State) -> - case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of - {false, false} -> + case {exmpp_xml:get_element(Packet, 'subject'), + exmpp_xml:get_element(Packet, 'body')} of + {'undefined', 'undefined'} -> ok; - {false, SubEl} -> - Message = {body, xml:get_tag_cdata(SubEl)}, + {'undefined', SubEl} -> + Message = {body, exmpp_xml:get_cdata_as_list(SubEl)}, add_message_to_log(Nick, Message, Room, Opts, State); {SubEl, _} -> - Message = {subject, xml:get_tag_cdata(SubEl)}, + Message = {subject, exmpp_xml:get_cdata_as_list(SubEl)}, add_message_to_log(Nick, Message, Room, Opts, State) end; @@ -225,13 +227,13 @@ add_to_log2(join, Nick, Room, Opts, State) -> add_message_to_log(Nick, join, Room, Opts, State); add_to_log2(leave, {Nick, Reason}, Room, Opts, State) -> - case Reason of + case binary_to_list(Reason) of "" -> add_message_to_log(Nick, leave, Room, Opts, State); - _ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State) + R -> add_message_to_log(Nick, {leave, R}, Room, Opts, State) end; add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) -> - add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State). + add_message_to_log(Nick, {kickban, Code, binary_to_list(Reason)}, Room, Opts, State). %%---------------------------------------------------------------------- @@ -269,8 +271,8 @@ build_filename_string(TimeStamp, OutDir, RoomJID, DirType, DirName, FileFormat) {Fd, Fn, Fnrel}. get_room_name(RoomJID) -> - JID = jlib:string_to_jid(RoomJID), - JID#jid.user. + JID = exmpp_jid:list_to_jid(RoomJID), + JID#jid.node. %% calculate day before get_timestamp_daydiff(TimeStamp, Daydiff) -> @@ -458,16 +460,27 @@ get_dateweek(Date, Lang) -> end. make_dir_rec(Dir) -> + Path = filename:split(Dir), + inc_foreach(Path,fun make_dir_if_not_exists/1). + + +make_dir_if_not_exists(DirPath) -> + Dir = filename:join(DirPath), case file:read_file_info(Dir) of {ok, _} -> ok; {error, enoent} -> - DirS = filename:split(Dir), - DirR = lists:sublist(DirS, length(DirS)-1), - make_dir_rec(filename:join(DirR)), file:make_dir(Dir) end. +inc_foreach(Lists, F) -> + lists:foldl(fun(Item, Accum) -> + New = Accum ++ [Item], + F(New), + New + end,[],Lists). + + %% {ok, F1}=file:open("valid-xhtml10.png", [read]). %% {ok, F1b}=file:read(F1, 1000000). @@ -723,6 +736,8 @@ put_room_config(F, RoomConfig, Lang, _FileFormat) -> %% htmlize %% The default behaviour is to ignore the nofollow spam prevention on links %% (NoFollow=false) + + htmlize(S1) -> htmlize(S1, html). @@ -779,7 +794,7 @@ get_room_info(RoomJID, Opts) -> {value, {_, SA}} -> SA; false -> "" end, - #room{jid = jlib:jid_to_string(RoomJID), + #room{jid = exmpp_jid:jid_to_list(RoomJID), title = Title, subject = Subject, subject_author = SubjectAuthor, diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index 16268196f..8982cd832 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -46,8 +46,9 @@ terminate/3, code_change/4]). +-include_lib("exmpp/include/exmpp.hrl"). + -include("ejabberd.hrl"). --include("jlib.hrl"). -define(MAX_USERS_DEFAULT, 200). -define(MAX_USERS_DEFAULT_LIST, @@ -134,6 +135,12 @@ Creator, Nick, DefRoomOpts])). -endif. + +-define(ERR(Packet,Type, Lang, ErrText), + exmpp_stanza:error(Packet#xmlel.ns, + Type, + {Lang, translate:translate(Lang, ErrText)})). + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- @@ -178,12 +185,12 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, D access = Access, room = Room, history = lqueue_new(HistorySize), - jid = jlib:make_jid(Room, Host, ""), + jid = exmpp_jid:make_jid(Room, Host), just_created = true, room_shaper = Shaper}), State1 = set_opts(DefRoomOpts, State), ?INFO_MSG("Created MUC room ~s@~s by ~s", - [Room, Host, jlib:jid_to_string(Creator)]), + [Room, Host, exmpp_jid:jid_to_list(Creator)]), {ok, normal_state, State1}; init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) -> process_flag(trap_exit, true), @@ -193,7 +200,7 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) -> access = Access, room = Room, history = lqueue_new(HistorySize), - jid = jlib:make_jid(Room, Host, ""), + jid = exmpp_jid:make_jid(Room, Host), room_shaper = Shaper}), {ok, normal_state, State}. @@ -203,32 +210,33 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) -> %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -normal_state({route, From, "", - {xmlelement, "message", Attrs, Els} = Packet}, +normal_state({route, From, undefined, + #xmlel{name = 'message'} = Packet}, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), case is_user_online(From, StateData) orelse is_user_allowed_message_nonparticipant(From, StateData) of true -> - case xml:get_attr_s("type", Attrs) of - "groupchat" -> + case exmpp_message:get_type(Packet) of + groupchat -> Activity = get_user_activity(From, StateData), Now = now_to_usec(now()), MinMessageInterval = trunc(gen_mod:get_module_opt( StateData#state.server_host, mod_muc, min_message_interval, 0) * 1000000), - Size = lists:flatlength(xml:element_to_string(Packet)), + Size = lists:flatlength(exmpp_xml:document_to_list(Packet)), {MessageShaper, MessageShaperInterval} = shaper:update(Activity#activity.message_shaper, Size), if Activity#activity.message /= undefined -> ErrText = "Traffic rate limit is exceeded", - Err = jlib:make_error_reply( - Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)), + Err = exmpp_stanza:error(Packet#xmlel.ns, + 'resource-constraint', + {Lang, translate:translate(Lang, ErrText)}), ejabberd_router:route( StateData#state.jid, - From, Err), + From, exmpp_stanza:reply_with_error(Packet, Err)), {next_state, normal_state, StateData}; Now >= Activity#activity.message_time + MinMessageInterval, MessageShaperInterval == 0 -> @@ -245,7 +253,7 @@ normal_state({route, From, "", StateData1 = StateData#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity), room_shaper = RoomShaper}, @@ -272,7 +280,7 @@ normal_state({route, From, "", StateData2 = StateData1#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity), room_queue = RoomQueue}, @@ -292,12 +300,12 @@ normal_state({route, From, "", StateData1 = StateData#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity)}, {next_state, normal_state, StateData1} end; - "error" -> + error -> case is_user_online(From, StateData) of true -> ErrorText = "This participant is kicked from the room because " @@ -308,19 +316,22 @@ normal_state({route, From, "", _ -> {next_state, normal_state, StateData} end; - "chat" -> + chat -> ErrText = "It is not allowed to send private messages to the conference", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + Err = exmpp_stanza:error(Packet#xmlel.ns, + 'not-acceptable', + {Lang, translate:translate(Lang, ErrText)}), ejabberd_router:route( StateData#state.jid, - From, Err), + From, exmpp_stanza:reply_with_error(Packet, Err)), {next_state, normal_state, StateData}; - Type when (Type == "") or (Type == "normal") -> - case catch check_invitation(From, Els, Lang, StateData) of + normal -> + case catch check_invitation(From, + exmpp_xml:get_child_elements(Packet), + Lang, + StateData) of {error, Error} -> - Err = jlib:make_error_reply( - Packet, Error), + Err = exmpp_stanza:reply_with_error(Packet, Error), ejabberd_router:route( StateData#state.jid, From, Err), @@ -355,28 +366,29 @@ normal_state({route, From, "", end; _ -> ErrText = "Improper message type", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + Err = exmpp_stanza:error(Packet#xmlel.ns, + 'not-acceptable', + {Lang, translate:translate(Lang, ErrText)}), ejabberd_router:route( StateData#state.jid, - From, Err), + From, exmpp_stanza:reply_with_error(Packet, Err)), {next_state, normal_state, StateData} end; _ -> - case xml:get_attr_s("type", Attrs) of - "error" -> + case exmpp_stanza:is_stanza_error(Packet) of + true -> ok; - _ -> + false -> handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From) end, {next_state, normal_state, StateData} end; -normal_state({route, From, "", - {xmlelement, "iq", _Attrs, _Els} = Packet}, +normal_state({route, From, undefined, + #xmlel{name = 'iq'} = Packet}, StateData) -> - case jlib:iq_query_info(Packet) of - #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ when + case exmpp_iq:xmlel_to_iq(Packet) of + #iq{type = Type, ns = XMLNS, lang = Lang, payload = SubEl} = IQ when (XMLNS == ?NS_MUC_ADMIN) or (XMLNS == ?NS_MUC_OWNER) or (XMLNS == ?NS_DISCO_INFO) or @@ -393,21 +405,18 @@ normal_state({route, From, "", end, {IQRes, NewStateData} = case Res1 of + {result, [], SD} -> + {exmpp_iq:result(IQ), SD}; {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}, - SD}; + {exmpp_iq:result(IQ,#xmlel{ns = XMLNS, + name = 'query', + children = Res}), SD}; {error, Error} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - StateData} + {exmpp_iq:error(IQ, Error), StateData} end, ejabberd_router:route(StateData#state.jid, From, - jlib:iq_to_xml(IQRes)), + exmpp_iq:iq_to_xmlel(IQRes)), case NewStateData of stop -> {stop, normal, StateData}; @@ -417,14 +426,13 @@ normal_state({route, From, "", reply -> {next_state, normal_state, StateData}; _ -> - Err = jlib:make_error_reply( - Packet, ?ERR_FEATURE_NOT_IMPLEMENTED), + Err = exmpp_stanza:reply_with_error(Packet, 'feature-not-implemented'), ejabberd_router:route(StateData#state.jid, From, Err), {next_state, normal_state, StateData} end; normal_state({route, From, Nick, - {xmlelement, "presence", _Attrs, _Els} = Packet}, + #xmlel{name = 'presence'} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = now_to_usec(now()), @@ -439,7 +447,7 @@ normal_state({route, From, Nick, StateData1 = StateData#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity)}, process_presence(From, Nick, Packet, StateData1); @@ -457,17 +465,17 @@ normal_state({route, From, Nick, StateData1 = StateData#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity)}, {next_state, normal_state, StateData1} end; normal_state({route, From, ToNick, - {xmlelement, "message", Attrs, _} = Packet}, + #xmlel{name = 'message'} = Packet}, StateData) -> - Type = xml:get_attr_s("type", Attrs), - Lang = xml:get_attr_s("xml:lang", Attrs), + Type = exmpp_message:get_type(Packet), + Lang = exmpp_stanza:get_lang(Packet), case decide_fate_message(Type, Packet, From, StateData) of {expulse_sender, Reason} -> ?DEBUG(Reason, []), @@ -486,10 +494,11 @@ normal_state({route, From, ToNick, "groupchat" -> ErrText = "It is not allowed to send private " "messages of type \"groupchat\"", - Err = jlib:make_error_reply( - Packet, ?ERRT_BAD_REQUEST(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'bad-request', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, ToNick), From, Err); @@ -497,19 +506,20 @@ 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, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'item-not-found', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, ToNick), From, Err); ToJID -> {ok, #user{nick = FromNick}} = - ?DICT:find(jlib:jid_tolower(From), + ?DICT:find(jlib:short_prepd_jid(From), StateData#state.users), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, FromNick), ToJID, Packet) @@ -517,19 +527,21 @@ normal_state({route, From, ToNick, end; {true, false} -> ErrText = "Only occupants are allowed to send messages to the conference", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'not-acceptable', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, ToNick), From, Err); {false, _} -> ErrText = "It is not allowed to send private messages", - Err = jlib:make_error_reply( - Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, ToNick), From, Err) @@ -538,56 +550,65 @@ normal_state({route, From, ToNick, end; normal_state({route, From, ToNick, - {xmlelement, "iq", Attrs, _Els} = Packet}, + #xmlel{name = 'iq'} = Packet}, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), 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 -> + case exmpp_iq:get_type(Packet) of + result -> + ok; + error -> ok; _ -> ErrText = "Recipient is not in the conference room", - Err = jlib:make_error_reply( - Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'item-not-found', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, ToNick), From, Err) end; ToJID -> {ok, #user{nick = FromNick}} = - ?DICT:find(jlib:jid_tolower(From), + ?DICT:find(jlib:short_prepd_jid(From), StateData#state.users), ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, FromNick), + jid_replace_resource(StateData#state.jid, FromNick), ToJID, Packet) end; {_, false} -> - case jlib:iq_query_info(Packet) of - reply -> + case exmpp_iq:get_type(Packet) of + result -> + ok; + error -> ok; _ -> ErrText = "Only occupants are allowed to send queries to the conference", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'not-acceptable', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, ToNick), + jid_replace_resource(StateData#state.jid, ToNick), From, Err) end; _ -> - case jlib:iq_query_info(Packet) of - reply -> + case exmpp_iq:get_type(Packet) of + result -> + ok; + error -> ok; _ -> ErrText = "Queries to the conference members are not allowed in this room", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_ALLOWED(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'not-allowed', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, ToNick), + jid_replace_resource(StateData#state.jid, ToNick), From, Err) end end, @@ -605,9 +626,10 @@ normal_state(_Event, StateData) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event({service_message, Msg}, _StateName, StateData) -> - MessagePkt = {xmlelement, "message", - [{"type", "groupchat"}], - [{xmlelement, "body", [], [{xmlcdata, Msg}]}]}, + MessagePkt = #xmlel{name = 'message', + attrs = [#xmlattr{name = 'type', value = "groupchat"}], + children = [#xmlel{name = 'body', + children = [#xmlcdata{cdata = Msg}]}]}, lists:foreach( fun({_LJID, Info}) -> ejabberd_router:route( @@ -623,21 +645,20 @@ handle_event({service_message, Msg}, _StateName, StateData) -> handle_event({destroy, Reason}, _StateName, StateData) -> {result, [], stop} = - destroy_room( - {xmlelement, "destroy", - [{"xmlns", ?NS_MUC_OWNER}], - case Reason of - none -> []; - _Else -> - [{xmlelement, "reason", - [], [{xmlcdata, Reason}]}] - end}, StateData), + destroy_room( + #xmlel{ns = ?NS_MUC_OWNER, name = 'destroy', + children = case Reason of + none -> []; + _Else -> [#xmlel{name = 'reason', + children = [#xmlcdata{cdata = Reason}]}] + end}, StateData), + ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", - [jlib:jid_to_string(StateData#state.jid), Reason]), + [exmpp_jid:jid_to_list(StateData#state.jid), Reason]), {stop, normal, StateData}; handle_event(destroy, StateName, StateData) -> ?INFO_MSG("Destroyed MUC room ~s", - [jlib:jid_to_string(StateData#state.jid)]), + [exmpp_jid:jid_to_list(StateData#state.jid)]), handle_event({destroy, none}, StateName, StateData); handle_event({set_affiliations, Affiliations}, StateName, StateData) -> @@ -676,7 +697,7 @@ handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) -> _ -> " (n/a)" end, - Reply = case ((StateData#state.config)#config.public == true) orelse + Reply = case ((StateData#state.config)#config.public == true) orelse (FRole /= none) orelse (FAffiliation == admin) orelse (FAffiliation == owner) of @@ -739,7 +760,7 @@ handle_info(process_room_queue, normal_state = StateName, StateData) -> StateData1 = StateData#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity), room_queue = RoomQueue}, @@ -752,7 +773,7 @@ handle_info(process_room_queue, normal_state = StateName, StateData) -> StateData1 = StateData#state{ activity = ?DICT:store( - jlib:jid_tolower(From), + jlib:short_prepd_jid(From), NewActivity, StateData#state.activity), room_queue = RoomQueue}, @@ -785,9 +806,9 @@ terminate(_Reason, _StateName, StateData) -> route(Pid, From, ToNick, Packet) -> gen_fsm:send_event(Pid, {route, From, ToNick, Packet}). -process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, +process_groupchat_message(From, #xmlel{name = 'message'} = Packet, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), case is_user_online(From, StateData) orelse is_user_allowed_message_nonparticipant(From, StateData) of true -> @@ -827,7 +848,7 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, lists:foreach( fun({_LJID, Info}) -> ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, FromNick), Info#user.jid, @@ -843,26 +864,29 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, Err = case (StateData#state.config)#config.allow_change_subj of true -> - ?ERRT_FORBIDDEN( - Lang, - "Only moderators and participants " - "are allowed to change subject in this room"); + exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', + {Lang, translate:translate(Lang, + "Only moderators and participants " + "are allowed to change subject in this room")})); _ -> - ?ERRT_FORBIDDEN( - Lang, - "Only moderators " - "are allowed to change subject in this room") + exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', + {Lang, translate:translate(Lang, + "Only moderators " + "are allowed to change subject in this room")})) end, ejabberd_router:route( StateData#state.jid, From, - jlib:make_error_reply(Packet, Err)), + 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, ?ERRT_FORBIDDEN(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'forbidden', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( StateData#state.jid, From, Err), @@ -870,8 +894,9 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, end; false -> ErrText = "Only occupants are allowed to send messages to the conference", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'not-acceptable', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route(StateData#state.jid, From, Err), {next_state, normal_state, StateData} end. @@ -897,7 +922,7 @@ is_user_allowed_message_nonparticipant(JID, StateData) -> %% @doc Get information of this participant, or default values. %% If the JID is not a participant, return values for a service message. get_participant_data(From, StateData) -> - case ?DICT:find(jlib:jid_tolower(From), StateData#state.users) of + case ?DICT:find(jlib:short_prepd_jid(From), StateData#state.users) of {ok, #user{nick = FromNick, role = Role}} -> {FromNick, Role}; error -> @@ -905,27 +930,27 @@ get_participant_data(From, StateData) -> end. -process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet, +process_presence(From, Nick, #xmlel{name = 'presence'} = Packet, StateData) -> - Type = xml:get_attr_s("type", Attrs), - Lang = xml:get_attr_s("xml:lang", Attrs), + Type = exmpp_presence:get_type(Packet), + Lang = exmpp_stanza:get_lang(Packet), StateData1 = case Type of - "unavailable" -> + unavailable -> case is_user_online(From, StateData) of true -> NewState = add_user_presence_un(From, Packet, StateData), send_new_presence(From, NewState), - Reason = case xml:get_subtag(Packet, "status") of - false -> ""; - Status_el -> xml:get_tag_cdata(Status_el) + Reason = case exmpp_xml:get_element(Packet, 'status') of + undefined -> <<>>; + Status_el -> exmpp_xml:get_cdata(Status_el) end, remove_online_user(From, NewState, Reason); _ -> StateData end; - "error" -> + error -> case is_user_online(From, StateData) of true -> ErrorText = "This participant is kicked from the room because " @@ -935,7 +960,7 @@ process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet, _ -> StateData end; - "" -> + available -> case is_user_online(From, StateData) of true -> case is_nick_change(From, Nick, StateData) of @@ -943,40 +968,40 @@ process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet, case {is_nick_exists(Nick, StateData), mod_muc:can_use_nick( StateData#state.host, From, Nick), - {(StateData#state.config)#config.allow_visitor_nickchange, - is_visitor(From, StateData)}} of - {_, _, {false, true}} -> + {(StateData#state.config)#config.allow_visitor_nickchange, + is_visitor(From, StateData)}} of + {_, _, {false, true}} -> ErrText = "Visitors are not allowed to change their nicknames in this room", - Err = jlib:make_error_reply( - Packet, - ?ERRT_NOT_ALLOWED(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'not-allowed', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, Nick), From, Err), StateData; {true, _, _} -> - Lang = xml:get_attr_s("xml:lang", Attrs), + Lang = exmpp_stanza:get_lang(Packet), ErrText = "Nickname is already in use by another occupant", - Err = jlib:make_error_reply( - Packet, - ?ERRT_CONFLICT(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'conflict', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( - jlib:jid_replace_resource( + 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)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, 'conflict', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, Nick), From, Err), @@ -1006,14 +1031,14 @@ process_presence(From, Nick, {xmlelement, "presence", Attrs, _Els} = Packet, (?DICT:to_list(StateData1#state.users) == []) of true -> ?INFO_MSG("Destroyed MUC room ~s because it's temporary and empty", - [jlib:jid_to_string(StateData#state.jid)]), + [exmpp_jid:jid_to_list(StateData#state.jid)]), {stop, normal, StateData1}; _ -> {next_state, normal_state, StateData1} end. is_user_online(JID, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), ?DICT:is_key(LJID, StateData#state.users). role_to_list(Role) -> @@ -1052,13 +1077,13 @@ list_to_affiliation(Affiliation) -> %% Decide the fate of the message and its sender %% Returns: continue_delivery | forget_message | {expulse_sender, Reason} -decide_fate_message("error", Packet, From, StateData) -> +decide_fate_message(error, Packet, From, StateData) -> %% Make a preliminary decision PD = case check_error_kick(Packet) of %% If this is an error stanza and its condition matches a criteria true -> Reason = io_lib:format("This participant is considered a ghost and is expulsed: ~s", - [jlib:jid_to_string(From)]), + [exmpp_jid:jid_to_list(From)]), {expulse_sender, Reason}; false -> continue_delivery @@ -1083,47 +1108,39 @@ decide_fate_message(_, _, _, _) -> %% If so, return true to kick the participant. check_error_kick(Packet) -> case get_error_condition(Packet) of - "gone" -> true; - "internal-server-error" -> true; - "item-not-found" -> true; - "jid-malformed" -> true; - "recipient-unavailable" -> true; - "redirect" -> true; - "remote-server-not-found" -> true; - "remote-server-timeout" -> true; - "service-unavailable" -> true; + 'gone' -> true; + 'internal-server-error' -> true; + 'item-not-found' -> true; + 'jid-malformed' -> true; + 'recipient-unavailable' -> true; + 'redirect' -> true; + 'remote-server-not-found' -> true; + 'remote-server-timeout' -> true; + 'service-unavailable' -> true; _ -> false end. get_error_condition(Packet) -> - case catch get_error_condition2(Packet) of - {condition, ErrorCondition} -> - ErrorCondition; - {'EXIT', _} -> - "badformed error stanza" + try exmpp_stanza:get_condition(Packet) of + ErrorCondition -> ErrorCondition + catch + _:_ -> + 'badformed-error-stanza' end. -get_error_condition2(Packet) -> - {xmlelement, _, _, EEls} = xml:get_subtag(Packet, "error"), - [Condition] = [Name || {xmlelement, Name, [{"xmlns", ?NS_STANZAS}], []} <- EEls], - {condition, Condition}. expulse_participant(Packet, From, StateData, Reason1) -> ErrorCondition = get_error_condition(Packet), Reason2 = io_lib:format(Reason1 ++ ": " ++ "~s", [ErrorCondition]), NewState = add_user_presence_un( From, - {xmlelement, "presence", - [{"type", "unavailable"}], - [{xmlelement, "status", [], - [{xmlcdata, Reason2}] - }]}, + exmpp_presence:presence('unavailable',Reason2), StateData), send_new_presence(From, NewState), remove_online_user(From, NewState). set_affiliation(JID, Affiliation, StateData) -> - LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)), + LJID = jlib:short_prepd_bare_jid(JID), Affiliations = case Affiliation of none -> ?DICT:erase(LJID, @@ -1136,7 +1153,7 @@ set_affiliation(JID, Affiliation, StateData) -> StateData#state{affiliations = Affiliations}. set_affiliation_and_reason(JID, Affiliation, Reason, StateData) -> - LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)), + LJID = jlib:short_prepd_bare_jid(JID), Affiliations = case Affiliation of none -> ?DICT:erase(LJID, @@ -1155,22 +1172,22 @@ get_affiliation(JID, StateData) -> allow -> owner; _ -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), case ?DICT:find(LJID, StateData#state.affiliations) of {ok, Affiliation} -> Affiliation; _ -> - LJID1 = jlib:jid_remove_resource(LJID), + LJID1 = jlib:short_prepd_bare_jid(JID), case ?DICT:find(LJID1, StateData#state.affiliations) of {ok, Affiliation} -> Affiliation; _ -> - LJID2 = setelement(1, LJID, ""), + LJID2 = setelement(1, LJID, undefined), case ?DICT:find(LJID2, StateData#state.affiliations) of {ok, Affiliation} -> Affiliation; _ -> - LJID3 = jlib:jid_remove_resource(LJID2), + LJID3 = setelement(1,jlib:short_prepd_bare_jid(JID),undefined), case ?DICT:find(LJID3, StateData#state.affiliations) of {ok, Affiliation} -> Affiliation; @@ -1199,9 +1216,9 @@ get_service_affiliation(JID, StateData) -> end. set_role(JID, Role, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), LJIDs = case LJID of - {U, S, ""} -> + {U, S, undefined} -> ?DICT:fold( fun(J, _, Js) -> case J of @@ -1236,7 +1253,7 @@ set_role(JID, Role, StateData) -> StateData#state{users = Users}. get_role(JID, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), case ?DICT:find(LJID, StateData#state.users) of {ok, #user{role = Role}} -> Role; @@ -1284,7 +1301,7 @@ get_max_users_admin_threshold(StateData) -> mod_muc, max_users_admin_threshold, 5). get_user_activity(JID, StateData) -> - case ?DICT:find(jlib:jid_tolower(JID), + case ?DICT:find(jlib:short_prepd_jid(JID), StateData#state.activity) of {ok, A} -> A; error -> @@ -1305,7 +1322,7 @@ prepare_room_queue(StateData) -> {{value, {message, From}}, _RoomQueue} -> Activity = get_user_activity(From, StateData), Packet = Activity#activity.message, - Size = lists:flatlength(xml:element_to_string(Packet)), + Size = lists:flatlength(exmpp_xml:documenent_to_list(Packet)), {RoomShaper, RoomShaperInterval} = shaper:update(StateData#state.room_shaper, Size), erlang:send_after( @@ -1316,7 +1333,7 @@ prepare_room_queue(StateData) -> {{value, {presence, From}}, _RoomQueue} -> Activity = get_user_activity(From, StateData), {_Nick, Packet} = Activity#activity.presence, - Size = lists:flatlength(xml:element_to_string(Packet)), + Size = lists:flatlength(exmpp_xml:document_to_list(Packet)), {RoomShaper, RoomShaperInterval} = shaper:update(StateData#state.room_shaper, Size), erlang:send_after( @@ -1330,7 +1347,7 @@ prepare_room_queue(StateData) -> add_online_user(JID, Nick, Role, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), Users = ?DICT:store(LJID, #user{jid = JID, nick = Nick, @@ -1341,10 +1358,10 @@ add_online_user(JID, Nick, Role, StateData) -> StateData#state{users = Users}. remove_online_user(JID, StateData) -> - remove_online_user(JID, StateData, ""). + remove_online_user(JID, StateData, <<>>). remove_online_user(JID, StateData, Reason) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), {ok, #user{nick = Nick}} = ?DICT:find(LJID, StateData#state.users), add_to_log(leave, {Nick, Reason}, StateData), @@ -1353,34 +1370,31 @@ remove_online_user(JID, StateData, Reason) -> StateData#state{users = Users}. -filter_presence({xmlelement, "presence", Attrs, Els}) -> +filter_presence(#xmlel{name = 'presence'} = Packet) -> FEls = lists:filter( fun(El) -> - case El of - {xmlcdata, _} -> - false; - {xmlelement, _Name1, Attrs1, _Els1} -> - XMLNS = xml:get_attr_s("xmlns", Attrs1), - case XMLNS of - ?NS_MUC ++ _ -> + case El of + #xmlel{ns = XMLNS} -> + case atom_to_list(XMLNS) of + ?NS_MUC_s ++ _ -> false; _ -> true end end - end, Els), - {xmlelement, "presence", Attrs, FEls}. + end, exmpp_xml:get_child_elements(Packet)), + exmpp_xml:set_children(Packet, FEls). -strip_status({xmlelement, "presence", Attrs, Els}) -> +strip_status(#xmlel{name = 'presence', children = Children} = Packet) -> FEls = lists:filter( - fun({xmlelement, "status", _Attrs1, _Els1}) -> + fun(#xmlel{name = 'status'}) -> false; (_) -> true - end, Els), - {xmlelement, "presence", Attrs, FEls}. + end, Children), + exmpp_xml:set_children(Packet,FEls). add_user_presence(JID, Presence, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), FPresence = filter_presence(Presence), Users = ?DICT:update( @@ -1391,7 +1405,7 @@ add_user_presence(JID, Presence, StateData) -> StateData#state{users = Users}. add_user_presence_un(JID, Presence, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), FPresence = filter_presence(Presence), Users = ?DICT:update( @@ -1417,7 +1431,7 @@ find_jid_by_nick(Nick, StateData) -> end, false, StateData#state.users). is_nick_change(JID, Nick, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), case Nick of "" -> false; @@ -1427,8 +1441,8 @@ is_nick_change(JID, Nick, StateData) -> Nick /= OldNick end. -add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> - Lang = xml:get_attr_s("xml:lang", Attrs), +add_new_user(From, Nick, Packet, StateData) -> + Lang = exmpp_stanza:get_lang(Packet), MaxUsers = get_max_users(StateData), MaxAdminUsers = MaxUsers + get_max_users_admin_threshold(StateData), NUsers = dict:fold(fun(_, _, Acc) -> Acc + 1 end, 0, @@ -1450,59 +1464,70 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> get_default_role(Affiliation, StateData)} of {false, _, _, _} -> % max user reached and user is not admin or owner - Err = jlib:make_error_reply( + Err = exmpp_stanza:reply_with_error( Packet, - ?ERR_SERVICE_UNAVAILABLE), + 'service-unavailable'), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), From, Err), StateData; {_, _, _, none} -> - Err = jlib:make_error_reply( + Err = exmpp_stanza:reply_with_error( Packet, case Affiliation of outcast -> ErrText = "You have been banned from this room", - ?ERRT_FORBIDDEN(Lang, ErrText); + exmpp_stanza:error(Packet#xmlel.ns, + 'forbidden', + {Lang, translate:translate(Lang, ErrText)}); _ -> ErrText = "Membership required to enter this room", - ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText) + exmpp_stanza:error(Packet#xmlel.ns, + 'registration-required', + {Lang, translate:translate(Lang, ErrText)}) end), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), From, Err), StateData; {_, true, _, _} -> ErrText = "Nickname is already in use by another occupant", - Err = jlib:make_error_reply(Packet, ?ERRT_CONFLICT(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error(Packet, + exmpp_stanza:error(Packet#xmlel.ns, + 'conflict', + {Lang, translate:translate(Lang, ErrText)})), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource(StateData#state.jid, Nick), + 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)), + Err = exmpp_stanza:reply_with_error(Packet, + ?ERR(Packet, 'conflict', Lang, ErrText)), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), From, Err), StateData; {_, _, _, Role} -> - case check_password(Affiliation, Els, StateData) of + case check_password(Affiliation, + exmpp_xml:get_child_elements(Packet), + StateData) of true -> NewState = add_user_presence( From, Packet, add_online_user(From, Nick, Role, StateData)), if not (NewState#state.config)#config.anonymous -> - WPacket = {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "body", [], - [{xmlcdata, translate:translate( - Lang, - "This room is not anonymous")}]}, - {xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "status", [{"code", "100"}], []}]}]}, + WPacket = + #xmlel{name = 'message', + attrs = [#xmlattr{name = 'type', value = "groupchat"}], + children = [ + #xmlel{name = 'body', + children = [#xmlcdata{cdata = + translate:translate(Lang, + "This room is not anonymous")}]}]}, ejabberd_router:route( StateData#state.jid, From, WPacket); @@ -1511,7 +1536,9 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> end, send_existing_presences(From, NewState), send_new_presence(From, NewState), - Shift = count_stanza_shift(Nick, Els, NewState), + Shift = count_stanza_shift(Nick, + exmpp_xml:get_child_elements(Packet), + NewState), case send_history(From, Shift, NewState) of true -> ok; @@ -1526,19 +1553,19 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> end; nopass -> ErrText = "Password required to enter this room", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error( + Packet, ?ERR(Packet, 'not-authorized', Lang, ErrText)), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, Nick), From, Err), StateData; _ -> ErrText = "Incorrect password", - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_AUTHORIZED(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error( + Packet, ?ERR(Packet, 'not-authorized', Lang, ErrText)), ejabberd_router:route( % TODO: s/Nick/""/ - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, Nick), From, Err), StateData @@ -1568,14 +1595,14 @@ check_password(_Affiliation, Els, StateData) -> extract_password([]) -> false; -extract_password([{xmlelement, _Name, Attrs, _SubEls} = El | Els]) -> - case xml:get_attr_s("xmlns", Attrs) of +extract_password([#xmlel{ns = XMLNS} = El | Els]) -> + case XMLNS of ?NS_MUC -> - case xml:get_subtag(El, "password") of - false -> + case exmpp_xml:get_element(El, 'password') of + undefined -> false; SubEl -> - xml:get_tag_cdata(SubEl) + exmpp_xml:get_cdata(SubEl) end; _ -> extract_password(Els) @@ -1664,11 +1691,11 @@ calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> extract_history([], _Type) -> false; -extract_history([{xmlelement, _Name, Attrs, _SubEls} = El | Els], Type) -> - case xml:get_attr_s("xmlns", Attrs) of +extract_history([#xmlel{ns = XMLNS} = El | Els], Type) -> + case XMLNS of ?NS_MUC -> - AttrVal = xml:get_path_s(El, - [{elem, "history"}, {attr, Type}]), + AttrVal = exmpp_xml:get_path(El, + [{element, 'history'}, {attribute, Type,""}]), case Type of "since" -> case jlib:datetime_string_to_timestamp(AttrVal) of @@ -1696,9 +1723,9 @@ send_update_presence(JID, StateData) -> send_update_presence(JID, "", StateData). send_update_presence(JID, Reason, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), LJIDs = case LJID of - {U, S, ""} -> + {U, S, undefined} -> ?DICT:fold( fun(J, _, Js) -> case J of @@ -1728,7 +1755,7 @@ send_new_presence(NJID, Reason, StateData) -> nick = Nick, role = Role, last_presence = Presence}} = - ?DICT:find(jlib:jid_tolower(NJID), StateData#state.users), + ?DICT:find(jlib:short_prepd_jid(NJID), StateData#state.users), Affiliation = get_affiliation(NJID, StateData), SAffiliation = affiliation_to_list(Affiliation), SRole = role_to_list(Role), @@ -1738,39 +1765,41 @@ send_new_presence(NJID, Reason, StateData) -> case (Info#user.role == moderator) orelse ((StateData#state.config)#config.anonymous == false) of true -> - [{"jid", jlib:jid_to_string(RealJID)}, - {"affiliation", SAffiliation}, - {"role", SRole}]; + [#xmlattr{name = 'jid', value = exmpp_jid:jid_to_list(RealJID)}, + #xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = SRole}]; _ -> - [{"affiliation", SAffiliation}, - {"role", SRole}] + [#xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = SRole}] end, ItemEls = case Reason of "" -> []; _ -> - [{xmlelement, "reason", [], - [{xmlcdata, Reason}]}] + [#xmlel{name = 'reason', + children = [#xmlcdata{cdata = Reason}]}] end, Status = case StateData#state.just_created of true -> - [{xmlelement, "status", [{"code", "201"}], []}]; + [#xmlel{name = 'status', + attrs = [#xmlattr{name = 'code', value = "201"}]}]; false -> [] end, - Packet = append_subtags( - Presence, - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, ItemEls} | Status]}]), + Packet = exmpp_xml:append_child(Presence, + #xmlel{ns = ?NS_MUC_USER, name = 'x', + children = [#xmlel{ns = ?NS_MUC_USER, name ='item', + attrs = ItemAttrs, + children = ItemEls} | Status]}), ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet) end, ?DICT:to_list(StateData#state.users)). send_existing_presences(ToJID, StateData) -> - LToJID = jlib:jid_tolower(ToJID), + LToJID = jlib:short_prepd_jid(ToJID), {ok, #user{jid = RealToJID, role = Role}} = ?DICT:find(LToJID, StateData#state.users), @@ -1784,27 +1813,29 @@ send_existing_presences(ToJID, StateData) -> FromJID -> ok; _ -> - FromAffiliation = get_affiliation(LJID, StateData), + {N,D,R} = LJID, + FromAffiliation = get_affiliation(exmpp_jid:make_jid(N,D,R), + StateData), ItemAttrs = case (Role == moderator) orelse ((StateData#state.config)#config.anonymous == false) of true -> - [{"jid", jlib:jid_to_string(FromJID)}, - {"affiliation", - affiliation_to_list(FromAffiliation)}, - {"role", role_to_list(FromRole)}]; + [#xmlattr{name = 'jid', value = exmpp_jid:jid_to_list(FromJID)}, + #xmlattr{name = 'affiliation', + value = affiliation_to_list(FromAffiliation)}, + #xmlattr{name = 'role', value = role_to_list(FromRole)}]; _ -> - [{"affiliation", - affiliation_to_list(FromAffiliation)}, - {"role", role_to_list(FromRole)}] + [#xmlattr{name = 'affiliation', + value = affiliation_to_list(FromAffiliation)}, + #xmlattr{name = 'role', value = role_to_list(FromRole)}] end, - Packet = append_subtags( - Presence, - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, []}]}]), + Packet = exmpp_xml:append_child(Presence, + #xmlel{ns = ?NS_MUC_USER, name = 'x', + children = [#xmlel{ns = ?NS_MUC_USER, name ='item', + attrs = ItemAttrs}]}), ejabberd_router:route( - jlib:jid_replace_resource( + jid_replace_resource( StateData#state.jid, FromNick), RealToJID, Packet) @@ -1812,16 +1843,13 @@ send_existing_presences(ToJID, StateData) -> end, ?DICT:to_list(StateData#state.users)). -append_subtags({xmlelement, Name, Attrs, SubTags1}, SubTags2) -> - {xmlelement, Name, Attrs, SubTags1 ++ SubTags2}. - now_to_usec({MSec, Sec, USec}) -> (MSec*1000000 + Sec)*1000000 + USec. change_nick(JID, Nick, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), {ok, #user{nick = OldNick}} = ?DICT:find(LJID, StateData#state.users), Users = @@ -1840,7 +1868,7 @@ send_nick_changing(JID, OldNick, StateData) -> nick = Nick, role = Role, last_presence = Presence}} = - ?DICT:find(jlib:jid_tolower(JID), StateData#state.users), + ?DICT:find(jlib:short_prepd_jid(JID), StateData#state.users), Affiliation = get_affiliation(JID, StateData), SAffiliation = affiliation_to_list(Affiliation), SRole = role_to_list(Role), @@ -1850,41 +1878,51 @@ send_nick_changing(JID, OldNick, StateData) -> case (Info#user.role == moderator) orelse ((StateData#state.config)#config.anonymous == false) of true -> - [{"jid", jlib:jid_to_string(RealJID)}, - {"affiliation", SAffiliation}, - {"role", SRole}, - {"nick", Nick}]; + [#xmlattr{name = 'jid', value = exmpp_jid:jid_to_list(RealJID)}, + #xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = SRole}, + #xmlattr{name = 'nick', value = Nick}]; _ -> - [{"affiliation", SAffiliation}, - {"role", SRole}, - {"nick", Nick}] + [#xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = SRole}, + #xmlattr{name = 'nick', value = Nick}] end, ItemAttrs2 = case (Info#user.role == moderator) orelse ((StateData#state.config)#config.anonymous == false) of true -> - [{"jid", jlib:jid_to_string(RealJID)}, - {"affiliation", SAffiliation}, - {"role", SRole}]; + [#xmlattr{name = 'jid', value = exmpp_jid:jid_to_list(RealJID)}, + #xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = SRole}]; _ -> - [{"affiliation", SAffiliation}, - {"role", SRole}] + [#xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = SRole}] end, Packet1 = - {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs1, []}, - {xmlelement, "status", [{"code", "303"}], []}]}]}, - Packet2 = append_subtags( + #xmlel{ns = ?NS_JABBER_CLIENT, + name = 'presence', + attrs = [#xmlattr{name = 'type', value = "unavailable"}], + children = [#xmlel{ns = ?NS_MUC_USER, name = 'x', + children = [ + #xmlel{ns = ?NS_MUC_USER, name = 'item', + attrs = ItemAttrs1}, + #xmlel{ns = ?NS_MUC_USER, name = 'status', + attrs = [#xmlattr{name = 'code', + value = "303"}]}]}]}, + + Packet2 = exmpp_xml:append_child( Presence, - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs2, []}]}]), + #xmlel{ns = ?NS_MUC_USER, name = 'x', + children =[#xmlel{ns = ?NS_MUC_USER, + name = 'item', + attrs = ItemAttrs2}]}), + ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, OldNick), + jid_replace_resource(StateData#state.jid, OldNick), Info#user.jid, Packet1), ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet2) end, ?DICT:to_list(StateData#state.users)). @@ -1920,20 +1958,16 @@ lqueue_to_list(#lqueue{queue = Q1}) -> add_message_to_history(FromNick, Packet, StateData) -> - HaveSubject = case xml:get_subtag(Packet, "subject") of - false -> - false; - _ -> - true - end, + HaveSubject = exmpp_xml:has_element(Packet, 'subject'), TimeStamp = calendar:now_to_universal_time(now()), - TSPacket = append_subtags(Packet, - [jlib:timestamp_to_xml(TimeStamp)]), - SPacket = jlib:replace_from_to( - jlib:jid_replace_resource(StateData#state.jid, FromNick), - StateData#state.jid, - TSPacket), - Size = lists:flatlength(xml:element_to_string(SPacket)), + TSPacket = exmpp_xml:append_child(Packet, + jlib:timestamp_to_xml(TimeStamp)), + SPacket = exmpp_stanza:set_recipient( + exmpp_stanza:set_sender(TSPacket, + jid_replace_resource(StateData#state.jid, FromNick)), + StateData#state.jid), + + Size = lists:flatlength(exmpp_xml:document_to_list(SPacket)), Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size}, StateData#state.history), add_to_log(text, {FromNick, Packet}, StateData), @@ -1943,7 +1977,7 @@ send_history(JID, Shift, StateData) -> lists:foldl( fun({Nick, Packet, HaveSubject, _TimeStamp, _Size}, B) -> ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), JID, Packet), B or HaveSubject @@ -1956,14 +1990,9 @@ send_subject(JID, Lang, StateData) -> ok; Nick -> Subject = StateData#state.subject, - Packet = {xmlelement, "message", [{"type", "groupchat"}], - [{xmlelement, "subject", [], [{xmlcdata, Subject}]}, - {xmlelement, "body", [], - [{xmlcdata, - Nick ++ - translate:translate(Lang, - " has set the subject to: ") ++ - Subject}]}]}, + Packet = exmpp_message:groupchat(Subject, + Nick ++ translate:translate(Lang, + " has set the subject to: ") ++ Subject), ejabberd_router:route( StateData#state.jid, JID, @@ -1971,11 +2000,11 @@ send_subject(JID, Lang, StateData) -> end. check_subject(Packet) -> - case xml:get_subtag(Packet, "subject") of - false -> + case exmpp_message:get_subject(Packet) of + undefined -> false; - SubjEl -> - xml:get_tag_cdata(SubjEl) + Subj -> + Subj end. can_change_subject(Role, StateData) -> @@ -1990,25 +2019,25 @@ can_change_subject(Role, StateData) -> % Admin stuff process_iq_admin(From, set, Lang, SubEl, StateData) -> - {xmlelement, _, _, Items} = SubEl, + #xmlel{children = Items} = SubEl, process_admin_items_set(From, Items, Lang, StateData); process_iq_admin(From, get, Lang, SubEl, StateData) -> - case xml:get_subtag(SubEl, "item") of - false -> - {error, ?ERR_BAD_REQUEST}; + case exmpp_xml:get_element(SubEl, 'item') of + 'undefined' -> + {error, 'bad-request'}; Item -> FAffiliation = get_affiliation(From, StateData), FRole = get_role(From, StateData), - case xml:get_tag_attr("role", Item) of + case exmpp_xml:get_attribute(Item, 'role', false) of false -> - case xml:get_tag_attr("affiliation", Item) of + case exmpp_xml:get_attribute(Item, 'affiliation', false) of false -> - {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> + {error, 'bad-request'}; + StrAffiliation -> case catch list_to_affiliation(StrAffiliation) of {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; + {error, 'bad-request'}; SAffiliation -> if (FAffiliation == owner) or @@ -2018,14 +2047,14 @@ process_iq_admin(From, get, Lang, SubEl, StateData) -> {result, Items, StateData}; true -> ErrText = "Administrator privileges required", - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + {error, ?ERR(SubEl, 'forbidden', Lang, ErrText)} end end end; - {value, StrRole} -> + StrRole -> case catch list_to_role(StrRole) of {'EXIT', _} -> - {error, ?ERR_BAD_REQUEST}; + {error, 'bad-request'}; SRole -> if FRole == moderator -> @@ -2033,7 +2062,7 @@ process_iq_admin(From, get, Lang, SubEl, StateData) -> {result, Items, StateData}; true -> ErrText = "Moderator privileges required", - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + {error, ?ERR(SubEl, 'forbidden', Lang, ErrText)} end end end @@ -2049,15 +2078,22 @@ items_with_role(SRole, StateData) -> items_with_affiliation(SAffiliation, StateData) -> lists:map( fun({JID, {Affiliation, Reason}}) -> - {xmlelement, "item", - [{"affiliation", affiliation_to_list(Affiliation)}, - {"jid", jlib:jid_to_string(JID)}], - [{xmlelement, "reason", [], [{xmlcdata, Reason}]}]}; + {N, D, R} = JID, + #xmlel{name = 'item', + attrs = [#xmlattr{name = 'affiliation', + value = affiliation_to_list(Affiliation)}, + #xmlattr{name = 'jid', + value = exmpp_jid:jid_to_list(N, D, R)}], + children = [ #xmlel{name = 'reason', + children = [#xmlcdata{cdata = Reason}]}]}; + ({JID, Affiliation}) -> - {xmlelement, "item", - [{"affiliation", affiliation_to_list(Affiliation)}, - {"jid", jlib:jid_to_string(JID)}], - []} + {N, D, R} = JID, + #xmlel{name = 'item', + attrs = [#xmlattr{name = 'affiliation', + value = affiliation_to_list(Affiliation)}, + #xmlattr{name = 'jid', + value = exmpp_jid:jid_to_list(N, D, R)}]} end, search_affiliation(SAffiliation, StateData)). user_to_item(#user{role = Role, @@ -2065,12 +2101,13 @@ user_to_item(#user{role = Role, jid = JID }, StateData) -> Affiliation = get_affiliation(JID, StateData), - {xmlelement, "item", - [{"role", role_to_list(Role)}, - {"affiliation", affiliation_to_list(Affiliation)}, - {"nick", Nick}, - {"jid", jlib:jid_to_string(JID)}], - []}. + #xmlel{name = 'item', + attrs = [ + #xmlattr{name = 'role', value = role_to_list(Role)}, + #xmlattr{name = 'affiliation', value = affiliation_to_list(Affiliation)}, + #xmlattr{name = 'nick', value = Nick}, + #xmlattr{name = 'jid', value = exmpp_jid:jid_to_list(JID)}] + }. search_role(Role, StateData) -> lists:filter( @@ -2096,14 +2133,14 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> case find_changed_items(UJID, UAffiliation, URole, Items, Lang, StateData, []) of {result, Res} -> ?INFO_MSG("Processing MUC admin query from ~s in room ~s:~n ~p", - [jlib:jid_to_string(UJID), jlib:jid_to_string(StateData#state.jid), Res]), + [exmpp_jid:jid_to_list(UJID), exmpp_jid:jid_to_list(StateData#state.jid), Res]), NSD = lists:foldl( fun(E, SD) -> case catch ( case E of {JID, affiliation, owner, _} - when (JID#jid.luser == "") -> + when (JID#jid.lnode == "") -> %% If the provided JID does not have username, %% forget the affiliation completely SD; @@ -2174,27 +2211,28 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) -> {result, Res}; -find_changed_items(UJID, UAffiliation, URole, [{xmlcdata, _} | Items], +find_changed_items(UJID, UAffiliation, URole, [#xmlcdata{} | Items], 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], + [#xmlel{name = 'item'} = Item | Items], Lang, StateData, Res) -> - TJID = case xml:get_attr("jid", Attrs) of - {value, S} -> - case jlib:string_to_jid(S) of - error -> + TJID = case exmpp_xml:get_attribute(Item, 'jid',false) of + S when S =/= false -> + try exmpp_jid:list_to_jid(S) of + J -> + {value, J} + catch + _:_ -> ErrText = io_lib:format( translate:translate( Lang, "JID ~s is invalid"), [S]), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> - {value, J} + {error, ?ERR(Item, 'not-acceptable', Lang, ErrText)} end; _ -> - case xml:get_attr("nick", Attrs) of - {value, N} -> + case exmpp_xml:get_attribute(Item, 'nick', false) of + N when N =/= false -> case find_jid_by_nick(N, StateData) of false -> ErrText = @@ -2203,24 +2241,24 @@ find_changed_items(UJID, UAffiliation, URole, Lang, "Nickname ~s does not exist in the room"), [N]), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + {error, ?ERR(Item, 'not-acceptable', Lang, ErrText)}; J -> {value, J} end; _ -> - {error, ?ERR_BAD_REQUEST} + {error, 'bad-request'} end end, case TJID of {value, JID} -> TAffiliation = get_affiliation(JID, StateData), TRole = get_role(JID, StateData), - case xml:get_attr("role", Attrs) of + case exmpp_xml:get_attribute(Item, 'role',false) of false -> - case xml:get_attr("affiliation", Attrs) of + case exmpp_xml:get_attribute(Item, 'affiliation', false) of false -> - {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> + {error, 'bad-request'}; + StrAffiliation -> case catch list_to_affiliation(StrAffiliation) of {'EXIT', _} -> ErrText1 = @@ -2229,7 +2267,7 @@ find_changed_items(UJID, UAffiliation, URole, Lang, "Invalid affiliation: ~s"), [StrAffiliation]), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; + {error, ?ERR(Item, 'not-acceptable', Lang, ErrText1)}; SAffiliation -> ServiceAf = get_service_affiliation(JID, StateData), CanChangeRA = @@ -2246,8 +2284,8 @@ find_changed_items(UJID, UAffiliation, URole, case search_affiliation( owner, StateData) of [{OJID, _}] -> - jlib:jid_remove_resource(OJID) /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); + jlib:short_bare_jid(OJID) /= + jlib:short_prepd_bare_jid(UJID); _ -> true end; @@ -2266,18 +2304,18 @@ find_changed_items(UJID, UAffiliation, URole, UJID, UAffiliation, URole, Items, Lang, StateData, - [{jlib:jid_remove_resource(JID), + [{exmpp_jid:jid_to_bare_jid(JID), affiliation, SAffiliation, - xml:get_path_s( - Item, [{elem, "reason"}, + exmpp_xml:get_path( + Item, [{element, 'reason'}, cdata])} | Res]); false -> - {error, ?ERR_NOT_ALLOWED} + {error, 'not-allowed'} end end end; - {value, StrRole} -> + StrRole -> case catch list_to_role(StrRole) of {'EXIT', _} -> ErrText1 = @@ -2286,7 +2324,7 @@ find_changed_items(UJID, UAffiliation, URole, Lang, "Invalid role: ~s"), [StrRole]), - {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; + {error, ?ERR(Item, 'bad-request', Lang, ErrText1)}; SRole -> ServiceAf = get_service_affiliation(JID, StateData), CanChangeRA = @@ -2303,8 +2341,8 @@ find_changed_items(UJID, UAffiliation, URole, case search_affiliation( owner, StateData) of [{OJID, _}] -> - jlib:jid_remove_resource(OJID) /= - jlib:jid_tolower(jlib:jid_remove_resource(UJID)); + jlib:short_bare_jid(OJID) /= + jlib:short_prepd_bare_jid(UJID); _ -> true end; @@ -2324,11 +2362,11 @@ find_changed_items(UJID, UAffiliation, URole, UAffiliation, URole, Items, Lang, StateData, [{JID, role, SRole, - xml:get_path_s( - Item, [{elem, "reason"}, + exmpp_xml:get_path( + Item, [{element, 'reason'}, cdata])} | Res]); _ -> - {error, ?ERR_NOT_ALLOWED} + {error, 'not-allowed'} end end end; @@ -2337,7 +2375,7 @@ find_changed_items(UJID, UAffiliation, URole, end; find_changed_items(_UJID, _UAffiliation, _URole, _Items, _Lang, _StateData, _Res) -> - {error, ?ERR_BAD_REQUEST}. + {error, 'bad-request'}. can_change_ra(_FAffiliation, _FRole, @@ -2487,9 +2525,9 @@ can_change_ra(_FAffiliation, _FRole, send_kickban_presence(JID, Reason, Code, StateData) -> - LJID = jlib:jid_tolower(JID), + LJID = jlib:short_prepd_jid(JID), LJIDs = case LJID of - {U, S, ""} -> + {U, S, undefined} -> ?DICT:fold( fun(J, _, Js) -> case J of @@ -2517,27 +2555,35 @@ send_kickban_presence(JID, Reason, Code, StateData) -> send_kickban_presence1(UJID, Reason, Code, StateData) -> {ok, #user{jid = _RealJID, - nick = Nick}} = - ?DICT:find(jlib:jid_tolower(UJID), StateData#state.users), - Affiliation = get_affiliation(UJID, StateData), + nick = Nick}} = ?DICT:find(UJID, StateData#state.users), + {N,D,R} = UJID, + Affiliation = get_affiliation(exmpp_jid:make_jid(N,D,R), StateData), SAffiliation = affiliation_to_list(Affiliation), lists:foreach( fun({_LJID, Info}) -> - ItemAttrs = [{"affiliation", SAffiliation}, - {"role", "none"}], + ItemAttrs = [#xmlattr{name = 'affiliation', value = SAffiliation}, + #xmlattr{name = 'role', value = "none"}], ItemEls = case Reason of - "" -> + <<>> -> []; _ -> - [{xmlelement, "reason", [], - [{xmlcdata, Reason}]}] - end, - Packet = {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, ItemEls}, - {xmlelement, "status", [{"code", Code}], []}]}]}, + [#xmlel{name = 'reason', + children = [#xmlcdata{cdata = Reason}]}] + end, + Packet = + #xmlel{ns = ?NS_JABBER_CLIENT, + name = 'presence', + attrs = [#xmlattr{name = 'type', value = "unavailable"}], + children = [#xmlel{ns = ?NS_MUC_USER, name = 'x', + children = [ + #xmlel{ns = ?NS_MUC_USER, name = 'item', + attrs = ItemAttrs, + children = ItemEls}, + #xmlel{ns = ?NS_MUC_USER, name = 'status', + attrs = [#xmlattr{name='code', + value = Code}]}]}]}, ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet) end, ?DICT:to_list(StateData#state.users)). @@ -2551,47 +2597,44 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> FAffiliation = get_affiliation(From, StateData), case FAffiliation of owner -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> - case {xml:get_tag_attr_s("xmlns", XEl), - xml:get_tag_attr_s("type", XEl)} of - {?NS_XDATA, "cancel"} -> + case exmpp_xml:get_child_elements(SubEl) of + [#xmlel{ns = XMLNS, name = 'x'} = XEl] -> + case {XMLNS, exmpp_xml:get_attribute(XEl, 'type',false)} of + {?NS_DATA_FORMS, "cancel"} -> {result, [], StateData}; - {?NS_XDATA, "submit"} -> + {?NS_DATA_FORMS, "submit"} -> case {check_allowed_log_change(XEl, StateData, From), check_allowed_persistent_change(XEl, StateData, From)} of {allow, allow} -> set_config(XEl, StateData); - _ -> {error, ?ERR_BAD_REQUEST} + _ -> {error, 'bad-request'} end; _ -> - {error, ?ERR_BAD_REQUEST} + {error, 'bad-request'} end; - [{xmlelement, "destroy", _Attrs1, _Els1} = SubEl1] -> + [#xmlel{name = 'destroy'} = SubEl1] -> ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", - [jlib:jid_to_string(StateData#state.jid), jlib:jid_to_string(From)]), + [exmpp_jid:jid_to_list(StateData#state.jid), exmpp_jid:jid_to_list(From)]), destroy_room(SubEl1, StateData); Items -> process_admin_items_set(From, Items, Lang, StateData) end; _ -> ErrText = "Owner privileges required", - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + {error, ?ERR(SubEl, 'forbidden', Lang, ErrText)} end; process_iq_owner(From, get, Lang, SubEl, StateData) -> FAffiliation = get_affiliation(From, StateData), case FAffiliation of owner -> - {xmlelement, _Name, _Attrs, Els} = SubEl, - case xml:remove_cdata(Els) of + case exmpp_xml:get_child_elements(SubEl) of [] -> get_config(Lang, StateData, From); [Item] -> - case xml:get_tag_attr("affiliation", Item) of + case exmpp_xml:get_attribute(Item, 'affiliation',false) of false -> - {error, ?ERR_BAD_REQUEST}; - {value, StrAffiliation} -> + {error, 'bad-request'}; + StrAffiliation -> case catch list_to_affiliation(StrAffiliation) of {'EXIT', _} -> ErrText = @@ -2600,7 +2643,7 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> Lang, "Invalid affiliation: ~s"), [StrAffiliation]), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + {error, ?ERR(SubEl, 'not-acceptable', Lang, ErrText)}; SAffiliation -> Items = items_with_affiliation( SAffiliation, StateData), @@ -2608,11 +2651,11 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> end end; _ -> - {error, ?ERR_FEATURE_NOT_IMPLEMENTED} + {error, 'feature-not-implemented'} end; _ -> ErrText = "Owner privileges required", - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + {error, ?ERR(SubEl, 'forbidden', Lang, ErrText)} end. check_allowed_log_change(XEl, StateData, From) -> @@ -2636,10 +2679,13 @@ check_allowed_persistent_change(XEl, StateData, From) -> end. -define(XFIELD(Type, Label, Var, Val), - {xmlelement, "field", [{"type", Type}, - {"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = 'field', + attrs = [#xmlattr{name = 'type', value = Type}, + #xmlattr{name = 'label', value = translate:translate(Lang, Label)}, + #xmlattr{name = 'var', value = Var}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = Val} ]}]}). + -define(BOOLXFIELD(Label, Var, Val), ?XFIELD("boolean", Label, Var, @@ -2654,19 +2700,19 @@ check_allowed_persistent_change(XEl, StateData, From) -> -define(PRIVATEXFIELD(Label, Var, Val), ?XFIELD("text-private", Label, Var, Val)). - get_config(Lang, StateData, From) -> {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access, ServiceMaxUsers = get_service_max_users(StateData), Config = StateData#state.config, Res = - [{xmlelement, "title", [], - [{xmlcdata, translate:translate(Lang, "Configuration for ") ++ - jlib:jid_to_string(StateData#state.jid)}]}, - {xmlelement, "field", [{"type", "hidden"}, - {"var", "FORM_TYPE"}], - [{xmlelement, "value", [], - [{xmlcdata, "http://jabber.org/protocol/muc#roomconfig"}]}]}, + [#xmlel{name = 'title', children = [ #xmlcdata{cdata = + translate:translate(Lang, "Configuration for ") ++ + exmpp_jid:jid_to_list(StateData#state.jid)}]}, + #xmlel{name = 'field', attrs = [#xmlattr{name = 'type', value = "hidden"}, + #xmlattr{name = 'var', value = "FORM_TYPE"}], + children = [#xmlel{name = 'value', children = [#xmlcdata{cdata = + <<"http://jabber.org/protocol/muc#roomconfig">> + }]}]}, ?STRINGXFIELD("Room title", "muc#roomconfig_roomname", Config#config.title), @@ -2697,73 +2743,85 @@ get_config(Lang, StateData, From) -> true -> Config#config.password; false -> "" end), - {xmlelement, "field", - [{"type", "list-single"}, - {"label", translate:translate(Lang, "Maximum Number of Occupants")}, - {"var", "muc#roomconfig_maxusers"}], - [{xmlelement, "value", [], [{xmlcdata, - case get_max_users(StateData) of - N when is_integer(N) -> - erlang:integer_to_list(N); - _ -> "none" - end - }]}] ++ + #xmlel{name = 'field', attrs = [ + #xmlattr{name = 'type', value = "list-single"}, + #xmlattr{name = 'label', value = translate:translate(Lang, + "Maximum Number of Occupants")}, + #xmlattr{name = 'var', value = "muc#roomconfig_maxusers"}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = + case get_max_users(StateData) of + N when is_integer(N) -> + erlang:integer_to_list(N); + _ -> "none" + end}]}] ++ if is_integer(ServiceMaxUsers) -> []; true -> - [{xmlelement, "option", - [{"label", translate:translate(Lang, "No limit")}], - [{xmlelement, "value", [], [{xmlcdata, "none"}]}]}] + [#xmlel{name = 'option', attrs = [#xmlattr{name = 'label', + value = translate:translate(Lang, "No limit")}], + children = [#xmlel{name = 'value', children = [#xmlcdata{ + cdata = <<"none">>}]}]}] end ++ - [{xmlelement, "option", [{"label", erlang:integer_to_list(N)}], - [{xmlelement, "value", [], - [{xmlcdata, erlang:integer_to_list(N)}]}]} || - N <- ?MAX_USERS_DEFAULT_LIST, N =< ServiceMaxUsers] - }, - {xmlelement, "field", - [{"type", "list-single"}, - {"label", translate:translate(Lang, "Present real JIDs to")}, - {"var", "muc#roomconfig_whois"}], - [{xmlelement, "value", [], [{xmlcdata, - if Config#config.anonymous -> - "moderators"; - true -> - "anyone" - end}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "moderators only")}], - [{xmlelement, "value", [], [{xmlcdata, "moderators"}]}]}, - {xmlelement, "option", [{"label", translate:translate(Lang, "anyone")}], - [{xmlelement, "value", [], [{xmlcdata, "anyone"}]}]}]}, - ?BOOLXFIELD("Make room members-only", - "muc#roomconfig_membersonly", - Config#config.members_only), - ?BOOLXFIELD("Make room moderated", - "muc#roomconfig_moderatedroom", - Config#config.moderated), - ?BOOLXFIELD("Default users as participants", - "members_by_default", - Config#config.members_by_default), - ?BOOLXFIELD("Allow users to change subject", - "muc#roomconfig_changesubject", - Config#config.allow_change_subj), - ?BOOLXFIELD("Allow users to send private messages", - "allow_private_messages", - Config#config.allow_private_messages), - ?BOOLXFIELD("Allow users to query other users", - "allow_query_users", - Config#config.allow_query_users), - ?BOOLXFIELD("Allow users to send invites", - "muc#roomconfig_allowinvites", - Config#config.allow_user_invites), - ?BOOLXFIELD("Allow visitors to send status text in presence updates", - "muc#roomconfig_allowvisitorstatus", - Config#config.allow_visitor_status), - ?BOOLXFIELD("Allow visitors to change nickname", - "muc#roomconfig_allowvisitornickchange", - Config#config.allow_visitor_nickchange) - ] ++ + [#xmlel{name = 'option', attrs = [#xmlattr{name = 'label', + value = erlang:integer_to_list(N)}], + children = [#xmlel{name = 'value', children = [ + #xmlcdata{cdata = erlang:integer_to_list(N)}]}]} || + N <- ?MAX_USERS_DEFAULT_LIST, N =< ServiceMaxUsers]}, + #xmlel{name = 'field', attrs = [ + #xmlattr{name = 'type', value = "list-single"}, + #xmlattr{name = 'label', + value = translate:translate(Lang, "Present real JIDs to")}, + #xmlattr{name = 'var', value = "muc#roomconfig_whois"}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = + if Config#config.anonymous -> <<"moderators">>; + true -> <<"anyone">> + end}]}, + #xmlel{name = 'option', attrs = [ + #xmlattr{name = 'label', value = + translate:translate(Lang, "moderators only")}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = + <<"moderators">>}]}]}, + #xmlel{name = 'option', attrs = [ + #xmlattr{name = 'label', value = + translate:translate(Lang, "anyone")}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = + <<"anyone">>}]}]}]}, + ?BOOLXFIELD("Make room members-only", + "muc#roomconfig_membersonly", + Config#config.members_only), + ?BOOLXFIELD("Make room moderated", + "muc#roomconfig_moderatedroom", + Config#config.moderated), + ?BOOLXFIELD("Default users as participants", + "members_by_default", + Config#config.members_by_default), + ?BOOLXFIELD("Allow users to change subject", + "muc#roomconfig_changesubject", + Config#config.allow_change_subj), + ?BOOLXFIELD("Allow users to send private messages", + "allow_private_messages", + Config#config.allow_private_messages), + ?BOOLXFIELD("Allow users to query other users", + "allow_query_users", + Config#config.allow_query_users), + ?BOOLXFIELD("Allow users to send invites", + "muc#roomconfig_allowinvites", + Config#config.allow_user_invites), + ?BOOLXFIELD("Allow visitors to send status text in presence updates", + "muc#roomconfig_allowvisitorstatus", + Config#config.allow_visitor_status), + ?BOOLXFIELD("Allow visitors to change nickname", + "muc#roomconfig_allowvisitornickchange", + Config#config.allow_visitor_nickchange) + ] ++ + + case mod_muc_log:check_access_log( - StateData#state.server_host, From) of + StateData#state.server_host, From) of allow -> [?BOOLXFIELD( "Enable logging", @@ -2771,14 +2829,13 @@ get_config(Lang, StateData, From) -> Config#config.logging)]; _ -> [] end, - {result, [{xmlelement, "instructions", [], - [{xmlcdata, - translate:translate( - Lang, "You need an x:data capable client to configure room")}]}, - {xmlelement, "x", [{"xmlns", ?NS_XDATA}, - {"type", "form"}], - Res}], - StateData}. + {result , [#xmlel{name = 'instructions', children = [ + #xmlcdata{cdata = translate:translate(Lang, + "You need an x:data capable client to configure room")}]}, + #xmlel{ns = ?NS_DATA_FORMS, name = 'x', + attrs = [#xmlattr{name = 'type', value = "form"}], + children = Res}], + StateData}. @@ -2786,7 +2843,7 @@ set_config(XEl, StateData) -> XData = jlib:parse_xdata_submit(XEl), case XData of invalid -> - {error, ?ERR_BAD_REQUEST}; + {error, 'bad-request'}; _ -> case set_xoption(XData, StateData#state.config) of #config{} = Config -> @@ -2805,7 +2862,7 @@ set_config(XEl, StateData) -> "false" -> set_xoption(Opts, Config#config{Opt = false}); "1" -> set_xoption(Opts, Config#config{Opt = true}); "true" -> set_xoption(Opts, Config#config{Opt = true}); - _ -> {error, ?ERR_BAD_REQUEST} + _ -> {error, 'bad-request'} end). -define(SET_NAT_XOPT(Opt, Val), @@ -2814,7 +2871,7 @@ set_config(XEl, StateData) -> I > 0 -> set_xoption(Opts, Config#config{Opt = I}); _ -> - {error, ?ERR_BAD_REQUEST} + {error, 'bad-request'} end). -define(SET_STRING_XOPT(Opt, Val), @@ -2864,7 +2921,7 @@ set_xoption([{"muc#roomconfig_whois", [Val]} | Opts], Config) -> "anyone" -> ?SET_BOOL_XOPT(anonymous, "0"); _ -> - {error, ?ERR_BAD_REQUEST} + {error, 'bad-request'} end; set_xoption([{"muc#roomconfig_maxusers", [Val]} | Opts], Config) -> case Val of @@ -2879,7 +2936,7 @@ set_xoption([{"FORM_TYPE", _} | Opts], Config) -> %% Ignore our FORM_TYPE set_xoption(Opts, Config); set_xoption([_ | _Opts], _Config) -> - {error, ?ERR_BAD_REQUEST}. + {error, 'bad-request'}. change_config(Config, StateData) -> @@ -2997,13 +3054,18 @@ destroy_room(DEl, StateData) -> lists:foreach( fun({_LJID, Info}) -> Nick = Info#user.nick, - ItemAttrs = [{"affiliation", "none"}, - {"role", "none"}], - Packet = {xmlelement, "presence", [{"type", "unavailable"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, []}, DEl]}]}, + ItemAttrs = [#xmlattr{name = 'affiliation', value = "none"}, + #xmlattr{name = 'role', value = "none"}], + Packet = #xmlel{ns = ?NS_JABBER_CLIENT, + name = 'presence', + attrs = [#xmlattr{name = 'type', + value = "unavailable"}], + children = [ + #xmlel{ns = ?NS_MUC_USER, name = 'x', children = + [#xmlel{name = 'item', attrs = ItemAttrs}, + DEl]}]}, ejabberd_router:route( - jlib:jid_replace_resource(StateData#state.jid, Nick), + jid_replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet) end, ?DICT:to_list(StateData#state.users)), @@ -3021,7 +3083,8 @@ destroy_room(DEl, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Disco --define(FEATURE(Var), {xmlelement, "feature", [{"var", Var}], []}). +-define(FEATURE(Var), #xmlel{name = 'feature', + attrs = [#xmlattr{name = 'var', value = Var}]}). -define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse), case Opt of @@ -3032,16 +3095,19 @@ destroy_room(DEl, StateData) -> end). process_iq_disco_info(_From, set, _Lang, _StateData) -> - {error, ?ERR_NOT_ALLOWED}; + {error, 'not-allowed'}; process_iq_disco_info(_From, get, Lang, StateData) -> Config = StateData#state.config, - {result, [{xmlelement, "identity", - [{"category", "conference"}, - {"type", "text"}, - {"name", get_title(StateData)}], []}, - {xmlelement, "feature", - [{"var", ?NS_MUC}], []}, + {result, [ #xmlel{name = 'identity', + attrs = [#xmlattr{name = 'category', + value = "conference"}, + #xmlattr{name = 'type', value = "text"}, + #xmlattr{name = 'name', + value = get_title(StateData)}]}, + #xmlel{name = 'feature', + attrs = [#xmlattr{name = 'var', value = ?NS_MUC_s}]}, + ?CONFIG_OPT_TO_FEATURE(Config#config.public, "muc_public", "muc_hidden"), ?CONFIG_OPT_TO_FEATURE(Config#config.persistent, @@ -3057,18 +3123,24 @@ process_iq_disco_info(_From, get, Lang, StateData) -> ] ++ iq_disco_info_extras(Lang, StateData), StateData}. -define(RFIELDT(Type, Var, Val), - {xmlelement, "field", [{"type", Type}, {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = 'field', attrs = [#xmlattr{name = 'type', value = Type}, + #xmlattr{name = 'var', value = Var}], + children = [#xmlel{name = 'value', + children = [#xmlcdata{cdata = Val}]}]}). -define(RFIELD(Label, Var, Val), - {xmlelement, "field", [{"label", translate:translate(Lang, Label)}, - {"var", Var}], - [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + #xmlel{name = 'field', attrs = [#xmlattr{name = 'label', value = + translate:translate(Lang, Label)}, + #xmlattr{name = 'var', value = Var}], + children = [#xmlel{name = 'value', children = [ + #xmlcdata{cdata = Val}]}]}). iq_disco_info_extras(Lang, StateData) -> Len = length(?DICT:to_list(StateData#state.users)), RoomDescription = (StateData#state.config)#config.description, - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], + [#xmlel{ns = ?NS_DATA_FORMS, name = 'x', + attrs = [#xmlattr{name = 'type', value = "result"}], + children = [?RFIELDT("hidden", "FORM_TYPE", "http://jabber.org/protocol/muc#roominfo"), ?RFIELD("Room description", "muc#roominfo_description", @@ -3078,7 +3150,7 @@ iq_disco_info_extras(Lang, StateData) -> ]}]. process_iq_disco_items(_From, set, _Lang, _StateData) -> - {error, ?ERR_NOT_ALLOWED}; + {error, 'not-allowed'}; process_iq_disco_items(From, get, _Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), @@ -3092,17 +3164,18 @@ process_iq_disco_items(From, get, _Lang, StateData) -> lists:map( fun({_LJID, Info}) -> Nick = Info#user.nick, - {xmlelement, "item", - [{"jid", jlib:jid_to_string( - {StateData#state.room, - StateData#state.host, - Nick})}, - {"name", Nick}], []} + #xmlel{name = 'item', attrs = [#xmlattr{name = 'jid', + value = exmpp_jid:jid_to_list( + StateData#state.room, + StateData#state.host, + Nick)}, + #xmlattr{name = 'name', + value = Nick}]} end, ?DICT:to_list(StateData#state.users)), {result, UList, StateData}; _ -> - {error, ?ERR_FORBIDDEN} + {error, 'forbidden'} end. get_title(StateData) -> @@ -3121,72 +3194,72 @@ check_invitation(From, Els, Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), CanInvite = (StateData#state.config)#config.allow_user_invites orelse (FAffiliation == admin) orelse (FAffiliation == owner), - InviteEl = case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, Els1} = XEl] -> - case xml:get_tag_attr_s("xmlns", XEl) of - ?NS_MUC_USER -> - ok; - _ -> - throw({error, ?ERR_BAD_REQUEST}) - end, - case xml:remove_cdata(Els1) of - [{xmlelement, "invite", _Attrs2, _Els2} = InviteEl1] -> - InviteEl1; - _ -> - throw({error, ?ERR_BAD_REQUEST}) - end; - _ -> - throw({error, ?ERR_BAD_REQUEST}) - end, - JID = case jlib:string_to_jid( - xml:get_tag_attr_s("to", InviteEl)) of - error -> - throw({error, ?ERR_JID_MALFORMED}); - JID1 -> - JID1 - end, + InviteEl = case Els of + [#xmlel{ns = XMLNS, name = 'x'} = XEl] -> + case XMLNS of + ?NS_MUC_USER -> ok; + _ -> throw({error, 'bad-request'}) + end, + case exmpp_xml:get_child_elements(XEl) of + [#xmlel{name = 'invite'} = InviteEl1] -> + InviteEl1; + _ -> + throw({error, 'bad-request'}) + end; + _ -> + throw({error, 'bad-request'}) + end, + JID = try exmpp_jid:list_to_jid(exmpp_xml:get_attribute(InviteEl, + 'to', + false)) of + JID1 -> JID1 + catch + _:_ -> + throw({error, 'jid-malformed'}) + end, case CanInvite of false -> - throw({error, ?ERR_NOT_ALLOWED}); + throw({error, 'not-allowed'}); true -> Reason = - xml:get_path_s( + exmpp_xml:get_path( InviteEl, - [{elem, "reason"}, cdata]), + [{element, 'reason'}, cdata]), ContinueEl = - case xml:get_path_s( + case xml:get_path( InviteEl, - [{elem, "continue"}]) of + [{element, 'continue'}]) of [] -> []; Continue1 -> [Continue1] end, IEl = - [{xmlelement, "invite", - [{"from", - jlib:jid_to_string(From)}], - [{xmlelement, "reason", [], - [{xmlcdata, Reason}]}] ++ ContinueEl}], + [#xmlel{name = 'invite', + attrs = [#xmlattr{name = 'from', + value = exmpp_jid:jid_to_list(From)}], + children = [#xmlel{name = 'reason', + children = [#xmlcdata{cdata = Reason} ]}] + ++ ContinueEl}], PasswdEl = case (StateData#state.config)#config.password_protected of true -> - [{xmlelement, "password", [], - [{xmlcdata, (StateData#state.config)#config.password}]}]; + [#xmlel{name = 'password', + children = [#xmlcdata{cdata = + (StateData#state.config)#config.password}]}]; _ -> [] end, Body = - {xmlelement, "body", [], - [{xmlcdata, - lists:flatten( - io_lib:format( - translate:translate( - Lang, - "~s invites you to the room ~s"), - [jlib:jid_to_string(From), - jlib:jid_to_string({StateData#state.room, - StateData#state.host, - ""}) - ])) ++ + #xmlel{name = 'body', + children = [#xmlcdata{cdata = + lists:flatten( + io_lib:format( + translate:translate(Lang, + "~s invites you to the room ~s"), + [exmpp_jid:jid_to_list(From), + exmpp_jid:jid_to_list(StateData#state.room, + StateData#state.host, + "") + ])) ++ case (StateData#state.config)#config.password_protected of true -> ", " ++ @@ -3202,17 +3275,18 @@ check_invitation(From, Els, Lang, StateData) -> end }]}, Msg = - {xmlelement, "message", - [{"type", "normal"}], - [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], IEl ++ PasswdEl}, - {xmlelement, "x", - [{"xmlns", ?NS_XCONFERENCE}, - {"jid", jlib:jid_to_string( - {StateData#state.room, - StateData#state.host, - ""})}], - [{xmlcdata, Reason}]}, - Body]}, + #xmlel{name = 'message', + attrs = [#xmlattr{name = 'type', value = "normal"}], + children = [#xmlel{ns = ?NS_MUC_USER, name = 'x', + children = IEl ++ PasswdEl}, + #xmlel{ns = 'jabber:x:conference', name = 'x', + attrs = [#xmlattr{name = 'jid', + value = exmpp_jid:jid_to_list( + StateData#state.room, + StateData#state.host, + "")}], + children = [#xmlcdata{cdata = Reason}]}, + Body]}, ejabberd_router:route(StateData#state.jid, JID, Msg), JID end. @@ -3233,36 +3307,37 @@ handle_roommessage_from_nonparticipant(Packet, Lang, StateData, From) -> %% This function must be catched, %% because it crashes when the packet is not a decline message. check_decline_invitation(Packet) -> - {xmlelement, "message", _, _} = Packet, - XEl = xml:get_subtag(Packet, "x"), - ?NS_MUC_USER = xml:get_tag_attr_s("xmlns", XEl), - DEl = xml:get_subtag(XEl, "decline"), - ToString = xml:get_tag_attr_s("to", DEl), - ToJID = jlib:string_to_jid(ToString), + #xmlel{name = 'message'} = Packet, + #xmlel{ns = ?NS_MUC_USER} = XEl = exmpp_xml:get_element(Packet, 'x'), + DEl = exmpp_xml:get_element(XEl, 'decline'), + ToString = exmpp_xml:get_attribute(DEl, 'to', false), + ToJID = exmpp_jid:list_to_jid(ToString), {true, {Packet, XEl, DEl, ToJID}}. %% Send the decline to the inviter user. %% The original stanza must be slightly modified. -send_decline_invitation({Packet, XEl, DEl, ToJID}, RoomJID, FromJID) -> - FromString = jlib:jid_to_string(FromJID), - {xmlelement, "decline", DAttrs, DEls} = DEl, - DAttrs2 = lists:keydelete("to", 1, DAttrs), - DAttrs3 = [{"from", FromString} | DAttrs2], - DEl2 = {xmlelement, "decline", DAttrs3, DEls}, - XEl2 = replace_subelement(XEl, DEl2), - Packet2 = replace_subelement(Packet, XEl2), +send_decline_invitation({Packet, XEl, DEl = #xmlel{name='decline'}, ToJID}, + RoomJID, FromJID) -> + FromString = exmpp_jid:jid_to_list(FromJID), + + DEl1 = exmpp_xml:remove_attribute(DEl, 'to'), + DEl2 = exmpp_xml:set_attribute(DEl1, 'from',FromString), + XEl2 = replace_subelement(XEl,DEl2), + Packet2 = replace_subelement(Packet,XEl2), ejabberd_router:route(RoomJID, ToJID, Packet2). %% Given an element and a new subelement, %% replace the instance of the subelement in element with the new subelement. -replace_subelement({xmlelement, Name, Attrs, SubEls}, NewSubEl) -> - {_, NameNewSubEl, _, _} = NewSubEl, - SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl), - {xmlelement, Name, Attrs, SubEls2}. +replace_subelement(#xmlel{children = Els} = El, #xmlel{name = Name} = NewSubEl) -> + Els2 = lists:map(fun(#xmlel{name = Name2}) when Name2 =:= Name -> NewSubEl; + (S) -> S + end, Els), + exmpp_xml:set_children(El, Els2). send_error_only_occupants(Packet, Lang, RoomJID, From) -> ErrText = "Only occupants are allowed to send messages to the conference", - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + Err = exmpp_stanza:reply_with_error( + Packet, ?ERR(Packet, 'not-acceptable', Lang, ErrText)), ejabberd_router:route(RoomJID, From, Err). @@ -3283,7 +3358,8 @@ add_to_log(Type, Data, StateData) -> %% Users number checking tab_add_online_user(JID, StateData) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), + LUser = JID#jid.lnode, + LServer = JID#jid.ldomain, US = {LUser, LServer}, Room = StateData#state.room, Host = StateData#state.host, @@ -3292,8 +3368,11 @@ tab_add_online_user(JID, StateData) -> #muc_online_users{us = US, room = Room, host = Host}). -tab_remove_online_user(JID, StateData) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), + +tab_remove_online_user(#jid{lnode = LUser, ldomain = LServer}, StateData) -> + tab_remove_online_user({LUser, LServer, none},StateData); + +tab_remove_online_user({LUser, LServer,_}, StateData) -> US = {LUser, LServer}, Room = StateData#state.room, Host = StateData#state.host, @@ -3302,7 +3381,8 @@ tab_remove_online_user(JID, StateData) -> #muc_online_users{us = US, room = Room, host = Host}). tab_count_user(JID) -> - {LUser, LServer, _} = jlib:jid_tolower(JID), + LUser = JID#jid.lnode, + LServer = JID#jid.ldomain, US = {LUser, LServer}, case catch ets:select( muc_online_users, @@ -3312,3 +3392,14 @@ tab_count_user(JID) -> _ -> 0 end. + + +jid_replace_resource(JID, Resource) -> + case exmpp_stringprep:resourceprep(Resource) of + error -> error; + LResource -> + JID#jid{resource = Resource, lresource = LResource} + end. + + +