2014-11-05 14:00:31 +00:00
|
|
|
package eu.siacs.conversations.xmpp.jid;
|
|
|
|
|
2015-03-04 18:56:24 +00:00
|
|
|
import android.util.LruCache;
|
|
|
|
|
2014-11-10 10:37:31 +00:00
|
|
|
import net.java.otr4j.session.SessionID;
|
|
|
|
|
2014-11-05 14:00:31 +00:00
|
|
|
import java.net.IDN;
|
|
|
|
|
2015-03-05 09:15:04 +00:00
|
|
|
import eu.siacs.conversations.Config;
|
2014-11-05 14:00:31 +00:00
|
|
|
import gnu.inet.encoding.Stringprep;
|
|
|
|
import gnu.inet.encoding.StringprepException;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The `Jid' class provides an immutable representation of a JID.
|
|
|
|
*/
|
|
|
|
public final class Jid {
|
|
|
|
|
2015-03-04 18:56:24 +00:00
|
|
|
private static LruCache<String,Jid> cache = new LruCache<>(1024);
|
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
private final String localpart;
|
|
|
|
private final String domainpart;
|
|
|
|
private final String resourcepart;
|
2014-11-05 14:00:31 +00:00
|
|
|
|
2016-12-01 19:49:18 +00:00
|
|
|
private static final char[] JID_ESCAPING_CHARS = {' ','"','&','\'','/',':','<','>','@','\\'};
|
|
|
|
|
2016-10-20 15:31:46 +00:00
|
|
|
// It's much more efficient to store the ful JID as well as the parts instead of figuring them
|
|
|
|
// all out every time (since some characters are displayed but aren't used for comparisons).
|
|
|
|
private final String displayjid;
|
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
public String getLocalpart() {
|
|
|
|
return localpart;
|
|
|
|
}
|
2014-11-05 14:00:31 +00:00
|
|
|
|
2016-12-01 19:49:18 +00:00
|
|
|
public String getUnescapedLocalpart() {
|
|
|
|
if (localpart == null || !localpart.contains("\\")) {
|
|
|
|
return localpart;
|
|
|
|
} else {
|
|
|
|
String localpart = this.localpart;
|
|
|
|
for(char c : JID_ESCAPING_CHARS) {
|
|
|
|
localpart = localpart.replace(String.format ("\\%02x", (int)c),String.valueOf(c));
|
|
|
|
}
|
|
|
|
return localpart;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
public String getDomainpart() {
|
|
|
|
return IDN.toUnicode(domainpart);
|
|
|
|
}
|
2014-11-05 14:00:31 +00:00
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
public String getResourcepart() {
|
|
|
|
return resourcepart;
|
|
|
|
}
|
2014-11-05 14:00:31 +00:00
|
|
|
|
2014-12-21 20:43:58 +00:00
|
|
|
public static Jid fromSessionID(final SessionID id) throws InvalidJidException{
|
2014-11-10 10:37:31 +00:00
|
|
|
if (id.getUserID().isEmpty()) {
|
|
|
|
return Jid.fromString(id.getAccountID());
|
|
|
|
} else {
|
|
|
|
return Jid.fromString(id.getAccountID()+"/"+id.getUserID());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
public static Jid fromString(final String jid) throws InvalidJidException {
|
2015-03-05 21:11:59 +00:00
|
|
|
return Jid.fromString(jid, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Jid fromString(final String jid, final boolean safe) throws InvalidJidException {
|
|
|
|
return new Jid(jid, safe);
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static Jid fromParts(final String localpart,
|
|
|
|
final String domainpart,
|
|
|
|
final String resourcepart) throws InvalidJidException {
|
|
|
|
String out;
|
|
|
|
if (localpart == null || localpart.isEmpty()) {
|
|
|
|
out = domainpart;
|
|
|
|
} else {
|
|
|
|
out = localpart + "@" + domainpart;
|
|
|
|
}
|
|
|
|
if (resourcepart != null && !resourcepart.isEmpty()) {
|
|
|
|
out = out + "/" + resourcepart;
|
|
|
|
}
|
2015-03-05 21:11:59 +00:00
|
|
|
return new Jid(out, false);
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
|
2015-03-05 21:11:59 +00:00
|
|
|
private Jid(final String jid, final boolean safe) throws InvalidJidException {
|
2015-02-26 15:56:07 +00:00
|
|
|
if (jid == null) throw new InvalidJidException(InvalidJidException.IS_NULL);
|
|
|
|
|
2015-03-04 18:56:24 +00:00
|
|
|
Jid fromCache = Jid.cache.get(jid);
|
|
|
|
if (fromCache != null) {
|
2016-10-20 15:31:46 +00:00
|
|
|
displayjid = fromCache.displayjid;
|
2015-03-04 18:56:24 +00:00
|
|
|
localpart = fromCache.localpart;
|
|
|
|
domainpart = fromCache.domainpart;
|
|
|
|
resourcepart = fromCache.resourcepart;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
// Hackish Android way to count the number of chars in a string... should work everywhere.
|
|
|
|
final int atCount = jid.length() - jid.replace("@", "").length();
|
|
|
|
final int slashCount = jid.length() - jid.replace("/", "").length();
|
|
|
|
|
|
|
|
// Throw an error if there's anything obvious wrong with the JID...
|
|
|
|
if (jid.isEmpty() || jid.length() > 3071) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.INVALID_LENGTH);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go ahead and check if the localpart or resourcepart is empty.
|
2014-12-02 15:17:50 +00:00
|
|
|
if (jid.startsWith("@") || (jid.endsWith("@") && slashCount == 0) || jid.startsWith("/") || (jid.endsWith("/") && slashCount < 2)) {
|
2014-12-02 15:09:31 +00:00
|
|
|
throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER);
|
2014-12-02 15:17:50 +00:00
|
|
|
}
|
2014-12-02 15:09:31 +00:00
|
|
|
|
2016-10-20 15:31:46 +00:00
|
|
|
String finaljid;
|
|
|
|
|
2014-12-02 15:09:31 +00:00
|
|
|
final int domainpartStart;
|
|
|
|
final int atLoc = jid.indexOf("@");
|
|
|
|
final int slashLoc = jid.indexOf("/");
|
|
|
|
// If there is no "@" in the JID (eg. "example.net" or "example.net/resource")
|
|
|
|
// or there are one or more "@" signs but they're all in the resourcepart (eg. "example.net/@/rp@"):
|
|
|
|
if (atCount == 0 || (atCount > 0 && slashLoc != -1 && atLoc > slashLoc)) {
|
|
|
|
localpart = "";
|
2016-10-20 15:31:46 +00:00
|
|
|
finaljid = "";
|
2014-12-02 15:09:31 +00:00
|
|
|
domainpartStart = 0;
|
|
|
|
} else {
|
|
|
|
final String lp = jid.substring(0, atLoc);
|
|
|
|
try {
|
2015-03-05 21:11:59 +00:00
|
|
|
localpart = Config.DISABLE_STRING_PREP || safe ? lp : Stringprep.nodeprep(lp);
|
2014-12-02 15:09:31 +00:00
|
|
|
} catch (final StringprepException e) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
|
|
|
|
}
|
|
|
|
if (localpart.isEmpty() || localpart.length() > 1023) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
|
|
|
|
}
|
|
|
|
domainpartStart = atLoc + 1;
|
2016-10-20 15:31:46 +00:00
|
|
|
finaljid = lp + "@";
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
final String dp;
|
|
|
|
if (slashCount > 0) {
|
|
|
|
final String rp = jid.substring(slashLoc + 1, jid.length());
|
|
|
|
try {
|
2015-03-05 21:11:59 +00:00
|
|
|
resourcepart = Config.DISABLE_STRING_PREP || safe ? rp : Stringprep.resourceprep(rp);
|
2014-12-02 15:09:31 +00:00
|
|
|
} catch (final StringprepException e) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
|
|
|
|
}
|
|
|
|
if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
|
|
|
|
}
|
2015-05-14 13:25:52 +00:00
|
|
|
try {
|
|
|
|
dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, slashLoc)), IDN.USE_STD3_ASCII_RULES);
|
|
|
|
} catch (final StringprepException e) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
|
|
|
|
}
|
2016-10-20 15:31:46 +00:00
|
|
|
finaljid = finaljid + dp + "/" + rp;
|
2014-12-02 15:09:31 +00:00
|
|
|
} else {
|
|
|
|
resourcepart = "";
|
2015-05-14 13:25:52 +00:00
|
|
|
try{
|
|
|
|
dp = IDN.toUnicode(Stringprep.nameprep(jid.substring(domainpartStart, jid.length())), IDN.USE_STD3_ASCII_RULES);
|
|
|
|
} catch (final StringprepException e) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
|
|
|
|
}
|
2016-10-20 15:31:46 +00:00
|
|
|
finaljid = finaljid + dp;
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove trailing "." before storing the domain part.
|
|
|
|
if (dp.endsWith(".")) {
|
|
|
|
try {
|
|
|
|
domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
|
|
|
|
} catch (final IllegalArgumentException e) {
|
|
|
|
throw new InvalidJidException(e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
|
|
|
|
} catch (final IllegalArgumentException e) {
|
|
|
|
throw new InvalidJidException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Find a proper domain validation library; validate individual parts, separators, etc.
|
|
|
|
if (domainpart.isEmpty() || domainpart.length() > 1023) {
|
|
|
|
throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
|
|
|
|
}
|
|
|
|
|
2015-03-05 09:15:04 +00:00
|
|
|
Jid.cache.put(jid, this);
|
2016-10-20 15:31:46 +00:00
|
|
|
|
|
|
|
this.displayjid = finaljid;
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public Jid toBareJid() {
|
|
|
|
try {
|
|
|
|
return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
|
|
|
|
} catch (final InvalidJidException e) {
|
|
|
|
// This should never happen.
|
2015-10-11 14:01:03 +00:00
|
|
|
throw new AssertionError("Jid " + this.toString() + " invalid");
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Jid toDomainJid() {
|
|
|
|
try {
|
|
|
|
return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart());
|
|
|
|
} catch (final InvalidJidException e) {
|
|
|
|
// This should never happen.
|
2015-10-11 14:01:03 +00:00
|
|
|
throw new AssertionError("Jid " + this.toString() + " invalid");
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
2016-10-20 15:31:46 +00:00
|
|
|
return displayjid;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String toPreppedString() {
|
2016-09-12 20:48:51 +00:00
|
|
|
String out;
|
|
|
|
if (hasLocalpart()) {
|
|
|
|
out = localpart + '@' + domainpart;
|
|
|
|
} else {
|
|
|
|
out = domainpart;
|
|
|
|
}
|
|
|
|
if (!resourcepart.isEmpty()) {
|
|
|
|
out += '/'+resourcepart;
|
|
|
|
}
|
|
|
|
return out;
|
2014-12-02 15:09:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(final Object o) {
|
|
|
|
if (this == o) return true;
|
|
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
|
|
|
|
|
|
final Jid jid = (Jid) o;
|
|
|
|
|
|
|
|
return jid.hashCode() == this.hashCode();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
int result = localpart.hashCode();
|
|
|
|
result = 31 * result + domainpart.hashCode();
|
|
|
|
result = 31 * result + resourcepart.hashCode();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasLocalpart() {
|
|
|
|
return !localpart.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isBareJid() {
|
|
|
|
return this.resourcepart.isEmpty();
|
|
|
|
}
|
2014-12-21 20:43:58 +00:00
|
|
|
|
|
|
|
public boolean isDomainJid() {
|
|
|
|
return !this.hasLocalpart();
|
|
|
|
}
|
2014-11-05 14:00:31 +00:00
|
|
|
}
|