Home | History | Annotate | Download | only in filetransfer
      1 /**
      2  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      3  * you may not use this file except in compliance with the License.
      4  * You may obtain a copy of the License at
      5  *
      6  *     http://www.apache.org/licenses/LICENSE-2.0
      7  *
      8  * Unless required by applicable law or agreed to in writing, software
      9  * distributed under the License is distributed on an "AS IS" BASIS,
     10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     11  * See the License for the specific language governing permissions and
     12  * limitations under the License.
     13  */
     14 package org.jivesoftware.smackx.filetransfer;
     15 
     16 import java.io.IOException;
     17 import java.io.InputStream;
     18 import java.io.OutputStream;
     19 import java.io.PushbackInputStream;
     20 
     21 import org.jivesoftware.smack.Connection;
     22 import org.jivesoftware.smack.XMPPException;
     23 import org.jivesoftware.smack.filter.AndFilter;
     24 import org.jivesoftware.smack.filter.FromMatchesFilter;
     25 import org.jivesoftware.smack.filter.PacketFilter;
     26 import org.jivesoftware.smack.filter.PacketTypeFilter;
     27 import org.jivesoftware.smack.packet.IQ;
     28 import org.jivesoftware.smack.packet.Packet;
     29 import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager;
     30 import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest;
     31 import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
     32 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
     33 import org.jivesoftware.smackx.packet.StreamInitiation;
     34 
     35 /**
     36  * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the
     37  * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}.
     38  *
     39  * @author Henning Staib
     40  * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a>
     41  */
     42 public class Socks5TransferNegotiator extends StreamNegotiator {
     43 
     44     private Connection connection;
     45 
     46     private Socks5BytestreamManager manager;
     47 
     48     Socks5TransferNegotiator(Connection connection) {
     49         this.connection = connection;
     50         this.manager = Socks5BytestreamManager.getBytestreamManager(this.connection);
     51     }
     52 
     53     @Override
     54     public OutputStream createOutgoingStream(String streamID, String initiator, String target)
     55                     throws XMPPException {
     56         try {
     57             return this.manager.establishSession(target, streamID).getOutputStream();
     58         }
     59         catch (IOException e) {
     60             throw new XMPPException("error establishing SOCKS5 Bytestream", e);
     61         }
     62         catch (InterruptedException e) {
     63             throw new XMPPException("error establishing SOCKS5 Bytestream", e);
     64         }
     65     }
     66 
     67     @Override
     68     public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPException,
     69                     InterruptedException {
     70         /*
     71          * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session
     72          * ID
     73          */
     74         this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID());
     75 
     76         Packet streamInitiation = initiateIncomingStream(this.connection, initiation);
     77         return negotiateIncomingStream(streamInitiation);
     78     }
     79 
     80     @Override
     81     public PacketFilter getInitiationPacketFilter(final String from, String streamID) {
     82         /*
     83          * this method is always called prior to #negotiateIncomingStream() so the SOCKS5
     84          * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session
     85          * ID
     86          */
     87         this.manager.ignoreBytestreamRequestOnce(streamID);
     88 
     89         return new AndFilter(new FromMatchesFilter(from), new BytestreamSIDFilter(streamID));
     90     }
     91 
     92     @Override
     93     public String[] getNamespaces() {
     94         return new String[] { Socks5BytestreamManager.NAMESPACE };
     95     }
     96 
     97     @Override
     98     InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException,
     99                     InterruptedException {
    100         // build SOCKS5 Bytestream request
    101         Socks5BytestreamRequest request = new ByteStreamRequest(this.manager,
    102                         (Bytestream) streamInitiation);
    103 
    104         // always accept the request
    105         Socks5BytestreamSession session = request.accept();
    106 
    107         // test input stream
    108         try {
    109             PushbackInputStream stream = new PushbackInputStream(session.getInputStream());
    110             int firstByte = stream.read();
    111             stream.unread(firstByte);
    112             return stream;
    113         }
    114         catch (IOException e) {
    115             throw new XMPPException("Error establishing input stream", e);
    116         }
    117     }
    118 
    119     @Override
    120     public void cleanup() {
    121         /* do nothing */
    122     }
    123 
    124     /**
    125      * This PacketFilter accepts an incoming SOCKS5 Bytestream request with a specified session ID.
    126      */
    127     private static class BytestreamSIDFilter extends PacketTypeFilter {
    128 
    129         private String sessionID;
    130 
    131         public BytestreamSIDFilter(String sessionID) {
    132             super(Bytestream.class);
    133             if (sessionID == null) {
    134                 throw new IllegalArgumentException("StreamID cannot be null");
    135             }
    136             this.sessionID = sessionID;
    137         }
    138 
    139         @Override
    140         public boolean accept(Packet packet) {
    141             if (super.accept(packet)) {
    142                 Bytestream bytestream = (Bytestream) packet;
    143 
    144                 // packet must by of type SET and contains the given session ID
    145                 return this.sessionID.equals(bytestream.getSessionID())
    146                                 && IQ.Type.SET.equals(bytestream.getType());
    147             }
    148             return false;
    149         }
    150 
    151     }
    152 
    153     /**
    154      * Derive from Socks5BytestreamRequest to access protected constructor.
    155      */
    156     private static class ByteStreamRequest extends Socks5BytestreamRequest {
    157 
    158         private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) {
    159             super(manager, byteStreamRequest);
    160         }
    161 
    162     }
    163 
    164 }
    165