1package eu.siacs.conversations.generator;
2
3
4import android.os.Bundle;
5import android.util.Base64;
6import android.util.Log;
7
8import org.whispersystems.libaxolotl.IdentityKey;
9import org.whispersystems.libaxolotl.ecc.ECPublicKey;
10import org.whispersystems.libaxolotl.state.PreKeyRecord;
11import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
12
13import java.nio.ByteBuffer;
14import java.security.cert.CertificateEncodingException;
15import java.security.cert.X509Certificate;
16import java.util.ArrayList;
17import java.util.List;
18import java.util.Set;
19import java.util.TimeZone;
20import java.util.UUID;
21
22import eu.siacs.conversations.Config;
23import eu.siacs.conversations.R;
24import eu.siacs.conversations.crypto.axolotl.AxolotlService;
25import eu.siacs.conversations.entities.Account;
26import eu.siacs.conversations.entities.Conversation;
27import eu.siacs.conversations.entities.DownloadableFile;
28import eu.siacs.conversations.services.MessageArchiveService;
29import eu.siacs.conversations.services.XmppConnectionService;
30import eu.siacs.conversations.utils.Xmlns;
31import eu.siacs.conversations.xml.Element;
32import eu.siacs.conversations.xmpp.forms.Data;
33import eu.siacs.conversations.xmpp.jid.Jid;
34import eu.siacs.conversations.xmpp.pep.Avatar;
35import eu.siacs.conversations.xmpp.stanzas.IqPacket;
36
37public class IqGenerator extends AbstractGenerator {
38
39 public IqGenerator(final XmppConnectionService service) {
40 super(service);
41 }
42
43 public IqPacket discoResponse(final IqPacket request) {
44 final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
45 packet.setId(request.getId());
46 packet.setTo(request.getFrom());
47 final Element query = packet.addChild("query",
48 "http://jabber.org/protocol/disco#info");
49 query.setAttribute("node", request.query().getAttribute("node"));
50 final Element identity = query.addChild("identity");
51 identity.setAttribute("category", "client");
52 identity.setAttribute("type", getIdentityType());
53 identity.setAttribute("name", getIdentityName());
54 for (final String feature : getFeatures()) {
55 query.addChild("feature").setAttribute("var", feature);
56 }
57 return packet;
58 }
59
60 public IqPacket versionResponse(final IqPacket request) {
61 final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
62 Element query = packet.query("jabber:iq:version");
63 query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
64 query.addChild("version").setContent(getIdentityVersion());
65 if ("chromium".equals(android.os.Build.BRAND)) {
66 query.addChild("os").setContent("Chrome OS");
67 } else{
68 query.addChild("os").setContent("Android");
69 }
70 return packet;
71 }
72
73 public IqPacket entityTimeResponse(IqPacket request) {
74 final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
75 Element time = packet.addChild("time","urn:xmpp:time");
76 final long now = System.currentTimeMillis();
77 time.addChild("utc").setContent(getTimestamp(now));
78 TimeZone ourTimezone = TimeZone.getDefault();
79 long offsetSeconds = ourTimezone.getOffset(now) / 1000;
80 long offsetMinutes = offsetSeconds % (60 * 60);
81 long offsetHours = offsetSeconds / (60 * 60);
82 time.addChild("tzo").setContent(String.format("%02d",offsetHours)+":"+String.format("%02d",offsetMinutes));
83 return packet;
84 }
85
86 protected IqPacket publish(final String node, final Element item) {
87 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
88 final Element pubsub = packet.addChild("pubsub",
89 "http://jabber.org/protocol/pubsub");
90 final Element publish = pubsub.addChild("publish");
91 publish.setAttribute("node", node);
92 publish.addChild(item);
93 return packet;
94 }
95
96 protected IqPacket retrieve(String node, Element item) {
97 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
98 final Element pubsub = packet.addChild("pubsub",
99 "http://jabber.org/protocol/pubsub");
100 final Element items = pubsub.addChild("items");
101 items.setAttribute("node", node);
102 if (item != null) {
103 items.addChild(item);
104 }
105 return packet;
106 }
107
108 public IqPacket publishNick(String nick) {
109 final Element item = new Element("item");
110 item.addChild("nick","http://jabber.org/protocol/nick").setContent(nick);
111 return publish("http://jabber.org/protocol/nick", item);
112 }
113
114 public IqPacket publishAvatar(Avatar avatar) {
115 final Element item = new Element("item");
116 item.setAttribute("id", avatar.sha1sum);
117 final Element data = item.addChild("data", "urn:xmpp:avatar:data");
118 data.setContent(avatar.image);
119 return publish("urn:xmpp:avatar:data", item);
120 }
121
122 public IqPacket publishAvatarMetadata(final Avatar avatar) {
123 final Element item = new Element("item");
124 item.setAttribute("id", avatar.sha1sum);
125 final Element metadata = item
126 .addChild("metadata", "urn:xmpp:avatar:metadata");
127 final Element info = metadata.addChild("info");
128 info.setAttribute("bytes", avatar.size);
129 info.setAttribute("id", avatar.sha1sum);
130 info.setAttribute("height", avatar.height);
131 info.setAttribute("width", avatar.height);
132 info.setAttribute("type", avatar.type);
133 return publish("urn:xmpp:avatar:metadata", item);
134 }
135
136 public IqPacket retrievePepAvatar(final Avatar avatar) {
137 final Element item = new Element("item");
138 item.setAttribute("id", avatar.sha1sum);
139 final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
140 packet.setTo(avatar.owner);
141 return packet;
142 }
143
144 public IqPacket retrieveVcardAvatar(final Avatar avatar) {
145 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
146 packet.setTo(avatar.owner);
147 packet.addChild("vCard", "vcard-temp");
148 return packet;
149 }
150
151 public IqPacket retrieveAvatarMetaData(final Jid to) {
152 final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
153 if (to != null) {
154 packet.setTo(to);
155 }
156 return packet;
157 }
158
159 public IqPacket retrieveDeviceIds(final Jid to) {
160 final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
161 if(to != null) {
162 packet.setTo(to);
163 }
164 return packet;
165 }
166
167 public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
168 final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null);
169 packet.setTo(to);
170 return packet;
171 }
172
173 public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
174 final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION+":"+deviceid, null);
175 packet.setTo(to);
176 return packet;
177 }
178
179 public IqPacket publishDeviceIds(final Set<Integer> ids) {
180 final Element item = new Element("item");
181 final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
182 for(Integer id:ids) {
183 final Element device = new Element("device");
184 device.setAttribute("id", id);
185 list.addChild(device);
186 }
187 return publish(AxolotlService.PEP_DEVICE_LIST, item);
188 }
189
190 public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
191 final Set<PreKeyRecord> preKeyRecords, final int deviceId) {
192 final Element item = new Element("item");
193 final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
194 final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
195 signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
196 ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
197 signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT));
198 final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
199 signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT));
200 final Element identityKeyElement = bundle.addChild("identityKey");
201 identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
202
203 final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
204 for(PreKeyRecord preKeyRecord:preKeyRecords) {
205 final Element prekey = prekeys.addChild("preKeyPublic");
206 prekey.setAttribute("preKeyId", preKeyRecord.getId());
207 prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
208 }
209
210 return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item);
211 }
212
213 public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
214 final Element item = new Element("item");
215 final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
216 final Element chain = verification.addChild("chain");
217 for(int i = 0; i < certificates.length; ++i) {
218 try {
219 Element certificate = chain.addChild("certificate");
220 certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT));
221 certificate.setAttribute("index",i);
222 } catch (CertificateEncodingException e) {
223 Log.d(Config.LOGTAG, "could not encode certificate");
224 }
225 }
226 verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
227 return publish(AxolotlService.PEP_VERIFICATION+":"+deviceId, item);
228 }
229
230 public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
231 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
232 final Element query = packet.query(mam.isLegacy() ? Xmlns.MAM_LEGACY : Xmlns.MAM);
233 query.setAttribute("queryid", mam.getQueryId());
234 final Data data = new Data();
235 data.setFormType(mam.isLegacy() ? Xmlns.MAM_LEGACY : Xmlns.MAM);
236 if (mam.muc()) {
237 packet.setTo(mam.getWith());
238 } else if (mam.getWith()!=null) {
239 data.put("with", mam.getWith().toString());
240 }
241 data.put("start", getTimestamp(mam.getStart()));
242 data.put("end", getTimestamp(mam.getEnd()));
243 data.submit();
244 query.addChild(data);
245 if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
246 query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference());
247 } else if (mam.getReference() != null) {
248 query.addChild("set", "http://jabber.org/protocol/rsm").addChild("after").setContent(mam.getReference());
249 }
250 return packet;
251 }
252 public IqPacket generateGetBlockList() {
253 final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
254 iq.addChild("blocklist", Xmlns.BLOCKING);
255
256 return iq;
257 }
258
259 public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
260 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
261 final Element block = iq.addChild("block", Xmlns.BLOCKING);
262 final Element item = block.addChild("item").setAttribute("jid", jid.toBareJid().toString());
263 if (reportSpam) {
264 item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
265 }
266 Log.d(Config.LOGTAG,iq.toString());
267 return iq;
268 }
269
270 public IqPacket generateSetUnblockRequest(final Jid jid) {
271 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
272 final Element block = iq.addChild("unblock", Xmlns.BLOCKING);
273 block.addChild("item").setAttribute("jid", jid.toBareJid().toString());
274 return iq;
275 }
276
277 public IqPacket generateSetPassword(final Account account, final String newPassword) {
278 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
279 packet.setTo(account.getServer());
280 final Element query = packet.addChild("query", Xmlns.REGISTER);
281 final Jid jid = account.getJid();
282 query.addChild("username").setContent(jid.getLocalpart());
283 query.addChild("password").setContent(newPassword);
284 return packet;
285 }
286
287 public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
288 List<Jid> jids = new ArrayList<>();
289 jids.add(jid);
290 return changeAffiliation(conference,jids,affiliation);
291 }
292
293 public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
294 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
295 packet.setTo(conference.getJid().toBareJid());
296 packet.setFrom(conference.getAccount().getJid());
297 Element query = packet.query("http://jabber.org/protocol/muc#admin");
298 for(Jid jid : jids) {
299 Element item = query.addChild("item");
300 item.setAttribute("jid", jid.toString());
301 item.setAttribute("affiliation", affiliation);
302 }
303 return packet;
304 }
305
306 public IqPacket changeRole(Conversation conference, String nick, String role) {
307 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
308 packet.setTo(conference.getJid().toBareJid());
309 packet.setFrom(conference.getAccount().getJid());
310 Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
311 item.setAttribute("nick", nick);
312 item.setAttribute("role", role);
313 return packet;
314 }
315
316 public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
317 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
318 packet.setTo(host);
319 Element request = packet.addChild("request", Xmlns.HTTP_UPLOAD);
320 request.addChild("filename").setContent(convertFilename(file.getName()));
321 request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
322 if (mime != null) {
323 request.addChild("content-type").setContent(mime);
324 }
325 return packet;
326 }
327
328 private static String convertFilename(String name) {
329 int pos = name.indexOf('.');
330 if (pos != -1) {
331 try {
332 UUID uuid = UUID.fromString(name.substring(0, pos));
333 ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
334 bb.putLong(uuid.getMostSignificantBits());
335 bb.putLong(uuid.getLeastSignificantBits());
336 return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos, name.length());
337 } catch (Exception e) {
338 return name;
339 }
340 } else {
341 return name;
342 }
343 }
344
345 public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
346 final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
347 register.setFrom(account.getJid().toBareJid());
348 register.setTo(account.getServer());
349 register.setId(id);
350 Element query = register.query("jabber:iq:register");
351 if (data != null) {
352 query.addChild(data);
353 }
354 return register;
355 }
356
357 public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
358 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
359 packet.setTo(appServer);
360 Element command = packet.addChild("command", "http://jabber.org/protocol/commands");
361 command.setAttribute("node","register-push-gcm");
362 command.setAttribute("action","execute");
363 Data data = new Data();
364 data.put("token", token);
365 data.put("device-id", deviceId);
366 data.submit();
367 command.addChild(data);
368 return packet;
369 }
370
371 public IqPacket enablePush(Jid jid, String node, String secret) {
372 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
373 Element enable = packet.addChild("enable","urn:xmpp:push:0");
374 enable.setAttribute("jid",jid.toString());
375 enable.setAttribute("node", node);
376 Data data = new Data();
377 data.setFormType("http://jabber.org/protocol/pubsub#publish-options");
378 data.put("secret",secret);
379 data.submit();
380 enable.addChild(data);
381 return packet;
382 }
383
384 public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
385 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
386 packet.setTo(conversation.getJid().toBareJid());
387 packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation",affiliation);
388 return packet;
389 }
390
391 public static Bundle defaultRoomConfiguration() {
392 Bundle options = new Bundle();
393 options.putString("muc#roomconfig_persistentroom", "1");
394 options.putString("muc#roomconfig_membersonly", "1");
395 options.putString("muc#roomconfig_publicroom", "0");
396 options.putString("muc#roomconfig_whois", "anyone");
397 return options;
398 }
399}