1package eu.siacs.conversations.xmpp;
2
3import androidx.annotation.NonNull;
4import com.google.common.base.CharMatcher;
5import im.conversations.android.xmpp.model.stanza.Stanza;
6import java.io.Serializable;
7import org.jxmpp.jid.impl.JidCreate;
8import org.jxmpp.jid.parts.Domainpart;
9import org.jxmpp.jid.parts.Localpart;
10import org.jxmpp.jid.parts.Resourcepart;
11import org.jxmpp.stringprep.XmppStringprepException;
12
13public abstract class Jid implements Comparable<Jid>, Serializable, CharSequence {
14
15 public static Jid of(
16 final CharSequence local, final CharSequence domain, final CharSequence resource) {
17 if (local == null) {
18 if (resource == null) {
19 return ofDomain(domain);
20 } else {
21 return ofDomainAndResource(domain, resource);
22 }
23 }
24 if (resource == null) {
25 return ofLocalAndDomain(local, domain);
26 }
27 try {
28 return new InternalRepresentation(
29 JidCreate.entityFullFrom(
30 Localpart.from(local.toString()),
31 Domainpart.from(domain.toString()),
32 Resourcepart.from(resource.toString())));
33 } catch (final XmppStringprepException e) {
34 throw new IllegalArgumentException(e);
35 }
36 }
37
38 public static Jid ofDomain(final CharSequence domain) {
39 try {
40 return new InternalRepresentation(JidCreate.domainBareFrom(domain));
41 } catch (final XmppStringprepException e) {
42 throw new IllegalArgumentException(e);
43 }
44 }
45
46 public static Jid ofLocalAndDomain(final CharSequence local, final CharSequence domain) {
47 try {
48 return new InternalRepresentation(
49 JidCreate.bareFrom(
50 Localpart.from(local.toString()), Domainpart.from(domain.toString())));
51 } catch (final XmppStringprepException e) {
52 throw new IllegalArgumentException(e);
53 }
54 }
55
56 public static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) {
57 try {
58 return new InternalRepresentation(
59 JidCreate.domainFullFrom(
60 Domainpart.from(domain.toString()),
61 Resourcepart.from(resource.toString())));
62 } catch (final XmppStringprepException e) {
63 throw new IllegalArgumentException(e);
64 }
65 }
66
67 public static Jid of(final CharSequence input) {
68 if (input instanceof Jid jid) {
69 return jid;
70 }
71 try {
72 return new InternalRepresentation(JidCreate.from(input));
73 } catch (final XmppStringprepException e) {
74 throw new IllegalArgumentException(e);
75 }
76 }
77
78 public static Jid ofUserInput(final CharSequence input) {
79 final var jid = of(input);
80 if (CharMatcher.is('@').matchesAnyOf(jid.getDomain())) {
81 throw new IllegalArgumentException("Domain should not contain @");
82 }
83 return jid;
84 }
85
86 public static Jid ofOrInvalid(final String input) {
87 return ofOrInvalid(input, false);
88 }
89
90 /**
91 * @param jid a string representation of the jid to parse
92 * @param fallback indicates whether an attempt should be made to parse a bare version of the
93 * jid
94 * @return an instance of Jid; may be Jid.Invalid
95 */
96 public static Jid ofOrInvalid(final String jid, final boolean fallback) {
97 try {
98 return Jid.of(jid);
99 } catch (final IllegalArgumentException e) {
100 return Jid.invalidOf(jid, fallback);
101 }
102 }
103
104 private static Jid invalidOf(final String jid, boolean fallback) {
105 final int pos = jid.indexOf('/');
106 if (fallback && pos >= 0 && jid.length() >= pos + 1) {
107 if (jid.substring(pos + 1).trim().isEmpty()) {
108 return Jid.of(jid.substring(0, pos));
109 }
110 }
111 return new Invalid(jid);
112 }
113
114 public abstract boolean isFullJid();
115
116 public abstract boolean isBareJid();
117
118 public abstract boolean isDomainJid();
119
120 public abstract Jid asBareJid();
121
122 public abstract Jid withResource(CharSequence resource);
123
124 public abstract String getLocal();
125
126 public abstract Jid getDomain();
127
128 public abstract String getResource();
129
130 private static class InternalRepresentation extends Jid {
131 private final org.jxmpp.jid.Jid inner;
132
133 private InternalRepresentation(final org.jxmpp.jid.Jid inner) {
134 this.inner = inner;
135 }
136
137 @Override
138 public boolean isFullJid() {
139 return inner.isEntityFullJid() || inner.isDomainFullJid();
140 }
141
142 @Override
143 public boolean isBareJid() {
144 return inner.isDomainBareJid() || inner.isEntityBareJid();
145 }
146
147 @Override
148 public boolean isDomainJid() {
149 return inner.isDomainBareJid() || inner.isDomainFullJid();
150 }
151
152 @Override
153 public Jid asBareJid() {
154 return new InternalRepresentation(inner.asBareJid());
155 }
156
157 @Override
158 public Jid withResource(CharSequence resource) {
159 final Localpart localpart = inner.getLocalpartOrNull();
160 try {
161 final Resourcepart resourcepart = Resourcepart.from(resource.toString());
162 if (localpart == null) {
163 return new InternalRepresentation(
164 JidCreate.domainFullFrom(inner.getDomain(), resourcepart));
165 } else {
166 return new InternalRepresentation(
167 JidCreate.fullFrom(localpart, inner.getDomain(), resourcepart));
168 }
169 } catch (XmppStringprepException e) {
170 throw new IllegalArgumentException(e);
171 }
172 }
173
174 @Override
175 public String getLocal() {
176 final Localpart localpart = inner.getLocalpartOrNull();
177 return localpart == null ? null : localpart.toString();
178 }
179
180 @Override
181 public Jid getDomain() {
182 return new InternalRepresentation(inner.asDomainBareJid());
183 }
184
185 @Override
186 public String getResource() {
187 final Resourcepart resourcepart = inner.getResourceOrNull();
188 return resourcepart == null ? null : resourcepart.toString();
189 }
190
191 @NonNull
192 @Override
193 public String toString() {
194 return inner.toString();
195 }
196
197 @Override
198 public int length() {
199 return inner.length();
200 }
201
202 @Override
203 public char charAt(int i) {
204 return inner.charAt(i);
205 }
206
207 @NonNull
208 @Override
209 public CharSequence subSequence(int i, int i1) {
210 return inner.subSequence(i, i1);
211 }
212
213 @Override
214 public int compareTo(Jid jid) {
215 if (jid instanceof InternalRepresentation) {
216 return inner.compareTo(((InternalRepresentation) jid).inner);
217 } else {
218 return 0;
219 }
220 }
221
222 @Override
223 public boolean equals(Object o) {
224 if (this == o) return true;
225 if (o == null || getClass() != o.getClass()) return false;
226 InternalRepresentation that = (InternalRepresentation) o;
227 return inner.equals(that.inner);
228 }
229
230 @Override
231 public int hashCode() {
232 return inner.hashCode();
233 }
234 }
235
236 public static class Invalid extends Jid {
237
238 private final String value;
239
240 private Invalid(final String jid) {
241 this.value = jid;
242 }
243
244 @Override
245 @NonNull
246 public String toString() {
247 return value;
248 }
249
250 @Override
251 public boolean isFullJid() {
252 throw new AssertionError("Not implemented");
253 }
254
255 @Override
256 public boolean isBareJid() {
257 throw new AssertionError("Not implemented");
258 }
259
260 @Override
261 public boolean isDomainJid() {
262 throw new AssertionError("Not implemented");
263 }
264
265 @Override
266 public Jid asBareJid() {
267 throw new AssertionError("Not implemented");
268 }
269
270 @Override
271 public Jid withResource(CharSequence charSequence) {
272 throw new AssertionError("Not implemented");
273 }
274
275 @Override
276 public String getLocal() {
277 throw new AssertionError("Not implemented");
278 }
279
280 @Override
281 public Jid getDomain() {
282 throw new AssertionError("Not implemented");
283 }
284
285 @Override
286 public String getResource() {
287 throw new AssertionError("Not implemented");
288 }
289
290 @Override
291 public int length() {
292 return value.length();
293 }
294
295 @Override
296 public char charAt(int index) {
297 return value.charAt(index);
298 }
299
300 @NonNull
301 @Override
302 public CharSequence subSequence(int start, int end) {
303 return value.subSequence(start, end);
304 }
305
306 @Override
307 public int compareTo(@NonNull Jid o) {
308 throw new AssertionError("Not implemented");
309 }
310
311 public static Jid getNullForInvalid(final Jid jid) {
312 if (jid instanceof Invalid) {
313 return null;
314 } else {
315 return jid;
316 }
317 }
318
319 public static boolean isValid(Jid jid) {
320 return !(jid instanceof Invalid);
321 }
322
323 public static boolean hasValidFrom(final Stanza stanza) {
324 final String from = stanza.getAttribute("from");
325 if (from == null) {
326 return false;
327 }
328 try {
329 Jid.of(from);
330 return true;
331 } catch (final IllegalArgumentException e) {
332 return false;
333 }
334 }
335 }
336}