1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.net.ftp;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.OutputStream;
25 import java.io.Reader;
26 import java.io.UnsupportedEncodingException;
27 import java.net.Inet6Address;
28 import java.net.Socket;
29 import java.net.SocketException;
30 import java.nio.charset.Charset;
31 import java.nio.charset.StandardCharsets;
32 import java.util.ArrayList;
33 import java.util.Base64;
34 import java.util.List;
35
36
37
38
39
40
41 public class FTPHTTPClient extends FTPClient {
42
43 private static final byte[] CRLF = { '\r', '\n' };
44 private final String proxyHost;
45 private final int proxyPort;
46 private final String proxyUsername;
47 private final String proxyPassword;
48 private final Charset charset;
49 private String tunnelHost;
50
51
52
53
54
55
56
57 public FTPHTTPClient(final String proxyHost, final int proxyPort) {
58 this(proxyHost, proxyPort, null, null);
59 }
60
61
62
63
64
65
66
67
68 public FTPHTTPClient(final String proxyHost, final int proxyPort, final Charset encoding) {
69 this(proxyHost, proxyPort, null, null, encoding);
70 }
71
72
73
74
75
76
77
78
79
80 public FTPHTTPClient(final String proxyHost, final int proxyPort, final String proxyUser, final String proxyPass) {
81 this(proxyHost, proxyPort, proxyUser, proxyPass, StandardCharsets.UTF_8);
82 }
83
84
85
86
87
88
89
90
91
92
93 public FTPHTTPClient(final String proxyHost, final int proxyPort, final String proxyUser, final String proxyPass, final Charset encoding) {
94 this.proxyHost = proxyHost;
95 this.proxyPort = proxyPort;
96 this.proxyUsername = proxyUser;
97 this.proxyPassword = proxyPass;
98 this.tunnelHost = null;
99 this.charset = encoding;
100 }
101
102
103
104
105
106
107
108
109
110 @Override
111 @Deprecated
112 protected Socket _openDataConnection_(final int command, final String arg) throws IOException {
113 return super._openDataConnection_(command, arg);
114 }
115
116
117
118
119
120
121
122 @Override
123 protected Socket _openDataConnection_(final String command, final String arg) throws IOException {
124
125 if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) {
126 throw new IllegalStateException("Only passive connection mode supported");
127 }
128
129 final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address;
130 String passiveHost;
131
132 final boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address;
133 if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) {
134 _parseExtendedPassiveModeReply(_replyLines.get(0));
135 passiveHost = this.tunnelHost;
136 } else {
137 if (isInet6Address) {
138 return null;
139 }
140
141 if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) {
142 return null;
143 }
144 _parsePassiveModeReply(_replyLines.get(0));
145 passiveHost = this.getPassiveHost();
146 }
147
148 final Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort);
149 final InputStream is = socket.getInputStream();
150 final OutputStream os = socket.getOutputStream();
151 tunnelHandshake(passiveHost, this.getPassivePort(), is, os);
152 if (getRestartOffset() > 0 && !restart(getRestartOffset())) {
153 socket.close();
154 return null;
155 }
156
157 if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) {
158 socket.close();
159 return null;
160 }
161
162 return socket;
163 }
164
165 @Override
166 public void connect(final String host, final int port) throws SocketException, IOException {
167
168 _socket_ = _socketFactory_.createSocket(proxyHost, proxyPort);
169 _input_ = _socket_.getInputStream();
170 _output_ = _socket_.getOutputStream();
171 final Reader socketIsReader;
172 try {
173 socketIsReader = tunnelHandshake(host, port, _input_, _output_);
174 } catch (final Exception e) {
175 final IOException ioe = new IOException("Could not connect to " + host + " using port " + port);
176 ioe.initCause(e);
177 throw ioe;
178 }
179 super._connectAction_(socketIsReader);
180 }
181
182 private BufferedReader tunnelHandshake(final String host, final int port, final InputStream input, final OutputStream output)
183 throws IOException, UnsupportedEncodingException {
184 final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1";
185 final String hostString = "Host: " + host + ":" + port;
186
187 this.tunnelHost = host;
188 output.write(connectString.getBytes(charset));
189 output.write(CRLF);
190 output.write(hostString.getBytes(charset));
191 output.write(CRLF);
192
193 if (proxyUsername != null && proxyPassword != null) {
194 final String auth = proxyUsername + ":" + proxyPassword;
195 final String header = "Proxy-Authorization: Basic " + Base64.getEncoder().encodeToString(auth.getBytes(charset));
196 output.write(header.getBytes(charset));
197 }
198 output.write(CRLF);
199
200 final List<String> response = new ArrayList<>();
201 final BufferedReader reader = new BufferedReader(new InputStreamReader(input, getCharset()));
202
203 for (String line = reader.readLine(); line != null && !line.isEmpty(); line = reader.readLine()) {
204 response.add(line);
205 }
206
207 final int size = response.size();
208 if (size == 0) {
209 throw new IOException("No response from proxy");
210 }
211
212 String code;
213 final String resp = response.get(0);
214 if (!resp.startsWith("HTTP/") || resp.length() < 12) {
215 throw new IOException("Invalid response from proxy: " + resp);
216 }
217 code = resp.substring(9, 12);
218
219 if (!"200".equals(code)) {
220 final StringBuilder msg = new StringBuilder();
221 msg.append("HTTPTunnelConnector: connection failed\r\n");
222 msg.append("Response received from the proxy:\r\n");
223 for (final String line : response) {
224 msg.append(line);
225 msg.append("\r\n");
226 }
227 throw new IOException(msg.toString());
228 }
229 return reader;
230 }
231 }