Added privacy list support and improved error handling from Francisco (SMACK-121, SMACK-31).

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@4603 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Matt Tucker 2006-07-19 19:24:00 +00:00 committed by matt
parent 1716c6ed22
commit 47abf627b7
20 changed files with 2099 additions and 81 deletions

View File

@ -5,6 +5,7 @@
<!-- Classes that will be loaded when Smack starts -->
<startupClasses>
<className>org.jivesoftware.smackx.ServiceDiscoveryManager</className>
<className>org.jivesoftware.smack.PrivacyListManager</className>
<className>org.jivesoftware.smackx.XHTMLManager</className>
<className>org.jivesoftware.smackx.muc.MultiUserChat</className>
<className>org.jivesoftware.smackx.filetransfer.FileTransferManager</className>

View File

@ -180,4 +180,11 @@
<className>org.jivesoftware.smackx.provider.IBBProviders$Data</className>
</extensionProvider>
<!-- Privacy -->
<iqProvider>
<elementName>query</elementName>
<namespace>jabber:iq:privacy</namespace>
<className>org.jivesoftware.smack.provider.PrivacyProvider</className>
</iqProvider>
</smackProviders>

View File

@ -25,12 +25,13 @@
<li><a href="providers.html">Provider Architecture</a>
<li><a href="properties.html">Packet Properties</a>
<li><a href="debugging.html">Debugging with Smack</a>
<li><a href="privacy.html">Privacy</a>
<p>
<li><a href="extensions/index.html">Smack Extensions Manual</a>
</ul>
<div class="footer">
Copyright &copy; Jive Software 2002-2005
Copyright &copy; Jive Software 2002-2006
</div>
</body>

167
documentation/privacy.html Normal file
View File

@ -0,0 +1,167 @@
<html>
<head>
<title>Smack: Privacy - Jive Software</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div class="header">
Privacy
</div>
<div class="nav">
&laquo; <a href="index.html">Table of Contents</a>
</div>
<p class="subheader">What is?</p>
<p>
<tt>Privacy</tt> is a method for users to block communications from particular other users. In XMPP this is done by managing one's privacy lists.<br />
Server-side privacy lists enable successful completion of the following use cases:
<ul>
<li>Retrieving one's privacy lists.
<li>Adding, removing, and editing one's privacy lists.
<li>Setting, changing, or declining active lists.
<li>Setting, changing, or declining the default list (i.e., the list that is active by default).
<li>Allowing or blocking messages based on JID, group, or subscription type (or globally).
<li>Allowing or blocking inbound presence notifications based on JID, group, or subscription type (or globally).
<li>Allowing or blocking outbound presence notifications based on JID, group, or subscription type (or globally).
<li>Allowing or blocking IQ stanzas based on JID, group, or subscription type (or globally).
<li>Allowing or blocking all communications based on JID, group, or subscription type (or globally).
</ul>
<p>
<p class="subheader">How can I use it?</p>
<p>
The API implementation releases three main public classes:
<ul>
<li><tt>PrivacyListManager</tt>: this is the main API class to retrieve and handle server privacy lists.
<li><tt>PrivacyList</tt>: witch represents one privacy list, with a name, a set of privacy items. For example, the list with visible or invisible.
<li><tt>PrivacyItem</tt>: block or allow one aspect of privacy. For example, to allow my friend to see my presence.
</ul>
<ol>
<li> Right from the start, a client MAY <b>get his/her privacy list</b> that is stored in the server:<br />
<div class="code">
<pre>
<font color="gray"><i>// Create a privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Retrieve server privacy lists</i></font>
PrivacyList[] lists = privacyManager.getPrivacyLists();
</pre>
</div>
Now the client is able to show every <tt>PrivacyItem</tt> of the server and also for every list if it is active, default or none of them. The client is a listener of privacy changes.<br />
<br />
<li> In order to <b>add a new list in the server</b>, the client MAY implement something like:
<div class="code">
<pre>
<font color="gray"><i>// Set the name of the list</i></font>
String listName = <font color="green">"newList"</font>;
<font color="gray"><i>// Create the list of <tt>PrivacyItem</tt> that will allow or deny some privacy aspect</i></font>
String user = <font color="green">"tybalt@example.com"</font>;
String groupName = <font color="green">"enemies"</font>;
ArrayList privacyItems = new ArrayList();
PrivacyItem item = new PrivacyItem(PrivacyRule.<font color="navy"><i>JID</i></font>, <font color="navy">true</font>, 1);
item.setValue(user);
privacyItems.add(item);
item = new PrivacyItem(PrivacyRule.<font color="navy"><i>SUBSCRIPTION</i></font>, <font color="navy">true</font>, 2);
item.setValue(PrivacyRule.<font color="navy"><i>SUBSCRIPTION_BOTH</i></font>);
privacyItems.add(item);
item = new PrivacyItem(PrivacyRule.<font color="navy"><i>GROUP</i></font>, <font color="navy">false</font>, 3);
item.setValue(groupName);
item.setFilterMessage(<font color="navy">true</font>);
privacyItems.add(item);
<font color="gray"><i>// Get the privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Create the new list.</i></font>
privacyManager.createPrivacyList(listName, Arrays.<i>asList</i>(privacyItems));
</pre>
</div>
<li> To <b>modify an existent list</b>, the client code MAY be like:
<div class="code">
<pre>
<font color="gray"><i>// Set the name of the list</i></font>
String listName = <font color="green">"existingList"</font>;
<font color="gray"><i>// Get the privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Sent the new list to the server.</i></font>
privacyManager.updatePrivacyList(listName, items);
</pre>
</div>
Notice <tt>items</tt> was defined at the example 2 and MUST contain all the elements in the list (not the "delta").
<li> In order to <b>delete an existing list</b>, the client MAY perform something like:
<div class="code">
<pre>
<font color="gray"><i>// Set the name of the list</i></font>
String listName = <font color="green">"existingList"</font>;
<font color="gray"><i>// Get the privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Remove the list.</i></font>
privacyManager.deletePrivacyList(listName);
</pre>
</div>
<li> In order to <b>decline the use of an active list</b>, the client MAY perform something like:
<div class="code">
<pre>
<font color="gray"><i>// Get the privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Decline the use of the active list.</i></font>
privacyManager.declineActiveList();
</pre>
</div>
<li> In order to <b>decline the use of a default list</b>, the client MAY perform something like:
<div class="code">
<pre>
<font color="gray"><i>// Get the privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Decline the use of the default list.</i></font>
privacyManager.declineDefaultList();
</pre>
</div>
</ol>
<p class="subheader">Listening for Privacy Changes</p>
<p>
In order to handle privacy changes, clients SHOULD listen manager's updates.
When a list is changed the manager notifies every added listener. Listeners MUST implement the <tt>PrivacyListListener</tt> interface.
Clients may need to react when a privacy list is modified. The <tt>PrivacyListManager</tt> lets you add listerners that will be notified when a list has been changed. Listeners should implement the <tt>PrivacyListListener</tt> interface.<br />
The most important notification is <tt>updatedPrivacyList</tt> that is performed when a privacy list changes its privacy items.<br />
The listener becomes notified after performing:
<div class="code">
<pre>
<font color="gray"><i>// Get the privacy manager for the current connection.</i></font>
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
<font color="gray"><i>// Add the listener (this) to get notified</i></font>
privacyManager.addListener(<font color="navy">this</font>);
</pre>
</div>
</p>
<p class="subheader">References</p>
<ul>
<li><a href="http://www.xmpp.org/specs/rfc3921.html#privacy">Blocking communication</a> from the RFC3921.
</ul>
</p>
<br clear="all" /><br><br>
<div class="footer">
Copyright &copy; Jive Software 2002-2006
</div>
</body>
</html>

View File

@ -607,7 +607,7 @@ class PacketReader {
iqPacket.setTo(from);
iqPacket.setFrom(to);
iqPacket.setType(IQ.Type.ERROR);
iqPacket.setError(new XMPPError(501, "feature-not-implemented"));
iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented));
connection.sendPacket(iqPacket);
return null;
}

View File

@ -0,0 +1,57 @@
package org.jivesoftware.smack;
import org.jivesoftware.smack.packet.PrivacyItem;
import java.util.List;
/**
* A Privacy List is a read only class used to represent a set of allowed or blocked communications.
* Basically it can:<ul>
*
* <li>Handle many {@link org.jivesoftware.smack.packet.PrivacyItem}.</li>
* <li>Answer if it is the default list.</li>
* <li>Answer if it is the active list.</li>
* </ul>
*
* {@link PrivacyItem Privacy Items} can handle different kind of blocking communications based on JID, group,
* subscription type or globally.
*
* @author Francisco Vives
*/
public class PrivacyList {
/** Holds if it is an active list or not **/
private boolean isActiveList;
/** Holds if it is an default list or not **/
private boolean isDefaultList;
/** Holds the list name used to print **/
private String listName;
/** Holds the list of {@see PrivacyItem} **/
private List items;
protected PrivacyList(boolean isActiveList, boolean isDefaultList,
String listName, List<PrivacyItem> privacyItems) {
super();
this.isActiveList = isActiveList;
this.isDefaultList = isDefaultList;
this.listName = listName;
this.items = privacyItems;
}
public boolean isActiveList() {
return isActiveList;
}
public boolean isDefaultList() {
return isDefaultList;
}
public List getItems() {
return items;
}
public String toString() {
return listName;
}
}

View File

@ -0,0 +1,30 @@
package org.jivesoftware.smack;
import java.util.List;
/**
* Interface to implement classes to listen for server events about privacy communication.
* Listeners are registered with the {@link PrivacyListManager}.
*
* @see {@link PrivacyListManager#addListener}
*
* @author Francisco Vives
*/
public interface PrivacyListListener {
/**
* Set or update a privacy list with PrivacyItem.
*
* @param listName the name of the new or updated privacy list.
* @param listItem the PrivacyItems that rules the list.
*/
public void setPrivacyList(String listName, List listItem);
/**
* A privacy list has been modified by another. It gets notified.
*
* @param listName the name of the updated privacy list.
*/
public void updatedPrivacyList(String listName);
}

View File

@ -0,0 +1,442 @@
package org.jivesoftware.smack;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.IQTypeFilter;
import org.jivesoftware.smack.filter.PacketExtensionFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Privacy;
import org.jivesoftware.smack.packet.PrivacyItem;
/**
* A PrivacyListManager is used by XMPP clients to block or allow communications from other
* users. Use the manager to: <ul>
* <li>Retrieve privacy lists.
* <li>Add, remove, and edit privacy lists.
* <li>Set, change, or decline active lists.
* <li>Set, change, or decline the default list (i.e., the list that is active by default).
* </ul>
* Privacy Items can handle different kind of permission communications based on JID, group,
* subscription type or globally (@see PrivacyItem).
*
* @author Francisco Vives
*/
public class PrivacyListManager {
// Keep the list of instances of this class.
private static Map instances = new Hashtable();
private XMPPConnection connection;
private List listeners = new ArrayList();
PacketFilter packetFilter = new AndFilter(new IQTypeFilter(IQ.Type.SET),
new PacketExtensionFilter("query", "jabber:iq:privacy"));
static {
// Create a new PrivacyListManager on every established connection. In the init()
// method of PrivacyListManager, we'll add a listener that will delete the
// instance when the connection is closed.
XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
public void connectionEstablished(XMPPConnection connection) {
new PrivacyListManager(connection);
}
});
}
/**
* Creates a new privacy manager to maintain the communication privacy. Note: no
* information is sent to or received from the server until you attempt to
* get or set the privacy communication.<p>
*
* @param connection the XMPP connection.
*/
private PrivacyListManager(XMPPConnection connection) {
this.connection = connection;
this.init();
}
/** Answer the connection userJID that owns the privacy.
* @return the userJID that owns the privacy
*/
private String getUser() {
return connection.getUser();
}
/**
* Initializes the packet listeners of the connection that will notify for any set privacy
* package.
*/
private void init() {
// Register the new instance and associate it with the connection
instances.put(connection, this);
// Add a listener to the connection that removes the registered instance when
// the connection is closed
connection.addConnectionListener(new ConnectionListener() {
public void connectionClosed() {
// Unregister this instance since the connection has been closed
instances.remove(connection);
}
public void connectionClosedOnError(Exception e) {
// Unregister this instance since the connection has been closed
instances.remove(connection);
}
});
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
if (packet == null || packet.getError() != null) {
return;
}
// The packet is correct.
Privacy privacy = (Privacy) packet;
// Prepare the information before starting the notification
int listNameSize = privacy.getItemLists().size();
Object[] listItemsPairs = privacy.getItemLists().entrySet().toArray();
// Notifies the event to the listeners.
synchronized (listeners) {
for (Iterator i = listeners.iterator(); i.hasNext();) {
PrivacyListListener listener = (PrivacyListListener) i.next();
// Notifies the created or updated privacy lists
for (int j = 0; j < listNameSize; j++) {
Map.Entry entry = (Map.Entry) listItemsPairs[j];
String listName = (String) entry.getKey();
List items = (List) entry.getValue();
if (items.isEmpty()) {
listener.updatedPrivacyList(listName);
} else {
listener.setPrivacyList(listName, items);
}
}
}
}
// Send a result package acknowledging the reception of a privacy package.
// Prepare the IQ packet to send
IQ iq = new IQ() {
public String getChildElementXML() {
return "";
}
};
iq.setType(IQ.Type.RESULT);
iq.setFrom(packet.getFrom());
iq.setPacketID(packet.getPacketID());
// Send create & join packet.
connection.sendPacket(iq);
}
}, packetFilter);
}
/**
* Returns the PrivacyListManager instance associated with a given XMPPConnection.
*
* @param connection the connection used to look for the proper PrivacyListManager.
* @return the PrivacyListManager associated with a given XMPPConnection.
*/
public static PrivacyListManager getInstanceFor(XMPPConnection connection) {
return (PrivacyListManager) instances.get(connection);
}
/**
* Send the {@link Privacy} packet to the server in order to know some privacy content and then
* waits for the answer.
*
* @param requestPrivacy is the {@link Privacy} packet configured properly whose XML
* will be sent to the server.
* @return a new {@link Privacy} with the data received from the server.
* @exception XMPPException if the request or the answer failed, it raises an exception.
*/
private Privacy getRequest(Privacy requestPrivacy) throws XMPPException {
// The request is a get iq type
requestPrivacy.setType(Privacy.Type.GET);
requestPrivacy.setFrom(this.getUser());
// Filter packets looking for an answer from the server.
PacketFilter responseFilter = new PacketIDFilter(requestPrivacy.getPacketID());
PacketCollector response = connection.createPacketCollector(responseFilter);
// Send create & join packet.
connection.sendPacket(requestPrivacy);
// Wait up to a certain number of seconds for a reply.
Privacy privacyAnswer =
(Privacy) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
response.cancel();
// Interprete the result and answer the privacy only if it is valid
if (privacyAnswer == null) {
throw new XMPPException("No response from server.");
}
else if (privacyAnswer.getError() != null) {
throw new XMPPException(privacyAnswer.getError());
}
return privacyAnswer;
}
/**
* Send the {@link Privacy} packet to the server in order to modify the server privacy and
* waits for the answer.
*
* @param requestPrivacy is the {@link Privacy} packet configured properly whose xml will be sent
* to the server.
* @return a new {@link Privacy} with the data received from the server.
* @exception XMPPException if the request or the answer failed, it raises an exception.
*/
private Packet setRequest(Privacy requestPrivacy) throws XMPPException {
// The request is a get iq type
requestPrivacy.setType(Privacy.Type.SET);
requestPrivacy.setFrom(this.getUser());
// Filter packets looking for an answer from the server.
PacketFilter responseFilter = new PacketIDFilter(requestPrivacy.getPacketID());
PacketCollector response = connection.createPacketCollector(responseFilter);
// Send create & join packet.
connection.sendPacket(requestPrivacy);
// Wait up to a certain number of seconds for a reply.
Packet privacyAnswer = response.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
response.cancel();
// Interprete the result and answer the privacy only if it is valid
if (privacyAnswer == null) {
throw new XMPPException("No response from server.");
} else if (privacyAnswer.getError() != null) {
throw new XMPPException(privacyAnswer.getError());
}
return privacyAnswer;
}
/**
* Answer a privacy containing the list structre without {@link PrivacyItem}.
*
* @return a Privacy with the list names.
*/
private Privacy getPrivacyWithListNames() throws XMPPException {
// The request of the list is an empty privacy message
Privacy request = new Privacy();
// Send the package to the server and get the answer
return getRequest(request);
}
/**
* Answer the active privacy list.
*
* @return the {@see PrivacyList} of the active list.
*/
public PrivacyList getActiveList() throws XMPPException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
String listName = privacyAnswer.getActiveName();
boolean isDefaultAndActive = privacyAnswer.getActiveName() != null
&& privacyAnswer.getDefaultName() != null
&& privacyAnswer.getActiveName().equals(
privacyAnswer.getDefaultName());
return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName));
}
/**
* Answer the default privacy list.
*
* @return the {@see PrivacyList} of the default list.
*/
public PrivacyList getDefaultList() throws XMPPException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
String listName = privacyAnswer.getDefaultName();
boolean isDefaultAndActive = privacyAnswer.getActiveName() != null
&& privacyAnswer.getDefaultName() != null
&& privacyAnswer.getActiveName().equals(
privacyAnswer.getDefaultName());
return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName));
}
/**
* Answer the privacy list items under listName with the allowed and blocked permissions.
*
* @param listName the name of the list to get the allowed and blocked permissions.
* @return a list of {@link PrivacyItem} under the list listName.
*/
private List getPrivacyListItems(String listName) throws XMPPException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setPrivacyList(listName, new ArrayList());
// Send the package to the server and get the answer
Privacy privacyAnswer = getRequest(request);
return privacyAnswer.getPrivacyList(listName);
}
/**
* Answer the privacy list items under listName with the allowed and blocked permissions.
*
* @param listName the name of the list to get the allowed and blocked permissions.
* @return a {@link PrivacyList} under the list listName.
*/
public PrivacyList getPrivacyList(String listName) throws XMPPException {
PrivacyList list = new PrivacyList(false, false, listName, getPrivacyListItems(listName));
return list;
}
/**
* Answer every privacy list with the allowed and blocked permissions.
*
* @return a List of {@link PrivacyList}.
*/
public PrivacyList[] getPrivacyLists() throws XMPPException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
Set names = privacyAnswer.getPrivacyListNames();
PrivacyList[] lists = new PrivacyList[names.size()];
String listName;
boolean isActiveList;
boolean isDefaultList;
int index=0;
for (Iterator iter = names.iterator(); iter.hasNext();) {
listName = (String) iter.next();
isActiveList = listName.equals(privacyAnswer.getActiveName());
isDefaultList = listName.equals(privacyAnswer.getDefaultName());
lists[index] = new PrivacyList(isActiveList, isDefaultList,
listName, getPrivacyListItems(listName));
index = index + 1;
}
return lists;
}
/**
* Set or change the active list to listName.
*
* @param listName the list name to set as the active one.
* @exception XMPPException if the request or the answer failed, it raises an exception.
*/
public void setActiveListName(String listName) throws XMPPException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setActiveName(listName);
// Send the package to the server
setRequest(request);
}
/**
* Client declines the use of active lists.
*/
public void declineActiveList() throws XMPPException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setDeclineActiveList(true);
// Send the package to the server
setRequest(request);
}
/**
* Set or change the default list to listName.
*
* @param listName the list name to set as the default one.
* @exception XMPPException if the request or the answer failed, it raises an exception.
*/
public void setDefaultListName(String listName) throws XMPPException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setDefaultName(listName);
// Send the package to the server
setRequest(request);
}
/**
* Client declines the use of default lists.
*/
public void declineDefaultList() throws XMPPException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setDeclineDefaultList(true);
// Send the package to the server
setRequest(request);
}
/**
* The client has created a new list. It send the new one to the server.
*
* @param listName the list that has changed its content.
* @param privacyItems a List with every {@link PrivacyItem} in the list.
*/
public void createPrivacyList(String listName, List privacyItems) throws XMPPException {
this.updatePrivacyList(listName, privacyItems);
}
/**
* The client has edited an existing list. It updates the server content with the resulting
* list of {@link PrivacyItem}. The {@link PrivacyItem} list MUST contain all elements in the
* list (not the "delta").
*
* @param listName the list that has changed its content.
* @param privacyItems a List with every {@link PrivacyItem} in the list.
*/
public void updatePrivacyList(String listName, List privacyItems) throws XMPPException {
// Build the privacy package to add or update the new list
Privacy request = new Privacy();
request.setPrivacyList(listName, privacyItems);
// Send the package to the server
setRequest(request);
}
/**
* Remove a privacy list.
*
* @param listName the list that has changed its content.
*/
public void deletePrivacyList(String listName) throws XMPPException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setPrivacyList(listName, new ArrayList());
// Send the package to the server
setRequest(request);
}
/**
* Adds a packet listener that will be notified of any new update in the user
* privacy communication.
*
* @param listener a packet listener.
*/
public void addListener(PrivacyListListener listener) {
// Keep track of the listener so that we can manually deliver extra
// messages to it later if needed.
synchronized (listeners) {
listeners.add(listener);
}
}
}

View File

@ -277,16 +277,16 @@ public class XMPPConnection {
}
}
catch (UnknownHostException uhe) {
throw new XMPPException(
"Could not connect to " + host + ":" + port + ".",
new XMPPError(504),
uhe);
String errorMessage = "Could not connect to " + host + ":" + port + ".";
throw new XMPPException(errorMessage, new XMPPError(
XMPPError.Condition.remote_server_timeout, errorMessage),
uhe);
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError connecting to " + host + ":" + port + ".",
new XMPPError(502),
ioe);
String errorMessage = "XMPPError connecting to " + host + ":"
+ port + ".";
throw new XMPPException(errorMessage, new XMPPError(
XMPPError.Condition.remote_server_error, errorMessage), ioe);
}
this.serviceName = config.getServiceName();
try {
@ -966,9 +966,10 @@ public class XMPPConnection {
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError establishing connection with server.",
new XMPPError(502),
ioe);
"XMPPError establishing connection with server.",
new XMPPError(XMPPError.Condition.remote_server_error,
"XMPPError establishing connection with server."),
ioe);
}
// If debugging is enabled, we open a window and write out all network traffic.

View File

@ -0,0 +1,315 @@
package org.jivesoftware.smack.packet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.Set;
/**
* A Privacy IQ Packet, is used by the {@see PrivacyListManager} and {@see PrivacyProvider} to allow
* and block communications from other users. It contains the appropriate structure to suit
* user-defined privacy lists. Different configured Privacy packages are used in the Server
* Manager communication in order to:
* <ul>
* <li>Retrieving one's privacy lists.
* <li>Adding, removing, and editing one's privacy lists.
* <li>Setting, changing, or declining active lists.
* <li>Setting, changing, or declining the default list (i.e., the list that is active by default).
* </ul>
* Privacy Items can handle different kind of blocking communications based on JID, group,
* subscription type or globally {@see PrivacyItem}
*
* @author Francisco Vives
*/
public class Privacy extends IQ {
/** declineActiveList is true when the user declines the use of the active list **/
private boolean declineActiveList=false;
/** activeName is the name associated with the active list set for the session **/
private String activeName;
/** declineDefaultList is true when the user declines the use of the default list **/
private boolean declineDefaultList=false;
/** defaultName is the name of the default list that applies to the user as a whole **/
private String defaultName;
/** itemLists holds the set of privacy items classified in lists. It is a map where the
* key is the name of the list and the value a collection with privacy items. **/
private Map itemLists = new HashMap();
/**
* Set or update a privacy list with {@link PrivacyItem}.
*
* @param listName the name of the new privacy list.
* @param listItem the {@link PrivacyItem} that rules the list.
* @return the privacy List.
*/
public List setPrivacyList(String listName, List listItem) {
// Add new list to the itemLists
this.getItemLists().put(listName, listItem);
return listItem;
}
/**
* Set the active list based on the default list.
*
* @return the active List.
*/
public List setActivePrivacyList() {
this.setActiveName(this.getDefaultName());
return (List) this.getItemLists().get(this.getActiveName());
}
/**
* Deletes an existing privacy list. If the privacy list being deleted was the default list
* then the user will end up with no default list. Therefore, the user will have to set a new
* default list.
*
* @param listName the name of the list being deleted.
*/
public void deletePrivacyList(String listName) {
// Remove the list from the cache
this.getItemLists().remove(listName);
// Check if deleted list was the default list
if (this.getDefaultName() != null && listName.equals(this.getDefaultName())) {
this.setDefaultName(null);
}
}
/**
* Returns the active privacy list or <tt>null</tt> if none was found.
*
* @return list with {@link PrivacyItem} or <tt>null</tt> if none was found.
*/
public List getActivePrivacyList() {
// Check if we have the default list
if (this.getActiveName() == null) {
return null;
} else {
return (List) this.getItemLists().get(this.getActiveName());
}
}
/**
* Returns the default privacy list or <tt>null</tt> if none was found.
*
* @return list with {@link PrivacyItem} or <tt>null</tt> if none was found.
*/
public List getDefaultPrivacyList() {
// Check if we have the default list
if (this.getDefaultName() == null) {
return null;
} else {
return (List) this.getItemLists().get(this.getDefaultName());
}
}
/**
* Returns a specific privacy list.
*
* @param listName the name of the list to get.
* @return a List with {@link PrivacyItem}
*/
public List getPrivacyList(String listName) {
return (List) this.getItemLists().get(listName);
}
/**
* Returns the privacy item in the specified order.
*
* @param order the order of the element.
* @return a List with {@link PrivacyItem}
*/
public PrivacyItem getItem(String listName, int order) {
Iterator values = getPrivacyList(listName).iterator();
PrivacyItem itemFound = null;
while (itemFound == null && values.hasNext()) {
PrivacyItem element = (PrivacyItem) values.next();
if (element.getOrder() == order) {
itemFound = element;
}
}
return itemFound;
}
/**
* Sets a given privacy list as the new user default list.
*
* @param newDefault the new default privacy list.
* @return if the default list was changed.
*/
public boolean changeDefaultList(String newDefault) {
if (this.getItemLists().containsKey(newDefault)) {
this.setDefaultName(newDefault);
return true;
} else {
return false;
}
}
/**
* Remove the list.
*
* @param listName name of the list to remove.
*/
public void deleteList(String listName) {
this.getItemLists().remove(listName);
}
/**
* Returns the name associated with the active list set for the session. Communications
* will be verified against the active list.<p>
*
* @return the name of the active list.
*/
public String getActiveName() {
return activeName;
}
/**
* Sets the name associated with the active list set for the session. Communications
* will be verified against the active list.<p>
*
* @param activeName is the name of the active list.
*/
public void setActiveName(String activeName) {
this.activeName = activeName;
}
/**
* Returns the name of the default list that applies to the user as a whole. Default list is
* processed if there is no active list set for the target session/resource to which a stanza
* is addressed, or if there are no current sessions for the user.
*
* @return the name of the default list.
*/
public String getDefaultName() {
return defaultName;
}
/**
* Sets the name of the default list that applies to the user as a whole. Default list is
* processed if there is no active list set for the target session/resource to which a stanza
* is addressed, or if there are no current sessions for the user.
*
* If there is no default list set, then all Privacy Items are processed.
*
* @param defaultName is the name of the default list.
*/
public void setDefaultName(String defaultName) {
this.defaultName = defaultName;
}
/**
* Returns the collection of privacy list that the user holds. A Privacy List contains a set of
* rules that define if communication with the list owner is allowed or denied.
* Users may have zero, one or more privacy items.
*
* @return a map where the key is the name of the list and the value the
* collection of privacy items.
*/
public Map getItemLists() {
return itemLists;
}
/**
* Returns whether the receiver allows or declines the use of an active list.
*
* @return the decline status of the list.
*/
public boolean isDeclineActiveList() {
return declineActiveList;
}
/**
* Sets whether the receiver allows or declines the use of an active list.
*
* @param declineActiveList indicates if the receiver declines the use of an active list.
*/
public void setDeclineActiveList(boolean declineActiveList) {
this.declineActiveList = declineActiveList;
}
/**
* Returns whether the receiver allows or declines the use of a default list.
*
* @return the decline status of the list.
*/
public boolean isDeclineDefaultList() {
return declineDefaultList;
}
/**
* Sets whether the receiver allows or declines the use of a default list.
*
* @param declineActiveList indicates if the receiver declines the use of a default list.
*/
public void setDeclineDefaultList(boolean declineDefaultList) {
this.declineDefaultList = declineDefaultList;
}
/**
* Returns all the list names the user has defined to group restrictions.
*
* @return a Set with Strings containing every list names.
*/
public Set getPrivacyListNames() {
return this.itemLists.keySet();
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:privacy\">");
// Add the active tag
if (this.isDeclineActiveList()) {
buf.append("<active/>");
} else {
if (this.getActiveName() != null) {
buf.append("<active name=\"").append(this.getActiveName()).append("\"/>");
}
}
// Add the default tag
if (this.isDeclineDefaultList()) {
buf.append("<default/>");
} else {
if (this.getDefaultName() != null) {
buf.append("<default name=\"").append(this.getDefaultName()).append("\"/>");
}
}
// Add the list with their privacy items
int listNameSize = this.getItemLists().size();
Iterator listItemsPairs = this.getItemLists().entrySet().iterator();
for (int i = 0; i < listNameSize; i++) {
Map.Entry entry = (Map.Entry) listItemsPairs.next();
String listName = (String) entry.getKey();
List items = (List) entry.getValue();
// Begin the list tag
if (items.isEmpty()) {
buf.append("<list name=\"").append(listName).append("\"/>");
} else {
buf.append("<list name=\"").append(listName).append("\">");
}
for (Iterator itemIterator = items.iterator(); itemIterator.hasNext();) {
PrivacyItem item = (PrivacyItem) itemIterator.next();
// Append the item xml representation
buf.append(item.toXML());
}
// Close the list tag
if (!items.isEmpty()) {
buf.append("</list>");
}
}
// Add packet extensions, if any are defined.
buf.append(getExtensionsXML());
buf.append("</query>");
String generatedXML = buf.toString();
return generatedXML;
}
}

View File

@ -0,0 +1,454 @@
package org.jivesoftware.smack.packet;
/**
* A privacy item acts a rule that when matched defines if a packet should be blocked or not.
*
* Privacy Items can handle different kind of blocking communications based on JID, group,
* subscription type or globally by:<ul>
* <li>Allowing or blocking messages.
* <li>Allowing or blocking inbound presence notifications.
* <li>Allowing or blocking outbound presence notifications.
* <li>Allowing or blocking IQ stanzas.
* <li>Allowing or blocking all communications.
* </ul>
* @author Francisco Vives
*/
public class PrivacyItem {
/** allow is the action associated with the item, it can allow or deny the communication. */
private boolean allow;
/** order is a non-negative integer that is unique among all items in the list. */
private int order;
/** rule hold the kind of communication ([jid|group|subscription]) it will allow or block and
* identifier to apply the action.
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none". */
private PrivacyRule rule;
/** blocks incoming IQ stanzas. */
private boolean filterIQ = false;
/** filterMessage blocks incoming message stanzas. */
private boolean filterMessage = false;
/** blocks incoming presence notifications. */
private boolean filterPresence_in = false;
/** blocks outgoing presence notifications. */
private boolean filterPresence_out = false;
/**
* Creates a new privacy item.
*
* @param type the type.
*/
public PrivacyItem(String type, boolean allow, int order) {
this.setRule(PrivacyRule.fromString(type));
this.setAllow(allow);
this.setOrder(order);
}
/**
* Returns the action associated with the item, it MUST be filled and will allow or deny
* the communication.
*
* @return the allow communication status.
*/
public boolean isAllow() {
return allow;
}
/**
* Sets the action associated with the item, it can allow or deny the communication.
*
* @param allow indicates if the receiver allow or deny the communication.
*/
private void setAllow(boolean allow) {
this.allow = allow;
}
/**
* Returns whether the receiver allow or deny incoming IQ stanzas or not.
*
* @return the iq filtering status.
*/
public boolean isFilterIQ() {
return filterIQ;
}
/**
* Sets whether the receiver allows or denies incoming IQ stanzas or not.
*
* @param filterIQ indicates if the receiver allows or denies incoming IQ stanzas.
*/
public void setFilterIQ(boolean filterIQ) {
this.filterIQ = filterIQ;
}
/**
* Returns whether the receiver allows or denies incoming messages or not.
*
* @return the message filtering status.
*/
public boolean isFilterMessage() {
return filterMessage;
}
/**
* Sets wheather the receiver allows or denies incoming messages or not.
*
* @param filterMessage indicates if the receiver allows or denies incoming messages or not.
*/
public void setFilterMessage(boolean filterMessage) {
this.filterMessage = filterMessage;
}
/**
* Returns whether the receiver allows or denies incoming presence or not.
*
* @return the iq filtering incoming presence status.
*/
public boolean isFilterPresence_in() {
return filterPresence_in;
}
/**
* Sets whether the receiver allows or denies incoming presence or not.
*
* @param filterPresence_in indicates if the receiver allows or denies filtering incoming presence.
*/
public void setFilterPresence_in(boolean filterPresence_in) {
this.filterPresence_in = filterPresence_in;
}
/**
* Returns whether the receiver allows or denies incoming presence or not.
*
* @return the iq filtering incoming presence status.
*/
public boolean isFilterPresence_out() {
return filterPresence_out;
}
/**
* Sets whether the receiver allows or denies outgoing presence or not.
*
* @param filterPresence_out indicates if the receiver allows or denies filtering outgoing presence
*/
public void setFilterPresence_out(boolean filterPresence_out) {
this.filterPresence_out = filterPresence_out;
}
/**
* Returns the order where the receiver is processed. List items are processed in
* ascending order.
*
* The order MUST be filled and its value MUST be a non-negative integer
* that is unique among all items in the list.
*
* @return the order number.
*/
public int getOrder() {
return order;
}
/**
* Sets the order where the receiver is processed.
*
* The order MUST be filled and its value MUST be a non-negative integer
* that is unique among all items in the list.
*
* @param order indicates the order in the list.
*/
private void setOrder(int order) {
this.order = order;
}
/**
* Sets the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @param value is the identifier to apply the action.
*/
public void setValue(String value) {
if (!(this.getRule() == null && value == null)) {
this.getRule().setValue(value);
}
}
/**
* Returns the type hold the kind of communication it will allow or block.
* It MUST be filled with one of these values: jid, group or subscription.
*
* @return the type of communication it represent.
*/
public String getType() {
if (this.getRule() == null) {
return null;
} else {
return this.getRule().getType();
}
}
/**
* Returns the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @return the identifier to apply the action.
*/
public String getValue() {
if (this.getRule() == null) {
return null;
} else {
return this.getRule().getValue();
}
}
/**
* Returns whether the receiver allows or denies every kind of communication.
*
* When filterIQ, filterMessage, filterPresence_in and filterPresence_out are not set
* the receiver will block all communications.
*
* @return the all communications status.
*/
public boolean isFilterEverything() {
return !(this.isFilterIQ() || this.isFilterMessage() || this.isFilterPresence_in()
|| this.isFilterPresence_out());
}
private PrivacyRule getRule() {
return rule;
}
private void setRule(PrivacyRule rule) {
this.rule = rule;
}
/**
* Answer an xml representation of the receiver according to the RFC 3921.
*
* @return the text xml representation.
*/
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item");
if (this.isAllow()) {
buf.append(" action=\"allow\"");
} else {
buf.append(" action=\"deny\"");
}
buf.append(" order=\"").append(getOrder()).append("\"");
if (getType() != null) {
buf.append(" type=\"").append(getType()).append("\"");
}
if (getValue() != null) {
buf.append(" value=\"").append(getValue()).append("\"");
}
if (isFilterEverything()) {
buf.append("/>");
} else {
buf.append(">");
if (this.isFilterIQ()) {
buf.append("<iq/>");
}
if (this.isFilterMessage()) {
buf.append("<message/>");
}
if (this.isFilterPresence_in()) {
buf.append("<presence-in/>");
}
if (this.isFilterPresence_out()) {
buf.append("<presence-out/>");
}
buf.append("</item>");
}
return buf.toString();
}
/**
* Privacy Rule represents the kind of action to apply.
* It holds the kind of communication ([jid|group|subscription]) it will allow or block and
* identifier to apply the action.
*/
public static class PrivacyRule {
/**
* Type defines if the rule is based on JIDs, roster groups or presence subscription types.
* Available values are: [jid|group|subscription]
*/
private String type;
/**
* The value hold the element identifier to apply the action.
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*/
private String value;
/**
* JID being analyzed should have a resource match, domain match or bare JID match.
*/
public static final String GROUP = "group";
/**
* JID being analyzed should belong to a roster group of the list's owner.
*/
public static final String JID = "jid";
/**
* JID being analyzed should belong to a contact present in the owner's roster with
* the specified subscription status.
*/
public static final String SUBSCRIPTION = "subscription";
/**
* If the type is "subscription", then the 'value' attribute MUST be one of "both",
* "to", "from", or "none"
*/
public static final String SUBSCRIPTION_BOTH = "subscription";
public static final String SUBSCRIPTION_TO = "to";
public static final String SUBSCRIPTION_FROM = "from";
public static final String SUBSCRIPTION_NONE = "none";
/**
* Returns the type constant associated with the String value.
*/
protected static PrivacyRule fromString(String value) {
String type = null;
if (value == null) {
return null;
}
if (SUBSCRIPTION.equalsIgnoreCase(value)) {
type = SUBSCRIPTION;
}
else if (GROUP.equalsIgnoreCase(value)) {
type = GROUP;
}
else if (JID.equalsIgnoreCase(value)) {
type = JID;
}
// Default to available.
else {
return null;
}
PrivacyRule rule = new PrivacyRule();
rule.setType(type);
return rule;
}
/**
* Returns the type hold the kind of communication it will allow or block.
* It MUST be filled with one of these values: jid, group or subscription.
*
* @return the type of communication it represent.
*/
public String getType() {
return type;
}
/**
* Sets the action associated with the item, it can allow or deny the communication.
*
* @param allow indicates if the receiver allows or denies the communication.
*/
private void setType(String type) {
this.type = type;
}
/**
* Returns the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @return the identifier to apply the action.
*/
public String getValue() {
return value;
}
/**
* Sets the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @param value is the identifier to apply the action.
*/
protected void setValue(String value) {
if (this.isSuscription()) {
setSuscriptionValue(value);
} else {
this.value = value;
}
}
/**
* Sets the element identifier to apply the action.
*
* The 'value' attribute MUST be one of "both", "to", "from", or "none".
*
* @param value is the identifier to apply the action.
*/
private void setSuscriptionValue(String value) {
String setValue = null;
if (value == null) {
setValue = null;
}
if (SUBSCRIPTION_BOTH.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_BOTH;
}
else if (SUBSCRIPTION_TO.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_TO;
}
else if (SUBSCRIPTION_FROM.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_FROM;
}
else if (SUBSCRIPTION_NONE.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_NONE;
}
// Default to available.
else {
setValue = null;
}
this.value = setValue;
}
/**
* Returns if the receiver represents a subscription rule.
*
* @return if the receiver represents a subscription rule.
*/
public boolean isSuscription () {
return this.getValue() == SUBSCRIPTION;
}
}
}

View File

@ -20,29 +20,43 @@
package org.jivesoftware.smack.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* Represents a XMPP error sub-packet. Typically, a server responds to a request that has
* problems by sending the packet back and including an error packet. Each error has a code
* as well as as an optional text explanation. Typical error codes are as follows:<p>
* problems by sending the packet back and including an error packet. Each error has a code, type,
* error condition as well as as an optional text explanation. Typical errors are:<p>
*
* <table border=1>
* <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
* <tr><td> 302 </td><td> Redirect </td></tr>
* <tr><td> 400 </td><td> Bad Request </td></tr>
* <tr><td> 401 </td><td> Unauthorized </td></tr>
* <tr><td> 402 </td><td> Payment Required </td></tr>
* <tr><td> 403 </td><td> Forbidden </td></tr>
* <tr><td> 404 </td><td> Not Found </td></tr>
* <tr><td> 405 </td><td> Not Allowed </td></tr>
* <tr><td> 406 </td><td> Not Acceptable </td></tr>
* <tr><td> 407 </td><td> Registration Required </td></tr>
* <tr><td> 408 </td><td> Request Timeout </td></tr>
* <tr><td> 409 </td><td> Conflict </td></tr>
* <tr><td> 500 </td><td> Internal Server XMPPError </td></tr>
* <tr><td> 501 </td><td> Not Implemented </td></tr>
* <tr><td> 502 </td><td> Remote Server Error </td></tr>
* <tr><td> 503 </td><td> Service Unavailable </td></tr>
* <tr><td> 504 </td><td> Remote Server Timeout </td></tr>
* <hr><td><b>Code</b></td><td><b>XMPP Error</b></td><td><b>Type</b></td></hr>
* <tr><td>500</td><td>interna-server-error</td><td>WAIT</td></tr>
* <tr><td>403</td><td>forbidden</td><td>AUTH</td></tr>
* <tr><td>400</td<td>bad-request</td><td>MODIFY</td>></tr>
* <tr><td>404</td><td>item-not-found</td><td>CANCEL</td></tr>
* <tr><td>409</td><td>conflict</td><td>CANCEL</td></tr>
* <tr><td>501</td><td>feature-not-implemented</td><td>CANCEL</td></tr>
* <tr><td>302</td><td>gone</td><td>MODIFY</td></tr>
* <tr><td>400</td><td>jid-malformed</td><td>MODIFY</td></tr>
* <tr><td>406</td><td>no-acceptable</td><td> MODIFY</td></tr>
* <tr><td>405</td><td>not-allowed</td><td>CANCEL</td></tr>
* <tr><td>401</td><td>not-authorized</td><td>AUTH</td></tr>
* <tr><td>402</td><td>payment-required</td><td>AUTH</td></tr>
* <tr><td>404</td><td>recipient-unavailable</td><td>WAIT</td></tr>
* <tr><td>302</td><td>redirect</td><td>MODIFY</td></tr>
* <tr><td>407</td><td>registration-required</td><td>AUTH</td></tr>
* <tr><td>404</td><td>remote-server-not-found</td><td>CANCEL</td></tr>
* <tr><td>504</td><td>remote-server-timeout</td><td>WAIT</td></tr>
* <tr><td>502</td><td>remote-server-error</td><td>CANCEL</td></tr>
* <tr><td>500</td><td>resource-constraint</td><td>WAIT</td></tr>
* <tr><td>503</td><td>service-unavailable</td><td>CANCEL</td></tr>
* <tr><td>407</td><td>subscription-required</td><td>AUTH</td></tr>
* <tr><td>500</td><td>undefined-condition</td><td>WAIT</td></tr>
* <tr><td>400</td><td>unexpected-condition</td><td>WAIT</td></tr>
* <tr><td>408</td><td>request-timeout</td><td>CANCEL</td></tr>
* </table>
*
* @author Matt Tucker
@ -50,29 +64,119 @@ package org.jivesoftware.smack.packet;
public class XMPPError {
private int code;
private Type type;
private String condition;
private String message;
private List applicationExtensions = null;
/**
* Creates a new error with the specified code and no message..
* Creates a new error with the specified condition infering the type and code.
* If the Condition is predefined, client code should be like:
* new XMPPError(XMPPError.Condition.remote_server_timeout);
* If the Condition is not predefined, invocations should be like
* new XMPPError(new XMPPError.Condition("my_own_error"));
*
* @param code the error code.
*/
public XMPPError(Condition condition) {
this.init(condition);
this.message = null;
}
/**
* Creates a new error with the specified condition and message infering the type and code.
* If the Condition is predefined, client code should be like:
* new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation");
* If the Condition is not predefined, invocations should be like
* new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation");
*
* @param code the error code.
* @param message a message describing the error.
*/
public XMPPError(Condition condition, String messageText) {
this.init(condition);
this.message = messageText;
}
/**
* Creates a new error with the specified code and no message.
*
* @param code the error code.
* @Deprecated new errors should be created using the constructor XMPPError(condition)
*/
public XMPPError(int code) {
this.code = code;
this.message = null;
}
/**
* Creates a new error with the specified code and message.
* deprecated
*
* @param code the error code.
* @param message a message describing the error.
* @Deprecated new errors should be created using the constructor XMPPError(condition, message)
*/
public XMPPError(int code, String message) {
this.code = code;
this.message = message;
}
/**
* Creates a new error with the specified code, type, condition and message.
* This constructor is used when the condition is not recognized automatically by XMPPError
* i.e. there is not a defined instance of ErrorCondition or it does not applies the default
* specification.
*
* @param code the error code.
* @param type the error type.
* @param condition the error condition.
* @param message a message describing the error.
*/
public XMPPError(int code, Type type, String condition, String message, List extension) {
this.code = code;
this.type = type;
this.condition = condition;
this.message = message;
this.applicationExtensions = extension;
}
/**
* Initialize the error infering the type and code for the received condition.
*
* @param condition the error condition.
*/
private void init(Condition condition) {
// Look for the condition and its default code and type
ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition);
this.condition = condition.value;
if (defaultErrorSpecification != null) {
// If there is a default error specification for the received condition,
// it get configured with the infered type and code.
this.type = defaultErrorSpecification.getType();
this.code = defaultErrorSpecification.getCode();
}
}
/**
* Returns the error condition.
*
* @return the error condition.
*/
public String getCondition() {
return condition;
}
/**
* Returns the error type.
*
* @return the error type.
*/
public Type getType() {
return type;
}
/**
* Returns the error code.
*
@ -97,21 +201,299 @@ public class XMPPError {
* @return the error as XML.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<error code=\"").append(code).append("\">");
if (message != null) {
buf.append(message);
StringBuffer buf = new StringBuffer();
buf.append("<error code=\"").append(code).append("\"");
if (type != null) {
buf.append(" type=\"");
buf.append(type.value);
buf.append("\"");
}
buf.append(">");
if (condition != null) {
buf.append("<").append(condition);
buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>");
}
if (message != null) {
buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">");
buf.append(message);
buf.append("</text>");
}
for (Iterator extensions = this.getExtensions(); extensions.hasNext();) {
PacketExtension element = (PacketExtension) extensions.next();
buf.append(element.toXML());
}
buf.append("</error>");
return buf.toString();
}
public String toString() {
StringBuilder txt = new StringBuilder();
StringBuffer txt = new StringBuffer();
txt.append("(").append(code).append(")");
if (message != null) {
txt.append(" ").append(message);
}
return txt.toString();
}
/**
* Returns an Iterator for the error extensions attached to the xmppError.
* An application MAY provide application-specific error information by including a
* properly-namespaced child in the error element.
*
* @return an Iterator for the error extensions.
*/
public synchronized Iterator getExtensions() {
if (applicationExtensions == null) {
return Collections.EMPTY_LIST.iterator();
}
return Collections.unmodifiableList(new ArrayList(applicationExtensions)).iterator();
}
/**
* Returns the first patcket extension that matches the specified element name and
* namespace, or <tt>null</tt> if it doesn't exist.
*
* @param elementName the XML element name of the packet extension.
* @param namespace the XML element namespace of the packet extension.
* @return the extension, or <tt>null</tt> if it doesn't exist.
*/
public synchronized PacketExtension getExtension(String elementName, String namespace) {
if (applicationExtensions == null || elementName == null || namespace == null) {
return null;
}
for (Iterator i=applicationExtensions.iterator(); i.hasNext(); ) {
PacketExtension ext = (PacketExtension)i.next();
if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) {
return ext;
}
}
return null;
}
/**
* Adds a packet extension to the error.
*
* @param extension a packet extension.
*/
public synchronized void addExtension(PacketExtension extension) {
if (applicationExtensions == null) {
applicationExtensions = new ArrayList();
}
applicationExtensions.add(extension);
}
/**
* Set the packet extension to the error.
*
* @param extension a packet extension.
*/
public synchronized void setExtension(List extension) {
applicationExtensions = extension;
}
/**
* A class to represent the type of the Error. The types are:
*
* <ul>
* <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary)
* <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable)
* <li>XMPPError.Type.MODIFY - retry after changing the data sent
* <li>XMPPError.Type.AUTH - retry after providing credentials
* <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning)
* </ul>
*/
public static class Type {
public static final Type WAIT = new Type("wait");
public static final Type CANCEL = new Type("cancel");
public static final Type MODIFY = new Type("modify");
public static final Type AUTH = new Type("auth");
public static final Type CONTINUE = new Type("continue");
/**
* Converts a String into the corresponding types. Valid String values
* that can be converted to types are: "wait", "cancel", "modify", "auth" or a user defined.
*
* @param type the String value to covert.
* @return the corresponding Type.
*/
public static Type fromString(String type) {
if (type == null) {
return null;
}
type = type.toLowerCase();
if (CANCEL.toString().equals(type)) {
return CANCEL;
}
else if (CONTINUE.toString().equals(type)) {
return CONTINUE;
}
else if (WAIT.toString().equals(type)) {
return WAIT;
}
else if (MODIFY.toString().equals(type)) {
return MODIFY;
}
else if (AUTH.toString().equals(type)) {
return AUTH;
}
else {
return null;
}
}
private String value;
private Type(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
/**
* A class to represent predefined error conditions.
*/
public static class Condition {
public static final Condition interna_server_error = new Condition("internal-server-error");
public static final Condition forbidden = new Condition("forbidden");
public static final Condition bad_request = new Condition("bad-request");
public static final Condition conflict = new Condition("conflict");
public static final Condition feature_not_implemented = new Condition("feature-not-implemented");
public static final Condition gone = new Condition("gone");
public static final Condition item_not_found = new Condition("item-not-found");
public static final Condition jid_malformed = new Condition("jid-malformed");
public static final Condition no_acceptable = new Condition("not-acceptable");
public static final Condition not_allowed = new Condition("not-allowed");
public static final Condition not_authorized = new Condition("not-authorized");
public static final Condition payment_required = new Condition("payment-required");
public static final Condition recipient_unavailable = new Condition("recipient-unavailable");
public static final Condition redirect = new Condition("redirect");
public static final Condition registration_required = new Condition("registration-required");
public static final Condition remote_server_error = new Condition("remote-server-error");
public static final Condition remote_server_not_found = new Condition("remote-server-not-found");
public static final Condition remote_server_timeout = new Condition("remote-server-timeout");
public static final Condition resource_constraint = new Condition("resource-constraint");
public static final Condition service_unavailable = new Condition("service-unavailable");
public static final Condition subscription_required = new Condition("subscription-required");
public static final Condition undefined_condition = new Condition("undefined-condition");
public static final Condition unexpected_condition = new Condition("unexpected-condition");
public static final Condition request_timeout = new Condition("request-timeout");
private String value;
public Condition(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
/**
* A class to represent the error specification used to infer common usage.
*/
private static class ErrorSpecification {
private int code;
private Type type;
private Condition condition;
private static HashMap instances = errorSpecifications();
private ErrorSpecification(Condition condition, Type type, int code) {
this.code = code;
this.type = type;
this.condition = condition;
}
private static HashMap errorSpecifications() {
HashMap instances = new HashMap(22);
instances.put(Condition.interna_server_error, new ErrorSpecification(
Condition.interna_server_error, Type.WAIT, 500));
instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden,
Type.AUTH, 403));
instances.put(Condition.bad_request, new XMPPError.ErrorSpecification(
Condition.bad_request, Type.MODIFY, 400));
instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification(
Condition.item_not_found, Type.CANCEL, 404));
instances.put(Condition.conflict, new XMPPError.ErrorSpecification(
Condition.conflict, Type.CANCEL, 409));
instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification(
Condition.feature_not_implemented, Type.CANCEL, 501));
instances.put(Condition.gone, new XMPPError.ErrorSpecification(
Condition.gone, Type.MODIFY, 302));
instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification(
Condition.jid_malformed, Type.MODIFY, 400));
instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification(
Condition.no_acceptable, Type.MODIFY, 406));
instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification(
Condition.not_allowed, Type.CANCEL, 405));
instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification(
Condition.not_authorized, Type.AUTH, 401));
instances.put(Condition.payment_required, new XMPPError.ErrorSpecification(
Condition.payment_required, Type.AUTH, 402));
instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification(
Condition.recipient_unavailable, Type.WAIT, 404));
instances.put(Condition.redirect, new XMPPError.ErrorSpecification(
Condition.redirect, Type.MODIFY, 302));
instances.put(Condition.registration_required, new XMPPError.ErrorSpecification(
Condition.registration_required, Type.AUTH, 407));
instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification(
Condition.remote_server_not_found, Type.CANCEL, 404));
instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification(
Condition.remote_server_timeout, Type.WAIT, 504));
instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification(
Condition.remote_server_error, Type.CANCEL, 502));
instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification(
Condition.resource_constraint, Type.WAIT, 500));
instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification(
Condition.service_unavailable, Type.CANCEL, 503));
instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification(
Condition.subscription_required, Type.AUTH, 407));
instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification(
Condition.undefined_condition, Type.WAIT, 500));
instances.put(Condition.unexpected_condition, new XMPPError.ErrorSpecification(
Condition.unexpected_condition, Type.WAIT, 400));
instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification(
Condition.request_timeout, Type.CANCEL, 408));
return instances;
}
protected static ErrorSpecification specFor(Condition condition) {
return (ErrorSpecification) instances.get(condition);
}
/**
* Returns the error condition.
*
* @return the error condition.
*/
protected Condition getCondition() {
return condition;
}
/**
* Returns the error type.
*
* @return the error type.
*/
protected Type getType() {
return type;
}
/**
* Returns the error code.
*
* @return the error code.
*/
protected int getCode() {
return code;
}
}
}

View File

@ -0,0 +1,134 @@
package org.jivesoftware.smack.provider;
import java.util.ArrayList;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Privacy;
import org.jivesoftware.smack.packet.PrivacyItem;
import org.xmlpull.v1.XmlPullParser;
/**
* The PrivacyProvider parses {@link Privacy} packets. {@link Privacy}
* Parses the <tt>query</tt> sub-document and creates an instance of {@link Privacy}.
* For each <tt>item</tt> in the <tt>list</tt> element, it creates an instance
* of {@link PrivacyItem} and {@link PrivacyRule}.
*
* @author Francisco Vives
*/
public class PrivacyProvider implements IQProvider {
public PrivacyProvider() {
}
public IQ parseIQ(XmlPullParser parser) throws Exception {
Privacy privacy = new Privacy();
/* privacy.addExtension(PacketParserUtils.parsePacketExtension(parser
.getName(), parser.getNamespace(), parser)); */
privacy.addExtension(new DefaultPacketExtension(parser.getName(), parser.getNamespace()));
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("active")) {
String activeName = parser.getAttributeValue("", "name");
if (activeName == null) {
privacy.setDeclineActiveList(true);
} else {
privacy.setActiveName(activeName);
}
}
else if (parser.getName().equals("default")) {
String defaultName = parser.getAttributeValue("", "name");
if (defaultName == null) {
privacy.setDeclineDefaultList(true);
} else {
privacy.setDefaultName(defaultName);
}
}
else if (parser.getName().equals("list")) {
parseList(parser, privacy);
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("query")) {
done = true;
}
}
}
return privacy;
}
// Parse the list complex type
public void parseList(XmlPullParser parser, Privacy privacy) throws Exception {
boolean done = false;
String listName = parser.getAttributeValue("", "name");
ArrayList items = new ArrayList();
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("item")) {
items.add(parseItem(parser));
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("list")) {
done = true;
}
}
}
privacy.setPrivacyList(listName, items);
}
// Parse the list complex type
public PrivacyItem parseItem(XmlPullParser parser) throws Exception {
boolean done = false;
// Retrieves the required attributes
String actionValue = parser.getAttributeValue("", "action");
String orderValue = parser.getAttributeValue("", "order");
String type = parser.getAttributeValue("", "type");
/*
* According the action value it sets the allow status. The fall-through action is assumed
* to be "allow"
*/
boolean allow = true;
if ("allow".equalsIgnoreCase(actionValue)) {
allow = true;
} else if ("deny".equalsIgnoreCase(actionValue)) {
allow = false;
}
// Set the order number
int order = Integer.parseInt(orderValue);
// Create the privacy item
PrivacyItem item = new PrivacyItem(type, allow, order);
item.setValue(parser.getAttributeValue("", "value"));
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("iq")) {
item.setFilterIQ(true);
}
if (parser.getName().equals("message")) {
item.setFilterMessage(true);
}
if (parser.getName().equals("presence-in")) {
item.setFilterPresence_in(true);
}
if (parser.getName().equals("presence-out")) {
item.setFilterPresence_out(true);
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("item")) {
done = true;
}
}
}
return item;
}
}

View File

@ -21,9 +21,7 @@
package org.jivesoftware.smack.util;
import java.beans.PropertyDescriptor;
import java.util.Map;
import java.util.Iterator;
import java.util.HashMap;
import java.util.*;
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
@ -31,7 +29,6 @@ import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Utility class that helps to parse packets. Any parsing packets method that must be shared
@ -69,7 +66,7 @@ public class PacketParserUtils {
String subject = null;
String body = null;
String thread = null;
Map properties = null;
Map<String, Object> properties = null;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
@ -115,8 +112,7 @@ public class PacketParserUtils {
message.setThread(thread);
// Set packet properties.
if (properties != null) {
for (Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
String name = (String)i.next();
for (String name : properties.keySet()) {
message.setProperty(name, properties.get(name));
}
}
@ -186,10 +182,9 @@ public class PacketParserUtils {
else if (elementName.equals("properties") &&
namespace.equals(PROPERTIES_NAMESPACE))
{
Map properties = parseProperties(parser);
Map<String,Object> properties = parseProperties(parser);
// Set packet properties.
for (Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
String name = (String)i.next();
for (String name : properties.keySet()) {
presence.setProperty(name, properties.get(name));
}
}
@ -219,8 +214,8 @@ public class PacketParserUtils {
* @return a map of the properties.
* @throws Exception if an error occurs while parsing the properties.
*/
public static Map parseProperties(XmlPullParser parser) throws Exception {
Map properties = new HashMap();
public static Map<String, Object> parseProperties(XmlPullParser parser) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
while (true) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
@ -257,7 +252,7 @@ public class PacketParserUtils {
value = new Double(valueText);
}
else if ("boolean".equals(type)) {
value = new Boolean(valueText);
value = Boolean.valueOf(valueText);
}
else if ("string".equals(type)) {
value = valueText;
@ -297,25 +292,52 @@ public class PacketParserUtils {
* @throws Exception if an exception occurs while parsing the packet.
*/
public static XMPPError parseError(XmlPullParser parser) throws Exception {
String errorCode = "-1";
final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
String errorCode = "-1";
String type = null;
String message = null;
String condition = null;
List<PacketExtension> extensions = new ArrayList<PacketExtension>();
// Parse the error header
for (int i=0; i<parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("code")) {
errorCode = parser.getAttributeValue("", "code");
}
}
// Get the error text in a safe way since we are not sure about the error message format
try {
message = parser.nextText();
}
catch (XmlPullParserException ex) {}
while (true) {
if (parser.getEventType() == XmlPullParser.END_TAG && parser.getName().equals("error")) {
break;
if (parser.getAttributeName(i).equals("type")) {
type = parser.getAttributeValue("", "type");
}
parser.next();
}
return new XMPPError(Integer.parseInt(errorCode), message);
boolean done = false;
// Parse the text and condition tags
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("text")) {
parser.next();
message = parser.getText();
parser.next();
}
else {
// Condition tag, it can be xmpp error or an application defined error.
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (errorNamespace.equals(namespace)) {
condition = elementName;
}
else {
extensions.add(parsePacketExtension(elementName, namespace, parser));
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("error")) {
done = true;
}
}
}
return new XMPPError(Integer.parseInt(errorCode), XMPPError.Type
.fromString(type), condition, message, extensions);
}
/**
@ -387,7 +409,7 @@ public class PacketParserUtils {
// String to the correct object type.
Object value = decode(propertyType, stringValue);
// Set the value of the bean.
descriptor.getWriteMethod().invoke(object, new Object[] { value });
descriptor.getWriteMethod().invoke(object, value);
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(elementName)) {
@ -430,5 +452,4 @@ public class PacketParserUtils {
}
return null;
}
}
}

View File

@ -184,7 +184,7 @@ public class ServiceDiscoveryManager {
// Return <item-not-found/> error since client doesn't contain
// the specified node
response.setType(IQ.Type.ERROR);
response.setError(new XMPPError(404, "item-not-found"));
response.setError(new XMPPError(XMPPError.Condition.item_not_found));
}
connection.sendPacket(response);
}
@ -236,7 +236,7 @@ public class ServiceDiscoveryManager {
else {
// Return <item-not-found/> error since specified node was not found
response.setType(IQ.Type.ERROR);
response.setError(new XMPPError(404, "item-not-found"));
response.setError(new XMPPError(XMPPError.Condition.item_not_found));
}
}
connection.sendPacket(response);

View File

@ -172,7 +172,7 @@ public class FileTransferManager {
IQ rejection = FileTransferNegotiator.createIQ(
initiation.getPacketID(), initiation.getFrom(), initiation
.getTo(), IQ.Type.ERROR);
rejection.setError(new XMPPError(403));
rejection.setError(new XMPPError(XMPPError.Condition.forbidden));
connection.sendPacket(rejection);
}
}

View File

@ -223,12 +223,13 @@ public class FileTransferNegotiator {
.getFeatureNegotiationForm());
if (streamMethodField == null) {
XMPPError error = new XMPPError(400);
String errorMessage = "No stream methods contained in packet.";
XMPPError error = new XMPPError(XMPPError.Condition.bad_request, errorMessage);
IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(),
IQ.Type.ERROR);
iqPacket.setError(error);
connection.sendPacket(iqPacket);
throw new XMPPException("No stream methods contained in packet.", error);
throw new XMPPException(errorMessage, error);
}
// select the appropriate protocol
@ -278,8 +279,9 @@ public class FileTransferNegotiator {
}
if (!isByteStream && !isIBB) {
XMPPError error = new XMPPError(400);
throw new XMPPException("No acceptable transfer mechanism", error);
XMPPError error = new XMPPError(XMPPError.Condition.bad_request,
"No acceptable transfer mechanism");
throw new XMPPException(error.getMessage(), error);
}
if (isByteStream && isIBB && field.getType().equals(FormField.TYPE_LIST_MULTI)) {
@ -299,7 +301,7 @@ public class FileTransferNegotiator {
* @param si The Stream Initiation request to reject.
*/
public void rejectStream(final StreamInitiation si) {
XMPPError error = new XMPPError(403, "Offer Declined");
XMPPError error = new XMPPError(XMPPError.Condition.forbidden, "Offer Declined");
IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(),
IQ.Type.ERROR);
iqPacket.setError(error);
@ -409,8 +411,9 @@ public class FileTransferNegotiator {
}
if (!isByteStream && !isIBB) {
XMPPError error = new XMPPError(400);
throw new XMPPException("No acceptable transfer mechanism", error);
XMPPError error = new XMPPError(XMPPError.Condition.bad_request,
"No acceptable transfer mechanism");
throw new XMPPException(error.getMessage(), error);
}
if (isByteStream && isIBB) {

View File

@ -377,7 +377,7 @@ public class IBBTransferNegotiator extends StreamNegotiator {
private void sendCancelMessage(Message message) {
IQ error = FileTransferNegotiator.createIQ(message.getPacketID(), message.getFrom(), message.getTo(),
IQ.Type.ERROR);
error.setError(new XMPPError(504));
error.setError(new XMPPError(XMPPError.Condition.remote_server_timeout, "Cancel Message Transfer"));
connection.sendPacket(error);
}

View File

@ -220,8 +220,9 @@ public class Socks5TransferNegotiator extends StreamNegotiator {
}
}
if (selectedHost == null || socket == null || !socket.isConnected()) {
throw new XMPPException(
"Could not establish socket with any provided host", new XMPPError(406));
String errorMessage = "Could not establish socket with any provided host";
throw new XMPPException(errorMessage, new XMPPError(
XMPPError.Condition.no_acceptable, errorMessage));
}
return new SelectedHostInfo(selectedHost, socket);

View File

@ -522,7 +522,9 @@ public class VCard extends IQ {
result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
if (result == null) {
throw new XMPPException(new XMPPError(408, "Timeout getting VCard information"));
String errorMessage = "Timeout getting VCard information";
throw new XMPPException(errorMessage, new XMPPError(
XMPPError.Condition.request_timeout, errorMessage));
}
if (result.getError() != null) {
throw new XMPPException(result.getError());