diff --git a/include/mod_muc_room.hrl b/include/mod_muc_room.hrl index d985f3f3b..fc20f44c6 100644 --- a/include/mod_muc_room.hrl +++ b/include/mod_muc_room.hrl @@ -71,6 +71,7 @@ -type config() :: #config{}. -type role() :: moderator | participant | visitor | none. +-type affiliation() :: admin | member | outcast | owner | none. -record(user, { @@ -120,5 +121,3 @@ host = <<>> :: binary() | '_' | '$2'}). -type muc_online_users() :: #muc_online_users{}. - --type muc_room_state() :: #state{}. diff --git a/include/xmpp_codec.hrl b/include/xmpp_codec.hrl index 7b8275ed5..85601035d 100644 --- a/include/xmpp_codec.hrl +++ b/include/xmpp_codec.hrl @@ -11,13 +11,20 @@ -record(csi, {type :: active | inactive}). -type csi() :: #csi{}. --record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | - 'store' | 'no-permanent-store'}). +-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' | + 'no-permanent-store' | 'no-permanent-storage'}). -type hint() :: #hint{}. -record(feature_register, {}). -type feature_register() :: #feature_register{}. +-record(address, {type :: 'bcc' | 'cc' | 'noreply' | 'ofrom' | 'replyroom' | 'replyto' | 'to', + jid :: any(), + desc :: binary(), + node :: binary(), + delivered :: any()}). +-type address() :: #address{}. + -record(sasl_success, {text :: any()}). -type sasl_success() :: #sasl_success{}. @@ -55,6 +62,9 @@ stored :: non_neg_integer()}). -type expire() :: #expire{}. +-record(muc_unsubscribe, {}). +-type muc_unsubscribe() :: #muc_unsubscribe{}. + -record(pubsub_unsubscribe, {node :: binary(), jid :: any(), subid :: binary()}). @@ -81,7 +91,7 @@ type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}). -type pubsub_affiliation() :: #pubsub_affiliation{}. --record(muc_decline, {reason :: binary(), +-record(muc_decline, {reason = <<>> :: 'undefined' | binary(), from :: any(), to :: any()}). -type muc_decline() :: #muc_decline{}. @@ -90,9 +100,20 @@ xmlns :: binary()}). -type sm_a() :: #sm_a{}. +-record(muc_subscribe, {nick :: binary(), + events = [] :: [binary()]}). +-type muc_subscribe() :: #muc_subscribe{}. + +-record(stanza_id, {by :: any(), + id :: binary()}). +-type stanza_id() :: #stanza_id{}. + -record(starttls_proceed, {}). -type starttls_proceed() :: #starttls_proceed{}. +-record(client_id, {id :: binary()}). +-type client_id() :: #client_id{}. + -record(sm_resumed, {h :: non_neg_integer(), previd :: binary(), xmlns :: binary()}). @@ -116,9 +137,19 @@ -record(gone, {uri :: binary()}). -type gone() :: #gone{}. +-record(x_conference, {jid :: any(), + password = <<>> :: binary(), + reason = <<>> :: binary(), + continue :: any(), + thread = <<>> :: binary()}). +-type x_conference() :: #x_conference{}. + -record(private, {xml_els = [] :: [any()]}). -type private() :: #private{}. +-record(nick, {name :: binary()}). +-type nick() :: #nick{}. + -record(p1_ack, {}). -type p1_ack() :: #p1_ack{}. @@ -163,6 +194,9 @@ error = [] :: [{integer(),'undefined' | binary()}]}). -type stat() :: #stat{}. +-record(addresses, {list = [] :: [#address{}]}). +-type addresses() :: #addresses{}. + -record('see-other-host', {host :: binary()}). -type 'see-other-host'() :: #'see-other-host'{}. @@ -194,6 +228,9 @@ -record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}). -type pubsub_event() :: #pubsub_event{}. +-record(muc_unique, {name = <<>> :: binary()}). +-type muc_unique() :: #muc_unique{}. + -record(sasl_response, {text :: any()}). -type sasl_response() :: #sasl_response{}. @@ -217,19 +254,11 @@ -record(feature_csi, {xmlns :: binary()}). -type feature_csi() :: #feature_csi{}. --record(muc_user_destroy, {reason :: binary(), - jid :: any()}). --type muc_user_destroy() :: #muc_user_destroy{}. - -record(disco_item, {jid :: any(), name :: binary(), node :: binary()}). -type disco_item() :: #disco_item{}. --record(disco_items, {node :: binary(), - items = [] :: [#disco_item{}]}). --type disco_items() :: #disco_items{}. - -record(unblock, {items = [] :: [any()]}). -type unblock() :: #unblock{}. @@ -239,10 +268,8 @@ -record(compression, {methods = [] :: [binary()]}). -type compression() :: #compression{}. --record(muc_owner_destroy, {jid :: any(), - reason :: binary(), - password :: binary()}). --type muc_owner_destroy() :: #muc_owner_destroy{}. +-record(muc_subscriptions, {list = [] :: [any()]}). +-type muc_subscriptions() :: #muc_subscriptions{}. -record(pubsub_subscription, {jid :: any(), node :: binary(), @@ -252,7 +279,7 @@ -record(muc_item, {actor :: #muc_actor{}, continue :: binary(), - reason :: binary(), + reason = <<>> :: 'undefined' | binary(), affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner', role :: 'moderator' | 'none' | 'participant' | 'visitor', jid :: any(), @@ -267,8 +294,8 @@ -record(mam_prefs, {xmlns :: binary(), default :: 'always' | 'never' | 'roster', - always = [] :: [any()], - never = [] :: [any()]}). + always :: [any()], + never :: [any()]}). -type mam_prefs() :: #mam_prefs{}. -record(caps, {node :: binary(), @@ -350,13 +377,17 @@ -record(block_list, {items = [] :: [any()]}). -type block_list() :: #block_list{}. +-record(xdata_option, {label :: binary(), + value :: binary()}). +-type xdata_option() :: #xdata_option{}. + -record(xdata_field, {label :: binary(), type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single', var :: binary(), required = false :: boolean(), desc :: binary(), values = [] :: [binary()], - options = [] :: [binary()]}). + options = [] :: [#xdata_option{}]}). -type xdata_field() :: #xdata_field{}. -record(version, {name :: binary(), @@ -364,11 +395,6 @@ os :: binary()}). -type version() :: #version{}. --record(muc_invite, {reason :: binary(), - from :: any(), - to :: any()}). --type muc_invite() :: #muc_invite{}. - -record(bind, {jid :: any(), resource :: any()}). -type bind() :: #bind{}. @@ -376,13 +402,11 @@ -record(rosterver_feature, {}). -type rosterver_feature() :: #rosterver_feature{}. --record(muc_user, {decline :: #muc_decline{}, - destroy :: #muc_user_destroy{}, - invites = [] :: [#muc_invite{}], - items = [] :: [#muc_item{}], - status_codes = [] :: [pos_integer()], - password :: binary()}). --type muc_user() :: #muc_user{}. +-record(muc_invite, {reason = <<>> :: 'undefined' | binary(), + from :: any(), + to :: any(), + continue :: binary()}). +-type muc_invite() :: #muc_invite{}. -record(carbons_disable, {}). -type carbons_disable() :: #carbons_disable{}. @@ -400,7 +424,7 @@ -type vcard_org() :: #vcard_org{}. -record(rsm_set, {'after' :: binary(), - before :: 'none' | binary(), + before :: binary(), count :: non_neg_integer(), first :: #rsm_first{}, index :: non_neg_integer(), @@ -414,6 +438,11 @@ complete :: any()}). -type mam_fin() :: #mam_fin{}. +-record(disco_items, {node :: binary(), + items = [] :: [#disco_item{}], + rsm :: #rsm_set{}}). +-type disco_items() :: #disco_items{}. + -record(vcard_tel, {home = false :: boolean(), work = false :: boolean(), voice = false :: boolean(), @@ -430,6 +459,20 @@ number :: binary()}). -type vcard_tel() :: #vcard_tel{}. +-record(muc_destroy, {xmlns :: binary(), + jid :: any(), + reason = <<>> :: 'undefined' | binary(), + password :: binary()}). +-type muc_destroy() :: #muc_destroy{}. + +-record(muc_user, {decline :: #muc_decline{}, + destroy :: #muc_destroy{}, + invites = [] :: [#muc_invite{}], + items = [] :: [#muc_item{}], + status_codes = [] :: [pos_integer()], + password :: binary()}). +-type muc_user() :: #muc_user{}. + -record(vcard_key, {type :: binary(), cred :: binary()}). -type vcard_key() :: #vcard_key{}. @@ -530,14 +573,11 @@ start :: any(), 'end' :: any(), with :: any(), + withtext :: binary(), rsm :: #rsm_set{}, xdata :: #xdata{}}). -type mam_query() :: #mam_query{}. --record(muc_owner, {destroy :: #muc_owner_destroy{}, - config :: #xdata{}}). --type muc_owner() :: #muc_owner{}. - -record(pubsub_options, {node :: binary(), jid :: any(), subid :: binary(), @@ -592,6 +632,11 @@ fetch = false :: boolean()}). -type offline() :: #offline{}. +-record(muc_owner, {destroy :: #muc_destroy{}, + config :: #xdata{}, + items = [] :: [#muc_item{}]}). +-type muc_owner() :: #muc_owner{}. + -record(sasl_mechanisms, {list = [] :: [binary()]}). -type sasl_mechanisms() :: #sasl_mechanisms{}. @@ -709,6 +754,7 @@ -type xmpp_element() :: compression() | pubsub_subscription() | + xdata_option() | version() | pubsub_affiliation() | muc_admin() | @@ -726,7 +772,9 @@ rsm_set() | 'see-other-host'() | hint() | + stanza_id() | starttls_proceed() | + client_id() | sm_resumed() | forwarded() | xevent() | @@ -742,8 +790,11 @@ pubsub_event_item() | muc_item() | vcard_temp() | + address() | sasl_success() | + addresses() | pubsub_event_items() | + muc_subscriptions() | disco_items() | pubsub_options() | compress() | @@ -751,33 +802,25 @@ muc_history() | identity() | feature_csi() | - muc_user_destroy() | privacy_query() | delay() | vcard_tel() | - vcard_logo() | - disco_info() | vcard_geo() | vcard_photo() | - feature_register() | - register() | - muc_owner() | pubsub() | - sm_r() | + muc_owner() | muc_actor() | - error() | - stream_error() | - muc_user() | - vcard_adr() | carbons_private() | mix_leave() | - muc_invite() | + muc_subscribe() | rosterver_feature() | + muc_invite() | vcard_xupdate() | carbons_disable() | bookmark_conference() | offline() | time() | + muc_unique() | sasl_response() | pubsub_subscribe() | presence() | @@ -786,6 +829,7 @@ starttls_failure() | sasl_challenge() | gone() | + x_conference() | private() | compress_failure() | sasl_failure() | @@ -794,6 +838,7 @@ sm_resume() | carbons_enable() | expire() | + muc_unsubscribe() | pubsub_unsubscribe() | muc_decline() | chatstate() | @@ -803,6 +848,7 @@ search() | pubsub_publish() | unblock() | + nick() | p1_ack() | block() | mix_join() | @@ -832,11 +878,20 @@ starttls() | mam_prefs() | sasl_mechanisms() | + muc_destroy() | vcard_key() | csi() | roster_query() | - muc_owner_destroy() | mam_query() | bookmark_url() | vcard_email() | - vcard_label(). + vcard_label() | + vcard_logo() | + disco_info() | + feature_register() | + register() | + sm_r() | + error() | + stream_error() | + muc_user() | + vcard_adr(). diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index 5924d92c0..83ffd932b 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -72,7 +72,7 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). --spec route(jid(), jid(), xmlel() | xmpp_element()) -> ok. +-spec route(jid(), jid(), xmlel() | stanza()) -> ok. route(From, To, Packet) -> case catch do_route(From, To, Packet) of @@ -85,13 +85,21 @@ route(From, To, Packet) -> %% Route the error packet only if the originating packet is not an error itself. %% RFC3920 9.3.1 --spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok. +-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok; + (jid(), jid(), stanza(), error()) -> ok. -route_error(From, To, ErrPacket, OrigPacket) -> +route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) -> #xmlel{attrs = Attrs} = OrigPacket, case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of false -> route(From, To, ErrPacket); true -> ok + end; +route_error(From, To, Packet, #error{} = Err) -> + Type = xmpp:get_type(Packet), + if Type == error; Type == result -> + ok; + true -> + ejabberd_router:route(From, To, xmpp:make_error(Packet, Err)) end. -spec register_route(binary()) -> term(). @@ -406,11 +414,16 @@ do_route(OrigFrom, OrigTo, OrigPacket) -> end. -spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any(). -do_route(From, To, Packet, - #route{local_hint = {apply, Module, Function}, pid = Pid}) - when is_pid(Pid) andalso node(Pid) == node() -> - try - Module:Function(From, To, xmpp:decode(Packet, [ignore_els])) +do_route(From, To, Packet, #route{local_hint = LocalHint, + pid = Pid}) when is_pid(Pid) -> + try xmpp:decode(Packet, [ignore_els]) of + Pkt -> + case LocalHint of + {apply, Module, Function} when node(Pid) == node() -> + Module:Function(From, To, Pkt); + _ -> + Pid ! {route, From, To, Pkt} + end catch error:{xmpp_codec, Why} -> ?ERROR_MSG("failed to decode xml element ~p when " "routing from ~s to ~s: ~s", @@ -418,8 +431,6 @@ do_route(From, To, Packet, xmpp:format_error(Why)]), drop end; -do_route(From, To, Packet, #route{pid = Pid}) when is_pid(Pid) -> - Pid ! {route, From, To, xmpp:encode(Packet)}; do_route(_From, _To, _Packet, _Route) -> drop. diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 03fdab209..0d3f242a0 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -295,7 +295,7 @@ process_sm_iq_items(#iq{type = get, lang = Lang, end; false -> Txt = <<"Not subscribed">>, - xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)) + xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. -spec get_sm_items({error, error()} | {result, [disco_item()]} | empty, @@ -371,7 +371,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang, end; false -> Txt = <<"Not subscribed">>, - xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)) + xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. -spec get_sm_identity([identity()], jid(), jid(), diff --git a/src/mod_last.erl b/src/mod_last.erl index b267aa89b..6d0edebf0 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -140,7 +140,7 @@ process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) -> end; true -> Txt = <<"Not subscribed">>, - xmpp:make_error(IQ, xmpp:err_not_subscribed(Txt, Lang)) + xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang)) end. %% @spec (LUser::string(), LServer::string()) -> diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 10eb098da..b4ee17720 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -34,12 +34,12 @@ -export([start/2, stop/1, depends/2]). -export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5, - process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5, - remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4, + process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5, + remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2, muc_filter_message/5, message_is_archived/5, delete_old_messages/2, get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("logger.hrl"). -include("mod_muc_room.hrl"). -include("ejabberd_commands.hrl"). @@ -54,17 +54,12 @@ -callback delete_old_messages(binary() | global, erlang:timestamp(), all | chat | groupchat) -> any(). --callback extended_fields() -> [xmlel()]. +-callback extended_fields() -> [xdata_field()]. -callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat, jid(), binary(), recv | send) -> {ok, binary()} | any(). -callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any(). -callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error. --callback select(binary(), jid(), jid(), - none | erlang:timestamp(), - none | erlang:timestamp(), - none | ljid() | {text, binary()}, - none | #rsm_in{}, - chat | groupchat) -> +-callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) -> {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}. %%%=================================================================== @@ -200,14 +195,10 @@ get_room_config(X, RoomState, _From, Lang) -> true -> <<"1">>; _ -> <<"0">> end, - XField = #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"boolean">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}, + XField = #xdata_field{type = boolean, + label = translate:translate(Lang, Label), + var = Var, + values = [Val]}, X ++ [XField]. set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) -> @@ -222,7 +213,7 @@ set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) -> catch _:{case_clause, _} -> Txt = <<"Value of '~s' should be boolean">>, ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)} + {error, xmpp:err_bad_request(Lang, ErrTxt)} end; set_room_option(Acc, _Opt, _Vals, _Lang) -> Acc. @@ -236,16 +227,7 @@ user_receive_packet(Pkt, C2SState, JID, Peer, To) -> NewPkt = strip_my_archived_tag(Pkt, LServer), case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of {ok, ID} -> - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - StanzaID = #xmlel{name = <<"stanza-id">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_SID_0}, - {<<"id">>, ID}]}, - NewEls = [Archived, StanzaID|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; + set_stanza_id(NewPkt, LServer, ID); _ -> NewPkt end; @@ -259,19 +241,10 @@ user_send_packet(Pkt, C2SState, JID, Peer) -> case should_archive(Pkt, LServer) of true -> NewPkt = strip_my_archived_tag(Pkt, LServer), - case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt), + case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer), LUser, LServer, Peer, send) of {ok, ID} -> - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - StanzaID = #xmlel{name = <<"stanza-id">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_SID_0}, - {<<"id">>, ID}]}, - NewEls = [Archived, StanzaID|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; + set_stanza_id(NewPkt, LServer, ID); _ -> NewPkt end; @@ -291,16 +264,7 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState, StorePkt = strip_x_jid_tags(NewPkt), case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of {ok, ID} -> - Archived = #xmlel{name = <<"archived">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_MAM_TMP}, - {<<"id">>, ID}]}, - StanzaID = #xmlel{name = <<"stanza-id">>, - attrs = [{<<"by">>, LServer}, - {<<"xmlns">>, ?NS_SID_0}, - {<<"id">>, ID}]}, - NewEls = [Archived, StanzaID|NewPkt#xmlel.children], - NewPkt#xmlel{children = NewEls}; + set_stanza_id(NewPkt, LServer, ID); _ -> NewPkt end; @@ -308,71 +272,98 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState, Pkt end. +set_stanza_id(Pkt, LServer, ID) -> + Archived = #mam_archived{by = jid:make(LServer), id = ID}, + StanzaID = #stanza_id{by = jid:make(LServer), id = ID}, + NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)], + xmpp:set_els(Pkt, NewEls). + % Query archive v0.2 -process_iq_v0_2(#jid{lserver = LServer} = From, - #jid{lserver = LServer} = To, - #iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> - Fs = parse_query_v0_2(SubEl), - process_iq(LServer, From, To, IQ, SubEl, Fs, chat); -process_iq_v0_2(From, To, IQ) -> - process_iq(From, To, IQ). +process_iq_v0_2(#iq{from = #jid{lserver = LServer}, + to = #jid{lserver = LServer}, + type = get, sub_els = [#mam_query{}]} = IQ) -> + process_iq(LServer, IQ, chat); +process_iq_v0_2(IQ) -> + process_iq(IQ). % Query archive v0.3 -process_iq_v0_3(#jid{lserver = LServer} = From, - #jid{lserver = LServer} = To, - #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) -> - process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat); -process_iq_v0_3(#jid{lserver = LServer}, - #jid{lserver = LServer}, - #iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) -> +process_iq_v0_3(#iq{from = #jid{lserver = LServer}, + to = #jid{lserver = LServer}, + type = set, sub_els = [#mam_query{}]} = IQ) -> + process_iq(LServer, IQ, chat); +process_iq_v0_3(#iq{from = #jid{lserver = LServer}, + to = #jid{lserver = LServer}, + type = get, sub_els = [#mam_query{}]} = IQ) -> process_iq(LServer, IQ); -process_iq_v0_3(From, To, IQ) -> - process_iq(From, To, IQ). +process_iq_v0_3(IQ) -> + process_iq(IQ). -muc_process_iq(#iq{type = set, - sub_el = #xmlel{name = <<"query">>, - attrs = Attrs} = SubEl} = IQ, - MUCState, From, To) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> - muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl)); - _ -> - IQ +muc_process_iq(#iq{type = T, lang = Lang, + from = From, + sub_els = [#mam_query{xmlns = NS}]} = IQ, + MUCState) + when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse + (T == get andalso NS == ?NS_MAM_TMP) -> + case may_enter_room(From, MUCState) of + true -> + LServer = MUCState#state.server_host, + Role = mod_muc_room:get_role(From, MUCState), + process_iq(LServer, IQ, {groupchat, Role, MUCState}); + false -> + Text = <<"Only members may query archives of this room">>, + xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang)) end; muc_process_iq(#iq{type = get, - sub_el = #xmlel{name = <<"query">>, - attrs = Attrs} = SubEl} = IQ, - MUCState, From, To) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MAM_TMP -> - muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl)); - NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> - LServer = MUCState#state.server_host, - process_iq(LServer, IQ); - _ -> - IQ - end; -muc_process_iq(IQ, _MUCState, _From, _To) -> + sub_els = [#mam_query{xmlns = NS}]} = IQ, + MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> + LServer = MUCState#state.server_host, + process_iq(LServer, IQ); +muc_process_iq(IQ, _MUCState) -> IQ. -get_xdata_fields(SubEl) -> - case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA), - fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of - {#xmlel{} = XData, false} -> - jlib:parse_xdata_submit(XData); - {#xmlel{} = XData, #xmlel{}} -> - [{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)]; - {false, #xmlel{}} -> - [{<<"set">>, SubEl}]; - {false, false} -> - [] - end. +parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) -> + try + lists:foldl( + fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) -> + case jlib:datetime_string_to_timestamp(Data) of + undefined -> throw({error, <<"start">>}); + {_, _, _} = TS -> Q#mam_query{start = TS} + end; + (#xdata_field{var = <<"end">>, values = [Data|_]}, Q) -> + case jlib:datetime_string_to_timestamp(Data) of + undefined -> throw({error, <<"end">>}); + {_, _, _} = TS -> Q#mam_query{'end' = TS} + end; + (#xdata_field{var = <<"with">>, values = [Data|_]}, Q) -> + case jid:from_string(Data) of + error -> throw({error, <<"with">>}); + J -> Q#mam_query{with = J} + end; + (#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) -> + case Data of + <<"">> -> throw({error, <<"withtext">>}); + _ -> Q#mam_query{withtext = Data} + end; + (#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) -> + case Query#mam_query.xmlns of + NS -> Q; + _ -> throw({error, <<"FORM_TYPE">>}) + end; + (#xdata_field{}, Acc) -> + Acc + end, Query, Fs) + catch throw:{error, Var} -> + Txt = io_lib:format("Incorrect value of field '~s'", [Var]), + {error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)} + end; +parse_query(Query, _Lang) -> + Query. disco_sm_features(empty, From, To, Node, Lang) -> disco_sm_features({result, []}, From, To, Node, Lang); disco_sm_features({result, OtherFeatures}, #jid{luser = U, lserver = S}, - #jid{luser = U, lserver = S}, <<>>, _Lang) -> + #jid{luser = U, lserver = S}, undefined, _Lang) -> {result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]}; disco_sm_features(Acc, _From, _To, _Node, _Lang) -> Acc. @@ -440,210 +431,155 @@ delete_old_messages(_TypeBin, _Days) -> %%% Internal functions %%%=================================================================== -process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) -> - NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MAM_0 -> - ?NS_MAM_0; - _ -> - ?NS_MAM_1 - end, - CommonFields = [#xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"hidden">>}, - {<<"var">>, <<"FORM_TYPE">>}], - children = [#xmlel{name = <<"value">>, - children = [{xmlcdata, NS}]}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"jid-single">>}, - {<<"var">>, <<"with">>}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"start">>}]}, - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"end">>}]}], +process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) -> + CommonFields = [#xdata_field{type = hidden, + var = <<"FORM_TYPE">>, + values = [NS]}, + #xdata_field{type = 'jid-single', var = <<"with">>}, + #xdata_field{type = 'text-single', var = <<"start">>}, + #xdata_field{type = 'text-single', var = <<"end">>}], Mod = gen_mod:db_mod(LServer, ?MODULE), ExtendedFields = Mod:extended_fields(), - Fields = ExtendedFields ++ CommonFields, - Form = #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = Fields}, - IQ#iq{type = result, - sub_el = [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, NS}], - children = [Form]}]}. + Fields = CommonFields ++ ExtendedFields, + Form = #xdata{type = form, fields = Fields}, + xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}). % Preference setting (both v0.2 & v0.3) -process_iq(#jid{luser = LUser, lserver = LServer}, - #jid{lserver = LServer}, - #iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) -> - try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of - <<"always">> -> always; - <<"never">> -> never; - <<"roster">> -> roster - end, - lists:foldl( - fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) -> - {get_jids(Els) ++ A, N}; - (#xmlel{name = <<"never">>, children = Els}, {A, N}) -> - {A, get_jids(Els) ++ N}; - (_, {A, N}) -> - {A, N} - end, {[], []}, SubEl#xmlel.children)} of - {Default, {Always0, Never0}} -> - Always = lists:usort(Always0), - Never = lists:usort(Never0), - case write_prefs(LUser, LServer, LServer, Default, Always, Never) of - ok -> - NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns), - IQ#iq{type = result, sub_el = [NewPrefs]}; - _Err -> - Txt = <<"Database failure">>, - IQ#iq{type = error, - sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]} - end - catch _:_ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} +process_iq(#iq{type = set, lang = Lang, + sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) -> + Why = {missing_attr, <<"default">>, <<"prefs">>, NS}, + ErrTxt = xmpp:format_error(Why), + xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang)); +process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, + to = #jid{lserver = LServer}, + type = set, lang = Lang, + sub_els = [#mam_prefs{xmlns = NS, + default = Default, + always = Always0, + never = Never0}]} = IQ) -> + Always = lists:usort(get_jids(Always0)), + Never = lists:usort(get_jids(Never0)), + case write_prefs(LUser, LServer, LServer, Default, Always, Never) of + ok -> + NewPrefs = prefs_el(Default, Always, Never, NS), + xmpp:make_iq_result(IQ, NewPrefs); + _Err -> + Txt = <<"Database failure">>, + xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end; -process_iq(#jid{luser = LUser, lserver = LServer}, - #jid{lserver = LServer}, - #iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) -> +process_iq(#iq{from = #jid{luser = LUser, lserver = LServer}, + to = #jid{lserver = LServer}, + type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) -> Prefs = get_prefs(LUser, LServer), PrefsEl = prefs_el(Prefs#archive_prefs.default, Prefs#archive_prefs.always, Prefs#archive_prefs.never, - IQ#iq.xmlns), - IQ#iq{type = result, sub_el = [PrefsEl]}; -process_iq(_, _, #iq{sub_el = SubEl} = IQ) -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. + NS), + xmpp:make_iq_result(IQ, PrefsEl); +process_iq(IQ) -> + xmpp:make_error(IQ, xmpp:err_not_allowed()). -process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) -> +process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang, + sub_els = [SubEl]} = IQ, MsgType) -> case MsgType of chat -> maybe_activate_mam(LUser, LServer); {groupchat, _Role, _MUCState} -> ok end, - case catch lists:foldl( - fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) -> - {{_, _, _} = jlib:datetime_string_to_timestamp(Data), - End, With, RSM}; - ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) -> - {Start, - {_, _, _} = jlib:datetime_string_to_timestamp(Data), - With, RSM}; - ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, jid:tolower(jid:from_string(Data)), RSM}; - ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) -> - {Start, End, {text, Data}, RSM}; - ({<<"set">>, El}, {Start, End, With, _}) -> - {Start, End, With, jlib:rsm_decode(El)}; - (_, Acc) -> - Acc - end, {none, [], none, none}, Fs) of - {'EXIT', _} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - {_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}; - {Start, End, With, RSM} -> - NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl), - select_and_send(LServer, From, To, Start, End, - With, limit_max(RSM, NS), IQ, MsgType) + case parse_query(SubEl, Lang) of + #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) -> + xmpp:make_error(IQ, xmpp:err_feature_not_implemented()); + #mam_query{rsm = RSM, xmlns = NS} = Query -> + NewRSM = limit_max(RSM, NS), + NewQuery = Query#mam_query{rsm = NewRSM}, + select_and_send(LServer, NewQuery, IQ, MsgType); + {error, Err} -> + xmpp:make_error(IQ, Err) end. -muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) -> - case may_enter_room(From, MUCState) of +should_archive(#message{type = T}, _LServer) when T == error; T == result -> + false; +should_archive(#message{body = Body} = Pkt, LServer) -> + case is_resent(Pkt, LServer) of true -> - LServer = MUCState#state.server_host, - Role = mod_muc_room:get_role(From, MUCState), - process_iq(LServer, From, To, IQ, SubEl, Fs, - {groupchat, Role, MUCState}); + false; false -> - Text = <<"Only members may query archives of this room">>, - Error = ?ERRT_FORBIDDEN(Lang, Text), - IQ#iq{type = error, sub_el = [SubEl, Error]} - end. - -parse_query_v0_2(Query) -> - lists:flatmap( - fun (#xmlel{name = <<"start">>} = El) -> - [{<<"start">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"end">>} = El) -> - [{<<"end">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"with">>} = El) -> - [{<<"with">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"withtext">>} = El) -> - [{<<"withtext">>, [fxml:get_tag_cdata(El)]}]; - (#xmlel{name = <<"set">>}) -> - [{<<"set">>, Query}]; - (_) -> - [] - end, Query#xmlel.children). - -should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) -> - case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of - <<"error">> -> - false; - <<"groupchat">> -> - false; - _ -> - case is_resent(Pkt, LServer) of - true -> + case check_store_hint(Pkt) of + store -> + true; + no_store -> false; - false -> - case check_store_hint(Pkt) of - store -> - true; - no_store -> - false; - none -> - case fxml:get_subtag_cdata(Pkt, <<"body">>) of - <<>> -> - %% Empty body - false; - _ -> - true - end - end - end - end; -should_archive(#xmlel{}, _LServer) -> - false. - -strip_my_archived_tag(Pkt, LServer) -> - NewEls = lists:filter( - fun(#xmlel{name = Tag, attrs = Attrs}) - when Tag == <<"archived">>; Tag == <<"stanza-id">> -> - case catch jid:nameprep( - fxml:get_attr_s( - <<"by">>, Attrs)) of - LServer -> + none -> + case xmpp:get_text(Body) of + <<>> -> + %% Empty body false; _ -> true - end; - (_) -> - true - end, Pkt#xmlel.children), - Pkt#xmlel{children = NewEls}. + end + end + end; +should_archive(_, _LServer) -> + false. + +strip_my_archived_tag(Pkt, LServer) -> + NewPkt = xmpp:decode_els( + Pkt, fun(El) -> + case xmpp:get_name(El) of + <<"archived">> -> + xmpp:get_ns(El) == ?NS_MAM_TMP; + <<"stanza-id">> -> + xmpp:get_ns(El) == ?NS_SID_0; + _ -> + false + end + end), + NewEls = lists:filter( + fun(#mam_archived{by = #jid{luser = <<>>} = By}) -> + By#jid.lserver /= LServer; + (#stanza_id{by = #jid{luser = <<>>} = By}) -> + By#jid.lserver /= LServer; + (_) -> + true + end, xmpp:get_els(NewPkt)), + xmpp:set_els(NewPkt, NewEls). strip_x_jid_tags(Pkt) -> + NewPkt = xmpp:decode_els( + Pkt, fun(El) -> + case xmpp:get_name(El) of + <<"x">> -> + case xmpp:get_ns(El) of + ?NS_MUC_USER -> true; + ?NS_MUC_ADMIN -> true; + ?NS_MUC_OWNER -> true; + _ -> false + end; + _ -> + false + end + end), NewEls = lists:filter( - fun(#xmlel{name = <<"x">>} = XEl) -> - not lists:any(fun(ItemEl) -> - fxml:get_tag_attr(<<"jid">>, ItemEl) - /= false - end, fxml:get_subtags(XEl, <<"item">>)); - (_) -> - true - end, Pkt#xmlel.children), - Pkt#xmlel{children = NewEls}. + fun(El) -> + Items = case El of + #muc_user{items = Is} -> Is; + #muc_admin{items = Is} -> Is; + #muc_owner{items = Is} -> Is; + _ -> [] + end, + not lists:any(fun(#muc_item{jid = JID}) -> + JID /= undefined + end, Items) + end, xmpp:get_els(NewPkt)), + xmpp:set_els(NewPkt, NewEls). should_archive_peer(C2SState, #archive_prefs{default = Default, always = Always, never = Never}, Peer) -> - LPeer = jid:tolower(Peer), + LPeer = jid:remove_resource(jid:tolower(Peer)), case lists:member(LPeer, Always) of true -> true; @@ -667,30 +603,30 @@ should_archive_peer(C2SState, end end. -should_archive_muc(Pkt) -> - case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of - <<"groupchat">> -> - case check_store_hint(Pkt) of - store -> - true; - no_store -> - false; - none -> - case fxml:get_subtag_cdata(Pkt, <<"body">>) of - <<>> -> - case fxml:get_subtag_cdata(Pkt, <<"subject">>) of - <<>> -> - false; - _ -> - true - end; +should_archive_muc(#message{type = groupchat, + body = Body, subject = Subj} = Pkt) -> + case check_store_hint(Pkt) of + store -> + true; + no_store -> + false; + none -> + case xmpp:get_text(Body) of + <<"">> -> + case xmpp:get_text(Subj) of + <<"">> -> + false; _ -> true - end + end; + _ -> + true end; _ -> false - end. + end; +should_archive_muc(_) -> + false. check_store_hint(Pkt) -> case has_store_hint(Pkt) of @@ -705,30 +641,24 @@ check_store_hint(Pkt) -> end end. + +-spec has_store_hint(message()) -> boolean(). has_store_hint(Message) -> - fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS) - /= false. + xmpp:has_subtag(Message, #hint{type = 'store'}). +-spec has_no_store_hint(message()) -> boolean(). has_no_store_hint(Message) -> - fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS) - /= false orelse - fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS) - /= false orelse - fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS) - /= false orelse - fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS) - /= false. + xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse + xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse + xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse + xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}). +-spec is_resent(message(), binary()) -> boolean(). is_resent(Pkt, LServer) -> - case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of - #xmlel{attrs = Attrs} -> - case fxml:get_attr(<<"by">>, Attrs) of - {value, LServer} -> - true; - _ -> - false - end; - false -> + case xmpp:get_subtag(Pkt, #stanza_id{}) of + #stanza_id{by = #jid{luser = <<>>, lserver = LServer}} -> + true; + _ -> false end. @@ -744,7 +674,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) -> true -> US = {LUser, LServer}, Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir); + El = xmpp:encode(Pkt), + Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir); false -> pass end. @@ -755,7 +686,8 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) -> LServer = MUCState#state.server_host, {U, S, _} = jid:tolower(RoomJID), Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv); + El = xmpp:encode(Pkt), + Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv); false -> pass end. @@ -796,20 +728,10 @@ get_prefs(LUser, LServer) -> end. prefs_el(Default, Always, Never, NS) -> - Default1 = jlib:atom_to_binary(Default), - JFun = fun(L) -> - [#xmlel{name = <<"jid">>, - children = [{xmlcdata, jid:to_string(J)}]} - || J <- L] - end, - Always1 = #xmlel{name = <<"always">>, - children = JFun(Always)}, - Never1 = #xmlel{name = <<"never">>, - children = JFun(Never)}, - #xmlel{name = <<"prefs">>, - attrs = [{<<"xmlns">>, NS}, - {<<"default">>, Default1}], - children = [Always1, Never1]}. + #mam_prefs{default = Default, + always = [jid:make(LJ) || LJ <- Always], + never = [jid:make(LJ) || LJ <- Never], + xmlns = NS}. maybe_activate_mam(LUser, LServer) -> ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE, @@ -838,21 +760,19 @@ maybe_activate_mam(LUser, LServer) -> ok end. -select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) -> - {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End, - With, RSM, MsgType), +select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) -> + {Msgs, IsComplete, Count} = + case MsgType of + chat -> + select(LServer, From, From, Query, MsgType); + {groupchat, _Role, _MUCState} -> + select(LServer, From, To, Query, MsgType) + end, SortedMsgs = lists:keysort(2, Msgs), - send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ). + send(SortedMsgs, Count, IsComplete, IQ). -select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) -> - case MsgType of - chat -> - select(LServer, From, From, Start, End, With, RSM, MsgType); - {groupchat, _Role, _MUCState} -> - select(LServer, From, To, Start, End, With, RSM, MsgType) - end. - -select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM, +select(_LServer, JidRequestor, JidArchive, + #mam_query{start = Start, 'end' = End, rsm = RSM}, {groupchat, _Role, #state{config = #config{mam = false}, history = History}} = MsgType) -> #lqueue{len = L, queue = Q} = History, @@ -864,7 +784,7 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM, case match_interval(Now, Start, End) and match_rsm(Now, RSM) of true -> - {[{jlib:integer_to_binary(TS), TS, + {[{integer_to_binary(TS), TS, msg_to_el(#archive_msg{ type = groupchat, timestamp = Now, @@ -879,31 +799,27 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM, end, 0, queue:to_list(Q)), Msgs = lists:flatten(Msgs0), case RSM of - #rsm_in{max = Max, direction = before} -> + #rsm_set{max = Max, before = Before} when is_binary(Before) -> {NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max), {NewMsgs, IsComplete, L}; - #rsm_in{max = Max} -> + #rsm_set{max = Max} -> {NewMsgs, IsComplete} = filter_by_max(Msgs, Max), {NewMsgs, IsComplete, L}; _ -> {Msgs, true, L} end; -select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) -> +select(LServer, JidRequestor, JidArchive, Query, MsgType) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, - MsgType). + Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType). msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer}, MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) -> Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType, Nick), - Pkt3 = #xmlel{name = <<"forwarded">>, - attrs = [{<<"xmlns">>, ?NS_FORWARD}], - children = [fxml:replace_tag_attr( - <<"xmlns">>, <<"jabber:client">>, Pkt2)]}, - jlib:add_delay_info(Pkt3, LServer, TS). + #forwarded{sub_els = [Pkt2], + delay = #delay{stamp = TS, from = jid:make(LServer)}}. -maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive, +maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive, Peer, {groupchat, Role, #state{config = #config{anonymous = Anon}}}, Nick) -> @@ -919,18 +835,13 @@ maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive, end, Items = case ExposeJID of true -> - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = [{<<"jid">>, - jid:to_string(Peer)}]}]}]; + [#muc_user{items = [#muc_item{jid = Peer}]}]; false -> [] end, - Pkt1 = Pkt#xmlel{children = Items ++ Els}, - Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1), - jlib:remove_attr(<<"to">>, Pkt2); + Pkt#message{from = jid:replace_resource(JidArchive, Nick), + to = undefined, + sub_els = Items ++ Els}; maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) -> Pkt. @@ -966,62 +877,46 @@ is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) -> false end. -send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) -> - QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl), - NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl), - QIDAttr = if QID /= <<>> -> - [{<<"queryid">>, QID}]; - true -> - [] - end, - CompleteAttr = if NS == ?NS_MAM_TMP -> - []; - NS == ?NS_MAM_0; NS == ?NS_MAM_1 -> - [{<<"complete">>, jlib:atom_to_binary(IsComplete)}] - end, +-spec send([{binary(), integer(), xmlel()}], + non_neg_integer(), boolean(), iq()) -> iq() | ignore. +send(Msgs, Count, IsComplete, + #iq{from = From, to = To, + sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) -> Els = lists:map( fun({ID, _IDInt, El}) -> - #xmlel{name = <<"message">>, - children = [#xmlel{name = <<"result">>, - attrs = [{<<"xmlns">>, NS}, - {<<"id">>, ID}|QIDAttr], - children = [El]}]} + #message{sub_els = [#mam_result{xmlns = NS, + id = ID, + queryid = QID, + sub_els = [El]}]} end, Msgs), - RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS), + RSMOut = make_rsm_out(Msgs, Count), + Result = if NS == ?NS_MAM_TMP -> + #mam_query{xmlns = NS, id = QID, rsm = RSMOut}; + true -> + #mam_fin{id = QID, rsm = RSMOut, complete = IsComplete} + end, if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 -> lists:foreach( fun(El) -> ejabberd_router:route(To, From, El) end, Els), - IQ#iq{type = result, sub_el = RSMOut}; + xmpp:make_iq_result(IQ, Result); NS == ?NS_MAM_0 -> - ejabberd_router:route( - To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})), + ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)), lists:foreach( fun(El) -> ejabberd_router:route(To, From, El) end, Els), - ejabberd_router:route( - To, From, #xmlel{name = <<"message">>, - children = RSMOut}), + ejabberd_router:route(To, From, #message{sub_els = [Result]}), ignore end. -make_rsm_out([], _, Count, Attrs, NS) -> - Tag = if NS == ?NS_MAM_TMP -> <<"query">>; - true -> <<"fin">> - end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], - children = jlib:rsm_encode(#rsm_out{count = Count})}]; -make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) -> +-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set(). +make_rsm_out([], Count) -> + #rsm_set{count = Count}; +make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) -> {LastID, _, _} = lists:last(Msgs), - Tag = if NS == ?NS_MAM_TMP -> <<"query">>; - true -> <<"fin">> - end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], - children = jlib:rsm_encode( - #rsm_out{first = FirstID, count = Count, - last = LastID})}]. + #rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}. filter_by_max(Msgs, undefined) -> {Msgs, true}; @@ -1030,23 +925,24 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> filter_by_max(_Msgs, _Junk) -> {[], true}. +-spec limit_max(rsm_set(), binary()) -> rsm_set(). limit_max(RSM, ?NS_MAM_TMP) -> RSM; % XEP-0313 v0.2 doesn't require clients to support RSM. -limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) -> - RSM#rsm_in{max = ?DEF_PAGE_SIZE}; -limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE -> - RSM#rsm_in{max = ?MAX_PAGE_SIZE}; +limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) -> + RSM#rsm_set{max = ?DEF_PAGE_SIZE}; +limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE -> + RSM#rsm_set{max = ?MAX_PAGE_SIZE}; limit_max(RSM, _NS) -> RSM. match_interval(Now, Start, End) -> (Now >= Start) and (Now =< End). -match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> -> - Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))), +match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> -> + Now1 = (catch usec_to_now(binary_to_integer(ID))), Now > Now1; -match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> -> - Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))), +match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> -> + Now1 = (catch usec_to_now(binary_to_integer(ID))), Now < Now1; match_rsm(_Now, _) -> true. @@ -1066,15 +962,10 @@ datetime_to_now(DateTime, USecs) -> calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), {Seconds div 1000000, Seconds rem 1000000, USecs}. -get_jids(Els) -> - lists:flatmap( - fun(#xmlel{name = <<"jid">>} = El) -> - J = jid:from_string(fxml:get_tag_cdata(El)), - [jid:tolower(jid:remove_resource(J)), - jid:tolower(J)]; - (_) -> - [] - end, Els). +get_jids(undefined) -> + []; +get_jids(Js) -> + [jid:tolower(jid:remove_resource(J)) || J <- Js]. get_commands_spec() -> [#ejabberd_commands{name = delete_old_mam_messages, tags = [purge], diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index be14d0fff..cbe7c336c 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -12,10 +12,10 @@ %% API -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, - extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]). + extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]). -include_lib("stdlib/include/ms_transform.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("logger.hrl"). -include("mod_mam.hrl"). @@ -132,7 +132,8 @@ get_prefs(LUser, LServer) -> select(_LServer, JidRequestor, #jid{luser = LUser, lserver = LServer} = JidArchive, - Start, End, With, RSM, MsgType) -> + #mam_query{start = Start, 'end' = End, + with = With, rsm = RSM}, MsgType) -> MS = make_matchspec(LUser, LServer, Start, End, With), Msgs = mnesia:dirty_select(archive_msg, MS), SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs), @@ -174,7 +175,7 @@ make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) -> Peer == With -> Msg end); -make_matchspec(LUser, LServer, Start, End, none) -> +make_matchspec(LUser, LServer, Start, End, undefined) -> ets:fun2ms( fun(#archive_msg{timestamp = TS, us = US, @@ -184,28 +185,27 @@ make_matchspec(LUser, LServer, Start, End, none) -> Msg end). -filter_by_rsm(Msgs, none) -> +filter_by_rsm(Msgs, undefined) -> {Msgs, true}; -filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 -> +filter_by_rsm(_Msgs, #rsm_set{max = Max}) when Max < 0 -> {[], true}; -filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> - NewMsgs = case Direction of - aft when ID /= <<"">> -> +filter_by_rsm(Msgs, #rsm_set{max = Max, before = Before, 'after' = After}) -> + NewMsgs = if is_binary(After), After /= <<"">> -> lists:filter( fun(#archive_msg{id = I}) -> - ?BIN_GREATER_THAN(I, ID) + ?BIN_GREATER_THAN(I, After) end, Msgs); - before when ID /= <<"">> -> + is_binary(Before), Before /= <<"">> -> lists:foldl( fun(#archive_msg{id = I} = Msg, Acc) - when ?BIN_LESS_THAN(I, ID) -> + when ?BIN_LESS_THAN(I, Before) -> [Msg|Acc]; (_, Acc) -> Acc end, [], Msgs); - before when ID == <<"">> -> + is_binary(Before), Before == <<"">> -> lists:reverse(Msgs); - _ -> + true -> Msgs end, filter_by_max(NewMsgs, Max). diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 20ed8d4f1..6e5231989 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -14,10 +14,10 @@ %% API -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, - extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]). + extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]). -include_lib("stdlib/include/ms_transform.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_mam.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). @@ -51,9 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) -> ok. extended_fields() -> - [#xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"withtext">>}]}]. + [#xdata_field{type = 'text-single', var = <<"withtext">>}]. store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) -> TSinteger = p1_time_compat:system_time(micro_seconds), @@ -126,13 +124,12 @@ get_prefs(LUser, LServer) -> end. select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, - Start, End, With, RSM, MsgType) -> + MAMQuery, MsgType) -> User = case MsgType of chat -> LUser; {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive) end, - {Query, CountQuery} = make_sql_query(User, LServer, - Start, End, With, RSM), + {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery), % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a % reasonable limit on how many stanzas may be pushed to a client in one % request. If a query returns a number of stanzas greater than this limit @@ -142,10 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, case {ejabberd_sql:sql_query(LServer, Query), ejabberd_sql:sql_query(LServer, CountQuery)} of {{selected, _, Res}, {selected, _, [[Count]]}} -> - {Max, Direction} = case RSM of - #rsm_in{max = M, direction = D} -> {M, D}; - _ -> {undefined, undefined} - end, + {Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm), {Res1, IsComplete} = if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> if Direction == before -> @@ -200,15 +194,10 @@ usec_to_now(Int) -> Sec = Secs rem 1000000, {MSec, Sec, USec}. -make_sql_query(User, LServer, Start, End, With, RSM) -> - {Max, Direction, ID} = case RSM of - #rsm_in{} -> - {RSM#rsm_in.max, - RSM#rsm_in.direction, - RSM#rsm_in.id}; - none -> - {none, none, <<>>} - end, +make_sql_query(User, LServer, + #mam_query{start = Start, 'end' = End, with = With, + withtext = WithText, rsm = RSM}) -> + {Max, Direction, ID} = get_max_direction_id(RSM), ODBCType = ejabberd_config:get_option( {sql_type, LServer}, ejabberd_sql:opt_type(sql_type)), @@ -228,12 +217,16 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> true -> [] end, - WithClause = case With of - {text, <<>>} -> - []; - {text, Txt} -> - [<<" and match (txt) against ('">>, - Escape(Txt), <<"')">>]; + WithTextClause = case WithText of + {text, <<>>} -> + []; + {text, Txt} -> + [<<" and match (txt) against ('">>, + Escape(Txt), <<"')">>]; + undefined -> + [] + end, + WithClause = case catch jid:tolower(With) of {_, _, <<>>} -> [<<" and bare_peer='">>, Escape(jid:to_string(With)), @@ -242,7 +235,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> [<<" and peer='">>, Escape(jid:to_string(With)), <<"'">>]; - none -> + _ -> [] end, PageClause = case catch jlib:binary_to_integer(ID) of @@ -250,7 +243,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> case Direction of before -> [<<" AND timestamp < ">>, ID]; - aft -> + 'after' -> [<<" AND timestamp > ">>, ID]; _ -> [] @@ -276,7 +269,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" " FROM archive WHERE username='">>, - SUser, <<"'">>, WithClause, StartClause, EndClause, + SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, PageClause], QueryPage = @@ -294,4 +287,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) -> end, {QueryPage, [<<"SELECT COUNT(*) FROM archive WHERE username='">>, - SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}. + SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, <<";">>]}. + +-spec get_max_direction_id(rsm_set() | undefined) -> + {integer() | undefined, + before | 'after' | undefined, + binary()}. +get_max_direction_id(RSM) -> + case RSM of + #rsm_set{max = Max, before = Before} when is_binary(Before) -> + {Max, before, Before}; + #rsm_set{max = Max, 'after' = After} when is_binary(After) -> + {Max, 'after', After}; + #rsm_set{max = Max} -> + {Max, undefined, <<>>}; + _ -> + {undefined, undefined, <<>>} + end. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index a86f580d3..294456ee2 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -43,7 +43,12 @@ forget_room/3, create_room/5, shutdown_rooms/1, - process_iq_disco_items/4, + process_disco_info/1, + process_disco_items/1, + process_vcard/1, + process_register/1, + process_muc_unique/1, + process_mucsub/1, broadcast_service_message/2, export/1, import/1, @@ -58,7 +63,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc.hrl"). -record(state, @@ -154,17 +159,6 @@ forget_room(ServerHost, Host, Name) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:forget_room(LServer, Host, Name). -process_iq_disco_items(Host, From, To, - #iq{lang = Lang} = IQ) -> - Rsm = jlib:rsm_decode(IQ), - DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el), - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}], - children = iq_disco_items(Host, From, Lang, DiscoNode, Rsm)}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)). - can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false; can_use_nick(ServerHost, Host, JID, Nick) -> LServer = jid:nameprep(ServerHost), @@ -176,6 +170,8 @@ can_use_nick(ServerHost, Host, JID, Nick) -> %%==================================================================== init([Host, Opts]) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, + one_queue), MyHost = gen_mod:get_opt_host(Host, Opts, <<"conference.@HOST@">>), Mod = gen_mod:db_mod(Host, Opts, ?MODULE), @@ -255,6 +251,18 @@ init([Host, Opts]) -> RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) when is_atom(A) -> A end, none), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER, + ?MODULE, process_register, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD, + ?MODULE, process_vcard, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB, + ?MODULE, process_mucsub, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE, + ?MODULE, process_muc_unique, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO, + ?MODULE, process_disco_info, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS, + ?MODULE, process_disco_items, IQDisc), ejabberd_router:register_route(MyHost, Host), load_permanent_rooms(MyHost, Host, {Access, AccessCreate, AccessAdmin, AccessPersistent}, @@ -314,8 +322,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) -> {noreply, State}; handle_info(_Info, State) -> {noreply, State}. -terminate(_Reason, State) -> - ejabberd_router:unregister_route(State#state.host), +terminate(_Reason, #state{host = MyHost}) -> + ejabberd_router:unregister_route(MyHost), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO), + gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -331,197 +345,162 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper, allow -> do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts); - _ -> - #xmlel{attrs = Attrs} = Packet, - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + deny -> + Lang = xmpp:get_lang(Packet), ErrText = <<"Access denied by service policy">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route_error(To, From, Err, Packet) + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) end. - +do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, + From, #jid{luser = <<"">>, lresource = <<"">>} = To, + #iq{} = IQ, _DefRoomOpts) -> + ejabberd_local:process_iq(From, To, IQ); +do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper, + From, #jid{luser = <<"">>, lresource = <<"">>} = To, + #message{lang = Lang, body = Body, type = Type} = Packet, _) -> + {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access, + if Type == error -> + ok; + true -> + case acl:match_rule(ServerHost, AccessAdmin, From) of + allow -> + Msg = xmpp:get_text(Body), + broadcast_service_message(Host, Msg); + deny -> + ErrText = <<"Only service administrators are allowed " + "to send service messages">>, + Err = xmpp:make_error( + Packet, xmpp:err_forbidden(ErrText, Lang)), + ejabberd_router:route(To, From, Err) + end + end; +do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper, + From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) -> + Err = xmpp:err_item_not_found(), + ejabberd_router:route_error(To, From, Packet, Err); do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) -> - {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access, + {_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access, {Room, _, Nick} = jid:tolower(To), - #xmlel{name = Name, attrs = Attrs} = Packet, - case Room of - <<"">> -> - case Nick of - <<"">> -> - 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 -> - Info = ejabberd_hooks:run_fold(disco_info, - ServerHost, [], - [ServerHost, ?MODULE, - <<"">>, <<"">>]), - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - iq_disco_info( - ServerHost, Lang) ++ - Info}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{type = get, xmlns = ?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 = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - iq_get_register_info(ServerHost, - Host, - From, - Lang)}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS, - lang = Lang, sub_el = SubEl} = - IQ -> - case process_iq_register_set(ServerHost, Host, From, - SubEl, Lang) - of - {result, IQRes} -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"query">>, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = IQRes}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - {error, Error} -> - Err = jlib:make_error_reply(Packet, Error), - ejabberd_router:route(To, From, Err) - end; - #iq{type = get, xmlns = (?NS_VCARD) = XMLNS, - lang = Lang, sub_el = _SubEl} = - IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"vCard">>, - attrs = - [{<<"xmlns">>, XMLNS}], - children = - iq_get_vcard(Lang)}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{type = get, xmlns = ?NS_MUCSUB, - sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ -> - RoomJIDs = get_subscribed_rooms(ServerHost, Host, From), - Subs = lists:map( - fun(J) -> - #xmlel{name = <<"subscription">>, - attrs = [{<<"jid">>, - jid:to_string(J)}]} - end, RoomJIDs), - Res = IQ#iq{type = result, - sub_el = [SubEl#xmlel{children = Subs}]}, - ejabberd_router:route(To, From, jlib:iq_to_xml(Res)); - #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ -> - Res = IQ#iq{type = result, - sub_el = - [#xmlel{name = <<"unique">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_UNIQUE}], - children = - [iq_get_unique(From)]}]}, - ejabberd_router:route(To, From, - jlib:iq_to_xml(Res)); - #iq{} -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(To, From, Err); - _ -> ok - end; - <<"message">> -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - case acl:match_rule(ServerHost, AccessAdmin, From) - of - allow -> - Msg = fxml:get_path_s(Packet, - [{elem, <<"body">>}, - cdata]), - broadcast_service_message(Host, Msg); - _ -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = - <<"Only service administrators are allowed " - "to send service messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(To, From, Err) - end - end; - <<"presence">> -> ok - end; - _ -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - <<"result">> -> ok; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_ITEM_NOT_FOUND), - ejabberd_router:route(To, From, Err) - end - end; - _ -> - case mnesia:dirty_read(muc_online_room, {Room, Host}) of - [] -> - Type = fxml:get_attr_s(<<"type">>, Attrs), - case {Name, Type} of - {<<"presence">>, <<"">>} -> - case check_user_can_create_room(ServerHost, - AccessCreate, From, Room) and - check_create_roomid(ServerHost, Room) of - true -> - {ok, Pid} = start_new_room(Host, ServerHost, Access, - Room, HistorySize, - RoomShaper, From, Nick, DefRoomOpts), - register_room(Host, Room, Pid), - mod_muc_room:route(Pid, From, Nick, Packet), - ok; - false -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = <<"Room creation is denied by service policy">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_FORBIDDEN(Lang, ErrText)), - ejabberd_router:route(To, From, Err) - end; - _ -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - ErrText = <<"Conference room does not exist">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)), + case mnesia:dirty_read(muc_online_room, {Room, Host}) of + [] -> + case Packet of + #presence{type = available, lang = Lang} -> + case check_user_can_create_room( + ServerHost, AccessCreate, From, Room) and + check_create_roomid(ServerHost, Room) of + true -> + {ok, Pid} = start_new_room( + Host, ServerHost, Access, + Room, HistorySize, + RoomShaper, From, Nick, DefRoomOpts), + register_room(Host, Room, Pid), + mod_muc_room:route(Pid, From, Nick, Packet), + ok; + false -> + ErrText = <<"Room creation is denied by service policy">>, + Err = xmpp:make_error( + Packet, xmpp:err_forbidden(ErrText, Lang)), ejabberd_router:route(To, From, Err) end; - [R] -> - Pid = R#muc_online_room.pid, - ?DEBUG("MUC: send to process ~p~n", [Pid]), - mod_muc_room:route(Pid, From, Nick, Packet), - ok - end + _ -> + Lang = xmpp:get_lang(Packet), + ErrText = <<"Conference room does not exist">>, + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error(To, From, Packet, Err) + end; + [R] -> + Pid = R#muc_online_room.pid, + ?DEBUG("MUC: send to process ~p~n", [Pid]), + mod_muc_room:route(Pid, From, Nick, Packet), + ok end. +-spec process_vcard(iq()) -> iq(). +process_vcard(#iq{type = get, lang = Lang} = IQ) -> + Desc = translate:translate(Lang, <<"ejabberd MUC module">>), + Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>, + xmpp:make_iq_result( + IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>, + url = ?EJABBERD_URI, + desc = <>}); +process_vcard(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)). + +-spec process_register(iq()) -> iq(). +process_register(#iq{type = get, from = From, to = To, lang = Lang} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang)); +process_register(#iq{type = set, from = From, to = To, + lang = Lang, sub_els = [El]} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + case process_iq_register_set(ServerHost, Host, From, El, Lang) of + {result, Result} -> + xmpp:make_iq_result(IQ, Result); + {error, Err} -> + xmpp:make_error(IQ, Err) + end. + +-spec process_disco_info(iq()) -> iq(). +process_disco_info(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_info(#iq{type = get, to = To, lang = Lang, + sub_els = [#disco_info{node = undefined}]} = IQ) -> + ServerHost = ejabberd_router:host_of_route(To#jid.lserver), + X = ejabberd_hooks:run_fold(disco_info, ServerHost, [], + [ServerHost, ?MODULE, undefined, Lang]), + MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of + true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]; + false -> [] + end, + Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, + ?NS_REGISTER, ?NS_MUC, ?NS_RSM, + ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures], + Identity = #identity{category = <<"conference">>, + type = <<"text">>, + name = translate:translate(Lang, <<"Chatrooms">>)}, + xmpp:make_iq_result( + IQ, #disco_info{features = Features, + identities = [Identity], + xdata = X}); +process_disco_info(#iq{type = get, lang = Lang} = IQ) -> + xmpp:make_error(IQ, xmpp:err_item_not_found(<<"No info available">>, Lang)). + +-spec process_disco_items(iq()) -> iq(). +process_disco_items(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_disco_items(#iq{type = get, from = From, to = To, lang = Lang, + sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) -> + Host = To#jid.lserver, + xmpp:make_iq_result( + IQ, #disco_items{node = Node, + items = iq_disco_items(Host, From, Lang, Node, RSM)}). + +-spec process_muc_unique(iq()) -> iq(). +process_muc_unique(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_muc_unique(#iq{from = From, type = get} = IQ) -> + Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(), + randoms:get_string()])), + xmpp:make_iq_result(IQ, #muc_unique{name = Name}). + +-spec process_mucsub(iq()) -> iq(). +process_mucsub(#iq{type = set, lang = Lang} = IQ) -> + Txt = <<"Value 'set' of 'type' attribute is not allowed">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); +process_mucsub(#iq{type = get, from = From, to = To} = IQ) -> + Host = To#jid.lserver, + ServerHost = ejabberd_router:host_of_route(Host), + RoomJIDs = get_subscribed_rooms(ServerHost, Host, From), + xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs}). + check_user_can_create_room(ServerHost, AccessCreate, From, _RoomID) -> case acl:match_rule(ServerHost, AccessCreate, From) of @@ -583,61 +562,21 @@ register_room(Host, Room, Pid) -> end, mnesia:transaction(F). - -iq_disco_info(ServerHost, Lang) -> - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, - translate:translate(Lang, <<"Chatrooms">>)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_REGISTER}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_RSM}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUCSUB}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++ - case gen_mod:is_loaded(ServerHost, mod_mam) of - true -> - [#xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MAM_TMP}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MAM_0}]}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MAM_1}]}]; - false -> - [] - end. - -iq_disco_items(Host, From, Lang, <<>>, none) -> +iq_disco_items(Host, From, Lang, undefined, undefined) -> Rooms = get_vh_rooms(Host), case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of true -> iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}); false -> - iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) + iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) end; -iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) -> - XmlEmpty = #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, <<"conference.localhost">>}, - {<<"node">>, <<"emptyrooms">>}, - {<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}], - children = []}, +iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) -> + Empty = #disco_item{jid = jid:make(<<"conference.localhost">>), + node = <<"emptyrooms">>, + name = translate:translate(Lang, <<"Empty Rooms">>)}, Query = {get_disco_item, only_non_empty, From, Lang}, - [XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)]; -iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) -> + [Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)]; +iq_disco_items(Host, From, Lang, <<"emptyrooms">>, undefined) -> iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang}); iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) -> {Rooms, RsmO} = get_vh_rooms(Host, Rsm), @@ -645,62 +584,55 @@ iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) -> iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut. iq_disco_items_list(Host, Rooms, Query) -> - lists:zf(fun (#muc_online_room{name_host = - {Name, _Host}, - pid = Pid}) -> - case catch gen_fsm:sync_send_all_state_event(Pid, - Query, - 100) - of - {item, Desc} -> - flush(), - {true, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string({Name, Host, - <<"">>})}, - {<<"name">>, Desc}], - children = []}}; - _ -> false - end - end, Rooms). + lists:zf( + fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) -> + case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of + {item, Desc} -> + flush(), + {true, #disco_item{jid = jid:make(Name, Host), + name = Desc}}; + _ -> + false + end + end, Rooms). -get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> - AllRooms = lists:sort(get_vh_rooms(Host)), - Count = erlang:length(AllRooms), - Guard = case Direction of - _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}]; - aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}]; - before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}]; - _ -> [{'==', {element, 2, '$1'}, Host}] - end, - L = lists:sort( - mnesia:dirty_select(muc_online_room, - [{#muc_online_room{name_host = '$1', _ = '_'}, - Guard, - ['$_']}])), - L2 = if - Index == undefined andalso Direction == before -> - lists:reverse(lists:sublist(lists:reverse(L), 1, M)); - Index == undefined -> - lists:sublist(L, 1, M); - Index > Count orelse Index < 0 -> - []; - true -> - lists:sublist(L, Index+1, M) - end, - if L2 == [] -> {L2, #rsm_out{count = Count}}; - true -> - H = hd(L2), - NewIndex = get_room_pos(H, AllRooms), - T = lists:last(L2), - {F, _} = H#muc_online_room.name_host, - {Last, _} = T#muc_online_room.name_host, - {L2, - #rsm_out{first = F, last = Last, count = Count, - index = NewIndex}} - end. +get_vh_rooms(_, _) -> + todo. +%% get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})-> +%% AllRooms = lists:sort(get_vh_rooms(Host)), +%% Count = erlang:length(AllRooms), +%% Guard = case Direction of +%% _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}]; +%% aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}]; +%% before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}]; +%% _ -> [{'==', {element, 2, '$1'}, Host}] +%% end, +%% L = lists:sort( +%% mnesia:dirty_select(muc_online_room, +%% [{#muc_online_room{name_host = '$1', _ = '_'}, +%% Guard, +%% ['$_']}])), +%% L2 = if +%% Index == undefined andalso Direction == before -> +%% lists:reverse(lists:sublist(lists:reverse(L), 1, M)); +%% Index == undefined -> +%% lists:sublist(L, 1, M); +%% Index > Count orelse Index < 0 -> +%% []; +%% true -> +%% lists:sublist(L, Index+1, M) +%% end, +%% if L2 == [] -> {L2, #rsm_out{count = Count}}; +%% true -> +%% H = hd(L2), +%% NewIndex = get_room_pos(H, AllRooms), +%% T = lists:last(L2), +%% {F, _} = H#muc_online_room.name_host, +%% {Last, _} = T#muc_online_room.name_host, +%% {L2, +%% #rsm_out{first = F, last = Last, count = Count, +%% index = NewIndex}} +%% end. get_subscribed_rooms(ServerHost, Host, From) -> Rooms = get_rooms(ServerHost, Host), @@ -730,60 +662,32 @@ get_room_pos(Desired, [_ | Rooms], HeadPosition) -> flush() -> receive _ -> flush() after 0 -> ok end. --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). - -iq_get_unique(From) -> - {xmlcdata, - p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(), - randoms:get_string()]))}. - get_nick(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:get_nick(LServer, Host, From). iq_get_register_info(ServerHost, Host, From, Lang) -> - {Nick, Registered} = case get_nick(ServerHost, Host, - From) - of - error -> {<<"">>, []}; - N -> - {N, - [#xmlel{name = <<"registered">>, attrs = [], - children = []}]} + {Nick, Registered} = case get_nick(ServerHost, Host, From) of + error -> {<<"">>, false}; + N -> {N, true} end, - Registered ++ - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need a client that supports x:data " - "to register the nickname">>)}]}, - #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_XDATA}, - {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"Nickname Registration at ">>))/binary, - Host/binary>>}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Enter nickname you want to register">>)}]}, - ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>, - Nick)]}]. + Title = <<(translate:translate( + Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>, + Inst = translate:translate(Lang, <<"Enter nickname you want to register">>), + Field = #xdata_field{type = 'text-single', + label = translate:translate(Lang, <<"Nickname">>), + var = <<"nick">>, + values = [Nick]}, + X = #xdata{type = form, title = Title, + instructions = [Inst], fields = [Field]}, + #register{nick = Nick, + registered = Registered, + instructions = + translate:translate( + Lang, <<"You need a client that supports x:data " + "to register the nickname">>), + xdata = X}. set_nick(ServerHost, Host, From, Nick) -> LServer = jid:nameprep(ServerHost), @@ -793,66 +697,43 @@ set_nick(ServerHost, Host, From, Nick) -> iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> case set_nick(ServerHost, Host, From, Nick) of - {atomic, ok} -> {result, []}; + {atomic, ok} -> {result, undefined}; {atomic, false} -> ErrText = <<"That nickname is registered by another " "person">>, - {error, ?ERRT_CONFLICT(Lang, ErrText)}; + {error, xmpp:err_conflict(ErrText, Lang)}; _ -> Txt = <<"Database failure">>, - {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)} + {error, xmpp:err_internal_server_error(Txt, Lang)} end. -process_iq_register_set(ServerHost, Host, From, SubEl, - Lang) -> - #xmlel{children = Els} = SubEl, - case fxml:get_subtag(SubEl, <<"remove">>) of - false -> - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), - fxml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, []}; - {?NS_XDATA, <<"submit">>} -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - _ -> - case lists:keysearch(<<"nick">>, 1, XData) of - {value, {_, [Nick]}} when Nick /= <<"">> -> - iq_set_register_info(ServerHost, Host, From, - Nick, Lang); - _ -> - ErrText = - <<"You must fill in field \"Nickname\" " - "in the form">>, - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)} - end - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> {error, ?ERR_BAD_REQUEST} - end; - _ -> - iq_set_register_info(ServerHost, Host, From, <<"">>, - Lang) +process_iq_register_set(ServerHost, Host, From, + #register{remove = true}, Lang) -> + iq_set_register_info(ServerHost, Host, From, <<"">>, Lang); +process_iq_register_set(_ServerHost, _Host, _From, + #register{xdata = #xdata{type = cancel}}, _Lang) -> + {result, undefined}; +process_iq_register_set(ServerHost, Host, From, + #register{nick = Nick, xdata = XData}, Lang) -> + case XData of + #xdata{type = submit, fields = Fs} -> + case lists:keyfind(<<"nick">>, #xdata_field.var, Fs) of + #xdata_field{values = [N]} -> + iq_set_register_info(ServerHost, Host, From, N, Lang); + _ -> + ErrText = <<"You must fill in field \"Nickname\" in the form">>, + {error, xmpp:err_not_acceptable(ErrText, Lang)} + end; + #xdata{} -> + Txt = <<"Incorrect data form">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + _ when is_binary(Nick), Nick /= <<"">> -> + iq_set_register_info(ServerHost, Host, From, Nick, Lang); + _ -> + ErrText = <<"You must fill in field \"Nickname\" in the form">>, + {error, xmpp:err_not_acceptable(ErrText, Lang)} end. -iq_get_vcard(Lang) -> - [#xmlel{name = <<"FN">>, attrs = [], - children = [{xmlcdata, <<"ejabberd/mod_muc">>}]}, - #xmlel{name = <<"URL">>, attrs = [], - children = [{xmlcdata, ?EJABBERD_URI}]}, - #xmlel{name = <<"DESC">>, attrs = [], - children = - [{xmlcdata, - <<(translate:translate(Lang, - <<"ejabberd MUC module">>))/binary, - "\nCopyright (c) 2003-2016 ProcessOne">>}]}]. - broadcast_service_message(Host, Msg) -> lists:foreach( fun(#muc_online_room{pid = Pid}) -> diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 0b5e79f60..a7ba16138 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -24,7 +24,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc_room.hrl"). -include("mod_muc.hrl"). -include("ejabberd_http.hrl"). @@ -241,7 +241,7 @@ web_menu_host(Acc, _Host, Lang) -> -define(TDTD(L, N), ?XE(<<"tr">>, [?XCT(<<"td">>, L), - ?XC(<<"td">>, jlib:integer_to_binary(N)) + ?XC(<<"td">>, integer_to_binary(N)) ])). web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) -> @@ -283,7 +283,7 @@ get_sort_query(Q) -> get_sort_query2(Q) -> {value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q), - Integer = jlib:binary_to_integer(String), + Integer = binary_to_integer(String), case Integer >= 0 of true -> {ok, {normal, Integer}}; false -> {ok, {reverse, abs(Integer)}} @@ -309,7 +309,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) -> {Titles_TR, _} = lists:mapfoldl( fun(Title, Num_column) -> - NCS = jlib:integer_to_binary(Num_column), + NCS = integer_to_binary(Num_column), TD = ?XE(<<"td">>, [?CT(Title), ?C(<<" ">>), ?AC(<<"?sort=", NCS/binary>>, <<"<">>), @@ -383,7 +383,7 @@ prepare_room_info(Room_info) -> Just_created, Title} = Room_info, [NameHost, - jlib:integer_to_binary(Num_participants), + integer_to_binary(Num_participants), Ts_last_message, jlib:atom_to_binary(Public), jlib:atom_to_binary(Persistent), @@ -830,7 +830,7 @@ get_options(Config) -> Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)], [config | ValuesRaw] = tuple_to_list(Config), Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V); - (V) when is_integer(V) -> jlib:integer_to_binary(V); + (V) when is_integer(V) -> integer_to_binary(V); (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V]))); (V) -> V end, ValuesRaw), lists:zip(Fields, Values). diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index ec4711b43..4b129ce81 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -46,7 +46,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc.hrl"). -include("mod_muc_room.hrl"). @@ -196,15 +196,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. add_to_log2(text, {Nick, Packet}, Room, Opts, State) -> case has_no_permanent_store_hint(Packet) of false -> - case {fxml:get_subtag(Packet, <<"subject">>), - fxml:get_subtag(Packet, <<"body">>)} - of - {false, false} -> ok; - {false, SubEl} -> - Message = {body, fxml:get_tag_cdata(SubEl)}, + case {Packet#message.subject, Packet#message.body} of + {[], []} -> ok; + {[], Body} -> + Message = {body, xmpp:get_text(Body)}, add_message_to_log(Nick, Message, Room, Opts, State); - {SubEl, _} -> - Message = {subject, fxml:get_tag_cdata(SubEl)}, + {Subj, _} -> + Message = {subject, xmpp:get_text(Subj)}, add_message_to_log(Nick, Message, Room, Opts, State) end; true -> ok @@ -1035,7 +1033,7 @@ roomconfig_to_string(Options, Lang, FileFormat) -> max_users -> <<"
", OptText/binary, ": \"", - (htmlize(jlib:integer_to_binary(T), + (htmlize(integer_to_binary(T), FileFormat))/binary, "\"
">>; title -> @@ -1053,7 +1051,7 @@ roomconfig_to_string(Options, Lang, FileFormat) -> allow_private_messages_from_visitors -> <<"
", OptText/binary, ": \"", - (htmlize(?T((jlib:atom_to_binary(T))), + (htmlize(?T(jlib:atom_to_binary(T)), FileFormat))/binary, "\"
">>; _ -> <<"\"", T/binary, "\"">> @@ -1168,7 +1166,7 @@ get_room_occupants(RoomJIDString) -> [{U#user.jid, U#user.nick, U#user.role} || {_, U} <- (?DICT):to_list(StateData#state.users)]. --spec get_room_state(binary(), binary()) -> muc_room_state(). +-spec get_room_state(binary(), binary()) -> mod_muc_room:state(). get_room_state(RoomName, MucService) -> case mnesia:dirty_read(muc_online_room, @@ -1180,7 +1178,7 @@ get_room_state(RoomName, MucService) -> [] -> #state{} end. --spec get_room_state(pid()) -> muc_room_state(). +-spec get_room_state(pid()) -> mod_muc_room:state(). get_room_state(RoomPid) -> {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, @@ -1204,14 +1202,10 @@ fjoin(FileList) -> list_to_binary(filename:join([binary_to_list(File) || File <- FileList])). has_no_permanent_store_hint(Packet) -> - fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS) - =/= false orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS) - =/= false orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS) - =/= false orelse - fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS) - =/= false. + xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse + xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse + xmpp:has_subtag(Packet, #hint{type = 'no-permanent-store'}) orelse + xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}). mod_opt_type(access_log) -> fun (A) when is_atom(A) -> A end; diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 773953c4a..29b7942cf 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -51,7 +51,7 @@ -include("ejabberd.hrl"). -include("logger.hrl"). --include("jlib.hrl"). +-include("xmpp.hrl"). -include("mod_muc_room.hrl"). @@ -72,6 +72,18 @@ -endif. +-type state() :: #state{}. +-type fsm_stop() :: {stop, normal, state()}. +-type fsm_next() :: {next_state, normal_state, state()}. +-type fsm_transition() :: fsm_stop() | fsm_next(). +-type history_element() :: {binary(), %% nick + message(), %% message itself + boolean(), %% have subject + erlang:timestamp(), + non_neg_integer()}. + +-export_type([state/0]). + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- @@ -133,349 +145,187 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) -> {ok, normal_state, State}. normal_state({route, From, <<"">>, - #xmlel{name = <<"message">>, attrs = Attrs, - children = Els} = - Packet}, - StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), + #message{type = Type, lang = Lang} = Packet}, StateData) -> case is_user_online(From, StateData) orelse - is_user_allowed_message_nonparticipant(From, StateData) - of - true -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"groupchat">> -> - Activity = get_user_activity(From, StateData), - Now = p1_time_compat:system_time(micro_seconds), - MinMessageInterval = - trunc(gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, min_message_interval, fun(MMI) when is_number(MMI) -> MMI end, 0) - * 1000000), - Size = element_size(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)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - Now >= - Activity#activity.message_time + MinMessageInterval, - MessageShaperInterval == 0 -> - {RoomShaper, RoomShaperInterval} = - shaper:update(StateData#state.room_shaper, Size), - RoomQueueEmpty = - queue:is_empty(StateData#state.room_queue), - if RoomShaperInterval == 0, RoomQueueEmpty -> - NewActivity = Activity#activity{message_time = - Now, - message_shaper = - MessageShaper}, - StateData1 = store_user_activity(From, - NewActivity, - StateData), - StateData2 = StateData1#state{room_shaper = - RoomShaper}, - process_groupchat_message(From, Packet, - StateData2); - true -> - StateData1 = if RoomQueueEmpty -> - erlang:send_after(RoomShaperInterval, - self(), - process_room_queue), - StateData#state{room_shaper = - RoomShaper}; - true -> StateData - end, - NewActivity = Activity#activity{message_time = - Now, - message_shaper = - MessageShaper, - message = Packet}, - RoomQueue = queue:in({message, From}, - StateData#state.room_queue), - StateData2 = store_user_activity(From, - NewActivity, - StateData1), - StateData3 = StateData2#state{room_queue = - RoomQueue}, - {next_state, normal_state, StateData3} - end; - true -> - MessageInterval = (Activity#activity.message_time + - MinMessageInterval - - Now) - div 1000, - Interval = lists:max([MessageInterval, - MessageShaperInterval]), - erlang:send_after(Interval, self(), - {process_user_message, From}), - NewActivity = Activity#activity{message = Packet, - message_shaper = - MessageShaper}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - {next_state, normal_state, StateData1} - end; - <<"error">> -> - case is_user_online(From, StateData) of - true -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)), - close_room_if_temporary_and_empty(NewState); - _ -> {next_state, normal_state, StateData} - end; - <<"chat">> -> - ErrText = - <<"It is not allowed to send private messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - Type when (Type == <<"">>) or (Type == <<"normal">>) -> - IsInvitation = is_invitation(Els), - IsVoiceRequest = is_voice_request(Els) and - is_visitor(From, StateData), - IsVoiceApprovement = is_voice_approvement(Els) and - not is_visitor(From, StateData), - if IsInvitation -> - case catch check_invitation(From, Packet, Lang, StateData) - of - {error, Error} -> - Err = jlib:make_error_reply(Packet, Error), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData}; - IJID -> - Config = StateData#state.config, - case Config#config.members_only of - true -> - case get_affiliation(IJID, StateData) of - none -> - NSD = set_affiliation(IJID, member, - StateData), - send_affiliation(IJID, member, - StateData), - store_room(NSD), - {next_state, normal_state, NSD}; - _ -> {next_state, normal_state, StateData} - end; - false -> {next_state, normal_state, StateData} - end - end; - IsVoiceRequest -> - NewStateData = case - (StateData#state.config)#config.allow_voice_requests - of - true -> - MinInterval = - (StateData#state.config)#config.voice_request_min_interval, - BareFrom = - jid:remove_resource(jid:tolower(From)), - NowPriority = -p1_time_compat:system_time(micro_seconds), - CleanPriority = NowPriority + - MinInterval * - 1000000, - Times = - clean_treap(StateData#state.last_voice_request_time, - CleanPriority), - case treap:lookup(BareFrom, Times) - of - error -> - Times1 = - treap:insert(BareFrom, - NowPriority, - true, Times), - NSD = - StateData#state{last_voice_request_time - = - Times1}, - send_voice_request(From, NSD), - NSD; - {ok, _, _} -> - ErrText = - <<"Please, wait for a while before sending " - "new voice request">>, - Err = - jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData#state{last_voice_request_time - = Times} - end; - false -> - ErrText = - <<"Voice requests are disabled in this " - "conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData - end, - {next_state, normal_state, NewStateData}; - IsVoiceApprovement -> - NewStateData = case is_moderator(From, StateData) of - true -> - case - extract_jid_from_voice_approvement(Els) - of - error -> - ErrText = - <<"Failed to extract JID from your voice " - "request approval">>, - Err = - jlib:make_error_reply(Packet, - ?ERRT_BAD_REQUEST(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData; - {ok, TargetJid} -> - case is_visitor(TargetJid, - StateData) - of - true -> - Reason = <<>>, - NSD = - set_role(TargetJid, - participant, - StateData), - catch - send_new_presence(TargetJid, - Reason, - NSD, - StateData), - NSD; - _ -> StateData - end - end; - _ -> - ErrText = - <<"Only moderators can approve voice requests">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, - From, Err), - StateData - end, - {next_state, normal_state, NewStateData}; - true -> {next_state, normal_state, StateData} - end; - _ -> - ErrText = <<"Improper message type">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end; - _ -> - case fxml:get_attr_s(<<"type">>, Attrs) of - <<"error">> -> ok; - _ -> - handle_roommessage_from_nonparticipant(Packet, Lang, - StateData, From) - end, - {next_state, normal_state, StateData} + is_user_allowed_message_nonparticipant(From, StateData) of + true when Type == groupchat -> + Activity = get_user_activity(From, StateData), + Now = p1_time_compat:system_time(micro_seconds), + MinMessageInterval = trunc(gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, min_message_interval, + fun(MMI) when is_number(MMI) -> MMI end, 0) + * 1000000), + Size = element_size(Packet), + {MessageShaper, MessageShaperInterval} = + shaper:update(Activity#activity.message_shaper, Size), + if Activity#activity.message /= undefined -> + ErrText = <<"Traffic rate limit is exceeded">>, + Err = xmpp:make_error( + Packet, + xmpp:err_resource_constraint(ErrText, Lang)), + ejabberd_router:route(StateData#state.jid, From, Err), + {next_state, normal_state, StateData}; + Now >= Activity#activity.message_time + MinMessageInterval, + MessageShaperInterval == 0 -> + {RoomShaper, RoomShaperInterval} = + shaper:update(StateData#state.room_shaper, Size), + RoomQueueEmpty = queue:is_empty(StateData#state.room_queue), + if RoomShaperInterval == 0, RoomQueueEmpty -> + NewActivity = Activity#activity{ + message_time = Now, + message_shaper = MessageShaper}, + StateData1 = store_user_activity(From, + NewActivity, + StateData), + StateData2 = StateData1#state{room_shaper = + RoomShaper}, + process_groupchat_message(From, Packet, + StateData2); + true -> + StateData1 = if RoomQueueEmpty -> + erlang:send_after(RoomShaperInterval, + self(), + process_room_queue), + StateData#state{room_shaper = + RoomShaper}; + true -> StateData + end, + NewActivity = Activity#activity{ + message_time = Now, + message_shaper = MessageShaper, + message = Packet}, + RoomQueue = queue:in({message, From}, + StateData#state.room_queue), + StateData2 = store_user_activity(From, + NewActivity, + StateData1), + StateData3 = StateData2#state{room_queue = RoomQueue}, + {next_state, normal_state, StateData3} + end; + true -> + MessageInterval = (Activity#activity.message_time + + MinMessageInterval - Now) div 1000, + Interval = lists:max([MessageInterval, + MessageShaperInterval]), + erlang:send_after(Interval, self(), + {process_user_message, From}), + NewActivity = Activity#activity{ + message = Packet, + message_shaper = MessageShaper}, + StateData1 = store_user_activity(From, NewActivity, StateData), + {next_state, normal_state, StateData1} + end; + true when Type == error -> + case is_user_online(From, StateData) of + true -> + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + NewState = expulse_participant(Packet, From, StateData, + translate:translate(Lang, + ErrorText)), + close_room_if_temporary_and_empty(NewState); + _ -> + {next_state, normal_state, StateData} + end; + true when Type == chat -> + ErrText = <<"It is not allowed to send private messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), + {next_state, normal_state, StateData}; + true when Type == normal -> + {next_state, normal_state, + try xmpp:decode_els(Packet) of + Pkt -> process_normal_message(From, Pkt, StateData) + catch _:{xmpp_codec, Why} -> + Txt = xmpp:format_error(Why), + Err = xmpp:err_bad_request(Txt, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Packet, Err), + StateData + end}; + true -> + ErrText = <<"Improper message type">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), + {next_state, normal_state, StateData}; + false when Type /= error -> + handle_roommessage_from_nonparticipant(Packet, StateData, From); + false -> + {next_state, normal_state, StateData} end; normal_state({route, From, <<"">>, - #xmlel{name = <<"iq">>} = Packet}, - StateData) -> - case jlib:iq_query_info(Packet) of - reply -> - {next_state, normal_state, StateData}; - IQ0 -> - case ejabberd_hooks:run_fold( - muc_process_iq, - StateData#state.server_host, - IQ0, [StateData, From, StateData#state.jid]) of - ignore -> - {next_state, normal_state, StateData}; - #iq{type = T} = IQRes when T == error; T == result -> - ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)), - {next_state, normal_state, StateData}; - #iq{type = Type, xmlns = XMLNS, lang = Lang, - sub_el = #xmlel{name = SubElName, attrs = Attrs} = SubEl} = IQ - when (XMLNS == (?NS_MUC_ADMIN)) or - (XMLNS == (?NS_MUC_OWNER)) - or (XMLNS == (?NS_DISCO_INFO)) - or (XMLNS == (?NS_DISCO_ITEMS)) - or (XMLNS == (?NS_VCARD)) - or (XMLNS == (?NS_MUCSUB)) - or (XMLNS == (?NS_CAPTCHA)) -> - Res1 = case XMLNS of - ?NS_MUC_ADMIN -> - process_iq_admin(From, Type, Lang, SubEl, StateData); - ?NS_MUC_OWNER -> - process_iq_owner(From, Type, Lang, SubEl, StateData); - ?NS_DISCO_INFO -> - case fxml:get_attr(<<"node">>, Attrs) of - false -> process_iq_disco_info(From, Type, Lang, StateData); - {value, _} -> - Txt = <<"Disco info is not available for this node">>, - {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)} - end; - ?NS_DISCO_ITEMS -> - process_iq_disco_items(From, Type, Lang, StateData); - ?NS_VCARD -> - process_iq_vcard(From, Type, Lang, SubEl, StateData); - ?NS_MUCSUB -> - process_iq_mucsub(From, Packet, IQ, StateData); - ?NS_CAPTCHA -> - process_iq_captcha(From, Type, Lang, SubEl, StateData) - end, - {IQRes, NewStateData} = - case Res1 of - {result, Res, SD} -> - {IQ#iq{type = result, - sub_el = - [#xmlel{name = SubElName, - attrs = - [{<<"xmlns">>, - XMLNS}], - children = Res}]}, - SD}; - {ignore, SD} -> {ignore, SD}; - {error, Error, ResStateData} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - ResStateData}; - {error, Error} -> - {IQ#iq{type = error, - sub_el = [SubEl, Error]}, - StateData} - end, - if IQRes /= ignore -> - ejabberd_router:route( - StateData#state.jid, From, jlib:iq_to_xml(IQRes)); - true -> - ok + #iq{type = Type, lang = Lang, sub_els = [_]} = IQ0}, + StateData) when Type == get; Type == set -> + try + case ejabberd_hooks:run_fold( + muc_process_iq, + StateData#state.server_host, + xmpp:set_from_to(xmpp:decode_els(IQ0), + From, StateData#state.jid), + [StateData]) of + ignore -> + {next_state, normal_state, StateData}; + #iq{type = T} = IQRes when T == error; T == result -> + ejabberd_router:route(StateData#state.jid, From, IQRes), + {next_state, normal_state, StateData}; + #iq{sub_els = [SubEl]} = IQ -> + Res1 = case xmpp:get_ns(SubEl) of + ?NS_MUC_ADMIN -> + process_iq_admin(From, IQ, StateData); + ?NS_MUC_OWNER -> + process_iq_owner(From, IQ, StateData); + ?NS_DISCO_INFO when SubEl#disco_info.node == undefined -> + process_iq_disco_info(From, IQ, StateData); + ?NS_DISCO_INFO -> + Txt = <<"Disco info is not available for this node">>, + {error, xmpp:err_service_unavailable(Txt, Lang)}; + ?NS_DISCO_ITEMS -> + process_iq_disco_items(From, IQ, StateData); + ?NS_VCARD -> + process_iq_vcard(From, IQ, StateData); + ?NS_MUCSUB -> + process_iq_mucsub(From, IQ, StateData); + ?NS_CAPTCHA -> + process_iq_captcha(From, IQ, StateData); + _ -> + {error, xmpp:err_feature_not_implemented()} + end, + {IQRes, NewStateData} = + case Res1 of + {result, Res, SD} -> + {xmpp:make_iq_result(IQ, Res), SD}; + {result, Res} -> + {xmpp:make_iq_result(IQ, Res), StateData}; + {ignore, SD} -> + {ignore, SD}; + {error, Error, ResStateData} -> + {xmpp:make_error(IQ0, Error), ResStateData}; + {error, Error} -> + {xmpp:make_error(IQ0, Error), StateData} end, - case NewStateData of - stop -> {stop, normal, StateData}; - _ -> {next_state, normal_state, NewStateData} - end; - _ -> - Err = jlib:make_error_reply(Packet, - ?ERR_FEATURE_NOT_IMPLEMENTED), - ejabberd_router:route(StateData#state.jid, From, Err), - {next_state, normal_state, StateData} - end + if IQRes /= ignore -> + ejabberd_router:route(StateData#state.jid, From, IQRes); + true -> + ok + end, + case NewStateData of + stop -> {stop, normal, StateData}; + _ -> {next_state, normal_state, NewStateData} + end + end + catch _:{xmpp_codec, Why} -> + ErrTxt = xmpp:format_error(Why), + Err = xmpp:make_error(IQ0, xmpp:err_bad_request(ErrTxt, Lang)), + ejabberd_router:route(StateData#state.jid, From, Err) end; -normal_state({route, From, Nick, - #xmlel{name = <<"presence">>} = Packet}, - StateData) -> +normal_state({route, From, <<"">>, #iq{} = IQ}, StateData) -> + Err = xmpp:err_bad_request(), + ejabberd_router:route_error(StateData#state.jid, From, IQ, Err), + {next_state, normal_state, StateData}; +normal_state({route, From, Nick, #presence{} = Packet}, StateData) -> Activity = get_user_activity(From, StateData), Now = p1_time_compat:system_time(micro_seconds), MinPresenceInterval = @@ -485,185 +335,135 @@ normal_state({route, From, Nick, I end, 0) * 1000000), - if (Now >= - Activity#activity.presence_time + MinPresenceInterval) - and (Activity#activity.presence == undefined) -> - NewActivity = Activity#activity{presence_time = Now}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - process_presence(From, Nick, Packet, StateData1); + if (Now >= Activity#activity.presence_time + MinPresenceInterval) + and (Activity#activity.presence == undefined) -> + NewActivity = Activity#activity{presence_time = Now}, + StateData1 = store_user_activity(From, NewActivity, + StateData), + process_presence(From, Nick, Packet, StateData1); true -> - if Activity#activity.presence == undefined -> - Interval = (Activity#activity.presence_time + - MinPresenceInterval - - Now) - div 1000, - erlang:send_after(Interval, self(), - {process_user_presence, From}); - true -> ok - end, - NewActivity = Activity#activity{presence = - {Nick, Packet}}, - StateData1 = store_user_activity(From, NewActivity, - StateData), - {next_state, normal_state, StateData1} + if Activity#activity.presence == undefined -> + Interval = (Activity#activity.presence_time + + MinPresenceInterval - Now) div 1000, + erlang:send_after(Interval, self(), + {process_user_presence, From}); + true -> ok + end, + NewActivity = Activity#activity{presence = {Nick, Packet}}, + StateData1 = store_user_activity(From, NewActivity, + StateData), + {next_state, normal_state, StateData1} end; normal_state({route, From, ToNick, - #xmlel{name = <<"message">>, attrs = Attrs} = Packet}, + #message{type = Type, lang = Lang} = Packet}, StateData) -> - Type = fxml:get_attr_s(<<"type">>, Attrs), - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - case decide_fate_message(Type, Packet, From, StateData) - of - {expulse_sender, Reason} -> - ?DEBUG(Reason, []), - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, - NewState = expulse_participant(Packet, From, StateData, - translate:translate(Lang, ErrorText)), - {next_state, normal_state, NewState}; - forget_message -> {next_state, normal_state, StateData}; - continue_delivery -> - case - {(StateData#state.config)#config.allow_private_messages, - is_user_online(From, StateData)} - of - {true, true} -> - case Type of - <<"groupchat">> -> - ErrText = - <<"It is not allowed to send private messages " - "of type \"groupchat\"">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_BAD_REQUEST(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err); - _ -> - case find_jids_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)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err); + case decide_fate_message(Packet, From, StateData) of + {expulse_sender, Reason} -> + ?DEBUG(Reason, []), + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + NewState = expulse_participant(Packet, From, StateData, + translate:translate(Lang, ErrorText)), + {next_state, normal_state, NewState}; + forget_message -> + {next_state, normal_state, StateData}; + continue_delivery -> + case {(StateData#state.config)#config.allow_private_messages, + is_user_online(From, StateData)} of + {true, true} when Type == groupchat -> + ErrText = <<"It is not allowed to send private messages " + "of type \"groupchat\"">>, + Err = xmpp:err_bad_request(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + {true, true} -> + case find_jids_by_nick(ToNick, StateData) of + [] -> + ErrText = <<"Recipient is not in the conference room">>, + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); ToJIDs -> SrcIsVisitor = is_visitor(From, StateData), - DstIsModerator = is_moderator(hd(ToJIDs), - StateData), + DstIsModerator = is_moderator(hd(ToJIDs), StateData), PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors, if SrcIsVisitor == false; PmFromVisitors == anyone; (PmFromVisitors == moderators) and - DstIsModerator -> - {ok, #user{nick = FromNick}} = - (?DICT):find(jid:tolower(From), - StateData#state.users), - FromNickJID = - jid:replace_resource(StateData#state.jid, - FromNick), - X = #xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}]}, - PrivMsg = fxml:append_subtags(Packet, [X]), - [ejabberd_router:route(FromNickJID, ToJID, PrivMsg) - || ToJID <- ToJIDs]; + DstIsModerator -> + {ok, #user{nick = FromNick}} = + (?DICT):find(jid:tolower(From), + StateData#state.users), + FromNickJID = + jid:replace_resource(StateData#state.jid, + FromNick), + X = #muc_user{}, + PrivMsg = xmpp:set_subtag(Packet, X), + [ejabberd_router:route(FromNickJID, ToJID, PrivMsg) + || ToJID <- ToJIDs]; true -> - ErrText = - <<"It is not allowed to send private messages">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_FORBIDDEN(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) + ErrText = <<"It is not allowed to send private messages">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err) end - end - 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)), - ejabberd_router:route(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)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end, + end; + {true, false} -> + ErrText = <<"Only occupants are allowed to send messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + {false, _} -> + ErrText = <<"It is not allowed to send private messages">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err) + end, {next_state, normal_state, StateData} end; normal_state({route, From, ToNick, - #xmlel{name = <<"iq">>, attrs = Attrs} = Packet}, + #iq{id = StanzaId, lang = Lang} = Packet}, StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - StanzaId = fxml:get_attr_s(<<"id">>, Attrs), case {(StateData#state.config)#config.allow_query_users, - is_user_online_iq(StanzaId, From, StateData)} - of - {true, {true, NewId, FromFull}} -> - case find_jid_by_nick(ToNick, StateData) of - false -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = <<"Recipient is not in the conference room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_ITEM_NOT_FOUND(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end; - ToJID -> - {ok, #user{nick = FromNick}} = - (?DICT):find(jid:tolower(FromFull), - StateData#state.users), - {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID, - StanzaId, NewId, Packet), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - FromNick), - ToJID2, Packet2) - end; - {_, {false, _, _}} -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = - <<"Only occupants are allowed to send queries " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end; - _ -> - case jlib:iq_query_info(Packet) of - reply -> ok; - _ -> - ErrText = <<"Queries to the conference members are " - "not allowed in this room">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ALLOWED(Lang, ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - ToNick), - From, Err) - end + is_user_online_iq(StanzaId, From, StateData)} of + {true, {true, NewId, FromFull}} -> + case find_jid_by_nick(ToNick, StateData) of + false -> + ErrText = <<"Recipient is not in the conference room">>, + Err = xmpp:err_item_not_found(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + ToJID -> + {ok, #user{nick = FromNick}} = + (?DICT):find(jid:tolower(FromFull), StateData#state.users), + {ToJID2, Packet2} = handle_iq_vcard(ToJID, NewId, Packet), + ejabberd_router:route( + jid:replace_resource(StateData#state.jid, FromNick), + ToJID2, Packet2) + end; + {_, {false, _, _}} -> + ErrText = <<"Only occupants are allowed to send queries " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err); + _ -> + ErrText = <<"Queries to the conference members are " + "not allowed in this room">>, + Err = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, ToNick), + From, Packet, Err) end, {next_state, normal_state, StateData}; normal_state(_Event, StateData) -> @@ -671,11 +471,7 @@ normal_state(_Event, StateData) -> handle_event({service_message, Msg}, _StateName, StateData) -> - MessagePkt = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"body">>, attrs = [], - children = [{xmlcdata, Msg}]}]}, + MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)}, send_wrapped_multiple( StateData#state.jid, StateData#state.users, @@ -687,22 +483,9 @@ handle_event({service_message, Msg}, _StateName, {next_state, normal_state, NSD}; handle_event({destroy, Reason}, _StateName, StateData) -> - {result, [], stop} = destroy_room(#xmlel{name = - <<"destroy">>, - attrs = - [{<<"xmlns">>, ?NS_MUC_OWNER}], - children = - case Reason of - none -> []; - _Else -> - [#xmlel{name = - <<"reason">>, - attrs = [], - children = - [{xmlcdata, - Reason}]}] - end}, - StateData), + {result, undefined, stop} = + destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason}, + StateData), ?INFO_MSG("Destroyed MUC room ~s with reason: ~p", [jid:to_string(StateData#state.jid), Reason]), add_to_log(room_existence, destroyed, StateData), @@ -710,7 +493,7 @@ handle_event({destroy, Reason}, _StateName, handle_event(destroy, StateName, StateData) -> ?INFO_MSG("Destroyed MUC room ~s", [jid:to_string(StateData#state.jid)]), - handle_event({destroy, none}, StateName, StateData); + handle_event({destroy, undefined}, StateName, StateData); handle_event({set_affiliations, Affiliations}, StateName, StateData) -> {next_state, StateName, @@ -741,7 +524,7 @@ handle_sync_event(get_state, _From, StateName, {reply, {ok, StateData}, StateName, StateData}; handle_sync_event({change_config, Config}, _From, StateName, StateData) -> - {result, [], NSD} = change_config(Config, StateData), + {result, undefined, NSD} = change_config(Config, StateData), {reply, {ok, NSD#state.config}, StateName, NSD}; handle_sync_event({change_state, NewStateData}, _From, StateName, _StateData) -> @@ -821,12 +604,11 @@ handle_info({captcha_failed, From}, normal_state, {ok, {Nick, Packet}} -> Robots = (?DICT):erase(From, StateData#state.robots), Txt = <<"The CAPTCHA verification has failed">>, - Err = jlib:make_error_reply( - Packet, ?ERRT_NOT_AUTHORIZED(?MYLANG, Txt)), - ejabberd_router:route % TODO: s/Nick/""/ - (jid:replace_resource(StateData#state.jid, - Nick), - From, Err), + Lang = xmpp:get_lang(Packet), + Err = xmpp:err_not_authorized(Txt, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), StateData#state{robots = Robots}; _ -> StateData end, @@ -845,22 +627,12 @@ terminate(Reason, _StateName, StateData) -> "because of a system shutdown">>; _ -> <<"Room terminates">> end, - ItemAttrs = [{<<"affiliation">>, <<"none">>}, - {<<"role">>, <<"none">>}], - ReasonEl = #xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, ReasonT}]}, - Packet = #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = ItemAttrs, - children = [ReasonEl]}, - #xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"332">>}], - children = []}]}]}, + Packet = #presence{ + type = unavailable, + sub_els = [#muc_user{items = [#muc_item{affiliation = none, + reason = ReasonT, + role = none}], + status_codes = [332]}]}, (?DICT):fold(fun (LJID, Info, _) -> Nick = Info#user.nick, case Reason of @@ -883,14 +655,12 @@ terminate(Reason, _StateName, StateData) -> %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- - +-spec route(pid(), jid(), binary(), stanza()) -> ok. route(Pid, From, ToNick, Packet) -> gen_fsm:send_event(Pid, {route, From, ToNick, Packet}). -process_groupchat_message(From, - #xmlel{name = <<"message">>, attrs = Attrs} = Packet, - StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), +-spec process_groupchat_message(jid(), message(), state()) -> fsm_next(). +process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) -> case is_user_online(From, StateData) orelse is_user_allowed_message_nonparticipant(From, StateData) of @@ -932,7 +702,7 @@ process_groupchat_message(From, drop -> {next_state, normal_state, StateData}; NewPacket1 -> - NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}), + NewPacket = xmpp:remove_subtag(NewPacket1, #nick{}), Node = if Subject == false -> ?NS_MUCSUB_NODES_MESSAGES; true -> ?NS_MUCSUB_NODES_SUBJECT end, @@ -951,41 +721,136 @@ process_groupchat_message(From, {next_state, normal_state, NewStateData2} end; _ -> - Err = case - (StateData#state.config)#config.allow_change_subj - of + Err = case (StateData#state.config)#config.allow_change_subj of true -> - ?ERRT_FORBIDDEN(Lang, - <<"Only moderators and participants are " - "allowed to change the subject in this " - "room">>); + xmpp:err_forbidden( + <<"Only moderators and participants are " + "allowed to change the subject in this " + "room">>, Lang); _ -> - ?ERRT_FORBIDDEN(Lang, - <<"Only moderators are allowed to change " - "the subject in this room">>) + xmpp:err_forbidden( + <<"Only moderators are allowed to change " + "the subject in this room">>, Lang) end, - ejabberd_router:route(StateData#state.jid, From, - jlib:make_error_reply(Packet, Err)), + ejabberd_router:route_error( + StateData#state.jid, From, Packet, 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)), - ejabberd_router:route(StateData#state.jid, From, Err), + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Packet, Err), {next_state, normal_state, StateData} end; false -> - ErrText = - <<"Only occupants are allowed to send messages " - "to the conference">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(StateData#state.jid, From, Err), + ErrText = <<"Only occupants are allowed to send messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err), {next_state, normal_state, StateData} end. +-spec process_normal_message(jid(), message(), state()) -> state(). +process_normal_message(From, #message{lang = Lang} = Pkt, StateData) -> + IsInvitation = is_invitation(Pkt), + IsVoiceRequest = is_voice_request(Pkt) and + is_visitor(From, StateData), + IsVoiceApprovement = is_voice_approvement(Pkt) and + not is_visitor(From, StateData), + if IsInvitation -> + case check_invitation(From, Pkt, StateData) of + {error, Error} -> + ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error), + StateData; + IJID -> + Config = StateData#state.config, + case Config#config.members_only of + true -> + case get_affiliation(IJID, StateData) of + none -> + NSD = set_affiliation(IJID, member, StateData), + send_affiliation(IJID, member, StateData), + store_room(NSD), + NSD; + _ -> + StateData + end; + false -> + StateData + end + end; + IsVoiceRequest -> + case (StateData#state.config)#config.allow_voice_requests of + true -> + MinInterval = (StateData#state.config)#config.voice_request_min_interval, + BareFrom = jid:remove_resource(jid:tolower(From)), + NowPriority = -p1_time_compat:system_time(micro_seconds), + CleanPriority = NowPriority + MinInterval * 1000000, + Times = clean_treap(StateData#state.last_voice_request_time, + CleanPriority), + case treap:lookup(BareFrom, Times) of + error -> + Times1 = treap:insert(BareFrom, + NowPriority, + true, Times), + NSD = StateData#state{last_voice_request_time = Times1}, + send_voice_request(From, Lang, NSD), + NSD; + {ok, _, _} -> + ErrText = <<"Please, wait for a while before sending " + "new voice request">>, + Err = xmpp:err_not_acceptable(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData#state{last_voice_request_time = Times} + end; + false -> + ErrText = <<"Voice requests are disabled in this conference">>, + Err = xmpp:err_forbidden(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData + end; + IsVoiceApprovement -> + case is_moderator(From, StateData) of + true -> + case extract_jid_from_voice_approvement(Pkt) of + error -> + ErrText = <<"Failed to extract JID from your voice " + "request approval">>, + Err = xmpp:err_bad_request(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData; + TargetJid -> + case is_visitor(TargetJid, StateData) of + true -> + Reason = <<>>, + NSD = set_role(TargetJid, + participant, + StateData), + catch send_new_presence(TargetJid, + Reason, + NSD, + StateData), + NSD; + _ -> + StateData + end + end; + _ -> + ErrText = <<"Only moderators can approve voice requests">>, + Err = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + StateData#state.jid, From, Pkt, Err), + StateData + end; + true -> + StateData + end. + %% @doc Check if this non participant can send message to room. %% %% XEP-0045 v1.23: @@ -993,6 +858,7 @@ process_groupchat_message(From, %% an implementation MAY allow users with certain privileges %% (e.g., a room owner, room admin, or service-level admin) %% to send messages to the room even if those users are not occupants. +-spec is_user_allowed_message_nonparticipant(jid(), state()) -> boolean(). is_user_allowed_message_nonparticipant(JID, StateData) -> case get_service_affiliation(JID, StateData) of @@ -1002,6 +868,7 @@ is_user_allowed_message_nonparticipant(JID, %% @doc Get information of this participant, or default values. %% If the JID is not a participant, return values for a service message. +-spec get_participant_data(jid(), state()) -> {binary(), role(), boolean()}. get_participant_data(From, StateData) -> case (?DICT):find(jid:tolower(From), StateData#state.users) @@ -1011,14 +878,11 @@ get_participant_data(From, StateData) -> error -> {<<"">>, moderator, false} end. -process_presence(From, Nick, - #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0, - StateData) -> - Type0 = fxml:get_attr_s(<<"type">>, Attrs0), +-spec process_presence(jid(), binary(), presence(), state()) -> fsm_transition(). +process_presence(From, Nick, #presence{type = Type0} = Packet0, StateData) -> IsOnline = is_user_online(From, StateData), - IsSubscriber = is_subscriber(From, StateData), - if Type0 == <<"">>; - IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) -> + if Type0 == available; + IsOnline and ((Type0 == unavailable) or (Type0 == error)) -> case ejabberd_hooks:run_fold(muc_filter_presence, StateData#state.server_host, Packet0, @@ -1027,119 +891,104 @@ process_presence(From, Nick, From, Nick]) of drop -> {next_state, normal_state, StateData}; - #xmlel{attrs = Attrs} = Packet -> - Type = fxml:get_attr_s(<<"type">>, Attrs), - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), - StateData1 = case Type of - <<"unavailable">> -> - NewPacket = case - {(StateData#state.config)#config.allow_visitor_status, - is_visitor(From, StateData)} - of - {false, true} -> - strip_status(Packet); - _ -> Packet - end, - NewState = add_user_presence_un(From, NewPacket, - StateData), - case (?DICT):find(Nick, StateData#state.nicks) of - {ok, [_, _ | _]} -> ok; - _ -> send_new_presence(From, NewState, StateData) - end, - Reason = case fxml:get_subtag(NewPacket, - <<"status">>) - of - false -> <<"">>; - Status_el -> - fxml:get_tag_cdata(Status_el) - end, - remove_online_user(From, NewState, IsSubscriber, Reason); - <<"error">> -> - ErrorText = <<"It is not allowed to send error messages to the" - " room. The participant (~s) has sent an error " - "message (~s) and got kicked from the room">>, - expulse_participant(Packet, From, StateData, - translate:translate(Lang, - ErrorText)); - <<"">> -> - if not IsOnline -> - add_new_user(From, Nick, Packet, StateData); - true -> - case is_nick_change(From, Nick, StateData) of - true -> - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, - From, Nick), - {(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)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - {true, _, _} -> - Lang = fxml:get_attr_s(<<"xml:lang">>, - Attrs), - ErrText = - <<"That nickname is already in use by another " - "occupant">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), % TODO: s/Nick/""/ - From, Err), - StateData; - {_, false, _} -> - ErrText = - <<"That nickname is registered by another " - "person">>, - Err = jlib:make_error_reply(Packet, - ?ERRT_CONFLICT(Lang, - ErrText)), - ejabberd_router:route(jid:replace_resource(StateData#state.jid, - Nick), - From, Err), - StateData; - _ -> - case is_initial_presence(From, StateData) of - true -> - subscriber_becomes_available( - From, Nick, Packet, StateData); - false -> - change_nick(From, Nick, StateData) - end - end; - _NotNickChange -> - case is_initial_presence(From, StateData) of - true -> - subscriber_becomes_available( - From, Nick, Packet, StateData); - false -> - Stanza = maybe_strip_status_from_presence( - From, Packet, StateData), - NewState = add_user_presence(From, Stanza, - StateData), - send_new_presence(From, NewState, StateData), - NewState - end - end - end - end, - close_room_if_temporary_and_empty(StateData1) + #presence{} = Packet -> + close_room_if_temporary_and_empty( + do_process_presence(From, Nick, Packet, StateData)) end; true -> - {next_state, normal_state, StateData} + {next_state, normal_state, StateData} end. +-spec do_process_presence(jid(), binary(), presence(), state()) -> + state(). +do_process_presence(From, Nick, #presence{type = available, lang = Lang} = Packet, + StateData) -> + case is_user_online(From, StateData) of + false -> + add_new_user(From, Nick, Packet, StateData); + true -> + case is_nick_change(From, Nick, StateData) of + true -> + case {nick_collision(From, Nick, StateData), + mod_muc:can_use_nick(StateData#state.server_host, + StateData#state.host, + From, Nick), + {(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 = xmpp:err_not_allowed(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), + StateData; + {true, _, _} -> + ErrText = <<"That nickname is already in use by another " + "occupant">>, + Err = xmpp:err_conflict(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), + StateData; + {_, false, _} -> + ErrText = <<"That nickname is registered by another " + "person">>, + Err = xmpp:err_conflict(ErrText, Lang), + ejabberd_router:route_error( + jid:replace_resource(StateData#state.jid, Nick), + From, Packet, Err), + StateData; + _ -> + case is_initial_presence(From, StateData) of + true -> + subscriber_becomes_available( + From, Nick, Packet, StateData); + false -> + change_nick(From, Nick, StateData) + end + end; + _NotNickChange -> + case is_initial_presence(From, StateData) of + true -> + subscriber_becomes_available( + From, Nick, Packet, StateData); + false -> + Stanza = maybe_strip_status_from_presence( + From, Packet, StateData), + NewState = add_user_presence(From, Stanza, + StateData), + send_new_presence(From, NewState, StateData), + NewState + end + end + end; +do_process_presence(From, Nick, #presence{type = unavailable} = Packet, + StateData) -> + IsSubscriber = is_subscriber(From, StateData), + NewPacket = case {(StateData#state.config)#config.allow_visitor_status, + is_visitor(From, StateData)} of + {false, true} -> + strip_status(Packet); + _ -> Packet + end, + NewState = add_user_presence_un(From, NewPacket, StateData), + case (?DICT):find(Nick, StateData#state.nicks) of + {ok, [_, _ | _]} -> ok; + _ -> send_new_presence(From, NewState, StateData) + end, + Reason = xmpp:get_text(NewPacket#presence.status), + remove_online_user(From, NewState, IsSubscriber, Reason); +do_process_presence(From, _Nick, #presence{type = error, lang = Lang} = Packet, + StateData) -> + ErrorText = <<"It is not allowed to send error messages to the" + " room. The participant (~s) has sent an error " + "message (~s) and got kicked from the room">>, + expulse_participant(Packet, From, StateData, + translate:translate(Lang, ErrorText)). + +-spec maybe_strip_status_from_presence(jid(), presence(), + state()) -> presence(). maybe_strip_status_from_presence(From, Packet, StateData) -> case {(StateData#state.config)#config.allow_visitor_status, is_visitor(From, StateData)} of @@ -1148,6 +997,8 @@ maybe_strip_status_from_presence(From, Packet, StateData) -> _Allowed -> Packet end. +-spec subscriber_becomes_available(jid(), binary(), presence(), + state()) -> state(). subscriber_becomes_available(From, Nick, Packet, StateData) -> Stanza = maybe_strip_status_from_presence(From, Packet, StateData), State1 = add_user_presence(From, Stanza, StateData), @@ -1159,9 +1010,10 @@ subscriber_becomes_available(From, Nick, Packet, StateData) -> send_initial_presence(From, State3, StateData), State3. +-spec close_room_if_temporary_and_empty(state()) -> fsm_transition(). close_room_if_temporary_and_empty(StateData1) -> case not (StateData1#state.config)#config.persistent - andalso (?DICT):to_list(StateData1#state.users) == [] + andalso (?DICT):size(StateData1#state.users) == 0 of true -> ?INFO_MSG("Destroyed MUC room ~s because it's temporary " @@ -1172,10 +1024,12 @@ close_room_if_temporary_and_empty(StateData1) -> _ -> {next_state, normal_state, StateData1} end. +-spec is_user_online(jid(), state()) -> boolean(). is_user_online(JID, StateData) -> LJID = jid:tolower(JID), (?DICT):is_key(LJID, StateData#state.users). +-spec is_subscriber(jid(), state()) -> boolean(). is_subscriber(JID, StateData) -> LJID = jid:tolower(JID), case (?DICT):find(LJID, StateData#state.users) of @@ -1186,6 +1040,7 @@ is_subscriber(JID, StateData) -> end. %% Check if the user is occupant of the room, or at least is an admin or owner. +-spec is_occupant_or_admin(jid(), state()) -> boolean(). is_occupant_or_admin(JID, StateData) -> FAffiliation = get_affiliation(JID, StateData), FRole = get_role(JID, StateData), @@ -1200,6 +1055,8 @@ is_occupant_or_admin(JID, StateData) -> %%% %%% Handle IQ queries of vCard %%% +-spec is_user_online_iq(binary(), jid(), state()) -> + {boolean(), binary(), jid()}. is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= <<"">> -> {is_user_online(JID, StateData), StanzaId, JID}; @@ -1207,93 +1064,55 @@ is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == <<"">> -> try stanzaid_unpack(StanzaId) of {OriginalId, Resource} -> - JIDWithResource = jid:replace_resource(JID, - Resource), + JIDWithResource = jid:replace_resource(JID, Resource), {is_user_online(JIDWithResource, StateData), OriginalId, JIDWithResource} catch _:_ -> {is_user_online(JID, StateData), StanzaId, JID} end. -handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, - Packet) -> +-spec handle_iq_vcard(jid(), binary(), iq()) -> {jid(), iq()}. +handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) -> ToBareJID = jid:remove_resource(ToJID), - IQ = jlib:iq_query_info(Packet), - handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, - NewId, IQ, Packet). - -handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, - _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet) - when ToBareJID /= ToJID -> - {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)}; -handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, - _StanzaId, NewId, _IQ, Packet) -> - {ToJID, change_stanzaid(NewId, Packet)}. + case SubEls of + [SubEl] when Type == get, ToBareJID /= ToJID -> + case xmpp:get_ns(SubEl) of + ?NS_VCARD -> + {ToBareJID, change_stanzaid(ToJID, IQ)}; + _ -> + {ToJID, xmpp:set_id(IQ, NewId)} + end; + _ -> + {ToJID, xmpp:set_id(IQ, NewId)} + end. +-spec stanzaid_pack(binary(), binary()) -> binary(). stanzaid_pack(OriginalId, Resource) -> <<"berd", (jlib:encode_base64(<<"ejab\000", OriginalId/binary, "\000", Resource/binary>>))/binary>>. +-spec stanzaid_unpack(binary()) -> {binary(), binary()}. stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) -> StanzaId = jlib:decode_base64(StanzaIdBase64), [<<"ejab">>, OriginalId, Resource] = str:tokens(StanzaId, <<"\000">>), {OriginalId, Resource}. -change_stanzaid(NewId, Packet) -> - #xmlel{name = Name, attrs = Attrs, children = Els} = - jlib:remove_attr(<<"id">>, Packet), - #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs], - children = Els}. - -change_stanzaid(PreviousId, ToJID, Packet) -> +-spec change_stanzaid(jid(), iq()) -> iq(). +change_stanzaid(ToJID, #iq{id = PreviousId} = Packet) -> NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource), - change_stanzaid(NewId, Packet). - -%%% -%%% - -role_to_list(Role) -> - case Role of - moderator -> <<"moderator">>; - participant -> <<"participant">>; - visitor -> <<"visitor">>; - none -> <<"none">> - end. - -affiliation_to_list(Affiliation) -> - case Affiliation of - owner -> <<"owner">>; - admin -> <<"admin">>; - member -> <<"member">>; - outcast -> <<"outcast">>; - none -> <<"none">> - end. - -list_to_role(Role) -> - case Role of - <<"moderator">> -> moderator; - <<"participant">> -> participant; - <<"visitor">> -> visitor; - <<"none">> -> none - end. - -list_to_affiliation(Affiliation) -> - case Affiliation of - <<"owner">> -> owner; - <<"admin">> -> admin; - <<"member">> -> member; - <<"outcast">> -> outcast; - <<"none">> -> none - end. + xmpp:set_id(Packet, NewId). %% Decide the fate of the message and its sender %% Returns: continue_delivery | forget_message | {expulse_sender, Reason} -decide_fate_message(<<"error">>, Packet, From, - StateData) -> - PD = case check_error_kick(Packet) of +-spec decide_fate_message(message(), jid(), state()) -> + continue_delivery | forget_message | + {expulse_sender, binary()}. +decide_fate_message(#message{type = error, error = Err}, + From, StateData) -> + PD = case check_error_kick(Err) of %% If this is an error stanza and its condition matches a criteria true -> Reason = @@ -1311,67 +1130,61 @@ decide_fate_message(<<"error">>, Packet, From, end; Other -> Other end; -decide_fate_message(_, _, _, _) -> continue_delivery. +decide_fate_message(_, _, _) -> continue_delivery. %% Check if the elements of this error stanza indicate %% that the sender is a dead participant. %% 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; - _ -> false - end. +-spec check_error_kick(error()) -> boolean(). +check_error_kick(#error{reason = Reason}) -> + case Reason 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; + _ -> false + end; +check_error_kick(undefined) -> + false. -get_error_condition(Packet) -> - case catch get_error_condition2(Packet) of - {condition, ErrorCondition} -> ErrorCondition; - {'EXIT', _} -> <<"badformed error stanza">> - end. - -get_error_condition2(Packet) -> - #xmlel{children = EEls} = fxml:get_subtag(Packet, - <<"error">>), - [Condition] = [Name - || #xmlel{name = Name, - attrs = [{<<"xmlns">>, ?NS_STANZAS}], - children = []} - <- EEls], - {condition, Condition}. +-spec get_error_condition(error()) -> string(). +get_error_condition(#error{reason = Reason}) -> + case Reason of + #gone{} -> "gone"; + #redirect{} -> "redirect"; + Atom -> atom_to_list(Atom) + end; +get_error_condition(undefined) -> + "undefined". +-spec make_reason(stanza(), jid(), state(), binary()) -> binary(). make_reason(Packet, From, StateData, Reason1) -> {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users), - Condition = get_error_condition(Packet), + Condition = get_error_condition(xmpp:get_error(Packet)), iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])). +-spec expulse_participant(stanza(), jid(), state(), binary()) -> + state(). expulse_participant(Packet, From, StateData, Reason1) -> IsSubscriber = is_subscriber(From, StateData), Reason2 = make_reason(Packet, From, StateData, Reason1), NewState = add_user_presence_un(From, - #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = <<"status">>, - attrs = [], - children = - [{xmlcdata, - Reason2}]}]}, + #presence{type = unavailable, + status = xmpp:mk_text(Reason2)}, StateData), send_new_presence(From, NewState, StateData), remove_online_user(From, NewState, IsSubscriber). +-spec set_affiliation(jid(), affiliation(), state()) -> state(). set_affiliation(JID, Affiliation, StateData) -> set_affiliation(JID, Affiliation, StateData, <<"">>). +-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state(). set_affiliation(JID, Affiliation, StateData, Reason) -> LJID = jid:remove_resource(jid:tolower(JID)), Affiliations = case Affiliation of @@ -1383,6 +1196,7 @@ set_affiliation(JID, Affiliation, StateData, Reason) -> end, StateData#state{affiliations = Affiliations}. +-spec get_affiliation(jid(), state()) -> affiliation(). get_affiliation(JID, StateData) -> {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = @@ -1423,6 +1237,7 @@ get_affiliation(JID, StateData) -> _ -> Res end. +-spec get_service_affiliation(jid(), state()) -> owner | none. get_service_affiliation(JID, StateData) -> {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = @@ -1434,6 +1249,7 @@ get_service_affiliation(JID, StateData) -> _ -> none end. +-spec set_role(jid(), role(), state()) -> state(). set_role(JID, Role, StateData) -> LJID = jid:tolower(JID), LJIDs = case LJID of @@ -1482,6 +1298,7 @@ set_role(JID, Role, StateData) -> end, StateData#state{users = Users, nicks = Nicks}. +-spec get_role(jid(), state()) -> role(). get_role(JID, StateData) -> LJID = jid:tolower(JID), case (?DICT):find(LJID, StateData#state.users) of @@ -1489,6 +1306,7 @@ get_role(JID, StateData) -> _ -> none end. +-spec get_default_role(affiliation(), state()) -> role(). get_default_role(Affiliation, StateData) -> case Affiliation of owner -> moderator; @@ -1507,12 +1325,15 @@ get_default_role(Affiliation, StateData) -> end end. +-spec is_visitor(jid(), state()) -> boolean(). is_visitor(Jid, StateData) -> get_role(Jid, StateData) =:= visitor. +-spec is_moderator(jid(), state()) -> boolean(). is_moderator(Jid, StateData) -> get_role(Jid, StateData) =:= moderator. +-spec get_max_users(state()) -> non_neg_integer(). get_max_users(StateData) -> MaxUsers = (StateData#state.config)#config.max_users, ServiceMaxUsers = get_service_max_users(StateData), @@ -1520,18 +1341,21 @@ get_max_users(StateData) -> true -> ServiceMaxUsers end. +-spec get_service_max_users(state()) -> pos_integer(). get_service_max_users(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, mod_muc, max_users, fun(I) when is_integer(I), I>0 -> I end, ?MAX_USERS_DEFAULT). +-spec get_max_users_admin_threshold(state()) -> pos_integer(). get_max_users_admin_threshold(StateData) -> gen_mod:get_module_opt(StateData#state.server_host, mod_muc, max_users_admin_threshold, fun(I) when is_integer(I), I>0 -> I end, 5). +-spec get_user_activity(jid(), state()) -> #activity{}. get_user_activity(JID, StateData) -> case treap:lookup(jid:tolower(JID), StateData#state.activity) @@ -1552,6 +1376,7 @@ get_user_activity(JID, StateData) -> presence_shaper = PresenceShaper} end. +-spec store_user_activity(jid(), #activity{}, state()) -> state(). store_user_activity(JID, UserActivity, StateData) -> MinMessageInterval = trunc(gen_mod:get_module_opt(StateData#state.server_host, @@ -1613,6 +1438,7 @@ store_user_activity(JID, UserActivity, StateData) -> end, StateData1. +-spec clean_treap(treap:treap(), integer()) -> treap:treap(). clean_treap(Treap, CleanPriority) -> case treap:is_empty(Treap) of true -> Treap; @@ -1624,6 +1450,7 @@ clean_treap(Treap, CleanPriority) -> end end. +-spec prepare_room_queue(state()) -> state(). prepare_room_queue(StateData) -> case queue:out(StateData#state.room_queue) of {{value, {message, From}}, _RoomQueue} -> @@ -1647,6 +1474,7 @@ prepare_room_queue(StateData) -> {empty, _} -> StateData end. +-spec update_online_user(jid(), #user{}, state()) -> state(). update_online_user(JID, #user{nick = Nick, subscriptions = Nodes, is_subscriber = IsSubscriber} = User, StateData) -> LJID = jid:tolower(JID), @@ -1681,6 +1509,7 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes, end, NewStateData. +-spec add_online_user(jid(), binary(), role(), boolean(), [binary()], state()) -> state(). add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) -> tab_add_online_user(JID, StateData), User = #user{jid = JID, nick = Nick, role = Role, @@ -1693,9 +1522,11 @@ add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) -> end, StateData1. +-spec remove_online_user(jid(), state(), boolean()) -> state(). remove_online_user(JID, StateData, IsSubscriber) -> remove_online_user(JID, StateData, IsSubscriber, <<"">>). +-spec remove_online_user(jid(), state(), boolean(), binary()) -> state(). remove_online_user(JID, StateData, _IsSubscriber = true, _Reason) -> LJID = jid:tolower(JID), Users = case (?DICT):find(LJID, StateData#state.users) of @@ -1723,38 +1554,23 @@ remove_online_user(JID, StateData, _IsSubscriber, Reason) -> end, StateData#state{users = Users, nicks = Nicks}. -filter_presence(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}) -> - FEls = lists:filter(fun (El) -> - case El of - {xmlcdata, _} -> false; - #xmlel{attrs = Attrs1} -> - XMLNS = fxml:get_attr_s(<<"xmlns">>, - Attrs1), - NS_MUC = ?NS_MUC, - Size = byte_size(NS_MUC), - case XMLNS of - <> -> - false; - _ -> - true - end - end - end, - Els), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = FEls}. +-spec filter_presence(presence()) -> presence(). +filter_presence(Presence) -> + Els = lists:filter( + fun(El) -> + XMLNS = xmpp:get_ns(El), + case catch binary:part(XMLNS, 0, size(?NS_MUC)) of + ?NS_MUC -> false; + _ -> true + end + end, xmpp:get_els(Presence)), + xmpp:set_els(Presence, Els). -strip_status(#xmlel{name = <<"presence">>, - attrs = Attrs, children = Els}) -> - FEls = lists:filter(fun (#xmlel{name = <<"status">>}) -> - false; - (_) -> true - end, - Els), - #xmlel{name = <<"presence">>, attrs = Attrs, - children = FEls}. +-spec strip_status(presence()) -> presence(). +strip_status(Presence) -> + Presence#presence{status = []}. +-spec add_user_presence(jid(), presence(), state()) -> state(). add_user_presence(JID, Presence, StateData) -> LJID = jid:tolower(JID), FPresence = filter_presence(Presence), @@ -1765,6 +1581,7 @@ add_user_presence(JID, Presence, StateData) -> StateData#state.users), StateData#state{users = Users}. +-spec add_user_presence_un(jid(), presence(), state()) -> state(). add_user_presence_un(JID, Presence, StateData) -> LJID = jid:tolower(JID), FPresence = filter_presence(Presence), @@ -1778,15 +1595,17 @@ add_user_presence_un(JID, Presence, StateData) -> %% Find and return a list of the full JIDs of the users of Nick. %% Return jid record. +-spec find_jids_by_nick(binary(), state()) -> [jid()]. find_jids_by_nick(Nick, StateData) -> case (?DICT):find(Nick, StateData#state.nicks) of {ok, [User]} -> [jid:make(User)]; {ok, Users} -> [jid:make(LJID) || LJID <- Users]; - error -> false + error -> [] end. %% Find and return the full JID of the user of Nick with %% highest-priority presence. Return jid record. +-spec find_jid_by_nick(binary(), state()) -> jid() | false. find_jid_by_nick(Nick, StateData) -> case (?DICT):find(Nick, StateData#state.nicks) of {ok, [User]} -> jid:make(User); @@ -1811,6 +1630,8 @@ find_jid_by_nick(Nick, StateData) -> error -> false end. +-spec higher_presence(undefined | presence(), + undefined | presence()) -> boolean(). higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined -> Pri1 = get_priority_from_presence(Pres1), Pri2 = get_priority_from_presence(Pres2), @@ -1818,26 +1639,20 @@ higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined -> higher_presence(Pres1, Pres2) -> Pres1 > Pres2. -get_priority_from_presence(PresencePacket) -> - case fxml:get_subtag(PresencePacket, <<"priority">>) of - false -> 0; - SubEl -> - case catch - jlib:binary_to_integer(fxml:get_tag_cdata(SubEl)) - of - P when is_integer(P) -> P; - _ -> 0 - end +-spec get_priority_from_presence(presence()) -> integer(). +get_priority_from_presence(#presence{priority = Prio}) -> + case Prio of + undefined -> 0; + _ -> Prio end. -find_nick_by_jid(Jid, StateData) -> - [{_, #user{nick = Nick}}] = lists:filter(fun ({_, - #user{jid = FJid}}) -> - FJid == Jid - end, - (?DICT):to_list(StateData#state.users)), +-spec find_nick_by_jid(jid(), state()) -> binary(). +find_nick_by_jid(JID, StateData) -> + LJID = jid:tolower(JID), + {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users), Nick. +-spec is_nick_change(jid(), binary(), state()) -> boolean(). is_nick_change(JID, Nick, StateData) -> LJID = jid:tolower(JID), case Nick of @@ -1848,16 +1663,20 @@ is_nick_change(JID, Nick, StateData) -> Nick /= OldNick end. +-spec nick_collision(jid(), binary(), state()) -> boolean(). nick_collision(User, Nick, StateData) -> UserOfNick = find_jid_by_nick(Nick, StateData), (UserOfNick /= false andalso jid:remove_resource(jid:tolower(UserOfNick)) /= jid:remove_resource(jid:tolower(User))). -add_new_user(From, Nick, - #xmlel{name = Name, attrs = Attrs, children = Els} = Packet, - StateData) -> - Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs), +-spec add_new_user(jid(), binary(), presence() | iq(), state()) -> + state() | + {error, error()} | + {ignore, state()} | + {result, xmpp_element(), state()}. +add_new_user(From, Nick, Packet, StateData) -> + Lang = xmpp:get_lang(Packet), UserRoomJID = jid:replace_resource(StateData#state.jid, Nick), MaxUsers = get_max_users(StateData), MaxAdminUsers = MaxUsers + @@ -1874,7 +1693,7 @@ add_new_user(From, Nick, fun(I) when is_integer(I), I>0 -> I end, 10), Collision = nick_collision(From, Nick, StateData), - IsSubscribeRequest = Name /= <<"presence">>, + IsSubscribeRequest = not is_record(Packet, presence), case {(ServiceAffiliation == owner orelse ((Affiliation == admin orelse Affiliation == owner) andalso NUsers < MaxAdminUsers) @@ -1887,72 +1706,72 @@ add_new_user(From, Nick, of {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers -> Txt = <<"Too many users in this conference">>, - Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_resource_constraint(Txt, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; {false, _, _, _} when NConferences >= MaxConferences -> Txt = <<"You have joined too many conferences">>, - Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_resource_constraint(Txt, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; {false, _, _, _} -> - Err = ?ERR_SERVICE_UNAVAILABLE, - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_service_unavailable(), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, _, _, none} -> Err = case Affiliation of outcast -> ErrText = <<"You have been banned from this room">>, - ?ERRT_FORBIDDEN(Lang, ErrText); + xmpp:err_forbidden(ErrText, Lang); _ -> ErrText = <<"Membership is required to enter this room">>, - ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText) + xmpp:err_registration_required(ErrText, Lang) end, - ErrPacket = jlib:make_error_reply(Packet, Err), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, true, _, _} -> ErrText = <<"That nickname is already in use by another occupant">>, - Err = ?ERRT_CONFLICT(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_conflict(ErrText, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, _, false, _} -> ErrText = <<"That nickname is registered by another person">>, - Err = ?ERRT_CONFLICT(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_conflict(ErrText, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; {_, _, _, Role} -> case check_password(ServiceAffiliation, Affiliation, - Els, From, StateData) + Packet, From, StateData) of true -> Nodes = get_subscription_nodes(Packet), @@ -1965,7 +1784,7 @@ add_new_user(From, Nick, Nodes, StateData)), send_existing_presences(From, NewState), send_initial_presence(From, NewState, StateData), - Shift = count_stanza_shift(Nick, Els, NewState), + Shift = count_stanza_shift(Nick, Packet, NewState), case send_history(From, Shift, NewState) of true -> ok; _ -> send_subject(From, StateData) @@ -1985,20 +1804,20 @@ add_new_user(From, Nick, NewStateData#state{robots = Robots} end, if not IsSubscribeRequest -> ResultState; - true -> {result, subscription_nodes_to_events(Nodes), ResultState} + true -> {result, subscribe_result(Packet), ResultState} end; nopass -> ErrText = <<"A password is required to enter this room">>, - Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_not_authorized(ErrText, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; captcha_required -> - SID = fxml:get_attr_s(<<"id">>, Attrs), + SID = xmpp:get_id(Packet), RoomJID = StateData#state.jid, To = jid:replace_resource(RoomJID, Nick), Limiter = {From#jid.luser, From#jid.lserver}, @@ -2006,9 +1825,7 @@ add_new_user(From, Nick, Lang, Limiter, From) of {ok, ID, CaptchaEls} -> - MsgPkt = #xmlel{name = <<"message">>, - attrs = [{<<"id">>, ID}], - children = CaptchaEls}, + MsgPkt = #message{id = ID, sub_els = CaptchaEls}, Robots = (?DICT):store(From, {Nick, Packet}, StateData#state.robots), ejabberd_router:route(RoomJID, From, MsgPkt), @@ -2020,49 +1837,51 @@ add_new_user(From, Nick, end; {error, limit} -> ErrText = <<"Too many CAPTCHA requests">>, - Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_resource_constraint(ErrText, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end; _ -> ErrText = <<"Unable to generate a CAPTCHA">>, - Err = ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_internal_server_error(ErrText, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end end; _ -> ErrText = <<"Incorrect password">>, - Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText), - ErrPacket = jlib:make_error_reply(Packet, Err), + Err = xmpp:err_not_authorized(ErrText, Lang), + ErrPacket = xmpp:make_error(Packet, Err), if not IsSubscribeRequest -> ejabberd_router:route(UserRoomJID, From, ErrPacket), StateData; true -> - {error, Err, StateData} + {error, Err} end end end. -check_password(owner, _Affiliation, _Els, _From, +-spec check_password(affiliation(), affiliation(), + stanza(), jid(), state()) -> boolean() | nopass. +check_password(owner, _Affiliation, _Packet, _From, _StateData) -> %% Don't check pass if user is owner in MUC service (access_admin option) true; -check_password(_ServiceAffiliation, Affiliation, Els, +check_password(_ServiceAffiliation, Affiliation, Packet, From, StateData) -> case (StateData#state.config)#config.password_protected of false -> check_captcha(Affiliation, From, StateData); true -> - Pass = extract_password(Els), + Pass = extract_password(Packet), case Pass of false -> nopass; _ -> @@ -2073,6 +1892,7 @@ check_password(_ServiceAffiliation, Affiliation, Els, end end. +-spec check_captcha(affiliation(), jid(), state()) -> true | captcha_required. check_captcha(Affiliation, From, StateData) -> case (StateData#state.config)#config.captcha_protected andalso ejabberd_captcha:is_feature_available() @@ -2101,47 +1921,52 @@ check_captcha(Affiliation, From, StateData) -> _ -> true end. -extract_password([]) -> false; -extract_password([#xmlel{attrs = Attrs} = El | Els]) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> - case fxml:get_subtag(El, <<"password">>) of - false -> false; - SubEl -> fxml:get_tag_cdata(SubEl) - end; - _ -> extract_password(Els) - end; -extract_password([_ | Els]) -> extract_password(Els). +-spec extract_password(stanza()) -> binary() | false. +extract_password(Packet) -> + case xmpp:get_subtag(Packet, #muc{}) of + #muc{password = Password} when is_binary(Password) -> + Password; + _ -> + false + end. -count_stanza_shift(Nick, Els, StateData) -> - HL = lqueue_to_list(StateData#state.history), - Since = extract_history(Els, <<"since">>), - Shift0 = case Since of - false -> 0; - _ -> - Sin = calendar:datetime_to_gregorian_seconds(Since), - count_seconds_shift(Sin, HL) - end, - Seconds = extract_history(Els, <<"seconds">>), - Shift1 = case Seconds of - false -> 0; - _ -> - Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - - Seconds, - count_seconds_shift(Sec, HL) - end, - MaxStanzas = extract_history(Els, <<"maxstanzas">>), - Shift2 = case MaxStanzas of - false -> 0; - _ -> count_maxstanzas_shift(MaxStanzas, HL) - end, - MaxChars = extract_history(Els, <<"maxchars">>), - Shift3 = case MaxChars of - false -> 0; - _ -> count_maxchars_shift(Nick, MaxChars, HL) - end, - lists:max([Shift0, Shift1, Shift2, Shift3]). +-spec count_stanza_shift(binary(), stanza(), state()) -> non_neg_integer(). +count_stanza_shift(Nick, Packet, StateData) -> + case xmpp:get_subtag(Packet, #muc_history{}) of + #muc_history{since = Since, + seconds = Seconds, + maxstanzas = MaxStanzas, + maxchars = MaxChars} -> + HL = lqueue_to_list(StateData#state.history), + Shift0 = case Since of + undefined -> 0; + _ -> + Sin = calendar:datetime_to_gregorian_seconds( + calendar:now_to_datetime(Since)), + count_seconds_shift(Sin, HL) + end, + Shift1 = case Seconds of + undefined -> 0; + _ -> + Sec = calendar:datetime_to_gregorian_seconds( + calendar:universal_time()) - Seconds, + count_seconds_shift(Sec, HL) + end, + Shift2 = case MaxStanzas of + undefined -> 0; + _ -> count_maxstanzas_shift(MaxStanzas, HL) + end, + Shift3 = case MaxChars of + undefined -> 0; + _ -> count_maxchars_shift(Nick, MaxChars, HL) + end, + lists:max([Shift0, Shift1, Shift2, Shift3]); + false -> + 0 + end. +-spec count_seconds_shift(non_neg_integer(), + [history_element()]) -> non_neg_integer(). count_seconds_shift(Seconds, HistoryList) -> lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject, TimeStamp, _Size}) -> @@ -2153,12 +1978,16 @@ count_seconds_shift(Seconds, HistoryList) -> end, HistoryList)). +-spec count_maxstanzas_shift(non_neg_integer(), + [history_element()]) -> non_neg_integer(). count_maxstanzas_shift(MaxStanzas, HistoryList) -> S = length(HistoryList) - MaxStanzas, if S =< 0 -> 0; true -> S end. +-spec count_maxchars_shift(binary(), non_neg_integer(), + [history_element()]) -> integer(). count_maxchars_shift(Nick, MaxSize, HistoryList) -> NLen = byte_size(Nick) + 1, Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject, @@ -2168,41 +1997,20 @@ count_maxchars_shift(Nick, MaxSize, HistoryList) -> HistoryList), calc_shift(MaxSize, Sizes). +-spec calc_shift(non_neg_integer(), [non_neg_integer()]) -> integer(). calc_shift(MaxSize, Sizes) -> Total = lists:sum(Sizes), calc_shift(MaxSize, Total, 0, Sizes). +-spec calc_shift(non_neg_integer(), integer(), integer(), + [non_neg_integer()]) -> integer(). calc_shift(_MaxSize, _Size, Shift, []) -> Shift; calc_shift(MaxSize, Size, Shift, [S | TSizes]) -> if MaxSize >= Size -> Shift; true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes) end. -extract_history([], _Type) -> false; -extract_history([#xmlel{attrs = Attrs} = El | Els], - Type) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC -> - AttrVal = fxml:get_path_s(El, - [{elem, <<"history">>}, {attr, Type}]), - case Type of - <<"since">> -> - case jlib:datetime_string_to_timestamp(AttrVal) of - undefined -> false; - TS -> calendar:now_to_universal_time(TS) - end; - _ -> - case catch jlib:binary_to_integer(AttrVal) of - IntVal when is_integer(IntVal) and (IntVal >= 0) -> - IntVal; - _ -> false - end - end; - _ -> extract_history(Els, Type) - end; -extract_history([_ | Els], Type) -> - extract_history(Els, Type). - +-spec is_room_overcrowded(state()) -> boolean(). is_room_overcrowded(StateData) -> MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host, mod_muc, max_users_presence, @@ -2210,10 +2018,12 @@ is_room_overcrowded(StateData) -> ?DEFAULT_MAX_USERS_PRESENCE), (?DICT):size(StateData#state.users) > MaxUsersPresence. +-spec presence_broadcast_allowed(jid(), state()) -> boolean(). presence_broadcast_allowed(JID, StateData) -> Role = get_role(JID, StateData), lists:member(Role, (StateData#state.config)#config.presence_broadcast). +-spec is_initial_presence(jid(), state()) -> boolean(). is_initial_presence(From, StateData) -> LJID = jid:tolower(From), case (?DICT):find(LJID, StateData#state.users) of @@ -2223,18 +2033,22 @@ is_initial_presence(From, StateData) -> true end. +-spec send_initial_presence(jid(), state(), state()) -> ok. send_initial_presence(NJID, StateData, OldStateData) -> send_new_presence1(NJID, <<"">>, true, StateData, OldStateData). +-spec send_update_presence(jid(), state(), state()) -> ok. send_update_presence(JID, StateData, OldStateData) -> send_update_presence(JID, <<"">>, StateData, OldStateData). +-spec send_update_presence(jid(), binary(), state(), state()) -> ok. send_update_presence(JID, Reason, StateData, OldStateData) -> case is_room_overcrowded(StateData) of true -> ok; false -> send_update_presence1(JID, Reason, StateData, OldStateData) end. +-spec send_update_presence1(jid(), binary(), state(), state()) -> ok. send_update_presence1(JID, Reason, StateData, OldStateData) -> LJID = jid:tolower(JID), LJIDs = case LJID of @@ -2258,12 +2072,15 @@ send_update_presence1(JID, Reason, StateData, OldStateData) -> end, LJIDs). +-spec send_new_presence(jid(), state(), state()) -> ok. send_new_presence(NJID, StateData, OldStateData) -> send_new_presence(NJID, <<"">>, false, StateData, OldStateData). +-spec send_new_presence(jid(), binary(), state(), state()) -> ok. send_new_presence(NJID, Reason, StateData, OldStateData) -> send_new_presence(NJID, Reason, false, StateData, OldStateData). +-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok. send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> case is_room_overcrowded(StateData) of true -> ok; @@ -2271,6 +2088,7 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> OldStateData) end. +-spec is_ra_changed(jid() | ljid(), boolean(), state(), state()) -> boolean(). is_ra_changed(_, _IsInitialPresence = true, _, _) -> false; is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) -> @@ -2289,6 +2107,7 @@ is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) -> (NewRole /= OldRole) or (NewAff /= OldAff) end. +-spec send_new_presence1(jid(), binary(), boolean(), state(), state()) -> ok. send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> LNJID = jid:tolower(NJID), #user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users), @@ -2301,15 +2120,9 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> {Role1, Presence1} = case presence_broadcast_allowed(NJID, StateData) of true -> {Role0, Presence0}; - false -> - {none, - #xmlel{name = <<"presence">>, - attrs = [{<<"type">>, <<"unavailable">>}], - children = []} - } + false -> {none, #presence{type = unavailable}} end, Affiliation = get_affiliation(LJID, StateData), - SAffiliation = affiliation_to_list(Affiliation), UserList = case not (presence_broadcast_allowed(NJID, StateData) orelse presence_broadcast_allowed(NJID, OldStateData)) of @@ -2323,59 +2136,38 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> {Role, Presence} = if LNJID == LUJID -> {Role0, Presence0}; true -> {Role1, Presence1} end, - SRole = role_to_list(Role), - ItemAttrs = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}] - end, - ItemEls = case Reason of - <<"">> -> []; - _ -> - [#xmlel{name = <<"reason">>, - attrs = [], - children = - [{xmlcdata, Reason}]}] - end, - StatusEls = status_els(IsInitialPresence, NJID, Info, - StateData), - Pres = if Presence == undefined -> #xmlel{name = <<"presence">>}; + Item0 = #muc_item{affiliation = Affiliation, + role = Role}, + Item1 = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Item = if is_binary(Reason), Reason /= <<"">> -> + Item1#muc_item{reason = Reason}; + true -> + Item1 + end, + StatusCodes = status_codes(IsInitialPresence, NJID, Info, + StateData), + Pres = if Presence == undefined -> #presence{}; true -> Presence end, - Packet = fxml:append_subtags(Pres, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs - = - ItemAttrs, - children - = - ItemEls} - | StatusEls]}]), + Packet = xmpp:set_subtag( + Pres, #muc_user{items = [Item], + status_codes = StatusCodes}), Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of true -> ?NS_MUCSUB_NODES_AFFILIATIONS; false -> ?NS_MUCSUB_NODES_PRESENCE end, send_wrapped(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet, Node1, StateData), - Type = fxml:get_tag_attr_s(<<"type">>, Packet), + Type = xmpp:get_type(Packet), IsSubscriber = Info#user.is_subscriber, IsOccupant = Info#user.last_presence /= undefined, if (IsSubscriber and not IsOccupant) and - (IsInitialPresence or (Type == <<"unavailable">>)) -> + (IsInitialPresence or (Type == unavailable)) -> Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS, send_wrapped(jid:replace_resource(StateData#state.jid, Nick), Info#user.jid, Packet, Node2, StateData); @@ -2385,12 +2177,14 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) -> end, UserList). +-spec send_existing_presences(jid(), state()) -> ok. send_existing_presences(ToJID, StateData) -> case is_room_overcrowded(StateData) of true -> ok; false -> send_existing_presences1(ToJID, StateData) end. +-spec send_existing_presences1(jid(), state()) -> ok. send_existing_presences1(ToJID, StateData) -> LToJID = jid:tolower(ToJID), {ok, #user{jid = RealToJID, role = Role}} = @@ -2410,46 +2204,23 @@ send_existing_presences1(ToJID, StateData) -> {_, false} -> ok; _ -> FromAffiliation = get_affiliation(LJID, StateData), - ItemAttrs = case Role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(FromJID)}, - {<<"affiliation">>, - affiliation_to_list(FromAffiliation)}, - {<<"role">>, - role_to_list(FromRole)}]; - _ -> - [{<<"affiliation">>, - affiliation_to_list(FromAffiliation)}, - {<<"role">>, - role_to_list(FromRole)}] - end, - Packet = fxml:append_subtags( - Presence, - [#xmlel{name = - <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - ItemAttrs, - children - = - []}]}]), + Item0 = #muc_item{affiliation = FromAffiliation, + role = FromRole}, + Item = case Role == moderator orelse + (StateData#state.config)#config.anonymous + == false of + true -> Item0#muc_item{jid = FromJID}; + false -> Item0 + end, + Packet = xmpp:set_subtag( + Presence, #muc_user{items = [Item]}), send_wrapped(jid:replace_resource(StateData#state.jid, FromNick), RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData) end end, (?DICT):to_list(StateData#state.nicks)). +-spec set_nick(jid(), binary(), state()) -> state(). set_nick(JID, Nick, State) -> LJID = jid:tolower(JID), {ok, #user{nick = OldNick}} = (?DICT):find(LJID, State#state.users), @@ -2472,6 +2243,7 @@ set_nick(JID, Nick, State) -> end, State#state{users = Users, nicks = Nicks}. +-spec change_nick(jid(), binary(), state()) -> state(). change_nick(JID, Nick, StateData) -> LJID = jid:tolower(JID), {ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users), @@ -2492,6 +2264,7 @@ change_nick(JID, Nick, StateData) -> add_to_log(nickchange, {OldNick, Nick}, StateData), NewStateData. +-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok. send_nick_changing(JID, OldNick, StateData, SendOldUnavailable, SendNewAvailable) -> {ok, @@ -2500,104 +2273,52 @@ send_nick_changing(JID, OldNick, StateData, (?DICT):find(jid:tolower(JID), StateData#state.users), Affiliation = get_affiliation(JID, StateData), - SAffiliation = affiliation_to_list(Affiliation), - SRole = role_to_list(Role), - lists:foreach(fun ({_LJID, Info}) when Presence /= undefined -> - ItemAttrs1 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}, - {<<"nick">>, Nick}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}, - {<<"nick">>, Nick}] - end, - ItemAttrs2 = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, - jid:to_string(RealJID)}, - {<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}]; - _ -> - [{<<"affiliation">>, SAffiliation}, - {<<"role">>, SRole}] - end, - Status110 = case JID == Info#user.jid of - true -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"110">>}] - }]; - false -> - [] - end, - Packet1 = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, - <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs1, - children = - []}, - #xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - <<"303">>}], - children = - []}|Status110]}]}, - Packet2 = fxml:append_subtags(Presence, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name - = - <<"item">>, - attrs - = - ItemAttrs2, - children - = - []}|Status110]}]), - if SendOldUnavailable -> - send_wrapped(jid:replace_resource(StateData#state.jid, - OldNick), - Info#user.jid, Packet1, - ?NS_MUCSUB_NODES_PRESENCE, - StateData); - true -> ok + lists:foreach( + fun({_LJID, Info}) when Presence /= undefined -> + Item0 = #muc_item{affiliation = Affiliation, role = Role}, + Item1 = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false of + true -> Item0#muc_item{jid = RealJID, nick = Nick}; + false -> Item0#muc_item{nick = Nick} + end, + Item2 = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Status110 = case JID == Info#user.jid of + true -> [110]; + false -> [] end, - if SendNewAvailable -> - send_wrapped(jid:replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet2, - ?NS_MUCSUB_NODES_PRESENCE, - StateData); - true -> ok - end; - (_) -> - ok - end, - (?DICT):to_list(StateData#state.users)). + Packet1 = #presence{type = unavailable, + sub_els = [#muc_user{ + items = [Item1], + status_codes = [303|Status110]}]}, + Packet2 = xmpp:set_subtag(Presence, + #muc_user{items = [Item2], + status_codes = Status110}), + if SendOldUnavailable -> + send_wrapped( + jid:replace_resource(StateData#state.jid, OldNick), + Info#user.jid, Packet1, ?NS_MUCSUB_NODES_PRESENCE, + StateData); + true -> ok + end, + if SendNewAvailable -> + send_wrapped( + jid:replace_resource(StateData#state.jid, Nick), + Info#user.jid, Packet2, ?NS_MUCSUB_NODES_PRESENCE, + StateData); + true -> ok + end; + (_) -> + ok + end, + (?DICT):to_list(StateData#state.users)). +-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok. maybe_send_affiliation(JID, Affiliation, StateData) -> LJID = jid:tolower(JID), IsOccupant = case LJID of @@ -2617,18 +2338,13 @@ maybe_send_affiliation(JID, Affiliation, StateData) -> send_affiliation(LJID, Affiliation, StateData) end. +-spec send_affiliation(ljid(), affiliation(), state()) -> ok. send_affiliation(LJID, Affiliation, StateData) -> - ItemAttrs = [{<<"jid">>, jid:to_string(LJID)}, - {<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"role">>, <<"none">>}], - Message = #xmlel{name = <<"message">>, - attrs = [{<<"id">>, randoms:get_string()}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = - [#xmlel{name = <<"item">>, - attrs = ItemAttrs}]}]}, + Item = #muc_item{jid = jid:make(LJID), + affiliation = Affiliation, + role = none}, + Message = #message{id = randoms:get_string(), + sub_els = [#muc_user{items = [Item]}]}, Recipients = case (StateData#state.config)#config.anonymous of true -> (?DICT):filter(fun(_, #user{role = moderator}) -> @@ -2643,43 +2359,33 @@ send_affiliation(LJID, Affiliation, StateData) -> StateData#state.server_host, Recipients, Message). -status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) -> - Status = case IsInitialPresence of - true -> - S1 = case StateData#state.just_created of - true -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"201">>}], - children = []}]; - false -> [] - end, - S2 = case (StateData#state.config)#config.anonymous of - true -> S1; - false -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"100">>}], - children = []} | S1] - end, - S3 = case (StateData#state.config)#config.logging of - true -> - [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, <<"170">>}], - children = []} | S2]; - false -> S2 - end, - S3; - false -> [] - end, - [#xmlel{name = <<"status">>, - attrs = - [{<<"code">>, - <<"110">>}], - children = []} | Status]; -status_els(_IsInitialPresence, _JID, _Info, _StateData) -> []. +-spec status_codes(boolean(), jid(), #user{}, state()) -> [pos_integer()]. +status_codes(IsInitialPresence, JID, #user{jid = JID}, StateData) -> + S0 = [110], + case IsInitialPresence of + true -> + S1 = case StateData#state.just_created of + true -> [201|S0]; + false -> S0 + end, + S2 = case (StateData#state.config)#config.anonymous of + true -> S1; + false -> [100|S1] + end, + S3 = case (StateData#state.config)#config.logging of + true -> [170|S2]; + false -> S2 + end, + S3; + false -> S0 + end; +status_codes(_IsInitialPresence, _JID, _Info, _StateData) -> []. +-spec lqueue_new(non_neg_integer()) -> lqueue(). lqueue_new(Max) -> #lqueue{queue = queue:new(), len = 0, max = Max}. +-spec lqueue_in(term(), lqueue()) -> lqueue(). %% If the message queue limit is set to 0, do not store messages. lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ; %% Otherwise, rotate messages in the queue store. @@ -2692,39 +2398,33 @@ lqueue_in(Item, true -> #lqueue{queue = Q2, len = Len + 1, max = Max} end. +-spec lqueue_cut(queue:queue(), non_neg_integer()) -> queue:queue(). lqueue_cut(Q, 0) -> Q; lqueue_cut(Q, N) -> {_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1). +-spec lqueue_to_list(lqueue()) -> list(). lqueue_to_list(#lqueue{queue = Q1}) -> queue:to_list(Q1). - +-spec add_message_to_history(binary(), jid(), message(), state()) -> state(). add_message_to_history(FromNick, FromJID, Packet, StateData) -> - HaveSubject = case fxml:get_subtag(Packet, <<"subject">>) - of - false -> false; - _ -> true - end, + HaveSubject = Packet#message.subject /= [], TimeStamp = p1_time_compat:timestamp(), AddrPacket = case (StateData#state.config)#config.anonymous of true -> Packet; false -> - Address = #xmlel{name = <<"address">>, - attrs = [{<<"type">>, <<"ofrom">>}, - {<<"jid">>, - jid:to_string(FromJID)}], - children = []}, - Addresses = #xmlel{name = <<"addresses">>, - attrs = [{<<"xmlns">>, ?NS_ADDRESS}], - children = [Address]}, - fxml:append_subtags(Packet, [Addresses]) + Addresses = #addresses{ + list = [#address{type = ofrom, + jid = FromJID}]}, + xmpp:set_subtag(Packet, Addresses) end, - TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp), - SPacket = - jlib:replace_from_to(jid:replace_resource(StateData#state.jid, - FromNick), - StateData#state.jid, TSPacket), + TSPacket = xmpp_util:add_delay_info( + AddrPacket, StateData#state.jid, TimeStamp), + SPacket = xmpp:set_from_to( + TSPacket, + jid:replace_resource(StateData#state.jid, FromNick), + StateData#state.jid), Size = element_size(SPacket), Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, calendar:now_to_universal_time(TimeStamp), Size}, @@ -2732,6 +2432,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) -> add_to_log(text, {FromNick, Packet}, StateData), StateData#state{history = Q1}. +-spec send_history(jid(), integer(), state()) -> boolean(). send_history(JID, Shift, StateData) -> lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp, _Size}, @@ -2745,23 +2446,19 @@ send_history(JID, Shift, StateData) -> lists:nthtail(Shift, lqueue_to_list(StateData#state.history))). +-spec send_subject(jid(), state()) -> ok. send_subject(_JID, #state{subject_author = <<"">>}) -> ok; send_subject(JID, #state{subject_author = Nick} = StateData) -> Subject = StateData#state.subject, - Packet = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}], - children = - [#xmlel{name = <<"subject">>, attrs = [], - children = [{xmlcdata, Subject}]}]}, + Packet = #message{type = groupchat, subject = xmpp:mk_text(Subject)}, ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID, Packet). -check_subject(Packet) -> - case fxml:get_subtag(Packet, <<"subject">>) of - false -> false; - SubjEl -> fxml:get_tag_cdata(SubjEl) - end. +-spec check_subject(message()) -> false | binary(). +check_subject(#message{subject = []}) -> false; +check_subject(#message{subject = Subj}) -> xmpp:get_text(Subj). +-spec can_change_subject(role(), state()) -> boolean(). can_change_subject(Role, StateData) -> case (StateData#state.config)#config.allow_change_subj of @@ -2772,99 +2469,89 @@ can_change_subject(Role, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Admin stuff -process_iq_admin(From, set, Lang, SubEl, StateData) -> - #xmlel{children = Items} = SubEl, +-spec process_iq_admin(jid(), iq(), #state{}) -> {error, error()} | + {result, undefined, #state{}} | + {result, muc_admin()}. +process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]}, + _StateData) -> + Txt = <<"No 'item' element found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; +process_iq_admin(From, #iq{type = set, lang = Lang, + sub_els = [#muc_admin{items = Items}]}, + StateData) -> process_admin_items_set(From, Items, Lang, StateData); -process_iq_admin(From, get, Lang, SubEl, StateData) -> - case fxml:get_subtag(SubEl, <<"item">>) of - false -> - Txt = <<"No 'item' element found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - Item -> - FAffiliation = get_affiliation(From, StateData), - FRole = get_role(From, StateData), - case fxml:get_tag_attr(<<"role">>, Item) of - false -> - case fxml:get_tag_attr(<<"affiliation">>, Item) of - false -> - Txt = <<"No 'affiliation' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> {error, ?ERR_BAD_REQUEST}; - SAffiliation -> - if (FAffiliation == owner) or - (FAffiliation == admin) or - ((FAffiliation == member) and not - (StateData#state.config)#config.anonymous) -> - Items = items_with_affiliation(SAffiliation, - StateData), - {result, Items, StateData}; - true -> - ErrText = - <<"Administrator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> - Txt = <<"Incorrect value of 'role' attribute">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - SRole -> - if FRole == moderator -> - Items = items_with_role(SRole, StateData), - {result, Items, StateData}; - true -> - ErrText = <<"Moderator privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} - end - end - end - end. +process_iq_admin(From, #iq{type = get, lang = Lang, + sub_els = [#muc_admin{items = [Item]}]}, + StateData) -> + FAffiliation = get_affiliation(From, StateData), + FRole = get_role(From, StateData), + case Item of + #muc_item{role = undefined, affiliation = undefined} -> + Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + #muc_item{role = undefined, affiliation = Affiliation} -> + if (FAffiliation == owner) or + (FAffiliation == admin) or + ((FAffiliation == member) and + not (StateData#state.config)#config.anonymous) -> + Items = items_with_affiliation(Affiliation, StateData), + {result, #muc_admin{items = Items}}; + true -> + ErrText = <<"Administrator privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)} + end; + #muc_item{role = Role} -> + if FRole == moderator -> + Items = items_with_role(Role, StateData), + {result, #muc_admin{items = Items}}; + true -> + ErrText = <<"Moderator privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)} + end + end; +process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) -> + ErrText = <<"Too many elements">>, + {error, xmpp:err_bad_request(ErrText, Lang)}. +-spec items_with_role(role(), state()) -> [muc_item()]. items_with_role(SRole, StateData) -> lists:map(fun ({_, U}) -> user_to_item(U, StateData) end, search_role(SRole, StateData)). +-spec items_with_affiliation(affiliation(), state()) -> [muc_item()]. items_with_affiliation(SAffiliation, StateData) -> - lists:map(fun ({JID, {Affiliation, Reason}}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - affiliation_to_list(Affiliation)}, - {<<"jid">>, jid:to_string(JID)}], - children = - [#xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, Reason}]}]}; - ({JID, Affiliation}) -> - #xmlel{name = <<"item">>, - attrs = - [{<<"affiliation">>, - affiliation_to_list(Affiliation)}, - {<<"jid">>, jid:to_string(JID)}], - children = []} - end, - search_affiliation(SAffiliation, StateData)). + lists:map( + fun({JID, {Affiliation, Reason}}) -> + #muc_item{affiliation = Affiliation, jid = JID, + reason = if is_binary(Reason), Reason /= <<"">> -> + Reason; + true -> + undefined + end}; + ({JID, Affiliation}) -> + #muc_item{affiliation = Affiliation, jid = JID} + end, + search_affiliation(SAffiliation, StateData)). +-spec user_to_item(#user{}, state()) -> muc_item(). user_to_item(#user{role = Role, nick = Nick, jid = JID}, StateData) -> Affiliation = get_affiliation(JID, StateData), - #xmlel{name = <<"item">>, - attrs = - [{<<"role">>, role_to_list(Role)}, - {<<"affiliation">>, affiliation_to_list(Affiliation)}, - {<<"nick">>, Nick}, - {<<"jid">>, jid:to_string(JID)}], - children = []}. + #muc_item{role = Role, + affiliation = Affiliation, + nick = Nick, + jid = JID}. +-spec search_role(role(), state()) -> [{ljid(), #user{}}]. search_role(Role, StateData) -> lists:filter(fun ({_, #user{role = R}}) -> Role == R end, (?DICT):to_list(StateData#state.users)). +-spec search_affiliation(affiliation(), state()) -> + [{ljid(), + affiliation() | {affiliation(), binary()}}]. search_affiliation(Affiliation, StateData) -> lists:filter(fun ({_, A}) -> case A of @@ -2874,11 +2561,14 @@ search_affiliation(Affiliation, StateData) -> end, (?DICT):to_list(StateData#state.affiliations)). +-spec process_admin_items_set(jid(), [muc_item()], binary() | undefined, + #state{}) -> {result, undefined, #state{}} | + {error, error()}. process_admin_items_set(UJID, Items, Lang, StateData) -> UAffiliation = get_affiliation(UJID, StateData), URole = get_role(UJID, StateData), - case find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, []) + case catch find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, []) of {result, Res} -> ?INFO_MSG("Processing MUC admin query from ~s in " @@ -2888,249 +2578,161 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> NSD = lists:foldl(process_item_change(UJID), StateData, lists:flatten(Res)), store_room(NSD), - {result, [], NSD}; - Err -> Err + {result, undefined, NSD}; + {error, Err} -> {error, Err} end. +-spec process_item_change(jid()) -> function(). process_item_change(UJID) -> fun(E, SD) -> process_item_change(E, SD, UJID) end. -process_item_change(E, SD, UJID) -> - case catch case E of - {JID, affiliation, owner, _} when JID#jid.luser == <<"">> -> - %% If the provided JID does not have username, - %% forget the affiliation completely - SD; - {JID, role, none, Reason} -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"307">>, - SD), - set_role(JID, none, SD); - {JID, affiliation, none, Reason} -> - case (SD#state.config)#config.members_only of - true -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"321">>, - none, - SD), - maybe_send_affiliation(JID, none, SD), - SD1 = set_affiliation(JID, none, SD), - set_role(JID, none, SD1); - _ -> - SD1 = set_affiliation(JID, none, SD), - send_update_presence(JID, SD1, SD), - maybe_send_affiliation(JID, none, SD1), - SD1 - end; - {JID, affiliation, outcast, Reason} -> - catch - send_kickban_presence(UJID, JID, - Reason, - <<"301">>, - outcast, - SD), - maybe_send_affiliation(JID, outcast, SD), - set_affiliation(JID, - outcast, - set_role(JID, none, SD), - Reason); - {JID, affiliation, A, Reason} - when (A == admin) or (A == owner) -> - SD1 = set_affiliation(JID, A, SD, Reason), - SD2 = set_role(JID, moderator, SD1), - send_update_presence(JID, Reason, SD2, SD), - maybe_send_affiliation(JID, A, SD2), - SD2; - {JID, affiliation, member, Reason} -> - SD1 = set_affiliation(JID, member, SD, Reason), - SD2 = set_role(JID, participant, SD1), - send_update_presence(JID, Reason, SD2, SD), - maybe_send_affiliation(JID, member, SD2), - SD2; - {JID, role, Role, Reason} -> - SD1 = set_role(JID, Role, SD), - catch - send_new_presence(JID, Reason, SD1, SD), - SD1; - {JID, affiliation, A, _Reason} -> - SD1 = set_affiliation(JID, A, SD), - send_update_presence(JID, SD1, SD), - maybe_send_affiliation(JID, A, SD1), - SD1 - end - of - {'EXIT', ErrReason} -> - ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n", [ErrReason]), - SD; - NSD -> NSD +-type admin_action() :: {jid(), affiliation | role, + affiliation() | role(), binary()}. + +-spec process_item_change(admin_action(), state(), jid()) -> state(). +process_item_change(Item, SD, UJID) -> + try case Item of + {JID, affiliation, owner, _} when JID#jid.luser == <<"">> -> + %% If the provided JID does not have username, + %% forget the affiliation completely + SD; + {JID, role, none, Reason} -> + catch send_kickban_presence(UJID, JID, Reason, 307, SD), + set_role(JID, none, SD); + {JID, affiliation, none, Reason} -> + case (SD#state.config)#config.members_only of + true -> + catch send_kickban_presence(UJID, JID, Reason, 321, none, SD), + maybe_send_affiliation(JID, none, SD), + SD1 = set_affiliation(JID, none, SD), + set_role(JID, none, SD1); + _ -> + SD1 = set_affiliation(JID, none, SD), + send_update_presence(JID, SD1, SD), + maybe_send_affiliation(JID, none, SD1), + SD1 + end; + {JID, affiliation, outcast, Reason} -> + catch send_kickban_presence(UJID, JID, Reason, 301, outcast, SD), + maybe_send_affiliation(JID, outcast, SD), + set_affiliation(JID, outcast, set_role(JID, none, SD), Reason); + {JID, affiliation, A, Reason} when (A == admin) or (A == owner) -> + SD1 = set_affiliation(JID, A, SD, Reason), + SD2 = set_role(JID, moderator, SD1), + send_update_presence(JID, Reason, SD2, SD), + maybe_send_affiliation(JID, A, SD2), + SD2; + {JID, affiliation, member, Reason} -> + SD1 = set_affiliation(JID, member, SD, Reason), + SD2 = set_role(JID, participant, SD1), + send_update_presence(JID, Reason, SD2, SD), + maybe_send_affiliation(JID, member, SD2), + SD2; + {JID, role, Role, Reason} -> + SD1 = set_role(JID, Role, SD), + catch send_new_presence(JID, Reason, SD1, SD), + SD1; + {JID, affiliation, A, _Reason} -> + SD1 = set_affiliation(JID, A, SD), + send_update_presence(JID, SD1, SD), + maybe_send_affiliation(JID, A, SD1), + SD1 + end + catch E:R -> + ?ERROR_MSG("failed to set item ~p from ~s: ~p", + [Item, jid:to_string(UJID), + {E, {R, erlang:get_stacktrace()}}]), + SD end. +-spec find_changed_items(jid(), affiliation(), role(), + [muc_item()], binary(), state(), [admin_action()]) -> + {result, [admin_action()]}. find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) -> {result, Res}; +find_changed_items(_UJID, _UAffiliation, _URole, + [#muc_item{jid = undefined, nick = undefined}|_], + Lang, _StateData, _Res) -> + Txt = <<"Neither 'jid' nor 'nick' attribute found">>, + throw({error, xmpp:err_bad_request(Txt, Lang)}); +find_changed_items(_UJID, _UAffiliation, _URole, + [#muc_item{role = undefined, affiliation = undefined}|_], + Lang, _StateData, _Res) -> + Txt = <<"Neither 'role' nor 'affiliation' attribute found">>, + throw({error, xmpp:err_bad_request(Txt, Lang)}); 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, - [#xmlel{name = <<"item">>, attrs = Attrs} = Item - | Items], + [#muc_item{jid = J, nick = Nick, reason = Reason0, + role = Role, affiliation = Affiliation}|Items], Lang, StateData, Res) -> - TJID = case fxml:get_attr(<<"jid">>, Attrs) of - {value, S} -> - case jid:from_string(S) of - error -> - ErrText = iolist_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Jabber ID ~s is invalid">>), - [S])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> {value, [J]} - end; - _ -> - case fxml:get_attr(<<"nick">>, Attrs) of - {value, N} -> - case find_jids_by_nick(N, StateData) of - false -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Nickname ~s does not exist in the room">>), - [N])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - J -> {value, J} - end; - _ -> - Txt1 = <<"No 'nick' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt1)} - end - end, - case TJID of - {value, [JID | _] = JIDs} -> - TAffiliation = get_affiliation(JID, StateData), - TRole = get_role(JID, StateData), - case fxml:get_attr(<<"role">>, Attrs) of - false -> - case fxml:get_attr(<<"affiliation">>, Attrs) of - false -> - Txt2 = <<"No 'affiliation' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt2)}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText1 = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Invalid affiliation: ~s">>), - [StrAffiliation])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)}; - SAffiliation -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = case can_change_ra(UAffiliation, - URole, - TAffiliation, - TRole, affiliation, - SAffiliation, - ServiceAf) - of - nothing -> nothing; - true -> true; - check_owner -> - case search_affiliation(owner, - StateData) - of - [{OJID, _}] -> - jid:remove_resource(OJID) - /= - jid:tolower(jid:remove_resource(UJID)); - _ -> true - end; - _ -> false - end, - case CanChangeRA of - nothing -> - find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, - Res); - true -> - Reason = fxml:get_path_s(Item, - [{elem, <<"reason">>}, - cdata]), - MoreRes = [{jid:remove_resource(Jidx), - affiliation, SAffiliation, Reason} - || Jidx <- JIDs], - find_changed_items(UJID, UAffiliation, URole, - Items, Lang, StateData, - [MoreRes | Res]); - false -> - Txt3 = <<"Changing role/affiliation is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)} - end - end - end; - {value, StrRole} -> - case catch list_to_role(StrRole) of - {'EXIT', _} -> - ErrText1 = iolist_to_binary( - io_lib:format(translate:translate( - Lang, - <<"Invalid role: ~s">>), - [StrRole])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)}; - SRole -> - ServiceAf = get_service_affiliation(JID, StateData), - CanChangeRA = case can_change_ra(UAffiliation, URole, - TAffiliation, TRole, - role, SRole, ServiceAf) - of - nothing -> nothing; - true -> true; - check_owner -> - case search_affiliation(owner, - StateData) - of - [{OJID, _}] -> - jid:remove_resource(OJID) - /= - jid:tolower(jid:remove_resource(UJID)); - _ -> true - end; - _ -> false - end, - case CanChangeRA of - nothing -> - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, Res); - true -> - Reason = fxml:get_path_s(Item, - [{elem, <<"reason">>}, - cdata]), - MoreRes = [{Jidx, role, SRole, Reason} - || Jidx <- JIDs], - find_changed_items(UJID, UAffiliation, URole, Items, - Lang, StateData, - [MoreRes | Res]); - _ -> - Txt4 = <<"Changing role/affiliation is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)} - end + [JID | _] = JIDs = + if J /= undefined -> + [J]; + Nick /= undefined -> + case find_jids_by_nick(Nick, StateData) of + [] -> + ErrText = iolist_to_binary( + io_lib:format( + translate:translate( + Lang, + <<"Nickname ~s does not exist in the room">>), + [Nick])), + throw({error, xmpp:err_not_acceptable(ErrText, Lang)}); + JIDList -> + JIDList end - end; - Err -> Err - end; -find_changed_items(_UJID, _UAffiliation, _URole, _Items, - _Lang, _StateData, _Res) -> - {error, ?ERR_BAD_REQUEST}. + end, + {RoleOrAff, RoleOrAffValue} = if Role == undefined -> + {affiliation, Affiliation}; + true -> + {role, Role} + end, + TAffiliation = get_affiliation(JID, StateData), + TRole = get_role(JID, StateData), + ServiceAf = get_service_affiliation(JID, StateData), + CanChangeRA = case can_change_ra(UAffiliation, + URole, + TAffiliation, + TRole, RoleOrAff, RoleOrAffValue, + ServiceAf) of + nothing -> nothing; + true -> true; + check_owner -> + case search_affiliation(owner, StateData) of + [{OJID, _}] -> + jid:remove_resource(OJID) + /= + jid:tolower(jid:remove_resource(UJID)); + _ -> true + end; + _ -> false + end, + case CanChangeRA of + nothing -> + find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, + Res); + true -> + Reason = if is_binary(Reason0) -> Reason0; + true -> <<"">> + end, + MoreRes = [{jid:remove_resource(Jidx), + RoleOrAff, RoleOrAffValue, Reason} + || Jidx <- JIDs], + find_changed_items(UJID, UAffiliation, URole, + Items, Lang, StateData, + [MoreRes | Res]); + false -> + Txt = <<"Changing role/affiliation is not allowed">>, + throw({error, xmpp:err_not_allowed(Txt, Lang)}) + end. +-spec can_change_ra(affiliation(), role(), affiliation(), role(), + affiliation, affiliation(), affiliation()) -> boolean(); + (affiliation(), role(), affiliation(), role(), + role, role(), affiliation()) -> boolean(). can_change_ra(_FAffiliation, _FRole, owner, _TRole, affiliation, owner, owner) -> %% A room owner tries to add as persistent owner a @@ -3255,11 +2857,15 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation, _TRole, role, _Value, _ServiceAf) -> false. +-spec send_kickban_presence(jid(), jid(), binary(), + pos_integer(), state()) -> ok. send_kickban_presence(UJID, JID, Reason, Code, StateData) -> NewAffiliation = get_affiliation(JID, StateData), send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, StateData). +-spec send_kickban_presence(jid(), jid(), binary(), pos_integer(), + affiliation(), state()) -> ok. send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, StateData) -> LJID = jid:tolower(JID), @@ -3288,77 +2894,51 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation, end, LJIDs). +-spec send_kickban_presence1(jid(), jid(), binary(), pos_integer(), + affiliation(), state()) -> ok. send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation, StateData) -> {ok, #user{jid = RealJID, nick = Nick}} = (?DICT):find(jid:tolower(UJID), StateData#state.users), - SAffiliation = affiliation_to_list(Affiliation), - BannedJIDString = jid:to_string(RealJID), ActorNick = get_actor_nick(MJID, StateData), - lists:foreach(fun ({_LJID, Info}) -> - JidAttrList = case Info#user.role == moderator orelse - (StateData#state.config)#config.anonymous - == false - of - true -> - [{<<"jid">>, BannedJIDString}]; - false -> [] - end, - ItemAttrs = [{<<"affiliation">>, SAffiliation}, - {<<"role">>, <<"none">>}] - ++ JidAttrList, - ItemEls = case Reason of - <<"">> -> []; - _ -> - [#xmlel{name = <<"reason">>, - attrs = [], - children = - [{xmlcdata, Reason}]}] - end, - ItemElsActor = case MJID of - <<"">> -> []; - _ -> [#xmlel{name = <<"actor">>, - attrs = - [{<<"nick">>, ActorNick}]}] - end, - Packet = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs, - children = - ItemElsActor ++ ItemEls}, - #xmlel{name = - <<"status">>, - attrs = - [{<<"code">>, - Code}], - children = - []}]}]}, - RoomJIDNick = jid:replace_resource( - StateData#state.jid, Nick), - send_wrapped(RoomJIDNick, Info#user.jid, Packet, - ?NS_MUCSUB_NODES_AFFILIATIONS, StateData), - IsSubscriber = Info#user.is_subscriber, - IsOccupant = Info#user.last_presence /= undefined, - if (IsSubscriber and not IsOccupant) -> - send_wrapped(RoomJIDNick, Info#user.jid, Packet, - ?NS_MUCSUB_NODES_PARTICIPANTS, StateData); - true -> - ok - end - end, - (?DICT):to_list(StateData#state.users)). + lists:foreach( + fun({_LJID, Info}) -> + Item0 = #muc_item{affiliation = Affiliation, + role = none}, + Item1 = case Info#user.role == moderator orelse + (StateData#state.config)#config.anonymous + == false of + true -> Item0#muc_item{jid = RealJID}; + false -> Item0 + end, + Item2 = if is_binary(Reason), Reason /= <<"">> -> + Item1#muc_item{reason = Reason}; + true -> + Item1 + end, + Item = case ActorNick of + <<"">> -> Item2; + _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}} + end, + Packet = #presence{type = unavailable, + sub_els = [#muc_user{items = [Item], + status_codes = [Code]}]}, + RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick), + send_wrapped(RoomJIDNick, Info#user.jid, Packet, + ?NS_MUCSUB_NODES_AFFILIATIONS, StateData), + IsSubscriber = Info#user.is_subscriber, + IsOccupant = Info#user.last_presence /= undefined, + if (IsSubscriber and not IsOccupant) -> + send_wrapped(RoomJIDNick, Info#user.jid, Packet, + ?NS_MUCSUB_NODES_PARTICIPANTS, StateData); + true -> + ok + end + end, + (?DICT):to_list(StateData#state.users)). +-spec get_actor_nick(binary() | jid(), state()) -> binary(). get_actor_nick(<<"">>, _StateData) -> <<"">>; get_actor_nick(MJID, StateData) -> @@ -3369,97 +2949,86 @@ get_actor_nick(MJID, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Owner stuff - -process_iq_owner(From, set, Lang, SubEl, StateData) -> +-spec process_iq_owner(jid(), iq(), state()) -> + {result, undefined | muc_owner()} | + {result, undefined | muc_owner(), state() | stop} | + {error, error()}. +process_iq_owner(From, #iq{type = set, lang = Lang, + sub_els = [#muc_owner{destroy = Destroy, + config = Config, + items = Items}]}, + StateData) -> FAffiliation = get_affiliation(From, StateData), - case FAffiliation of - owner -> - #xmlel{children = Els} = SubEl, - case fxml:remove_cdata(Els) of - [#xmlel{name = <<"x">>} = XEl] -> - case {fxml:get_tag_attr_s(<<"xmlns">>, XEl), - fxml:get_tag_attr_s(<<"type">>, XEl)} - of - {?NS_XDATA, <<"cancel">>} -> {result, [], StateData}; - {?NS_XDATA, <<"submit">>} -> - case is_allowed_log_change(XEl, StateData, From) andalso - is_allowed_persistent_change(XEl, StateData, From) - andalso - is_allowed_room_name_desc_limits(XEl, StateData) - andalso - is_password_settings_correct(XEl, StateData) - of - true -> set_config(XEl, StateData, Lang); - false -> {error, ?ERR_NOT_ACCEPTABLE} - end; - _ -> - Txt = <<"Incorrect data form">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} - end; - [#xmlel{name = <<"destroy">>} = SubEl1] -> - ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", - [jid:to_string(StateData#state.jid), - jid:to_string(From)]), - add_to_log(room_existence, destroyed, StateData), - destroy_room(SubEl1, StateData); - Items -> - process_admin_items_set(From, Items, Lang, StateData) - end; - _ -> - ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + if FAffiliation /= owner -> + ErrText = <<"Owner privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)}; + Destroy /= undefined, Config == undefined, Items == [] -> + ?INFO_MSG("Destroyed MUC room ~s by the owner ~s", + [jid:to_string(StateData#state.jid), jid:to_string(From)]), + add_to_log(room_existence, destroyed, StateData), + destroy_room(Destroy, StateData); + Config /= undefined, Destroy == undefined, Items == [] -> + case Config of + #xdata{type = cancel} -> + {result, undefined}; + #xdata{type = submit} -> + case is_allowed_log_change(Config, StateData, From) andalso + is_allowed_persistent_change(Config, StateData, From) andalso + is_allowed_room_name_desc_limits(Config, StateData) andalso + is_password_settings_correct(Config, StateData) of + true -> set_config(Config, StateData, Lang); + false -> {error, xmpp:err_not_acceptable()} + end; + _ -> + Txt = <<"Incorrect data form">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + Items /= [], Config == undefined, Destroy == undefined -> + process_admin_items_set(From, Items, Lang, StateData); + true -> + {error, xmpp:err_bad_request()} end; -process_iq_owner(From, get, Lang, SubEl, StateData) -> +process_iq_owner(From, #iq{type = get, lang = Lang, + sub_els = [#muc_owner{destroy = Destroy, + config = Config, + items = Items}]}, + StateData) -> FAffiliation = get_affiliation(From, StateData), - case FAffiliation of - owner -> - #xmlel{children = Els} = SubEl, - case fxml:remove_cdata(Els) of - [] -> get_config(Lang, StateData, From); - [Item] -> - case fxml:get_tag_attr(<<"affiliation">>, Item) of - false -> - Txt = <<"No 'affiliation' attribute found">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; - {value, StrAffiliation} -> - case catch list_to_affiliation(StrAffiliation) of - {'EXIT', _} -> - ErrText = iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Invalid affiliation: ~s">>), - [StrAffiliation])), - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - SAffiliation -> - Items = items_with_affiliation(SAffiliation, - StateData), - {result, Items, StateData} - end - end; - _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED} - end; - _ -> - ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + if FAffiliation /= owner -> + ErrText = <<"Owner privileges required">>, + {error, xmpp:err_forbidden(ErrText, Lang)}; + Destroy == undefined, Config == undefined -> + case Items of + [] -> + {result, + #muc_owner{config = get_config(Lang, StateData, From)}}; + [#muc_item{affiliation = undefined}] -> + Txt = <<"No 'affiliation' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + [#muc_item{affiliation = Affiliation}] -> + Items = items_with_affiliation(Affiliation, StateData), + {result, #muc_owner{items = Items}}; + [_|_] -> + Txt = <<"Too many elements">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end; + true -> + {error, xmpp:err_bad_request()} end. -is_allowed_log_change(XEl, StateData, From) -> - case lists:keymember(<<"muc#roomconfig_enablelogging">>, - 1, jlib:parse_xdata_submit(XEl)) - of - false -> true; - true -> - allow == - mod_muc_log:check_access_log(StateData#state.server_host, - From) +-spec is_allowed_log_change(xdata(), state(), jid()) -> boolean(). +is_allowed_log_change(X, StateData, From) -> + case xmpp_util:has_xdata_var(<<"muc#roomconfig_enablelogging">>, X) of + false -> true; + true -> + allow == + mod_muc_log:check_access_log(StateData#state.server_host, + From) end. -is_allowed_persistent_change(XEl, StateData, From) -> - case - lists:keymember(<<"muc#roomconfig_persistentroom">>, 1, - jlib:parse_xdata_submit(XEl)) - of +-spec is_allowed_persistent_change(xdata(), state(), jid()) -> boolean(). +is_allowed_persistent_change(X, StateData, From) -> + case xmpp_util:has_xdata_var(<<"muc#roomconfig_persistentroom">>, X) of false -> true; true -> {_AccessRoute, _AccessCreate, _AccessAdmin, @@ -3472,58 +3041,57 @@ is_allowed_persistent_change(XEl, StateData, From) -> %% Check if the Room Name and Room Description defined in the Data Form %% are conformant to the configured limits -is_allowed_room_name_desc_limits(XEl, StateData) -> - IsNameAccepted = case - lists:keysearch(<<"muc#roomconfig_roomname">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [N]}} -> - byte_size(N) =< - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_room_name, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> I - end, infinity); - _ -> true +-spec is_allowed_room_name_desc_limits(xdata(), state()) -> boolean(). +is_allowed_room_name_desc_limits(XData, StateData) -> + IsNameAccepted = case xmpp_util:get_xdata_values( + <<"muc#roomconfig_roomname">>, XData) of + [N] -> + byte_size(N) =< + gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, max_room_name, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> I + end, infinity); + _ -> + true end, - IsDescAccepted = case - lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [D]}} -> - byte_size(D) =< - gen_mod:get_module_opt(StateData#state.server_host, - mod_muc, max_room_desc, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> - I - end, infinity); - _ -> true + IsDescAccepted = case xmpp_util:get_xdata_values( + <<"muc#roomconfig_roomdesc">>, XData) of + [D] -> + byte_size(D) =< + gen_mod:get_module_opt( + StateData#state.server_host, + mod_muc, max_room_desc, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> + I + end, infinity); + _ -> true end, IsNameAccepted and IsDescAccepted. %% Return false if: %% "the password for a password-protected room is blank" -is_password_settings_correct(XEl, StateData) -> +-spec is_password_settings_correct(xdata(), state()) -> boolean(). +is_password_settings_correct(XData, StateData) -> Config = StateData#state.config, OldProtected = Config#config.password_protected, OldPassword = Config#config.password, - NewProtected = case - lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>, - 1, jlib:parse_xdata_submit(XEl)) - of - {value, {_, [<<"1">>]}} -> true; - {value, {_, [<<"0">>]}} -> false; - _ -> undefined + NewProtected = case xmpp_util:get_xdata_values( + <<"muc#roomconfig_passwordprotectedroom">>, XData) of + [<<"1">>] -> true; + [<<"true">>] -> true; + [<<"0">>] -> false; + [<<"false">>] -> false; + _ -> undefined end, - NewPassword = case - lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1, - jlib:parse_xdata_submit(XEl)) - of - {value, {_, [P]}} -> P; - _ -> undefined + NewPassword = case xmpp_util:get_xdata_values( + <<"muc#roomconfig_roomsecret">>, XData) of + [P] -> P; + _ -> undefined end, case {OldProtected, NewProtected, OldPassword, NewPassword} @@ -3535,40 +3103,35 @@ is_password_settings_correct(XEl, StateData) -> _ -> true end. --define(XFIELD(Type, Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, Type}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). +-define(XFIELD(Type, Label, Var, Vals), + #xdata_field{type = Type, + label = translate:translate(Lang, Label), + var = Var, + values = Vals}). -define(BOOLXFIELD(Label, Var, Val), - ?XFIELD(<<"boolean">>, Label, Var, + ?XFIELD(boolean, Label, Var, case Val of - true -> <<"1">>; - _ -> <<"0">> + true -> [<<"1">>]; + _ -> [<<"0">>] end)). -define(STRINGXFIELD(Label, Var, Val), - ?XFIELD(<<"text-single">>, Label, Var, Val)). + ?XFIELD('text-single', Label, Var, [Val])). -define(PRIVATEXFIELD(Label, Var, Val), - ?XFIELD(<<"text-private">>, Label, Var, Val)). + ?XFIELD('text-private', Label, Var, [Val])). -define(JIDMULTIXFIELD(Label, Var, JIDList), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"jid-multi">>}, - {<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, jid:to_string(JID)}]} - || JID <- JIDList]}). + ?XFIELD('jid-multi', Label, Var, + [jid:to_string(JID) || JID <- JIDList])). +-spec make_options([{binary(), binary()}], binary()) -> [xdata_option()]. +make_options(Options, Lang) -> + [#xdata_option{label = translate:translate(Lang, Label), + value = Value} || {Label, Value} <- Options]. + +-spec get_default_room_maxusers(state()) -> non_neg_integer(). get_default_room_maxusers(RoomState) -> DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, @@ -3578,342 +3141,193 @@ get_default_room_maxusers(RoomState) -> RoomState2 = set_opts(DefRoomOpts, RoomState), (RoomState2#state.config)#config.max_users. +-spec get_config(binary(), state(), jid()) -> xdata(). get_config(Lang, StateData, From) -> - {_AccessRoute, _AccessCreate, _AccessAdmin, - AccessPersistent} = + {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} = StateData#state.access, ServiceMaxUsers = get_service_max_users(StateData), - DefaultRoomMaxUsers = - get_default_room_maxusers(StateData), + DefaultRoomMaxUsers = get_default_room_maxusers(StateData), Config = StateData#state.config, - {MaxUsersRoomInteger, MaxUsersRoomString} = case - get_max_users(StateData) - of - N when is_integer(N) -> - {N, - jlib:integer_to_binary(N)}; - _ -> {0, <<"none">>} - end, - Res = [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - io_lib:format( - translate:translate( - Lang, - <<"Configuration of room ~s">>), - [jid:to_string(StateData#state.jid)]))}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"hidden">>}, - {<<"var">>, <<"FORM_TYPE">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"http://jabber.org/protocol/muc#roomconfig">>}]}]}, - ?STRINGXFIELD(<<"Room title">>, - <<"muc#roomconfig_roomname">>, (Config#config.title)), - ?STRINGXFIELD(<<"Room description">>, - <<"muc#roomconfig_roomdesc">>, - (Config#config.description))] - ++ - case acl:match_rule(StateData#state.server_host, - AccessPersistent, From) - of - allow -> - [?BOOLXFIELD(<<"Make room persistent">>, - <<"muc#roomconfig_persistentroom">>, - (Config#config.persistent))]; - _ -> [] - end - ++ - [?BOOLXFIELD(<<"Make room public searchable">>, - <<"muc#roomconfig_publicroom">>, - (Config#config.public)), - ?BOOLXFIELD(<<"Make participants list public">>, - <<"public_list">>, (Config#config.public_list)), - ?BOOLXFIELD(<<"Make room password protected">>, - <<"muc#roomconfig_passwordprotectedroom">>, - (Config#config.password_protected)), - ?PRIVATEXFIELD(<<"Password">>, - <<"muc#roomconfig_roomsecret">>, - case Config#config.password_protected of - true -> Config#config.password; - false -> <<"">> - end), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Maximum Number of Occupants">>)}, - {<<"var">>, <<"muc#roomconfig_maxusers">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, MaxUsersRoomString}]}] - ++ - if is_integer(ServiceMaxUsers) -> []; - true -> - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"No limit">>)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - <<"none">>}]}]}] - end - ++ - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - jlib:integer_to_binary(N)}], - children = - [#xmlel{name = <<"value">>, - attrs = [], - children = - [{xmlcdata, - jlib:integer_to_binary(N)}]}]} - || N - <- lists:usort([ServiceMaxUsers, - DefaultRoomMaxUsers, - MaxUsersRoomInteger - | ?MAX_USERS_DEFAULT_LIST]), - N =< ServiceMaxUsers]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Present real Jabber IDs to">>)}, - {<<"var">>, <<"muc#roomconfig_whois">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - if Config#config.anonymous -> - <<"moderators">>; - true -> <<"anyone">> - end}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"moderators only">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderators">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"anyone">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"anyone">>}]}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-multi">>}, - {<<"label">>, - translate:translate(Lang, - <<"Roles for which Presence is Broadcasted">>)}, - {<<"var">>, <<"muc#roomconfig_presencebroadcast">>}], - children = - lists:map( - fun(Role) -> - #xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - atom_to_binary(Role, utf8)}]} - end, Config#config.presence_broadcast - ) ++ - [#xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"Moderator">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderator">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"Participant">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"participant">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"Visitor">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"visitor">>}]}]} - ]}, - ?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 the subject">>, - <<"muc#roomconfig_changesubject">>, - (Config#config.allow_change_subj)), - ?BOOLXFIELD(<<"Allow users to send private messages">>, - <<"allow_private_messages">>, - (Config#config.allow_private_messages)), - #xmlel{name = <<"field">>, - attrs = - [{<<"type">>, <<"list-single">>}, - {<<"label">>, - translate:translate(Lang, - <<"Allow visitors to send private messages to">>)}, - {<<"var">>, - <<"allow_private_messages_from_visitors">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - case - Config#config.allow_private_messages_from_visitors - of - anyone -> <<"anyone">>; - moderators -> <<"moderators">>; - nobody -> <<"nobody">> - end}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"nobody">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, <<"nobody">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"moderators only">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"moderators">>}]}]}, - #xmlel{name = <<"option">>, - attrs = - [{<<"label">>, - translate:translate(Lang, - <<"anyone">>)}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"anyone">>}]}]}]}, - ?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)), - ?BOOLXFIELD(<<"Allow visitors to send voice requests">>, - <<"muc#roomconfig_allowvoicerequests">>, - (Config#config.allow_voice_requests)), - ?BOOLXFIELD(<<"Allow subscription">>, - <<"muc#roomconfig_allow_subscription">>, - (Config#config.allow_subscription)), - ?STRINGXFIELD(<<"Minimum interval between voice requests " - "(in seconds)">>, - <<"muc#roomconfig_voicerequestmininterval">>, - (jlib:integer_to_binary(Config#config.voice_request_min_interval)))] - ++ - case ejabberd_captcha:is_feature_available() of - true -> - [?BOOLXFIELD(<<"Make room CAPTCHA protected">>, - <<"captcha_protected">>, - (Config#config.captcha_protected))]; - false -> [] - end ++ - [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>, - <<"muc#roomconfig_captcha_whitelist">>, - ((?SETS):to_list(Config#config.captcha_whitelist)))] - ++ - case - mod_muc_log:check_access_log(StateData#state.server_host, - From) - of - allow -> - [?BOOLXFIELD(<<"Enable logging">>, - <<"muc#roomconfig_enablelogging">>, - (Config#config.logging))]; - _ -> [] - end, - X = ejabberd_hooks:run_fold(get_room_config, - StateData#state.server_host, - Res, - [StateData, From, Lang]), - {result, - [#xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"You need an x:data capable client to " - "configure room">>)}]}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = X}], - StateData}. + {MaxUsersRoomInteger, MaxUsersRoomString} = + case get_max_users(StateData) of + N when is_integer(N) -> + {N, integer_to_binary(N)}; + _ -> {0, <<"none">>} + end, + Title = iolist_to_binary( + io_lib:format( + translate:translate(Lang, <<"Configuration of room ~s">>), + [jid:to_string(StateData#state.jid)])), + Fs = [#xdata_field{type = hidden, + var = <<"FORM_TYPE">>, + values = [<<"http://jabber.org/protocol/muc#roomconfig">>]}, + ?STRINGXFIELD(<<"Room title">>, + <<"muc#roomconfig_roomname">>, (Config#config.title)), + ?STRINGXFIELD(<<"Room description">>, + <<"muc#roomconfig_roomdesc">>, + (Config#config.description))] ++ + case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of + allow -> + [?BOOLXFIELD(<<"Make room persistent">>, + <<"muc#roomconfig_persistentroom">>, + (Config#config.persistent))]; + deny -> [] + end ++ + [?BOOLXFIELD(<<"Make room public searchable">>, + <<"muc#roomconfig_publicroom">>, + (Config#config.public)), + ?BOOLXFIELD(<<"Make participants list public">>, + <<"public_list">>, (Config#config.public_list)), + ?BOOLXFIELD(<<"Make room password protected">>, + <<"muc#roomconfig_passwordprotectedroom">>, + (Config#config.password_protected)), + ?PRIVATEXFIELD(<<"Password">>, + <<"muc#roomconfig_roomsecret">>, + case Config#config.password_protected of + true -> Config#config.password; + false -> <<"">> + end), + #xdata_field{type = 'list-single', + label = translate:translate( + Lang, <<"Maximum Number of Occupants">>), + var = <<"muc#roomconfig_maxusers">>, + values = [MaxUsersRoomString], + options = + if is_integer(ServiceMaxUsers) -> []; + true -> make_options( + [{<<"No limit">>, <<"none">>}], + Lang) + end ++ + make_options( + [{integer_to_binary(N), integer_to_binary(N)} + || N <- lists:usort([ServiceMaxUsers, + DefaultRoomMaxUsers, + MaxUsersRoomInteger + | ?MAX_USERS_DEFAULT_LIST]), + N =< ServiceMaxUsers], + Lang)}, + #xdata_field{type = 'list-single', + label = translate:translate( + Lang, <<"Present real Jabber IDs to">>), + var = <<"muc#roomconfig_whois">>, + values = [if Config#config.anonymous -> <<"moderators">>; + true -> <<"anyone">> + end], + options = make_options( + [{<<"moderators only">>, <<"moderators">>}, + {<<"anyone">>, <<"anyone">>}], + Lang)}, + #xdata_field{type = 'list-multi', + label = translate:translate( + Lang, + <<"Roles for which Presence is Broadcasted">>), + var = <<"muc#roomconfig_presencebroadcast">>, + values = [atom_to_binary(Role, utf8) + || Role <- Config#config.presence_broadcast], + options = make_options( + [{<<"Moderator">>, <<"moderator">>}, + {<<"Participant">>, <<"participant">>}, + {<<"Visitor">>, <<"visitor">>}], + Lang)}, + ?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 the subject">>, + <<"muc#roomconfig_changesubject">>, + (Config#config.allow_change_subj)), + ?BOOLXFIELD(<<"Allow users to send private messages">>, + <<"allow_private_messages">>, + (Config#config.allow_private_messages)), + #xdata_field{type = 'list-single', + label = translate:translate( + Lang, + <<"Allow visitors to send private messages to">>), + var = <<"allow_private_messages_from_visitors">>, + values = [case Config#config.allow_private_messages_from_visitors of + anyone -> <<"anyone">>; + moderators -> <<"moderators">>; + nobody -> <<"nobody">> + end], + options = make_options( + [{<<"nobody">>, <<"nobody">>}, + {<<"moderators only">>, <<"moderators">>}, + {<<"anyone">>, <<"anyone">>}], + Lang)}, + ?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)), + ?BOOLXFIELD(<<"Allow visitors to send voice requests">>, + <<"muc#roomconfig_allowvoicerequests">>, + (Config#config.allow_voice_requests)), + ?BOOLXFIELD(<<"Allow subscription">>, + <<"muc#roomconfig_allow_subscription">>, + (Config#config.allow_subscription)), + ?STRINGXFIELD(<<"Minimum interval between voice requests " + "(in seconds)">>, + <<"muc#roomconfig_voicerequestmininterval">>, + integer_to_binary(Config#config.voice_request_min_interval))] + ++ + case ejabberd_captcha:is_feature_available() of + true -> + [?BOOLXFIELD(<<"Make room CAPTCHA protected">>, + <<"captcha_protected">>, + (Config#config.captcha_protected))]; + false -> [] + end ++ + [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>, + <<"muc#roomconfig_captcha_whitelist">>, + ((?SETS):to_list(Config#config.captcha_whitelist)))] + ++ + case mod_muc_log:check_access_log(StateData#state.server_host, From) of + allow -> + [?BOOLXFIELD(<<"Enable logging">>, + <<"muc#roomconfig_enablelogging">>, + (Config#config.logging))]; + deny -> [] + end, + Fields = ejabberd_hooks:run_fold(get_room_config, + StateData#state.server_host, + Fs, + [StateData, From, Lang]), + #xdata{type = form, title = Title, fields = Fields}. -set_config(XEl, StateData, Lang) -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)}; - _ -> - case set_xoption(XData, StateData#state.config, - StateData#state.server_host, Lang) of - #config{} = Config -> - Res = change_config(Config, StateData), - {result, _, NSD} = Res, - Type = case {(StateData#state.config)#config.logging, - Config#config.logging} - of - {true, false} -> roomconfig_change_disabledlogging; - {false, true} -> roomconfig_change_enabledlogging; - {_, _} -> roomconfig_change - end, - Users = [{U#user.jid, U#user.nick, U#user.role} - || {_, U} <- (?DICT):to_list(StateData#state.users)], - add_to_log(Type, Users, NSD), - Res; - Err -> Err - end +-spec set_config(xdata(), state(), binary()) -> {error, error()} | + {result, undefined, state()}. +set_config(#xdata{fields = Fields}, StateData, Lang) -> + Options = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- Fields], + case set_xoption(Options, StateData#state.config, + StateData#state.server_host, Lang) of + #config{} = Config -> + Res = change_config(Config, StateData), + {result, _, NSD} = Res, + Type = case {(StateData#state.config)#config.logging, + Config#config.logging} + of + {true, false} -> roomconfig_change_disabledlogging; + {false, true} -> roomconfig_change_enabledlogging; + {_, _} -> roomconfig_change + end, + Users = [{U#user.jid, U#user.nick, U#user.role} + || {_, U} <- (?DICT):to_list(StateData#state.users)], + add_to_log(Type, Users, NSD), + Res; + Err -> Err end. -define(SET_BOOL_XOPT(Opt, Val), @@ -3928,21 +3342,32 @@ set_config(XEl, StateData, Lang) -> _ -> Txt = <<"Value of '~s' should be boolean">>, ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)} + {error, xmpp:err_bad_request(ErrTxt, Lang)} end). -define(SET_NAT_XOPT(Opt, Val), - case catch jlib:binary_to_integer(Val) of + case catch binary_to_integer(Val) of I when is_integer(I), I > 0 -> set_xoption(Opts, Config#config{Opt = I}, ServerHost, Lang); _ -> Txt = <<"Value of '~s' should be integer">>, ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)} + {error, xmpp:err_bad_request(ErrTxt, Lang)} end). --define(SET_STRING_XOPT(Opt, Val), - set_xoption(Opts, Config#config{Opt = Val}, ServerHost, Lang)). +-define(SET_STRING_XOPT(Opt, Vals), + try + V = case Vals of + [] -> <<"">>; + [Val] -> Val; + _ when is_atom(Vals) -> Vals + end, + set_xoption(Opts, Config#config{Opt = V}, ServerHost, Lang) + catch _:_ -> + Txt = <<"Incorrect value of option '~s'">>, + ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), + {error, xmpp:err_bad_request(ErrTxt, Lang)} + end). -define(SET_JIDMULTI_XOPT(Opt, Vals), begin @@ -3957,15 +3382,17 @@ set_config(XEl, StateData, Lang) -> set_xoption(Opts, Config#config{Opt = Set}, ServerHost, Lang) end). +-spec set_xoption([{binary(), [binary()]}], #config{}, + binary(), binary()) -> #config{} | {error, error()}. set_xoption([], Config, _ServerHost, _Lang) -> Config; -set_xoption([{<<"muc#roomconfig_roomname">>, [Val]} +set_xoption([{<<"muc#roomconfig_roomname">>, Vals} | Opts], Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(title, Val); -set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]} + ?SET_STRING_XOPT(title, Vals); +set_xoption([{<<"muc#roomconfig_roomdesc">>, Vals} | Opts], Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(description, Val); + ?SET_STRING_XOPT(description, Vals); set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]} | Opts], Config, ServerHost, Lang) -> @@ -3994,7 +3421,7 @@ set_xoption([{<<"allow_private_messages_from_visitors">>, _ -> Txt = <<"Value of 'allow_private_messages_from_visitors' " "should be anyone|moderators|nobody">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + {error, xmpp:err_bad_request(Txt, Lang)} end; set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>, [Val]} @@ -4045,10 +3472,10 @@ set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>, | Opts], Config, ServerHost, Lang) -> ?SET_BOOL_XOPT(password_protected, Val); -set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} +set_xoption([{<<"muc#roomconfig_roomsecret">>, Vals} | Opts], Config, ServerHost, Lang) -> - ?SET_STRING_XOPT(password, Val); + ?SET_STRING_XOPT(password, Vals); set_xoption([{<<"anonymous">>, [Val]} | Opts], Config, ServerHost, Lang) -> ?SET_BOOL_XOPT(anonymous, Val); @@ -4069,7 +3496,7 @@ set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts], error -> Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should " "be moderator|participant|visitor">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; + {error, xmpp:err_bad_request(Txt, Lang)}; {M, P, V} -> Res = if M -> [moderator]; true -> [] end ++ @@ -4101,7 +3528,7 @@ set_xoption([{<<"muc#roomconfig_whois">>, [Val]} _ -> Txt = <<"Value of 'muc#roomconfig_whois' should be " "moderators|anyone">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)} + {error, xmpp:err_bad_request(Txt, Lang)} end; set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]} | Opts], @@ -4125,7 +3552,7 @@ set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) -> set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) -> Txt = <<"Unknown option '~s'">>, ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])), - Err = {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}, + Err = {error, xmpp:err_bad_request(ErrTxt, Lang)}, case ejabberd_hooks:run_fold(set_room_option, ServerHost, Err, @@ -4136,6 +3563,7 @@ set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) -> set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang) end. +-spec change_config(#config{}, state()) -> {result, undefined, state()}. change_config(Config, StateData) -> send_config_change_info(Config, StateData), NSD = remove_subscriptions(StateData#state{config = Config}), @@ -4154,57 +3582,54 @@ change_config(Config, StateData) -> Config#config.members_only} of {false, true} -> - NSD1 = remove_nonmembers(NSD), {result, [], NSD1}; - _ -> {result, [], NSD} + NSD1 = remove_nonmembers(NSD), {result, undefined, NSD1}; + _ -> {result, undefined, NSD} end. +-spec send_config_change_info(#config{}, state()) -> ok. send_config_change_info(Config, #state{config = Config}) -> ok; send_config_change_info(New, #state{config = Old} = StateData) -> Codes = case {Old#config.logging, New#config.logging} of - {false, true} -> [<<"170">>]; - {true, false} -> [<<"171">>]; + {false, true} -> [170]; + {true, false} -> [171]; _ -> [] end ++ case {Old#config.anonymous, New#config.anonymous} of - {true, false} -> [<<"172">>]; - {false, true} -> [<<"173">>]; + {true, false} -> [172]; + {false, true} -> [173]; _ -> [] end ++ case Old#config{anonymous = New#config.anonymous, logging = New#config.logging} of New -> []; - _ -> [<<"104">>] + _ -> [104] end, - StatusEls = [#xmlel{name = <<"status">>, - attrs = [{<<"code">>, Code}], - children = []} || Code <- Codes], - Message = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"groupchat">>}, - {<<"id">>, randoms:get_string()}], - children = [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = StatusEls}]}, + Message = #message{type = groupchat, + id = randoms:get_string(), + sub_els = [#muc_user{status_codes = Codes}]}, send_wrapped_multiple(StateData#state.jid, StateData#state.users, Message, ?NS_MUCSUB_NODES_CONFIG, StateData). +-spec remove_nonmembers(state()) -> state(). remove_nonmembers(StateData) -> lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) -> Affiliation = get_affiliation(JID, SD), case Affiliation of none -> catch send_kickban_presence(<<"">>, JID, <<"">>, - <<"322">>, SD), + 322, SD), set_role(JID, none, SD); _ -> SD end end, StateData, (?DICT):to_list(StateData#state.users)). +-spec set_opts([{atom(), any()}], state()) -> state(). set_opts([], StateData) -> StateData; set_opts([{Opt, Val} | Opts], StateData) -> NSD = case Opt of @@ -4342,7 +3767,7 @@ set_opts([{Opt, Val} | Opts], StateData) -> -define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}). - +-spec make_opts(state()) -> [{atom(), any()}]. make_opts(StateData) -> Config = StateData#state.config, Subscribers = (?DICT):fold( @@ -4381,243 +3806,221 @@ make_opts(StateData) -> {subject_author, StateData#state.subject_author}, {subscribers, Subscribers}]. +-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}. destroy_room(DEl, StateData) -> - lists:foreach(fun ({_LJID, Info}) -> - Nick = Info#user.nick, - ItemAttrs = [{<<"affiliation">>, <<"none">>}, - {<<"role">>, <<"none">>}], - Packet = #xmlel{name = <<"presence">>, - attrs = - [{<<"type">>, <<"unavailable">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, - ?NS_MUC_USER}], - children = - [#xmlel{name = - <<"item">>, - attrs = - ItemAttrs, - children = - []}, - DEl]}]}, - send_wrapped(jid:replace_resource(StateData#state.jid, - Nick), - Info#user.jid, Packet, - ?NS_MUCSUB_NODES_CONFIG, StateData) - end, - (?DICT):to_list(StateData#state.users)), + Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER}, + lists:foreach( + fun({_LJID, Info}) -> + Nick = Info#user.nick, + Item = #muc_item{affiliation = none, + role = none}, + Packet = #presence{ + type = unavailable, + sub_els = [#muc_user{items = [Item], + destroy = Destroy}]}, + send_wrapped(jid:replace_resource(StateData#state.jid, Nick), + Info#user.jid, Packet, + ?NS_MUCSUB_NODES_CONFIG, StateData) + end, + (?DICT):to_list(StateData#state.users)), case (StateData#state.config)#config.persistent of true -> mod_muc:forget_room(StateData#state.server_host, StateData#state.host, StateData#state.room); false -> ok end, - {result, [], stop}. + {result, undefined, stop}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Disco --define(FEATURE(Var), - #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}], - children = []}). - -define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse), case Opt of - true -> ?FEATURE(Fiftrue); - false -> ?FEATURE(Fiffalse) + true -> Fiftrue; + false -> Fiffalse end). -process_iq_disco_info(_From, set, Lang, _StateData) -> +-spec process_iq_disco_info(jid(), iq(), state()) -> + {result, disco_info()} | {error, error()}. +process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}; -process_iq_disco_info(_From, get, Lang, StateData) -> + {error, xmpp:err_not_allowed(Txt, Lang)}; +process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) -> Config = StateData#state.config, - {result, - [#xmlel{name = <<"identity">>, - attrs = - [{<<"category">>, <<"conference">>}, - {<<"type">>, <<"text">>}, - {<<"name">>, get_title(StateData)}], - children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_VCARD}], children = []}, - #xmlel{name = <<"feature">>, - attrs = [{<<"var">>, ?NS_MUC}], children = []}, - ?CONFIG_OPT_TO_FEATURE((Config#config.public), - <<"muc_public">>, <<"muc_hidden">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.persistent), - <<"muc_persistent">>, <<"muc_temporary">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.members_only), - <<"muc_membersonly">>, <<"muc_open">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous), - <<"muc_semianonymous">>, <<"muc_nonanonymous">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.moderated), - <<"muc_moderated">>, <<"muc_unmoderated">>), - ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), - <<"muc_passwordprotected">>, <<"muc_unsecured">>)] - ++ case Config#config.allow_subscription of - true -> [?FEATURE(?NS_MUCSUB)]; - false -> [] - end - ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam), - Config#config.mam} of - {true, true} -> - [?FEATURE(?NS_MAM_TMP), - ?FEATURE(?NS_MAM_0), - ?FEATURE(?NS_MAM_1)]; - _ -> - [] - end - ++ iq_disco_info_extras(Lang, StateData), - StateData}. + Feats = [?NS_VCARD, ?NS_MUC, + ?CONFIG_OPT_TO_FEATURE((Config#config.public), + <<"muc_public">>, <<"muc_hidden">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.persistent), + <<"muc_persistent">>, <<"muc_temporary">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.members_only), + <<"muc_membersonly">>, <<"muc_open">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous), + <<"muc_semianonymous">>, <<"muc_nonanonymous">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.moderated), + <<"muc_moderated">>, <<"muc_unmoderated">>), + ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected), + <<"muc_passwordprotected">>, <<"muc_unsecured">>)] + ++ case Config#config.allow_subscription of + true -> [?NS_MUCSUB]; + false -> [] + end + ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam), + Config#config.mam} of + {true, true} -> + [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]; + _ -> + [] + end, + {result, #disco_info{xdata = [iq_disco_info_extras(Lang, StateData)], + identities = [#identity{category = <<"conference">>, + type = <<"text">>, + name = get_title(StateData)}], + features = Feats}}. --define(RFIELDT(Type, Var, Val), - #xmlel{name = <<"field">>, - attrs = [{<<"type">>, Type}, {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). +-spec mk_rfieldt('boolean' | 'fixed' | 'hidden' | + 'jid-multi' | 'jid-single' | 'list-multi' | + 'list-single' | 'text-multi' | 'text-private' | + 'text-single', binary(), binary()) -> xdata_field(). +mk_rfieldt(Type, Var, Val) -> + #xdata_field{type = Type, var = Var, values = [Val]}. --define(RFIELD(Label, Var, Val), - #xmlel{name = <<"field">>, - attrs = - [{<<"label">>, translate:translate(Lang, Label)}, - {<<"var">>, Var}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = [{xmlcdata, Val}]}]}). +-spec mk_rfield(binary(), binary(), binary(), binary()) -> xdata_field(). +mk_rfield(Label, Var, Val, Lang) -> + #xdata_field{type = 'text-single', + label = translate:translate(Lang, Label), + var = Var, + values = [Val]}. +-spec iq_disco_info_extras(binary(), state()) -> xdata(). iq_disco_info_extras(Lang, StateData) -> Len = (?DICT):size(StateData#state.users), - RoomDescription = - (StateData#state.config)#config.description, - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}], - children = - [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>, - <<"http://jabber.org/protocol/muc#roominfo">>), - ?RFIELD(<<"Room description">>, - <<"muc#roominfo_description">>, RoomDescription), - ?RFIELD(<<"Number of occupants">>, - <<"muc#roominfo_occupants">>, - (iolist_to_binary(integer_to_list(Len))))]}]. + RoomDescription = (StateData#state.config)#config.description, + #xdata{type = result, + fields = [mk_rfieldt(hidden, <<"FORM_TYPE">>, + "http://jabber.org/protocol/muc#roominfo"), + mk_rfield(<<"Room description">>, + <<"muc#roominfo_description">>, + RoomDescription, Lang), + mk_rfield(<<"Number of occupants">>, + <<"muc#roominfo_occupants">>, + integer_to_binary(Len), Lang)]}. -process_iq_disco_items(_From, set, Lang, _StateData) -> +-spec process_iq_disco_items(jid(), iq(), state()) -> + {error, error()} | {result, disco_items()}. +process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) -> Txt = <<"Value 'set' of 'type' attribute is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}; -process_iq_disco_items(From, get, Lang, StateData) -> + {error, xmpp:err_not_allowed(Txt, Lang)}; +process_iq_disco_items(From, #iq{type = get, lang = Lang}, StateData) -> case (StateData#state.config)#config.public_list of true -> - {result, get_mucroom_disco_items(StateData), StateData}; + {result, get_mucroom_disco_items(StateData)}; _ -> case is_occupant_or_admin(From, StateData) of true -> - {result, get_mucroom_disco_items(StateData), StateData}; + {result, get_mucroom_disco_items(StateData)}; _ -> Txt = <<"Only occupants or administrators can perform this query">>, - {error, ?ERRT_FORBIDDEN(Lang, Txt)} + {error, xmpp:err_forbidden(Txt, Lang)} end end. -process_iq_captcha(_From, get, Lang, _SubEl, - _StateData) -> +-spec process_iq_captcha(jid(), iq(), state()) -> {error, error()} | + {result, undefined}. +process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) -> Txt = <<"Value 'get' of 'type' attribute is not allowed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}; -process_iq_captcha(_From, set, Lang, SubEl, - StateData) -> + {error, xmpp:err_not_allowed(Txt, Lang)}; +process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, + _StateData) -> case ejabberd_captcha:process_reply(SubEl) of - ok -> {result, [], StateData}; + ok -> {result, undefined}; {error, malformed} -> Txt = <<"Incorrect CAPTCHA submit">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; + {error, xmpp:err_bad_request(Txt, Lang)}; _ -> Txt = <<"The CAPTCHA verification has failed">>, - {error, ?ERRT_NOT_ALLOWED(Lang, Txt)} + {error, xmpp:err_not_allowed(Txt, Lang)} end. -process_iq_vcard(_From, get, _Lang, _SubEl, StateData) -> +-spec process_iq_vcard(jid(), iq(), state()) -> + {result, vcard_temp() | xmlel()} | + {result, undefined, state()} | + {error, error()}. +process_iq_vcard(_From, #iq{type = get}, StateData) -> #state{config = #config{vcard = VCardRaw}} = StateData, case fxml_stream:parse_element(VCardRaw) of - #xmlel{children = VCardEls} -> - {result, VCardEls, StateData}; + #xmlel{} = VCard -> + {result, VCard}; {error, _} -> - {result, [], StateData} + {result, #vcard_temp{}} end; -process_iq_vcard(From, set, Lang, SubEl, StateData) -> +process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]}, + StateData) -> case get_affiliation(From, StateData) of owner -> - VCardRaw = fxml:element_to_binary(SubEl), + VCardRaw = fxml:element_to_binary(xmpp:encode(SubEl)), Config = StateData#state.config, NewConfig = Config#config{vcard = VCardRaw}, change_config(NewConfig, StateData); _ -> ErrText = <<"Owner privileges required">>, - {error, ?ERRT_FORBIDDEN(Lang, ErrText)} + {error, xmpp:err_forbidden(ErrText, Lang)} end. -process_iq_mucsub(From, Packet, +-spec process_iq_mucsub(jid(), iq(), state()) -> + {error, error()} | + {result, undefined | muc_subscribe(), state()} | + {ignore, state()}. +process_iq_mucsub(_From, #iq{type = set, lang = Lang, + sub_els = [#muc_subscribe{}]}, + #state{config = #config{allow_subscription = false}}) -> + {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)}; +process_iq_mucsub(From, #iq{type = set, lang = Lang, - sub_el = #xmlel{name = <<"subscribe">>} = SubEl}, - #state{config = Config} = StateData) -> - case fxml:get_tag_attr_s(<<"nick">>, SubEl) of - <<"">> -> - Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>), - {error, Err}; - Nick when Config#config.allow_subscription -> - LJID = jid:tolower(From), - case (?DICT):find(LJID, StateData#state.users) of - {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick -> - Nodes = get_subscription_nodes(Packet), - case {nick_collision(From, Nick, StateData), - mod_muc:can_use_nick(StateData#state.server_host, - StateData#state.host, - From, Nick)} of - {true, _} -> - ErrText = <<"That nickname is already in use by another occupant">>, - {error, ?ERRT_CONFLICT(Lang, ErrText)}; - {_, false} -> - ErrText = <<"That nickname is registered by another person">>, - {error, ?ERRT_CONFLICT(Lang, ErrText)}; - _ -> - NewStateData = add_online_user( - From, Nick, Role, true, Nodes, StateData), - {result, subscription_nodes_to_events(Nodes), NewStateData} - end; - {ok, #user{role = Role}} -> - Nodes = get_subscription_nodes(Packet), + sub_els = [#muc_subscribe{nick = Nick}]} = Packet, + StateData) -> + LJID = jid:tolower(From), + case (?DICT):find(LJID, StateData#state.users) of + {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick -> + Nodes = get_subscription_nodes(Packet), + case {nick_collision(From, Nick, StateData), + mod_muc:can_use_nick(StateData#state.server_host, + StateData#state.host, + From, Nick)} of + {true, _} -> + ErrText = <<"That nickname is already in use by another occupant">>, + {error, xmpp:err_conflict(ErrText, Lang)}; + {_, false} -> + ErrText = <<"That nickname is registered by another person">>, + {error, xmpp:err_conflict(ErrText, Lang)}; + _ -> NewStateData = add_online_user( From, Nick, Role, true, Nodes, StateData), - {result, subscription_nodes_to_events(Nodes), NewStateData}; - error -> - add_new_user(From, Nick, Packet, StateData) + {result, subscribe_result(Packet), NewStateData} end; - _ -> - Err = ?ERRT_NOT_ALLOWED(Lang, <<"Subscriptions are not allowed">>), - {error, Err} + {ok, #user{role = Role}} -> + Nodes = get_subscription_nodes(Packet), + NewStateData = add_online_user( + From, Nick, Role, true, Nodes, StateData), + {result, subscribe_result(Packet), NewStateData}; + error -> + add_new_user(From, Nick, Packet, StateData) end; -process_iq_mucsub(From, _Packet, - #iq{type = set, - sub_el = #xmlel{name = <<"unsubscribe">>}}, +process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]}, StateData) -> LJID = jid:tolower(From), case ?DICT:find(LJID, StateData#state.users) of {ok, #user{is_subscriber = true} = User} -> NewStateData = remove_subscription(From, User, StateData), store_room(NewStateData), - {result, [], NewStateData}; + {result, undefined, NewStateData}; _ -> - {result, [], StateData} + {result, undefined, StateData} end; -process_iq_mucsub(_From, _Packet, #iq{type = set, lang = Lang}, _StateData) -> - Txt = <<"Unrecognized subscription command">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}; -process_iq_mucsub(_From, _Packet, #iq{type = get, lang = Lang}, _StateData) -> +process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) -> Txt = <<"Value 'get' of 'type' attribute is not allowed">>, - {error, ?ERRT_BAD_REQUEST(Lang, Txt)}. + {error, xmpp:err_bad_request(Txt, Lang)}. +-spec remove_subscription(jid(), #user{}, state()) -> state(). remove_subscription(JID, #user{is_subscriber = true} = User, StateData) -> case User#user.last_presence of undefined -> @@ -4631,6 +4034,7 @@ remove_subscription(JID, #user{is_subscriber = true} = User, StateData) -> remove_subscription(_JID, #user{}, StateData) -> StateData. +-spec remove_subscriptions(state()) -> state(). remove_subscriptions(StateData) -> if not (StateData#state.config)#config.allow_subscription -> dict:fold( @@ -4641,41 +4045,32 @@ remove_subscriptions(StateData) -> StateData end. -get_subscription_nodes(#xmlel{name = <<"iq">>} = Packet) -> - case fxml:get_subtag_with_xmlns(Packet, <<"subscribe">>, ?NS_MUCSUB) of - #xmlel{children = Els} -> - lists:flatmap( - fun(#xmlel{name = <<"event">>, attrs = Attrs}) -> - Node = fxml:get_attr_s(<<"node">>, Attrs), - case lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE, - ?NS_MUCSUB_NODES_MESSAGES, - ?NS_MUCSUB_NODES_AFFILIATIONS, - ?NS_MUCSUB_NODES_SUBJECT, - ?NS_MUCSUB_NODES_CONFIG, - ?NS_MUCSUB_NODES_PARTICIPANTS]) of - true -> - [Node]; - false -> - [] - end; - (_) -> - [] - end, Els); - false -> - [] - end; +-spec get_subscription_nodes(iq()) -> [binary()]. +get_subscription_nodes(#iq{sub_els = [#muc_subscribe{events = Nodes}]}) -> + lists:filter( + fun(Node) -> + lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE, + ?NS_MUCSUB_NODES_MESSAGES, + ?NS_MUCSUB_NODES_AFFILIATIONS, + ?NS_MUCSUB_NODES_SUBJECT, + ?NS_MUCSUB_NODES_CONFIG, + ?NS_MUCSUB_NODES_PARTICIPANTS]) + end, Nodes); get_subscription_nodes(_) -> []. -subscription_nodes_to_events(Nodes) -> - [#xmlel{name = <<"event">>, attrs = [{<<"node">>, Node}]} || Node <- Nodes]. +-spec subscribe_result(iq()) -> muc_subscribe(). +subscribe_result(#iq{sub_els = [#muc_subscribe{nick = Nick}]} = Packet) -> + #muc_subscribe{nick = Nick, events = get_subscription_nodes(Packet)}. +-spec get_title(state()) -> binary(). get_title(StateData) -> case (StateData#state.config)#config.title of <<"">> -> StateData#state.room; Name -> Name end. +-spec get_roomdesc_reply(jid(), state(), binary()) -> {item, binary()} | false. get_roomdesc_reply(JID, StateData, Tail) -> IsOccupantOrAdmin = is_occupant_or_admin(JID, StateData), @@ -4689,352 +4084,215 @@ get_roomdesc_reply(JID, StateData, Tail) -> true -> false end. +-spec get_roomdesc_tail(state(), binary()) -> binary(). get_roomdesc_tail(StateData, Lang) -> Desc = case (StateData#state.config)#config.public of true -> <<"">>; _ -> translate:translate(Lang, <<"private, ">>) end, - Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0, - StateData#state.users), + Len = (?DICT):size(StateData#state.users), <<" (", Desc/binary, (iolist_to_binary(integer_to_list(Len)))/binary, ")">>. +-spec get_mucroom_disco_items(state()) -> disco_items(). get_mucroom_disco_items(StateData) -> - lists:map(fun ({_LJID, Info}) -> + Items = lists:map( + fun({_LJID, Info}) -> Nick = Info#user.nick, - #xmlel{name = <<"item">>, - attrs = - [{<<"jid">>, - jid:to_string({StateData#state.room, - StateData#state.host, - Nick})}, - {<<"name">>, Nick}], - children = []} + #disco_item{jid = jid:make(StateData#state.room, + StateData#state.host, + Nick), + name = Nick} end, - (?DICT):to_list(StateData#state.users)). + (?DICT):to_list(StateData#state.users)), + #disco_items{items = Items}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Voice request support -is_voice_request(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - case jlib:parse_xdata_submit(El) of - [_ | _] = Fields -> - case {lists:keysearch(<<"FORM_TYPE">>, 1, - Fields), - lists:keysearch(<<"muc#role">>, 1, - Fields)} - of - {{value, - {_, - [<<"http://jabber.org/protocol/muc#request">>]}}, - {value, {_, [<<"participant">>]}}} -> - true; - _ -> false - end; - _ -> false - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). +-spec is_voice_request(message()) -> boolean(). +is_voice_request(Packet) -> + Els = xmpp:get_els(Packet), + lists:any( + fun(#xdata{} = X) -> + case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), + xmpp_util:get_xdata_values(<<"muc#role">>, X)} of + {[<<"http://jabber.org/protocol/muc#request">>], + [<<"participant">>]} -> + true; + _ -> + false + end; + (_) -> + false + end, Els). +-spec prepare_request_form(jid(), binary(), binary()) -> message(). prepare_request_form(Requester, Nick, Lang) -> - #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Voice request">>)}]}, - #xmlel{name = <<"instructions">>, attrs = [], - children = - [{xmlcdata, - translate:translate(Lang, - <<"Either approve or decline the voice " - "request.">>)}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"FORM_TYPE">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"http://jabber.org/protocol/muc#request">>}]}]}, - #xmlel{name = <<"field">>, - attrs = - [{<<"var">>, <<"muc#role">>}, - {<<"type">>, <<"hidden">>}], - children = - [#xmlel{name = <<"value">>, attrs = [], - children = - [{xmlcdata, - <<"participant">>}]}]}, - ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, - (jid:to_string(Requester))), - ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, - Nick), - ?BOOLXFIELD(<<"Grant voice to this person?">>, - <<"muc#request_allow">>, - (jlib:binary_to_atom(<<"false">>)))]}]}. + Title = translate:translate(Lang, <<"Voice request">>), + Instruction = translate:translate( + Lang, <<"Either approve or decline the voice request.">>), + Fs = [#xdata_field{var = <<"FORM_TYPE">>, + type = hidden, + values = [<<"http://jabber.org/protocol/muc#request">>]}, + #xdata_field{var = <<"muc#role">>, + type = hidden, + values = [<<"participant">>]}, + ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>, + jid:to_string(Requester)), + ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, Nick), + ?BOOLXFIELD(<<"Grant voice to this person?">>, + <<"muc#request_allow">>, false)], + #message{type = normal, + sub_els = [#xdata{type = form, + title = Title, + instructions = [Instruction], + fields = Fs}]}. -send_voice_request(From, StateData) -> +-spec send_voice_request(jid(), binary(), state()) -> ok. +send_voice_request(From, Lang, StateData) -> Moderators = search_role(moderator, StateData), FromNick = find_nick_by_jid(From, StateData), lists:foreach(fun ({_, User}) -> ejabberd_router:route( StateData#state.jid, User#user.jid, - prepare_request_form(From, FromNick, <<"">>)) + prepare_request_form(From, FromNick, Lang)) end, Moderators). -is_voice_approvement(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_XDATA -> - case jlib:parse_xdata_submit(El) of - [_ | _] = Fs -> - case {lists:keysearch(<<"FORM_TYPE">>, 1, - Fs), - lists:keysearch(<<"muc#role">>, 1, - Fs), - lists:keysearch(<<"muc#request_allow">>, - 1, Fs)} - of - {{value, - {_, - [<<"http://jabber.org/protocol/muc#request">>]}}, - {value, {_, [<<"participant">>]}}, - {value, {_, [Flag]}}} - when Flag == <<"true">>; - Flag == <<"1">> -> - true; - _ -> false - end; - _ -> false - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). +-spec is_voice_approvement(message()) -> boolean(). +is_voice_approvement(Packet) -> + Els = xmpp:get_els(Packet), + lists:any( + fun(#xdata{} = X) -> + case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), + xmpp_util:get_xdata_values(<<"muc#role">>, X), + xmpp_util:get_xdata_values(<<"muc#request_allow">>, X)} of + {[<<"http://jabber.org/protocol/muc#request">>], + [<<"participant">>], [Flag]} when Flag == <<"true">>; + Flag == <<"1">> -> + true; + _ -> + false + end; + (_) -> + false + end, Els). -extract_jid_from_voice_approvement(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) -> - Fields = case jlib:parse_xdata_submit(El) of - invalid -> []; - Res -> Res - end, - lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) -> - case jid:from_string(JIDStr) of - error -> error; - J -> {ok, J} - end; - (_, Acc) -> Acc - end, - error, Fields); - (_, Acc) -> Acc - end, - error, Els). +-spec extract_jid_from_voice_approvement(message()) -> jid() | error. +extract_jid_from_voice_approvement(Packet) -> + Els = xmpp:get_els(Packet), + lists:foldl( + fun(#xdata{} = X, error) -> + case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X), + xmpp_util:get_xdata_values(<<"muc#role">>, X), + xmpp_util:get_xdata_values(<<"muc#request_allow">>, X), + xmpp_util:get_xdata_values(<<"muc#jid">>, X)} of + {[<<"http://jabber.org/protocol/muc#request">>], + [<<"participant">>], [Flag], [J]} when Flag == <<"true">>; + Flag == <<"1">> -> + jid:from_string(J); + _ -> + error + end; + (_, Acc) -> + Acc + end, error, Els). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Invitation support -is_invitation(Els) -> - lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} = - El, - false) -> - case fxml:get_attr_s(<<"xmlns">>, Attrs) of - ?NS_MUC_USER -> - case fxml:get_subtag(El, <<"invite">>) of - false -> false; - _ -> true - end; - _ -> false - end; - (_, Acc) -> Acc - end, - false, Els). +-spec is_invitation(message()) -> boolean(). +is_invitation(Packet) -> + Els = xmpp:get_els(Packet), + lists:any( + fun(#muc_user{invites = [_|_]}) -> true; + (_) -> false + end, Els). -check_invitation(From, Packet, Lang, StateData) -> +-spec check_invitation(jid(), message(), state()) -> {error, error()} | jid(). +check_invitation(From, Packet, StateData) -> + Lang = xmpp:get_lang(Packet), FAffiliation = get_affiliation(From, StateData), - CanInvite = - (StateData#state.config)#config.allow_user_invites - orelse - FAffiliation == admin orelse FAffiliation == owner, - InviteEl = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of - false -> - Txt1 = <<"No 'x' element found">>, - throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)}); - XEl -> - case fxml:get_subtag(XEl, <<"invite">>) of - false -> - Txt2 = <<"No 'invite' element found">>, - throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)}); - InviteEl1 -> - InviteEl1 - end - end, - JID = case - jid:from_string(fxml:get_tag_attr_s(<<"to">>, - InviteEl)) - of - error -> - Txt = <<"Incorrect value of 'to' attribute">>, - throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)}); - JID1 -> JID1 - end, + CanInvite = (StateData#state.config)#config.allow_user_invites + orelse + FAffiliation == admin orelse FAffiliation == owner, case CanInvite of - false -> - Txt3 = <<"Invitations are not allowed in this conference">>, - throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}); - true -> - Reason = fxml:get_path_s(InviteEl, - [{elem, <<"reason">>}, cdata]), - ContinueEl = case fxml:get_path_s(InviteEl, - [{elem, <<"continue">>}]) - of - <<>> -> []; - Continue1 -> [Continue1] - end, - IEl = [#xmlel{name = <<"invite">>, - attrs = [{<<"from">>, jid:to_string(From)}], - children = - [#xmlel{name = <<"reason">>, attrs = [], - children = [{xmlcdata, Reason}]}] - ++ ContinueEl}], - PasswdEl = case - (StateData#state.config)#config.password_protected - of - true -> - [#xmlel{name = <<"password">>, attrs = [], - children = - [{xmlcdata, - (StateData#state.config)#config.password}]}]; - _ -> [] - end, - Body = #xmlel{name = <<"body">>, attrs = [], - children = - [{xmlcdata, - iolist_to_binary( - [io_lib:format( - translate:translate( - Lang, - <<"~s invites you to the room ~s">>), - [jid:to_string(From), - jid:to_string({StateData#state.room, - StateData#state.host, - <<"">>})]), - case - (StateData#state.config)#config.password_protected - of + false -> + Txt = <<"Invitations are not allowed in this conference">>, + {error, xmpp:err_not_allowed(Txt, Lang)}; + true -> + case xmpp:get_subtag(Packet, #muc_user{}) of + #muc_user{invites = [#muc_invite{to = undefined}]} -> + Txt = <<"No 'to' attribute found">>, + {error, xmpp:err_bad_request(Txt, Lang)}; + #muc_user{invites = [#muc_invite{to = JID, reason = Reason} = I]} -> + Invite = I#muc_invite{to = undefined, from = From}, + Password = case (StateData#state.config)#config.password_protected of + true -> + (StateData#state.config)#config.password; + false -> + undefined + end, + XUser = #muc_user{password = Password, invites = [Invite]}, + XConference = #x_conference{jid = jid:make(StateData#state.room, + StateData#state.host), + reason = Reason}, + Body = iolist_to_binary( + [io_lib:format( + translate:translate( + Lang, + <<"~s invites you to the room ~s">>), + [jid:to_string(From), + jid:to_string({StateData#state.room, + StateData#state.host, + <<"">>})]), + case (StateData#state.config)#config.password_protected of true -> <<", ", - (translate:translate(Lang, - <<"the password is">>))/binary, + (translate:translate( + Lang, <<"the password is">>))/binary, " '", ((StateData#state.config)#config.password)/binary, "'">>; _ -> <<"">> - end - , - case Reason of - <<"">> -> <<"">>; - _ -> <<" (", Reason/binary, ") ">> - end])}]}, - Msg = #xmlel{name = <<"message">>, - attrs = [{<<"type">>, <<"normal">>}], - children = - [#xmlel{name = <<"x">>, - attrs = [{<<"xmlns">>, ?NS_MUC_USER}], - children = IEl ++ PasswdEl}, - #xmlel{name = <<"x">>, - attrs = - [{<<"xmlns">>, ?NS_XCONFERENCE}, - {<<"jid">>, - jid:to_string({StateData#state.room, - StateData#state.host, - <<"">>})}], - children = [{xmlcdata, Reason}]}, - Body]}, - ejabberd_router:route(StateData#state.jid, JID, Msg), - JID + end, + case Reason of + <<"">> -> <<"">>; + _ -> <<" (", Reason/binary, ") ">> + end]), + Msg = #message{type = normal, + body = xmpp:mk_text(Body), + sub_els = [XUser, XConference]}, + ejabberd_router:route(StateData#state.jid, JID, Msg), + JID; + #muc_user{invites = [_|_]} -> + Txt = <<"Multiple elements are not allowed">>, + {error, xmpp:err_forbidden(Txt, Lang)}; + _ -> + Txt = <<"No element found">>, + {error, xmpp:err_bad_request(Txt, Lang)} + end end. %% Handle a message sent to the room by a non-participant. %% If it is a decline, send to the inviter. %% Otherwise, an error message is sent to the sender. -handle_roommessage_from_nonparticipant(Packet, Lang, - StateData, From) -> - case catch check_decline_invitation(Packet) of - {true, Decline_data} -> - send_decline_invitation(Decline_data, - StateData#state.jid, From); - _ -> - send_error_only_occupants(Packet, Lang, - StateData#state.jid, From) +-spec handle_roommessage_from_nonparticipant(message(), state(), jid()) -> ok. +handle_roommessage_from_nonparticipant(Packet, StateData, From) -> + case xmpp:get_subtag(Packet, #muc_user{}) of + #muc_user{decline = #muc_decline{to = #jid{} = To} = Decline} = XUser -> + NewDecline = Decline#muc_decline{to = undefined, from = From}, + NewXUser = XUser#muc_user{decline = NewDecline}, + NewPacket = xmpp:set_subtag(Packet, NewXUser), + ejabberd_router:route(StateData#state.jid, To, NewPacket); + _ -> + ErrText = <<"Only occupants are allowed to send messages " + "to the conference">>, + Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)), + ejabberd_router:route_error(StateData#state.jid, From, Packet, Err) end. -%% Check in the packet is a decline. -%% If so, also returns the splitted packet. -%% This function must be catched, -%% because it crashes when the packet is not a decline message. -check_decline_invitation(Packet) -> - #xmlel{name = <<"message">>} = Packet, - XEl = fxml:get_subtag(Packet, <<"x">>), - (?NS_MUC_USER) = fxml:get_tag_attr_s(<<"xmlns">>, XEl), - DEl = fxml:get_subtag(XEl, <<"decline">>), - ToString = fxml:get_tag_attr_s(<<"to">>, DEl), - ToJID = jid:from_string(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 = - jid:to_string(jid:remove_resource(FromJID)), - #xmlel{name = <<"decline">>, attrs = DAttrs, - children = DEls} = - DEl, - DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs), - DAttrs3 = [{<<"from">>, FromString} | DAttrs2], - DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3, - children = DEls}, - 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(#xmlel{name = Name, attrs = Attrs, - children = SubEls}, - NewSubEl) -> - {_, NameNewSubEl, _, _} = NewSubEl, - SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl), - #xmlel{name = Name, attrs = Attrs, children = SubEls2}. - -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)), - ejabberd_router:route(RoomJID, From, Err). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Logging @@ -5055,6 +4313,7 @@ add_to_log(Type, Data, StateData) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Users number checking +-spec tab_add_online_user(jid(), state()) -> ok. tab_add_online_user(JID, StateData) -> {LUser, LServer, LResource} = jid:tolower(JID), US = {LUser, LServer}, @@ -5062,8 +4321,10 @@ tab_add_online_user(JID, StateData) -> Host = StateData#state.host, catch ets:insert(muc_online_users, #muc_online_users{us = US, resource = LResource, - room = Room, host = Host}). + room = Room, host = Host}), + ok. +-spec tab_remove_online_user(jid(), state()) -> ok. tab_remove_online_user(JID, StateData) -> {LUser, LServer, LResource} = jid:tolower(JID), US = {LUser, LServer}, @@ -5071,8 +4332,10 @@ tab_remove_online_user(JID, StateData) -> Host = StateData#state.host, catch ets:delete_object(muc_online_users, #muc_online_users{us = US, resource = LResource, - room = Room, host = Host}). + room = Room, host = Host}), + ok. +-spec tab_count_user(jid()) -> non_neg_integer(). tab_count_user(JID) -> {LUser, LServer, _} = jid:tolower(JID), US = {LUser, LServer}, @@ -5083,9 +4346,11 @@ tab_count_user(JID) -> _ -> 0 end. +-spec element_size(stanza()) -> non_neg_integer(). element_size(El) -> - byte_size(fxml:element_to_binary(El)). + byte_size(fxml:element_to_binary(xmpp:encode(El))). +-spec store_room(state()) -> ok. store_room(StateData) -> if (StateData#state.config)#config.persistent -> mod_muc:store_room(StateData#state.server_host, @@ -5095,6 +4360,7 @@ store_room(StateData) -> ok end. +-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok. send_wrapped(From, To, Packet, Node, State) -> LTo = jid:tolower(To), case ?DICT:find(LTo, State#state.users) of @@ -5112,27 +4378,26 @@ send_wrapped(From, To, Packet, Node, State) -> ejabberd_router:route(From, To, Packet) end. +-spec wrap(jid(), jid(), stanza(), binary()) -> message(). wrap(From, To, Packet, Node) -> - Pkt1 = jlib:replace_from_to(From, To, Packet), - Pkt2 = #xmlel{attrs = Attrs} = jlib:remove_attr(<<"xmlns">>, Pkt1), - Pkt3 = Pkt2#xmlel{attrs = [{<<"xmlns">>, <<"jabber:client">>}|Attrs]}, - Item = #xmlel{name = <<"item">>, - attrs = [{<<"id">>, randoms:get_string()}], - children = [Pkt3]}, - Items = #xmlel{name = <<"items">>, attrs = [{<<"node">>, Node}], - children = [Item]}, - Event = #xmlel{name = <<"event">>, - attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], - children = [Items]}, - #xmlel{name = <<"message">>, children = [Event]}. + El = xmpp:encode(xmpp:set_from_to(Packet, From, To)), + #message{ + sub_els = [#pubsub_event{ + items = [#pubsub_event_items{ + node = Node, + items = [#pubsub_event_item{ + id = randoms:get_string(), + xml_els = [El]}]}]}]}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Multicast +-spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok. send_multiple(From, Server, Users, Packet) -> JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)], ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). +-spec send_wrapped_multiple(jid(), [#user{}], stanza(), binary(), state()) -> ok. send_wrapped_multiple(From, Users, Packet, Node, State) -> lists:foreach( fun({_, #user{jid = To}}) -> @@ -5141,10 +4406,6 @@ send_wrapped_multiple(From, Users, Packet, Node, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Detect messange stanzas that don't have meaninful content - -has_body_or_subject(Packet) -> - [] /= lists:dropwhile(fun - (#xmlel{name = <<"body">>}) -> false; - (#xmlel{name = <<"subject">>}) -> false; - (_) -> true - end, Packet#xmlel.children). +-spec has_body_or_subject(message()) -> boolean(). +has_body_or_subject(#message{body = Body, subject = Subj}) -> + Body /= [] orelse Subj /= []. diff --git a/src/mod_private.erl b/src/mod_private.erl index 28d49bb3f..e6d0fd7cd 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -72,7 +72,7 @@ process_sm_iq(#iq{type = Type, lang = Lang, case filter_xmlels(Els0) of [] -> Txt = <<"No private data found in this query">>, - xmpp:make_error(IQ, xmpp:err_bad_format(Txt, Lang)); + xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); Data when Type == set -> set_data(LUser, LServer, Data), xmpp:make_iq_result(IQ); diff --git a/src/xmpp.erl b/src/xmpp.erl index f17eefa21..369fb90c5 100644 --- a/src/xmpp.erl +++ b/src/xmpp.erl @@ -16,22 +16,33 @@ set_type/2, set_to/2, set_from/2, set_id/2, set_lang/2, set_error/2, set_els/2, set_from_to/3, format_error/1, is_stanza/1, set_subtag/2, get_subtag/2, - remove_subtag/2, has_subtag/2, decode_els/1, pp/1, - get_name/1, get_text/1, mk_text/1, mk_text/2]). + remove_subtag/2, has_subtag/2, decode_els/1, decode_els/2, + pp/1, get_name/1, get_text/1, mk_text/1, mk_text/2]). %% XMPP errors -export([err_bad_request/0, err_bad_request/2, - err_bad_format/0, err_bad_format/2, - err_not_allowed/0, err_not_allowed/2, - err_conflict/0, err_conflict/2, - err_forbidden/0, err_forbidden/2, - err_not_acceptable/0, err_not_acceptable/2, - err_internal_server_error/0, err_internal_server_error/2, - err_service_unavailable/0, err_service_unavailable/2, - err_item_not_found/0, err_item_not_found/2, - err_jid_malformed/0, err_jid_malformed/2, - err_not_authorized/0, err_not_authorized/2, - err_feature_not_implemented/0, err_feature_not_implemented/2]). + err_conflict/0, err_conflict/2, + err_feature_not_implemented/0, err_feature_not_implemented/2, + err_forbidden/0, err_forbidden/2, + err_gone/0, err_gone/2, + err_internal_server_error/0, err_internal_server_error/2, + err_item_not_found/0, err_item_not_found/2, + err_jid_malformed/0, err_jid_malformed/2, + err_not_acceptable/0, err_not_acceptable/2, + err_not_allowed/0, err_not_allowed/2, + err_not_authorized/0, err_not_authorized/2, + err_payment_required/0, err_payment_required/2, + err_policy_violation/0, err_policy_violation/2, + err_recipient_unavailable/0, err_recipient_unavailable/2, + err_redirect/0, err_redirect/2, + err_registration_required/0, err_registration_required/2, + err_remote_server_not_found/0, err_remote_server_not_found/2, + err_remote_server_timeout/0, err_remote_server_timeout/2, + err_resource_constraint/0, err_resource_constraint/2, + err_service_unavailable/0, err_service_unavailable/2, + err_subscription_required/0, err_subscription_required/2, + err_undefined_condition/0, err_undefined_condition/2, + err_unexpected_request/0, err_unexpected_request/2]). %% XMPP stream errors -export([serr_bad_format/0, serr_bad_format/2, @@ -246,9 +257,12 @@ decode(Pkt, _Opts) -> (message()) -> message(); (presence()) -> presence(). decode_els(Stanza) -> + decode_els(Stanza, fun xmpp_codec:is_known_tag/1). + +decode_els(Stanza, MatchFun) -> Els = lists:map( fun(#xmlel{} = El) -> - case xmpp_codec:is_known_tag(El) of + case MatchFun(El) of true -> decode(El); false -> El end; @@ -287,10 +301,10 @@ set_subtag(Stanza, Tag) -> set_els(Stanza, NewEls). set_subtag([El|Els], Tag, TagName, XMLNS) -> - case {get_name(El), get_ns(El)} of - {TagName, XMLNS} -> + case match_tag(El, TagName, XMLNS) of + true -> [Tag|Els]; - _ -> + false -> [El|set_subtag(Els, Tag, TagName, XMLNS)] end; set_subtag([], Tag, _, _) -> @@ -304,14 +318,14 @@ get_subtag(Stanza, Tag) -> get_subtag(Els, TagName, XMLNS). get_subtag([El|Els], TagName, XMLNS) -> - case {get_name(El), get_ns(El)} of - {TagName, XMLNS} -> + case match_tag(El, TagName, XMLNS) of + true -> try decode(El) catch _:{xmpp_codec, _Why} -> get_subtag(Els, TagName, XMLNS) end; - _ -> + false -> get_subtag(Els, TagName, XMLNS) end; get_subtag([], _, _) -> @@ -328,10 +342,10 @@ remove_subtag(Stanza, Tag) -> set_els(Stanza, NewEls). remove_subtag([El|Els], TagName, XMLNS) -> - case {get_name(El), get_ns(El)} of - {TagName, XMLNS} -> + case match_tag(El, TagName, XMLNS) of + true -> remove_subtag(Els, TagName, XMLNS); - _ -> + false -> [El|remove_subtag(Els, TagName, XMLNS)] end; remove_subtag([], _, _) -> @@ -345,10 +359,10 @@ has_subtag(Stanza, Tag) -> has_subtag(Els, TagName, XMLNS). has_subtag([El|Els], TagName, XMLNS) -> - case {get_name(El), get_ns(El)} of - {TagName, XMLNS} -> + case match_tag(El, TagName, XMLNS) of + true -> true; - _ -> + false -> has_subtag(Els, TagName, XMLNS) end; has_subtag([], _, _) -> @@ -385,14 +399,6 @@ err_bad_request() -> err_bad_request(Text, Lang) -> err(modify, 'bad-request', 400, Text, Lang). --spec err_bad_format() -> error(). -err_bad_format() -> - err(modify, 'bad-format', 406). - --spec err_bad_format(binary(), binary() | undefined) -> error(). -err_bad_format(Text, Lang) -> - err(modify, 'bad-format', 406, Text, Lang). - -spec err_conflict() -> error(). err_conflict() -> err(cancel, 'conflict', 409). @@ -401,14 +407,6 @@ err_conflict() -> err_conflict(Text, Lang) -> err(cancel, 'conflict', 409, Text, Lang). --spec err_not_allowed() -> error(). -err_not_allowed() -> - err(cancel, 'not-allowed', 405). - --spec err_not_allowed(binary(), binary() | undefined) -> error(). -err_not_allowed(Text, Lang) -> - err(cancel, 'not-allowed', 405, Text, Lang). - -spec err_feature_not_implemented() -> error(). err_feature_not_implemented() -> err(cancel, 'feature-not-implemented', 501). @@ -417,14 +415,6 @@ err_feature_not_implemented() -> err_feature_not_implemented(Text, Lang) -> err(cancel, 'feature-not-implemented', 501, Text, Lang). --spec err_item_not_found() -> error(). -err_item_not_found() -> - err(cancel, 'item-not-found', 404). - --spec err_item_not_found(binary(), binary() | undefined) -> error(). -err_item_not_found(Text, Lang) -> - err(cancel, 'item-not-found', 404, Text, Lang). - -spec err_forbidden() -> error(). err_forbidden() -> err(auth, 'forbidden', 403). @@ -433,14 +423,18 @@ err_forbidden() -> err_forbidden(Text, Lang) -> err(auth, 'forbidden', 403, Text, Lang). --spec err_not_acceptable() -> error(). -err_not_acceptable() -> - err(modify, 'not-acceptable', 406). +%% RFC 6120 says error type SHOULD be "cancel". +%% RFC 3920 and XEP-0082 says it SHOULD be "modify". +-spec err_gone() -> error(). +err_gone() -> + err(modify, 'gone', 302). --spec err_not_acceptable(binary(), binary() | undefined) -> error(). -err_not_acceptable(Text, Lang) -> - err(modify, 'not-acceptable', 406, Text, Lang). +-spec err_gone(binary(), binary() | undefined) -> error(). +err_gone(Text, Lang) -> + err(modify, 'gone', 302, Text, Lang). +%% RFC 6120 sasy error type SHOULD be "cancel". +%% RFC 3920 and XEP-0082 says it SHOULD be "wait". -spec err_internal_server_error() -> error(). err_internal_server_error() -> err(wait, 'internal-server-error', 500). @@ -449,13 +443,13 @@ err_internal_server_error() -> err_internal_server_error(Text, Lang) -> err(wait, 'internal-server-error', 500, Text, Lang). --spec err_service_unavailable() -> error(). -err_service_unavailable() -> - err(cancel, 'service-unavailable', 503). +-spec err_item_not_found() -> error(). +err_item_not_found() -> + err(cancel, 'item-not-found', 404). --spec err_service_unavailable(binary(), binary() | undefined) -> error(). -err_service_unavailable(Text, Lang) -> - err(cancel, 'service-unavailable', 503, Text, Lang). +-spec err_item_not_found(binary(), binary() | undefined) -> error(). +err_item_not_found(Text, Lang) -> + err(cancel, 'item-not-found', 404, Text, Lang). -spec err_jid_malformed() -> error(). err_jid_malformed() -> @@ -465,6 +459,22 @@ err_jid_malformed() -> err_jid_malformed(Text, Lang) -> err(modify, 'jid-malformed', 400, Text, Lang). +-spec err_not_acceptable() -> error(). +err_not_acceptable() -> + err(modify, 'not-acceptable', 406). + +-spec err_not_acceptable(binary(), binary() | undefined) -> error(). +err_not_acceptable(Text, Lang) -> + err(modify, 'not-acceptable', 406, Text, Lang). + +-spec err_not_allowed() -> error(). +err_not_allowed() -> + err(cancel, 'not-allowed', 405). + +-spec err_not_allowed(binary(), binary() | undefined) -> error(). +err_not_allowed(Text, Lang) -> + err(cancel, 'not-allowed', 405, Text, Lang). + -spec err_not_authorized() -> error(). err_not_authorized() -> err(auth, 'not-authorized', 401). @@ -473,6 +483,108 @@ err_not_authorized() -> err_not_authorized(Text, Lang) -> err(auth, 'not-authorized', 401, Text, Lang). +-spec err_payment_required() -> error(). +err_payment_required() -> + err(auth, 'not-authorized', 402). + +-spec err_payment_required(binary(), binary() | undefined) -> error(). +err_payment_required(Text, Lang) -> + err(auth, 'not-authorized', 402, Text, Lang). + +%% is defined in neither RFC 3920 nor XEP-0086. +%% We choose '403' error code (as in ). +-spec err_policy_violation() -> error(). +err_policy_violation() -> + err(modify, 'policy-violation', 403). + +-spec err_policy_violation(binary(), binary() | undefined) -> error(). +err_policy_violation(Text, Lang) -> + err(modify, 'policy-violation', 403, Text, Lang). + +-spec err_recipient_unavailable() -> error(). +err_recipient_unavailable() -> + err(wait, 'recipient-unavailable', 404). + +-spec err_recipient_unavailable(binary(), binary() | undefined) -> error(). +err_recipient_unavailable(Text, Lang) -> + err(wait, 'recipient-unavailable', 404, Text, Lang). + +-spec err_redirect() -> error(). +err_redirect() -> + err(modify, 'redirect', 302). + +-spec err_redirect(binary(), binary() | undefined) -> error(). +err_redirect(Text, Lang) -> + err(modify, 'redirect', 302, Text, Lang). + +-spec err_registration_required() -> error(). +err_registration_required() -> + err(auth, 'registration-required', 407). + +-spec err_registration_required(binary(), binary() | undefined) -> error(). +err_registration_required(Text, Lang) -> + err(auth, 'registration-required', 407, Text, Lang). + +-spec err_remote_server_not_found() -> error(). +err_remote_server_not_found() -> + err(cancel, 'remote-server-not-found', 404). + +-spec err_remote_server_not_found(binary(), binary() | undefined) -> error(). +err_remote_server_not_found(Text, Lang) -> + err(cancel, 'remote-server-not-found', 404, Text, Lang). + +-spec err_remote_server_timeout() -> error(). +err_remote_server_timeout() -> + err(wait, 'remote-server-timeout', 504). + +-spec err_remote_server_timeout(binary(), binary() | undefined) -> error(). +err_remote_server_timeout(Text, Lang) -> + err(wait, 'remote-server-timeout', 504, Text, Lang). + +-spec err_resource_constraint() -> error(). +err_resource_constraint() -> + err(wait, 'resource-constraint', 500). + +-spec err_resource_constraint(binary(), binary() | undefined) -> error(). +err_resource_constraint(Text, Lang) -> + err(wait, 'resource-constraint', 500, Text, Lang). + +-spec err_service_unavailable() -> error(). +err_service_unavailable() -> + err(cancel, 'service-unavailable', 503). + +-spec err_service_unavailable(binary(), binary() | undefined) -> error(). +err_service_unavailable(Text, Lang) -> + err(cancel, 'service-unavailable', 503, Text, Lang). + +-spec err_subscription_required() -> error(). +err_subscription_required() -> + err(auth, 'subscription-required', 407). + +-spec err_subscription_required(binary(), binary() | undefined) -> error(). +err_subscription_required(Text, Lang) -> + err(auth, 'subscription-required', 407, Text, Lang). + +%% No error type is defined for . +%% We choose "modify" as it's used in RFC 6120 example. +-spec err_undefined_condition() -> error(). +err_undefined_condition() -> + err(modify, 'undefined-condition', 500). + +-spec err_undefined_condition(binary(), binary() | undefined) -> error(). +err_undefined_condition(Text, Lang) -> + err(modify, 'undefined-condition', 500, Text, Lang). + +%% RFC 6120 says error type SHOULD be "wait" or "modify". +%% RFC 3920 and XEP-0082 says it SHOULD be "wait". +-spec err_unexpected_request() -> error(). +err_unexpected_request() -> + err(wait, 'unexpected-request', 400). + +-spec err_unexpected_request(binary(), binary() | undefined) -> error(). +err_unexpected_request(Text, Lang) -> + err(wait, 'unexpected-request', 400, Text, Lang). + %%%=================================================================== %%% Functions to construct stream errors %%%=================================================================== @@ -712,3 +824,7 @@ add_ns(#xmlel{name = Name} = El) when Name == <<"message">>; El#xmlel{attrs = Attrs}; add_ns(El) -> El. + +-spec match_tag(xmlel() | xmpp_element(), binary(), binary()) -> boolean(). +match_tag(El, TagName, XMLNS) -> + get_name(El) == TagName andalso get_ns(El) == XMLNS. diff --git a/src/xmpp_codec.erl b/src/xmpp_codec.erl index f6e5f0f1a..c59c347f9 100644 --- a/src/xmpp_codec.erl +++ b/src/xmpp_codec.erl @@ -15,6 +15,21 @@ decode(_el) -> decode(_el, []). decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls = proplists:get_bool(ignore_els, Opts), case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"client-id">>, <<"urn:xmpp:sid:0">>} -> + decode_client_id(<<"urn:xmpp:sid:0">>, IgnoreEls, _el); + {<<"stanza-id">>, <<"urn:xmpp:sid:0">>} -> + decode_stanza_id(<<"urn:xmpp:sid:0">>, IgnoreEls, _el); + {<<"addresses">>, + <<"http://jabber.org/protocol/address">>} -> + decode_addresses(<<"http://jabber.org/protocol/address">>, + IgnoreEls, _el); + {<<"address">>, + <<"http://jabber.org/protocol/address">>} -> + decode_address(<<"http://jabber.org/protocol/address">>, + IgnoreEls, _el); + {<<"nick">>, <<"http://jabber.org/protocol/nick">>} -> + decode_nick(<<"http://jabber.org/protocol/nick">>, + IgnoreEls, _el); {<<"x">>, <<"jabber:x:expire">>} -> decode_expire(<<"jabber:x:expire">>, IgnoreEls, _el); {<<"x">>, <<"jabber:x:event">>} -> @@ -53,6 +68,9 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> {<<"instructions">>, <<"jabber:iq:search">>} -> decode_search_instructions(<<"jabber:iq:search">>, IgnoreEls, _el); + {<<"no-permanent-storage">>, <<"urn:xmpp:hints">>} -> + decode_hint_no_permanent_storage(<<"urn:xmpp:hints">>, + IgnoreEls, _el); {<<"no-permanent-store">>, <<"urn:xmpp:hints">>} -> decode_hint_no_permanent_store(<<"urn:xmpp:hints">>, IgnoreEls, _el); @@ -160,12 +178,24 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} -> decode_mam_prefs(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"always">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_always(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"always">>, <<"urn:xmpp:mam:1">>} -> + decode_mam_always(<<"urn:xmpp:mam:1">>, IgnoreEls, _el); {<<"always">>, <<"urn:xmpp:mam:tmp">>} -> decode_mam_always(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"never">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_never(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"never">>, <<"urn:xmpp:mam:1">>} -> + decode_mam_never(<<"urn:xmpp:mam:1">>, IgnoreEls, _el); {<<"never">>, <<"urn:xmpp:mam:tmp">>} -> decode_mam_never(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"jid">>, <<"urn:xmpp:mam:0">>} -> + decode_mam_jid(<<"urn:xmpp:mam:0">>, IgnoreEls, _el); + {<<"jid">>, <<"urn:xmpp:mam:1">>} -> + decode_mam_jid(<<"urn:xmpp:mam:1">>, IgnoreEls, _el); {<<"jid">>, <<"urn:xmpp:mam:tmp">>} -> decode_mam_jid(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); {<<"result">>, <<"urn:xmpp:mam:0">>} -> @@ -185,6 +215,9 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> {<<"query">>, <<"urn:xmpp:mam:tmp">>} -> decode_mam_query(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); + {<<"withtext">>, <<"urn:xmpp:mam:tmp">>} -> + decode_mam_withtext(<<"urn:xmpp:mam:tmp">>, IgnoreEls, + _el); {<<"with">>, <<"urn:xmpp:mam:tmp">>} -> decode_mam_with(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el); {<<"end">>, <<"urn:xmpp:mam:tmp">>} -> @@ -216,6 +249,28 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> {<<"after">>, <<"http://jabber.org/protocol/rsm">>} -> decode_rsm_after(<<"http://jabber.org/protocol/rsm">>, IgnoreEls, _el); + {<<"unsubscribe">>, <<"urn:xmpp:mucsub:0">>} -> + decode_muc_unsubscribe(<<"urn:xmpp:mucsub:0">>, + IgnoreEls, _el); + {<<"subscribe">>, <<"urn:xmpp:mucsub:0">>} -> + decode_muc_subscribe(<<"urn:xmpp:mucsub:0">>, IgnoreEls, + _el); + {<<"event">>, <<"urn:xmpp:mucsub:0">>} -> + decode_muc_subscribe_event(<<"urn:xmpp:mucsub:0">>, + IgnoreEls, _el); + {<<"subscriptions">>, <<"urn:xmpp:mucsub:0">>} -> + decode_muc_subscriptions(<<"urn:xmpp:mucsub:0">>, + IgnoreEls, _el); + {<<"subscription">>, <<"urn:xmpp:mucsub:0">>} -> + decode_muc_subscription(<<"urn:xmpp:mucsub:0">>, + IgnoreEls, _el); + {<<"x">>, <<"jabber:x:conference">>} -> + decode_x_conference(<<"jabber:x:conference">>, + IgnoreEls, _el); + {<<"unique">>, + <<"http://jabber.org/protocol/muc#unique">>} -> + decode_muc_unique(<<"http://jabber.org/protocol/muc#unique">>, + IgnoreEls, _el); {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> decode_muc(<<"http://jabber.org/protocol/muc">>, IgnoreEls, _el); @@ -223,10 +278,6 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> <<"http://jabber.org/protocol/muc#admin">>} -> decode_muc_admin(<<"http://jabber.org/protocol/muc#admin">>, IgnoreEls, _el); - {<<"reason">>, - <<"http://jabber.org/protocol/muc#admin">>} -> - decode_muc_admin_reason(<<"http://jabber.org/protocol/muc#admin">>, - IgnoreEls, _el); {<<"continue">>, <<"http://jabber.org/protocol/muc#admin">>} -> decode_muc_admin_continue(<<"http://jabber.org/protocol/muc#admin">>, @@ -239,22 +290,26 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> <<"http://jabber.org/protocol/muc#admin">>} -> decode_muc_admin_item(<<"http://jabber.org/protocol/muc#admin">>, IgnoreEls, _el); + {<<"item">>, + <<"http://jabber.org/protocol/muc#owner">>} -> + decode_muc_owner_item(<<"http://jabber.org/protocol/muc#owner">>, + IgnoreEls, _el); {<<"query">>, <<"http://jabber.org/protocol/muc#owner">>} -> decode_muc_owner(<<"http://jabber.org/protocol/muc#owner">>, IgnoreEls, _el); - {<<"destroy">>, - <<"http://jabber.org/protocol/muc#owner">>} -> - decode_muc_owner_destroy(<<"http://jabber.org/protocol/muc#owner">>, - IgnoreEls, _el); - {<<"reason">>, - <<"http://jabber.org/protocol/muc#owner">>} -> - decode_muc_owner_reason(<<"http://jabber.org/protocol/muc#owner">>, - IgnoreEls, _el); {<<"password">>, <<"http://jabber.org/protocol/muc#owner">>} -> - decode_muc_owner_password(<<"http://jabber.org/protocol/muc#owner">>, - IgnoreEls, _el); + decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>, + IgnoreEls, _el); + {<<"password">>, + <<"http://jabber.org/protocol/muc#user">>} -> + decode_muc_password(<<"http://jabber.org/protocol/muc#user">>, + IgnoreEls, _el); + {<<"password">>, + <<"http://jabber.org/protocol/muc">>} -> + decode_muc_password(<<"http://jabber.org/protocol/muc">>, + IgnoreEls, _el); {<<"x">>, <<"http://jabber.org/protocol/muc#user">>} -> decode_muc_user(<<"http://jabber.org/protocol/muc#user">>, IgnoreEls, _el); @@ -280,16 +335,28 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls, _el); {<<"destroy">>, <<"http://jabber.org/protocol/muc#user">>} -> - decode_muc_user_destroy(<<"http://jabber.org/protocol/muc#user">>, - IgnoreEls, _el); + decode_muc_destroy(<<"http://jabber.org/protocol/muc#user">>, + IgnoreEls, _el); + {<<"destroy">>, + <<"http://jabber.org/protocol/muc#owner">>} -> + decode_muc_destroy(<<"http://jabber.org/protocol/muc#owner">>, + IgnoreEls, _el); {<<"decline">>, <<"http://jabber.org/protocol/muc#user">>} -> decode_muc_user_decline(<<"http://jabber.org/protocol/muc#user">>, IgnoreEls, _el); {<<"reason">>, <<"http://jabber.org/protocol/muc#user">>} -> - decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>, - IgnoreEls, _el); + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + IgnoreEls, _el); + {<<"reason">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + IgnoreEls, _el); + {<<"reason">>, + <<"http://jabber.org/protocol/muc#owner">>} -> + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + IgnoreEls, _el); {<<"history">>, <<"http://jabber.org/protocol/muc">>} -> decode_muc_history(<<"http://jabber.org/protocol/muc">>, IgnoreEls, _el); @@ -1002,6 +1069,10 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} -> decode_error_policy_violation(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>, IgnoreEls, _el); + {<<"payment-required">>, + <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} -> + decode_error_payment_required(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>, + IgnoreEls, _el); {<<"not-authorized">>, <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} -> decode_error_not_authorized(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>, @@ -1183,6 +1254,16 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"client-id">>, <<"urn:xmpp:sid:0">>} -> true; + {<<"stanza-id">>, <<"urn:xmpp:sid:0">>} -> true; + {<<"addresses">>, + <<"http://jabber.org/protocol/address">>} -> + true; + {<<"address">>, + <<"http://jabber.org/protocol/address">>} -> + true; + {<<"nick">>, <<"http://jabber.org/protocol/nick">>} -> + true; {<<"x">>, <<"jabber:x:expire">>} -> true; {<<"x">>, <<"jabber:x:event">>} -> true; {<<"id">>, <<"jabber:x:event">>} -> true; @@ -1197,6 +1278,8 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"last">>, <<"jabber:iq:search">>} -> true; {<<"first">>, <<"jabber:iq:search">>} -> true; {<<"instructions">>, <<"jabber:iq:search">>} -> true; + {<<"no-permanent-storage">>, <<"urn:xmpp:hints">>} -> + true; {<<"no-permanent-store">>, <<"urn:xmpp:hints">>} -> true; {<<"store">>, <<"urn:xmpp:hints">>} -> true; @@ -1248,8 +1331,14 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"prefs">>, <<"urn:xmpp:mam:0">>} -> true; {<<"prefs">>, <<"urn:xmpp:mam:1">>} -> true; {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"always">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"always">>, <<"urn:xmpp:mam:1">>} -> true; {<<"always">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"never">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"never">>, <<"urn:xmpp:mam:1">>} -> true; {<<"never">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"jid">>, <<"urn:xmpp:mam:0">>} -> true; + {<<"jid">>, <<"urn:xmpp:mam:1">>} -> true; {<<"jid">>, <<"urn:xmpp:mam:tmp">>} -> true; {<<"result">>, <<"urn:xmpp:mam:0">>} -> true; {<<"result">>, <<"urn:xmpp:mam:1">>} -> true; @@ -1258,6 +1347,7 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"query">>, <<"urn:xmpp:mam:0">>} -> true; {<<"query">>, <<"urn:xmpp:mam:1">>} -> true; {<<"query">>, <<"urn:xmpp:mam:tmp">>} -> true; + {<<"withtext">>, <<"urn:xmpp:mam:tmp">>} -> true; {<<"with">>, <<"urn:xmpp:mam:tmp">>} -> true; {<<"end">>, <<"urn:xmpp:mam:tmp">>} -> true; {<<"start">>, <<"urn:xmpp:mam:tmp">>} -> true; @@ -1277,13 +1367,19 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> true; {<<"after">>, <<"http://jabber.org/protocol/rsm">>} -> true; + {<<"unsubscribe">>, <<"urn:xmpp:mucsub:0">>} -> true; + {<<"subscribe">>, <<"urn:xmpp:mucsub:0">>} -> true; + {<<"event">>, <<"urn:xmpp:mucsub:0">>} -> true; + {<<"subscriptions">>, <<"urn:xmpp:mucsub:0">>} -> true; + {<<"subscription">>, <<"urn:xmpp:mucsub:0">>} -> true; + {<<"x">>, <<"jabber:x:conference">>} -> true; + {<<"unique">>, + <<"http://jabber.org/protocol/muc#unique">>} -> + true; {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> true; {<<"query">>, <<"http://jabber.org/protocol/muc#admin">>} -> true; - {<<"reason">>, - <<"http://jabber.org/protocol/muc#admin">>} -> - true; {<<"continue">>, <<"http://jabber.org/protocol/muc#admin">>} -> true; @@ -1293,18 +1389,21 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"item">>, <<"http://jabber.org/protocol/muc#admin">>} -> true; + {<<"item">>, + <<"http://jabber.org/protocol/muc#owner">>} -> + true; {<<"query">>, <<"http://jabber.org/protocol/muc#owner">>} -> true; - {<<"destroy">>, - <<"http://jabber.org/protocol/muc#owner">>} -> - true; - {<<"reason">>, - <<"http://jabber.org/protocol/muc#owner">>} -> - true; {<<"password">>, <<"http://jabber.org/protocol/muc#owner">>} -> true; + {<<"password">>, + <<"http://jabber.org/protocol/muc#user">>} -> + true; + {<<"password">>, + <<"http://jabber.org/protocol/muc">>} -> + true; {<<"x">>, <<"http://jabber.org/protocol/muc#user">>} -> true; {<<"item">>, @@ -1325,12 +1424,21 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"destroy">>, <<"http://jabber.org/protocol/muc#user">>} -> true; + {<<"destroy">>, + <<"http://jabber.org/protocol/muc#owner">>} -> + true; {<<"decline">>, <<"http://jabber.org/protocol/muc#user">>} -> true; {<<"reason">>, <<"http://jabber.org/protocol/muc#user">>} -> true; + {<<"reason">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + true; + {<<"reason">>, + <<"http://jabber.org/protocol/muc#owner">>} -> + true; {<<"history">>, <<"http://jabber.org/protocol/muc">>} -> true; {<<"query">>, @@ -1773,6 +1881,9 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> {<<"policy-violation">>, <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} -> true; + {<<"payment-required">>, + <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} -> + true; {<<"not-authorized">>, <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} -> true; @@ -1914,7 +2025,7 @@ encode({disco_item, _, _, _} = Item) -> encode_disco_item(Item, [{<<"xmlns">>, <<"http://jabber.org/protocol/disco#items">>}]); -encode({disco_items, _, _} = Query) -> +encode({disco_items, _, _, _} = Query) -> encode_disco_items(Query, [{<<"xmlns">>, <<"http://jabber.org/protocol/disco#items">>}]); @@ -2107,6 +2218,9 @@ encode({vcard_temp, _, _, _, _, _, _, _, _, _, _, _, _, encode({vcard_xupdate, undefined, _} = X) -> encode_vcard_xupdate(X, [{<<"xmlns">>, <<"vcard-temp:x:update">>}]); +encode({xdata_option, _, _} = Option) -> + encode_xdata_field_option(Option, + [{<<"xmlns">>, <<"jabber:x:data">>}]); encode({xdata_field, _, _, _, _, _, _, _} = Field) -> encode_xdata_field(Field, [{<<"xmlns">>, <<"jabber:x:data">>}]); @@ -2206,11 +2320,9 @@ encode({muc_decline, _, _, _} = Decline) -> encode_muc_user_decline(Decline, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc#user">>}]); -encode({muc_user_destroy, _, _} = Destroy) -> - encode_muc_user_destroy(Destroy, - [{<<"xmlns">>, - <<"http://jabber.org/protocol/muc#user">>}]); -encode({muc_invite, _, _, _} = Invite) -> +encode({muc_destroy, _, _, _, _} = Destroy) -> + encode_muc_destroy(Destroy, []); +encode({muc_invite, _, _, _, _} = Invite) -> encode_muc_user_invite(Invite, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc#user">>}]); @@ -2218,11 +2330,7 @@ encode({muc_user, _, _, _, _, _, _} = X) -> encode_muc_user(X, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc#user">>}]); -encode({muc_owner_destroy, _, _, _} = Destroy) -> - encode_muc_owner_destroy(Destroy, - [{<<"xmlns">>, - <<"http://jabber.org/protocol/muc#owner">>}]); -encode({muc_owner, _, _} = Query) -> +encode({muc_owner, _, _, _} = Query) -> encode_muc_owner(Query, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc#owner">>}]); @@ -2237,13 +2345,29 @@ encode({muc_admin, _} = Query) -> encode({muc, _, _} = X) -> encode_muc(X, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}]); +encode({muc_unique, _} = Unique) -> + encode_muc_unique(Unique, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/muc#unique">>}]); +encode({x_conference, _, _, _, _, _} = X) -> + encode_x_conference(X, + [{<<"xmlns">>, <<"jabber:x:conference">>}]); +encode({muc_subscriptions, _} = Subscriptions) -> + encode_muc_subscriptions(Subscriptions, + [{<<"xmlns">>, <<"urn:xmpp:mucsub:0">>}]); +encode({muc_subscribe, _, _} = Subscribe) -> + encode_muc_subscribe(Subscribe, + [{<<"xmlns">>, <<"urn:xmpp:mucsub:0">>}]); +encode({muc_unsubscribe} = Unsubscribe) -> + encode_muc_unsubscribe(Unsubscribe, + [{<<"xmlns">>, <<"urn:xmpp:mucsub:0">>}]); encode({rsm_first, _, _} = First) -> encode_rsm_first(First, [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]); encode({rsm_set, _, _, _, _, _, _, _} = Set) -> encode_rsm_set(Set, [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]); -encode({mam_query, _, _, _, _, _, _, _} = Query) -> +encode({mam_query, _, _, _, _, _, _, _, _} = Query) -> encode_mam_query(Query, []); encode({mam_archived, _, _} = Archived) -> encode_mam_archived(Archived, @@ -2328,6 +2452,10 @@ encode({hint, 'no-permanent-store'} = No_permanent_store) -> encode_hint_no_permanent_store(No_permanent_store, [{<<"xmlns">>, <<"urn:xmpp:hints">>}]); +encode({hint, 'no-permanent-storage'} = + No_permanent_storage) -> + encode_hint_no_permanent_storage(No_permanent_storage, + [{<<"xmlns">>, <<"urn:xmpp:hints">>}]); encode({search_item, _, _, _, _, _} = Item) -> encode_search_item(Item, [{<<"xmlns">>, <<"jabber:iq:search">>}]); @@ -2338,7 +2466,24 @@ encode({xevent, _, _, _, _, _} = X) -> encode_xevent(X, [{<<"xmlns">>, <<"jabber:x:event">>}]); encode({expire, _, _} = X) -> encode_expire(X, - [{<<"xmlns">>, <<"jabber:x:expire">>}]). + [{<<"xmlns">>, <<"jabber:x:expire">>}]); +encode({nick, _} = Nick) -> + encode_nick(Nick, + [{<<"xmlns">>, <<"http://jabber.org/protocol/nick">>}]); +encode({address, _, _, _, _, _} = Address) -> + encode_address(Address, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/address">>}]); +encode({addresses, _} = Addresses) -> + encode_addresses(Addresses, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/address">>}]); +encode({stanza_id, _, _} = Stanza_id) -> + encode_stanza_id(Stanza_id, + [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]); +encode({client_id, _} = Client_id) -> + encode_client_id(Client_id, + [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]). get_name({last, _, _}) -> <<"query">>; get_name({version, _, _, _}) -> <<"query">>; @@ -2355,7 +2500,7 @@ get_name({block_list, _}) -> <<"blocklist">>; get_name({identity, _, _, _, _}) -> <<"identity">>; get_name({disco_info, _, _, _, _}) -> <<"query">>; get_name({disco_item, _, _, _}) -> <<"item">>; -get_name({disco_items, _, _}) -> <<"query">>; +get_name({disco_items, _, _, _}) -> <<"query">>; get_name({private, _}) -> <<"query">>; get_name({bookmark_conference, _, _, _, _, _}) -> <<"conference">>; @@ -2424,6 +2569,7 @@ get_name({vcard_temp, _, _, _, _, _, _, _, _, _, _, _, _}) -> <<"vCard">>; get_name({vcard_xupdate, undefined, _}) -> <<"x">>; +get_name({xdata_option, _, _}) -> <<"option">>; get_name({xdata_field, _, _, _, _, _, _, _}) -> <<"field">>; get_name({xdata, _, _, _, _, _, _}) -> <<"x">>; @@ -2456,18 +2602,22 @@ get_name({bytestreams, _, _, _, _, _, _}) -> <<"query">>; get_name({muc_history, _, _, _, _}) -> <<"history">>; get_name({muc_decline, _, _, _}) -> <<"decline">>; -get_name({muc_user_destroy, _, _}) -> <<"destroy">>; -get_name({muc_invite, _, _, _}) -> <<"invite">>; +get_name({muc_destroy, _, _, _, _}) -> <<"destroy">>; +get_name({muc_invite, _, _, _, _}) -> <<"invite">>; get_name({muc_user, _, _, _, _, _, _}) -> <<"x">>; -get_name({muc_owner_destroy, _, _, _}) -> <<"destroy">>; -get_name({muc_owner, _, _}) -> <<"query">>; +get_name({muc_owner, _, _, _}) -> <<"query">>; get_name({muc_item, _, _, _, _, _, _, _}) -> <<"item">>; get_name({muc_actor, _, _}) -> <<"actor">>; get_name({muc_admin, _}) -> <<"query">>; get_name({muc, _, _}) -> <<"x">>; +get_name({muc_unique, _}) -> <<"unique">>; +get_name({x_conference, _, _, _, _, _}) -> <<"x">>; +get_name({muc_subscriptions, _}) -> <<"subscriptions">>; +get_name({muc_subscribe, _, _}) -> <<"subscribe">>; +get_name({muc_unsubscribe}) -> <<"unsubscribe">>; get_name({rsm_first, _, _}) -> <<"first">>; get_name({rsm_set, _, _, _, _, _, _, _}) -> <<"set">>; -get_name({mam_query, _, _, _, _, _, _, _}) -> +get_name({mam_query, _, _, _, _, _, _, _, _}) -> <<"query">>; get_name({mam_archived, _, _}) -> <<"archived">>; get_name({mam_result, _, _, _, _}) -> <<"result">>; @@ -2501,10 +2651,17 @@ get_name({hint, 'no-storage'}) -> <<"no-storage">>; get_name({hint, store}) -> <<"store">>; get_name({hint, 'no-permanent-store'}) -> <<"no-permanent-store">>; +get_name({hint, 'no-permanent-storage'}) -> + <<"no-permanent-storage">>; get_name({search_item, _, _, _, _, _}) -> <<"item">>; get_name({search, _, _, _, _, _, _, _}) -> <<"query">>; get_name({xevent, _, _, _, _, _}) -> <<"x">>; -get_name({expire, _, _}) -> <<"x">>. +get_name({expire, _, _}) -> <<"x">>; +get_name({nick, _}) -> <<"nick">>; +get_name({address, _, _, _, _, _}) -> <<"address">>; +get_name({addresses, _}) -> <<"addresses">>; +get_name({stanza_id, _, _}) -> <<"stanza-id">>; +get_name({client_id, _}) -> <<"client-id">>. get_ns({last, _, _}) -> <<"jabber:iq:last">>; get_ns({version, _, _, _}) -> <<"jabber:iq:version">>; @@ -2527,7 +2684,7 @@ get_ns({disco_info, _, _, _, _}) -> <<"http://jabber.org/protocol/disco#info">>; get_ns({disco_item, _, _, _}) -> <<"http://jabber.org/protocol/disco#items">>; -get_ns({disco_items, _, _}) -> +get_ns({disco_items, _, _, _}) -> <<"http://jabber.org/protocol/disco#items">>; get_ns({private, _}) -> <<"jabber:iq:private">>; get_ns({bookmark_conference, _, _, _, _, _}) -> @@ -2624,6 +2781,7 @@ get_ns({vcard_temp, _, _, _, _, _, _, _, _, _, _, _, _, <<"vcard-temp">>; get_ns({vcard_xupdate, undefined, _}) -> <<"vcard-temp:x:update">>; +get_ns({xdata_option, _, _}) -> <<"jabber:x:data">>; get_ns({xdata_field, _, _, _, _, _, _, _}) -> <<"jabber:x:data">>; get_ns({xdata, _, _, _, _, _, _}) -> @@ -2675,25 +2833,32 @@ get_ns({muc_history, _, _, _, _}) -> <<"http://jabber.org/protocol/muc">>; get_ns({muc_decline, _, _, _}) -> <<"http://jabber.org/protocol/muc#user">>; -get_ns({muc_user_destroy, _, _}) -> - <<"http://jabber.org/protocol/muc#user">>; -get_ns({muc_invite, _, _, _}) -> +get_ns({muc_destroy, Xmlns, _, _, _}) -> Xmlns; +get_ns({muc_invite, _, _, _, _}) -> <<"http://jabber.org/protocol/muc#user">>; get_ns({muc_user, _, _, _, _, _, _}) -> <<"http://jabber.org/protocol/muc#user">>; -get_ns({muc_owner_destroy, _, _, _}) -> - <<"http://jabber.org/protocol/muc#owner">>; -get_ns({muc_owner, _, _}) -> +get_ns({muc_owner, _, _, _}) -> <<"http://jabber.org/protocol/muc#owner">>; get_ns({muc_admin, _}) -> <<"http://jabber.org/protocol/muc#admin">>; get_ns({muc, _, _}) -> <<"http://jabber.org/protocol/muc">>; +get_ns({muc_unique, _}) -> + <<"http://jabber.org/protocol/muc#unique">>; +get_ns({x_conference, _, _, _, _, _}) -> + <<"jabber:x:conference">>; +get_ns({muc_subscriptions, _}) -> + <<"urn:xmpp:mucsub:0">>; +get_ns({muc_subscribe, _, _}) -> + <<"urn:xmpp:mucsub:0">>; +get_ns({muc_unsubscribe}) -> <<"urn:xmpp:mucsub:0">>; get_ns({rsm_first, _, _}) -> <<"http://jabber.org/protocol/rsm">>; get_ns({rsm_set, _, _, _, _, _, _, _}) -> <<"http://jabber.org/protocol/rsm">>; -get_ns({mam_query, Xmlns, _, _, _, _, _, _}) -> Xmlns; +get_ns({mam_query, Xmlns, _, _, _, _, _, _, _}) -> + Xmlns; get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>; get_ns({mam_result, Xmlns, _, _, _}) -> Xmlns; get_ns({mam_prefs, Xmlns, _, _, _}) -> Xmlns; @@ -2729,12 +2894,22 @@ get_ns({hint, 'no-storage'}) -> <<"urn:xmpp:hints">>; get_ns({hint, store}) -> <<"urn:xmpp:hints">>; get_ns({hint, 'no-permanent-store'}) -> <<"urn:xmpp:hints">>; +get_ns({hint, 'no-permanent-storage'}) -> + <<"urn:xmpp:hints">>; get_ns({search_item, _, _, _, _, _}) -> <<"jabber:iq:search">>; get_ns({search, _, _, _, _, _, _, _}) -> <<"jabber:iq:search">>; get_ns({xevent, _, _, _, _, _}) -> <<"jabber:x:event">>; -get_ns({expire, _, _}) -> <<"jabber:x:expire">>. +get_ns({expire, _, _}) -> <<"jabber:x:expire">>; +get_ns({nick, _}) -> + <<"http://jabber.org/protocol/nick">>; +get_ns({address, _, _, _, _, _}) -> + <<"http://jabber.org/protocol/address">>; +get_ns({addresses, _}) -> + <<"http://jabber.org/protocol/address">>; +get_ns({stanza_id, _, _}) -> <<"urn:xmpp:sid:0">>; +get_ns({client_id, _}) -> <<"urn:xmpp:sid:0">>. dec_int(Val) -> dec_int(Val, infinity, infinity). @@ -2801,7 +2976,7 @@ pp(identity, 4) -> [category, type, lang, name]; pp(disco_info, 4) -> [node, identities, features, xdata]; pp(disco_item, 3) -> [jid, name, node]; -pp(disco_items, 2) -> [node, items]; +pp(disco_items, 3) -> [node, items, rsm]; pp(private, 1) -> [xml_els]; pp(bookmark_conference, 5) -> [name, jid, autojoin, nick, password]; @@ -2876,6 +3051,7 @@ pp(vcard_temp, 29) -> org, categories, note, prodid, rev, sort_string, sound, uid, url, class, key, desc]; pp(vcard_xupdate, 2) -> [us, hash]; +pp(xdata_option, 2) -> [label, value]; pp(xdata_field, 7) -> [label, type, var, required, desc, values, options]; pp(xdata, 6) -> @@ -2905,23 +3081,28 @@ pp(bytestreams, 6) -> pp(muc_history, 4) -> [maxchars, maxstanzas, seconds, since]; pp(muc_decline, 3) -> [reason, from, to]; -pp(muc_user_destroy, 2) -> [reason, jid]; -pp(muc_invite, 3) -> [reason, from, to]; +pp(muc_destroy, 4) -> [xmlns, jid, reason, password]; +pp(muc_invite, 4) -> [reason, from, to, continue]; pp(muc_user, 6) -> [decline, destroy, invites, items, status_codes, password]; -pp(muc_owner_destroy, 3) -> [jid, reason, password]; -pp(muc_owner, 2) -> [destroy, config]; +pp(muc_owner, 3) -> [destroy, config, items]; pp(muc_item, 7) -> [actor, continue, reason, affiliation, role, jid, nick]; pp(muc_actor, 2) -> [jid, nick]; pp(muc_admin, 1) -> [items]; pp(muc, 2) -> [history, password]; +pp(muc_unique, 1) -> [name]; +pp(x_conference, 5) -> + [jid, password, reason, continue, thread]; +pp(muc_subscriptions, 1) -> [list]; +pp(muc_subscribe, 2) -> [nick, events]; +pp(muc_unsubscribe, 0) -> []; pp(rsm_first, 2) -> [index, data]; pp(rsm_set, 7) -> ['after', before, count, first, index, last, max]; -pp(mam_query, 7) -> - [xmlns, id, start, 'end', with, rsm, xdata]; +pp(mam_query, 8) -> + [xmlns, id, start, 'end', with, withtext, rsm, xdata]; pp(mam_archived, 2) -> [by, id]; pp(mam_result, 4) -> [xmlns, queryid, id, sub_els]; pp(mam_prefs, 4) -> [xmlns, default, always, never]; @@ -2954,6 +3135,11 @@ pp(search, 7) -> pp(xevent, 5) -> [offline, delivered, displayed, composing, id]; pp(expire, 2) -> [seconds, stored]; +pp(nick, 1) -> [name]; +pp(address, 5) -> [type, jid, desc, node, delivered]; +pp(addresses, 1) -> [list]; +pp(stanza_id, 2) -> [by, id]; +pp(client_id, 1) -> [id]; pp(_, _) -> no. join([], _Sep) -> <<>>; @@ -3000,6 +3186,274 @@ dec_tzo(Val) -> M = jlib:binary_to_integer(M1), if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end. +decode_client_id(__TopXMLNS, __IgnoreEls, + {xmlel, <<"client-id">>, _attrs, _els}) -> + Id = decode_client_id_attrs(__TopXMLNS, _attrs, + undefined), + {client_id, Id}. + +decode_client_id_attrs(__TopXMLNS, + [{<<"id">>, _val} | _attrs], _Id) -> + decode_client_id_attrs(__TopXMLNS, _attrs, _val); +decode_client_id_attrs(__TopXMLNS, [_ | _attrs], Id) -> + decode_client_id_attrs(__TopXMLNS, _attrs, Id); +decode_client_id_attrs(__TopXMLNS, [], Id) -> + decode_client_id_attr_id(__TopXMLNS, Id). + +encode_client_id({client_id, Id}, _xmlns_attrs) -> + _els = [], + _attrs = encode_client_id_attr_id(Id, _xmlns_attrs), + {xmlel, <<"client-id">>, _attrs, _els}. + +decode_client_id_attr_id(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"id">>, <<"client-id">>, __TopXMLNS}}); +decode_client_id_attr_id(__TopXMLNS, _val) -> _val. + +encode_client_id_attr_id(_val, _acc) -> + [{<<"id">>, _val} | _acc]. + +decode_stanza_id(__TopXMLNS, __IgnoreEls, + {xmlel, <<"stanza-id">>, _attrs, _els}) -> + {Id, By} = decode_stanza_id_attrs(__TopXMLNS, _attrs, + undefined, undefined), + {stanza_id, By, Id}. + +decode_stanza_id_attrs(__TopXMLNS, + [{<<"id">>, _val} | _attrs], _Id, By) -> + decode_stanza_id_attrs(__TopXMLNS, _attrs, _val, By); +decode_stanza_id_attrs(__TopXMLNS, + [{<<"by">>, _val} | _attrs], Id, _By) -> + decode_stanza_id_attrs(__TopXMLNS, _attrs, Id, _val); +decode_stanza_id_attrs(__TopXMLNS, [_ | _attrs], Id, + By) -> + decode_stanza_id_attrs(__TopXMLNS, _attrs, Id, By); +decode_stanza_id_attrs(__TopXMLNS, [], Id, By) -> + {decode_stanza_id_attr_id(__TopXMLNS, Id), + decode_stanza_id_attr_by(__TopXMLNS, By)}. + +encode_stanza_id({stanza_id, By, Id}, _xmlns_attrs) -> + _els = [], + _attrs = encode_stanza_id_attr_by(By, + encode_stanza_id_attr_id(Id, + _xmlns_attrs)), + {xmlel, <<"stanza-id">>, _attrs, _els}. + +decode_stanza_id_attr_id(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"id">>, <<"stanza-id">>, __TopXMLNS}}); +decode_stanza_id_attr_id(__TopXMLNS, _val) -> _val. + +encode_stanza_id_attr_id(_val, _acc) -> + [{<<"id">>, _val} | _acc]. + +decode_stanza_id_attr_by(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"by">>, <<"stanza-id">>, __TopXMLNS}}); +decode_stanza_id_attr_by(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"by">>, <<"stanza-id">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_stanza_id_attr_by(_val, _acc) -> + [{<<"by">>, enc_jid(_val)} | _acc]. + +decode_addresses(__TopXMLNS, __IgnoreEls, + {xmlel, <<"addresses">>, _attrs, _els}) -> + List = decode_addresses_els(__TopXMLNS, __IgnoreEls, + _els, []), + {addresses, List}. + +decode_addresses_els(__TopXMLNS, __IgnoreEls, [], + List) -> + lists:reverse(List); +decode_addresses_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"address">>, _attrs, _} = _el | _els], + List) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/address">> -> + decode_addresses_els(__TopXMLNS, __IgnoreEls, _els, + [decode_address(__TopXMLNS, __IgnoreEls, _el) + | List]); + <<"http://jabber.org/protocol/address">> -> + decode_addresses_els(__TopXMLNS, __IgnoreEls, _els, + [decode_address(<<"http://jabber.org/protocol/address">>, + __IgnoreEls, _el) + | List]); + _ -> + decode_addresses_els(__TopXMLNS, __IgnoreEls, _els, + List) + end; +decode_addresses_els(__TopXMLNS, __IgnoreEls, + [_ | _els], List) -> + decode_addresses_els(__TopXMLNS, __IgnoreEls, _els, + List). + +encode_addresses({addresses, List}, _xmlns_attrs) -> + _els = lists:reverse('encode_addresses_$list'(List, + [])), + _attrs = _xmlns_attrs, + {xmlel, <<"addresses">>, _attrs, _els}. + +'encode_addresses_$list'([], _acc) -> _acc; +'encode_addresses_$list'([List | _els], _acc) -> + 'encode_addresses_$list'(_els, + [encode_address(List, []) | _acc]). + +decode_address(__TopXMLNS, __IgnoreEls, + {xmlel, <<"address">>, _attrs, _els}) -> + {Type, Jid, Desc, Node, Delivered} = + decode_address_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined, undefined, undefined), + {address, Type, Jid, Desc, Node, Delivered}. + +decode_address_attrs(__TopXMLNS, + [{<<"type">>, _val} | _attrs], _Type, Jid, Desc, Node, + Delivered) -> + decode_address_attrs(__TopXMLNS, _attrs, _val, Jid, + Desc, Node, Delivered); +decode_address_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], Type, _Jid, Desc, Node, + Delivered) -> + decode_address_attrs(__TopXMLNS, _attrs, Type, _val, + Desc, Node, Delivered); +decode_address_attrs(__TopXMLNS, + [{<<"desc">>, _val} | _attrs], Type, Jid, _Desc, Node, + Delivered) -> + decode_address_attrs(__TopXMLNS, _attrs, Type, Jid, + _val, Node, Delivered); +decode_address_attrs(__TopXMLNS, + [{<<"node">>, _val} | _attrs], Type, Jid, Desc, _Node, + Delivered) -> + decode_address_attrs(__TopXMLNS, _attrs, Type, Jid, + Desc, _val, Delivered); +decode_address_attrs(__TopXMLNS, + [{<<"delivered">>, _val} | _attrs], Type, Jid, Desc, + Node, _Delivered) -> + decode_address_attrs(__TopXMLNS, _attrs, Type, Jid, + Desc, Node, _val); +decode_address_attrs(__TopXMLNS, [_ | _attrs], Type, + Jid, Desc, Node, Delivered) -> + decode_address_attrs(__TopXMLNS, _attrs, Type, Jid, + Desc, Node, Delivered); +decode_address_attrs(__TopXMLNS, [], Type, Jid, Desc, + Node, Delivered) -> + {decode_address_attr_type(__TopXMLNS, Type), + decode_address_attr_jid(__TopXMLNS, Jid), + decode_address_attr_desc(__TopXMLNS, Desc), + decode_address_attr_node(__TopXMLNS, Node), + decode_address_attr_delivered(__TopXMLNS, Delivered)}. + +encode_address({address, Type, Jid, Desc, Node, + Delivered}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_address_attr_delivered(Delivered, + encode_address_attr_node(Node, + encode_address_attr_desc(Desc, + encode_address_attr_jid(Jid, + encode_address_attr_type(Type, + _xmlns_attrs))))), + {xmlel, <<"address">>, _attrs, _els}. + +decode_address_attr_type(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"type">>, <<"address">>, __TopXMLNS}}); +decode_address_attr_type(__TopXMLNS, _val) -> + case catch dec_enum(_val, + [bcc, cc, noreply, ofrom, replyroom, replyto, to]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"type">>, <<"address">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_address_attr_type(_val, _acc) -> + [{<<"type">>, enc_enum(_val)} | _acc]. + +decode_address_attr_jid(__TopXMLNS, undefined) -> + undefined; +decode_address_attr_jid(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"address">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_address_attr_jid(undefined, _acc) -> _acc; +encode_address_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_address_attr_desc(__TopXMLNS, undefined) -> + undefined; +decode_address_attr_desc(__TopXMLNS, _val) -> _val. + +encode_address_attr_desc(undefined, _acc) -> _acc; +encode_address_attr_desc(_val, _acc) -> + [{<<"desc">>, _val} | _acc]. + +decode_address_attr_node(__TopXMLNS, undefined) -> + undefined; +decode_address_attr_node(__TopXMLNS, _val) -> _val. + +encode_address_attr_node(undefined, _acc) -> _acc; +encode_address_attr_node(_val, _acc) -> + [{<<"node">>, _val} | _acc]. + +decode_address_attr_delivered(__TopXMLNS, undefined) -> + undefined; +decode_address_attr_delivered(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"delivered">>, <<"address">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_address_attr_delivered(undefined, _acc) -> _acc; +encode_address_attr_delivered(_val, _acc) -> + [{<<"delivered">>, enc_bool(_val)} | _acc]. + +decode_nick(__TopXMLNS, __IgnoreEls, + {xmlel, <<"nick">>, _attrs, _els}) -> + Name = decode_nick_els(__TopXMLNS, __IgnoreEls, _els, + <<>>), + {nick, Name}. + +decode_nick_els(__TopXMLNS, __IgnoreEls, [], Name) -> + decode_nick_cdata(__TopXMLNS, Name); +decode_nick_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Name) -> + decode_nick_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_nick_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Name) -> + decode_nick_els(__TopXMLNS, __IgnoreEls, _els, Name). + +encode_nick({nick, Name}, _xmlns_attrs) -> + _els = encode_nick_cdata(Name, []), + _attrs = _xmlns_attrs, + {xmlel, <<"nick">>, _attrs, _els}. + +decode_nick_cdata(__TopXMLNS, <<>>) -> + erlang:error({xmpp_codec, + {missing_cdata, <<>>, <<"nick">>, __TopXMLNS}}); +decode_nick_cdata(__TopXMLNS, _val) -> _val. + +encode_nick_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + decode_expire(__TopXMLNS, __IgnoreEls, {xmlel, <<"x">>, _attrs, _els}) -> {Seconds, Stored} = decode_expire_attrs(__TopXMLNS, @@ -3745,6 +4199,19 @@ encode_search_instructions_cdata(undefined, _acc) -> encode_search_instructions_cdata(_val, _acc) -> [{xmlcdata, _val} | _acc]. +decode_hint_no_permanent_storage(__TopXMLNS, + __IgnoreEls, + {xmlel, <<"no-permanent-storage">>, _attrs, + _els}) -> + {hint, 'no-permanent-storage'}. + +encode_hint_no_permanent_storage({hint, + 'no-permanent-storage'}, + _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"no-permanent-storage">>, _attrs, _els}. + decode_hint_no_permanent_store(__TopXMLNS, __IgnoreEls, {xmlel, <<"no-permanent-store">>, _attrs, _els}) -> @@ -5380,7 +5847,8 @@ encode_mam_fin_attr_complete(_val, _acc) -> decode_mam_prefs(__TopXMLNS, __IgnoreEls, {xmlel, <<"prefs">>, _attrs, _els}) -> {Never, Always} = decode_mam_prefs_els(__TopXMLNS, - __IgnoreEls, _els, [], []), + __IgnoreEls, _els, undefined, + undefined), {Default, Xmlns} = decode_mam_prefs_attrs(__TopXMLNS, _attrs, undefined, undefined), {mam_prefs, Xmlns, Default, Always, Never}. @@ -5392,10 +5860,23 @@ decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"always">>, _attrs, _} = _el | _els], Never, Always) -> case get_attr(<<"xmlns">>, _attrs) of - <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> + <<"">> + when __TopXMLNS == <<"urn:xmpp:mam:1">>; + __TopXMLNS == <<"urn:xmpp:mam:0">>; + __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, Never, decode_mam_always(__TopXMLNS, __IgnoreEls, _el)); + <<"urn:xmpp:mam:0">> -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + Never, + decode_mam_always(<<"urn:xmpp:mam:0">>, + __IgnoreEls, _el)); + <<"urn:xmpp:mam:1">> -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + Never, + decode_mam_always(<<"urn:xmpp:mam:1">>, + __IgnoreEls, _el)); <<"urn:xmpp:mam:tmp">> -> decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, Never, @@ -5409,10 +5890,23 @@ decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"never">>, _attrs, _} = _el | _els], Never, Always) -> case get_attr(<<"xmlns">>, _attrs) of - <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> + <<"">> + when __TopXMLNS == <<"urn:xmpp:mam:1">>; + __TopXMLNS == <<"urn:xmpp:mam:0">>; + __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, decode_mam_never(__TopXMLNS, __IgnoreEls, _el), Always); + <<"urn:xmpp:mam:0">> -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + decode_mam_never(<<"urn:xmpp:mam:0">>, + __IgnoreEls, _el), + Always); + <<"urn:xmpp:mam:1">> -> + decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, + decode_mam_never(<<"urn:xmpp:mam:1">>, + __IgnoreEls, _el), + Always); <<"urn:xmpp:mam:tmp">> -> decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els, decode_mam_never(<<"urn:xmpp:mam:tmp">>, @@ -5454,11 +5948,11 @@ encode_mam_prefs({mam_prefs, Xmlns, Default, Always, _xmlns_attrs)), {xmlel, <<"prefs">>, _attrs, _els}. -'encode_mam_prefs_$never'([], _acc) -> _acc; +'encode_mam_prefs_$never'(undefined, _acc) -> _acc; 'encode_mam_prefs_$never'(Never, _acc) -> [encode_mam_never(Never, []) | _acc]. -'encode_mam_prefs_$always'([], _acc) -> _acc; +'encode_mam_prefs_$always'(undefined, _acc) -> _acc; 'encode_mam_prefs_$always'(Always, _acc) -> [encode_mam_always(Always, []) | _acc]. @@ -5497,12 +5991,31 @@ decode_mam_always_els(__TopXMLNS, __IgnoreEls, [], decode_mam_always_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) -> case get_attr(<<"xmlns">>, _attrs) of - <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> + <<"">> + when __TopXMLNS == <<"urn:xmpp:mam:1">>; + __TopXMLNS == <<"urn:xmpp:mam:0">>; + __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els, case decode_mam_jid(__TopXMLNS, __IgnoreEls, _el) of - [] -> Jids; + undefined -> Jids; + _new_el -> [_new_el | Jids] + end); + <<"urn:xmpp:mam:0">> -> + decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els, + case decode_mam_jid(<<"urn:xmpp:mam:0">>, + __IgnoreEls, _el) + of + undefined -> Jids; + _new_el -> [_new_el | Jids] + end); + <<"urn:xmpp:mam:1">> -> + decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els, + case decode_mam_jid(<<"urn:xmpp:mam:1">>, + __IgnoreEls, _el) + of + undefined -> Jids; _new_el -> [_new_el | Jids] end); <<"urn:xmpp:mam:tmp">> -> @@ -5510,7 +6023,7 @@ decode_mam_always_els(__TopXMLNS, __IgnoreEls, case decode_mam_jid(<<"urn:xmpp:mam:tmp">>, __IgnoreEls, _el) of - [] -> Jids; + undefined -> Jids; _new_el -> [_new_el | Jids] end); _ -> @@ -5545,11 +6058,30 @@ decode_mam_never_els(__TopXMLNS, __IgnoreEls, [], decode_mam_never_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) -> case get_attr(<<"xmlns">>, _attrs) of - <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> + <<"">> + when __TopXMLNS == <<"urn:xmpp:mam:1">>; + __TopXMLNS == <<"urn:xmpp:mam:0">>; + __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els, case decode_mam_jid(__TopXMLNS, __IgnoreEls, _el) of - [] -> Jids; + undefined -> Jids; + _new_el -> [_new_el | Jids] + end); + <<"urn:xmpp:mam:0">> -> + decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els, + case decode_mam_jid(<<"urn:xmpp:mam:0">>, + __IgnoreEls, _el) + of + undefined -> Jids; + _new_el -> [_new_el | Jids] + end); + <<"urn:xmpp:mam:1">> -> + decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els, + case decode_mam_jid(<<"urn:xmpp:mam:1">>, + __IgnoreEls, _el) + of + undefined -> Jids; _new_el -> [_new_el | Jids] end); <<"urn:xmpp:mam:tmp">> -> @@ -5557,7 +6089,7 @@ decode_mam_never_els(__TopXMLNS, __IgnoreEls, case decode_mam_jid(<<"urn:xmpp:mam:tmp">>, __IgnoreEls, _el) of - [] -> Jids; + undefined -> Jids; _new_el -> [_new_el | Jids] end); _ -> @@ -5759,104 +6291,125 @@ encode_mam_archived_attr_by(_val, _acc) -> decode_mam_query(__TopXMLNS, __IgnoreEls, {xmlel, <<"query">>, _attrs, _els}) -> - {Xdata, End, Start, With, Rsm} = + {Xdata, Withtext, End, Start, With, Rsm} = decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, undefined, undefined, undefined, undefined, - undefined), + undefined, undefined), {Id, Xmlns} = decode_mam_query_attrs(__TopXMLNS, _attrs, undefined, undefined), - {mam_query, Xmlns, Id, Start, End, With, Rsm, Xdata}. + {mam_query, Xmlns, Id, Start, End, With, Withtext, Rsm, + Xdata}. decode_mam_query_els(__TopXMLNS, __IgnoreEls, [], Xdata, - End, Start, With, Rsm) -> - {Xdata, End, Start, With, Rsm}; + Withtext, End, Start, With, Rsm) -> + {Xdata, Withtext, End, Start, With, Rsm}; decode_mam_query_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"start">>, _attrs, _} = _el | _els], Xdata, - End, Start, With, Rsm) -> + Withtext, End, Start, With, Rsm) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, + Xdata, Withtext, End, decode_mam_start(__TopXMLNS, __IgnoreEls, _el), With, Rsm); <<"urn:xmpp:mam:tmp">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, + Xdata, Withtext, End, decode_mam_start(<<"urn:xmpp:mam:tmp">>, __IgnoreEls, _el), With, Rsm); _ -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, Rsm) + Xdata, Withtext, End, Start, With, Rsm) end; decode_mam_query_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"end">>, _attrs, _} = _el | _els], Xdata, - End, Start, With, Rsm) -> + Withtext, End, Start, With, Rsm) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, + Xdata, Withtext, decode_mam_end(__TopXMLNS, __IgnoreEls, _el), Start, With, Rsm); <<"urn:xmpp:mam:tmp">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, + Xdata, Withtext, decode_mam_end(<<"urn:xmpp:mam:tmp">>, __IgnoreEls, _el), Start, With, Rsm); _ -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, Rsm) + Xdata, Withtext, End, Start, With, Rsm) end; decode_mam_query_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"with">>, _attrs, _} = _el | _els], Xdata, - End, Start, With, Rsm) -> + Withtext, End, Start, With, Rsm) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, + Xdata, Withtext, End, Start, decode_mam_with(__TopXMLNS, __IgnoreEls, _el), Rsm); <<"urn:xmpp:mam:tmp">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, + Xdata, Withtext, End, Start, decode_mam_with(<<"urn:xmpp:mam:tmp">>, __IgnoreEls, _el), Rsm); _ -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, Rsm) + Xdata, Withtext, End, Start, With, Rsm) + end; +decode_mam_query_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"withtext">>, _attrs, _} = _el | _els], + Xdata, Withtext, End, Start, With, Rsm) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, + decode_mam_withtext(__TopXMLNS, __IgnoreEls, + _el), + End, Start, With, Rsm); + <<"urn:xmpp:mam:tmp">> -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, + decode_mam_withtext(<<"urn:xmpp:mam:tmp">>, + __IgnoreEls, _el), + End, Start, With, Rsm); + _ -> + decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, + Xdata, Withtext, End, Start, With, Rsm) end; decode_mam_query_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"set">>, _attrs, _} = _el | _els], Xdata, - End, Start, With, Rsm) -> + Withtext, End, Start, With, Rsm) -> case get_attr(<<"xmlns">>, _attrs) of <<"http://jabber.org/protocol/rsm">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, + Xdata, Withtext, End, Start, With, decode_rsm_set(<<"http://jabber.org/protocol/rsm">>, __IgnoreEls, _el)); _ -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, Rsm) + Xdata, Withtext, End, Start, With, Rsm) end; decode_mam_query_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata, End, - Start, With, Rsm) -> + [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata, + Withtext, End, Start, With, Rsm) -> case get_attr(<<"xmlns">>, _attrs) of <<"jabber:x:data">> -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, decode_xdata(<<"jabber:x:data">>, __IgnoreEls, _el), - End, Start, With, Rsm); + Withtext, End, Start, With, Rsm); _ -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, Rsm) + Xdata, Withtext, End, Start, With, Rsm) end; decode_mam_query_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Xdata, End, Start, With, Rsm) -> + [_ | _els], Xdata, Withtext, End, Start, With, Rsm) -> decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els, - Xdata, End, Start, With, Rsm). + Xdata, Withtext, End, Start, With, Rsm). decode_mam_query_attrs(__TopXMLNS, [{<<"queryid">>, _val} | _attrs], _Id, Xmlns) -> @@ -5872,14 +6425,15 @@ decode_mam_query_attrs(__TopXMLNS, [], Id, Xmlns) -> decode_mam_query_attr_xmlns(__TopXMLNS, Xmlns)}. encode_mam_query({mam_query, Xmlns, Id, Start, End, - With, Rsm, Xdata}, + With, Withtext, Rsm, Xdata}, _xmlns_attrs) -> _els = lists:reverse('encode_mam_query_$xdata'(Xdata, - 'encode_mam_query_$end'(End, - 'encode_mam_query_$start'(Start, - 'encode_mam_query_$with'(With, - 'encode_mam_query_$rsm'(Rsm, - [])))))), + 'encode_mam_query_$withtext'(Withtext, + 'encode_mam_query_$end'(End, + 'encode_mam_query_$start'(Start, + 'encode_mam_query_$with'(With, + 'encode_mam_query_$rsm'(Rsm, + []))))))), _attrs = encode_mam_query_attr_xmlns(Xmlns, encode_mam_query_attr_queryid(Id, _xmlns_attrs)), @@ -5891,6 +6445,10 @@ encode_mam_query({mam_query, Xmlns, Id, Start, End, [{<<"xmlns">>, <<"jabber:x:data">>}]) | _acc]. +'encode_mam_query_$withtext'(undefined, _acc) -> _acc; +'encode_mam_query_$withtext'(Withtext, _acc) -> + [encode_mam_withtext(Withtext, []) | _acc]. + 'encode_mam_query_$end'(undefined, _acc) -> _acc; 'encode_mam_query_$end'(End, _acc) -> [encode_mam_end(End, []) | _acc]. @@ -5925,6 +6483,37 @@ encode_mam_query_attr_xmlns(undefined, _acc) -> _acc; encode_mam_query_attr_xmlns(_val, _acc) -> [{<<"xmlns">>, _val} | _acc]. +decode_mam_withtext(__TopXMLNS, __IgnoreEls, + {xmlel, <<"withtext">>, _attrs, _els}) -> + Cdata = decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + Cdata. + +decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_mam_withtext_cdata(__TopXMLNS, Cdata); +decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). + +encode_mam_withtext(Cdata, _xmlns_attrs) -> + _els = encode_mam_withtext_cdata(Cdata, []), + _attrs = _xmlns_attrs, + {xmlel, <<"withtext">>, _attrs, _els}. + +decode_mam_withtext_cdata(__TopXMLNS, <<>>) -> + erlang:error({xmpp_codec, + {missing_cdata, <<>>, <<"withtext">>, __TopXMLNS}}); +decode_mam_withtext_cdata(__TopXMLNS, _val) -> _val. + +encode_mam_withtext_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + decode_mam_with(__TopXMLNS, __IgnoreEls, {xmlel, <<"with">>, _attrs, _els}) -> Cdata = decode_mam_with_els(__TopXMLNS, __IgnoreEls, @@ -6455,10 +7044,10 @@ encode_rsm_before(Cdata, _xmlns_attrs) -> _attrs = _xmlns_attrs, {xmlel, <<"before">>, _attrs, _els}. -decode_rsm_before_cdata(__TopXMLNS, <<>>) -> none; +decode_rsm_before_cdata(__TopXMLNS, <<>>) -> <<>>; decode_rsm_before_cdata(__TopXMLNS, _val) -> _val. -encode_rsm_before_cdata(none, _acc) -> _acc; +encode_rsm_before_cdata(<<>>, _acc) -> _acc; encode_rsm_before_cdata(_val, _acc) -> [{xmlcdata, _val} | _acc]. @@ -6492,63 +7081,438 @@ encode_rsm_after_cdata(undefined, _acc) -> _acc; encode_rsm_after_cdata(_val, _acc) -> [{xmlcdata, _val} | _acc]. +decode_muc_unsubscribe(__TopXMLNS, __IgnoreEls, + {xmlel, <<"unsubscribe">>, _attrs, _els}) -> + {muc_unsubscribe}. + +encode_muc_unsubscribe({muc_unsubscribe}, + _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"unsubscribe">>, _attrs, _els}. + +decode_muc_subscribe(__TopXMLNS, __IgnoreEls, + {xmlel, <<"subscribe">>, _attrs, _els}) -> + Events = decode_muc_subscribe_els(__TopXMLNS, + __IgnoreEls, _els, []), + Nick = decode_muc_subscribe_attrs(__TopXMLNS, _attrs, + undefined), + {muc_subscribe, Nick, Events}. + +decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, [], + Events) -> + lists:reverse(Events); +decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"event">>, _attrs, _} = _el | _els], + Events) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"urn:xmpp:mucsub:0">> -> + decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els, + [decode_muc_subscribe_event(__TopXMLNS, + __IgnoreEls, _el) + | Events]); + <<"urn:xmpp:mucsub:0">> -> + decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els, + [decode_muc_subscribe_event(<<"urn:xmpp:mucsub:0">>, + __IgnoreEls, _el) + | Events]); + _ -> + decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els, + Events) + end; +decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Events) -> + decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els, + Events). + +decode_muc_subscribe_attrs(__TopXMLNS, + [{<<"nick">>, _val} | _attrs], _Nick) -> + decode_muc_subscribe_attrs(__TopXMLNS, _attrs, _val); +decode_muc_subscribe_attrs(__TopXMLNS, [_ | _attrs], + Nick) -> + decode_muc_subscribe_attrs(__TopXMLNS, _attrs, Nick); +decode_muc_subscribe_attrs(__TopXMLNS, [], Nick) -> + decode_muc_subscribe_attr_nick(__TopXMLNS, Nick). + +encode_muc_subscribe({muc_subscribe, Nick, Events}, + _xmlns_attrs) -> + _els = + lists:reverse('encode_muc_subscribe_$events'(Events, + [])), + _attrs = encode_muc_subscribe_attr_nick(Nick, + _xmlns_attrs), + {xmlel, <<"subscribe">>, _attrs, _els}. + +'encode_muc_subscribe_$events'([], _acc) -> _acc; +'encode_muc_subscribe_$events'([Events | _els], _acc) -> + 'encode_muc_subscribe_$events'(_els, + [encode_muc_subscribe_event(Events, []) + | _acc]). + +decode_muc_subscribe_attr_nick(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"nick">>, <<"subscribe">>, + __TopXMLNS}}); +decode_muc_subscribe_attr_nick(__TopXMLNS, _val) -> + _val. + +encode_muc_subscribe_attr_nick(_val, _acc) -> + [{<<"nick">>, _val} | _acc]. + +decode_muc_subscribe_event(__TopXMLNS, __IgnoreEls, + {xmlel, <<"event">>, _attrs, _els}) -> + Node = decode_muc_subscribe_event_attrs(__TopXMLNS, + _attrs, undefined), + Node. + +decode_muc_subscribe_event_attrs(__TopXMLNS, + [{<<"node">>, _val} | _attrs], _Node) -> + decode_muc_subscribe_event_attrs(__TopXMLNS, _attrs, + _val); +decode_muc_subscribe_event_attrs(__TopXMLNS, + [_ | _attrs], Node) -> + decode_muc_subscribe_event_attrs(__TopXMLNS, _attrs, + Node); +decode_muc_subscribe_event_attrs(__TopXMLNS, [], + Node) -> + decode_muc_subscribe_event_attr_node(__TopXMLNS, Node). + +encode_muc_subscribe_event(Node, _xmlns_attrs) -> + _els = [], + _attrs = encode_muc_subscribe_event_attr_node(Node, + _xmlns_attrs), + {xmlel, <<"event">>, _attrs, _els}. + +decode_muc_subscribe_event_attr_node(__TopXMLNS, + undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"node">>, <<"event">>, __TopXMLNS}}); +decode_muc_subscribe_event_attr_node(__TopXMLNS, + _val) -> + _val. + +encode_muc_subscribe_event_attr_node(_val, _acc) -> + [{<<"node">>, _val} | _acc]. + +decode_muc_subscriptions(__TopXMLNS, __IgnoreEls, + {xmlel, <<"subscriptions">>, _attrs, _els}) -> + List = decode_muc_subscriptions_els(__TopXMLNS, + __IgnoreEls, _els, []), + {muc_subscriptions, List}. + +decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + [], List) -> + lists:reverse(List); +decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"subscription">>, _attrs, _} = _el + | _els], + List) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> when __TopXMLNS == <<"urn:xmpp:mucsub:0">> -> + decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + _els, + case decode_muc_subscription(__TopXMLNS, + __IgnoreEls, + _el) + of + undefined -> List; + _new_el -> [_new_el | List] + end); + <<"urn:xmpp:mucsub:0">> -> + decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + _els, + case + decode_muc_subscription(<<"urn:xmpp:mucsub:0">>, + __IgnoreEls, + _el) + of + undefined -> List; + _new_el -> [_new_el | List] + end); + _ -> + decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + _els, List) + end; +decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + [_ | _els], List) -> + decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls, + _els, List). + +encode_muc_subscriptions({muc_subscriptions, List}, + _xmlns_attrs) -> + _els = + lists:reverse('encode_muc_subscriptions_$list'(List, + [])), + _attrs = _xmlns_attrs, + {xmlel, <<"subscriptions">>, _attrs, _els}. + +'encode_muc_subscriptions_$list'([], _acc) -> _acc; +'encode_muc_subscriptions_$list'([List | _els], _acc) -> + 'encode_muc_subscriptions_$list'(_els, + [encode_muc_subscription(List, []) + | _acc]). + +decode_muc_subscription(__TopXMLNS, __IgnoreEls, + {xmlel, <<"subscription">>, _attrs, _els}) -> + Jid = decode_muc_subscription_attrs(__TopXMLNS, _attrs, + undefined), + Jid. + +decode_muc_subscription_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], _Jid) -> + decode_muc_subscription_attrs(__TopXMLNS, _attrs, _val); +decode_muc_subscription_attrs(__TopXMLNS, [_ | _attrs], + Jid) -> + decode_muc_subscription_attrs(__TopXMLNS, _attrs, Jid); +decode_muc_subscription_attrs(__TopXMLNS, [], Jid) -> + decode_muc_subscription_attr_jid(__TopXMLNS, Jid). + +encode_muc_subscription(Jid, _xmlns_attrs) -> + _els = [], + _attrs = encode_muc_subscription_attr_jid(Jid, + _xmlns_attrs), + {xmlel, <<"subscription">>, _attrs, _els}. + +decode_muc_subscription_attr_jid(__TopXMLNS, + undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"jid">>, <<"subscription">>, + __TopXMLNS}}); +decode_muc_subscription_attr_jid(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"subscription">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_muc_subscription_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_x_conference(__TopXMLNS, __IgnoreEls, + {xmlel, <<"x">>, _attrs, _els}) -> + {Jid, Password, Reason, Thread, Continue} = + decode_x_conference_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined, undefined, undefined), + {x_conference, Jid, Password, Reason, Continue, Thread}. + +decode_x_conference_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], _Jid, Password, Reason, + Thread, Continue) -> + decode_x_conference_attrs(__TopXMLNS, _attrs, _val, + Password, Reason, Thread, Continue); +decode_x_conference_attrs(__TopXMLNS, + [{<<"password">>, _val} | _attrs], Jid, _Password, + Reason, Thread, Continue) -> + decode_x_conference_attrs(__TopXMLNS, _attrs, Jid, _val, + Reason, Thread, Continue); +decode_x_conference_attrs(__TopXMLNS, + [{<<"reason">>, _val} | _attrs], Jid, Password, + _Reason, Thread, Continue) -> + decode_x_conference_attrs(__TopXMLNS, _attrs, Jid, + Password, _val, Thread, Continue); +decode_x_conference_attrs(__TopXMLNS, + [{<<"thread">>, _val} | _attrs], Jid, Password, + Reason, _Thread, Continue) -> + decode_x_conference_attrs(__TopXMLNS, _attrs, Jid, + Password, Reason, _val, Continue); +decode_x_conference_attrs(__TopXMLNS, + [{<<"continue">>, _val} | _attrs], Jid, Password, + Reason, Thread, _Continue) -> + decode_x_conference_attrs(__TopXMLNS, _attrs, Jid, + Password, Reason, Thread, _val); +decode_x_conference_attrs(__TopXMLNS, [_ | _attrs], Jid, + Password, Reason, Thread, Continue) -> + decode_x_conference_attrs(__TopXMLNS, _attrs, Jid, + Password, Reason, Thread, Continue); +decode_x_conference_attrs(__TopXMLNS, [], Jid, Password, + Reason, Thread, Continue) -> + {decode_x_conference_attr_jid(__TopXMLNS, Jid), + decode_x_conference_attr_password(__TopXMLNS, Password), + decode_x_conference_attr_reason(__TopXMLNS, Reason), + decode_x_conference_attr_thread(__TopXMLNS, Thread), + decode_x_conference_attr_continue(__TopXMLNS, + Continue)}. + +encode_x_conference({x_conference, Jid, Password, + Reason, Continue, Thread}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_x_conference_attr_continue(Continue, + encode_x_conference_attr_thread(Thread, + encode_x_conference_attr_reason(Reason, + encode_x_conference_attr_password(Password, + encode_x_conference_attr_jid(Jid, + _xmlns_attrs))))), + {xmlel, <<"x">>, _attrs, _els}. + +decode_x_conference_attr_jid(__TopXMLNS, undefined) -> + erlang:error({xmpp_codec, + {missing_attr, <<"jid">>, <<"x">>, __TopXMLNS}}); +decode_x_conference_attr_jid(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"x">>, __TopXMLNS}}); + _res -> _res + end. + +encode_x_conference_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_x_conference_attr_password(__TopXMLNS, + undefined) -> + <<>>; +decode_x_conference_attr_password(__TopXMLNS, _val) -> + _val. + +encode_x_conference_attr_password(<<>>, _acc) -> _acc; +encode_x_conference_attr_password(_val, _acc) -> + [{<<"password">>, _val} | _acc]. + +decode_x_conference_attr_reason(__TopXMLNS, + undefined) -> + <<>>; +decode_x_conference_attr_reason(__TopXMLNS, _val) -> + _val. + +encode_x_conference_attr_reason(<<>>, _acc) -> _acc; +encode_x_conference_attr_reason(_val, _acc) -> + [{<<"reason">>, _val} | _acc]. + +decode_x_conference_attr_thread(__TopXMLNS, + undefined) -> + <<>>; +decode_x_conference_attr_thread(__TopXMLNS, _val) -> + _val. + +encode_x_conference_attr_thread(<<>>, _acc) -> _acc; +encode_x_conference_attr_thread(_val, _acc) -> + [{<<"thread">>, _val} | _acc]. + +decode_x_conference_attr_continue(__TopXMLNS, + undefined) -> + undefined; +decode_x_conference_attr_continue(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"continue">>, <<"x">>, __TopXMLNS}}); + _res -> _res + end. + +encode_x_conference_attr_continue(undefined, _acc) -> + _acc; +encode_x_conference_attr_continue(_val, _acc) -> + [{<<"continue">>, enc_bool(_val)} | _acc]. + +decode_muc_unique(__TopXMLNS, __IgnoreEls, + {xmlel, <<"unique">>, _attrs, _els}) -> + Name = decode_muc_unique_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), + {muc_unique, Name}. + +decode_muc_unique_els(__TopXMLNS, __IgnoreEls, [], + Name) -> + decode_muc_unique_cdata(__TopXMLNS, Name); +decode_muc_unique_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Name) -> + decode_muc_unique_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_muc_unique_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Name) -> + decode_muc_unique_els(__TopXMLNS, __IgnoreEls, _els, + Name). + +encode_muc_unique({muc_unique, Name}, _xmlns_attrs) -> + _els = encode_muc_unique_cdata(Name, []), + _attrs = _xmlns_attrs, + {xmlel, <<"unique">>, _attrs, _els}. + +decode_muc_unique_cdata(__TopXMLNS, <<>>) -> <<>>; +decode_muc_unique_cdata(__TopXMLNS, _val) -> _val. + +encode_muc_unique_cdata(<<>>, _acc) -> _acc; +encode_muc_unique_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + decode_muc(__TopXMLNS, __IgnoreEls, {xmlel, <<"x">>, _attrs, _els}) -> - History = decode_muc_els(__TopXMLNS, __IgnoreEls, _els, - undefined), - Password = decode_muc_attrs(__TopXMLNS, _attrs, - undefined), + {Password, History} = decode_muc_els(__TopXMLNS, + __IgnoreEls, _els, undefined, + undefined), {muc, History, Password}. -decode_muc_els(__TopXMLNS, __IgnoreEls, [], History) -> - History; +decode_muc_els(__TopXMLNS, __IgnoreEls, [], Password, + History) -> + {Password, History}; decode_muc_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"history">>, _attrs, _} = _el | _els], - History) -> + Password, History) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/muc">> -> + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password, + decode_muc_history(__TopXMLNS, __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc">> -> + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password, + decode_muc_history(<<"http://jabber.org/protocol/muc">>, + __IgnoreEls, _el)); + _ -> + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password, + History) + end; +decode_muc_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"password">>, _attrs, _} = _el | _els], + Password, History) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"http://jabber.org/protocol/muc">> -> decode_muc_els(__TopXMLNS, __IgnoreEls, _els, - decode_muc_history(__TopXMLNS, __IgnoreEls, _el)); + decode_muc_password(__TopXMLNS, __IgnoreEls, _el), + History); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el), + History); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_password(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el), + History); <<"http://jabber.org/protocol/muc">> -> decode_muc_els(__TopXMLNS, __IgnoreEls, _els, - decode_muc_history(<<"http://jabber.org/protocol/muc">>, - __IgnoreEls, _el)); + decode_muc_password(<<"http://jabber.org/protocol/muc">>, + __IgnoreEls, _el), + History); _ -> - decode_muc_els(__TopXMLNS, __IgnoreEls, _els, History) + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password, + History) end; decode_muc_els(__TopXMLNS, __IgnoreEls, [_ | _els], - History) -> - decode_muc_els(__TopXMLNS, __IgnoreEls, _els, History). - -decode_muc_attrs(__TopXMLNS, - [{<<"password">>, _val} | _attrs], _Password) -> - decode_muc_attrs(__TopXMLNS, _attrs, _val); -decode_muc_attrs(__TopXMLNS, [_ | _attrs], Password) -> - decode_muc_attrs(__TopXMLNS, _attrs, Password); -decode_muc_attrs(__TopXMLNS, [], Password) -> - decode_muc_attr_password(__TopXMLNS, Password). + Password, History) -> + decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password, + History). encode_muc({muc, History, Password}, _xmlns_attrs) -> - _els = lists:reverse('encode_muc_$history'(History, - [])), - _attrs = encode_muc_attr_password(Password, - _xmlns_attrs), + _els = lists:reverse('encode_muc_$password'(Password, + 'encode_muc_$history'(History, + []))), + _attrs = _xmlns_attrs, {xmlel, <<"x">>, _attrs, _els}. +'encode_muc_$password'(undefined, _acc) -> _acc; +'encode_muc_$password'(Password, _acc) -> + [encode_muc_password(Password, []) | _acc]. + 'encode_muc_$history'(undefined, _acc) -> _acc; 'encode_muc_$history'(History, _acc) -> [encode_muc_history(History, []) | _acc]. -decode_muc_attr_password(__TopXMLNS, undefined) -> - undefined; -decode_muc_attr_password(__TopXMLNS, _val) -> _val. - -encode_muc_attr_password(undefined, _acc) -> _acc; -encode_muc_attr_password(_val, _acc) -> - [{<<"password">>, _val} | _acc]. - decode_muc_admin(__TopXMLNS, __IgnoreEls, {xmlel, <<"query">>, _attrs, _els}) -> Items = decode_muc_admin_els(__TopXMLNS, __IgnoreEls, @@ -6593,37 +7557,6 @@ encode_muc_admin({muc_admin, Items}, _xmlns_attrs) -> 'encode_muc_admin_$items'(_els, [encode_muc_admin_item(Items, []) | _acc]). -decode_muc_admin_reason(__TopXMLNS, __IgnoreEls, - {xmlel, <<"reason">>, _attrs, _els}) -> - Cdata = decode_muc_admin_reason_els(__TopXMLNS, - __IgnoreEls, _els, <<>>), - Cdata. - -decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, [], - Cdata) -> - decode_muc_admin_reason_cdata(__TopXMLNS, Cdata); -decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, - [{xmlcdata, _data} | _els], Cdata) -> - decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, - _els, <>); -decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Cdata) -> - decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, - _els, Cdata). - -encode_muc_admin_reason(Cdata, _xmlns_attrs) -> - _els = encode_muc_admin_reason_cdata(Cdata, []), - _attrs = _xmlns_attrs, - {xmlel, <<"reason">>, _attrs, _els}. - -decode_muc_admin_reason_cdata(__TopXMLNS, <<>>) -> - undefined; -decode_muc_admin_reason_cdata(__TopXMLNS, _val) -> _val. - -encode_muc_admin_reason_cdata(undefined, _acc) -> _acc; -encode_muc_admin_reason_cdata(_val, _acc) -> - [{xmlcdata, _val} | _acc]. - decode_muc_admin_continue(__TopXMLNS, __IgnoreEls, {xmlel, <<"continue">>, _attrs, _els}) -> Thread = decode_muc_admin_continue_attrs(__TopXMLNS, @@ -6724,7 +7657,7 @@ decode_muc_admin_item(__TopXMLNS, __IgnoreEls, {xmlel, <<"item">>, _attrs, _els}) -> {Actor, Continue, Reason} = decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els, - undefined, undefined, undefined), + undefined, undefined, <<>>), {Affiliation, Role, Jid, Nick} = decode_muc_admin_item_attrs(__TopXMLNS, _attrs, undefined, undefined, undefined, undefined), @@ -6785,13 +7718,23 @@ decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, <<"http://jabber.org/protocol/muc#admin">> -> decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els, Actor, Continue, - decode_muc_admin_reason(__TopXMLNS, - __IgnoreEls, _el)); + decode_muc_reason(__TopXMLNS, __IgnoreEls, + _el)); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); <<"http://jabber.org/protocol/muc#admin">> -> decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els, Actor, Continue, - decode_muc_admin_reason(<<"http://jabber.org/protocol/muc#admin">>, - __IgnoreEls, _el)); + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); _ -> decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els, Actor, Continue, Reason) @@ -6857,10 +7800,9 @@ encode_muc_admin_item({muc_item, Actor, Continue, 'encode_muc_admin_item_$continue'(Continue, _acc) -> [encode_muc_admin_continue(Continue, []) | _acc]. -'encode_muc_admin_item_$reason'(undefined, _acc) -> - _acc; +'encode_muc_admin_item_$reason'(<<>>, _acc) -> _acc; 'encode_muc_admin_item_$reason'(Reason, _acc) -> - [encode_muc_admin_reason(Reason, []) | _acc]. + [encode_muc_reason(Reason, []) | _acc]. decode_muc_admin_item_attr_affiliation(__TopXMLNS, undefined) -> @@ -6926,62 +7868,302 @@ encode_muc_admin_item_attr_nick(undefined, _acc) -> encode_muc_admin_item_attr_nick(_val, _acc) -> [{<<"nick">>, _val} | _acc]. +decode_muc_owner_item(__TopXMLNS, __IgnoreEls, + {xmlel, <<"item">>, _attrs, _els}) -> + {Actor, Continue, Reason} = + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + undefined, undefined, <<>>), + {Affiliation, Role, Jid, Nick} = + decode_muc_owner_item_attrs(__TopXMLNS, _attrs, + undefined, undefined, undefined, undefined), + {muc_item, Actor, Continue, Reason, Affiliation, Role, + Jid, Nick}. + +decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, [], + Actor, Continue, Reason) -> + {Actor, Continue, Reason}; +decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"actor">>, _attrs, _} = _el | _els], Actor, + Continue, Reason) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_admin_actor(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el), + Continue, Reason); + _ -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, Reason) + end; +decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"continue">>, _attrs, _} = _el | _els], + Actor, Continue, Reason) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, + decode_muc_admin_continue(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el), + Reason); + _ -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, Reason) + end; +decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"reason">>, _attrs, _} = _el | _els], + Actor, Continue, Reason) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(__TopXMLNS, __IgnoreEls, + _el)); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); + _ -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, Reason) + end; +decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Actor, Continue, Reason) -> + decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, Reason). + +decode_muc_owner_item_attrs(__TopXMLNS, + [{<<"affiliation">>, _val} | _attrs], _Affiliation, + Role, Jid, Nick) -> + decode_muc_owner_item_attrs(__TopXMLNS, _attrs, _val, + Role, Jid, Nick); +decode_muc_owner_item_attrs(__TopXMLNS, + [{<<"role">>, _val} | _attrs], Affiliation, _Role, + Jid, Nick) -> + decode_muc_owner_item_attrs(__TopXMLNS, _attrs, + Affiliation, _val, Jid, Nick); +decode_muc_owner_item_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], Affiliation, Role, + _Jid, Nick) -> + decode_muc_owner_item_attrs(__TopXMLNS, _attrs, + Affiliation, Role, _val, Nick); +decode_muc_owner_item_attrs(__TopXMLNS, + [{<<"nick">>, _val} | _attrs], Affiliation, Role, + Jid, _Nick) -> + decode_muc_owner_item_attrs(__TopXMLNS, _attrs, + Affiliation, Role, Jid, _val); +decode_muc_owner_item_attrs(__TopXMLNS, [_ | _attrs], + Affiliation, Role, Jid, Nick) -> + decode_muc_owner_item_attrs(__TopXMLNS, _attrs, + Affiliation, Role, Jid, Nick); +decode_muc_owner_item_attrs(__TopXMLNS, [], Affiliation, + Role, Jid, Nick) -> + {decode_muc_owner_item_attr_affiliation(__TopXMLNS, + Affiliation), + decode_muc_owner_item_attr_role(__TopXMLNS, Role), + decode_muc_owner_item_attr_jid(__TopXMLNS, Jid), + decode_muc_owner_item_attr_nick(__TopXMLNS, Nick)}. + +encode_muc_owner_item({muc_item, Actor, Continue, + Reason, Affiliation, Role, Jid, Nick}, + _xmlns_attrs) -> + _els = + lists:reverse('encode_muc_owner_item_$actor'(Actor, + 'encode_muc_owner_item_$continue'(Continue, + 'encode_muc_owner_item_$reason'(Reason, + [])))), + _attrs = encode_muc_owner_item_attr_nick(Nick, + encode_muc_owner_item_attr_jid(Jid, + encode_muc_owner_item_attr_role(Role, + encode_muc_owner_item_attr_affiliation(Affiliation, + _xmlns_attrs)))), + {xmlel, <<"item">>, _attrs, _els}. + +'encode_muc_owner_item_$actor'(undefined, _acc) -> _acc; +'encode_muc_owner_item_$actor'(Actor, _acc) -> + [encode_muc_admin_actor(Actor, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/muc#admin">>}]) + | _acc]. + +'encode_muc_owner_item_$continue'(undefined, _acc) -> + _acc; +'encode_muc_owner_item_$continue'(Continue, _acc) -> + [encode_muc_admin_continue(Continue, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/muc#admin">>}]) + | _acc]. + +'encode_muc_owner_item_$reason'(<<>>, _acc) -> _acc; +'encode_muc_owner_item_$reason'(Reason, _acc) -> + [encode_muc_reason(Reason, []) | _acc]. + +decode_muc_owner_item_attr_affiliation(__TopXMLNS, + undefined) -> + undefined; +decode_muc_owner_item_attr_affiliation(__TopXMLNS, + _val) -> + case catch dec_enum(_val, + [admin, member, none, outcast, owner]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"affiliation">>, <<"item">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_muc_owner_item_attr_affiliation(undefined, + _acc) -> + _acc; +encode_muc_owner_item_attr_affiliation(_val, _acc) -> + [{<<"affiliation">>, enc_enum(_val)} | _acc]. + +decode_muc_owner_item_attr_role(__TopXMLNS, + undefined) -> + undefined; +decode_muc_owner_item_attr_role(__TopXMLNS, _val) -> + case catch dec_enum(_val, + [moderator, none, participant, visitor]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"role">>, <<"item">>, __TopXMLNS}}); + _res -> _res + end. + +encode_muc_owner_item_attr_role(undefined, _acc) -> + _acc; +encode_muc_owner_item_attr_role(_val, _acc) -> + [{<<"role">>, enc_enum(_val)} | _acc]. + +decode_muc_owner_item_attr_jid(__TopXMLNS, undefined) -> + undefined; +decode_muc_owner_item_attr_jid(__TopXMLNS, _val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}}); + _res -> _res + end. + +encode_muc_owner_item_attr_jid(undefined, _acc) -> _acc; +encode_muc_owner_item_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_muc_owner_item_attr_nick(__TopXMLNS, + undefined) -> + undefined; +decode_muc_owner_item_attr_nick(__TopXMLNS, _val) -> + _val. + +encode_muc_owner_item_attr_nick(undefined, _acc) -> + _acc; +encode_muc_owner_item_attr_nick(_val, _acc) -> + [{<<"nick">>, _val} | _acc]. + decode_muc_owner(__TopXMLNS, __IgnoreEls, {xmlel, <<"query">>, _attrs, _els}) -> - {Config, Destroy} = decode_muc_owner_els(__TopXMLNS, - __IgnoreEls, _els, undefined, - undefined), - {muc_owner, Destroy, Config}. + {Items, Config, Destroy} = + decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, [], + undefined, undefined), + {muc_owner, Destroy, Config, Items}. -decode_muc_owner_els(__TopXMLNS, __IgnoreEls, [], +decode_muc_owner_els(__TopXMLNS, __IgnoreEls, [], Items, Config, Destroy) -> - {Config, Destroy}; + {lists:reverse(Items), Config, Destroy}; decode_muc_owner_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"destroy">>, _attrs, _} = _el | _els], + [{xmlel, <<"destroy">>, _attrs, _} = _el | _els], Items, Config, Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"http://jabber.org/protocol/muc#owner">> -> decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, - Config, - decode_muc_owner_destroy(__TopXMLNS, __IgnoreEls, - _el)); + Items, Config, + decode_muc_destroy(__TopXMLNS, __IgnoreEls, + _el)); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, + Items, Config, + decode_muc_destroy(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); <<"http://jabber.org/protocol/muc#owner">> -> decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, - Config, - decode_muc_owner_destroy(<<"http://jabber.org/protocol/muc#owner">>, - __IgnoreEls, _el)); + Items, Config, + decode_muc_destroy(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); _ -> decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, - Config, Destroy) + Items, Config, Destroy) end; decode_muc_owner_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"x">>, _attrs, _} = _el | _els], Config, - Destroy) -> + [{xmlel, <<"x">>, _attrs, _} = _el | _els], Items, + Config, Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"jabber:x:data">> -> decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, + Items, decode_xdata(<<"jabber:x:data">>, __IgnoreEls, _el), Destroy); _ -> decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, - Config, Destroy) + Items, Config, Destroy) end; decode_muc_owner_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Config, Destroy) -> + [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items, + Config, Destroy) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, + [decode_muc_owner_item(__TopXMLNS, __IgnoreEls, + _el) + | Items], + Config, Destroy); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, + [decode_muc_owner_item(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el) + | Items], + Config, Destroy); + _ -> + decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, + Items, Config, Destroy) + end; +decode_muc_owner_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Items, Config, Destroy) -> decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, - Config, Destroy). + Items, Config, Destroy). -encode_muc_owner({muc_owner, Destroy, Config}, +encode_muc_owner({muc_owner, Destroy, Config, Items}, _xmlns_attrs) -> - _els = lists:reverse('encode_muc_owner_$config'(Config, - 'encode_muc_owner_$destroy'(Destroy, - []))), + _els = lists:reverse('encode_muc_owner_$items'(Items, + 'encode_muc_owner_$config'(Config, + 'encode_muc_owner_$destroy'(Destroy, + [])))), _attrs = _xmlns_attrs, {xmlel, <<"query">>, _attrs, _els}. +'encode_muc_owner_$items'([], _acc) -> _acc; +'encode_muc_owner_$items'([Items | _els], _acc) -> + 'encode_muc_owner_$items'(_els, + [encode_muc_owner_item(Items, []) | _acc]). + 'encode_muc_owner_$config'(undefined, _acc) -> _acc; 'encode_muc_owner_$config'(Config, _acc) -> [encode_xdata(Config, @@ -6990,242 +8172,142 @@ encode_muc_owner({muc_owner, Destroy, Config}, 'encode_muc_owner_$destroy'(undefined, _acc) -> _acc; 'encode_muc_owner_$destroy'(Destroy, _acc) -> - [encode_muc_owner_destroy(Destroy, []) | _acc]. + [encode_muc_destroy(Destroy, []) | _acc]. -decode_muc_owner_destroy(__TopXMLNS, __IgnoreEls, - {xmlel, <<"destroy">>, _attrs, _els}) -> - {Password, Reason} = - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, undefined, undefined), - Jid = decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs, - undefined), - {muc_owner_destroy, Jid, Reason, Password}. - -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - [], Password, Reason) -> - {Password, Reason}; -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"password">>, _attrs, _} = _el | _els], - Password, Reason) -> - case get_attr(<<"xmlns">>, _attrs) of - <<"">> - when __TopXMLNS == - <<"http://jabber.org/protocol/muc#owner">> -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, - decode_muc_owner_password(__TopXMLNS, - __IgnoreEls, - _el), - Reason); - <<"http://jabber.org/protocol/muc#owner">> -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, - decode_muc_owner_password(<<"http://jabber.org/protocol/muc#owner">>, - __IgnoreEls, - _el), - Reason); - _ -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Password, Reason) - end; -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"reason">>, _attrs, _} = _el | _els], - Password, Reason) -> - case get_attr(<<"xmlns">>, _attrs) of - <<"">> - when __TopXMLNS == - <<"http://jabber.org/protocol/muc#owner">> -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Password, - decode_muc_owner_reason(__TopXMLNS, - __IgnoreEls, - _el)); - <<"http://jabber.org/protocol/muc#owner">> -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Password, - decode_muc_owner_reason(<<"http://jabber.org/protocol/muc#owner">>, - __IgnoreEls, - _el)); - _ -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Password, Reason) - end; -decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Password, Reason) -> - decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Password, Reason). - -decode_muc_owner_destroy_attrs(__TopXMLNS, - [{<<"jid">>, _val} | _attrs], _Jid) -> - decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs, - _val); -decode_muc_owner_destroy_attrs(__TopXMLNS, [_ | _attrs], - Jid) -> - decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs, Jid); -decode_muc_owner_destroy_attrs(__TopXMLNS, [], Jid) -> - decode_muc_owner_destroy_attr_jid(__TopXMLNS, Jid). - -encode_muc_owner_destroy({muc_owner_destroy, Jid, - Reason, Password}, - _xmlns_attrs) -> - _els = - lists:reverse('encode_muc_owner_destroy_$password'(Password, - 'encode_muc_owner_destroy_$reason'(Reason, - []))), - _attrs = encode_muc_owner_destroy_attr_jid(Jid, - _xmlns_attrs), - {xmlel, <<"destroy">>, _attrs, _els}. - -'encode_muc_owner_destroy_$password'(undefined, _acc) -> - _acc; -'encode_muc_owner_destroy_$password'(Password, _acc) -> - [encode_muc_owner_password(Password, []) | _acc]. - -'encode_muc_owner_destroy_$reason'(undefined, _acc) -> - _acc; -'encode_muc_owner_destroy_$reason'(Reason, _acc) -> - [encode_muc_owner_reason(Reason, []) | _acc]. - -decode_muc_owner_destroy_attr_jid(__TopXMLNS, - undefined) -> - undefined; -decode_muc_owner_destroy_attr_jid(__TopXMLNS, _val) -> - case catch dec_jid(_val) of - {'EXIT', _} -> - erlang:error({xmpp_codec, - {bad_attr_value, <<"jid">>, <<"destroy">>, - __TopXMLNS}}); - _res -> _res - end. - -encode_muc_owner_destroy_attr_jid(undefined, _acc) -> - _acc; -encode_muc_owner_destroy_attr_jid(_val, _acc) -> - [{<<"jid">>, enc_jid(_val)} | _acc]. - -decode_muc_owner_reason(__TopXMLNS, __IgnoreEls, - {xmlel, <<"reason">>, _attrs, _els}) -> - Cdata = decode_muc_owner_reason_els(__TopXMLNS, - __IgnoreEls, _els, <<>>), +decode_muc_password(__TopXMLNS, __IgnoreEls, + {xmlel, <<"password">>, _attrs, _els}) -> + Cdata = decode_muc_password_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), Cdata. -decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, [], - Cdata) -> - decode_muc_owner_reason_cdata(__TopXMLNS, Cdata); -decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, - [{xmlcdata, _data} | _els], Cdata) -> - decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, - _els, <>); -decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Cdata) -> - decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, - _els, Cdata). +decode_muc_password_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_muc_password_cdata(__TopXMLNS, Cdata); +decode_muc_password_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_muc_password_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_muc_password_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_muc_password_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). -encode_muc_owner_reason(Cdata, _xmlns_attrs) -> - _els = encode_muc_owner_reason_cdata(Cdata, []), - _attrs = _xmlns_attrs, - {xmlel, <<"reason">>, _attrs, _els}. - -decode_muc_owner_reason_cdata(__TopXMLNS, <<>>) -> - undefined; -decode_muc_owner_reason_cdata(__TopXMLNS, _val) -> _val. - -encode_muc_owner_reason_cdata(undefined, _acc) -> _acc; -encode_muc_owner_reason_cdata(_val, _acc) -> - [{xmlcdata, _val} | _acc]. - -decode_muc_owner_password(__TopXMLNS, __IgnoreEls, - {xmlel, <<"password">>, _attrs, _els}) -> - Cdata = decode_muc_owner_password_els(__TopXMLNS, - __IgnoreEls, _els, <<>>), - Cdata. - -decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls, - [], Cdata) -> - decode_muc_owner_password_cdata(__TopXMLNS, Cdata); -decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls, - [{xmlcdata, _data} | _els], Cdata) -> - decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls, - _els, <>); -decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Cdata) -> - decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls, - _els, Cdata). - -encode_muc_owner_password(Cdata, _xmlns_attrs) -> - _els = encode_muc_owner_password_cdata(Cdata, []), +encode_muc_password(Cdata, _xmlns_attrs) -> + _els = encode_muc_password_cdata(Cdata, []), _attrs = _xmlns_attrs, {xmlel, <<"password">>, _attrs, _els}. -decode_muc_owner_password_cdata(__TopXMLNS, <<>>) -> +decode_muc_password_cdata(__TopXMLNS, <<>>) -> undefined; -decode_muc_owner_password_cdata(__TopXMLNS, _val) -> - _val. +decode_muc_password_cdata(__TopXMLNS, _val) -> _val. -encode_muc_owner_password_cdata(undefined, _acc) -> - _acc; -encode_muc_owner_password_cdata(_val, _acc) -> +encode_muc_password_cdata(undefined, _acc) -> _acc; +encode_muc_password_cdata(_val, _acc) -> [{xmlcdata, _val} | _acc]. decode_muc_user(__TopXMLNS, __IgnoreEls, {xmlel, <<"x">>, _attrs, _els}) -> - {Status_codes, Items, Invites, Decline, Destroy} = + {Status_codes, Items, Invites, Password, Decline, + Destroy} = decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, [], - [], [], undefined, undefined), - Password = decode_muc_user_attrs(__TopXMLNS, _attrs, - undefined), + [], [], undefined, undefined, undefined), {muc_user, Decline, Destroy, Invites, Items, Status_codes, Password}. decode_muc_user_els(__TopXMLNS, __IgnoreEls, [], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> {lists:reverse(Status_codes), lists:reverse(Items), - lists:reverse(Invites), Decline, Destroy}; + lists:reverse(Invites), Password, Decline, Destroy}; decode_muc_user_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"decline">>, _attrs, _} = _el | _els], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, + Status_codes, Items, Invites, Password, decode_muc_user_decline(__TopXMLNS, __IgnoreEls, _el), Destroy); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, + Status_codes, Items, Invites, Password, decode_muc_user_decline(<<"http://jabber.org/protocol/muc#user">>, __IgnoreEls, _el), Destroy); _ -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, Destroy) + Status_codes, Items, Invites, Password, Decline, + Destroy) end; decode_muc_user_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"destroy">>, _attrs, _} = _el | _els], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, - decode_muc_user_destroy(__TopXMLNS, __IgnoreEls, - _el)); + Status_codes, Items, Invites, Password, Decline, + decode_muc_destroy(__TopXMLNS, __IgnoreEls, _el)); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, - decode_muc_user_destroy(<<"http://jabber.org/protocol/muc#user">>, - __IgnoreEls, _el)); + Status_codes, Items, Invites, Password, Decline, + decode_muc_destroy(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, + Status_codes, Items, Invites, Password, Decline, + decode_muc_destroy(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); _ -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, Destroy) + Status_codes, Items, Invites, Password, Decline, + Destroy) + end; +decode_muc_user_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"password">>, _attrs, _} = _el | _els], + Status_codes, Items, Invites, Password, Decline, + Destroy) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, + Status_codes, Items, Invites, + decode_muc_password(__TopXMLNS, __IgnoreEls, _el), + Decline, Destroy); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, + Status_codes, Items, Invites, + decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el), + Decline, Destroy); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, + Status_codes, Items, Invites, + decode_muc_password(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el), + Decline, Destroy); + <<"http://jabber.org/protocol/muc">> -> + decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, + Status_codes, Items, Invites, + decode_muc_password(<<"http://jabber.org/protocol/muc">>, + __IgnoreEls, _el), + Decline, Destroy); + _ -> + decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, + Status_codes, Items, Invites, Password, Decline, + Destroy) end; decode_muc_user_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"invite">>, _attrs, _} = _el | _els], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == @@ -7235,21 +8317,23 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls, [decode_muc_user_invite(__TopXMLNS, __IgnoreEls, _el) | Invites], - Decline, Destroy); + Password, Decline, Destroy); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, Status_codes, Items, [decode_muc_user_invite(<<"http://jabber.org/protocol/muc#user">>, __IgnoreEls, _el) | Invites], - Decline, Destroy); + Password, Decline, Destroy); _ -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, Destroy) + Status_codes, Items, Invites, Password, Decline, + Destroy) end; decode_muc_user_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"item">>, _attrs, _} = _el | _els], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == @@ -7259,21 +8343,23 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls, [decode_muc_user_item(__TopXMLNS, __IgnoreEls, _el) | Items], - Invites, Decline, Destroy); + Invites, Password, Decline, Destroy); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, Status_codes, [decode_muc_user_item(<<"http://jabber.org/protocol/muc#user">>, __IgnoreEls, _el) | Items], - Invites, Decline, Destroy); + Invites, Password, Decline, Destroy); _ -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, Destroy) + Status_codes, Items, Invites, Password, Decline, + Destroy) end; decode_muc_user_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"status">>, _attrs, _} = _el | _els], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == @@ -7285,7 +8371,7 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls, undefined -> Status_codes; _new_el -> [_new_el | Status_codes] end, - Items, Invites, Decline, Destroy); + Items, Invites, Password, Decline, Destroy); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, case @@ -7295,24 +8381,18 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls, undefined -> Status_codes; _new_el -> [_new_el | Status_codes] end, - Items, Invites, Decline, Destroy); + Items, Invites, Password, Decline, Destroy); _ -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, Destroy) + Status_codes, Items, Invites, Password, Decline, + Destroy) end; decode_muc_user_els(__TopXMLNS, __IgnoreEls, [_ | _els], - Status_codes, Items, Invites, Decline, Destroy) -> + Status_codes, Items, Invites, Password, Decline, + Destroy) -> decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, - Status_codes, Items, Invites, Decline, Destroy). - -decode_muc_user_attrs(__TopXMLNS, - [{<<"password">>, _val} | _attrs], _Password) -> - decode_muc_user_attrs(__TopXMLNS, _attrs, _val); -decode_muc_user_attrs(__TopXMLNS, [_ | _attrs], - Password) -> - decode_muc_user_attrs(__TopXMLNS, _attrs, Password); -decode_muc_user_attrs(__TopXMLNS, [], Password) -> - decode_muc_user_attr_password(__TopXMLNS, Password). + Status_codes, Items, Invites, Password, Decline, + Destroy). encode_muc_user({muc_user, Decline, Destroy, Invites, Items, Status_codes, Password}, @@ -7321,11 +8401,11 @@ encode_muc_user({muc_user, Decline, Destroy, Invites, lists:reverse('encode_muc_user_$status_codes'(Status_codes, 'encode_muc_user_$items'(Items, 'encode_muc_user_$invites'(Invites, - 'encode_muc_user_$decline'(Decline, - 'encode_muc_user_$destroy'(Destroy, - [])))))), - _attrs = encode_muc_user_attr_password(Password, - _xmlns_attrs), + 'encode_muc_user_$password'(Password, + 'encode_muc_user_$decline'(Decline, + 'encode_muc_user_$destroy'(Destroy, + []))))))), + _attrs = _xmlns_attrs, {xmlel, <<"x">>, _attrs, _els}. 'encode_muc_user_$status_codes'([], _acc) -> _acc; @@ -7345,27 +8425,23 @@ encode_muc_user({muc_user, Decline, Destroy, Invites, 'encode_muc_user_$invites'(_els, [encode_muc_user_invite(Invites, []) | _acc]). +'encode_muc_user_$password'(undefined, _acc) -> _acc; +'encode_muc_user_$password'(Password, _acc) -> + [encode_muc_password(Password, []) | _acc]. + 'encode_muc_user_$decline'(undefined, _acc) -> _acc; 'encode_muc_user_$decline'(Decline, _acc) -> [encode_muc_user_decline(Decline, []) | _acc]. 'encode_muc_user_$destroy'(undefined, _acc) -> _acc; 'encode_muc_user_$destroy'(Destroy, _acc) -> - [encode_muc_user_destroy(Destroy, []) | _acc]. - -decode_muc_user_attr_password(__TopXMLNS, undefined) -> - undefined; -decode_muc_user_attr_password(__TopXMLNS, _val) -> _val. - -encode_muc_user_attr_password(undefined, _acc) -> _acc; -encode_muc_user_attr_password(_val, _acc) -> - [{<<"password">>, _val} | _acc]. + [encode_muc_destroy(Destroy, []) | _acc]. decode_muc_user_item(__TopXMLNS, __IgnoreEls, {xmlel, <<"item">>, _attrs, _els}) -> {Actor, Continue, Reason} = decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els, - undefined, undefined, undefined), + undefined, undefined, <<>>), {Affiliation, Role, Jid, Nick} = decode_muc_user_item_attrs(__TopXMLNS, _attrs, undefined, undefined, undefined, undefined), @@ -7426,13 +8502,23 @@ decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els, Actor, Continue, - decode_muc_user_reason(__TopXMLNS, - __IgnoreEls, _el)); + decode_muc_reason(__TopXMLNS, __IgnoreEls, + _el)); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els, Actor, Continue, - decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>, - __IgnoreEls, _el)); + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els, + Actor, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); _ -> decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els, Actor, Continue, Reason) @@ -7498,9 +8584,9 @@ encode_muc_user_item({muc_item, Actor, Continue, Reason, 'encode_muc_user_item_$continue'(Continue, _acc) -> [encode_muc_user_continue(Continue, []) | _acc]. -'encode_muc_user_item_$reason'(undefined, _acc) -> _acc; +'encode_muc_user_item_$reason'(<<>>, _acc) -> _acc; 'encode_muc_user_item_$reason'(Reason, _acc) -> - [encode_muc_user_reason(Reason, []) | _acc]. + [encode_muc_reason(Reason, []) | _acc]. decode_muc_user_item_attr_affiliation(__TopXMLNS, undefined) -> @@ -7695,39 +8781,72 @@ encode_muc_user_actor_attr_nick(_val, _acc) -> decode_muc_user_invite(__TopXMLNS, __IgnoreEls, {xmlel, <<"invite">>, _attrs, _els}) -> - Reason = decode_muc_user_invite_els(__TopXMLNS, - __IgnoreEls, _els, undefined), + {Continue, Reason} = + decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + _els, undefined, <<>>), {To, From} = decode_muc_user_invite_attrs(__TopXMLNS, _attrs, undefined, undefined), - {muc_invite, Reason, From, To}. + {muc_invite, Reason, From, To, Continue}. decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, [], - Reason) -> - Reason; + Continue, Reason) -> + {Continue, Reason}; decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"reason">>, _attrs, _} = _el | _els], - Reason) -> + Continue, Reason) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + _els, Continue, + decode_muc_reason(__TopXMLNS, __IgnoreEls, + _el)); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + _els, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + _els, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + _els, Continue, + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); + _ -> + decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + _els, Continue, Reason) + end; +decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"continue">>, _attrs, _} = _el | _els], + Continue, Reason) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, _els, - decode_muc_user_reason(__TopXMLNS, - __IgnoreEls, _el)); + decode_muc_user_continue(__TopXMLNS, + __IgnoreEls, _el), + Reason); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, _els, - decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>, - __IgnoreEls, _el)); + decode_muc_user_continue(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el), + Reason); _ -> decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, - _els, Reason) + _els, Continue, Reason) end; decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Reason) -> + [_ | _els], Continue, Reason) -> decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, - _els, Reason). + _els, Continue, Reason). decode_muc_user_invite_attrs(__TopXMLNS, [{<<"to">>, _val} | _attrs], _To, From) -> @@ -7746,20 +8865,26 @@ decode_muc_user_invite_attrs(__TopXMLNS, [], To, {decode_muc_user_invite_attr_to(__TopXMLNS, To), decode_muc_user_invite_attr_from(__TopXMLNS, From)}. -encode_muc_user_invite({muc_invite, Reason, From, To}, +encode_muc_user_invite({muc_invite, Reason, From, To, + Continue}, _xmlns_attrs) -> _els = - lists:reverse('encode_muc_user_invite_$reason'(Reason, - [])), + lists:reverse('encode_muc_user_invite_$continue'(Continue, + 'encode_muc_user_invite_$reason'(Reason, + []))), _attrs = encode_muc_user_invite_attr_from(From, encode_muc_user_invite_attr_to(To, _xmlns_attrs)), {xmlel, <<"invite">>, _attrs, _els}. -'encode_muc_user_invite_$reason'(undefined, _acc) -> +'encode_muc_user_invite_$continue'(undefined, _acc) -> _acc; +'encode_muc_user_invite_$continue'(Continue, _acc) -> + [encode_muc_user_continue(Continue, []) | _acc]. + +'encode_muc_user_invite_$reason'(<<>>, _acc) -> _acc; 'encode_muc_user_invite_$reason'(Reason, _acc) -> - [encode_muc_user_reason(Reason, []) | _acc]. + [encode_muc_reason(Reason, []) | _acc]. decode_muc_user_invite_attr_to(__TopXMLNS, undefined) -> undefined; @@ -7792,69 +8917,125 @@ encode_muc_user_invite_attr_from(undefined, _acc) -> encode_muc_user_invite_attr_from(_val, _acc) -> [{<<"from">>, enc_jid(_val)} | _acc]. -decode_muc_user_destroy(__TopXMLNS, __IgnoreEls, - {xmlel, <<"destroy">>, _attrs, _els}) -> - Reason = decode_muc_user_destroy_els(__TopXMLNS, - __IgnoreEls, _els, undefined), - Jid = decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, - undefined), - {muc_user_destroy, Reason, Jid}. +decode_muc_destroy(__TopXMLNS, __IgnoreEls, + {xmlel, <<"destroy">>, _attrs, _els}) -> + {Password, Reason} = decode_muc_destroy_els(__TopXMLNS, + __IgnoreEls, _els, undefined, + <<>>), + {Jid, Xmlns} = decode_muc_destroy_attrs(__TopXMLNS, + _attrs, undefined, undefined), + {muc_destroy, Xmlns, Jid, Reason, Password}. -decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, [], - Reason) -> - Reason; -decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"reason">>, _attrs, _} = _el | _els], - Reason) -> +decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, [], + Password, Reason) -> + {Password, Reason}; +decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"reason">>, _attrs, _} = _el | _els], + Password, Reason) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == - <<"http://jabber.org/protocol/muc#user">> -> - decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, - _els, - decode_muc_user_reason(__TopXMLNS, - __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#user">>; + __TopXMLNS == + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, + decode_muc_reason(__TopXMLNS, __IgnoreEls, + _el)); <<"http://jabber.org/protocol/muc#user">> -> - decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, - _els, - decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>, - __IgnoreEls, _el)); + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); _ -> - decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Reason) + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, Reason) end; -decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Reason) -> - decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, - _els, Reason). +decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"password">>, _attrs, _} = _el | _els], + Password, Reason) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"">> + when __TopXMLNS == + <<"http://jabber.org/protocol/muc#user">>; + __TopXMLNS == + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_password(__TopXMLNS, __IgnoreEls, + _el), + Reason); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el), + Reason); + <<"http://jabber.org/protocol/muc#user">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_password(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el), + Reason); + <<"http://jabber.org/protocol/muc">> -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + decode_muc_password(<<"http://jabber.org/protocol/muc">>, + __IgnoreEls, _el), + Reason); + _ -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, Reason) + end; +decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Password, Reason) -> + decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els, + Password, Reason). -decode_muc_user_destroy_attrs(__TopXMLNS, - [{<<"jid">>, _val} | _attrs], _Jid) -> - decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, _val); -decode_muc_user_destroy_attrs(__TopXMLNS, [_ | _attrs], - Jid) -> - decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, Jid); -decode_muc_user_destroy_attrs(__TopXMLNS, [], Jid) -> - decode_muc_user_destroy_attr_jid(__TopXMLNS, Jid). +decode_muc_destroy_attrs(__TopXMLNS, + [{<<"jid">>, _val} | _attrs], _Jid, Xmlns) -> + decode_muc_destroy_attrs(__TopXMLNS, _attrs, _val, + Xmlns); +decode_muc_destroy_attrs(__TopXMLNS, + [{<<"xmlns">>, _val} | _attrs], Jid, _Xmlns) -> + decode_muc_destroy_attrs(__TopXMLNS, _attrs, Jid, _val); +decode_muc_destroy_attrs(__TopXMLNS, [_ | _attrs], Jid, + Xmlns) -> + decode_muc_destroy_attrs(__TopXMLNS, _attrs, Jid, + Xmlns); +decode_muc_destroy_attrs(__TopXMLNS, [], Jid, Xmlns) -> + {decode_muc_destroy_attr_jid(__TopXMLNS, Jid), + decode_muc_destroy_attr_xmlns(__TopXMLNS, Xmlns)}. -encode_muc_user_destroy({muc_user_destroy, Reason, Jid}, - _xmlns_attrs) -> +encode_muc_destroy({muc_destroy, Xmlns, Jid, Reason, + Password}, + _xmlns_attrs) -> _els = - lists:reverse('encode_muc_user_destroy_$reason'(Reason, - [])), - _attrs = encode_muc_user_destroy_attr_jid(Jid, - _xmlns_attrs), + lists:reverse('encode_muc_destroy_$password'(Password, + 'encode_muc_destroy_$reason'(Reason, + []))), + _attrs = encode_muc_destroy_attr_xmlns(Xmlns, + encode_muc_destroy_attr_jid(Jid, + _xmlns_attrs)), {xmlel, <<"destroy">>, _attrs, _els}. -'encode_muc_user_destroy_$reason'(undefined, _acc) -> - _acc; -'encode_muc_user_destroy_$reason'(Reason, _acc) -> - [encode_muc_user_reason(Reason, []) | _acc]. +'encode_muc_destroy_$password'(undefined, _acc) -> _acc; +'encode_muc_destroy_$password'(Password, _acc) -> + [encode_muc_password(Password, []) | _acc]. -decode_muc_user_destroy_attr_jid(__TopXMLNS, - undefined) -> +'encode_muc_destroy_$reason'(<<>>, _acc) -> _acc; +'encode_muc_destroy_$reason'(Reason, _acc) -> + [encode_muc_reason(Reason, []) | _acc]. + +decode_muc_destroy_attr_jid(__TopXMLNS, undefined) -> undefined; -decode_muc_user_destroy_attr_jid(__TopXMLNS, _val) -> +decode_muc_destroy_attr_jid(__TopXMLNS, _val) -> case catch dec_jid(_val) of {'EXIT', _} -> erlang:error({xmpp_codec, @@ -7863,15 +9044,22 @@ decode_muc_user_destroy_attr_jid(__TopXMLNS, _val) -> _res -> _res end. -encode_muc_user_destroy_attr_jid(undefined, _acc) -> - _acc; -encode_muc_user_destroy_attr_jid(_val, _acc) -> +encode_muc_destroy_attr_jid(undefined, _acc) -> _acc; +encode_muc_destroy_attr_jid(_val, _acc) -> [{<<"jid">>, enc_jid(_val)} | _acc]. +decode_muc_destroy_attr_xmlns(__TopXMLNS, undefined) -> + undefined; +decode_muc_destroy_attr_xmlns(__TopXMLNS, _val) -> _val. + +encode_muc_destroy_attr_xmlns(undefined, _acc) -> _acc; +encode_muc_destroy_attr_xmlns(_val, _acc) -> + [{<<"xmlns">>, _val} | _acc]. + decode_muc_user_decline(__TopXMLNS, __IgnoreEls, {xmlel, <<"decline">>, _attrs, _els}) -> Reason = decode_muc_user_decline_els(__TopXMLNS, - __IgnoreEls, _els, undefined), + __IgnoreEls, _els, <<>>), {To, From} = decode_muc_user_decline_attrs(__TopXMLNS, _attrs, undefined, undefined), {muc_decline, Reason, From, To}. @@ -7888,13 +9076,23 @@ decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, _els, - decode_muc_user_reason(__TopXMLNS, - __IgnoreEls, _el)); + decode_muc_reason(__TopXMLNS, __IgnoreEls, + _el)); <<"http://jabber.org/protocol/muc#user">> -> decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, _els, - decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>, - __IgnoreEls, _el)); + decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, + _els, + decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>, + __IgnoreEls, _el)); + <<"http://jabber.org/protocol/muc#owner">> -> + decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, + _els, + decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>, + __IgnoreEls, _el)); _ -> decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls, _els, Reason) @@ -7931,10 +9129,9 @@ encode_muc_user_decline({muc_decline, Reason, From, To}, _xmlns_attrs)), {xmlel, <<"decline">>, _attrs, _els}. -'encode_muc_user_decline_$reason'(undefined, _acc) -> - _acc; +'encode_muc_user_decline_$reason'(<<>>, _acc) -> _acc; 'encode_muc_user_decline_$reason'(Reason, _acc) -> - [encode_muc_user_reason(Reason, []) | _acc]. + [encode_muc_reason(Reason, []) | _acc]. decode_muc_user_decline_attr_to(__TopXMLNS, undefined) -> @@ -7969,35 +9166,34 @@ encode_muc_user_decline_attr_from(undefined, _acc) -> encode_muc_user_decline_attr_from(_val, _acc) -> [{<<"from">>, enc_jid(_val)} | _acc]. -decode_muc_user_reason(__TopXMLNS, __IgnoreEls, - {xmlel, <<"reason">>, _attrs, _els}) -> - Cdata = decode_muc_user_reason_els(__TopXMLNS, - __IgnoreEls, _els, <<>>), +decode_muc_reason(__TopXMLNS, __IgnoreEls, + {xmlel, <<"reason">>, _attrs, _els}) -> + Cdata = decode_muc_reason_els(__TopXMLNS, __IgnoreEls, + _els, <<>>), Cdata. -decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, [], - Cdata) -> - decode_muc_user_reason_cdata(__TopXMLNS, Cdata); -decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, - [{xmlcdata, _data} | _els], Cdata) -> - decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, - _els, <>); -decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Cdata) -> - decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, - _els, Cdata). +decode_muc_reason_els(__TopXMLNS, __IgnoreEls, [], + Cdata) -> + decode_muc_reason_cdata(__TopXMLNS, Cdata); +decode_muc_reason_els(__TopXMLNS, __IgnoreEls, + [{xmlcdata, _data} | _els], Cdata) -> + decode_muc_reason_els(__TopXMLNS, __IgnoreEls, _els, + <>); +decode_muc_reason_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Cdata) -> + decode_muc_reason_els(__TopXMLNS, __IgnoreEls, _els, + Cdata). -encode_muc_user_reason(Cdata, _xmlns_attrs) -> - _els = encode_muc_user_reason_cdata(Cdata, []), +encode_muc_reason(Cdata, _xmlns_attrs) -> + _els = encode_muc_reason_cdata(Cdata, []), _attrs = _xmlns_attrs, {xmlel, <<"reason">>, _attrs, _els}. -decode_muc_user_reason_cdata(__TopXMLNS, <<>>) -> - undefined; -decode_muc_user_reason_cdata(__TopXMLNS, _val) -> _val. +decode_muc_reason_cdata(__TopXMLNS, <<>>) -> undefined; +decode_muc_reason_cdata(__TopXMLNS, _val) -> _val. -encode_muc_user_reason_cdata(undefined, _acc) -> _acc; -encode_muc_user_reason_cdata(_val, _acc) -> +encode_muc_reason_cdata(undefined, _acc) -> _acc; +encode_muc_reason_cdata(_val, _acc) -> [{xmlcdata, _val} | _acc]. decode_muc_history(__TopXMLNS, __IgnoreEls, @@ -10554,23 +11750,15 @@ decode_xdata_field_els(__TopXMLNS, __IgnoreEls, case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == <<"jabber:x:data">> -> decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els, - case decode_xdata_field_option(__TopXMLNS, - __IgnoreEls, - _el) - of - undefined -> Options; - _new_el -> [_new_el | Options] - end, + [decode_xdata_field_option(__TopXMLNS, + __IgnoreEls, _el) + | Options], Values, Desc, Required); <<"jabber:x:data">> -> decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els, - case - decode_xdata_field_option(<<"jabber:x:data">>, - __IgnoreEls, _el) - of - undefined -> Options; - _new_el -> [_new_el | Options] - end, + [decode_xdata_field_option(<<"jabber:x:data">>, + __IgnoreEls, _el) + | Options], Values, Desc, Required); _ -> decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els, @@ -10675,7 +11863,9 @@ decode_xdata_field_option(__TopXMLNS, __IgnoreEls, {xmlel, <<"option">>, _attrs, _els}) -> Value = decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls, _els, error), - Value. + Label = decode_xdata_field_option_attrs(__TopXMLNS, + _attrs, undefined), + {xdata_option, Label, Value}. decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls, [], Value) -> @@ -10712,16 +11902,42 @@ decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls, decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls, _els, Value). -encode_xdata_field_option(Value, _xmlns_attrs) -> +decode_xdata_field_option_attrs(__TopXMLNS, + [{<<"label">>, _val} | _attrs], _Label) -> + decode_xdata_field_option_attrs(__TopXMLNS, _attrs, + _val); +decode_xdata_field_option_attrs(__TopXMLNS, + [_ | _attrs], Label) -> + decode_xdata_field_option_attrs(__TopXMLNS, _attrs, + Label); +decode_xdata_field_option_attrs(__TopXMLNS, [], + Label) -> + decode_xdata_field_option_attr_label(__TopXMLNS, Label). + +encode_xdata_field_option({xdata_option, Label, Value}, + _xmlns_attrs) -> _els = lists:reverse('encode_xdata_field_option_$value'(Value, [])), - _attrs = _xmlns_attrs, + _attrs = encode_xdata_field_option_attr_label(Label, + _xmlns_attrs), {xmlel, <<"option">>, _attrs, _els}. 'encode_xdata_field_option_$value'(Value, _acc) -> [encode_xdata_field_value(Value, []) | _acc]. +decode_xdata_field_option_attr_label(__TopXMLNS, + undefined) -> + undefined; +decode_xdata_field_option_attr_label(__TopXMLNS, + _val) -> + _val. + +encode_xdata_field_option_attr_label(undefined, _acc) -> + _acc; +encode_xdata_field_option_attr_label(_val, _acc) -> + [{<<"label">>, _val} | _acc]. + decode_xdata_field_value(__TopXMLNS, __IgnoreEls, {xmlel, <<"value">>, _attrs, _els}) -> Cdata = decode_xdata_field_value_els(__TopXMLNS, @@ -19881,6 +21097,19 @@ decode_error_els(__TopXMLNS, __IgnoreEls, decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text, Reason) end; +decode_error_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"payment-required">>, _attrs, _} = _el + | _els], + Text, Reason) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"urn:ietf:params:xml:ns:xmpp-stanzas">> -> + decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text, + decode_error_payment_required(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>, + __IgnoreEls, _el)); + _ -> + decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text, + Reason) + end; decode_error_els(__TopXMLNS, __IgnoreEls, [{xmlel, <<"policy-violation">>, _attrs, _} = _el | _els], @@ -20132,6 +21361,12 @@ encode_error({error, Type, Code, By, Reason, Text}, [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}]) | _acc]; +'encode_error_$reason'('payment-required' = Reason, + _acc) -> + [encode_error_payment_required(Reason, + [{<<"xmlns">>, + <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}]) + | _acc]; 'encode_error_$reason'('policy-violation' = Reason, _acc) -> [encode_error_policy_violation(Reason, @@ -20438,6 +21673,16 @@ encode_error_policy_violation('policy-violation', _attrs = _xmlns_attrs, {xmlel, <<"policy-violation">>, _attrs, _els}. +decode_error_payment_required(__TopXMLNS, __IgnoreEls, + {xmlel, <<"payment-required">>, _attrs, _els}) -> + 'payment-required'. + +encode_error_payment_required('payment-required', + _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"payment-required">>, _attrs, _els}. + decode_error_not_authorized(__TopXMLNS, __IgnoreEls, {xmlel, <<"not-authorized">>, _attrs, _els}) -> 'not-authorized'. @@ -22069,17 +23314,18 @@ encode_private({private, __Xmls}, _xmlns_attrs) -> decode_disco_items(__TopXMLNS, __IgnoreEls, {xmlel, <<"query">>, _attrs, _els}) -> - Items = decode_disco_items_els(__TopXMLNS, __IgnoreEls, - _els, []), + {Items, Rsm} = decode_disco_items_els(__TopXMLNS, + __IgnoreEls, _els, [], undefined), Node = decode_disco_items_attrs(__TopXMLNS, _attrs, undefined), - {disco_items, Node, Items}. + {disco_items, Node, Items, Rsm}. decode_disco_items_els(__TopXMLNS, __IgnoreEls, [], - Items) -> - lists:reverse(Items); + Items, Rsm) -> + {lists:reverse(Items), Rsm}; decode_disco_items_els(__TopXMLNS, __IgnoreEls, - [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) -> + [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items, + Rsm) -> case get_attr(<<"xmlns">>, _attrs) of <<"">> when __TopXMLNS == @@ -22087,20 +23333,35 @@ decode_disco_items_els(__TopXMLNS, __IgnoreEls, decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els, [decode_disco_item(__TopXMLNS, __IgnoreEls, _el) - | Items]); + | Items], + Rsm); <<"http://jabber.org/protocol/disco#items">> -> decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els, [decode_disco_item(<<"http://jabber.org/protocol/disco#items">>, __IgnoreEls, _el) - | Items]); + | Items], + Rsm); _ -> decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els, - Items) + Items, Rsm) end; decode_disco_items_els(__TopXMLNS, __IgnoreEls, - [_ | _els], Items) -> + [{xmlel, <<"set">>, _attrs, _} = _el | _els], Items, + Rsm) -> + case get_attr(<<"xmlns">>, _attrs) of + <<"http://jabber.org/protocol/rsm">> -> + decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els, + Items, + decode_rsm_set(<<"http://jabber.org/protocol/rsm">>, + __IgnoreEls, _el)); + _ -> + decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els, + Items, Rsm) + end; +decode_disco_items_els(__TopXMLNS, __IgnoreEls, + [_ | _els], Items, Rsm) -> decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els, - Items). + Items, Rsm). decode_disco_items_attrs(__TopXMLNS, [{<<"node">>, _val} | _attrs], _Node) -> @@ -22111,10 +23372,11 @@ decode_disco_items_attrs(__TopXMLNS, [_ | _attrs], decode_disco_items_attrs(__TopXMLNS, [], Node) -> decode_disco_items_attr_node(__TopXMLNS, Node). -encode_disco_items({disco_items, Node, Items}, +encode_disco_items({disco_items, Node, Items, Rsm}, _xmlns_attrs) -> _els = lists:reverse('encode_disco_items_$items'(Items, - [])), + 'encode_disco_items_$rsm'(Rsm, + []))), _attrs = encode_disco_items_attr_node(Node, _xmlns_attrs), {xmlel, <<"query">>, _attrs, _els}. @@ -22124,6 +23386,12 @@ encode_disco_items({disco_items, Node, Items}, 'encode_disco_items_$items'(_els, [encode_disco_item(Items, []) | _acc]). +'encode_disco_items_$rsm'(undefined, _acc) -> _acc; +'encode_disco_items_$rsm'(Rsm, _acc) -> + [encode_rsm_set(Rsm, + [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]) + | _acc]. + decode_disco_items_attr_node(__TopXMLNS, undefined) -> undefined; decode_disco_items_attr_node(__TopXMLNS, _val) -> _val. diff --git a/src/xmpp_util.erl b/src/xmpp_util.erl index 42b251fc1..83f1f4adc 100644 --- a/src/xmpp_util.erl +++ b/src/xmpp_util.erl @@ -10,7 +10,8 @@ %% API -export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1, - is_standalone_chat_state/1]). + is_standalone_chat_state/1, get_xdata_values/2, + has_xdata_var/2]). -include("xmpp.hrl"). @@ -76,6 +77,17 @@ is_standalone_chat_state(Stanza) -> false end. +-spec get_xdata_values(binary(), xdata()) -> [binary()]. +get_xdata_values(Var, #xdata{fields = Fields}) -> + case lists:keyfind(Var, #xdata_field.var, Fields) of + #xdata_field{values = Vals} -> Vals; + false -> [] + end. + +-spec has_xdata_var(binary(), xdata()) -> boolean(). +has_xdata_var(Var, #xdata{fields = Fields}) -> + lists:keymember(Var, #xdata_field.var, Fields). + %%%=================================================================== %%% Internal functions %%%=================================================================== diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 6c74a7ab1..fdf16ed37 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -224,10 +224,12 @@ -xml(disco_items, #elem{name = <<"query">>, xmlns = <<"http://jabber.org/protocol/disco#items">>, - result = {disco_items, '$node', '$items'}, + result = {disco_items, '$node', '$items', '$rsm'}, attrs = [#attr{name = <<"node">>}], refs = [#ref{name = disco_item, - label = '$items'}]}). + label = '$items'}, + #ref{name = rsm_set, min = 0, max = 1, + label = '$rsm'}]}). -xml(private, #elem{name = <<"query">>, @@ -468,6 +470,10 @@ #elem{name = <<"not-authorized">>, xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>, result = 'not-authorized'}). +-xml(error_payment_required, + #elem{name = <<"payment-required">>, + xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>, + result = 'payment-required'}). -xml(error_policy_violation, #elem{name = <<"policy-violation">>, xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>, @@ -561,6 +567,8 @@ min = 0, max = 1, label = '$reason'}, #ref{name = error_not_authorized, min = 0, max = 1, label = '$reason'}, + #ref{name = error_payment_required, + min = 0, max = 1, label = '$reason'}, #ref{name = error_policy_violation, min = 0, max = 1, label = '$reason'}, #ref{name = error_recipient_unavailable, @@ -1582,7 +1590,8 @@ -xml(xdata_field_option, #elem{name = <<"option">>, xmlns = <<"jabber:x:data">>, - result = '$value', + result = {xdata_option, '$label', '$value'}, + attrs = [#attr{name = <<"label">>}], refs = [#ref{name = xdata_field_value, label = '$value', min = 1, max = 1}]}). @@ -1945,9 +1954,11 @@ dec = {dec_utc, []}, enc = {enc_utc, []}}]}). --xml(muc_user_reason, +-xml(muc_reason, #elem{name = <<"reason">>, - xmlns = <<"http://jabber.org/protocol/muc#user">>, + xmlns = [<<"http://jabber.org/protocol/muc#user">>, + <<"http://jabber.org/protocol/muc#admin">>, + <<"http://jabber.org/protocol/muc#owner">>], result = '$cdata'}). -xml(muc_user_decline, @@ -1960,31 +1971,39 @@ #attr{name = <<"from">>, dec = {dec_jid, []}, enc = {enc_jid, []}}], - refs = [#ref{name = muc_user_reason, min = 0, + refs = [#ref{name = muc_reason, min = 0, + default = <<"">>, max = 1, label = '$reason'}]}). --xml(muc_user_destroy, +-xml(muc_destroy, #elem{name = <<"destroy">>, - xmlns = <<"http://jabber.org/protocol/muc#user">>, - result = {muc_user_destroy, '$reason', '$jid'}, - attrs = [#attr{name = <<"jid">>, + xmlns = [<<"http://jabber.org/protocol/muc#user">>, + <<"http://jabber.org/protocol/muc#owner">>], + result = {muc_destroy, '$xmlns', '$jid', '$reason', '$password'}, + attrs = [#attr{name = <<"jid">>, dec = {dec_jid, []}, - enc = {enc_jid, []}}], - refs = [#ref{name = muc_user_reason, min = 0, - max = 1, label = '$reason'}]}). + enc = {enc_jid, []}}, + #attr{name = <<"xmlns">>}], + refs = [#ref{name = muc_reason, min = 0, + default = <<"">>, + max = 1, label = '$reason'}, + #ref{name = muc_password, min = 0, max = 1, + label = '$password'}]}). -xml(muc_user_invite, #elem{name = <<"invite">>, xmlns = <<"http://jabber.org/protocol/muc#user">>, - result = {muc_invite, '$reason', '$from', '$to'}, + result = {muc_invite, '$reason', '$from', '$to', '$continue'}, attrs = [#attr{name = <<"to">>, dec = {dec_jid, []}, enc = {enc_jid, []}}, #attr{name = <<"from">>, dec = {dec_jid, []}, enc = {enc_jid, []}}], - refs = [#ref{name = muc_user_reason, min = 0, - max = 1, label = '$reason'}]}). + refs = [#ref{name = muc_reason, min = 0, default = <<"">>, + max = 1, label = '$reason'}, + #ref{name = muc_user_continue, min = 0, max = 1, + label = '$continue'}]}). -xml(muc_user_actor, #elem{name = <<"actor">>, @@ -2018,7 +2037,7 @@ min = 0, max = 1, label = '$actor'}, #ref{name = muc_user_continue, min = 0, max = 1, label = '$continue'}, - #ref{name = muc_user_reason, + #ref{name = muc_reason, default = <<"">>, min = 0, max = 1, label = '$reason'}], attrs = [#attr{name = <<"affiliation">>, dec = {dec_enum, [[admin, member, none, @@ -2038,44 +2057,56 @@ xmlns = <<"http://jabber.org/protocol/muc#user">>, result = {muc_user, '$decline', '$destroy', '$invites', '$items', '$status_codes', '$password'}, - attrs = [#attr{name = <<"password">>}], refs = [#ref{name = muc_user_decline, min = 0, max = 1, label = '$decline'}, - #ref{name = muc_user_destroy, min = 0, max = 1, + #ref{name = muc_destroy, min = 0, max = 1, label = '$destroy'}, + #ref{name = muc_password, min = 0, max = 1, + label = '$password'}, #ref{name = muc_user_invite, label = '$invites'}, #ref{name = muc_user_item, label = '$items'}, #ref{name = muc_user_status, label = '$status_codes'}]}). --xml(muc_owner_password, +-xml(muc_password, #elem{name = <<"password">>, - xmlns = <<"http://jabber.org/protocol/muc#owner">>, + xmlns = [<<"http://jabber.org/protocol/muc#owner">>, + <<"http://jabber.org/protocol/muc#user">>, + <<"http://jabber.org/protocol/muc">>], result = '$cdata'}). --xml(muc_owner_reason, - #elem{name = <<"reason">>, - xmlns = <<"http://jabber.org/protocol/muc#owner">>, - result = '$cdata'}). - --xml(muc_owner_destroy, - #elem{name = <<"destroy">>, - xmlns = <<"http://jabber.org/protocol/muc#owner">>, - result = {muc_owner_destroy, '$jid', '$reason', '$password'}, - attrs = [#attr{name = <<"jid">>, - dec = {dec_jid, []}, - enc = {enc_jid, []}}], - refs = [#ref{name = muc_owner_password, min = 0, max = 1, - label = '$password'}, - #ref{name = muc_owner_reason, min = 0, max = 1, - label = '$reason'}]}). - -xml(muc_owner, #elem{name = <<"query">>, xmlns = <<"http://jabber.org/protocol/muc#owner">>, - result = {muc_owner, '$destroy', '$config'}, - refs = [#ref{name = muc_owner_destroy, min = 0, max = 1, - label = '$destroy'}, - #ref{name = xdata, min = 0, max = 1, label = '$config'}]}). + result = {muc_owner, '$destroy', '$config', '$items'}, + refs = [#ref{name = muc_destroy, min = 0, max = 1, + label = '$destroy'}, + #ref{name = xdata, min = 0, max = 1, + label = '$config'}, + #ref{name = muc_owner_item, label = '$items'}]}). + +-xml(muc_owner_item, + #elem{name = <<"item">>, + xmlns = <<"http://jabber.org/protocol/muc#owner">>, + result = {muc_item, '$actor', '$continue', '$reason', + '$affiliation', '$role', '$jid', '$nick'}, + refs = [#ref{name = muc_admin_actor, + min = 0, max = 1, label = '$actor'}, + #ref{name = muc_admin_continue, + min = 0, max = 1, label = '$continue'}, + #ref{name = muc_reason, default = <<"">>, + min = 0, max = 1, label = '$reason'}], + attrs = [#attr{name = <<"affiliation">>, + dec = {dec_enum, [[admin, member, none, + outcast, owner]]}, + enc = {enc_enum, []}}, + #attr{name = <<"role">>, + dec = {dec_enum, [[moderator, none, + participant, visitor]]}, + enc = {enc_enum, []}}, + #attr{name = <<"jid">>, + dec = {dec_jid, []}, + enc = {enc_jid, []}}, + #attr{name = <<"nick">>}]}). -xml(muc_admin_item, #elem{name = <<"item">>, @@ -2086,7 +2117,7 @@ min = 0, max = 1, label = '$actor'}, #ref{name = muc_admin_continue, min = 0, max = 1, label = '$continue'}, - #ref{name = muc_admin_reason, + #ref{name = muc_reason, default = <<"">>, min = 0, max = 1, label = '$reason'}], attrs = [#attr{name = <<"affiliation">>, dec = {dec_enum, [[admin, member, none, @@ -2116,11 +2147,6 @@ result = '$thread', attrs = [#attr{name = <<"thread">>}]}). --xml(muc_admin_reason, - #elem{name = <<"reason">>, - xmlns = <<"http://jabber.org/protocol/muc#admin">>, - result = '$cdata'}). - -xml(muc_admin, #elem{name = <<"query">>, xmlns = <<"http://jabber.org/protocol/muc#admin">>, @@ -2131,9 +2157,66 @@ #elem{name = <<"x">>, xmlns = <<"http://jabber.org/protocol/muc">>, result = {muc, '$history', '$password'}, - attrs = [#attr{name = <<"password">>}], refs = [#ref{name = muc_history, min = 0, max = 1, - label = '$history'}]}). + label = '$history'}, + #ref{name = muc_password, min = 0, max = 1, + label = '$password'}]}). + +-xml(muc_unique, + #elem{name = <<"unique">>, + xmlns = <<"http://jabber.org/protocol/muc#unique">>, + result = {muc_unique, '$name'}, + cdata = #cdata{default = <<"">>, + label = '$name'}}). + +-xml(x_conference, + #elem{name = <<"x">>, + xmlns = <<"jabber:x:conference">>, + result = {x_conference, '$jid', '$password', '$reason', + '$continue', '$thread'}, + attrs = [#attr{name = <<"jid">>, + required = true, + dec = {dec_jid, []}, + enc = {enc_jid, []}}, + #attr{name = <<"password">>, default = <<"">>}, + #attr{name = <<"reason">>, default = <<"">>}, + #attr{name = <<"thread">>, default = <<"">>}, + #attr{name = <<"continue">>, + dec = {dec_bool, []}, + enc = {enc_bool, []}}]}). + +-xml(muc_subscription, + #elem{name = <<"subscription">>, + xmlns = <<"urn:xmpp:mucsub:0">>, + result = '$jid', + attrs = [#attr{name = <<"jid">>, + required = true, + dec = {dec_jid, []}, + enc = {enc_jid, []}}]}). + +-xml(muc_subscriptions, + #elem{name = <<"subscriptions">>, + xmlns = <<"urn:xmpp:mucsub:0">>, + result = {muc_subscriptions, '$list'}, + refs = [#ref{name = muc_subscription, label = '$list'}]}). + +-xml(muc_subscribe_event, + #elem{name = <<"event">>, + xmlns = <<"urn:xmpp:mucsub:0">>, + result = '$node', + attrs = [#attr{name = <<"node">>, required = true}]}). + +-xml(muc_subscribe, + #elem{name = <<"subscribe">>, + xmlns = <<"urn:xmpp:mucsub:0">>, + result = {muc_subscribe, '$nick', '$events'}, + attrs = [#attr{name = <<"nick">>, required = true}], + refs = [#ref{name = muc_subscribe_event, label = '$events'}]}). + +-xml(muc_unsubscribe, + #elem{name = <<"unsubscribe">>, + xmlns = <<"urn:xmpp:mucsub:0">>, + result = {muc_unsubscribe}}). -xml(rsm_after, #elem{name = <<"after">>, @@ -2143,7 +2226,7 @@ -xml(rsm_before, #elem{name = <<"before">>, xmlns = <<"http://jabber.org/protocol/rsm">>, - cdata = #cdata{default = none}, + cdata = #cdata{default = <<"">>}, result = '$cdata'}). -xml(rsm_last, @@ -2215,16 +2298,23 @@ dec = {dec_jid, []}, enc = {enc_jid, []}}}). +-xml(mam_withtext, + #elem{name = <<"withtext">>, + xmlns = <<"urn:xmpp:mam:tmp">>, + result = '$cdata', + cdata = #cdata{required = true}}). + -xml(mam_query, #elem{name = <<"query">>, xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>], result = {mam_query, '$xmlns', '$id', '$start', '$end', '$with', - '$rsm', '$xdata'}, + '$withtext', '$rsm', '$xdata'}, attrs = [#attr{name = <<"queryid">>, label = '$id'}, #attr{name = <<"xmlns">>}], refs = [#ref{name = mam_start, min = 0, max = 1, label = '$start'}, #ref{name = mam_end, min = 0, max = 1, label = '$end'}, #ref{name = mam_with, min = 0, max = 1, label = '$with'}, + #ref{name = mam_withtext, min = 0, max = 1, label = '$withtext'}, #ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}, #ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}). @@ -2248,7 +2338,7 @@ -xml(mam_jid, #elem{name = <<"jid">>, - xmlns = <<"urn:xmpp:mam:tmp">>, + xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>], result = '$cdata', cdata = #cdata{required = true, dec = {dec_jid, []}, @@ -2256,15 +2346,15 @@ -xml(mam_never, #elem{name = <<"never">>, - xmlns = <<"urn:xmpp:mam:tmp">>, + xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>], result = '$jids', - refs = [#ref{name = mam_jid, label = '$jids', default = []}]}). + refs = [#ref{name = mam_jid, label = '$jids'}]}). -xml(mam_always, #elem{name = <<"always">>, - xmlns = <<"urn:xmpp:mam:tmp">>, + xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>], result = '$jids', - refs = [#ref{name = mam_jid, label = '$jids', default = []}]}). + refs = [#ref{name = mam_jid, label = '$jids'}]}). -xml(mam_prefs, #elem{name = <<"prefs">>, @@ -2275,9 +2365,9 @@ enc = {enc_enum, []}}, #attr{name = <<"xmlns">>}], refs = [#ref{name = mam_always, label = '$always', - min = 0, max = 1, default = []}, + min = 0, max = 1}, #ref{name = mam_never, label = '$never', - min = 0, max = 1, default = []}]}). + min = 0, max = 1}]}). -xml(mam_fin, #elem{name = <<"fin">>, @@ -2538,8 +2628,8 @@ #attr{name = <<"nick">>, label = '$nick'}]}). --record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | - 'store' | 'no-permanent-store'}). +-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' | + 'no-permanent-store' | 'no-permanent-storage'}). -type hint() :: #hint{}. -xml(hint_no_copy, @@ -2567,6 +2657,11 @@ xmlns = <<"urn:xmpp:hints">>, result = {hint, 'no-permanent-store'}}). +-xml(hint_no_permanent_storage, + #elem{name = <<"no-permanent-storage">>, + xmlns = <<"urn:xmpp:hints">>, + result = {hint, 'no-permanent-storage'}}). + -xml(search_instructions, #elem{name = <<"instructions">>, xmlns = <<"jabber:iq:search">>, @@ -2679,6 +2774,53 @@ dec = {dec_int, [0, infinity]}, enc = {enc_int, []}}]}). +-xml(nick, + #elem{name = <<"nick">>, + xmlns = <<"http://jabber.org/protocol/nick">>, + result = {nick, '$name'}, + cdata = #cdata{label = '$name', + required = true}}). + +-xml(address, + #elem{name = <<"address">>, + xmlns = <<"http://jabber.org/protocol/address">>, + result = {address, '$type', '$jid', '$desc', '$node', '$delivered'}, + attrs = [#attr{name = <<"type">>, + required = true, + dec = {dec_enum, [[bcc, cc, noreply, ofrom, + replyroom, replyto, to]]}, + enc = {enc_enum, []}}, + #attr{name = <<"jid">>, + enc = {enc_jid, []}, + dec = {dec_jid, []}}, + #attr{name = <<"desc">>}, + #attr{name = <<"node">>}, + #attr{name = <<"delivered">>, + enc = {enc_bool, []}, + dec = {dec_bool, []}}]}). + +-xml(addresses, + #elem{name = <<"addresses">>, + xmlns = <<"http://jabber.org/protocol/address">>, + result = {addresses, '$list'}, + %% TODO: 'min' should be '1', but this is not implemented + refs = [#ref{name = address, label = '$list'}]}). + +-xml(stanza_id, + #elem{name = <<"stanza-id">>, + xmlns = <<"urn:xmpp:sid:0">>, + result = {stanza_id, '$by', '$id'}, + attrs = [#attr{name = <<"id">>, required = true}, + #attr{name = <<"by">>, required = true, + enc = {enc_jid, []}, + dec = {dec_jid, []}}]}). + +-xml(client_id, + #elem{name = <<"client-id">>, + xmlns = <<"urn:xmpp:sid:0">>, + result = {client_id, '$id'}, + attrs = [#attr{name = <<"id">>, required = true}]}). + dec_tzo(Val) -> [H1, M1] = str:tokens(Val, <<":">>), H = jlib:binary_to_integer(H1),