2014-10-22 16:38:44 +00:00
package eu.siacs.conversations.parser ;
2019-11-02 08:43:37 +00:00
import android.text.TextUtils ;
2014-11-17 19:45:00 +00:00
import android.util.Log ;
2015-10-16 21:48:42 +00:00
import android.util.Pair ;
2014-11-17 19:45:00 +00:00
2021-01-23 08:25:34 +00:00
import androidx.annotation.NonNull ;
2020-05-08 17:33:49 +00:00
import com.google.common.base.CharMatcher ;
2020-05-03 07:55:09 +00:00
import com.google.common.io.BaseEncoding ;
2017-06-18 14:35:30 +00:00
import org.whispersystems.libsignal.IdentityKey ;
2020-05-03 07:55:09 +00:00
import org.whispersystems.libsignal.InvalidKeyException ;
2017-06-18 14:35:30 +00:00
import org.whispersystems.libsignal.ecc.Curve ;
import org.whispersystems.libsignal.ecc.ECPublicKey ;
import org.whispersystems.libsignal.state.PreKeyBundle ;
2015-06-25 14:58:24 +00:00
2015-10-16 21:48:42 +00:00
import java.io.ByteArrayInputStream ;
import java.security.cert.CertificateException ;
import java.security.cert.CertificateFactory ;
import java.security.cert.X509Certificate ;
2014-12-21 20:43:58 +00:00
import java.util.ArrayList ;
import java.util.Collection ;
2015-06-25 14:58:24 +00:00
import java.util.HashMap ;
2015-06-29 12:18:11 +00:00
import java.util.HashSet ;
2015-06-25 14:58:24 +00:00
import java.util.List ;
import java.util.Map ;
2015-06-29 12:18:11 +00:00
import java.util.Set ;
2014-12-21 20:43:58 +00:00
2014-11-17 19:45:00 +00:00
import eu.siacs.conversations.Config ;
2015-07-08 15:44:24 +00:00
import eu.siacs.conversations.crypto.axolotl.AxolotlService ;
2014-10-22 16:38:44 +00:00
import eu.siacs.conversations.entities.Account ;
import eu.siacs.conversations.entities.Contact ;
2019-11-02 08:43:37 +00:00
import eu.siacs.conversations.entities.Room ;
2014-10-22 16:38:44 +00:00
import eu.siacs.conversations.services.XmppConnectionService ;
import eu.siacs.conversations.xml.Element ;
2020-05-03 07:55:09 +00:00
import eu.siacs.conversations.xml.Namespace ;
2018-04-28 14:26:40 +00:00
import eu.siacs.conversations.xmpp.InvalidJid ;
2021-01-23 08:25:34 +00:00
import eu.siacs.conversations.xmpp.Jid ;
2014-10-22 16:38:44 +00:00
import eu.siacs.conversations.xmpp.OnIqPacketReceived ;
2014-12-21 20:43:58 +00:00
import eu.siacs.conversations.xmpp.OnUpdateBlocklist ;
2019-11-02 08:43:37 +00:00
import eu.siacs.conversations.xmpp.forms.Data ;
2014-10-22 16:38:44 +00:00
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
public class IqParser extends AbstractParser implements OnIqPacketReceived {
2019-06-24 16:16:03 +00:00
public IqParser ( final XmppConnectionService service ) {
super ( service ) ;
}
2014-10-22 16:38:44 +00:00
2020-05-03 07:55:09 +00:00
public static List < Jid > items ( IqPacket packet ) {
ArrayList < Jid > items = new ArrayList < > ( ) ;
final Element query = packet . findChild ( " query " , Namespace . DISCO_ITEMS ) ;
if ( query = = null ) {
return items ;
}
for ( Element child : query . getChildren ( ) ) {
if ( " item " . equals ( child . getName ( ) ) ) {
Jid jid = child . getAttributeAsJid ( " jid " ) ;
if ( jid ! = null ) {
items . add ( jid ) ;
}
}
}
return items ;
}
public static Room parseRoom ( IqPacket packet ) {
final Element query = packet . findChild ( " query " , Namespace . DISCO_INFO ) ;
if ( query = = null ) {
return null ;
}
final Element x = query . findChild ( " x " ) ;
if ( x = = null ) {
return null ;
}
final Element identity = query . findChild ( " identity " ) ;
Data data = Data . parse ( x ) ;
2020-11-09 11:26:13 +00:00
String address = packet . getFrom ( ) . toString ( ) ;
2020-05-03 07:55:09 +00:00
String name = identity = = null ? null : identity . getAttribute ( " name " ) ;
String roomName = data . getValue ( " muc#roomconfig_roomname " ) ;
String description = data . getValue ( " muc#roominfo_description " ) ;
String language = data . getValue ( " muc#roominfo_lang " ) ;
String occupants = data . getValue ( " muc#roominfo_occupants " ) ;
int nusers ;
try {
nusers = occupants = = null ? 0 : Integer . parseInt ( occupants ) ;
} catch ( NumberFormatException e ) {
nusers = 0 ;
}
return new Room (
address ,
TextUtils . isEmpty ( roomName ) ? name : roomName ,
description ,
language ,
nusers
) ;
}
2019-06-24 16:16:03 +00:00
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 ) ;
}
2014-10-22 16:38:44 +00:00
2019-06-24 16:16:03 +00:00
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 ) ;
}
2014-10-22 16:38:44 +00:00
2019-06-24 16:16:03 +00:00
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 " ) ;
}
2015-06-25 14:58:24 +00:00
2019-06-24 16:16:03 +00:00
@NonNull
public Set < Integer > deviceIds ( final Element item ) {
Set < Integer > 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 <device> node in PEP ( " + e . getMessage ( ) + " ): " + device . toString ( ) + " , skipping... " ) ;
}
}
}
}
return deviceIds ;
}
2015-06-25 14:58:24 +00:00
2020-05-03 07:55:09 +00:00
private Integer signedPreKeyId ( final Element bundle ) {
2019-06-24 16:16:03 +00:00
final Element signedPreKeyPublic = bundle . findChild ( " signedPreKeyPublic " ) ;
if ( signedPreKeyPublic = = null ) {
return null ;
}
try {
return Integer . valueOf ( signedPreKeyPublic . getAttribute ( " signedPreKeyId " ) ) ;
} catch ( NumberFormatException e ) {
return null ;
}
}
2015-06-25 14:58:24 +00:00
2020-05-03 07:55:09 +00:00
private ECPublicKey signedPreKeyPublic ( final Element bundle ) {
2019-06-24 16:16:03 +00:00
ECPublicKey publicKey = null ;
2020-05-03 07:55:09 +00:00
final String signedPreKeyPublic = bundle . findChildContent ( " signedPreKeyPublic " ) ;
2019-06-24 16:16:03 +00:00
if ( signedPreKeyPublic = = null ) {
return null ;
}
try {
2020-05-08 17:33:49 +00:00
publicKey = Curve . decodePoint ( base64decode ( signedPreKeyPublic ) , 0 ) ;
2020-05-03 07:55:09 +00:00
} catch ( final IllegalArgumentException | InvalidKeyException e ) {
2019-06-24 16:16:03 +00:00
Log . e ( Config . LOGTAG , AxolotlService . LOGPREFIX + " : " + " Invalid signedPreKeyPublic in PEP: " + e . getMessage ( ) ) ;
}
return publicKey ;
}
2015-06-25 14:58:24 +00:00
2020-05-03 07:55:09 +00:00
private byte [ ] signedPreKeySignature ( final Element bundle ) {
final String signedPreKeySignature = bundle . findChildContent ( " signedPreKeySignature " ) ;
2019-06-24 16:16:03 +00:00
if ( signedPreKeySignature = = null ) {
return null ;
}
try {
2020-05-08 17:33:49 +00:00
return base64decode ( signedPreKeySignature ) ;
2020-05-03 07:55:09 +00:00
} catch ( final IllegalArgumentException e ) {
2019-06-24 16:16:03 +00:00
Log . e ( Config . LOGTAG , AxolotlService . LOGPREFIX + " : Invalid base64 in signedPreKeySignature " ) ;
return null ;
}
}
2015-06-25 14:58:24 +00:00
2020-05-03 07:55:09 +00:00
private IdentityKey identityKey ( final Element bundle ) {
final String identityKey = bundle . findChildContent ( " identityKey " ) ;
if ( identityKey = = null ) {
2019-06-24 16:16:03 +00:00
return null ;
}
try {
2020-05-08 17:33:49 +00:00
return new IdentityKey ( base64decode ( identityKey ) , 0 ) ;
2020-05-03 07:55:09 +00:00
} catch ( final IllegalArgumentException | InvalidKeyException e ) {
2019-06-24 16:16:03 +00:00
Log . e ( Config . LOGTAG , AxolotlService . LOGPREFIX + " : " + " Invalid identityKey in PEP: " + e . getMessage ( ) ) ;
2020-05-03 07:55:09 +00:00
return null ;
2019-06-24 16:16:03 +00:00
}
}
2015-06-25 14:58:24 +00:00
2019-06-24 16:16:03 +00:00
public Map < Integer , ECPublicKey > preKeyPublics ( final IqPacket packet ) {
Map < Integer , ECPublicKey > preKeyRecords = new HashMap < > ( ) ;
Element item = getItem ( packet ) ;
if ( item = = null ) {
Log . d ( Config . LOGTAG , AxolotlService . LOGPREFIX + " : " + " Couldn't find <item> 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 <prekeys> 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 ;
}
2020-05-08 17:33:49 +00:00
final String preKey = preKeyPublicElement . getContent ( ) ;
if ( preKey = = null ) {
continue ;
}
2019-06-24 16:16:03 +00:00
Integer preKeyId = null ;
try {
preKeyId = Integer . valueOf ( preKeyPublicElement . getAttribute ( " preKeyId " ) ) ;
2020-05-08 17:33:49 +00:00
final ECPublicKey preKeyPublic = Curve . decodePoint ( base64decode ( preKey ) , 0 ) ;
2019-06-24 16:16:03 +00:00
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 ;
}
2015-06-25 14:58:24 +00:00
2020-05-08 17:33:49 +00:00
private static byte [ ] base64decode ( String input ) {
return BaseEncoding . base64 ( ) . decode ( CharMatcher . whitespace ( ) . removeFrom ( input ) ) ;
}
2019-06-24 16:16:03 +00:00
public Pair < X509Certificate [ ] , byte [ ] > 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 ;
2020-05-03 07:55:09 +00:00
String signature = verification ! = null ? verification . findChildContent ( " signature " ) : null ;
2019-06-24 16:16:03 +00:00
if ( chain ! = null & & signature ! = null ) {
List < Element > certElements = chain . getChildren ( ) ;
X509Certificate [ ] certificates = new X509Certificate [ certElements . size ( ) ] ;
try {
CertificateFactory certificateFactory = CertificateFactory . getInstance ( " X.509 " ) ;
int i = 0 ;
2020-05-03 07:55:09 +00:00
for ( final Element certElement : certElements ) {
final String cert = certElement . getContent ( ) ;
if ( cert = = null ) {
continue ;
}
certificates [ i ] = ( X509Certificate ) certificateFactory . generateCertificate ( new ByteArrayInputStream ( BaseEncoding . base64 ( ) . decode ( cert ) ) ) ;
2019-06-24 16:16:03 +00:00
+ + i ;
}
2020-05-03 07:55:09 +00:00
return new Pair < > ( certificates , BaseEncoding . base64 ( ) . decode ( signature ) ) ;
2019-06-24 16:16:03 +00:00
} catch ( CertificateException e ) {
return null ;
}
} else {
return null ;
}
}
2015-10-16 21:48:42 +00:00
2019-06-24 16:16:03 +00:00
public PreKeyBundle bundle ( final IqPacket bundle ) {
2020-05-03 07:55:09 +00:00
final Element bundleItem = getItem ( bundle ) ;
2019-06-24 16:16:03 +00:00
if ( bundleItem = = null ) {
return null ;
}
final Element bundleElement = bundleItem . findChild ( " bundle " ) ;
if ( bundleElement = = null ) {
return null ;
}
2020-05-03 07:55:09 +00:00
final ECPublicKey signedPreKeyPublic = signedPreKeyPublic ( bundleElement ) ;
final Integer signedPreKeyId = signedPreKeyId ( bundleElement ) ;
final byte [ ] signedPreKeySignature = signedPreKeySignature ( bundleElement ) ;
final IdentityKey identityKey = identityKey ( bundleElement ) ;
if ( signedPreKeyId = = null
| | signedPreKeyPublic = = null
| | identityKey = = null
| | signedPreKeySignature = = null
| | signedPreKeySignature . length = = 0 ) {
2019-06-24 16:16:03 +00:00
return null ;
}
return new PreKeyBundle ( 0 , 0 , 0 , null ,
signedPreKeyId , signedPreKeyPublic , signedPreKeySignature , identityKey ) ;
}
2015-06-25 14:58:24 +00:00
2019-06-24 16:16:03 +00:00
public List < PreKeyBundle > preKeys ( final IqPacket preKeys ) {
List < PreKeyBundle > bundles = new ArrayList < > ( ) ;
Map < Integer , ECPublicKey > 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 ) ) ;
}
}
2015-06-25 14:58:24 +00:00
2019-06-24 16:16:03 +00:00
return bundles ;
}
2015-06-25 14:58:24 +00:00
2019-06-24 16:16:03 +00:00
@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 < Element > 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 < Jid > 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 < Element > items = packet . findChild ( " unblock " , Namespace . BLOCKING ) . getChildren ( ) ;
if ( items . size ( ) = = 0 ) {
// No children to unblock == unblock all
account . getBlocklist ( ) . clear ( ) ;
} else {
final Collection < Jid > 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 ) ;
}
}
}
2014-10-22 16:38:44 +00:00
}