diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index a504acfc5..5151f728d 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -177,7 +177,6 @@ db_tests() -> privacy, blocking, vcard, - muc_single, pubsub, test_unregister]}, {test_roster_subscribe, [parallel], @@ -185,6 +184,8 @@ db_tests() -> roster_subscribe_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, + {test_muc, [parallel], + [muc_master, muc_slave]}, {test_roster_remove, [parallel], [roster_remove_master, roster_remove_slave]}]. @@ -202,13 +203,14 @@ db_tests(riak) -> privacy, blocking, vcard, - muc_single, test_unregister]}, {test_roster_subscribe, [parallel], [roster_subscribe_master, roster_subscribe_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, + {test_muc, [parallel], + [muc_master, muc_slave]}, {test_roster_remove, [parallel], [roster_remove_master, roster_remove_slave]}]; @@ -225,7 +227,6 @@ db_tests(_) -> privacy, blocking, vcard, - muc_single, pubsub, test_unregister]}, {test_roster_subscribe, [parallel], @@ -233,6 +234,8 @@ db_tests(_) -> roster_subscribe_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, + {test_muc, [parallel], + [muc_master, muc_slave]}, {test_roster_remove, [parallel], [roster_remove_master, roster_remove_slave]}]. @@ -916,15 +919,22 @@ proxy65_slave(Config) -> socks5_recv(Socks5, Data), disconnect(Config). -muc_single(Config) -> +muc_master(Config) -> MyJID = my_jid(Config), + PeerJID = ?config(slave, Config), + PeerBareJID = jlib:jid_remove_resource(PeerJID), + PeerJIDStr = jlib:jid_to_string(PeerJID), MUC = muc_jid(Config), Room = muc_room_jid(Config), - Nick = ?config(user, Config), - NickJID = jlib:jid_replace_resource(Room, Nick), + MyNick = ?config(master_nick, Config), + MyNickJID = jlib:jid_replace_resource(Room, MyNick), + PeerNick = ?config(slave_nick, Config), + PeerNickJID = jlib:jid_replace_resource(Room, PeerNick), + Subject = ?config(room_subject, Config), + Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>), true = is_feature_advertised(Config, ?NS_MUC, MUC), %% Joining - send(Config, #presence{to = NickJID, sub_els = [#muc{}]}), + send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), %% As per XEP-0045 we MUST receive stanzas in the following order: %% 1. In-room presence from other occupants %% 2. In-room presence from the joining entity itself (so-called "self-presence") @@ -933,7 +943,7 @@ muc_single(Config) -> %% 5. Live messages, presence updates, new user joins, etc. %% As this is the newly created room, we receive only the 2nd stanza. #presence{ - from = NickJID, + from = MyNickJID, sub_els = [#muc_user{ status_codes = Codes, items = [#muc_item{role = moderator, @@ -941,8 +951,7 @@ muc_single(Config) -> affiliation = owner}]}]} = recv(), %% 110 -> Inform user that presence refers to itself %% 201 -> Inform user that a new room has been created - true = lists:member(110, Codes), - true = lists:member(201, Codes), + [110, 201] = lists:sort(Codes), %% Request the configuration #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} = send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}], @@ -959,10 +968,14 @@ muc_single(Config) -> [<<"Trying to break the server">>]; <<"muc#roomconfig_persistentroom">> -> [<<"1">>]; - <<"muc#roomconfig_changesubject">> -> - [<<"0">>]; - <<"muc#roomconfig_allowinvites">> -> - [<<"1">>]; + <<"members_by_default">> -> + [<<"0">>]; + <<"muc#roomconfig_allowvoicerequests">> -> + [<<"1">>]; + <<"public_list">> -> + [<<"1">>]; + <<"muc#roomconfig_publicroom">> -> + [<<"1">>]; _ -> [] end, @@ -974,20 +987,230 @@ muc_single(Config) -> end, RoomCfg#xdata.fields), NewRoomCfg = #xdata{type = submit, fields = NewFields}, %% BUG: We should not receive any sub_els! - %% TODO: fix this crap in ejabberd. #iq{type = result, sub_els = [_|_]} = send_recv(Config, #iq{type = set, to = Room, sub_els = [#muc_owner{config = NewRoomCfg}]}), %% Set subject send(Config, #message{to = Room, type = groupchat, - body = [#text{data = <<"Subject">>}]}), - #message{from = NickJID, type = groupchat, - body = [#text{data = <<"Subject">>}]} = recv(), - %% Leaving - send(Config, #presence{type = unavailable, to = NickJID}), - #presence{from = NickJID, type = unavailable, - sub_els = [#muc_user{status_codes = NewCodes}]} = recv(), - true = lists:member(110, NewCodes), + body = [#text{data = Subject}]}), + #message{from = MyNickJID, type = groupchat, + body = [#text{data = Subject}]} = recv(), + %% Sending messages (and thus, populating history for our peer) + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + I = send(Config, #message{to = Room, body = [Text], + type = groupchat}), + #message{from = MyNickJID, id = I, + type = groupchat, + body = [Text]} = recv() + end, lists:seq(1, 5)), + %% Inviting the peer + send(Config, #message{to = Room, type = normal, + sub_els = + [#muc_user{ + invites = + [#muc_invite{to = PeerJID}]}]}), + %% Peer is joining + #presence{from = PeerNickJID, + sub_els = [#muc_user{ + items = [#muc_item{role = visitor, + jid = PeerJID, + affiliation = none}]}]} = recv(), + %% Receiving a voice request + #message{from = Room, + sub_els = [#xdata{type = form, + instructions = [_], + fields = VoiceReqFs}]} = recv(), + %% Approving the voice request + ReplyVoiceReqFs = + lists:map( + fun(#xdata_field{var = Var, values = OrigVals}) -> + Vals = case {Var, OrigVals} of + {<<"FORM_TYPE">>, + [<<"http://jabber.org/protocol/muc#request">>]} -> + OrigVals; + {<<"muc#role">>, [<<"participant">>]} -> + [<<"participant">>]; + {<<"muc#jid">>, [PeerJIDStr]} -> + [PeerJIDStr]; + {<<"muc#roomnick">>, [PeerNick]} -> + [PeerNick]; + {<<"muc#request_allow">>, [<<"0">>]} -> + [<<"1">>] + end, + #xdata_field{values = Vals, var = Var} + end, VoiceReqFs), + send(Config, #message{to = Room, + sub_els = [#xdata{type = submit, + fields = ReplyVoiceReqFs}]}), + %% Peer is becoming a participant + #presence{from = PeerNickJID, + sub_els = [#muc_user{ + items = [#muc_item{role = participant, + jid = PeerJID, + affiliation = none}]}]} = recv(), + %% Receive private message from the peer + #message{from = PeerNickJID, body = [#text{data = Subject}]} = recv(), + %% Granting membership to the peer and localhost server + I1 = send(Config, + #iq{type = set, to = Room, + sub_els = + [#muc_admin{ + items = [#muc_item{jid = Localhost, + affiliation = member}, + #muc_item{nick = PeerNick, + jid = PeerBareJID, + affiliation = member}]}]}), + %% Peer became a member + #presence{from = PeerNickJID, + sub_els = [#muc_user{ + items = [#muc_item{affiliation = member, + jid = PeerJID, + role = participant}]}]} = recv(), + %% BUG: We should not receive any sub_els! + #iq{type = result, id = I1, sub_els = [_|_]} = recv(), + %% Receive groupchat message from the peer + #message{type = groupchat, from = PeerNickJID, + body = [#text{data = Subject}]} = recv(), + %% Kick the peer + I2 = send(Config, + #iq{type = set, to = Room, + sub_els = [#muc_admin{ + items = [#muc_item{nick = PeerNick, + role = none}]}]}), + %% Got notification the peer is kicked + %% 307 -> Inform user that he or she has been kicked from the room + #presence{from = PeerNickJID, + sub_els = [#muc_user{ + status_codes = [307], + items = [#muc_item{affiliation = member, + jid = PeerJID, + role = none}]}]} = recv(), + %% BUG: We should not receive any sub_els! + #iq{type = result, id = I2, sub_els = [_|_]} = recv(), + %% Destroying the room + I3 = send(Config, + #iq{type = set, to = Room, + sub_els = [#muc_owner{ + destroy = #muc_owner_destroy{ + reason = Subject}}]}), + %% Kicked off + #presence{from = MyNickJID, type = unavailable, + sub_els = [#muc_user{items = [#muc_item{role = none, + affiliation = none}], + destroy = #muc_user_destroy{ + reason = Subject}}]} = recv(), + %% BUG: We should not receive any sub_els! + #iq{type = result, id = I3, sub_els = [_|_]} = recv(), + disconnect(Config). + +muc_slave(Config) -> + MyJID = my_jid(Config), + MyBareJID = jlib:jid_remove_resource(MyJID), + PeerJID = ?config(master, Config), + MUC = muc_jid(Config), + Room = muc_room_jid(Config), + MyNick = ?config(slave_nick, Config), + MyNickJID = jlib:jid_replace_resource(Room, MyNick), + PeerNick = ?config(master_nick, Config), + PeerNickJID = jlib:jid_replace_resource(Room, PeerNick), + Subject = ?config(room_subject, Config), + Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>), + %% Receive an invite from the peer + #message{from = Room, type = normal, + sub_els = + [#muc_user{invites = + [#muc_invite{from = PeerJID}]}]} = recv(), + %% But before joining we discover the MUC service first + %% to check if the room is in the disco list + #iq{type = result, + sub_els = [#disco_items{items = [#disco_item{jid = Room}]}]} = + send_recv(Config, #iq{type = get, to = MUC, + sub_els = [#disco_items{}]}), + %% Now check if the peer is in the room. We check this via disco#items + #iq{type = result, + sub_els = [#disco_items{items = [#disco_item{jid = PeerNickJID, + name = PeerNick}]}]} = + send_recv(Config, #iq{type = get, to = Room, + sub_els = [#disco_items{}]}), + %% Now joining + send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), + %% First presence is from the participant, i.e. from the peer + #presence{ + from = PeerNickJID, + sub_els = [#muc_user{ + status_codes = [], + items = [#muc_item{role = moderator, + affiliation = owner}]}]} = recv(), + %% The next is the self-presence (code 110 means it) + #presence{ + from = MyNickJID, + sub_els = [#muc_user{ + status_codes = [110], + items = [#muc_item{role = visitor, + affiliation = none}]}]} = recv(), + %% Receive the room subject + #message{from = PeerNickJID, type = groupchat, + body = [#text{data = Subject}], + sub_els = [#delay{}, #legacy_delay{}]} = recv(), + %% Receive MUC history + lists:foreach( + fun(N) -> + Text = #text{data = jlib:integer_to_binary(N)}, + #message{from = PeerNickJID, + type = groupchat, + body = [Text], + sub_els = [#delay{}, #legacy_delay{}]} = recv() + end, lists:seq(1, 5)), + %% Sending a voice request + VoiceReq = #xdata{ + type = submit, + fields = + [#xdata_field{ + var = <<"FORM_TYPE">>, + values = [<<"http://jabber.org/protocol/muc#request">>]}, + #xdata_field{ + var = <<"muc#role">>, + type = 'text-single', + values = [<<"participant">>]}]}, + send(Config, #message{to = Room, sub_els = [VoiceReq]}), + %% Becoming a participant + #presence{from = MyNickJID, + sub_els = [#muc_user{ + items = [#muc_item{role = participant, + affiliation = none}]}]} = recv(), + %% Sending private message to the peer + send(Config, #message{to = PeerNickJID, + body = [#text{data = Subject}]}), + %% Becoming a member + #presence{from = MyNickJID, + sub_els = [#muc_user{ + items = [#muc_item{role = participant, + affiliation = member}]}]} = recv(), + %% Retrieving a member list + #iq{type = result, sub_els = [#muc_admin{items = MemberList}]} = + send_recv(Config, + #iq{type = get, to = Room, + sub_els = + [#muc_admin{items = [#muc_item{affiliation = member}]}]}), + [#muc_item{affiliation = member, + jid = Localhost}, + #muc_item{affiliation = member, + jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList), + %% Sending groupchat message + send(Config, #message{to = Room, type = groupchat, + body = [#text{data = Subject}]}), + %% Receive this message back + #message{type = groupchat, from = MyNickJID, + body = [#text{data = Subject}]} = recv(), + %% We're kicked off + %% 307 -> Inform user that he or she has been kicked from the room + #presence{from = MyNickJID, type = unavailable, + sub_els = [#muc_user{ + status_codes = [307], + items = [#muc_item{affiliation = member, + role = none}]}]} = recv(), disconnect(Config). offline_master(Config) -> diff --git a/test/suite.erl b/test/suite.erl index c35592c80..3f3432159 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -39,6 +39,9 @@ init_config(Config) -> {server_host, "localhost"}, {server, ?COMMON_VHOST}, {user, <<"test_single">>}, + {master_nick, <<"master_nick">>}, + {slave_nick, <<"slave_nick">>}, + {room_subject, <<"hello, world!">>}, {certfile, CertFile}, {base_dir, BaseDir}, {resource, <<"resource">>}, diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl index 6cfcc7a22..9ffa16354 100644 --- a/tools/xmpp_codec.erl +++ b/tools/xmpp_codec.erl @@ -14,6 +14,21 @@ decode({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> decode_muc(_el); + {<<"query">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + decode_muc_admin(_el); + {<<"reason">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + decode_muc_admin_reason(_el); + {<<"continue">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + decode_muc_admin_continue(_el); + {<<"actor">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + decode_muc_admin_actor(_el); + {<<"item">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + decode_muc_admin_item(_el); {<<"query">>, <<"http://jabber.org/protocol/muc#owner">>} -> decode_muc_owner(_el); @@ -691,6 +706,21 @@ decode({xmlel, _name, _attrs, _} = _el) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of {<<"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; + {<<"actor">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + true; + {<<"item">>, + <<"http://jabber.org/protocol/muc#admin">>} -> + true; {<<"query">>, <<"http://jabber.org/protocol/muc#owner">>} -> true; @@ -1240,6 +1270,10 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) -> encode({muc, _, _} = X) -> encode_muc(X, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}]); +encode({muc_admin, _} = Query) -> + encode_muc_admin(Query, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/muc#admin">>}]); encode({muc_owner, _, _} = Query) -> encode_muc_owner(Query, [{<<"xmlns">>, @@ -1252,14 +1286,6 @@ encode({muc_user, _, _, _, _, _, _} = X) -> encode_muc_user(X, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc#user">>}]); -encode({muc_item, _, _, _, _, _, _, _} = Item) -> - encode_muc_user_item(Item, - [{<<"xmlns">>, - <<"http://jabber.org/protocol/muc#user">>}]); -encode({muc_actor, _, _} = Actor) -> - encode_muc_user_actor(Actor, - [{<<"xmlns">>, - <<"http://jabber.org/protocol/muc#user">>}]); encode({muc_invite, _, _, _} = Invite) -> encode_muc_user_invite(Invite, [{<<"xmlns">>, @@ -1742,14 +1768,15 @@ pp(muc_history, 4) -> pp(muc_decline, 3) -> [reason, from, to]; pp(muc_user_destroy, 2) -> [reason, jid]; pp(muc_invite, 3) -> [reason, from, to]; -pp(muc_actor, 2) -> [jid, nick]; -pp(muc_item, 7) -> - [actor, continue, reason, affiliation, role, jid, nick]; 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_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(_, _) -> no. @@ -1834,6 +1861,319 @@ encode_muc_attr_password(undefined, _acc) -> _acc; encode_muc_attr_password(_val, _acc) -> [{<<"password">>, _val} | _acc]. +decode_muc_admin({xmlel, <<"query">>, _attrs, _els}) -> + Items = decode_muc_admin_els(_els, []), + {muc_admin, Items}. + +decode_muc_admin_els([], Items) -> lists:reverse(Items); +decode_muc_admin_els([{xmlel, <<"item">>, _attrs, _} = + _el + | _els], + Items) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; + _xmlns == <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_admin_els(_els, + [decode_muc_admin_item(_el) | Items]); + true -> decode_muc_admin_els(_els, Items) + end; +decode_muc_admin_els([_ | _els], Items) -> + decode_muc_admin_els(_els, Items). + +encode_muc_admin({muc_admin, Items}, _xmlns_attrs) -> + _els = 'encode_muc_admin_$items'(Items, []), + _attrs = _xmlns_attrs, + {xmlel, <<"query">>, _attrs, _els}. + +'encode_muc_admin_$items'([], _acc) -> _acc; +'encode_muc_admin_$items'([Items | _els], _acc) -> + 'encode_muc_admin_$items'(_els, + [encode_muc_admin_item(Items, []) | _acc]). + +decode_muc_admin_reason({xmlel, <<"reason">>, _attrs, + _els}) -> + Cdata = decode_muc_admin_reason_els(_els, <<>>), Cdata. + +decode_muc_admin_reason_els([], Cdata) -> + decode_muc_admin_reason_cdata(Cdata); +decode_muc_admin_reason_els([{xmlcdata, _data} | _els], + Cdata) -> + decode_muc_admin_reason_els(_els, + <>); +decode_muc_admin_reason_els([_ | _els], Cdata) -> + decode_muc_admin_reason_els(_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(<<>>) -> undefined; +decode_muc_admin_reason_cdata(_val) -> _val. + +encode_muc_admin_reason_cdata(undefined, _acc) -> _acc; +encode_muc_admin_reason_cdata(_val, _acc) -> + [{xmlcdata, _val} | _acc]. + +decode_muc_admin_continue({xmlel, <<"continue">>, + _attrs, _els}) -> + Thread = decode_muc_admin_continue_attrs(_attrs, + undefined), + Thread. + +decode_muc_admin_continue_attrs([{<<"thread">>, _val} + | _attrs], + _Thread) -> + decode_muc_admin_continue_attrs(_attrs, _val); +decode_muc_admin_continue_attrs([_ | _attrs], Thread) -> + decode_muc_admin_continue_attrs(_attrs, Thread); +decode_muc_admin_continue_attrs([], Thread) -> + decode_muc_admin_continue_attr_thread(Thread). + +encode_muc_admin_continue(Thread, _xmlns_attrs) -> + _els = [], + _attrs = encode_muc_admin_continue_attr_thread(Thread, + _xmlns_attrs), + {xmlel, <<"continue">>, _attrs, _els}. + +decode_muc_admin_continue_attr_thread(undefined) -> + undefined; +decode_muc_admin_continue_attr_thread(_val) -> _val. + +encode_muc_admin_continue_attr_thread(undefined, + _acc) -> + _acc; +encode_muc_admin_continue_attr_thread(_val, _acc) -> + [{<<"thread">>, _val} | _acc]. + +decode_muc_admin_actor({xmlel, <<"actor">>, _attrs, + _els}) -> + {Jid, Nick} = decode_muc_admin_actor_attrs(_attrs, + undefined, undefined), + {muc_actor, Jid, Nick}. + +decode_muc_admin_actor_attrs([{<<"jid">>, _val} + | _attrs], + _Jid, Nick) -> + decode_muc_admin_actor_attrs(_attrs, _val, Nick); +decode_muc_admin_actor_attrs([{<<"nick">>, _val} + | _attrs], + Jid, _Nick) -> + decode_muc_admin_actor_attrs(_attrs, Jid, _val); +decode_muc_admin_actor_attrs([_ | _attrs], Jid, Nick) -> + decode_muc_admin_actor_attrs(_attrs, Jid, Nick); +decode_muc_admin_actor_attrs([], Jid, Nick) -> + {decode_muc_admin_actor_attr_jid(Jid), + decode_muc_admin_actor_attr_nick(Nick)}. + +encode_muc_admin_actor({muc_actor, Jid, Nick}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_muc_admin_actor_attr_nick(Nick, + encode_muc_admin_actor_attr_jid(Jid, + _xmlns_attrs)), + {xmlel, <<"actor">>, _attrs, _els}. + +decode_muc_admin_actor_attr_jid(undefined) -> undefined; +decode_muc_admin_actor_attr_jid(_val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"actor">>, + <<"http://jabber.org/protocol/muc#admin">>}}); + _res -> _res + end. + +encode_muc_admin_actor_attr_jid(undefined, _acc) -> + _acc; +encode_muc_admin_actor_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_muc_admin_actor_attr_nick(undefined) -> + undefined; +decode_muc_admin_actor_attr_nick(_val) -> _val. + +encode_muc_admin_actor_attr_nick(undefined, _acc) -> + _acc; +encode_muc_admin_actor_attr_nick(_val, _acc) -> + [{<<"nick">>, _val} | _acc]. + +decode_muc_admin_item({xmlel, <<"item">>, _attrs, + _els}) -> + {Actor, Continue, Reason} = + decode_muc_admin_item_els(_els, undefined, undefined, + undefined), + {Affiliation, Role, Jid, Nick} = + decode_muc_admin_item_attrs(_attrs, undefined, + undefined, undefined, undefined), + {muc_item, Actor, Continue, Reason, Affiliation, Role, + Jid, Nick}. + +decode_muc_admin_item_els([], Actor, Continue, + Reason) -> + {Actor, Continue, Reason}; +decode_muc_admin_item_els([{xmlel, <<"actor">>, _attrs, + _} = + _el + | _els], + Actor, Continue, Reason) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; + _xmlns == <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_admin_item_els(_els, + decode_muc_admin_actor(_el), Continue, + Reason); + true -> + decode_muc_admin_item_els(_els, Actor, Continue, Reason) + end; +decode_muc_admin_item_els([{xmlel, <<"continue">>, + _attrs, _} = + _el + | _els], + Actor, Continue, Reason) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; + _xmlns == <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_admin_item_els(_els, Actor, + decode_muc_admin_continue(_el), Reason); + true -> + decode_muc_admin_item_els(_els, Actor, Continue, Reason) + end; +decode_muc_admin_item_els([{xmlel, <<"reason">>, _attrs, + _} = + _el + | _els], + Actor, Continue, Reason) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; + _xmlns == <<"http://jabber.org/protocol/muc#admin">> -> + decode_muc_admin_item_els(_els, Actor, Continue, + decode_muc_admin_reason(_el)); + true -> + decode_muc_admin_item_els(_els, Actor, Continue, Reason) + end; +decode_muc_admin_item_els([_ | _els], Actor, Continue, + Reason) -> + decode_muc_admin_item_els(_els, Actor, Continue, + Reason). + +decode_muc_admin_item_attrs([{<<"affiliation">>, _val} + | _attrs], + _Affiliation, Role, Jid, Nick) -> + decode_muc_admin_item_attrs(_attrs, _val, Role, Jid, + Nick); +decode_muc_admin_item_attrs([{<<"role">>, _val} + | _attrs], + Affiliation, _Role, Jid, Nick) -> + decode_muc_admin_item_attrs(_attrs, Affiliation, _val, + Jid, Nick); +decode_muc_admin_item_attrs([{<<"jid">>, _val} + | _attrs], + Affiliation, Role, _Jid, Nick) -> + decode_muc_admin_item_attrs(_attrs, Affiliation, Role, + _val, Nick); +decode_muc_admin_item_attrs([{<<"nick">>, _val} + | _attrs], + Affiliation, Role, Jid, _Nick) -> + decode_muc_admin_item_attrs(_attrs, Affiliation, Role, + Jid, _val); +decode_muc_admin_item_attrs([_ | _attrs], Affiliation, + Role, Jid, Nick) -> + decode_muc_admin_item_attrs(_attrs, Affiliation, Role, + Jid, Nick); +decode_muc_admin_item_attrs([], Affiliation, Role, Jid, + Nick) -> + {decode_muc_admin_item_attr_affiliation(Affiliation), + decode_muc_admin_item_attr_role(Role), + decode_muc_admin_item_attr_jid(Jid), + decode_muc_admin_item_attr_nick(Nick)}. + +encode_muc_admin_item({muc_item, Actor, Continue, + Reason, Affiliation, Role, Jid, Nick}, + _xmlns_attrs) -> + _els = 'encode_muc_admin_item_$reason'(Reason, + 'encode_muc_admin_item_$continue'(Continue, + 'encode_muc_admin_item_$actor'(Actor, + []))), + _attrs = encode_muc_admin_item_attr_nick(Nick, + encode_muc_admin_item_attr_jid(Jid, + encode_muc_admin_item_attr_role(Role, + encode_muc_admin_item_attr_affiliation(Affiliation, + _xmlns_attrs)))), + {xmlel, <<"item">>, _attrs, _els}. + +'encode_muc_admin_item_$actor'(undefined, _acc) -> _acc; +'encode_muc_admin_item_$actor'(Actor, _acc) -> + [encode_muc_admin_actor(Actor, []) | _acc]. + +'encode_muc_admin_item_$continue'(undefined, _acc) -> + _acc; +'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'(Reason, _acc) -> + [encode_muc_admin_reason(Reason, []) | _acc]. + +decode_muc_admin_item_attr_affiliation(undefined) -> + undefined; +decode_muc_admin_item_attr_affiliation(_val) -> + case catch dec_enum(_val, + [admin, member, none, outcast, owner]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"affiliation">>, <<"item">>, + <<"http://jabber.org/protocol/muc#admin">>}}); + _res -> _res + end. + +encode_muc_admin_item_attr_affiliation(undefined, + _acc) -> + _acc; +encode_muc_admin_item_attr_affiliation(_val, _acc) -> + [{<<"affiliation">>, enc_enum(_val)} | _acc]. + +decode_muc_admin_item_attr_role(undefined) -> undefined; +decode_muc_admin_item_attr_role(_val) -> + case catch dec_enum(_val, + [moderator, none, participant, visitor]) + of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"role">>, <<"item">>, + <<"http://jabber.org/protocol/muc#admin">>}}); + _res -> _res + end. + +encode_muc_admin_item_attr_role(undefined, _acc) -> + _acc; +encode_muc_admin_item_attr_role(_val, _acc) -> + [{<<"role">>, enc_enum(_val)} | _acc]. + +decode_muc_admin_item_attr_jid(undefined) -> undefined; +decode_muc_admin_item_attr_jid(_val) -> + case catch dec_jid(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"jid">>, <<"item">>, + <<"http://jabber.org/protocol/muc#admin">>}}); + _res -> _res + end. + +encode_muc_admin_item_attr_jid(undefined, _acc) -> _acc; +encode_muc_admin_item_attr_jid(_val, _acc) -> + [{<<"jid">>, enc_jid(_val)} | _acc]. + +decode_muc_admin_item_attr_nick(undefined) -> undefined; +decode_muc_admin_item_attr_nick(_val) -> _val. + +encode_muc_admin_item_attr_nick(undefined, _acc) -> + _acc; +encode_muc_admin_item_attr_nick(_val, _acc) -> + [{<<"nick">>, _val} | _acc]. + decode_muc_owner({xmlel, <<"query">>, _attrs, _els}) -> {Config, Destroy} = decode_muc_owner_els(_els, undefined, undefined), diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl index 004d8a8e2..b9b6aa9eb 100644 --- a/tools/xmpp_codec.hrl +++ b/tools/xmpp_codec.hrl @@ -61,9 +61,6 @@ node :: binary(), publisher :: binary()}). --record(muc_actor, {jid :: any(), - nick :: binary()}). - -record(stat, {name :: binary(), units :: binary(), value :: binary(), @@ -126,6 +123,9 @@ subid :: binary(), type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}). +-record(muc_actor, {jid :: any(), + nick :: binary()}). + -record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}). -record(caps, {hash :: binary(), @@ -208,6 +208,8 @@ status_codes = [] :: [pos_integer()], password :: binary()}). +-record(muc_admin, {items = [] :: [#muc_item{}]}). + -record(bytestreams, {hosts = [] :: [#streamhost{}], used :: any(), activate :: any(), diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 29e9ef91f..62f661381 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -1974,6 +1974,56 @@ label = '$destroy'}, #ref{name = xdata, min = 0, max = 1, label = '$config'}]}). +-xml(muc_admin_item, + #elem{name = <<"item">>, + xmlns = <<"http://jabber.org/protocol/muc#admin">>, + 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_admin_reason, + 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_actor, + #elem{name = <<"actor">>, + xmlns = <<"http://jabber.org/protocol/muc#admin">>, + result = {muc_actor, '$jid', '$nick'}, + attrs = [#attr{name = <<"jid">>, + dec = {dec_jid, []}, + enc = {enc_jid, []}}, + #attr{name = <<"nick">>}]}). + +-xml(muc_admin_continue, + #elem{name = <<"continue">>, + xmlns = <<"http://jabber.org/protocol/muc#admin">>, + 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">>, + result = {muc_admin, '$items'}, + refs = [#ref{name = muc_admin_item, label = '$items'}]}). + -xml(muc, #elem{name = <<"x">>, xmlns = <<"http://jabber.org/protocol/muc">>,