1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-06-27 05:54:53 +02:00
Smack/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java
Florian Schmaus dde0cfd7f6 Fix incoming file transfers
With bb8dcc9874 the concept if IQ request
handlers was introduced in Smack. This doesn't allow packet/stanza
collectors/listeners to filter for incoming IQ requests. Unfortunately
the file transfer code relied on this being able, so it broke with the
change.

There were two places where the file transfer code was listening for
incoming IQ requests:
- InitationListener(s)
- Negotiator(s)

With this change, we let the InitiationListener signal the existence of
an incoming initation request, send by an IQ of type 'set', using the
newly created EventManager utility.

The negotiator waits for those events to arrive and proceedes as it would
have done when the packet collector was used.
2015-03-02 15:56:26 +01:00

180 lines
7.8 KiB
Java

/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.filetransfer;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.EventManger;
import org.jivesoftware.smack.util.EventManger.Callback;
import org.jivesoftware.smackx.si.packet.StreamInitiation;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import java.io.InputStream;
import java.io.OutputStream;
/**
* After the file transfer negotiation process is completed according to
* XEP-0096, the negotiation process is passed off to a particular stream
* negotiator. The stream negotiator will then negotiate the chosen stream and
* return the stream to transfer the file.
*
* @author Alexander Wenckus
*/
public abstract class StreamNegotiator {
/**
* A event manager for stream initiation requests send to us.
* <p>
* Those are typical XEP-45 Open or XEP-65 Bytestream IQ requests. The even key is in the format
* "initiationFrom + '\t' + streamId"
* </p>
*/
// TODO This field currently being static is considered a quick hack. Ideally this should take
// the local connection into account, for example by changing the key to
// "localJid + '\t' + initiationFrom + '\t' + streamId" or making the field non-static (but then
// you need to provide access to the InitiationListeners, which could get tricky)
protected static final EventManger<String, IQ, SmackException.NotConnectedException> initationSetEvents = new EventManger<>();
/**
* Creates the initiation acceptance packet to forward to the stream
* initiator.
*
* @param streamInitiationOffer The offer from the stream initiator to connect for a stream.
* @param namespaces The namespace that relates to the accepted means of transfer.
* @return The response to be forwarded to the initiator.
*/
protected static StreamInitiation createInitiationAccept(
StreamInitiation streamInitiationOffer, String[] namespaces)
{
StreamInitiation response = new StreamInitiation();
response.setTo(streamInitiationOffer.getFrom());
response.setFrom(streamInitiationOffer.getTo());
response.setType(IQ.Type.result);
response.setStanzaId(streamInitiationOffer.getStanzaId());
DataForm form = new DataForm(DataForm.Type.submit);
FormField field = new FormField(
FileTransferNegotiator.STREAM_DATA_FIELD_NAME);
for (String namespace : namespaces) {
field.addValue(namespace);
}
form.addField(field);
response.setFeatureNegotiationForm(form);
return response;
}
protected final IQ initiateIncomingStream(final XMPPConnection connection, StreamInitiation initiation)
throws NoResponseException, XMPPErrorException, NotConnectedException {
final StreamInitiation response = createInitiationAccept(initiation,
getNamespaces());
newStreamInitiation(initiation.getFrom(), initiation.getSessionID());
final String eventKey = initiation.getFrom().toString() + '\t' + initiation.getSessionID();
IQ streamMethodInitiation;
try {
streamMethodInitiation = initationSetEvents.performActionAndWaitForEvent(eventKey, connection.getPacketReplyTimeout(), new Callback<NotConnectedException>() {
@Override
public void action() throws NotConnectedException {
connection.sendPacket(response);
}
});
}
catch (InterruptedException e) {
// TODO remove this try/catch once merged into 4.2's master branch
throw new IllegalStateException(e);
}
if (streamMethodInitiation == null) {
throw NoResponseException.newWith(connection);
}
XMPPErrorException.ifHasErrorThenThrow(streamMethodInitiation);
return streamMethodInitiation;
}
/**
* Signal that a new stream initiation arrived. The negotiator may needs to prepare for it.
*
* @param from The initiator of the file transfer.
* @param streamID The stream ID related to the transfer.
*/
protected abstract void newStreamInitiation(String from, String streamID);
abstract InputStream negotiateIncomingStream(Stanza streamInitiation) throws XMPPErrorException,
InterruptedException, NoResponseException, SmackException;
/**
* This method handles the file stream download negotiation process. The
* appropriate stream negotiator's initiate incoming stream is called after
* an appropriate file transfer method is selected. The manager will respond
* to the initiator with the selected means of transfer, then it will handle
* any negotiation specific to the particular transfer method. This method
* returns the InputStream, ready to transfer the file.
*
* @param initiation The initiation that triggered this download.
* @return After the negotiation process is complete, the InputStream to
* write a file to is returned.
* @throws XMPPErrorException If an error occurs during this process an XMPPException is
* thrown.
* @throws InterruptedException If thread is interrupted.
* @throws SmackException
*/
public abstract InputStream createIncomingStream(StreamInitiation initiation)
throws XMPPErrorException, InterruptedException, NoResponseException, SmackException;
/**
* This method handles the file upload stream negotiation process. The
* particular stream negotiator is determined during the file transfer
* negotiation process. This method returns the OutputStream to transmit the
* file to the remote user.
*
* @param streamID The streamID that uniquely identifies the file transfer.
* @param initiator The fully-qualified JID of the initiator of the file transfer.
* @param target The fully-qualified JID of the target or receiver of the file
* transfer.
* @return The negotiated stream ready for data.
* @throws XMPPErrorException If an error occurs during the negotiation process an
* exception will be thrown.
* @throws SmackException
* @throws XMPPException
*/
public abstract OutputStream createOutgoingStream(String streamID,
String initiator, String target) throws XMPPErrorException, NoResponseException, SmackException, XMPPException;
/**
* Returns the XMPP namespace reserved for this particular type of file
* transfer.
*
* @return Returns the XMPP namespace reserved for this particular type of
* file transfer.
*/
public abstract String[] getNamespaces();
public static void signal(String eventKey, IQ eventValue) {
initationSetEvents.signalEvent(eventKey, eventValue);
}
}