1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-06-27 14:04:52 +02:00
Smack/core/src/main/java/org/jivesoftware/smack/ChatManager.java
Lars Noschinski 980047c4e1 Create accurate filter for matching on from address (SMACK-71)
Smack contains two PacketFilters to filter on the from address.
FromContainsFilter simply does a substring match, which is problematic
as explained in SMACK-71. FromMatchesFilter partially fixes this
weakness, but it still uses String#startsWith to filter on bare
addresses. For example, when setup to match all JIDs with bare JID
"foo@example.co", it will still match "foo@example.com".

This commit changes FromMatchesFilter to test equality with the bare
from instead of startsWith with the full from.

Moreover, we convert all uses of FromContainsFilter to FromMatchesFilter
and remove FromContainsFilter. Additionally, the unused ToContainsFilter
(which as the same weaknesses) is removed, too.
2014-03-05 06:48:40 +01:00

351 lines
12 KiB
Java

/**
*
* Copyright 2003-2007 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromMatchesFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.ThreadFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Message.Type;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.collections.ReferenceMap;
/**
* The chat manager keeps track of references to all current chats. It will not hold any references
* in memory on its own so it is necessary to keep a reference to the chat object itself. To be
* made aware of new chats, register a listener by calling {@link #addChatListener(ChatManagerListener)}.
*
* @author Alexander Wenckus
*/
public class ChatManager {
/*
* Sets the default behaviour for allowing 'normal' messages to be used in chats. As some clients don't set
* the message type to chat, the type normal has to be accepted to allow chats with these clients.
*/
private static boolean defaultIsNormalInclude = true;
/*
* Sets the default behaviour for how to match chats when there is NO thread id in the incoming message.
*/
private static MatchMode defaultMatchMode = MatchMode.BARE_JID;
/**
* Defines the different modes under which a match will be attempted with an existing chat when
* the incoming message does not have a thread id.
*/
public enum MatchMode {
/**
* Will not attempt to match, always creates a new chat.
*/
NONE,
/**
* Will match on the JID in the from field of the message.
*/
SUPPLIED_JID,
/**
* Will attempt to match on the JID in the from field, and then attempt the base JID if no match was found.
* This is the most lenient matching.
*/
BARE_JID;
}
/*
* Determines whether incoming messages of type normal can create chats.
*/
private boolean normalIncluded = defaultIsNormalInclude;
/*
* Determines how incoming message with no thread will be matched to existing chats.
*/
private MatchMode matchMode = defaultMatchMode;
/**
* Maps thread ID to chat.
*/
private Map<String, Chat> threadChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
ReferenceMap.WEAK));
/**
* Maps jids to chats
*/
private Map<String, Chat> jidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
ReferenceMap.WEAK));
/**
* Maps base jids to chats
*/
private Map<String, Chat> baseJidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
ReferenceMap.WEAK));
private Set<ChatManagerListener> chatManagerListeners
= new CopyOnWriteArraySet<ChatManagerListener>();
private Map<PacketInterceptor, PacketFilter> interceptors
= new WeakHashMap<PacketInterceptor, PacketFilter>();
private Connection connection;
ChatManager(Connection connection) {
this.connection = connection;
PacketFilter filter = new PacketFilter() {
public boolean accept(Packet packet) {
if (!(packet instanceof Message)) {
return false;
}
Message.Type messageType = ((Message) packet).getType();
return (messageType == Type.chat) || (normalIncluded ? messageType == Type.normal : false);
}
};
// Add a listener for all message packets so that we can deliver
// messages to the best Chat instance available.
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
Message message = (Message) packet;
Chat chat;
if (message.getThread() == null) {
chat = getUserChat(message.getFrom());
}
else {
chat = getThreadChat(message.getThread());
}
if(chat == null) {
chat = createChat(message);
}
deliverMessage(chat, message);
}
}, filter);
}
/**
* Determines whether incoming messages of type <i>normal</i> will be used for creating new chats or matching
* a message to existing ones.
*
* @return true if normal is allowed, false otherwise.
*/
public boolean isNormalIncluded() {
return normalIncluded;
}
/**
* Sets whether to allow incoming messages of type <i>normal</i> to be used for creating new chats or matching
* a message to an existing one.
*
* @param normalIncluded true to allow normal, false otherwise.
*/
public void setNormalIncluded(boolean normalIncluded) {
this.normalIncluded = normalIncluded;
}
/**
* Gets the current mode for matching messages with <b>NO</b> thread id to existing chats.
*
* @return The current mode.
*/
public MatchMode getMatchMode() {
return matchMode;
}
/**
* Sets the mode for matching messages with <b>NO</b> thread id to existing chats.
*
* @param matchMode The mode to set.
*/
public void setMatchMode(MatchMode matchMode) {
this.matchMode = matchMode;
}
/**
* Creates a new chat and returns it.
*
* @param userJID the user this chat is with.
* @param listener the listener which will listen for new messages from this chat.
* @return the created chat.
*/
public Chat createChat(String userJID, MessageListener listener) {
return createChat(userJID, null, listener);
}
/**
* Creates a new chat using the specified thread ID, then returns it.
*
* @param userJID the jid of the user this chat is with
* @param thread the thread of the created chat.
* @param listener the listener to add to the chat
* @return the created chat.
*/
public Chat createChat(String userJID, String thread, MessageListener listener) {
if (thread == null) {
thread = nextID();
}
Chat chat = threadChats.get(thread);
if(chat != null) {
throw new IllegalArgumentException("ThreadID is already used");
}
chat = createChat(userJID, thread, true);
chat.addMessageListener(listener);
return chat;
}
private Chat createChat(String userJID, String threadID, boolean createdLocally) {
Chat chat = new Chat(this, userJID, threadID);
threadChats.put(threadID, chat);
jidChats.put(userJID, chat);
baseJidChats.put(StringUtils.parseBareAddress(userJID), chat);
for(ChatManagerListener listener : chatManagerListeners) {
listener.chatCreated(chat, createdLocally);
}
return chat;
}
private Chat createChat(Message message) {
String threadID = message.getThread();
if(threadID == null) {
threadID = nextID();
}
String userJID = message.getFrom();
return createChat(userJID, threadID, false);
}
/**
* Try to get a matching chat for the given user JID, based on the {@link MatchMode}.
* <li>NONE - return null
* <li>SUPPLIED_JID - match the jid in the from field of the message exactly.
* <li>BARE_JID - if not match for from field, try the bare jid.
*
* @param userJID jid in the from field of message.
* @return Matching chat, or null if no match found.
*/
private Chat getUserChat(String userJID) {
if (matchMode == MatchMode.NONE) {
return null;
}
Chat match = jidChats.get(userJID);
if (match == null && (matchMode == MatchMode.BARE_JID)) {
match = baseJidChats.get(StringUtils.parseBareAddress(userJID));
}
return match;
}
public Chat getThreadChat(String thread) {
return threadChats.get(thread);
}
/**
* Register a new listener with the ChatManager to recieve events related to chats.
*
* @param listener the listener.
*/
public void addChatListener(ChatManagerListener listener) {
chatManagerListeners.add(listener);
}
/**
* Removes a listener, it will no longer be notified of new events related to chats.
*
* @param listener the listener that is being removed
*/
public void removeChatListener(ChatManagerListener listener) {
chatManagerListeners.remove(listener);
}
/**
* Returns an unmodifiable collection of all chat listeners currently registered with this
* manager.
*
* @return an unmodifiable collection of all chat listeners currently registered with this
* manager.
*/
public Collection<ChatManagerListener> getChatListeners() {
return Collections.unmodifiableCollection(chatManagerListeners);
}
private void deliverMessage(Chat chat, Message message) {
// Here we will run any interceptors
chat.deliver(message);
}
void sendMessage(Chat chat, Message message) {
for(Map.Entry<PacketInterceptor, PacketFilter> interceptor : interceptors.entrySet()) {
PacketFilter filter = interceptor.getValue();
if(filter != null && filter.accept(message)) {
interceptor.getKey().interceptPacket(message);
}
}
// Ensure that messages being sent have a proper FROM value
if (message.getFrom() == null) {
message.setFrom(connection.getUser());
}
connection.sendPacket(message);
}
PacketCollector createPacketCollector(Chat chat) {
return connection.createPacketCollector(new AndFilter(new ThreadFilter(chat.getThreadID()),
new FromMatchesFilter(chat.getParticipant())));
}
/**
* Adds an interceptor which intercepts any messages sent through chats.
*
* @param packetInterceptor the interceptor.
*/
public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor) {
addOutgoingMessageInterceptor(packetInterceptor, null);
}
public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor, PacketFilter filter) {
if (packetInterceptor != null) {
interceptors.put(packetInterceptor, filter);
}
}
/**
* Returns a unique id.
*
* @return the next id.
*/
private static String nextID() {
return UUID.randomUUID().toString();
}
public static void setDefaultMatchMode(MatchMode mode) {
defaultMatchMode = mode;
}
public static void setDefaultIsNormalIncluded(boolean allowNormal) {
defaultIsNormalInclude = allowNormal;
}
}