Jid.java

  1package eu.siacs.conversations.xmpp.jid;
  2
  3import net.java.otr4j.session.SessionID;
  4
  5import java.net.IDN;
  6
  7import gnu.inet.encoding.Stringprep;
  8import gnu.inet.encoding.StringprepException;
  9
 10/**
 11 * The `Jid' class provides an immutable representation of a JID.
 12 */
 13public final class Jid {
 14
 15	private final String localpart;
 16	private final String domainpart;
 17	private final String resourcepart;
 18
 19	// It's much more efficient to store the ful JID as well as the parts instead of figuring them
 20	// all out every time (since some characters are displayed but aren't used for comparisons).
 21	private final String displayjid;
 22
 23	public String getLocalpart() {
 24		return localpart;
 25	}
 26
 27	public String getDomainpart() {
 28		return IDN.toUnicode(domainpart);
 29	}
 30
 31	public String getResourcepart() {
 32		return resourcepart;
 33	}
 34
 35	public static Jid fromSessionID(final SessionID id) throws InvalidJidException{
 36		if (id.getUserID().isEmpty()) {
 37			return Jid.fromString(id.getAccountID());
 38		} else {
 39			return Jid.fromString(id.getAccountID()+"/"+id.getUserID());
 40		}
 41	}
 42
 43	public static Jid fromString(final String jid) throws InvalidJidException {
 44		return new Jid(jid);
 45	}
 46
 47	public static Jid fromParts(final String localpart,
 48			final String domainpart,
 49			final String resourcepart) throws InvalidJidException {
 50		String out;
 51		if (localpart == null || localpart.isEmpty()) {
 52			out = domainpart;
 53		} else {
 54			out = localpart + "@" + domainpart;
 55		}
 56		if (resourcepart != null && !resourcepart.isEmpty()) {
 57			out = out + "/" + resourcepart;
 58		}
 59		return new Jid(out);
 60	}
 61
 62	private Jid(final String jid) throws InvalidJidException {
 63		if (jid == null) throw new InvalidJidException(InvalidJidException.IS_NULL);
 64
 65		// Hackish Android way to count the number of chars in a string... should work everywhere.
 66		final int atCount = jid.length() - jid.replace("@", "").length();
 67		final int slashCount = jid.length() - jid.replace("/", "").length();
 68
 69		// Throw an error if there's anything obvious wrong with the JID...
 70		if (jid.isEmpty() || jid.length() > 3071) {
 71			throw new InvalidJidException(InvalidJidException.INVALID_LENGTH);
 72		}
 73
 74		// Go ahead and check if the localpart or resourcepart is empty.
 75		if (jid.startsWith("@") || (jid.endsWith("@") && slashCount == 0) || jid.startsWith("/") || (jid.endsWith("/") && slashCount < 2)) {
 76			throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER);
 77		}
 78
 79		String finaljid;
 80
 81		final int domainpartStart;
 82		final int atLoc = jid.indexOf("@");
 83		final int slashLoc = jid.indexOf("/");
 84		// If there is no "@" in the JID (eg. "example.net" or "example.net/resource")
 85		// or there are one or more "@" signs but they're all in the resourcepart (eg. "example.net/@/rp@"):
 86		if (atCount == 0 || (atCount > 0 && slashLoc != -1 && atLoc > slashLoc)) {
 87			localpart = "";
 88			finaljid = "";
 89			domainpartStart = 0;
 90		} else {
 91			final String lp = jid.substring(0, atLoc);
 92			try {
 93				localpart = Stringprep.nodeprep(lp);
 94			} catch (final StringprepException e) {
 95				throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 96			}
 97			if (localpart.isEmpty() || localpart.length() > 1023) {
 98				throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
 99			}
100			domainpartStart = atLoc + 1;
101			finaljid = lp + "@";
102		}
103
104		final String dp;
105		if (slashCount > 0) {
106			final String rp = jid.substring(slashLoc + 1, jid.length());
107			try {
108				resourcepart = Stringprep.resourceprep(rp);
109			} catch (final StringprepException e) {
110				throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
111			}
112			if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
113				throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
114			}
115			dp = IDN.toUnicode(jid.substring(domainpartStart, slashLoc), IDN.USE_STD3_ASCII_RULES);
116			finaljid = finaljid + dp + "/" + rp;
117		} else {
118			resourcepart = "";
119			dp = IDN.toUnicode(jid.substring(domainpartStart, jid.length()),
120					IDN.USE_STD3_ASCII_RULES);
121			finaljid = finaljid + dp;
122		}
123
124		// Remove trailing "." before storing the domain part.
125		if (dp.endsWith(".")) {
126			try {
127				domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
128			} catch (final IllegalArgumentException e) {
129				throw new InvalidJidException(e);
130			}
131		} else {
132			try {
133				domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
134			} catch (final IllegalArgumentException e) {
135				throw new InvalidJidException(e);
136			}
137		}
138
139		// TODO: Find a proper domain validation library; validate individual parts, separators, etc.
140		if (domainpart.isEmpty() || domainpart.length() > 1023) {
141			throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
142		}
143
144		this.displayjid = finaljid;
145	}
146
147	public Jid toBareJid() {
148		try {
149			return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
150		} catch (final InvalidJidException e) {
151			// This should never happen.
152			return null;
153		}
154	}
155
156	public Jid toDomainJid() {
157		try {
158			return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart());
159		} catch (final InvalidJidException e) {
160			// This should never happen.
161			return null;
162		}
163	}
164
165	@Override
166	public String toString() {
167		return displayjid;
168	}
169
170	@Override
171	public boolean equals(final Object o) {
172		if (this == o) return true;
173		if (o == null || getClass() != o.getClass()) return false;
174
175		final Jid jid = (Jid) o;
176
177		return jid.hashCode() == this.hashCode();
178	}
179
180	@Override
181	public int hashCode() {
182		int result = localpart.hashCode();
183		result = 31 * result + domainpart.hashCode();
184		result = 31 * result + resourcepart.hashCode();
185		return result;
186	}
187
188	public boolean hasLocalpart() {
189		return !localpart.isEmpty();
190	}
191
192	public boolean isBareJid() {
193		return this.resourcepart.isEmpty();
194	}
195
196	public boolean isDomainJid() {
197		return !this.hasLocalpart();
198	}
199}