1package eu.siacs.conversations.xmpp.jid;
2
3import java.net.IDN;
4
5import gnu.inet.encoding.Stringprep;
6import gnu.inet.encoding.StringprepException;
7
8/**
9 * The `Jid' class provides an immutable representation of a JID.
10 */
11public final class Jid {
12
13 public final static class InvalidJidException extends Exception { }
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 IDN.toUnicode(localpart);
25 }
26
27 public String getDomainpart() {
28 return IDN.toUnicode(domainpart);
29 }
30
31 public String getResourcepart() {
32 return IDN.toUnicode(resourcepart);
33 }
34
35 // Special private constructor that doesn't do any checking...
36 private Jid(final String localpart, final String domainpart) {
37 this.localpart = localpart;
38 this.domainpart = domainpart;
39 this.resourcepart = "";
40 if (localpart.isEmpty()) {
41 this.displayjid = domainpart;
42 } else {
43 this.displayjid = localpart + "@" + domainpart;
44 }
45 }
46
47 // Note: If introducing a mutable instance variable for some reason, make the constructor
48 // private and add a factory method to ensure thread safety and hash-cach-ability (tm).
49 public Jid(final String jid) throws InvalidJidException {
50
51 // Hackish Android way to count the number of chars in a string... should work everywhere.
52 final int atCount = jid.length() - jid.replace("@", "").length();
53 final int slashCount = jid.length() - jid.replace("/", "").length();
54
55 // Throw an error if there's anything obvious wrong with the JID...
56 if (atCount > 1 || slashCount > 1 ||
57 jid.length() == 0 || jid.length() > 3071 ||
58 jid.startsWith("@") || jid.endsWith("@") ||
59 jid.startsWith("/") || jid.endsWith("/")) {
60 throw new InvalidJidException();
61 }
62
63 String finaljid;
64
65 final int domainpartStart;
66 if (atCount == 1) {
67 final int atLoc = jid.indexOf("@");
68 final String lp = jid.substring(0, atLoc);
69 try {
70 localpart = Stringprep.nodeprep(lp);
71 } catch (final StringprepException e) {
72 throw new InvalidJidException();
73 }
74 if (localpart.isEmpty() || localpart.length() > 1023) {
75 throw new InvalidJidException();
76 }
77 domainpartStart = atLoc;
78 finaljid = lp + "@";
79 } else {
80 localpart = "";
81 finaljid = "";
82 domainpartStart = 0;
83 }
84
85 final String dp;
86 if (slashCount == 1) {
87 final int slashLoc = jid.indexOf("/");
88 final String rp = jid.substring(slashLoc + 1, jid.length());
89 try {
90 resourcepart = Stringprep.resourceprep(rp);
91 } catch (final StringprepException e) {
92 throw new InvalidJidException();
93 }
94 if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
95 throw new InvalidJidException();
96 }
97 dp = jid.substring(domainpartStart, slashLoc);
98 finaljid = finaljid + dp + "/" + rp;
99 } else {
100 resourcepart = "";
101 dp = jid.substring(domainpartStart, jid.length());
102 finaljid = finaljid + dp;
103 }
104
105 // Remove trailling "." before storing the domain part.
106 if (dp.endsWith(".")) {
107 domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
108 } else {
109 domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
110 }
111
112 // TODO: Find a proper domain validation library; validate individual parts, separators, etc.
113 if (domainpart.isEmpty() || domainpart.length() > 1023) {
114 throw new InvalidJidException();
115 }
116
117 this.displayjid = finaljid;
118 }
119
120 public Jid getBareJid() {
121 return displayjid.contains("/") ? new Jid(localpart, domainpart) : this;
122 }
123
124 @Override
125 public String toString() {
126 return displayjid;
127 }
128
129 @Override
130 public boolean equals(final Object o) {
131 if (this == o) return true;
132 if (o == null || getClass() != o.getClass()) return false;
133
134 final Jid jid = (Jid) o;
135
136 // Since we're immutable, the JVM will cache hashcodes, making this very fast.
137 // I'm assuming Dalvik does the same sorts of optimizations...
138 // Since the hashcode does not include the displayJID it can be used for IDN comparison as
139 // well.
140 return jid.hashCode() == this.hashCode();
141
142 }
143
144 @Override
145 public int hashCode() {
146 int result = localpart.hashCode();
147 result = 31 * result + domainpart.hashCode();
148 result = 31 * result + resourcepart.hashCode();
149 return result;
150 }
151}