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