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