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