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