Jid.java

  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(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        // Hackish Android way to count the number of chars in a string... should work everywhere.
 64        final int atCount = jid.length() - jid.replace("@", "").length();
 65        final int slashCount = jid.length() - jid.replace("/", "").length();
 66
 67        // Throw an error if there's anything obvious wrong with the JID...
 68        if (jid.isEmpty() || jid.length() > 3071) {
 69            throw new InvalidJidException(InvalidJidException.INVALID_LENGTH);
 70        }
 71
 72        // Go ahead and check if the localpart or resourcepart is empty.
 73        if (jid.startsWith("@") || jid.endsWith("@") ||
 74                jid.startsWith("/") || jid.endsWith("/")) {
 75            throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER);
 76        }
 77
 78        String finaljid;
 79
 80        final int domainpartStart;
 81        if (atCount >= 1) {
 82            final int atLoc = jid.indexOf("@");
 83            final String lp = jid.substring(0, atLoc);
 84            try {
 85                localpart = Stringprep.nodeprep(lp);
 86            } catch (final StringprepException e) {
 87                throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
 88            }
 89            if (localpart.isEmpty() || localpart.length() > 1023) {
 90                throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
 91            }
 92            domainpartStart = atLoc + 1;
 93            finaljid = lp + "@";
 94        } else {
 95            localpart = "";
 96            finaljid = "";
 97            domainpartStart = 0;
 98        }
 99
100        final String dp;
101        if (slashCount >= 1) {
102            final int slashLoc = jid.indexOf("/");
103            final String rp = jid.substring(slashLoc + 1, jid.length());
104            try {
105                resourcepart = Stringprep.resourceprep(rp);
106            } catch (final StringprepException e) {
107                throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e);
108            }
109            if (resourcepart.isEmpty() || resourcepart.length() > 1023) {
110                throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
111            }
112            dp = IDN.toUnicode(jid.substring(domainpartStart, slashLoc), IDN.USE_STD3_ASCII_RULES);
113            finaljid = finaljid + dp + "/" + rp;
114        } else {
115            resourcepart = "";
116            dp = IDN.toUnicode(jid.substring(domainpartStart, jid.length()),
117                    IDN.USE_STD3_ASCII_RULES);
118            finaljid = finaljid + dp;
119        }
120
121        // Remove trailing "." before storing the domain part.
122        if (dp.endsWith(".")) {
123            try {
124                domainpart = IDN.toASCII(dp.substring(0, dp.length() - 1), IDN.USE_STD3_ASCII_RULES);
125            } catch (final IllegalArgumentException e) {
126                throw new InvalidJidException(e);
127            }
128        } else {
129            try {
130                domainpart = IDN.toASCII(dp, IDN.USE_STD3_ASCII_RULES);
131            } catch (final IllegalArgumentException e) {
132                throw new InvalidJidException(e);
133            }
134        }
135
136        // TODO: Find a proper domain validation library; validate individual parts, separators, etc.
137        if (domainpart.isEmpty() || domainpart.length() > 1023) {
138            throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH);
139        }
140
141        this.displayjid = finaljid;
142    }
143
144    public Jid toBareJid() {
145        try {
146            return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, "");
147        } catch (final InvalidJidException e) {
148            // This should never happen.
149            return null;
150        }
151    }
152
153    public Jid toDomainJid() {
154        try {
155            return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart());
156        } catch (final InvalidJidException e) {
157            // This should never happen.
158            return null;
159        }
160    }
161
162    @Override
163    public String toString() {
164        return displayjid;
165    }
166
167    @Override
168    public boolean equals(final Object o) {
169        if (this == o) return true;
170        if (o == null || getClass() != o.getClass()) return false;
171
172        final Jid jid = (Jid) o;
173
174        return jid.hashCode() == this.hashCode();
175    }
176
177    @Override
178    public int hashCode() {
179        int result = localpart.hashCode();
180        result = 31 * result + domainpart.hashCode();
181        result = 31 * result + resourcepart.hashCode();
182        return result;
183    }
184
185    public boolean hasLocalpart() {
186        return !localpart.isEmpty();
187    }
188
189    public boolean isBareJid() {
190        return this.resourcepart.isEmpty();
191    }
192}