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