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