XmppUri.java

  1package eu.siacs.conversations.utils;
  2
  3import android.net.Uri;
  4import android.support.annotation.NonNull;
  5
  6import com.google.common.collect.ImmutableList;
  7import com.google.common.collect.ImmutableMap;
  8
  9import java.io.UnsupportedEncodingException;
 10import java.net.URLDecoder;
 11import java.util.ArrayList;
 12import java.util.Collections;
 13import java.util.List;
 14import java.util.Locale;
 15import java.util.Map;
 16
 17import eu.siacs.conversations.xmpp.Jid;
 18
 19public class XmppUri {
 20
 21    public static final String ACTION_JOIN = "join";
 22    public static final String ACTION_MESSAGE = "message";
 23    public static final String ACTION_REGISTER = "register";
 24    public static final String ACTION_ROSTER = "roster";
 25    public static final String PARAMETER_PRE_AUTH = "preauth";
 26    public static final String PARAMETER_IBR = "ibr";
 27    private static final String OMEMO_URI_PARAM = "omemo-sid-";
 28    protected Uri uri;
 29    protected String jid;
 30    private List<Fingerprint> fingerprints = new ArrayList<>();
 31    private Map<String, String> parameters = Collections.emptyMap();
 32    private boolean safeSource = true;
 33
 34    public XmppUri(String uri) {
 35        try {
 36            parse(Uri.parse(uri));
 37        } catch (IllegalArgumentException e) {
 38            try {
 39                jid = Jid.ofEscaped(uri).asBareJid().toEscapedString();
 40            } catch (IllegalArgumentException e2) {
 41                jid = null;
 42            }
 43        }
 44    }
 45
 46    public XmppUri(Uri uri) {
 47        parse(uri);
 48    }
 49
 50    public XmppUri(Uri uri, boolean safeSource) {
 51        this.safeSource = safeSource;
 52        parse(uri);
 53    }
 54
 55    private static Map<String, String> parseParameters(final String query, final char seperator) {
 56        final ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
 57        final String[] pairs = query == null ? new String[0] : query.split(String.valueOf(seperator));
 58        for (String pair : pairs) {
 59            final String[] parts = pair.split("=", 2);
 60            if (parts.length == 0) {
 61                continue;
 62            }
 63            final String key = parts[0].toLowerCase(Locale.US);
 64            final String value;
 65            if (parts.length == 2) {
 66                String decoded;
 67                try {
 68                    decoded = URLDecoder.decode(parts[1], "UTF-8");
 69                } catch (UnsupportedEncodingException e) {
 70                    decoded = "";
 71                }
 72                value = decoded;
 73            } else {
 74                value = "";
 75            }
 76            builder.put(key, value);
 77        }
 78        return builder.build();
 79    }
 80
 81    private static List<Fingerprint> parseFingerprints(Map<String, String> parameters) {
 82        ImmutableList.Builder<Fingerprint> builder = new ImmutableList.Builder<>();
 83        for (Map.Entry<String, String> parameter : parameters.entrySet()) {
 84            final String key = parameter.getKey();
 85            final String value = parameter.getValue().toLowerCase(Locale.US);
 86            if (key.startsWith(OMEMO_URI_PARAM)) {
 87                try {
 88                    final int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length()));
 89                    builder.add(new Fingerprint(FingerprintType.OMEMO, value, id));
 90                } catch (Exception e) {
 91                    //ignoring invalid device id
 92                }
 93            } else if ("omemo".equals(key)) {
 94                builder.add(new Fingerprint(FingerprintType.OMEMO, value, 0));
 95            }
 96        }
 97        return builder.build();
 98    }
 99
100    public static String getFingerprintUri(final String base, final List<XmppUri.Fingerprint> fingerprints, char separator) {
101        final StringBuilder builder = new StringBuilder(base);
102        builder.append('?');
103        for (int i = 0; i < fingerprints.size(); ++i) {
104            XmppUri.FingerprintType type = fingerprints.get(i).type;
105            if (type == XmppUri.FingerprintType.OMEMO) {
106                builder.append(XmppUri.OMEMO_URI_PARAM);
107                builder.append(fingerprints.get(i).deviceId);
108            }
109            builder.append('=');
110            builder.append(fingerprints.get(i).fingerprint);
111            if (i != fingerprints.size() - 1) {
112                builder.append(separator);
113            }
114        }
115        return builder.toString();
116    }
117
118    private static String lameUrlDecode(String url) {
119        return url.replace("%23", "#").replace("%25", "%");
120    }
121
122    public static String lameUrlEncode(String url) {
123        return url.replace("%", "%25").replace("#", "%23");
124    }
125
126    public boolean isSafeSource() {
127        return safeSource;
128    }
129
130    protected void parse(final Uri uri) {
131        if (uri == null) {
132            return;
133        }
134        this.uri = uri;
135        String scheme = uri.getScheme();
136        String host = uri.getHost();
137        List<String> segments = uri.getPathSegments();
138        if ("https".equalsIgnoreCase(scheme) && "conversations.im".equalsIgnoreCase(host)) {
139            if (segments.size() >= 2 && segments.get(1).contains("@")) {
140                // sample : https://conversations.im/i/foo@bar.com
141                try {
142                    jid = Jid.ofEscaped(lameUrlDecode(segments.get(1))).toEscapedString();
143                } catch (Exception e) {
144                    jid = null;
145                }
146            } else if (segments.size() >= 3) {
147                // sample : https://conversations.im/i/foo/bar.com
148                jid = segments.get(1) + "@" + segments.get(2);
149            }
150            if (segments.size() > 1 && "j".equalsIgnoreCase(segments.get(0))) {
151                this.parameters = ImmutableMap.of(ACTION_JOIN, "");
152            }
153            final Map<String, String> parameters = parseParameters(uri.getQuery(), '&');
154            this.fingerprints = parseFingerprints(parameters);
155        } else if ("xmpp".equalsIgnoreCase(scheme)) {
156            // sample: xmpp:foo@bar.com
157            this.parameters = parseParameters(uri.getQuery(), ';');
158            if (uri.getAuthority() != null) {
159                jid = uri.getAuthority();
160            } else {
161                final String[] parts = uri.getSchemeSpecificPart().split("\\?");
162                if (parts.length > 0) {
163                    jid = parts[0];
164                } else {
165                    return;
166                }
167            }
168            this.fingerprints = parseFingerprints(parameters);
169        } else if ("imto".equalsIgnoreCase(scheme)) {
170            // sample: imto://xmpp/foo@bar.com
171            try {
172                jid = URLDecoder.decode(uri.getEncodedPath(), "UTF-8").split("/")[1].trim();
173            } catch (final UnsupportedEncodingException ignored) {
174                jid = null;
175            }
176        } else {
177            jid = null;
178        }
179    }
180
181    @Override
182    @NonNull
183    public String toString() {
184        if (uri != null) {
185            return uri.toString();
186        }
187        return "";
188    }
189
190    public boolean isAction(final String action) {
191        return parameters.containsKey(action);
192    }
193
194    public Jid getJid() {
195        try {
196            return this.jid == null ? null : Jid.ofEscaped(this.jid);
197        } catch (IllegalArgumentException e) {
198            return null;
199        }
200    }
201
202    public boolean isValidJid() {
203        if (jid == null) {
204            return false;
205        }
206        try {
207            Jid.ofEscaped(jid);
208            return true;
209        } catch (IllegalArgumentException e) {
210            return false;
211        }
212    }
213
214    public String getBody() {
215        return parameters.get("body");
216    }
217
218    public String getName() {
219        return parameters.get("name");
220    }
221
222    public String getParameter(String key) {
223        return this.parameters.get(key);
224    }
225
226    public List<Fingerprint> getFingerprints() {
227        return this.fingerprints;
228    }
229
230    public boolean hasFingerprints() {
231        return fingerprints.size() > 0;
232    }
233
234    public enum FingerprintType {
235        OMEMO
236    }
237
238    public static class Fingerprint {
239        public final FingerprintType type;
240        public final String fingerprint;
241        final int deviceId;
242
243        public Fingerprint(FingerprintType type, String fingerprint, int deviceId) {
244            this.type = type;
245            this.fingerprint = fingerprint;
246            this.deviceId = deviceId;
247        }
248
249        @NonNull
250        @Override
251        public String toString() {
252            return type.toString() + ": " + fingerprint + (deviceId != 0 ? " " + deviceId : "");
253        }
254    }
255}