Jid.java

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