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