]> granicus.if.org Git - ejabberd/commitdiff
Allow multiple entry with same nick to MUC rooms (thanks to Magnus Henoch)(EJAB-305)
authorBadlop <badlop@process-one.net>
Tue, 23 Aug 2011 19:52:08 +0000 (21:52 +0200)
committerBadlop <badlop@process-one.net>
Tue, 23 Aug 2011 19:52:08 +0000 (21:52 +0200)
src/mod_muc/mod_muc_room.erl
src/mod_muc/mod_muc_room.hrl

index 6ac949dee393b20b0e06f742cd8c7aaaa9d15928..dc97cf04073b2e476e52f81132b4505aaf00a14c 100644 (file)
@@ -451,8 +451,8 @@ normal_state({route, From, ToNick,
                                ToNick),
                              From, Err);
                        _ ->
-                           ToJID = find_jid_by_nick(ToNick, StateData),
-                           case ToJID of
+                           ToJIDs = find_jids_by_nick(ToNick, StateData),
+                           case ToJIDs of
                                false ->
                                    ErrText = "Recipient is not in the conference room",
                                    Err = exmpp_stanza:reply_with_error(Packet,
@@ -465,7 +465,7 @@ normal_state({route, From, ToNick,
                                      From, Err);
                                _ ->
                                    SrcIsVisitor = is_visitor(From, StateData),
-                                   DstIsModerator = is_moderator(ToJID, StateData),
+                                   DstIsModerator = is_moderator(hd(ToJIDs), StateData),
                                    PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors,
                                    if SrcIsVisitor == false;
                                       PmFromVisitors == anyone;
@@ -473,11 +473,8 @@ normal_state({route, From, ToNick,
                                            {ok, #user{nick = FromNick}} =
                                                ?DICT:find(jlib:jid_tolower(From),
                                                           StateData#state.users),
-                                           ejabberd_router:route(
-                                             jid_replace_resource(
-                                               StateData#state.jid,
-                                               FromNick),
-                                             ToJID, Packet);
+                                             FromNickJID = jid_replace_resource(StateData#state.jid, FromNick),
+                                           [ejabberd_router:route(FromNickJID, ToJID, Packet) || ToJID <- ToJIDs];
                                       true ->
                                            ErrText = "It is not allowed to send private messages",
                                            Err = exmpp_stanza:reply_with_error(Packet,
@@ -953,7 +950,10 @@ process_presence(From, Nick, #xmlel{name = 'presence'} = Packet,
                    true ->
                        NewState =
                            add_user_presence_un(From, Packet, StateData),
-                       send_new_presence(From, NewState),
+                       case ?DICT:find(Nick, StateData#state.nicks) of
+                           {ok, [_, _ | _]} -> ok;
+                           _ -> send_new_presence(From, NewState)
+                       end,
                        Reason = case exmpp_xml:get_element(Packet, 'status') of
                                undefined -> <<>>;
                                Status_el -> exmpp_xml:get_cdata(Status_el)
@@ -977,7 +977,7 @@ process_presence(From, Nick, #xmlel{name = 'presence'} = Packet,
                    true ->
                        case is_nick_change(From, Nick, StateData) of
                            true ->
-                               case {is_nick_exists(Nick, StateData),
+                               case {nick_collision(From, Nick, StateData),
                                      mod_muc:can_use_nick(
                                             StateData#state.host, From, Nick),
                                      {(StateData#state.config)#config.allow_visitor_nickchange,
@@ -1296,21 +1296,31 @@ set_role(JID, Role, StateData) ->
                            []
                    end
            end,
-    Users = case Role of
-               none ->
-                   lists:foldl(fun(J, Us) ->
-                                       ?DICT:erase(J,
-                                                   Us)
-                               end, StateData#state.users, LJIDs);
-               _ ->
-                   lists:foldl(fun(J, Us) ->
-                                       {ok, User} = ?DICT:find(J, Us),
-                                       ?DICT:store(J,
-                                                   User#user{role = Role},
-                                                   Us)
-                               end, StateData#state.users, LJIDs)
-           end,
-    StateData#state{users = Users}.
+    {Users, Nicks}
+       = case Role of
+             none ->
+                 lists:foldl(fun(J, {Us, Ns}) ->
+                                     NewNs =
+                                         case ?DICT:find(J, Us) of
+                                             {ok, #user{nick = Nick}} ->
+                                                 ?DICT:erase(Nick, Ns);
+                                             _ ->
+                                                 Ns
+                                         end,
+                                     {?DICT:erase(J, Us), NewNs}
+                             end,
+                             {StateData#state.users, StateData#state.nicks},
+                             LJIDs);
+             _ ->
+                 {lists:foldl(fun(J, Us) ->
+                                      {ok, User} = ?DICT:find(J, Us),
+                                      ?DICT:store(J,
+                                                  User#user{role = Role},
+                                                  Us)
+                              end, StateData#state.users, LJIDs),
+                  StateData#state.nicks}
+         end,
+    StateData#state{users = Users, nicks = Nicks}.
 
 get_role(JID, StateData) ->
     LJID = jlib:short_prepd_jid(JID),
@@ -1493,8 +1503,19 @@ add_online_user(JID, Nick, Role, StateData) ->
                              role = Role},
                        StateData#state.users),
     add_to_log(join, Nick, StateData),
+    Nicks = ?DICT:update(Nick,
+                        fun(Entry) ->
+                                case lists:member(LJID, Entry) of
+                                    true ->
+                                        Entry;
+                                    false ->
+                                        [LJID|Entry]
+                                end
+                        end,
+                        [LJID],
+                        StateData#state.nicks),
     tab_add_online_user(JID, StateData),
-    StateData#state{users = Users}.
+    StateData#state{users = Users, nicks = Nicks}.
 
 remove_online_user(JID, StateData) ->
        remove_online_user(JID, StateData, <<>>).
@@ -1506,7 +1527,15 @@ remove_online_user(JID, StateData, Reason) ->
     add_to_log(leave, {Nick, Reason}, StateData),
     tab_remove_online_user(JID, StateData),
     Users = ?DICT:erase(LJID, StateData#state.users),
-    StateData#state{users = Users}.
+    Nicks = case ?DICT:find(Nick, StateData#state.nicks) of
+               {ok, [LJID]} ->
+                   ?DICT:erase(Nick, StateData#state.nicks);
+               {ok, U} ->
+                   ?DICT:store(Nick, U -- [LJID], StateData#state.nicks);
+               false ->
+                   StateData#state.nicks
+           end,
+    StateData#state{users = Users, nicks = Nicks}.
 
 
 filter_presence(#xmlel{name = 'presence'} = Packet) ->
@@ -1556,19 +1585,48 @@ add_user_presence_un(JID, Presence, StateData) ->
     StateData#state{users = Users}.
 
 
-is_nick_exists(Nick, StateData) ->
-    ?DICT:fold(fun(_, #user{nick = N}, B) ->
-                      B orelse (N == Nick)
-              end, false, StateData#state.users).
+%% Find and return a list of the full JIDs of the users of Nick.
+%% Return jid record.
+find_jids_by_nick(Nick, StateData) ->
+    case ?DICT:find(Nick, StateData#state.nicks) of
+       {ok, [User]} ->
+           [exmpp_jid:make(User)];
+       {ok, Users} ->
+           [exmpp_jid:make(LJID) || LJID <- Users];
+       error ->
+           false
+    end.
 
 %% @spec(Nick::binary(), StateData) -> JID::jid() | false
+%% Find and return the full JID of the user of Nick with
+%% highest-priority presence.  Return jid record.
 find_jid_by_nick(Nick, StateData) ->
-    ?DICT:fold(fun(_, #user{jid = JID, nick = N}, R) ->
-                      case Nick of
-                          N -> JID;
-                          _ -> R
-                      end
-              end, false, StateData#state.users).
+    case ?DICT:find(Nick, StateData#state.nicks) of
+       {ok, [User]} ->
+           exmpp_jid:make(User);
+       {ok, [FirstUser|Users]} ->
+           #user{last_presence = FirstPresence} =
+               ?DICT:fetch(FirstUser, StateData#state.users),
+           {LJID, _} =
+               lists:foldl(fun(Compare, {HighestUser, HighestPresence}) ->
+                                   #user{last_presence = P1} =
+                                       ?DICT:fetch(Compare, StateData#state.users),
+                                   case higher_presence(P1, HighestPresence) of
+                                       true ->
+                                           {Compare, P1};
+                                       false ->
+                                           {HighestUser, HighestPresence}
+                                   end
+                           end, {FirstUser, FirstPresence}, Users),
+           exmpp_jid:make(LJID);
+       error ->
+           false
+    end.
+
+higher_presence(Pres1, Pres2) ->
+    Pri1 = exmpp_presence:get_priority(Pres1),
+    Pri2 = exmpp_presence:get_priority(Pres2),
+    Pri1 > Pri2.
 
 is_nick_change(JID, Nick, StateData) ->
     LJID = jlib:short_prepd_jid(JID),
@@ -1581,6 +1639,13 @@ is_nick_change(JID, Nick, StateData) ->
            Nick /= OldNick
     end.
 
+nick_collision(User, Nick, StateData) ->
+    UserOfNick = find_jid_by_nick(Nick, StateData),
+    %% if nick is not used, or is used by another resource of the same
+    %% user, it's ok.
+    UserOfNick /= false andalso
+       not exmpp_jid:bare_compare(UserOfNick, User).
+
 add_new_user(From, Nick, Packet, StateData) ->
     Lang = exmpp_stanza:get_lang(Packet),
     MaxUsers = get_max_users(StateData),
@@ -1593,13 +1658,14 @@ add_new_user(From, Nick, Packet, StateData) ->
     MaxConferences = gen_mod:get_module_opt(
                       StateData#state.server_host,
                       mod_muc, max_user_conferences, 10),
+    Collision = nick_collision(From, Nick, StateData),
     case {(ServiceAffiliation == owner orelse
           MaxUsers < 0 orelse
           ((Affiliation == admin orelse Affiliation == owner) andalso
            NUsers < MaxAdminUsers) orelse
           NUsers < MaxUsers) andalso
          NConferences < MaxConferences,
-         is_nick_exists(Nick, StateData),
+         Collision,
          mod_muc:can_use_nick(StateData#state.host, From, Nick),
          get_default_role(Affiliation, StateData)} of
        {false, _, _, _} ->
@@ -1937,13 +2003,19 @@ send_new_presence(NJID, StateData) ->
 %% @spec(NJID::jid(), Reason::binary(), StateData) ->
 send_new_presence({U, S, R}, Reason, StateData) ->
     send_new_presence(exmpp_jid:make(U, S, R), Reason, StateData);
-send_new_presence(NJID, Reason, StateData) ->
+send_new_presence(NJID1, Reason, StateData) ->
+    NJID = {exmpp_jid:node(NJID1), exmpp_jid:domain(NJID1), exmpp_jid:resource(NJID1)},
+    %% First, find the nick associated with this JID.
+    #user{nick = Nick} = ?DICT:fetch(NJID, StateData#state.users),
+    %% Then find the JID using this nick with highest priority.
+    LJID1 = find_jid_by_nick(Nick, StateData),
+    LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
+    %% Then we get the presence data we're supposed to send.
     {ok, #user{jid = RealJID,
-              nick = Nick,
               role = Role,
               last_presence = Presence}} =
-       ?DICT:find(jlib:short_prepd_jid(NJID), StateData#state.users),
-    Affiliation = get_affiliation(NJID, StateData),
+       ?DICT:find(LJID, StateData#state.users),
+    Affiliation = get_affiliation(LJID1, StateData),
     SAffiliation = affiliation_to_binary(Affiliation),
     SRole = role_to_binary(Role),
     lists:foreach(
@@ -2003,21 +2075,23 @@ send_new_presence(NJID, Reason, StateData) ->
 
 
 send_existing_presences(ToJID, StateData) ->
-    LToJID = jlib:short_prepd_jid(ToJID),
+    LToJID = {exmpp_jid:node(ToJID), exmpp_jid:domain(ToJID), exmpp_jid:resource(ToJID)},
     {ok, #user{jid = RealToJID,
               role = Role}} =
        ?DICT:find(LToJID, StateData#state.users),
     lists:foreach(
-      fun({LJID, #user{jid = FromJID,
-                      nick = FromNick,
-                      role = FromRole,
-                      last_presence = Presence
-                     }}) ->
+      fun({FromNick, _Users}) ->
+             LJID1 = find_jid_by_nick(FromNick, StateData),
+             LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
+             #user{jid = FromJID,
+                   role = FromRole,
+                   last_presence = Presence
+                  } = ?DICT:fetch(LJID, StateData#state.users),
              case RealToJID of
                  FromJID ->
                      ok;
                  _ ->
-              {N,D,R} = LJID,
+                     {N,D,R} = LJID,
                      FromAffiliation = get_affiliation(exmpp_jid:make(N,D,R), 
                                                 StateData),
                      ItemAttrs =
@@ -2044,7 +2118,7 @@ send_existing_presences(ToJID, StateData) ->
                        RealToJID,
                        Packet)
              end
-      end, ?DICT:to_list(StateData#state.users)).
+      end, ?DICT:to_list(StateData#state.nicks)).
 
 
 
@@ -2062,12 +2136,37 @@ change_nick(JID, Nick, StateData) ->
           fun(#user{} = User) ->
                   User#user{nick = Nick}
           end, StateData#state.users),
-    NewStateData = StateData#state{users = Users},
-    send_nick_changing(JID, OldNick, NewStateData),
+    OldNickUsers = ?DICT:fetch(OldNick, StateData#state.nicks),
+    NewNickUsers = case ?DICT:find(Nick, StateData#state.nicks) of
+                      {ok, U} -> U;
+                      error -> []
+                  end,
+    %% Send unavailable presence from the old nick if it's no longer
+    %% used.
+    SendOldUnavailable = length(OldNickUsers) == 1,
+    %% If we send unavailable presence from the old nick, we should
+    %% probably send presence from the new nick, in order not to
+    %% confuse clients.  Otherwise, do it only if the new nick was
+    %% unused.
+    SendNewAvailable = SendOldUnavailable orelse
+       NewNickUsers == [],
+    Nicks =
+       case OldNickUsers of
+           [LJID] ->
+               ?DICT:store(Nick, [LJID|NewNickUsers],
+                            ?DICT:erase(OldNick, StateData#state.nicks));
+           [_|_] ->
+               ?DICT:store(Nick, [LJID|NewNickUsers],
+                            ?DICT:store(OldNick, OldNickUsers -- [LJID],
+                                        StateData#state.nicks))
+       end,
+    NewStateData = StateData#state{users = Users, nicks = Nicks},
+    send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable),
     add_to_log(nickchange, {OldNick, Nick}, StateData),
     NewStateData.
 
-send_nick_changing(JID, OldNick, StateData) ->
+send_nick_changing(JID, OldNick, StateData,
+                 SendOldUnavailable, SendNewAvailable) ->
     {ok, #user{jid = RealJID,
               nick = Nick,
               role = Role,
@@ -2120,15 +2219,22 @@ send_nick_changing(JID, OldNick, StateData) ->
                      children =[#xmlel{ns = ?NS_MUC_USER, 
                                        name = 'item', 
                                        attrs = ItemAttrs2}]}),
-
-             ejabberd_router:route(
-               jid_replace_resource(StateData#state.jid, OldNick),
-               Info#user.jid,
-               Packet1),
-             ejabberd_router:route(
-               jid_replace_resource(StateData#state.jid, Nick),
-               Info#user.jid,
-               Packet2)
+             if SendOldUnavailable ->
+                     ejabberd_router:route(
+                       jid_replace_resource(StateData#state.jid, OldNick),
+                       Info#user.jid,
+                       Packet1);
+                true ->
+                     ok
+             end,
+             if SendNewAvailable ->
+                     ejabberd_router:route(
+                       jid_replace_resource(StateData#state.jid, Nick),
+                       Info#user.jid,
+                       Packet2);
+                true ->
+                     ok
+             end
       end, ?DICT:to_list(StateData#state.users)).
 
 
@@ -3530,7 +3636,7 @@ process_iq_disco_info(_From, get, Lang, StateData) ->
                             #xmlcdata{cdata = list_to_binary(Val)}]}]}).
 
 iq_disco_info_extras(Lang, StateData) ->
-    Len = length(?DICT:to_list(StateData#state.users)),
+    Len = ?DICT:size(StateData#state.users),
     RoomDescription = (StateData#state.config)#config.description,
     [#xmlel{ns = ?NS_DATA_FORMS, name = 'x', 
              attrs = [?XMLATTR(<<"type">>, <<"result">>)],
@@ -3795,27 +3901,29 @@ add_to_log(Type, Data, StateData) ->
 tab_add_online_user(JID, StateData) ->
     LUser = exmpp_jid:prep_node(JID),
     LServer = exmpp_jid:prep_domain(JID),
+    LResource = exmpp_jid:prep_resource(JID),
     US = {LUser, LServer},
     Room = StateData#state.room,
     Host = StateData#state.host,
     catch ets:insert(
            muc_online_users,
-           #muc_online_users{us = US, room = Room, host = Host}).
+           #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
 
 
 
 tab_remove_online_user(JID, StateData) when ?IS_JID(JID) ->
  LUser = exmpp_jid:prep_node(JID),
  LServer = exmpp_jid:prep_domain(JID),
-    tab_remove_online_user({LUser, LServer, none},StateData);
+    LResource = exmpp_jid:prep_resource(JID),
+    tab_remove_online_user({LUser, LServer, LResource},StateData);
 
-tab_remove_online_user({LUser, LServer,_}, StateData) ->
+tab_remove_online_user({LUser, LServer, LResource}, StateData) ->
     US = {LUser, LServer},
     Room = StateData#state.room,
     Host = StateData#state.host,
     catch ets:delete_object(
            muc_online_users,
-           #muc_online_users{us = US, room = Room, host = Host}).
+           #muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
 
 tab_count_user(JID) ->
     LUser = exmpp_jid:prep_node(JID),
index 2107959356fe274ded6b07e704db2f369b7fb23a..302ed37eb18a18abf8dccd8dac30f140887c48ca 100644 (file)
@@ -69,6 +69,7 @@
                config = #config{},
                users = ?DICT:new(),
                robots = ?DICT:new(),
+               nicks = ?DICT:new(),
                affiliations = ?DICT:new(),
                history,
                subject = "",
@@ -79,6 +80,7 @@
                room_queue = queue:new()}).
 
 -record(muc_online_users, {us,
+                          resource,
                           room,
                           host}).