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, final boolean reportSpam, final String serverMsgId) {
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 final Element report = item.addChild("report", Namespace.REPORTING);
369 report.setAttribute("reason", Namespace.REPORTING_REASON_SPAM);
370 if (serverMsgId != null) {
371 final Element stanzaId = report.addChild("stanza-id", Namespace.STANZA_IDS);
372 stanzaId.setAttribute("by", jid);
373 stanzaId.setAttribute("id", serverMsgId);
374 }
375 }
376 Log.d(Config.LOGTAG, iq.toString());
377 return iq;
378 }
379
380 public IqPacket generateSetUnblockRequest(final Jid jid) {
381 final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
382 final Element block = iq.addChild("unblock", Namespace.BLOCKING);
383 block.addChild("item").setAttribute("jid", jid);
384 return iq;
385 }
386
387 public IqPacket generateSetPassword(final Account account, final String newPassword) {
388 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
389 packet.setTo(account.getDomain());
390 final Element query = packet.addChild("query", Namespace.REGISTER);
391 final Jid jid = account.getJid();
392 query.addChild("username").setContent(jid.getLocal());
393 query.addChild("password").setContent(newPassword);
394 return packet;
395 }
396
397 public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
398 List<Jid> jids = new ArrayList<>();
399 jids.add(jid);
400 return changeAffiliation(conference, jids, affiliation);
401 }
402
403 public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
404 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
405 packet.setTo(conference.getJid().asBareJid());
406 packet.setFrom(conference.getAccount().getJid());
407 Element query = packet.query("http://jabber.org/protocol/muc#admin");
408 for (Jid jid : jids) {
409 Element item = query.addChild("item");
410 item.setAttribute("jid", jid);
411 item.setAttribute("affiliation", affiliation);
412 }
413 return packet;
414 }
415
416 public IqPacket changeRole(Conversation conference, String nick, String role) {
417 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
418 packet.setTo(conference.getJid().asBareJid());
419 packet.setFrom(conference.getAccount().getJid());
420 Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
421 item.setAttribute("nick", nick);
422 item.setAttribute("role", role);
423 return packet;
424 }
425
426 public IqPacket moderateMessage(Account account, Message m, String reason) {
427 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
428 packet.setTo(m.getConversation().getJid().asBareJid());
429 packet.setFrom(account.getJid());
430 Element moderate =
431 packet.addChild("apply-to", "urn:xmpp:fasten:0")
432 .setAttribute("id", m.getServerMsgId())
433 .addChild("moderate", "urn:xmpp:message-moderate:0");
434 moderate.addChild("retract", "urn:xmpp:message-retract:0");
435 moderate.addChild("reason", "urn:xmpp:message-moderate:0").setContent(reason);
436 return packet;
437 }
438
439 public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
440 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
441 packet.setTo(host);
442 Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
443 request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name);
444 request.setAttribute("size", file.getExpectedSize());
445 request.setAttribute("content-type", mime);
446 return packet;
447 }
448
449 public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
450 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
451 packet.setTo(host);
452 Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
453 request.addChild("filename").setContent(convertFilename(file.getName()));
454 request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
455 request.addChild("content-type").setContent(mime);
456 return packet;
457 }
458
459 private static String convertFilename(String name) {
460 int pos = name.indexOf('.');
461 if (pos != -1) {
462 try {
463 UUID uuid = UUID.fromString(name.substring(0, pos));
464 ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
465 bb.putLong(uuid.getMostSignificantBits());
466 bb.putLong(uuid.getLeastSignificantBits());
467 return Base64.encodeToString(bb.array(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP) + name.substring(pos);
468 } catch (Exception e) {
469 return name;
470 }
471 } else {
472 return name;
473 }
474 }
475
476 public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
477 final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
478 register.setFrom(account.getJid().asBareJid());
479 register.setTo(account.getDomain());
480 register.setId(id);
481 Element query = register.query(Namespace.REGISTER);
482 if (data != null) {
483 query.addChild(data);
484 }
485 return register;
486 }
487
488 public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
489 return pushTokenToAppServer(appServer, token, deviceId, null);
490 }
491
492 public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
493 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
494 packet.setTo(appServer);
495 final Element command = packet.addChild("command", Namespace.COMMANDS);
496 command.setAttribute("node", "register-push-fcm");
497 command.setAttribute("action", "execute");
498 final Data data = new Data();
499 data.put("token", token);
500 data.put("android-id", deviceId);
501 if (muc != null) {
502 data.put("muc", muc.toEscapedString());
503 }
504 data.submit();
505 command.addChild(data);
506 return packet;
507 }
508
509 public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
510 final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
511 packet.setTo(appServer);
512 final Element command = packet.addChild("command", Namespace.COMMANDS);
513 command.setAttribute("node", "unregister-push-fcm");
514 command.setAttribute("action", "execute");
515 final Data data = new Data();
516 data.put("channel", channel);
517 data.put("android-id", deviceId);
518 data.submit();
519 command.addChild(data);
520 return packet;
521 }
522
523 public IqPacket enablePush(final Jid jid, final String node, final String secret) {
524 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
525 Element enable = packet.addChild("enable", Namespace.PUSH);
526 enable.setAttribute("jid", jid);
527 enable.setAttribute("node", node);
528 if (secret != null) {
529 Data data = new Data();
530 data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS);
531 data.put("secret", secret);
532 data.submit();
533 enable.addChild(data);
534 }
535 return packet;
536 }
537
538 public IqPacket disablePush(final Jid jid, final String node) {
539 IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
540 Element disable = packet.addChild("disable", Namespace.PUSH);
541 disable.setAttribute("jid", jid);
542 disable.setAttribute("node", node);
543 return packet;
544 }
545
546 public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
547 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
548 packet.setTo(conversation.getJid().asBareJid());
549 packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
550 return packet;
551 }
552
553 public static Bundle defaultGroupChatConfiguration() {
554 Bundle options = new Bundle();
555 options.putString("muc#roomconfig_persistentroom", "1");
556 options.putString("muc#roomconfig_membersonly", "1");
557 options.putString("muc#roomconfig_publicroom", "0");
558 options.putString("muc#roomconfig_whois", "anyone");
559 options.putString("muc#roomconfig_changesubject", "0");
560 options.putString("muc#roomconfig_allowinvites", "0");
561 options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
562 options.putString("mam", "1"); //ejabberd community
563 options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
564 return options;
565 }
566
567 public static Bundle defaultChannelConfiguration() {
568 Bundle options = new Bundle();
569 options.putString("muc#roomconfig_persistentroom", "1");
570 options.putString("muc#roomconfig_membersonly", "0");
571 options.putString("muc#roomconfig_publicroom", "1");
572 options.putString("muc#roomconfig_whois", "moderators");
573 options.putString("muc#roomconfig_changesubject", "0");
574 options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
575 options.putString("mam", "1"); //ejabberd community
576 options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
577 return options;
578 }
579
580 public IqPacket requestPubsubConfiguration(Jid jid, String node) {
581 return pubsubConfiguration(jid, node, null);
582 }
583
584 public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) {
585 return pubsubConfiguration(jid, node, data);
586 }
587
588 private IqPacket pubsubConfiguration(Jid jid, String node, Data data) {
589 IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
590 packet.setTo(jid);
591 Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
592 Element configure = pubsub.addChild("configure").setAttribute("node", node);
593 if (data != null) {
594 configure.addChild(data);
595 }
596 return packet;
597 }
598
599 public IqPacket queryDiscoItems(Jid jid) {
600 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
601 packet.setTo(jid);
602 packet.query(Namespace.DISCO_ITEMS);
603 return packet;
604 }
605
606 public IqPacket queryDiscoItems(Jid jid, String node) {
607 IqPacket packet = queryDiscoItems(jid);
608 final Element query = packet.query(Namespace.DISCO_ITEMS);
609 query.setAttribute("node", node);
610 return packet;
611 }
612
613 public IqPacket queryDiscoInfo(Jid jid) {
614 IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
615 packet.setTo(jid);
616 packet.addChild("query",Namespace.DISCO_INFO);
617 return packet;
618 }
619
620 public IqPacket bobResponse(IqPacket request) {
621 try {
622 String bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
623 Cid cid = BobTransfer.cid(bobCid);
624 DownloadableFile f = mXmppConnectionService.getFileForCid(cid);
625 if (f == null || !f.canRead()) {
626 throw new IOException("No such file");
627 } else if (f.getSize() > 129000) {
628 final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
629 final Element error = response.addChild("error");
630 error.setAttribute("type", "cancel");
631 error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas");
632 return response;
633 } else {
634 final IqPacket response = request.generateResponse(IqPacket.TYPE.RESULT);
635 final Element data = response.addChild("data", "urn:xmpp:bob");
636 data.setAttribute("cid", bobCid);
637 data.setAttribute("type", f.getMimeType());
638 ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2);
639 Base64OutputStream b64wrap = new Base64OutputStream(b64, Base64.NO_WRAP);
640 ByteStreams.copy(new FileInputStream(f), b64wrap);
641 b64wrap.flush();
642 b64wrap.close();
643 data.setContent(b64.toString("utf-8"));
644 return response;
645 }
646 } catch (final IOException | IllegalStateException e) {
647 final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
648 final Element error = response.addChild("error");
649 error.setAttribute("type", "cancel");
650 error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
651 return response;
652 }
653 }
654}