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.charset = encoding;
99 }
100
101
102
103
104
105
106
107
108
109 @Override
110 @Deprecated
111 protected Socket _openDataConnection_(final int command, final String arg) throws IOException {
112 return super._openDataConnection_(command, arg);
113 }
114
115
116
117
118
119
120
121 @Override
122 protected Socket _openDataConnection_(final String command, final String arg) throws IOException {
123
124 if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) {
125 throw new IllegalStateException("Only passive connection mode supported");
126 }
127
128 final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address;
129 final String passiveHost;
130
131 final boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address;
132 if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) {
133 _parseExtendedPassiveModeReply(_replyLines.get(0));
134 passiveHost = tunnelHost;
135 } else {
136 if (isInet6Address) {
137 return null;
138 }
139
140 if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) {
141 return null;
142 }
143 _parsePassiveModeReply(_replyLines.get(0));
144 passiveHost = getPassiveHost();
145 }
146
147 final Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort);
148 final InputStream is = socket.getInputStream();
149 final OutputStream os = socket.getOutputStream();
150 tunnelHandshake(passiveHost, getPassivePort(), is, os);
151 if (getRestartOffset() > 0 && !restart(getRestartOffset())) {
152 socket.close();
153 return null;
154 }
155
156 if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) {
157 socket.close();
158 return null;
159 }
160
161 return socket;
162 }
163
164 @Override
165 public void connect(final String host, final int port) throws SocketException, IOException {
166
167 _socket_ = _socketFactory_.createSocket(proxyHost, proxyPort);
168 _input_ = _socket_.getInputStream();
169 _output_ = _socket_.getOutputStream();
170 final Reader socketIsReader;
171 try {
172 socketIsReader = tunnelHandshake(host, port, _input_, _output_);
173 } catch (final Exception e) {
174 throw new IOException("Could not connect to " + host + " using port " + port, e);
175 }
176 super._connectAction_(socketIsReader);
177 }
178
179 private BufferedReader tunnelHandshake(final String host, final int port, final InputStream input, final OutputStream output)
180 throws IOException, UnsupportedEncodingException {
181 final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1";
182 final String hostString = "Host: " + host + ":" + port;
183
184 this.tunnelHost = host;
185 output.write(connectString.getBytes(charset));
186 output.write(CRLF);
187 output.write(hostString.getBytes(charset));
188 output.write(CRLF);
189
190 if (proxyUserName != null && proxyPassword != null) {
191 final String auth = proxyUserName + ":" + proxyPassword;
192 final String header = "Proxy-Authorization: Basic " + Base64.getEncoder().encodeToString(auth.getBytes(charset));
193 output.write(header.getBytes(charset));
194 output.write(CRLF);
195 }
196 output.write(CRLF);
197
198 final List<String> response = new ArrayList<>();
199 final BufferedReader reader = new BufferedReader(new InputStreamReader(input, getCharset()));
200
201 for (String line = reader.readLine(); line != null && !line.isEmpty(); line = reader.readLine()) {
202 response.add(line);
203 }
204
205 final int size = response.size();
206 if (size == 0) {
207 throw new IOException("No response from proxy");
208 }
209
210 final String code;
211 final String resp = response.get(0);
212 if (!resp.startsWith("HTTP/") || resp.length() < 12) {
213 throw new IOException("Invalid response from proxy: " + resp);
214 }
215 code = resp.substring(9, 12);
216
217 if (!"200".equals(code)) {
218 final StringBuilder msg = new StringBuilder(256);
219 msg.append("HTTPTunnelConnector: connection failed\r\n");
220 msg.append("Response received from the proxy:\r\n");
221 for (final String line : response) {
222 msg.append(line);
223 msg.append("\r\n");
224 }
225 throw new IOException(msg.toString());
226 }
227 return reader;
228 }
229 }