1package eu.siacs.conversations.generator;
2
3import android.os.Bundle;
4import android.util.Base64;
5import android.util.Base64OutputStream;
6import android.util.Log;
7
8import com.cheogram.android.BobTransfer;
9
10import com.google.common.io.ByteStreams;
11
12import org.whispersystems.libsignal.IdentityKey;
13import org.whispersystems.libsignal.ecc.ECPublicKey;
14import org.whispersystems.libsignal.state.PreKeyRecord;
15import org.whispersystems.libsignal.state.SignedPreKeyRecord;
16
17import java.io.ByteArrayOutputStream;
18import java.io.FileInputStream;
19import java.io.IOException;
20import java.nio.ByteBuffer;
21import java.security.cert.CertificateEncodingException;
22import java.security.cert.X509Certificate;
23import java.util.ArrayList;
24import java.util.List;
25import java.util.Locale;
26import java.util.Set;
27import java.util.TimeZone;
28import java.util.UUID;
29
30import io.ipfs.cid.Cid;
31
32import eu.siacs.conversations.Config;
33import eu.siacs.conversations.R;
34import eu.siacs.conversations.crypto.axolotl.AxolotlService;
35import eu.siacs.conversations.entities.Account;
36import eu.siacs.conversations.entities.Bookmark;
37import eu.siacs.conversations.entities.Conversation;
38import eu.siacs.conversations.entities.DownloadableFile;
39import eu.siacs.conversations.entities.Message;
40import eu.siacs.conversations.services.MessageArchiveService;
41import eu.siacs.conversations.services.XmppConnectionService;
42import eu.siacs.conversations.xml.Element;
43import eu.siacs.conversations.xml.Namespace;
44import eu.siacs.conversations.xmpp.Jid;
45import eu.siacs.conversations.xmpp.forms.Data;
46import eu.siacs.conversations.xmpp.pep.Avatar;
47import eu.siacs.conversations.xmpp.stanzas.IqPacket;
48
49public class IqGenerator extends AbstractGenerator {
50
51 public IqGenerator(final XmppConnectionService service) {
52 super(service);
53 }
54
55 public IqPacket discoResponse(final Account account, final IqPacket request) {
56 final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
57 packet.setId(request.getId());
58 packet.setTo(request.getFrom());
59 final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
60 query.setAttribute("node", request.query().getAttribute("node"));
61 final Element identity = query.addChild("identity");
62 identity.setAttribute("category", "client");
63 identity.setAttribute("type", getIdentityType());
64 identity.setAttribute("name", getIdentityName());
65 for (final String feature : getFeatures(account)) {
66 query.addChild("feature").setAttribute("var", feature);
67 }
68 return packet;
69 }
70
71 public IqPacket versionResponse(final IqPacket request) {
72 final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
73 Element query = packet.query("jabber:iq:version");
74 query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
75 query.addChild("version").setContent(getIdentityVersion());
76 if ("chromium".equals(android.os.Build.BRAND)) {
77 query.addChild("os").setContent("Chrome OS");
78 } else {
79 query.addChild("os").setContent("Android");
80 }
81 return packet;
82 }
83
84 public IqPacket entityTimeResponse(IqPacket request) {
85 final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
86 Element time = packet.addChild("time", "urn:xmpp:time");
87 final long now = System.currentTimeMillis();
88 time.addChild("utc").setContent(getTimestamp(now));
89 TimeZone ourTimezone = TimeZone.getDefault();
90 long offsetSeconds = ourTimezone.getOffset(now) / 1000;
91 long offsetMinutes = Math.abs((offsetSeconds % 3600) / 60);
92 long offsetHours = offsetSeconds / 3600;
93 String hours;
94 if (offsetHours < 0) {
95 hours = String.format(Locale.US, "%03d", offsetHours);
96 } else {
97 hours = String.format(Locale.US, "%02d", offsetHours);
98 }
99 String minutes = String.format(Locale.US, "%02d", offsetMinutes);
100 time.addChild("tzo").setContent(hours + ":" + minutes);
101 return packet;
102 }
103
104 public IqPacket purgeOfflineMessages() {
105 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
106 packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
107 return packet;
108 }
109
110 protected IqPacket publish(final String node, final Element item, final Bundle options) {
111 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
112 final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
113 final Element publish = pubsub.addChild("publish");
114 publish.setAttribute("node", node);
115 publish.addChild(item);
116 if (options != null) {
117 final Element publishOptions = pubsub.addChild("publish-options");
118 publishOptions.addChild(Data.create(Namespace.PUBSUB_PUBLISH_OPTIONS, options));
119 }
120 return packet;
121 }
122
123 protected IqPacket publish(final String node, final Element item) {
124 return publish(node, item, null);
125 }
126
127 private IqPacket retrieve(String node, Element item) {
128 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
129 final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
130 final Element items = pubsub.addChild("items");
131 items.setAttribute("node", node);
132 if (item != null) {
133 items.addChild(item);
134 }
135 return packet;
136 }
137
138 public IqPacket retrieveVcard4(final Jid jid) {
139 final IqPacket packet = retrieve("urn:xmpp:vcard4", null);
140 packet.setTo(jid);
141 return packet;
142 }
143
144 public IqPacket retrieveBookmarks() {
145 return retrieve(Namespace.BOOKMARKS2, null);
146 }
147
148 public IqPacket publishNick(String nick) {
149 final Element item = new Element("item");
150 item.setAttribute("id", "current");
151 item.addChild("nick", Namespace.NICK).setContent(nick);
152 return publish(Namespace.NICK, item);
153 }
154
155 public IqPacket deleteNode(final String node) {
156 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
157 final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
158 pubsub.addChild("delete").setAttribute("node", node);
159 return packet;
160 }
161
162 public IqPacket deleteItem(final String node, final String id) {
163 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
164 final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
165 final Element retract = pubsub.addChild("retract");
166 retract.setAttribute("node", node);
167 retract.setAttribute("notify","true");
168 retract.addChild("item").setAttribute("id", id);
169 return packet;
170 }
171
172 public IqPacket publishAvatar(Avatar avatar, Bundle options) {
173 final Element item = new Element("item");
174 item.setAttribute("id", avatar.sha1sum);
175 final Element data = item.addChild("data", Namespace.AVATAR_DATA);
176 data.setContent(avatar.image);
177 return publish(Namespace.AVATAR_DATA, item, options);
178 }
179
180 public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
181 final Element item = new Element("item");
182 item.setAttribute("id", id);
183 item.addChild(element);
184 return publish(namespace, item, options);
185 }
186
187 public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) {
188 final Element item = new Element("item");
189 item.setAttribute("id", avatar.sha1sum);
190 final Element metadata = item
191 .addChild("metadata", Namespace.AVATAR_METADATA);
192 final Element info = metadata.addChild("info");
193 info.setAttribute("bytes", avatar.size);
194 info.setAttribute("id", avatar.sha1sum);
195 info.setAttribute("height", avatar.height);
196 info.setAttribute("width", avatar.height);
197 info.setAttribute("type", avatar.type);
198 return publish(Namespace.AVATAR_METADATA, item, options);
199 }
200
201 public IqPacket retrievePepAvatar(final Avatar avatar) {
202 final Element item = new Element("item");
203 item.setAttribute("id", avatar.sha1sum);
204 final IqPacket packet = retrieve(Namespace.AVATAR_DATA, item);
205 packet.setTo(avatar.owner);
206 return packet;
207 }
208
209 public IqPacket retrieveVcardAvatar(final Avatar avatar) {
210 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
211 packet.setTo(avatar.owner);
212 packet.addChild("vCard", "vcard-temp");
213 return packet;
214 }
215
216 public IqPacket retrieveVcardAvatar(final Jid to) {
217 final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
218 packet.setTo(to);
219 packet.addChild("vCard", "vcard-temp");
220 return packet;
221 }
222
223 public IqPacket retrieveAvatarMetaData(final Jid to) {
224 final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
225 if (to != null) {
226 packet.setTo(to);
227 }
228 return packet;
229 }
230
231 public IqPacket retrieveDeviceIds(final Jid to) {
232 final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
233 if (to != null) {
234 packet.setTo(to);
235 }
236 return packet;
237 }
238
239 public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
240 final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
241 packet.setTo(to);
242 return packet;
243 }
244
245 public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
246 final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
247 packet.setTo(to);
248 return packet;
249 }
250
251 public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
252 final Element item = new Element("item");
253 item.setAttribute("id", "current");
254 final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
255 for (Integer id : ids) {
256 final Element device = new Element("device");
257 device.setAttribute("id", id);
258 list.addChild(device);
259 }
260 return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
261 }
262
263 public Element publishBookmarkItem(final Bookmark bookmark) {
264 final String name = bookmark.getBookmarkName();
265 final String nick = bookmark.getNick();
266 final String password = bookmark.getPassword();
267 final boolean autojoin = bookmark.autojoin();
268 final Element conference = new Element("conference", Namespace.BOOKMARKS2);
269 if (name != null) {
270 conference.setAttribute("name", name);
271 }
272 if (nick != null) {
273 conference.addChild("nick").setContent(nick);
274 }
275 if (password != null) {
276 conference.addChild("password").setContent(password);
277 }
278 conference.setAttribute("autojoin",String.valueOf(autojoin));
279 conference.addChild(bookmark.getExtensions());
280 return conference;
281 }
282
283 public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
284 final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
285 final Element item = new Element("item");
286 item.setAttribute("id", "current");
287 final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
288 final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
289 signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
290 ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
291 signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP));
292 final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
293 signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP));
294 final Element identityKeyElement = bundle.addChild("identityKey");
295 identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP));
296
297 final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
298 for (PreKeyRecord preKeyRecord : preKeyRecords) {
299 final Element prekey = prekeys.addChild("preKeyPublic");
300 prekey.setAttribute("preKeyId", preKeyRecord.getId());
301 prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP));
302 }
303
304 return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
305 }
306
307 public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
308 final Element item = new Element("item");
309 item.setAttribute("id", "current");
310 final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
311 final Element chain = verification.addChild("chain");
312 for (int i = 0; i < certificates.length; ++i) {
313 try {
314 Element certificate = chain.addChild("certificate");
315 certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP));
316 certificate.setAttribute("index", i);
317 } catch (CertificateEncodingException e) {
318 Log.d(Config.LOGTAG, "could not encode certificate");
319 }
320 }
321 verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.NO_WRAP));
322 return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
323 }
324
325 public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
326 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
327 final Element query = packet.query(mam.version.namespace);
328 query.setAttribute("queryid", mam.getQueryId());
329 final Data data = new Data();
330 data.setFormType(mam.version.namespace);
331 if (mam.muc()) {
332 packet.setTo(mam.getWith());
333 } else if (mam.getWith() != null) {
334 data.put("with", mam.getWith().toEscapedString());
335 }
336 final long start = mam.getStart();
337 final long end = mam.getEnd();
338 if (start != 0) {
339 data.put("start", getTimestamp(start));
340 }
341 if (end != 0) {
342 data.put("end", getTimestamp(end));
343 }
344 data.submit();
345 query.addChild(data);
346 Element set = query.addChild("set", "http://jabber.org/protocol/rsm");
347 if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
348 set.addChild("before").setContent(mam.getReference());
349 } else if (mam.getReference() != null) {
350 set.addChild("after").setContent(mam.getReference());
351 }
352 set.addChild("max").setContent(String.valueOf(Config.PAGE_SIZE));
353 return packet;
354 }
355
356 public IqPacket generateGetBlockList() {
357 final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
358 iq.addChild("blocklist", Namespace.BLOCKING);
359
360 return iq;
361 }
362
363 public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
364 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
365 final Element block = iq.addChild("block", Namespace.BLOCKING);
366 final Element item = block.addChild("item").setAttribute("jid", jid);
367 if (reportSpam) {
368 item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
369 }
370 Log.d(Config.LOGTAG, iq.toString());
371 return iq;
372 }
373
374 public IqPacket generateSetUnblockRequest(final Jid jid) {
375 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
376 final Element block = iq.addChild("unblock", Namespace.BLOCKING);
377 block.addChild("item").setAttribute("jid", jid);
378 return iq;
379 }
380
381 public IqPacket generateSetPassword(final Account account, final String newPassword) {
382 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
383 packet.setTo(account.getDomain());
384 final Element query = packet.addChild("query", Namespace.REGISTER);
385 final Jid jid = account.getJid();
386 query.addChild("username").setContent(jid.getLocal());
387 query.addChild("password").setContent(newPassword);
388 return packet;
389 }
390
391 public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
392 List<Jid> jids = new ArrayList<>();
393 jids.add(jid);
394 return changeAffiliation(conference, jids, affiliation);
395 }
396
397 public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
398 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
399 packet.setTo(conference.getJid().asBareJid());
400 packet.setFrom(conference.getAccount().getJid());
401 Element query = packet.query("http://jabber.org/protocol/muc#admin");
402 for (Jid jid : jids) {
403 Element item = query.addChild("item");
404 item.setAttribute("jid", jid);
405 item.setAttribute("affiliation", affiliation);
406 }
407 return packet;
408 }
409
410 public IqPacket changeRole(Conversation conference, String nick, String role) {
411 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
412 packet.setTo(conference.getJid().asBareJid());
413 packet.setFrom(conference.getAccount().getJid());
414 Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
415 item.setAttribute("nick", nick);
416 item.setAttribute("role", role);
417 return packet;
418 }
419
420 public IqPacket moderateMessage(Account account, Message m, String reason) {
421 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
422 packet.setTo(m.getConversation().getJid().asBareJid());
423 packet.setFrom(account.getJid());
424 Element moderate =
425 packet.addChild("apply-to", "urn:xmpp:fasten:0")
426 .setAttribute("id", m.getServerMsgId())
427 .addChild("moderate", "urn:xmpp:message-moderate:0");
428 moderate.addChild("retract", "urn:xmpp:message-retract:0");
429 moderate.addChild("reason", "urn:xmpp:message-moderate:0").setContent(reason);
430 return packet;
431 }
432
433 public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
434 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
435 packet.setTo(host);
436 Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
437 request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name);
438 request.setAttribute("size", file.getExpectedSize());
439 request.setAttribute("content-type", mime);
440 return packet;
441 }
442
443 public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
444 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
445 packet.setTo(host);
446 Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
447 request.addChild("filename").setContent(convertFilename(file.getName()));
448 request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
449 request.addChild("content-type").setContent(mime);
450 return packet;
451 }
452
453 private static String convertFilename(String name) {
454 int pos = name.indexOf('.');
455 if (pos != -1) {
456 try {
457 UUID uuid = UUID.fromString(name.substring(0, pos));
458 ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
459 bb.putLong(uuid.getMostSignificantBits());
460 bb.putLong(uuid.getLeastSignificantBits());
461 return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos);
462 } catch (Exception e) {
463 return name;
464 }
465 } else {
466 return name;
467 }
468 }
469
470 public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
471 final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
472 register.setFrom(account.getJid().asBareJid());
473 register.setTo(account.getDomain());
474 register.setId(id);
475 Element query = register.query(Namespace.REGISTER);
476 if (data != null) {
477 query.addChild(data);
478 }
479 return register;
480 }
481
482 public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
483 return pushTokenToAppServer(appServer, token, deviceId, null);
484 }
485
486 public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
487 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
488 packet.setTo(appServer);
489 final Element command = packet.addChild("command", Namespace.COMMANDS);
490 command.setAttribute("node", "register-push-fcm");
491 command.setAttribute("action", "execute");
492 final Data data = new Data();
493 data.put("token", token);
494 data.put("android-id", deviceId);
495 if (muc != null) {
496 data.put("muc", muc.toEscapedString());
497 }
498 data.submit();
499 command.addChild(data);
500 return packet;
501 }
502
503 public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
504 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
505 packet.setTo(appServer);
506 final Element command = packet.addChild("command", Namespace.COMMANDS);
507 command.setAttribute("node", "unregister-push-fcm");
508 command.setAttribute("action", "execute");
509 final Data data = new Data();
510 data.put("channel", channel);
511 data.put("android-id", deviceId);
512 data.submit();
513 command.addChild(data);
514 return packet;
515 }
516
517 public IqPacket enablePush(final Jid jid, final String node, final String secret) {
518 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
519 Element enable = packet.addChild("enable", Namespace.PUSH);
520 enable.setAttribute("jid", jid);
521 enable.setAttribute("node", node);
522 if (secret != null) {
523 Data data = new Data();
524 data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
525 data.put("secret", secret);
526 data.submit();
527 enable.addChild(data);
528 }
529 return packet;
530 }
531
532 public IqPacket disablePush(final Jid jid, final String node) {
533 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
534 Element disable = packet.addChild("disable", Namespace.PUSH);
535 disable.setAttribute("jid", jid);
536 disable.setAttribute("node", node);
537 return packet;
538 }
539
540 public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
541 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
542 packet.setTo(conversation.getJid().asBareJid());
543 packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
544 return packet;
545 }
546
547 public static Bundle defaultGroupChatConfiguration() {
548 Bundle options = new Bundle();
549 options.putString("muc#roomconfig_persistentroom", "1");
550 options.putString("muc#roomconfig_membersonly", "1");
551 options.putString("muc#roomconfig_publicroom", "0");
552 options.putString("muc#roomconfig_whois", "anyone");
553 options.putString("muc#roomconfig_changesubject", "0");
554 options.putString("muc#roomconfig_allowinvites", "0");
555 options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
556 options.putString("mam", "1"); //ejabberd community
557 options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
558 return options;
559 }
560
561 public static Bundle defaultChannelConfiguration() {
562 Bundle options = new Bundle();
563 options.putString("muc#roomconfig_persistentroom", "1");
564 options.putString("muc#roomconfig_membersonly", "0");
565 options.putString("muc#roomconfig_publicroom", "1");
566 options.putString("muc#roomconfig_whois", "moderators");
567 options.putString("muc#roomconfig_changesubject", "0");
568 options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
569 options.putString("mam", "1"); //ejabberd community
570 options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
571 return options;
572 }
573
574 public IqPacket requestPubsubConfiguration(Jid jid, String node) {
575 return pubsubConfiguration(jid, node, null);
576 }
577
578 public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) {
579 return pubsubConfiguration(jid, node, data);
580 }
581
582 private IqPacket pubsubConfiguration(Jid jid, String node, Data data) {
583 IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
584 packet.setTo(jid);
585 Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
586 Element configure = pubsub.addChild("configure").setAttribute("node", node);
587 if (data != null) {
588 configure.addChild(data);
589 }
590 return packet;
591 }
592
593 public IqPacket queryDiscoItems(Jid jid) {
594 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
595 packet.setTo(jid);
596 packet.query(Namespace.DISCO_ITEMS);
597 return packet;
598 }
599
600 public IqPacket queryDiscoItems(Jid jid, String node) {
601 IqPacket packet = queryDiscoItems(jid);
602 final Element query = packet.query(Namespace.DISCO_ITEMS);
603 query.setAttribute("node", node);
604 return packet;
605 }
606
607 public IqPacket queryDiscoInfo(Jid jid) {
608 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
609 packet.setTo(jid);
610 packet.addChild("query",Namespace.DISCO_INFO);
611 return packet;
612 }
613
614 public IqPacket bobResponse(IqPacket request) {
615 try {
616 String bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
617 Cid cid = BobTransfer.cid(bobCid);
618 DownloadableFile f = mXmppConnectionService.getFileForCid(cid);
619 if (f == null || !f.canRead()) {
620 throw new IOException("No such file");
621 } else if (f.getSize() > 129000) {
622 final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
623 final Element error = response.addChild("error");
624 error.setAttribute("type", "cancel");
625 error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas");
626 return response;
627 } else {
628 final IqPacket response = request.generateResponse(IqPacket.TYPE.RESULT);
629 final Element data = response.addChild("data", "urn:xmpp:bob");
630 data.setAttribute("cid", bobCid);
631 data.setAttribute("type", f.getMimeType());
632 ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2);
633 Base64OutputStream b64wrap = new Base64OutputStream(b64, Base64.NO_WRAP);
634 ByteStreams.copy(new FileInputStream(f), b64wrap);
635 b64wrap.flush();
636 b64wrap.close();
637 data.setContent(b64.toString("utf-8"));
638 return response;
639 }
640 } catch (final IOException | IllegalStateException e) {
641 final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
642 final Element error = response.addChild("error");
643 error.setAttribute("type", "cancel");
644 error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
645 return response;
646 }
647 }
648}