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}