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