001/** 002 * 003 * Copyright the original author or authors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.filetransfer; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.io.PushbackInputStream; 023 024import org.jivesoftware.smack.SmackException; 025import org.jivesoftware.smack.XMPPConnection; 026import org.jivesoftware.smack.XMPPException; 027import org.jivesoftware.smack.XMPPException.XMPPErrorException; 028import org.jivesoftware.smack.packet.Stanza; 029 030import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; 031import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; 032import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; 033import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 034import org.jivesoftware.smackx.si.packet.StreamInitiation; 035 036import org.jxmpp.jid.Jid; 037 038/** 039 * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the 040 * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}. 041 * 042 * @author Henning Staib 043 * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a> 044 */ 045public class Socks5TransferNegotiator extends StreamNegotiator { 046 047 private final Socks5BytestreamManager manager; 048 049 Socks5TransferNegotiator(XMPPConnection connection) { 050 super(connection); 051 this.manager = Socks5BytestreamManager.getBytestreamManager(connection); 052 } 053 054 @Override 055 public OutputStream createOutgoingStream(String streamID, Jid initiator, Jid target) throws SmackException, XMPPException { 056 try { 057 return this.manager.establishSession(target, streamID).getOutputStream(); 058 } 059 catch (IOException e) { 060 throw new SmackException.SmackWrappedException("error establishing SOCKS5 Bytestream", e); 061 } 062 catch (InterruptedException e) { 063 throw new SmackException.SmackWrappedException("error establishing SOCKS5 Bytestream", e); 064 } 065 } 066 067 @Override 068 public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPErrorException, 069 InterruptedException, SmackException { 070 /* 071 * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session 072 * ID 073 */ 074 this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); 075 076 Stanza streamInitiation = initiateIncomingStream(connection(), initiation); 077 return negotiateIncomingStream(streamInitiation); 078 } 079 080 @Override 081 public void newStreamInitiation(final Jid from, String streamID) { 082 /* 083 * this method is always called prior to #negotiateIncomingStream() so the SOCKS5 084 * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session 085 * ID 086 */ 087 this.manager.ignoreBytestreamRequestOnce(streamID); 088 } 089 090 @Override 091 public String getNamespace() { 092 return Bytestream.NAMESPACE; 093 } 094 095 @Override 096 InputStream negotiateIncomingStream(Stanza streamInitiation) throws InterruptedException, 097 SmackException, XMPPErrorException { 098 // build SOCKS5 Bytestream request 099 Socks5BytestreamRequest request = new ByteStreamRequest(this.manager, 100 (Bytestream) streamInitiation); 101 102 // always accept the request 103 Socks5BytestreamSession session = request.accept(); 104 105 // test input stream 106 try { 107 PushbackInputStream stream = new PushbackInputStream(session.getInputStream()); 108 int firstByte = stream.read(); 109 stream.unread(firstByte); 110 return stream; 111 } 112 catch (IOException e) { 113 throw new SmackException.SmackWrappedException("Error establishing input stream", e); 114 } 115 } 116 117 /** 118 * Derive from Socks5BytestreamRequest to access protected constructor. 119 */ 120 private static final class ByteStreamRequest extends Socks5BytestreamRequest { 121 122 private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) { 123 super(manager, byteStreamRequest); 124 } 125 126 } 127 128}