IqParser.java

  1package eu.siacs.conversations.parser;
  2
  3import android.util.Base64;
  4import android.util.Log;
  5
  6import org.whispersystems.libaxolotl.IdentityKey;
  7import org.whispersystems.libaxolotl.InvalidKeyException;
  8import org.whispersystems.libaxolotl.ecc.Curve;
  9import org.whispersystems.libaxolotl.ecc.ECPublicKey;
 10import org.whispersystems.libaxolotl.state.PreKeyBundle;
 11
 12import java.util.ArrayList;
 13import java.util.Collection;
 14import java.util.HashMap;
 15import java.util.List;
 16import java.util.Map;
 17
 18import eu.siacs.conversations.Config;
 19import eu.siacs.conversations.entities.Account;
 20import eu.siacs.conversations.entities.Contact;
 21import eu.siacs.conversations.services.XmppConnectionService;
 22import eu.siacs.conversations.utils.Xmlns;
 23import eu.siacs.conversations.xml.Element;
 24import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 25import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 26import eu.siacs.conversations.xmpp.jid.Jid;
 27import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 28
 29public class IqParser extends AbstractParser implements OnIqPacketReceived {
 30
 31	public IqParser(final XmppConnectionService service) {
 32		super(service);
 33	}
 34
 35	private void rosterItems(final Account account, final Element query) {
 36		final String version = query.getAttribute("ver");
 37		if (version != null) {
 38			account.getRoster().setVersion(version);
 39		}
 40		for (final Element item : query.getChildren()) {
 41			if (item.getName().equals("item")) {
 42				final Jid jid = item.getAttributeAsJid("jid");
 43				if (jid == null) {
 44					continue;
 45				}
 46				final String name = item.getAttribute("name");
 47				final String subscription = item.getAttribute("subscription");
 48				final Contact contact = account.getRoster().getContact(jid);
 49				if (!contact.getOption(Contact.Options.DIRTY_PUSH)) {
 50					contact.setServerName(name);
 51					contact.parseGroupsFromElement(item);
 52				}
 53				if (subscription != null) {
 54					if (subscription.equals("remove")) {
 55						contact.resetOption(Contact.Options.IN_ROSTER);
 56						contact.resetOption(Contact.Options.DIRTY_DELETE);
 57						contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
 58					} else {
 59						contact.setOption(Contact.Options.IN_ROSTER);
 60						contact.resetOption(Contact.Options.DIRTY_PUSH);
 61						contact.parseSubscriptionFromElement(item);
 62					}
 63				}
 64				mXmppConnectionService.getAvatarService().clear(contact);
 65			}
 66		}
 67		mXmppConnectionService.updateConversationUi();
 68		mXmppConnectionService.updateRosterUi();
 69	}
 70
 71	public String avatarData(final IqPacket packet) {
 72		final Element pubsub = packet.findChild("pubsub",
 73                "http://jabber.org/protocol/pubsub");
 74		if (pubsub == null) {
 75			return null;
 76		}
 77		final Element items = pubsub.findChild("items");
 78		if (items == null) {
 79			return null;
 80		}
 81		return super.avatarData(items);
 82	}
 83
 84	public Element getItem(final IqPacket packet) {
 85		final Element pubsub = packet.findChild("pubsub",
 86				"http://jabber.org/protocol/pubsub");
 87		if (pubsub == null) {
 88			return null;
 89		}
 90		final Element items = pubsub.findChild("items");
 91		if (items == null) {
 92			return null;
 93		}
 94		return items.findChild("item");
 95	}
 96
 97	public List<Integer> deviceIds(final Element item) {
 98		List<Integer> deviceIds = new ArrayList<>();
 99		if (item == null) {
100			return null;
101		}
102		final Element list = item.findChild("list");
103		if(list == null) {
104			return null;
105		}
106		for(Element device : list.getChildren()) {
107			if(!device.getName().equals("device")) {
108				continue;
109			}
110			try {
111				Integer id = Integer.valueOf(device.getAttribute("id"));
112				deviceIds.add(id);
113			} catch (NumberFormatException e) {
114				Log.e(Config.LOGTAG, "Encountered nvalid <device> node in PEP:" + device.toString()
115					+ ", skipping...");
116				continue;
117			}
118		}
119		return deviceIds;
120	}
121
122	public Integer signedPreKeyId(final Element bundle) {
123		final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
124		if(signedPreKeyPublic == null) {
125			return null;
126		}
127		return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId"));
128	}
129
130	public ECPublicKey signedPreKeyPublic(final Element bundle) {
131		ECPublicKey publicKey = null;
132		final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
133		if(signedPreKeyPublic == null) {
134			return null;
135		}
136		try {
137			publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
138		} catch (InvalidKeyException e) {
139			Log.e(Config.LOGTAG, "Invalid signedPreKeyPublic in PEP: " + e.getMessage());
140		}
141		return publicKey;
142	}
143
144	public byte[] signedPreKeySignature(final Element bundle) {
145		final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
146		if(signedPreKeySignature == null) {
147			return null;
148		}
149		return Base64.decode(signedPreKeySignature.getContent(),Base64.DEFAULT);
150	}
151
152	public IdentityKey identityKey(final Element bundle) {
153		IdentityKey identityKey = null;
154		final Element identityKeyElement = bundle.findChild("identityKey");
155		if(identityKeyElement == null) {
156			return null;
157		}
158		try {
159			identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
160		} catch (InvalidKeyException e) {
161			Log.e(Config.LOGTAG,"Invalid identityKey in PEP: "+e.getMessage());
162		}
163		return identityKey;
164	}
165
166	public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
167		Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
168        Element prekeysItem = getItem(packet);
169        if (prekeysItem == null) {
170            Log.d(Config.LOGTAG, "Couldn't find <item> in preKeyPublic IQ packet: " + packet);
171            return null;
172        }
173        final Element prekeysElement = prekeysItem.findChild("prekeys");
174        if(prekeysElement == null) {
175            Log.d(Config.LOGTAG, "Couldn't find <prekeys> in preKeyPublic IQ packet: " + packet);
176            return null;
177        }
178		for(Element preKeyPublicElement : prekeysElement.getChildren()) {
179			if(!preKeyPublicElement.getName().equals("preKeyPublic")){
180                Log.d(Config.LOGTAG, "Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
181				continue;
182			}
183			Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
184			try {
185				ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
186				preKeyRecords.put(preKeyId, preKeyPublic);
187			} catch (InvalidKeyException e) {
188				Log.e(Config.LOGTAG, "Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
189				continue;
190			}
191		}
192		return preKeyRecords;
193	}
194
195    public PreKeyBundle bundle(final IqPacket bundle) {
196        Element bundleItem = getItem(bundle);
197        if(bundleItem == null) {
198            return null;
199        }
200        final Element bundleElement = bundleItem.findChild("bundle");
201        if(bundleElement == null) {
202            return null;
203        }
204        ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
205        Integer signedPreKeyId = signedPreKeyId(bundleElement);
206        byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
207        IdentityKey identityKey = identityKey(bundleElement);
208        if(signedPreKeyPublic == null || identityKey == null) {
209            return null;
210        }
211
212        return new PreKeyBundle(0, 0, 0, null,
213                signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
214    }
215
216	public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
217		List<PreKeyBundle> bundles = new ArrayList<>();
218		Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
219        if ( preKeyPublics != null) {
220            for (Integer preKeyId : preKeyPublics.keySet()) {
221                ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
222                bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
223                        0, null, null, null));
224            }
225        }
226
227		return bundles;
228	}
229
230	@Override
231	public void onIqPacketReceived(final Account account, final IqPacket packet) {
232		if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) {
233			final Element query = packet.findChild("query");
234			// If this is in response to a query for the whole roster:
235			if (packet.getType() == IqPacket.TYPE.RESULT) {
236				account.getRoster().markAllAsNotInRoster();
237			}
238			this.rosterItems(account, query);
239		} else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) &&
240				packet.fromServer(account)) {
241			// Block list or block push.
242			Log.d(Config.LOGTAG, "Received blocklist update from server");
243			final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING);
244			final Element block = packet.findChild("block", Xmlns.BLOCKING);
245			final Collection<Element> items = blocklist != null ? blocklist.getChildren() :
246				(block != null ? block.getChildren() : null);
247			// If this is a response to a blocklist query, clear the block list and replace with the new one.
248			// Otherwise, just update the existing blocklist.
249			if (packet.getType() == IqPacket.TYPE.RESULT) {
250				account.clearBlocklist();
251				account.getXmppConnection().getFeatures().setBlockListRequested(true);
252			}
253			if (items != null) {
254				final Collection<Jid> jids = new ArrayList<>(items.size());
255				// Create a collection of Jids from the packet
256				for (final Element item : items) {
257					if (item.getName().equals("item")) {
258						final Jid jid = item.getAttributeAsJid("jid");
259						if (jid != null) {
260							jids.add(jid);
261						}
262					}
263				}
264				account.getBlocklist().addAll(jids);
265			}
266			// Update the UI
267			mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
268		} else if (packet.hasChild("unblock", Xmlns.BLOCKING) &&
269				packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
270			Log.d(Config.LOGTAG, "Received unblock update from server");
271			final Collection<Element> items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren();
272			if (items.size() == 0) {
273				// No children to unblock == unblock all
274				account.getBlocklist().clear();
275			} else {
276				final Collection<Jid> jids = new ArrayList<>(items.size());
277				for (final Element item : items) {
278					if (item.getName().equals("item")) {
279						final Jid jid = item.getAttributeAsJid("jid");
280						if (jid != null) {
281							jids.add(jid);
282						}
283					}
284				}
285				account.getBlocklist().removeAll(jids);
286			}
287			mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
288		} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
289				|| packet.hasChild("data", "http://jabber.org/protocol/ibb")) {
290			mXmppConnectionService.getJingleConnectionManager()
291				.deliverIbbPacket(account, packet);
292		} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
293			final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet);
294			mXmppConnectionService.sendIqPacket(account, response, null);
295		} else if (packet.hasChild("query","jabber:iq:version")) {
296			final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
297			mXmppConnectionService.sendIqPacket(account,response,null);
298		} else if (packet.hasChild("ping", "urn:xmpp:ping")) {
299			final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
300			mXmppConnectionService.sendIqPacket(account, response, null);
301		} else {
302			if ((packet.getType() == IqPacket.TYPE.GET)
303					|| (packet.getType() == IqPacket.TYPE.SET)) {
304				final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
305				final Element error = response.addChild("error");
306				error.setAttribute("type", "cancel");
307				error.addChild("feature-not-implemented",
308						"urn:ietf:params:xml:ns:xmpp-stanzas");
309				account.getXmppConnection().sendIqPacket(response, null);
310					}
311		}
312	}
313
314}