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}