ServiceDiscoveryResult.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5import android.util.Base64;
  6import java.io.UnsupportedEncodingException;
  7import java.lang.Comparable;
  8import java.security.MessageDigest;
  9import java.security.NoSuchAlgorithmException;
 10import java.util.ArrayList;
 11import java.util.Collections;
 12import java.util.Comparator;
 13import java.util.List;
 14import org.json.JSONArray;
 15import org.json.JSONException;
 16import org.json.JSONObject;
 17
 18import eu.siacs.conversations.xml.Element;
 19import eu.siacs.conversations.xmpp.forms.Data;
 20import eu.siacs.conversations.xmpp.forms.Field;
 21import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 22
 23public class ServiceDiscoveryResult {
 24	public static final String TABLENAME = "discovery_results";
 25	public static final String HASH = "hash";
 26	public static final String VER = "ver";
 27	public static final String RESULT = "result";
 28
 29	protected static String blankNull(String s) {
 30		return s == null ? "" : s;
 31	}
 32
 33	public static class Identity implements Comparable {
 34		protected final String category;
 35		protected final String type;
 36		protected final String lang;
 37		protected final String name;
 38
 39		public Identity(final String category, final String type, final String lang, final String name) {
 40			this.category = category;
 41			this.type = type;
 42			this.lang = lang;
 43			this.name = name;
 44		}
 45
 46		public Identity(final Element el) {
 47			this(
 48				el.getAttribute("category"),
 49				el.getAttribute("type"),
 50				el.getAttribute("xml:lang"),
 51				el.getAttribute("name")
 52			);
 53		}
 54
 55		public Identity(final JSONObject o) {
 56
 57			this(
 58				o.optString("category", null),
 59				o.optString("type", null),
 60				o.optString("lang", null),
 61				o.optString("name", null)
 62			);
 63		}
 64
 65		public String getCategory() {
 66			return this.category;
 67		}
 68
 69		public String getType() {
 70			return this.type;
 71		}
 72
 73		public String getLang() {
 74			return this.lang;
 75		}
 76
 77		public String getName() {
 78			return this.name;
 79		}
 80
 81		public int compareTo(Object other) {
 82			Identity o = (Identity)other;
 83			int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
 84			if(r == 0) {
 85				r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
 86			}
 87			if(r == 0) {
 88				r = blankNull(this.getLang()).compareTo(blankNull(o.getLang()));
 89			}
 90			if(r == 0) {
 91				r = blankNull(this.getName()).compareTo(blankNull(o.getName()));
 92			}
 93
 94			return r;
 95		}
 96
 97		public JSONObject toJSON() {
 98			try {
 99				JSONObject o = new JSONObject();
100				o.put("category", this.getCategory());
101				o.put("type", this.getType());
102				o.put("lang", this.getLang());
103				o.put("name", this.getName());
104				return o;
105			} catch(JSONException e) {
106				return null;
107			}
108		}
109	}
110
111	protected final String hash;
112	protected final byte[] ver;
113	protected final List<Identity> identities;
114	protected final List<String> features;
115	protected final List<Data> forms;
116
117	public ServiceDiscoveryResult(final IqPacket packet) {
118		this.identities = new ArrayList<>();
119		this.features = new ArrayList<>();
120		this.forms = new ArrayList<>();
121		this.hash = "sha-1"; // We only support sha-1 for now
122
123		final List<Element> elements = packet.query().getChildren();
124
125		for (final Element element : elements) {
126			if (element.getName().equals("identity")) {
127				Identity id = new Identity(element);
128				if (id.getType() != null && id.getCategory() != null) {
129					identities.add(id);
130				}
131			} else if (element.getName().equals("feature")) {
132				if (element.getAttribute("var") != null) {
133					features.add(element.getAttribute("var"));
134				}
135			} else if (element.getName().equals("x") && "jabber:x:data".equals(element.getAttribute("xmlns"))) {
136				forms.add(Data.parse(element));
137			}
138		}
139		this.ver = this.mkCapHash();
140	}
141
142	public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException {
143		this.identities = new ArrayList<>();
144		this.features = new ArrayList<>();
145		this.forms = new ArrayList<>();
146		this.hash = hash;
147		this.ver = ver;
148
149		JSONArray identities = o.optJSONArray("identities");
150		if (identities != null) {
151			for (int i = 0; i < identities.length(); i++) {
152				this.identities.add(new Identity(identities.getJSONObject(i)));
153			}
154		}
155		JSONArray features = o.optJSONArray("features");
156		if (features != null) {
157			for (int i = 0; i < features.length(); i++) {
158				this.features.add(features.getString(i));
159			}
160		}
161		JSONArray forms = o.optJSONArray("forms");
162		if (forms != null) {
163			for(int i = 0; i < forms.length(); i++) {
164				this.forms.add(createFormFromJSONObject(forms.getJSONObject(i)));
165			}
166		}
167	}
168
169	private static Data createFormFromJSONObject(JSONObject o) {
170		Data data = new Data();
171		JSONArray names = o.names();
172		for(int i = 0; i < names.length(); ++i) {
173			try {
174				String name = names.getString(i);
175				JSONArray jsonValues = o.getJSONArray(name);
176				ArrayList<String> values = new ArrayList<>(jsonValues.length());
177				for(int j = 0; j < jsonValues.length(); ++j) {
178					values.add(jsonValues.getString(j));
179				}
180				data.put(name, values);
181			} catch (Exception e) {
182				e.printStackTrace();
183			}
184		}
185		return data;
186	}
187
188	private static JSONObject createJSONFromForm(Data data) {
189		JSONObject object = new JSONObject();
190		for(Field field : data.getFields()) {
191			try {
192				JSONArray jsonValues = new JSONArray();
193				for(String value : field.getValues()) {
194					jsonValues.put(value);
195				}
196				object.put(field.getFieldName(), jsonValues);
197			} catch(Exception e) {
198				e.printStackTrace();
199			}
200		}
201		try {
202			JSONArray jsonValues = new JSONArray();
203			jsonValues.put(data.getFormType());
204			object.put(Data.FORM_TYPE, jsonValues);
205		} catch(Exception e) {
206			e.printStackTrace();
207		}
208		return object;
209	}
210
211	public String getVer() {
212		return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
213	}
214
215	public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
216		this(
217			cursor.getString(cursor.getColumnIndex(HASH)),
218			Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT),
219			new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT)))
220		);
221	}
222
223	public List<Identity> getIdentities() {
224		return this.identities;
225	}
226
227	public List<String> getFeatures() {
228		return this.features;
229	}
230
231	public boolean hasIdentity(String category, String type) {
232		for(Identity id : this.getIdentities()) {
233			if((category == null || id.getCategory().equals(category)) &&
234			   (type == null || id.getType().equals(type))) {
235				return true;
236			}
237		}
238
239		return false;
240	}
241
242	public String getExtendedDiscoInformation(String formType, String name) {
243		for(Data form : this.forms) {
244			if (formType.equals(form.getFormType())) {
245				for(Field field: form.getFields()) {
246					if (name.equals(field.getFieldName())) {
247						return field.getValue();
248					}
249				}
250			}
251		}
252		return null;
253	}
254
255	protected byte[] mkCapHash() {
256		StringBuilder s = new StringBuilder();
257
258		List<Identity> identities = this.getIdentities();
259		Collections.sort(identities);
260
261		for(Identity id : identities) {
262			s.append(
263				blankNull(id.getCategory()) + "/" +
264				blankNull(id.getType()) + "/" +
265				blankNull(id.getLang()) + "/" +
266				blankNull(id.getName()) + "<"
267			);
268		}
269
270		List<String> features = this.getFeatures();
271		Collections.sort(features);
272
273		for (String feature : features) {
274			s.append(feature + "<");
275		}
276
277		Collections.sort(forms, new Comparator<Data>() {
278			@Override
279			public int compare(Data lhs, Data rhs) {
280				return lhs.getFormType().compareTo(rhs.getFormType());
281			}
282		});
283
284		for(Data form : forms) {
285			s.append(form.getFormType() + "<");
286			List<Field> fields = form.getFields();
287			Collections.sort(fields, new Comparator<Field>() {
288				@Override
289				public int compare(Field lhs, Field rhs) {
290					return lhs.getFieldName().compareTo(rhs.getFieldName());
291				}
292			});
293			for(Field field : fields) {
294				s.append(field.getFieldName()+"<");
295				List<String> values = field.getValues();
296				Collections.sort(values);
297				for(String value : values) {
298					s.append(value+"<");
299				}
300			}
301		}
302
303		MessageDigest md;
304		try {
305			md = MessageDigest.getInstance("SHA-1");
306		} catch (NoSuchAlgorithmException e) {
307			return null;
308		}
309
310		try {
311			return md.digest(s.toString().getBytes("UTF-8"));
312		} catch(UnsupportedEncodingException e) {
313			return null;
314		}
315	}
316
317	public JSONObject toJSON() {
318		try {
319			JSONObject o = new JSONObject();
320
321			JSONArray ids = new JSONArray();
322			for(Identity id : this.getIdentities()) {
323				ids.put(id.toJSON());
324			}
325			o.put("identities", ids);
326
327			o.put("features", new JSONArray(this.getFeatures()));
328
329			JSONArray forms = new JSONArray();
330			for(Data data : this.forms) {
331				forms.put(createJSONFromForm(data));
332			}
333			o.put("forms", forms);
334
335			return o;
336		} catch(JSONException e) {
337			return null;
338		}
339	}
340
341	public ContentValues getContentValues() {
342		final ContentValues values = new ContentValues();
343		values.put(HASH, this.hash);
344		values.put(VER, getVer());
345		values.put(RESULT, this.toJSON().toString());
346		return values;
347	}
348}