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}