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		// Hackish Android way to count the number of chars in a string... should work everywhere.
 64		final int atCount = jid.length() - jid.replace("@", "").length();
 65		final int slashCount = jid.length() - jid.replace("/", "").length();
 66
 67		// Throw an error if there's anything obvious wrong with the JID...
 68		if (jid.isEmpty() || jid.length() > 3071) {
 69			throw new InvalidJidException(InvalidJidException.INVALID_LENGTH);
 70		}
 71
 72		// Go ahead and check if the localpart or resourcepart is empty.
 73		if (jid.startsWith("@") || (jid.endsWith("@") && slashCount == 0) || jid.startsWith("/") || (jid.endsWith("/") && slashCount < 2)) {
 74			throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER);
 75		}
 76
 77		String finaljid;
 78
 79		final int domainpartStart;
 80		final int atLoc = jid.indexOf("@");
 81		final int slashLoc = jid.indexOf("/");
 82		// If there is no "@" in the JID (eg. "example.net" or "example.net/resource")
 83		// or there are one or more "@" signs but they're all in the resourcepart (eg. "example.net/@/rp@"):
 84		if (atCount == 0 || (atCount > 0 && slashLoc != -1 && atLoc > slashLoc)) {
 85			localpart = "";
 86			finaljid = "";
 87			domainpartStart = 0;
 88		} else {
 89			final String lp = jid.substring(0, atLoc);
 90			try {
 91				localpart = Stringprep.nodeprep(lp);
 92			} catch (final StringprepException e) {
 93				throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 94			}
 95			if (localpart.isEmpty() || localpart.length() > 1023) {
 96				throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
 97			}
 98			domainpartStart = atLoc + 1;
 99			finaljid = lp + "@";
100		}
101
102		final String dp;
103		if (slashCount > 0) {
104			final String rp = jid.substring(slashLoc + 1, jid.length());
105			try {
106				resourcepart = Stringprep.resourceprep(rp);
107			} catch (final StringprepException e) {
108				throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
109			}
110			if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
111				throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
112			}
113			dp = IDN.toUnicode(jid.substring(domainpartStart, slashLoc), IDN.USE_STD3_ASCII_RULES);
114			finaljid = finaljid + dp + "/" + rp;
115		} else {
116			resourcepart = "";
117			dp = IDN.toUnicode(jid.substring(domainpartStart, jid.length()),
118					IDN.USE_STD3_ASCII_RULES);
119			finaljid = finaljid + dp;
120		}
121
122		// Remove trailing "." before storing the domain part.
123		if (dp.endsWith(".")) {
124			try {
125				domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
126			} catch (final IllegalArgumentException e) {
127				throw new InvalidJidException(e);
128			}
129		} else {
130			try {
131				domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
132			} catch (final IllegalArgumentException e) {
133				throw new InvalidJidException(e);
134			}
135		}
136
137		// TODO: Find a proper domain validation library; validate individual parts, separators, etc.
138		if (domainpart.isEmpty() || domainpart.length() > 1023) {
139			throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
140		}
141
142		this.displayjid = finaljid;
143	}
144
145	public Jid toBareJid() {
146		try {
147			return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
148		} catch (final InvalidJidException e) {
149			// This should never happen.
150			return null;
151		}
152	}
153
154	public Jid toDomainJid() {
155		try {
156			return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart());
157		} catch (final InvalidJidException e) {
158			// This should never happen.
159			return null;
160		}
161	}
162
163	@Override
164	public String toString() {
165		return displayjid;
166	}
167
168	@Override
169	public boolean equals(final Object o) {
170		if (this == o) return true;
171		if (o == null || getClass() != o.getClass()) return false;
172
173		final Jid jid = (Jid) o;
174
175		return jid.hashCode() == this.hashCode();
176	}
177
178	@Override
179	public int hashCode() {
180		int result = localpart.hashCode();
181		result = 31 * result + domainpart.hashCode();
182		result = 31 * result + resourcepart.hashCode();
183		return result;
184	}
185
186	public boolean hasLocalpart() {
187		return !localpart.isEmpty();
188	}
189
190	public boolean isBareJid() {
191		return this.resourcepart.isEmpty();
192	}
193
194	public boolean isDomainJid() {
195		return !this.hasLocalpart();
196	}
197}