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; 029import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; 030import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; 031import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; 032import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 033import org.jivesoftware.smackx.si.packet.StreamInitiation; 034 035import org.jxmpp.jid.Jid; 036 037/** 038 * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the 039 * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}. 040 * 041 * @author Henning Staib 042 * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a> 043 */ 044public class Socks5TransferNegotiator extends StreamNegotiator { 045 046 private final Socks5BytestreamManager manager; 047 048 Socks5TransferNegotiator(XMPPConnection connection) { 049 super(connection); 050 this.manager = Socks5BytestreamManager.getBytestreamManager(connection); 051 } 052 053 @Override 054 public OutputStream createOutgoingStream(String streamID, Jid initiator, Jid target) throws SmackException, XMPPException { 055 try { 056 return this.manager.establishSession(target, streamID).getOutputStream(); 057 } 058 catch (IOException e) { 059 throw new SmackException("error establishing SOCKS5 Bytestream", e); 060 } 061 catch (InterruptedException e) { 062 throw new SmackException("error establishing SOCKS5 Bytestream", e); 063 } 064 } 065 066 @Override 067 public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPErrorException, 068 InterruptedException, SmackException { 069 /* 070 * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session 071 * ID 072 */ 073 this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); 074 075 Stanza streamInitiation = initiateIncomingStream(connection(), initiation); 076 return negotiateIncomingStream(streamInitiation); 077 } 078 079 @Override 080 public void newStreamInitiation(final Jid from, String streamID) { 081 /* 082 * this method is always called prior to #negotiateIncomingStream() so the SOCKS5 083 * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session 084 * ID 085 */ 086 this.manager.ignoreBytestreamRequestOnce(streamID); 087 } 088 089 @Override 090 public String[] getNamespaces() { 091 return new String[] { Bytestream.NAMESPACE }; 092 } 093 094 @Override 095 InputStream negotiateIncomingStream(Stanza streamInitiation) throws InterruptedException, 096 SmackException, XMPPErrorException { 097 // build SOCKS5 Bytestream request 098 Socks5BytestreamRequest request = new ByteStreamRequest(this.manager, 099 (Bytestream) streamInitiation); 100 101 // always accept the request 102 Socks5BytestreamSession session = request.accept(); 103 104 // test input stream 105 try { 106 PushbackInputStream stream = new PushbackInputStream(session.getInputStream()); 107 int firstByte = stream.read(); 108 stream.unread(firstByte); 109 return stream; 110 } 111 catch (IOException e) { 112 throw new SmackException("Error establishing input stream", e); 113 } 114 } 115 116 /** 117 * Derive from Socks5BytestreamRequest to access protected constructor. 118 */ 119 private static final class ByteStreamRequest extends Socks5BytestreamRequest { 120 121 private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) { 122 super(manager, byteStreamRequest); 123 } 124 125 } 126 127}