All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xmpp.packet.JID Maven / Gradle / Ivy

Go to download

Tinder is a Java based XMPP library, providing an implementation for XMPP stanzas and components. Tinders origins lie in code that's shared between Jive Software's Openfire and Whack implementations. The implementation that's provided in Tinder hasn't been written again from scratch. Instead, code has been moved from the original projects into Tinder, preserving al of the existing features and functionality. Most of the code that's now in Tinder is based on the org.xmpp package implementation that previously existed in Openfire and Whack. This is the code that defines classes such as Packet, JID, IQ, Component and their extensions. Additionally, some multi-purpose code (such as the DataForm and Result Set Management implementations have been moved to Tinder as well.

There is a newer version: 1.3.0
Show newest version
/**
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution, or a commercial license
 * agreement with Jive.
 */

package org.xmpp.packet;

import org.jivesoftware.stringprep.IDNA;
import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException;
import org.jivesoftware.util.cache.ExternalizableUtil;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * An XMPP address (JID). A JID is made up of a node (generally a username), a domain,
 * and a resource. The node and resource are optional; domain is required. In simple
 * ABNF form:
 *
 * 
    jid = [ node "@" ] domain [ "/" resource ]
* * Some sample JID's: * * * Each allowable portion of a JID (node, domain, and resource) must not be more * than 1023 bytes in length, resulting in a maximum total size (including the '@' * and '/' separators) of 3071 bytes. * * @author Matt Tucker */ public class JID implements Comparable, Externalizable { // Stringprep operations are very expensive. Therefore, we cache node, domain and // resource values that have already had stringprep applied so that we can check // incoming values against the cache. private static final Cache NODEPREP_CACHE = new Cache(10000); private static final Cache DOMAINPREP_CACHE = new Cache(500); private static final Cache RESOURCEPREP_CACHE = new Cache(10000); private String node; private String domain; private String resource; private String cachedFullJID; private String cachedBareJID; /** * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106). * Escaping replaces characters prohibited by node-prep with escape sequences, * as follows:

* *

* * * * * * * * * * * *
Unescaped CharacterEncoded Sequence
<space>\20
"\22
&\26
'\27
/\2f
:\3a
<\3c
>\3e
@\40
\\5c

* * This process is useful when the node comes from an external source that doesn't * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because * the <space> character isn't a valid part of a node, the username should * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\[email protected]" * after case-folding, etc. has been applied).

* * All node escaping and un-escaping must be performed manually at the appropriate * time; the JID class will not escape or un-escape automatically. * * @param node the node. * @return the escaped version of the node. */ public static String escapeNode(String node) { if (node == null) { return null; } StringBuilder buf = new StringBuilder(node.length() + 8); for (int i=0, n=node.length(); i': buf.append("\\3e"); break; case '@': buf.append("\\40"); break; case '\\': buf.append("\\5c"); break; default: { if (Character.isWhitespace(c)) { buf.append("\\20"); } else { buf.append(c); } } } } return buf.toString(); } /** * Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).

* Escaping replaces characters prohibited by node-prep with escape sequences, * as follows:

* *

* * * * * * * * * * * *
Unescaped CharacterEncoded Sequence
<space>\20
"\22
&\26
'\27
/\2f
:\3a
<\3c
>\3e
@\40
\\5c

* * This process is useful when the node comes from an external source that doesn't * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because * the <space> character isn't a valid part of a node, the username should * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\[email protected]" * after case-folding, etc. has been applied).

* * All node escaping and un-escaping must be performed manually at the appropriate * time; the JID class will not escape or un-escape automatically. * * @param node the escaped version of the node. * @return the un-escaped version of the node. */ public static String unescapeNode(String node) { if (node == null) { return null; } char [] nodeChars = node.toCharArray(); StringBuilder buf = new StringBuilder(nodeChars.length); for (int i=0, n=nodeChars.length; i'); i+=2; break compare; } } else if (c2 == '4') { if (c3 == '0') { buf.append("@"); i+=2; break compare; } } else if (c2 == '5') { if (c3 == 'c') { buf.append("\\"); i+=2; break compare; } } } buf.append(c); } } return buf.toString(); } public static String resourceprep(String resource) throws StringprepException { String answer = resource; if (!RESOURCEPREP_CACHE.contains(resource)) { answer = Stringprep.resourceprep(resource); // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. if (answer != null && answer.length()*2 > 1023) { return answer; } RESOURCEPREP_CACHE.put(answer); } return answer; } /** * Constructor added for Externalizable. Do not use this constructor. */ public JID() { } /** * Constructs a JID from it's String representation. * * @param jid a valid JID. * @throws IllegalArgumentException if the JID is not valid. */ public JID(String jid) { if (jid == null) { throw new NullPointerException("JID cannot be null"); } String[] parts = getParts(jid); init(parts[0], parts[1], parts[2]); } /** * Constructs a JID given a node, domain, and resource. * * @param node the node. * @param domain the domain, which must not be null. * @param resource the resource. * @throws IllegalArgumentException if the JID is not valid. */ public JID(String node, String domain, String resource) { if (domain == null) { throw new NullPointerException("Domain cannot be null"); } init(node, domain, resource); } /** * Constructs a JID given a node, domain, and resource being able to specify if stringprep * should be applied or not. * * @param node the node. * @param domain the domain, which must not be null. * @param resource the resource. * @param skipStringprep true if stringprep should not be applied. * @throws IllegalArgumentException if the JID is not valid. */ public JID(String node, String domain, String resource, boolean skipStringprep) { if (domain == null) { throw new NullPointerException("Domain cannot be null"); } if (skipStringprep) { this.node = node; this.domain = domain; this.resource = resource; // Cache the bare and full JID String representation updateCache(); } else { init(node, domain, resource); } } /** * Returns a String array with the parsed node, domain and resource. * No Stringprep is performed while parsing the textual representation. * * @param jid the textual JID representation. * @return a string array with the parsed node, domain and resource. */ static String[] getParts(String jid) { String[] parts = new String[3]; String node = null , domain, resource; if (jid == null) { return parts; } int atIndex = jid.indexOf("@"); int slashIndex = jid.indexOf("/"); // Node if (atIndex > 0) { node = jid.substring(0, atIndex); } // Domain if (atIndex + 1 > jid.length()) { throw new IllegalArgumentException("JID with empty domain not valid"); } if (atIndex < 0) { if (slashIndex > 0) { domain = jid.substring(0, slashIndex); } else { domain = jid; } } else { if (slashIndex > 0) { domain = jid.substring(atIndex + 1, slashIndex); } else { domain = jid.substring(atIndex + 1); } } // Resource if (slashIndex + 1 > jid.length() || slashIndex < 0) { resource = null; } else { resource = jid.substring(slashIndex + 1); } parts[0] = node; parts[1] = domain; parts[2] = resource; return parts; } /** * Transforms the JID parts using the appropriate Stringprep profiles, then * validates them. If they are fully valid, the field values are saved, otherwise * an IllegalArgumentException is thrown. * * @param node the node. * @param domain the domain. * @param resource the resource. */ private void init(String node, String domain, String resource) { // Set node and resource to null if they are the empty string. if (node != null && node.equals("")) { node = null; } if (resource != null && resource.equals("")) { resource = null; } // Stringprep (node prep, resourceprep, etc). try { if (!NODEPREP_CACHE.contains(node)) { this.node = Stringprep.nodeprep(node); // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. if (this.node != null && this.node.length()*2 > 1023) { throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " + "Size is " + (this.node.length() * 2) + " bytes."); } NODEPREP_CACHE.put(this.node); } else { this.node = node; } // XMPP specifies that domains should be run through IDNA and // that they should be run through nameprep before doing any // comparisons. We always run the domain through nameprep to // make comparisons easier later. if (!DOMAINPREP_CACHE.contains(domain)) { this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false); // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. if (this.domain.length()*2 > 1023) { throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " + "Size is " + (this.domain.length() * 2) + " bytes."); } DOMAINPREP_CACHE.put(this.domain); } else { this.domain = domain; } this.resource = resourceprep(resource); // Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes. if (resource != null && resource.length()*2 > 1023) { throw new IllegalArgumentException("Resource cannot be larger than 1023 bytes. " + "Size is " + (resource.length() * 2) + " bytes."); } // Cache the bare and full JID String representation updateCache(); } catch (Exception e) { StringBuilder buf = new StringBuilder(); if (node != null) { buf.append(node).append("@"); } buf.append(domain); if (resource != null) { buf.append("/").append(resource); } throw new IllegalArgumentException("Illegal JID: " + buf.toString(), e); } } private void updateCache() { // Cache the bare JID StringBuilder buf = new StringBuilder(40); if (node != null) { buf.append(node).append("@"); } buf.append(domain); cachedBareJID = buf.toString(); // Cache the full JID if (resource != null) { buf.append("/").append(resource); cachedFullJID = buf.toString(); } else { cachedFullJID = cachedBareJID; } } /** * Returns the node, or null if this JID does not contain node information. * * @return the node. */ public String getNode() { return node; } /** * Returns the domain. * * @return the domain. */ public String getDomain() { return domain; } /** * Returns the resource, or null if this JID does not contain resource information. * * @return the resource. */ public String getResource() { return resource; } /** * Returns the String representation of the bare JID, which is the JID with * resource information removed. * * @return the bare JID. */ public String toBareJID() { return cachedBareJID; } /** * Returns a String representation of the JID. * * @return a String representation of the JID. */ public String toString() { return cachedFullJID; } public int hashCode() { return toString().hashCode(); } public boolean equals(Object object) { if (!(object instanceof JID)) { return false; } if (this == object) { return true; } JID jid = (JID)object; // Node. If node isn't null, compare. if (node != null) { if (!node.equals(jid.node)) { return false; } } // Otherwise, jid.node must be null. else if (jid.node != null) { return false; } // Compare domain, which must be null. if (!domain.equals(jid.domain)) { return false; } // Resource. If resource isn't null, compare. if (resource != null) { if (!resource.equals(jid.resource)) { return false; } } // Otherwise, jid.resource must be null. else if (jid.resource != null) { return false; } // Passed all checks, so equal. return true; } public int compareTo(JID jid) { // Comparison order is domain, node, resource. int compare = domain.compareTo(jid.domain); if (compare == 0) { String myNode = node != null ? node : ""; String hisNode = jid.node != null ? jid.node : ""; compare = myNode.compareTo(hisNode); } if (compare == 0) { String myResource = resource != null ? resource : ""; String hisResource = jid.resource != null ? jid.resource : ""; compare = myResource.compareTo(hisResource); } return compare; } /** * Returns true if two JID's are equivalent. The JID components are compared using * the following rules:

    *
  • Nodes are normalized using nodeprep (case insensitive). *
  • Domains are normalized using IDNA and then nameprep (case insensitive). *
  • Resources are normalized using resourceprep (case sensitive).
* * These normalization rules ensure, for example, that * [email protected]/home is considered equal to [email protected]/home. * * @param jid1 a JID. * @param jid2 a JID. * @return true if the JIDs are equivalent; false otherwise. * @throws IllegalArgumentException if either JID is not valid. */ public static boolean equals(String jid1, String jid2) { return new JID(jid1).equals(new JID(jid2)); } /** * A simple cache class with limited functionality. It uses an FIFO * eviction policy to keep the cache at a maximum size. This class * offers acceptable thread safety for the purpose of the parent class. * * @author Guus der Kinderen, [email protected] */ private static class Cache { /** Cannot add null values in ConcurrentHashMap... */ private final static Object NULL = new Object(); /** Queue that records insertion order. Used to implement FIFO behavior. */ private final Queue fifoQueue = new ConcurrentLinkedQueue(); /** Values are cached in a hashmap for fast lookup. **/ private final Map cachedValues = new ConcurrentHashMap(); /** Cache capacity */ private int maxSize; /** * Constructs a new capacity-bound cache. * * @param maxSize * The maximum number of elements that the cache can contain. */ public Cache(int maxSize) { this.maxSize = maxSize; } /** * Adds a new element to the cache. The cache is pruned if the maximum * capacity has been reached. * * @param entry * The element to be added to the cache */ public void put(K entry) { synchronized (entry) { // add value to the cache if (cachedValues.put(entry, NULL) == null) { // ensure that queue doesn't contain duplicates. fifoQueue.offer(entry); } } // apply eviction policy if required. while (cachedValues.size() > maxSize) { cachedValues.remove(fifoQueue.poll()); } } /** * Checks if the cache contains an element. * * @param entry * The element to check for. * @return true if the cache currently contains the entry, * false otherwise. */ public boolean contains(K entry) { // no need to nodeprep null - it'll result in null. if (entry == null) { return true; } // Note that this method will need to record access if // we want to switch to LRU. return cachedValues.containsKey(entry); } } public void writeExternal(ObjectOutput out) throws IOException { ExternalizableUtil.getInstance().writeSafeUTF(out, toString()); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { String jid = ExternalizableUtil.getInstance().readSafeUTF(in); String[] parts = getParts(jid); this.node = parts[0]; this.domain = parts[1]; this.resource = parts[2]; // Cache the bare and full JID String representation updateCache(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy