XmppUri.java

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