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; 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 XMPPConnection connection; 047 048 private Socks5BytestreamManager manager; 049 050 Socks5TransferNegotiator(XMPPConnection connection) { 051 this.connection = connection; 052 this.manager = Socks5BytestreamManager.getBytestreamManager(this.connection); 053 } 054 055 @Override 056 public OutputStream createOutgoingStream(String streamID, Jid initiator, Jid target) throws NoResponseException, SmackException, XMPPException 057 { 058 try { 059 return this.manager.establishSession(target, streamID).getOutputStream(); 060 } 061 catch (IOException e) { 062 throw new SmackException("error establishing SOCKS5 Bytestream", e); 063 } 064 catch (InterruptedException e) { 065 throw new SmackException("error establishing SOCKS5 Bytestream", e); 066 } 067 } 068 069 @Override 070 public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPErrorException, 071 InterruptedException, SmackException { 072 /* 073 * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session 074 * ID 075 */ 076 this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); 077 078 Stanza streamInitiation = initiateIncomingStream(this.connection, initiation); 079 return negotiateIncomingStream(streamInitiation); 080 } 081 082 @Override 083 public void newStreamInitiation(final Jid from, String streamID) { 084 /* 085 * this method is always called prior to #negotiateIncomingStream() so the SOCKS5 086 * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session 087 * ID 088 */ 089 this.manager.ignoreBytestreamRequestOnce(streamID); 090 } 091 092 @Override 093 public String[] getNamespaces() { 094 return new String[] { Bytestream.NAMESPACE }; 095 } 096 097 @Override 098 InputStream negotiateIncomingStream(Stanza streamInitiation) throws InterruptedException, 099 SmackException, XMPPErrorException { 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 SmackException("Error establishing input stream", e); 116 } 117 } 118 119 /** 120 * Derive from Socks5BytestreamRequest to access protected constructor. 121 */ 122 private static final class ByteStreamRequest extends Socks5BytestreamRequest { 123 124 private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) { 125 super(manager, byteStreamRequest); 126 } 127 128 } 129 130}