blob: e168428dabde3d31d3e35deb69384408c2f1b9f2 [file] [log] [blame]
The Android Open Source Projectb5de22c2012-04-01 00:00:00 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package libcore.net.url;
19
20import java.io.BufferedInputStream;
21import java.io.EOFException;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.InterruptedIOException;
26import java.io.OutputStream;
27import java.net.InetSocketAddress;
28import java.net.Proxy;
29import java.net.ProxySelector;
30import java.net.ServerSocket;
31import java.net.Socket;
32import java.net.SocketPermission;
33import java.net.URI;
34import java.net.URISyntaxException;
35import java.net.URL;
36import java.net.URLConnection;
37import java.net.URLStreamHandler;
38import java.nio.charset.Charsets;
39import java.security.Permission;
40import java.util.ArrayList;
41import java.util.Iterator;
42import java.util.List;
43
44public class FtpURLConnection extends URLConnection {
45
46 private static final int FTP_PORT = 21;
47
48 // FTP Reply Constants
49 private static final int FTP_DATAOPEN = 125;
50
51 private static final int FTP_OPENDATA = 150;
52
53 private static final int FTP_OK = 200;
54
55 private static final int FTP_USERREADY = 220;
56
57 private static final int FTP_TRANSFEROK = 226;
58
59 // private static final int FTP_PASV = 227;
60
61 private static final int FTP_LOGGEDIN = 230;
62
63 private static final int FTP_FILEOK = 250;
64
65 private static final int FTP_PASWD = 331;
66
67 // private static final int FTP_DATAERROR = 451;
68
69 // private static final int FTP_ERROR = 500;
70
71 private static final int FTP_NOTFOUND = 550;
72
73 private Socket controlSocket;
74
75 private Socket dataSocket;
76
77 private ServerSocket acceptSocket;
78
79 private InputStream ctrlInput;
80
81 private InputStream inputStream;
82
83 private OutputStream ctrlOutput;
84
85 private int dataPort;
86
87 private String username = "anonymous";
88
89 private String password = "";
90
91 private String replyCode;
92
93 private String hostName;
94
95 private Proxy proxy;
96
97 private Proxy currentProxy;
98
99 private URI uri;
100
101 /**
102 * FtpURLConnection constructor comment.
103 *
104 * @param url
105 */
106 protected FtpURLConnection(URL url) {
107 super(url);
108 hostName = url.getHost();
109 String parse = url.getUserInfo();
110 if (parse != null) {
111 int split = parse.indexOf(':');
112 if (split >= 0) {
113 username = parse.substring(0, split);
114 password = parse.substring(split + 1);
115 } else {
116 username = parse;
117 }
118 }
119 uri = null;
120 try {
121 uri = url.toURI();
122 } catch (URISyntaxException e) {
123 // do nothing.
124 }
125 }
126
127 /**
128 * FtpURLConnection constructor.
129 *
130 * @param url
131 * @param proxy
132 */
133 protected FtpURLConnection(URL url, Proxy proxy) {
134 this(url);
135 this.proxy = proxy;
136 }
137
138 /**
139 * Change the server directory to that specified in the URL
140 */
141 private void cd() throws IOException {
142 int idx = url.getFile().lastIndexOf('/');
143
144 if (idx > 0) {
145 String dir = url.getFile().substring(0, idx);
146 write("CWD " + dir + "\r\n");
147 int reply = getReply();
148 if (reply != FTP_FILEOK && dir.length() > 0 && dir.charAt(0) == '/') {
149 write("CWD " + dir.substring(1) + "\r\n");
150 reply = getReply();
151 }
152 if (reply != FTP_FILEOK) {
153 throw new IOException("Unable to change directories");
154 }
155 }
156 }
157
158 /**
159 * Establishes the connection to the resource specified by this
160 * <code>URL</code>
161 *
162 * @see #connected
163 * @see java.io.IOException
164 * @see URLStreamHandler
165 */
166 @Override
167 public void connect() throws IOException {
168 // Use system-wide ProxySelect to select proxy list,
169 // then try to connect via elements in the proxy list.
170 List<Proxy> proxyList = null;
171 if (proxy != null) {
172 proxyList = new ArrayList<Proxy>(1);
173 proxyList.add(proxy);
174 } else {
175 ProxySelector selector = ProxySelector.getDefault();
176 if (selector != null) {
177 proxyList = selector.select(uri);
178 }
179 }
180 if (proxyList == null) {
181 currentProxy = null;
182 connectInternal();
183 } else {
184 ProxySelector selector = ProxySelector.getDefault();
185 Iterator<Proxy> iter = proxyList.iterator();
186 boolean connectOK = false;
187 String failureReason = "";
188 while (iter.hasNext() && !connectOK) {
189 currentProxy = iter.next();
190 try {
191 connectInternal();
192 connectOK = true;
193 } catch (IOException ioe) {
194 failureReason = ioe.getLocalizedMessage();
195 // If connect failed, callback "connectFailed"
196 // should be invoked.
197 if (selector != null && Proxy.NO_PROXY != currentProxy) {
198 selector.connectFailed(uri, currentProxy.address(), ioe);
199 }
200 }
201 }
202 if (!connectOK) {
203 throw new IOException("Unable to connect to server: " + failureReason);
204 }
205 }
206 }
207
208 private void connectInternal() throws IOException {
209 int port = url.getPort();
210 int connectTimeout = getConnectTimeout();
211 if (port <= 0) {
212 port = FTP_PORT;
213 }
214 if (currentProxy == null || Proxy.Type.HTTP == currentProxy.type()) {
215 controlSocket = new Socket();
216 } else {
217 controlSocket = new Socket(currentProxy);
218 }
219 InetSocketAddress addr = new InetSocketAddress(hostName, port);
220 controlSocket.connect(addr, connectTimeout);
221 connected = true;
222 ctrlOutput = controlSocket.getOutputStream();
223 ctrlInput = controlSocket.getInputStream();
224 login();
225 setType();
226 if (!getDoInput()) {
227 cd();
228 }
229
230 try {
231 acceptSocket = new ServerSocket(0);
232 dataPort = acceptSocket.getLocalPort();
233 /* Cannot set REUSEADDR so we need to send a PORT command */
234 port();
235 if (connectTimeout == 0) {
236 // set timeout rather than zero as before
237 connectTimeout = 3000;
238 }
239 acceptSocket.setSoTimeout(getConnectTimeout());
240 if (getDoInput()) {
241 getFile();
242 } else {
243 sendFile();
244 }
245 dataSocket = acceptSocket.accept();
246 dataSocket.setSoTimeout(getReadTimeout());
247 acceptSocket.close();
248 } catch (InterruptedIOException e) {
249 throw new IOException("Could not establish data connection");
250 }
251 if (getDoInput()) {
252 inputStream = new FtpURLInputStream(
253 new BufferedInputStream(dataSocket.getInputStream()), controlSocket);
254 }
255 }
256
257 /**
258 * Returns the content type of the resource. Just takes a guess based on the
259 * name.
260 */
261 @Override
262 public String getContentType() {
263 String result = guessContentTypeFromName(url.getFile());
264 if (result == null) {
265 return "content/unknown";
266 }
267 return result;
268 }
269
270 private void getFile() throws IOException {
271 int reply;
272 String file = url.getFile();
273 write("RETR " + file + "\r\n");
274 reply = getReply();
275 if (reply == FTP_NOTFOUND && file.length() > 0 && file.charAt(0) == '/') {
276 write("RETR " + file.substring(1) + "\r\n");
277 reply = getReply();
278 }
279 if (!(reply == FTP_OPENDATA || reply == FTP_TRANSFEROK)) {
280 throw new FileNotFoundException("Unable to retrieve file: " + reply);
281 }
282 }
283
284 /**
285 * Creates a input stream for writing to this URL Connection.
286 *
287 * @return The input stream to write to
288 * @throws IOException
289 * Cannot read from URL or error creating InputStream
290 *
291 * @see #getContent()
292 * @see #getOutputStream()
293 * @see java.io.InputStream
294 * @see java.io.IOException
295 *
296 */
297 @Override
298 public InputStream getInputStream() throws IOException {
299 if (!connected) {
300 connect();
301 }
302 return inputStream;
303 }
304
305 /**
306 * Returns the permission object (in this case, SocketPermission) with the
307 * host and the port number as the target name and "resolve, connect" as the
308 * action list.
309 *
310 * @return the permission object required for this connection
311 * @throws IOException
312 * thrown when an IO exception occurs during the creation of the
313 * permission object.
314 */
315 @Override
316 public Permission getPermission() throws IOException {
317 int port = url.getPort();
318 if (port <= 0) {
319 port = FTP_PORT;
320 }
321 return new SocketPermission(hostName + ":" + port, "connect, resolve");
322 }
323
324 /**
325 * Creates a output stream for writing to this URL Connection.
326 *
327 * @return The output stream to write to
328 * @throws IOException
329 * when the OutputStream could not be created
330 *
331 * @see #getContent()
332 * @see #getInputStream()
333 * @see java.io.IOException
334 *
335 */
336 @Override
337 public OutputStream getOutputStream() throws IOException {
338 if (!connected) {
339 connect();
340 }
341 return dataSocket.getOutputStream();
342 }
343
344 private int getReply() throws IOException {
345 byte[] code = new byte[3];
346 for (int i = 0; i < code.length; i++) {
347 final int tmp = ctrlInput.read();
348 if (tmp == -1) {
349 throw new EOFException();
350 }
351 code[i] = (byte) tmp;
352 }
353 replyCode = new String(code, 0, code.length, Charsets.ISO_8859_1);
354
355 boolean multiline = false;
356 if (ctrlInput.read() == '-') {
357 multiline = true;
358 }
359 readLine(); /* Skip the rest of the first line */
360 if (multiline) {
361 while (readMultiLine()) {/* Read all of a multiline reply */
362 }
363 }
364
365 try {
366 return Integer.parseInt(replyCode);
367 } catch (NumberFormatException e) {
368 throw (IOException)(new IOException("reply code is invalid").initCause(e));
369 }
370 }
371
372 private void login() throws IOException {
373 int reply;
374 reply = getReply();
375 if (reply == FTP_USERREADY) {
376 } else {
377 throw new IOException("Unable to connect to server: " + url.getHost());
378 }
379 write("USER " + username + "\r\n");
380 reply = getReply();
381 if (reply == FTP_PASWD || reply == FTP_LOGGEDIN) {
382 } else {
383 throw new IOException("Unable to log in to server (USER): " + url.getHost());
384 }
385 if (reply == FTP_PASWD) {
386 write("PASS " + password + "\r\n");
387 reply = getReply();
388 if (!(reply == FTP_OK || reply == FTP_USERREADY || reply == FTP_LOGGEDIN)) {
389 throw new IOException("Unable to log in to server (PASS): " + url.getHost());
390 }
391 }
392 }
393
394 private void port() throws IOException {
395 write("PORT "
396 + controlSocket.getLocalAddress().getHostAddress().replace('.',
397 ',') + ',' + (dataPort >> 8) + ','
398 + (dataPort & 255)
399 + "\r\n");
400 if (getReply() != FTP_OK) {
401 throw new IOException("Unable to configure data port");
402 }
403 }
404
405 /**
406 * Read a line of text and return it for possible parsing
407 */
408 private String readLine() throws IOException {
409 StringBuilder sb = new StringBuilder();
410 int c;
411 while ((c = ctrlInput.read()) != '\n') {
412 sb.append((char) c);
413 }
414 return sb.toString();
415 }
416
417 private boolean readMultiLine() throws IOException {
418 String line = readLine();
419 if (line.length() < 4) {
420 return true;
421 }
422 if (line.substring(0, 3).equals(replyCode)
423 && (line.charAt(3) == (char) 32)) {
424 return false;
425 }
426 return true;
427 }
428
429 /**
430 * Issue the STOR command to the server with the file as the parameter
431 */
432 private void sendFile() throws IOException {
433 int reply;
434 write("STOR "
435 + url.getFile().substring(url.getFile().lastIndexOf('/') + 1,
436 url.getFile().length()) + "\r\n");
437 reply = getReply();
438 if (!(reply == FTP_OPENDATA || reply == FTP_OK || reply == FTP_DATAOPEN)) {
439 throw new IOException("Unable to store file");
440 }
441 }
442
443 /**
444 * Set the flag if this <code>URLConnection</code> supports input (read).
445 * It cannot be set after the connection is made. FtpURLConnections cannot
446 * support both input and output
447 *
448 * @param newValue *
449 * @throws IllegalAccessError
450 * when this method attempts to change the flag after connected
451 *
452 * @see #doInput
453 * @see #getDoInput()
454 * @see java.lang.IllegalAccessError
455 * @see #setDoInput(boolean)
456 */
457 @Override
458 public void setDoInput(boolean newValue) {
459 if (connected) {
460 throw new IllegalAccessError();
461 }
462 this.doInput = newValue;
463 this.doOutput = !newValue;
464 }
465
466 /**
467 * Set the flag if this <code>URLConnection</code> supports output(read).
468 * It cannot be set after the connection is made.\ FtpURLConnections cannot
469 * support both input and output.
470 *
471 * @param newValue
472 *
473 * @throws IllegalAccessError
474 * when this method attempts to change the flag after connected
475 *
476 * @see #doOutput
477 * @see java.lang.IllegalAccessError
478 * @see #setDoOutput(boolean)
479 */
480 @Override
481 public void setDoOutput(boolean newValue) {
482 if (connected) {
483 throw new IllegalAccessError();
484 }
485 this.doOutput = newValue;
486 this.doInput = !newValue;
487 }
488
489 /**
490 * Set the type of the file transfer. Only Image is supported
491 */
492 private void setType() throws IOException {
493 write("TYPE I\r\n");
494 if (getReply() != FTP_OK) {
495 throw new IOException("Unable to set transfer type");
496 }
497 }
498
499 private void write(String command) throws IOException {
500 ctrlOutput.write(command.getBytes(Charsets.ISO_8859_1));
501 }
502}