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 IDN.toUnicode(localpart);
 23    }
 24
 25    public String getDomainpart() {
 26        return IDN.toUnicode(domainpart);
 27    }
 28
 29    public String getResourcepart() {
 30        return IDN.toUnicode(resourcepart);
 31    }
 32
 33    public 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;
 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            domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
112        } else {
113            domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
114        }
115
116        // TODO: Find a proper domain validation library; validate individual parts, separators, etc.
117        if (domainpart.isEmpty() || domainpart.length() > 1023) {
118            throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
119        }
120
121        this.displayjid = finaljid;
122    }
123
124    public Jid getBareJid() {
125        try {
126            return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
127        } catch (final InvalidJidException e) {
128            // This should never happen due to the contracts we have in place.
129            return null;
130        }
131    }
132
133    @Override
134    public String toString() {
135        return displayjid;
136    }
137
138    @Override
139    public boolean equals(final Object o) {
140        if (this == o) return true;
141        if (o == null || getClass() != o.getClass()) return false;
142
143        final Jid jid = (Jid) o;
144
145        return jid.hashCode() == this.hashCode();
146    }
147
148    @Override
149    public int hashCode() {
150        int result = localpart.hashCode();
151        result = 31 * result + domainpart.hashCode();
152        result = 31 * result + resourcepart.hashCode();
153        return result;
154    }
155}