001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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 */ 017 018 package org.apache.commons.net.ftp; 019 020 import java.io.BufferedReader; 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.io.InputStreamReader; 024 import java.io.OutputStream; 025 import java.io.UnsupportedEncodingException; 026 import java.net.Inet6Address; 027 import java.net.Socket; 028 import java.net.SocketException; 029 import java.util.ArrayList; 030 import java.util.List; 031 032 import org.apache.commons.net.util.Base64; 033 034 /** 035 * Experimental attempt at FTP client that tunnels over an HTTP proxy connection. 036 * 037 * @since 2.2 038 */ 039 public class FTPHTTPClient extends FTPClient { 040 private final String proxyHost; 041 private final int proxyPort; 042 private final String proxyUsername; 043 private final String proxyPassword; 044 045 private static final byte[] CRLF={'\r', '\n'}; 046 private final Base64 base64 = new Base64(); 047 048 public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { 049 this.proxyHost = proxyHost; 050 this.proxyPort = proxyPort; 051 this.proxyUsername = proxyUser; 052 this.proxyPassword = proxyPass; 053 } 054 055 public FTPHTTPClient(String proxyHost, int proxyPort) { 056 this(proxyHost, proxyPort, null, null); 057 } 058 059 060 /** 061 * {@inheritDoc} 062 * 063 * @throws IllegalStateException if connection mode is not passive 064 */ 065 // Kept to maintain binary compatibility 066 // Not strictly necessary, but Clirr complains even though there is a super-impl 067 @Override 068 protected Socket _openDataConnection_(int command, String arg) 069 throws IOException { 070 return super._openDataConnection_(command, arg); 071 } 072 073 /** 074 * {@inheritDoc} 075 * 076 * @throws IllegalStateException if connection mode is not passive 077 * @since 3.1 078 */ 079 @Override 080 protected Socket _openDataConnection_(String command, String arg) 081 throws IOException { 082 //Force local passive mode, active mode not supported by through proxy 083 if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { 084 throw new IllegalStateException("Only passive connection mode supported"); 085 } 086 087 final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; 088 089 boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; 090 if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) { 091 _parseExtendedPassiveModeReply(_replyLines.get(0)); 092 } else { 093 if (isInet6Address) { 094 return null; // Must use EPSV for IPV6 095 } 096 // If EPSV failed on IPV4, revert to PASV 097 if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { 098 return null; 099 } 100 _parsePassiveModeReply(_replyLines.get(0)); 101 } 102 103 Socket socket = new Socket(proxyHost, proxyPort); 104 InputStream is = socket.getInputStream(); 105 OutputStream os = socket.getOutputStream(); 106 tunnelHandshake(this.getPassiveHost(), this.getPassivePort(), is, os); 107 if ((getRestartOffset() > 0) && !restart(getRestartOffset())) { 108 socket.close(); 109 return null; 110 } 111 112 if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { 113 socket.close(); 114 return null; 115 } 116 117 return socket; 118 } 119 120 @Override 121 public void connect(String host, int port) throws SocketException, IOException { 122 123 _socket_ = new Socket(proxyHost, proxyPort); 124 _input_ = _socket_.getInputStream(); 125 _output_ = _socket_.getOutputStream(); 126 try { 127 tunnelHandshake(host, port, _input_, _output_); 128 } 129 catch (Exception e) { 130 IOException ioe = new IOException("Could not connect to " + host+ " using port " + port); 131 ioe.initCause(e); 132 throw ioe; 133 } 134 super._connectAction_(); 135 } 136 137 private void tunnelHandshake(String host, int port, InputStream input, OutputStream output) throws IOException, 138 UnsupportedEncodingException { 139 final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1"; 140 final String hostString = "Host: " + host + ":" + port; 141 142 output.write(connectString.getBytes("UTF-8")); // TODO what is the correct encoding? 143 output.write(CRLF); 144 output.write(hostString.getBytes("UTF-8")); 145 output.write(CRLF); 146 147 if (proxyUsername != null && proxyPassword != null) { 148 final String auth = proxyUsername + ":" + proxyPassword; 149 final String header = "Proxy-Authorization: Basic " 150 + base64.encodeToString(auth.getBytes("UTF-8")); 151 output.write(header.getBytes("UTF-8")); 152 } 153 output.write(CRLF); 154 155 List<String> response = new ArrayList<String>(); 156 BufferedReader reader = new BufferedReader( 157 new InputStreamReader(input)); 158 159 for (String line = reader.readLine(); line != null 160 && line.length() > 0; line = reader.readLine()) { 161 response.add(line); 162 } 163 164 int size = response.size(); 165 if (size == 0) { 166 throw new IOException("No response from proxy"); 167 } 168 169 String code = null; 170 String resp = response.get(0); 171 if (resp.startsWith("HTTP/") && resp.length() >= 12) { 172 code = resp.substring(9, 12); 173 } else { 174 throw new IOException("Invalid response from proxy: " + resp); 175 } 176 177 if (!"200".equals(code)) { 178 StringBuilder msg = new StringBuilder(); 179 msg.append("HTTPTunnelConnector: connection failed\r\n"); 180 msg.append("Response received from the proxy:\r\n"); 181 for (String line : response) { 182 msg.append(line); 183 msg.append("\r\n"); 184 } 185 throw new IOException(msg.toString()); 186 } 187 } 188 } 189 190