Jid.java

  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    private final String localpart;
 14    private final String domainpart;
 15    private final String resourcepart;
 16
 17    // It's much more efficient to store the ful JID as well as the parts instead of figuring them
 18    // all out every time (since some characters are displayed but aren't used for comparisons).
 19    private final String displayjid;
 20
 21    public String getLocalpart() {
 22        return localpart;
 23    }
 24
 25    public String getDomainpart() {
 26        return IDN.toUnicode(domainpart);
 27    }
 28
 29    public String getResourcepart() {
 30        return resourcepart;
 31    }
 32
 33    public static Jid fromString(final String jid) throws InvalidJidException {
 34        return new Jid(jid);
 35    }
 36
 37    public static Jid fromParts(final String localpart,
 38                                final String domainpart,
 39                                final String resourcepart) throws InvalidJidException {
 40        String out;
 41        if (localpart == null || localpart.isEmpty()) {
 42            out = domainpart;
 43        } else {
 44            out = localpart + "@" + domainpart;
 45        }
 46        if (resourcepart != null && !resourcepart.isEmpty()) {
 47            out = out + "/" + resourcepart;
 48        }
 49        return new Jid(out);
 50    }
 51
 52    private Jid(final String jid) throws InvalidJidException {
 53        // Hackish Android way to count the number of chars in a string... should work everywhere.
 54        final int atCount = jid.length() - jid.replace("@", "").length();
 55        final int slashCount = jid.length() - jid.replace("/", "").length();
 56
 57        // Throw an error if there's anything obvious wrong with the JID...
 58        if (jid.isEmpty() || jid.length() > 3071) {
 59            throw new InvalidJidException(InvalidJidException.INVALID_LENGTH);
 60        }
 61        if (atCount > 1 || slashCount > 1 ||
 62                jid.startsWith("@") || jid.endsWith("@") ||
 63                jid.startsWith("/") || jid.endsWith("/")) {
 64            throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER);
 65        }
 66
 67        String finaljid;
 68
 69        final int domainpartStart;
 70        if (atCount == 1) {
 71            final int atLoc = jid.indexOf("@");
 72            final String lp = jid.substring(0, atLoc);
 73            try {
 74                localpart = Stringprep.nodeprep(lp);
 75            } catch (final StringprepException e) {
 76                throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 77            }
 78            if (localpart.isEmpty() || localpart.length() > 1023) {
 79                throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
 80            }
 81            domainpartStart = atLoc + 1;
 82            finaljid = lp + "@";
 83        } else {
 84            localpart = "";
 85            finaljid = "";
 86            domainpartStart = 0;
 87        }
 88
 89        final String dp;
 90        if (slashCount == 1) {
 91            final int slashLoc = jid.indexOf("/");
 92            final String rp = jid.substring(slashLoc + 1, jid.length());
 93            try {
 94                resourcepart = Stringprep.resourceprep(rp);
 95            } catch (final StringprepException e) {
 96                throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 97            }
 98            if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
 99                throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
100            }
101            dp = jid.substring(domainpartStart, slashLoc);
102            finaljid = finaljid + dp + "/" + rp;
103        } else {
104            resourcepart = "";
105            dp = jid.substring(domainpartStart, jid.length());
106            finaljid = finaljid + dp;
107        }
108
109        // Remove trailling "." before storing the domain part.
110        if (dp.endsWith(".")) {
111            try {
112                domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
113            } catch (final IllegalArgumentException e) {
114                throw new InvalidJidException(e);
115            }
116        } else {
117            try {
118                domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
119            } catch (final IllegalArgumentException e) {
120                throw new InvalidJidException(e);
121            }
122        }
123
124        // TODO: Find a proper domain validation library; validate individual parts, separators, etc.
125        if (domainpart.isEmpty() || domainpart.length() > 1023) {
126            throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
127        }
128
129        this.displayjid = finaljid;
130    }
131
132    public Jid toBareJid() {
133        try {
134            return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
135        } catch (final InvalidJidException e) {
136            // This should never happen.
137            return null;
138        }
139    }
140
141    public Jid toDomainJid() {
142        try {
143            return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart());
144        } catch (final InvalidJidException e) {
145            // This should never happen.
146            return null;
147        }
148    }
149
150    @Override
151    public String toString() {
152        return displayjid;
153    }
154
155    @Override
156    public boolean equals(final Object o) {
157        if (this == o) return true;
158        if (o == null || getClass() != o.getClass()) return false;
159
160        final Jid jid = (Jid) o;
161
162        return jid.hashCode() == this.hashCode();
163    }
164
165    @Override
166    public int hashCode() {
167        int result = localpart.hashCode();
168        result = 31 * result + domainpart.hashCode();
169        result = 31 * result + resourcepart.hashCode();
170        return result;
171    }
172
173    public boolean hasLocalpart() {
174        return !localpart.isEmpty();
175    }
176
177    public boolean isBareJid() {
178        return this.resourcepart.isEmpty();
179    }
180}