diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 8f96a21ab..62777f194 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -17,6 +17,7 @@ import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; @@ -113,6 +114,10 @@ public class MucOptions { return MessageArchiveService.Version.has(getFeatures()); } + public boolean push() { + return getFeatures().contains(Namespace.PUSH); + } + public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) { this.serviceDiscoveryResult = serviceDiscoveryResult; String name; diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 69af74ac2..af5b2cd39 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -436,16 +436,26 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket enablePush(Jid jid, String node, String secret) { + public IqPacket enablePush(final Jid jid, final String node, final String secret) { IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - Element enable = packet.addChild("enable", "urn:xmpp:push:0"); + Element enable = packet.addChild("enable", Namespace.PUSH); enable.setAttribute("jid", jid.toString()); enable.setAttribute("node", node); - Data data = new Data(); - data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS); - data.put("secret", secret); - data.submit(); - enable.addChild(data); + if (secret != null) { + Data data = new Data(); + data.setFormType(Namespace.PUBSUB_PUBLISH_OPTIONS); + data.put("secret", secret); + data.submit(); + enable.addChild(data); + } + return packet; + } + + public IqPacket disablePush(final Jid jid, final String node) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + Element disable = packet.addChild("disable", Namespace.PUSH); + disable.setAttribute("jid", jid.toString()); + disable.setAttribute("node", node); return packet; } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 3b4cfba90..3042e510f 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -26,6 +26,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Element; @@ -37,360 +38,383 @@ import rocks.xmpp.addr.Jid; public class IqParser extends AbstractParser implements OnIqPacketReceived { - public IqParser(final XmppConnectionService service) { - super(service); - } + public IqParser(final XmppConnectionService service) { + super(service); + } - private void rosterItems(final Account account, final Element query) { - final String version = query.getAttribute("ver"); - if (version != null) { - account.getRoster().setVersion(version); - } - for (final Element item : query.getChildren()) { - if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); - if (jid == null) { - continue; - } - final String name = item.getAttribute("name"); - final String subscription = item.getAttribute("subscription"); - final Contact contact = account.getRoster().getContact(jid); - boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); - if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { - contact.setServerName(name); - contact.parseGroupsFromElement(item); - } - if ("remove".equals(subscription)) { - contact.resetOption(Contact.Options.IN_ROSTER); - contact.resetOption(Contact.Options.DIRTY_DELETE); - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - } else { - contact.setOption(Contact.Options.IN_ROSTER); - contact.resetOption(Contact.Options.DIRTY_PUSH); - contact.parseSubscriptionFromElement(item); - } - boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); - if ((both != bothPre) && both) { - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": gained mutual presence subscription with "+contact.getJid()); - AxolotlService axolotlService = account.getAxolotlService(); - if (axolotlService != null) { - axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); - } - } - mXmppConnectionService.getAvatarService().clear(contact); - } - } - mXmppConnectionService.updateConversationUi(); - mXmppConnectionService.updateRosterUi(); - mXmppConnectionService.getShortcutService().refresh(); - mXmppConnectionService.syncRoster(account); - } + private void rosterItems(final Account account, final Element query) { + final String version = query.getAttribute("ver"); + if (version != null) { + account.getRoster().setVersion(version); + } + for (final Element item : query.getChildren()) { + if (item.getName().equals("item")) { + final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + if (jid == null) { + continue; + } + final String name = item.getAttribute("name"); + final String subscription = item.getAttribute("subscription"); + final Contact contact = account.getRoster().getContact(jid); + boolean bothPre = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + if (!contact.getOption(Contact.Options.DIRTY_PUSH)) { + contact.setServerName(name); + contact.parseGroupsFromElement(item); + } + if ("remove".equals(subscription)) { + contact.resetOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_DELETE); + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + } else { + contact.setOption(Contact.Options.IN_ROSTER); + contact.resetOption(Contact.Options.DIRTY_PUSH); + contact.parseSubscriptionFromElement(item); + } + boolean both = contact.getOption(Contact.Options.TO) && contact.getOption(Contact.Options.FROM); + if ((both != bothPre) && both) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": gained mutual presence subscription with " + contact.getJid()); + AxolotlService axolotlService = account.getAxolotlService(); + if (axolotlService != null) { + axolotlService.clearErrorsInFetchStatusMap(contact.getJid()); + } + } + mXmppConnectionService.getAvatarService().clear(contact); + } + } + mXmppConnectionService.updateConversationUi(); + mXmppConnectionService.updateRosterUi(); + mXmppConnectionService.getShortcutService().refresh(); + mXmppConnectionService.syncRoster(account); + } - public String avatarData(final IqPacket packet) { - final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); - if (pubsub == null) { - return null; - } - final Element items = pubsub.findChild("items"); - if (items == null) { - return null; - } - return super.avatarData(items); - } + public String avatarData(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return super.avatarData(items); + } - public Element getItem(final IqPacket packet) { - final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); - if (pubsub == null) { - return null; - } - final Element items = pubsub.findChild("items"); - if (items == null) { - return null; - } - return items.findChild("item"); - } + public Element getItem(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return items.findChild("item"); + } - @NonNull - public Set deviceIds(final Element item) { - Set deviceIds = new HashSet<>(); - if (item != null) { - final Element list = item.findChild("list"); - if (list != null) { - for (Element device : list.getChildren()) { - if (!device.getName().equals("device")) { - continue; - } - try { - Integer id = Integer.valueOf(device.getAttribute("id")); - deviceIds.add(id); - } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered invalid node in PEP ("+e.getMessage()+"):" + device.toString()+ ", skipping..."); - continue; - } - } - } - } - return deviceIds; - } + @NonNull + public Set deviceIds(final Element item) { + Set deviceIds = new HashSet<>(); + if (item != null) { + final Element list = item.findChild("list"); + if (list != null) { + for (Element device : list.getChildren()) { + if (!device.getName().equals("device")) { + continue; + } + try { + Integer id = Integer.valueOf(device.getAttribute("id")); + deviceIds.add(id); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered invalid node in PEP (" + e.getMessage() + "):" + device.toString() + ", skipping..."); + continue; + } + } + } + } + return deviceIds; + } - public Integer signedPreKeyId(final Element bundle) { - final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); - if(signedPreKeyPublic == null) { - return null; - } - try { - return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); - } catch (NumberFormatException e) { - return null; - } - } + public Integer signedPreKeyId(final Element bundle) { + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if (signedPreKeyPublic == null) { + return null; + } + try { + return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); + } catch (NumberFormatException e) { + return null; + } + } - public ECPublicKey signedPreKeyPublic(final Element bundle) { - ECPublicKey publicKey = null; - final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); - if(signedPreKeyPublic == null) { - return null; - } - try { - publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0); - } catch (Throwable e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage()); - } - return publicKey; - } + public ECPublicKey signedPreKeyPublic(final Element bundle) { + ECPublicKey publicKey = null; + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if (signedPreKeyPublic == null) { + return null; + } + try { + publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(), Base64.DEFAULT), 0); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + } + return publicKey; + } - public byte[] signedPreKeySignature(final Element bundle) { - final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); - if(signedPreKeySignature == null) { - return null; - } - try { - return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); - } catch (Throwable e) { - Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature"); - return null; - } - } + public byte[] signedPreKeySignature(final Element bundle) { + final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); + if (signedPreKeySignature == null) { + return null; + } + try { + return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : Invalid base64 in signedPreKeySignature"); + return null; + } + } - public IdentityKey identityKey(final Element bundle) { - IdentityKey identityKey = null; - final Element identityKeyElement = bundle.findChild("identityKey"); - if(identityKeyElement == null) { - return null; - } - try { - identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); - } catch (Throwable e) { - Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage()); - } - return identityKey; - } + public IdentityKey identityKey(final Element bundle) { + IdentityKey identityKey = null; + final Element identityKeyElement = bundle.findChild("identityKey"); + if (identityKeyElement == null) { + return null; + } + try { + identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid identityKey in PEP: " + e.getMessage()); + } + return identityKey; + } - public Map preKeyPublics(final IqPacket packet) { - Map preKeyRecords = new HashMap<>(); - Element item = getItem(packet); - if (item == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find in bundle IQ packet: " + packet); - return null; - } - final Element bundleElement = item.findChild("bundle"); - if(bundleElement == null) { - return null; - } - final Element prekeysElement = bundleElement.findChild("prekeys"); - if(prekeysElement == null) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find in bundle IQ packet: " + packet); - return null; - } - for(Element preKeyPublicElement : prekeysElement.getChildren()) { - if(!preKeyPublicElement.getName().equals("preKeyPublic")){ - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement); - continue; - } - Integer preKeyId = null; - try { - preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); - final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); - preKeyRecords.put(preKeyId, preKeyPublic); - } catch (NumberFormatException e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"could not parse preKeyId from preKey "+preKeyPublicElement.toString()); - } catch (Throwable e) { - Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); - } - } - return preKeyRecords; - } + public Map preKeyPublics(final IqPacket packet) { + Map preKeyRecords = new HashMap<>(); + Element item = getItem(packet); + if (item == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + return null; + } + final Element bundleElement = item.findChild("bundle"); + if (bundleElement == null) { + return null; + } + final Element prekeysElement = bundleElement.findChild("prekeys"); + if (prekeysElement == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Couldn't find in bundle IQ packet: " + packet); + return null; + } + for (Element preKeyPublicElement : prekeysElement.getChildren()) { + if (!preKeyPublicElement.getName().equals("preKeyPublic")) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + continue; + } + Integer preKeyId = null; + try { + preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); + final ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); + preKeyRecords.put(preKeyId, preKeyPublic); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "could not parse preKeyId from preKey " + preKeyPublicElement.toString()); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Invalid preKeyPublic (ID=" + preKeyId + ") in PEP: " + e.getMessage() + ", skipping..."); + } + } + return preKeyRecords; + } - public Pair verification(final IqPacket packet) { - Element item = getItem(packet); - Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null; - Element chain = verification != null ? verification.findChild("chain") : null; - Element signature = verification != null ? verification.findChild("signature") : null; - if (chain != null && signature != null) { - List certElements = chain.getChildren(); - X509Certificate[] certificates = new X509Certificate[certElements.size()]; - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - int i = 0; - for(Element cert : certElements) { - certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT))); - ++i; - } - return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT)); - } catch (CertificateException e) { - return null; - } - } else { - return null; - } - } + public Pair verification(final IqPacket packet) { + Element item = getItem(packet); + Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; + Element chain = verification != null ? verification.findChild("chain") : null; + Element signature = verification != null ? verification.findChild("signature") : null; + if (chain != null && signature != null) { + List certElements = chain.getChildren(); + X509Certificate[] certificates = new X509Certificate[certElements.size()]; + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + int i = 0; + for (Element cert : certElements) { + certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(), Base64.DEFAULT))); + ++i; + } + return new Pair<>(certificates, Base64.decode(signature.getContent(), Base64.DEFAULT)); + } catch (CertificateException e) { + return null; + } + } else { + return null; + } + } - public PreKeyBundle bundle(final IqPacket bundle) { - Element bundleItem = getItem(bundle); - if(bundleItem == null) { - return null; - } - final Element bundleElement = bundleItem.findChild("bundle"); - if(bundleElement == null) { - return null; - } - ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); - Integer signedPreKeyId = signedPreKeyId(bundleElement); - byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); - IdentityKey identityKey = identityKey(bundleElement); - if(signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) { - return null; - } + public PreKeyBundle bundle(final IqPacket bundle) { + Element bundleItem = getItem(bundle); + if (bundleItem == null) { + return null; + } + final Element bundleElement = bundleItem.findChild("bundle"); + if (bundleElement == null) { + return null; + } + ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); + Integer signedPreKeyId = signedPreKeyId(bundleElement); + byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); + IdentityKey identityKey = identityKey(bundleElement); + if (signedPreKeyId == null || signedPreKeyPublic == null || identityKey == null) { + return null; + } - return new PreKeyBundle(0, 0, 0, null, - signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); - } + return new PreKeyBundle(0, 0, 0, null, + signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + } - public List preKeys(final IqPacket preKeys) { - List bundles = new ArrayList<>(); - Map preKeyPublics = preKeyPublics(preKeys); - if ( preKeyPublics != null) { - for (Integer preKeyId : preKeyPublics.keySet()) { - ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); - bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, - 0, null, null, null)); - } - } + public List preKeys(final IqPacket preKeys) { + List bundles = new ArrayList<>(); + Map preKeyPublics = preKeyPublics(preKeys); + if (preKeyPublics != null) { + for (Integer preKeyId : preKeyPublics.keySet()) { + ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, + 0, null, null, null)); + } + } - return bundles; - } + return bundles; + } - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - final boolean isGet = packet.getType() == IqPacket.TYPE.GET; - if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; - } - if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) { - final Element query = packet.findChild("query"); - // If this is in response to a query for the whole roster: - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getRoster().markAllAsNotInRoster(); - } - this.rosterItems(account, query); - } else if ((packet.hasChild("block", Namespace.BLOCKING) || packet.hasChild("blocklist", Namespace.BLOCKING)) && - packet.fromServer(account)) { - // Block list or block push. - Log.d(Config.LOGTAG, "Received blocklist update from server"); - final Element blocklist = packet.findChild("blocklist", Namespace.BLOCKING); - final Element block = packet.findChild("block", Namespace.BLOCKING); - final Collection items = blocklist != null ? blocklist.getChildren() : - (block != null ? block.getChildren() : null); - // If this is a response to a blocklist query, clear the block list and replace with the new one. - // Otherwise, just update the existing blocklist. - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.clearBlocklist(); - account.getXmppConnection().getFeatures().setBlockListRequested(true); - } - if (items != null) { - final Collection jids = new ArrayList<>(items.size()); - // Create a collection of Jids from the packet - for (final Element item : items) { - if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); - if (jid != null) { - jids.add(jid); - } - } - } - account.getBlocklist().addAll(jids); - if (packet.getType() == IqPacket.TYPE.SET) { - boolean removed = false; - for(Jid jid : jids) { - removed |= mXmppConnectionService.removeBlockedConversations(account,jid); - } - if (removed) { - mXmppConnectionService.updateConversationUi(); - } - } - } - // Update the UI - mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); - if (packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } - } else if (packet.hasChild("unblock", Namespace.BLOCKING) && - packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { - Log.d(Config.LOGTAG, "Received unblock update from server"); - final Collection items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); - if (items.size() == 0) { - // No children to unblock == unblock all - account.getBlocklist().clear(); - } else { - final Collection jids = new ArrayList<>(items.size()); - for (final Element item : items) { - if (item.getName().equals("item")) { - final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); - if (jid != null) { - jids.add(jid); - } - } - } - account.getBlocklist().removeAll(jids); - } - mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") - || packet.hasChild("data", "http://jabber.org/protocol/ibb") - || packet.hasChild("close","http://jabber.org/protocol/ibb")) { - mXmppConnectionService.getJingleConnectionManager() - .deliverIbbPacket(account, packet); - } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("query","jabber:iq:version") && isGet) { - final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); - mXmppConnectionService.sendIqPacket(account,response,null); - } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("time","urn:xmpp:time") && isGet) { - final IqPacket response; - if (mXmppConnectionService.useTorToConnect() || account.isOnion()) { - response = packet.generateResponse(IqPacket.TYPE.ERROR); - final Element error = response.addChild("error"); - error.setAttribute("type","cancel"); - error.addChild("not-allowed","urn:ietf:params:xml:ns:xmpp-stanzas"); - } else { - response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); - } - mXmppConnectionService.sendIqPacket(account,response, null); - } else { - if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); - final Element error = response.addChild("error"); - error.setAttribute("type", "cancel"); - error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas"); - account.getXmppConnection().sendIqPacket(response, null); - } - } - } + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + final boolean isGet = packet.getType() == IqPacket.TYPE.GET; + if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } + if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) { + final Element query = packet.findChild("query"); + // If this is in response to a query for the whole roster: + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.getRoster().markAllAsNotInRoster(); + } + this.rosterItems(account, query); + } else if ((packet.hasChild("block", Namespace.BLOCKING) || packet.hasChild("blocklist", Namespace.BLOCKING)) && + packet.fromServer(account)) { + // Block list or block push. + Log.d(Config.LOGTAG, "Received blocklist update from server"); + final Element blocklist = packet.findChild("blocklist", Namespace.BLOCKING); + final Element block = packet.findChild("block", Namespace.BLOCKING); + final Collection items = blocklist != null ? blocklist.getChildren() : + (block != null ? block.getChildren() : null); + // If this is a response to a blocklist query, clear the block list and replace with the new one. + // Otherwise, just update the existing blocklist. + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.clearBlocklist(); + account.getXmppConnection().getFeatures().setBlockListRequested(true); + } + if (items != null) { + final Collection jids = new ArrayList<>(items.size()); + // Create a collection of Jids from the packet + for (final Element item : items) { + if (item.getName().equals("item")) { + final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + if (jid != null) { + jids.add(jid); + } + } + } + account.getBlocklist().addAll(jids); + if (packet.getType() == IqPacket.TYPE.SET) { + boolean removed = false; + for (Jid jid : jids) { + removed |= mXmppConnectionService.removeBlockedConversations(account, jid); + } + if (removed) { + mXmppConnectionService.updateConversationUi(); + } + } + } + // Update the UI + mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + if (packet.getType() == IqPacket.TYPE.SET) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } + } else if (packet.hasChild("unblock", Namespace.BLOCKING) && + packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { + Log.d(Config.LOGTAG, "Received unblock update from server"); + final Collection items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); + if (items.size() == 0) { + // No children to unblock == unblock all + account.getBlocklist().clear(); + } else { + final Collection jids = new ArrayList<>(items.size()); + for (final Element item : items) { + if (item.getName().equals("item")) { + final Jid jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid")); + if (jid != null) { + jids.add(jid); + } + } + } + account.getBlocklist().removeAll(jids); + } + mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") + || packet.hasChild("data", "http://jabber.org/protocol/ibb") + || packet.hasChild("close", "http://jabber.org/protocol/ibb")) { + mXmppConnectionService.getJingleConnectionManager() + .deliverIbbPacket(account, packet); + } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { + final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("query", "jabber:iq:version") && isGet) { + final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("time", "urn:xmpp:time") && isGet) { + final IqPacket response; + if (mXmppConnectionService.useTorToConnect() || account.isOnion()) { + response = packet.generateResponse(IqPacket.TYPE.ERROR); + final Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas"); + } else { + response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); + } + mXmppConnectionService.sendIqPacket(account, response, null); + } else if (packet.hasChild("pubsub", Namespace.PUBSUB) && packet.getType() == IqPacket.TYPE.SET) { + final Jid server = packet.getFrom(); + final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); + final Element publish = pubsub == null ? null : pubsub.findChild("publish"); + final String node = publish == null ? null : publish.getAttribute("node"); + final Element item = publish == null ? null : publish.findChild("item"); + final Element notification = item == null ? null : item.findChild("notification", Namespace.PUSH); + if (notification != null && node != null && server != null) { + final Conversation conversation = mXmppConnectionService.findConversationByUuid(node); + if (conversation != null && conversation.getAccount() == account && conversation.getJid().getDomain().equals(server.getDomain())) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received muc push event for "+conversation.getJid().asBareJid()); + mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null); + mXmppConnectionService.mucSelfPingAndRejoin(conversation); + } else { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received push event for unknown conference from "+server); + final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + final Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas"); + mXmppConnectionService.sendIqPacket(account, response, null); + } + } + + } else { + if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { + final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); + final Element error = response.addChild("error"); + error.setAttribute("type", "cancel"); + error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas"); + account.getXmppConnection().sendIqPacket(response, null); + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index d39ebbc7b..75fa9b699 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2537,6 +2537,9 @@ public class XmppConnectionService extends Service { saveConversationAsBookmark(conversation, null); } } + if (mucOptions.push()) { + enableMucPush(conversation); + } synchronized (account.inProgressConferenceJoins) { account.inProgressConferenceJoins.remove(conversation); sendUnsentMessages(conversation); @@ -2580,6 +2583,35 @@ public class XmppConnectionService extends Service { } } + private void enableMucPush(final Conversation conversation) { + final Account account = conversation.getAccount(); + final Jid room = conversation.getJid().asBareJid(); + final IqPacket enable = mIqGenerator.enablePush(conversation.getAccount().getJid(), conversation.getUuid(), null); + enable.setTo(room); + sendIqPacket(account, enable, (a, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": enabled push for muc "+room); + } else if (response.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to enable push for muc "+room+" "+response.getError()); + } + }); + + } + + private void disableMucPush(final Conversation conversation) { + final Account account = conversation.getAccount(); + final Jid room = conversation.getJid().asBareJid(); + final IqPacket disable = mIqGenerator.disablePush(conversation.getAccount().getJid(), conversation.getUuid()); + disable.setTo(room); + sendIqPacket(account, disable, (a, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": disabled push for muc "+room); + } else if (response.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,a.getJid().asBareJid()+": unable to disable push for muc "+room+" "+response.getError()); + } + }); + } + private void fetchConferenceMembers(final Conversation conversation) { final Account account = conversation.getAccount(); final AxolotlService axolotlService = account.getAxolotlService(); @@ -2759,6 +2791,9 @@ public class XmppConnectionService extends Service { account.pendingConferenceJoins.remove(conversation); account.pendingConferenceLeaves.remove(conversation); if (account.getStatus() == Account.State.ONLINE || now) { + if (conversation.getMucOptions().push()) { + disableMucPush(conversation); + } sendPresencePacket(conversation.getAccount(), mPresenceGenerator.leave(conversation.getMucOptions())); conversation.getMucOptions().setOffline(); Bookmark bookmark = conversation.getBookmark(); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 73d3d452d..cfc20b1a2 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -28,4 +28,5 @@ public final class Namespace { public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1"; public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1"; public static final String PING = "urn:xmpp:ping"; + public static final String PUSH = "urn:xmpp:push:0"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index d157926b6..a57f0ae63 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1819,8 +1819,8 @@ public class XmppConnection implements Runnable { } public boolean push() { - return hasDiscoFeature(account.getJid().asBareJid(), "urn:xmpp:push:0") - || hasDiscoFeature(Jid.of(account.getServer()), "urn:xmpp:push:0"); + return hasDiscoFeature(account.getJid().asBareJid(), Namespace.PUSH) + || hasDiscoFeature(Jid.of(account.getServer()), Namespace.PUSH); } public boolean rosterVersioning() {