1 /* 2 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package sun.net.ftp.impl; 26 27 import java.net.*; 28 import java.io.*; 29 import java.security.AccessController; 30 import java.security.PrivilegedAction; 31 import java.security.PrivilegedExceptionAction; 32 import java.text.DateFormat; 33 import java.text.ParseException; 34 import java.text.SimpleDateFormat; 35 import java.util.ArrayList; 36 import java.util.Calendar; 37 import java.util.Date; 38 import java.util.Iterator; 39 import java.util.List; 40 import java.util.TimeZone; 41 import java.util.Vector; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import javax.net.ssl.SSLSocket; 45 import javax.net.ssl.SSLSocketFactory; 46 import sun.misc.BASE64Decoder; 47 import sun.misc.BASE64Encoder; 48 import sun.net.ftp.*; 49 import sun.util.logging.PlatformLogger; 50 51 52 public class FtpClient extends sun.net.ftp.FtpClient { 53 54 private static int defaultSoTimeout; 55 private static int defaultConnectTimeout; 56 private static final PlatformLogger logger = 57 PlatformLogger.getLogger("sun.net.ftp.FtpClient"); 58 private Proxy proxy; 59 private Socket server; 60 private PrintStream out; 61 private InputStream in; 62 private int readTimeout = -1; 63 private int connectTimeout = -1; 64 65 /* Name of encoding to use for output */ 66 private static String encoding = "ISO8859_1"; 67 /** remember the ftp server name because we may need it */ 68 private InetSocketAddress serverAddr; 69 private boolean replyPending = false; 70 private boolean loggedIn = false; 71 private boolean useCrypto = false; 72 private SSLSocketFactory sslFact; 73 private Socket oldSocket; 74 /** Array of strings (usually 1 entry) for the last reply from the server. */ 75 private Vector<String> serverResponse = new Vector<String>(1); 76 /** The last reply code from the ftp daemon. */ 77 private FtpReplyCode lastReplyCode = null; 78 /** Welcome message from the server, if any. */ 79 private String welcomeMsg; 80 /** 81 * Only passive mode used in JDK. See Bug 8010784. 82 */ 83 private final boolean passiveMode = true; 84 private TransferType type = TransferType.BINARY; 85 private long restartOffset = 0; 86 private long lastTransSize = -1; // -1 means 'unknown size' 87 private String lastFileName; 88 /** 89 * Static members used by the parser 90 */ 91 private static String[] patStrings = { 92 // drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog 93 "([\\-ld](?:[r\\-][w\\-][x\\-]){3})\\s*\\d+ (\\w+)\\s*(\\w+)\\s*(\\d+)\\s*([A-Z][a-z][a-z]\\s*\\d+)\\s*(\\d\\d:\\d\\d)\\s*(\\p{Print}*)", 94 // drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog 95 "([\\-ld](?:[r\\-][w\\-][x\\-]){3})\\s*\\d+ (\\w+)\\s*(\\w+)\\s*(\\d+)\\s*([A-Z][a-z][a-z]\\s*\\d+)\\s*(\\d{4})\\s*(\\p{Print}*)", 96 // 04/28/2006 09:12a 3,563 genBuffer.sh 97 "(\\d{2}/\\d{2}/\\d{4})\\s*(\\d{2}:\\d{2}[ap])\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)", 98 // 01-29-97 11:32PM <DIR> prog 99 "(\\d{2}-\\d{2}-\\d{2})\\s*(\\d{2}:\\d{2}[AP]M)\\s*((?:[0-9,]+)|(?:<DIR>))\\s*(\\p{Graph}*)" 100 }; 101 private static int[][] patternGroups = { 102 // 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 5 - permissions, 103 // 6 - user, 7 - group 104 {7, 4, 5, 6, 0, 1, 2, 3}, 105 {7, 4, 5, 0, 6, 1, 2, 3}, 106 {4, 3, 1, 2, 0, 0, 0, 0}, 107 {4, 3, 1, 2, 0, 0, 0, 0}}; 108 private static Pattern[] patterns; 109 private static Pattern linkp = Pattern.compile("(\\p{Print}+) \\-\\> (\\p{Print}+)$"); 110 private DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, java.util.Locale.US); 111 112 static { 113 final int vals[] = {0, 0}; 114 final String encs[] = {null}; 115 AccessController.doPrivileged( new PrivilegedAction<Object>() { public Object run() { vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 0).intValue(); vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 0).intValue(); encs[0] = System.getProperty("file.encoding", "ISO8859_1"); return null; } })116 AccessController.doPrivileged( 117 new PrivilegedAction<Object>() { 118 119 public Object run() { 120 vals[0] = Integer.getInteger("sun.net.client.defaultReadTimeout", 0).intValue(); 121 vals[1] = Integer.getInteger("sun.net.client.defaultConnectTimeout", 0).intValue(); 122 encs[0] = System.getProperty("file.encoding", "ISO8859_1"); 123 return null; 124 } 125 }); 126 if (vals[0] == 0) { 127 defaultSoTimeout = -1; 128 } else { 129 defaultSoTimeout = vals[0]; 130 } 131 132 if (vals[1] == 0) { 133 defaultConnectTimeout = -1; 134 } else { 135 defaultConnectTimeout = vals[1]; 136 } 137 138 encoding = encs[0]; 139 try { 140 if (!isASCIISuperset(encoding)) { 141 encoding = "ISO8859_1"; 142 } 143 } catch (Exception e) { 144 encoding = "ISO8859_1"; 145 } 146 147 patterns = new Pattern[patStrings.length]; 148 for (int i = 0; i < patStrings.length; i++) { 149 patterns[i] = Pattern.compile(patStrings[i]); 150 } 151 } 152 153 /** 154 * Test the named character encoding to verify that it converts ASCII 155 * characters correctly. We have to use an ASCII based encoding, or else 156 * the NetworkClients will not work correctly in EBCDIC based systems. 157 * However, we cannot just use ASCII or ISO8859_1 universally, because in 158 * Asian locales, non-ASCII characters may be embedded in otherwise 159 * ASCII based protocols (eg. HTTP). The specifications (RFC2616, 2398) 160 * are a little ambiguous in this matter. For instance, RFC2398 [part 2.1] 161 * says that the HTTP request URI should be escaped using a defined 162 * mechanism, but there is no way to specify in the escaped string what 163 * the original character set is. It is not correct to assume that 164 * UTF-8 is always used (as in URLs in HTML 4.0). For this reason, 165 * until the specifications are updated to deal with this issue more 166 * comprehensively, and more importantly, HTTP servers are known to 167 * support these mechanisms, we will maintain the current behavior 168 * where it is possible to send non-ASCII characters in their original 169 * unescaped form. 170 */ isASCIISuperset(String encoding)171 private static boolean isASCIISuperset(String encoding) throws Exception { 172 String chkS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 173 "abcdefghijklmnopqrstuvwxyz-_.!~*'();/?:@&=+$,"; 174 175 // Expected byte sequence for string above 176 byte[] chkB = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 177 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 178 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 179 115, 116, 117, 118, 119, 120, 121, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 59, 180 47, 63, 58, 64, 38, 61, 43, 36, 44}; 181 182 byte[] b = chkS.getBytes(encoding); 183 return java.util.Arrays.equals(b, chkB); 184 } 185 186 private class DefaultParser implements FtpDirParser { 187 188 /** 189 * Possible patterns: 190 * 191 * drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog 192 * drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog 193 * drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog 194 * lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000 195 * drwxr-xr-x 1 username ftp 512 Jan 29 23:32 prog 196 * -rw-r--r-- 1 jcc staff 105009 Feb 3 15:05 test.1 197 * 198 * 01-29-97 11:32PM <DIR> prog 199 * 04/28/2006 09:12a 3,563 genBuffer.sh 200 * 201 * drwxr-xr-x folder 0 Jan 29 23:32 prog 202 * 203 * 0 DIR 01-29-97 23:32 PROG 204 */ DefaultParser()205 private DefaultParser() { 206 } 207 parseLine(String line)208 public FtpDirEntry parseLine(String line) { 209 String fdate = null; 210 String fsize = null; 211 String time = null; 212 String filename = null; 213 String permstring = null; 214 String username = null; 215 String groupname = null; 216 boolean dir = false; 217 Calendar now = Calendar.getInstance(); 218 int year = now.get(Calendar.YEAR); 219 220 Matcher m = null; 221 for (int j = 0; j < patterns.length; j++) { 222 m = patterns[j].matcher(line); 223 if (m.find()) { 224 // 0 - file, 1 - size, 2 - date, 3 - time, 4 - year, 225 // 5 - permissions, 6 - user, 7 - group 226 filename = m.group(patternGroups[j][0]); 227 fsize = m.group(patternGroups[j][1]); 228 fdate = m.group(patternGroups[j][2]); 229 if (patternGroups[j][4] > 0) { 230 fdate += (", " + m.group(patternGroups[j][4])); 231 } else if (patternGroups[j][3] > 0) { 232 fdate += (", " + String.valueOf(year)); 233 } 234 if (patternGroups[j][3] > 0) { 235 time = m.group(patternGroups[j][3]); 236 } 237 if (patternGroups[j][5] > 0) { 238 permstring = m.group(patternGroups[j][5]); 239 dir = permstring.startsWith("d"); 240 } 241 if (patternGroups[j][6] > 0) { 242 username = m.group(patternGroups[j][6]); 243 } 244 if (patternGroups[j][7] > 0) { 245 groupname = m.group(patternGroups[j][7]); 246 } 247 // Old DOS format 248 if ("<DIR>".equals(fsize)) { 249 dir = true; 250 fsize = null; 251 } 252 } 253 } 254 255 if (filename != null) { 256 Date d; 257 try { 258 d = df.parse(fdate); 259 } catch (Exception e) { 260 d = null; 261 } 262 if (d != null && time != null) { 263 int c = time.indexOf(":"); 264 now.setTime(d); 265 now.set(Calendar.HOUR, Integer.parseInt(time.substring(0, c))); 266 now.set(Calendar.MINUTE, Integer.parseInt(time.substring(c + 1))); 267 d = now.getTime(); 268 } 269 // see if it's a symbolic link, i.e. the name if followed 270 // by a -> and a path 271 Matcher m2 = linkp.matcher(filename); 272 if (m2.find()) { 273 // Keep only the name then 274 filename = m2.group(1); 275 } 276 boolean[][] perms = new boolean[3][3]; 277 for (int i = 0; i < 3; i++) { 278 for (int j = 0; j < 3; j++) { 279 perms[i][j] = (permstring.charAt((i * 3) + j) != '-'); 280 } 281 } 282 FtpDirEntry file = new FtpDirEntry(filename); 283 file.setUser(username).setGroup(groupname); 284 file.setSize(Long.parseLong(fsize)).setLastModified(d); 285 file.setPermissions(perms); 286 file.setType(dir ? FtpDirEntry.Type.DIR : (line.charAt(0) == 'l' ? FtpDirEntry.Type.LINK : FtpDirEntry.Type.FILE)); 287 return file; 288 } 289 return null; 290 } 291 } 292 293 private class MLSxParser implements FtpDirParser { 294 295 private SimpleDateFormat df = new SimpleDateFormat("yyyyMMddhhmmss"); 296 parseLine(String line)297 public FtpDirEntry parseLine(String line) { 298 String name = null; 299 int i = line.lastIndexOf(";"); 300 if (i > 0) { 301 name = line.substring(i + 1).trim(); 302 line = line.substring(0, i); 303 } else { 304 name = line.trim(); 305 line = ""; 306 } 307 FtpDirEntry file = new FtpDirEntry(name); 308 while (!line.isEmpty()) { 309 String s; 310 i = line.indexOf(";"); 311 if (i > 0) { 312 s = line.substring(0, i); 313 line = line.substring(i + 1); 314 } else { 315 s = line; 316 line = ""; 317 } 318 i = s.indexOf("="); 319 if (i > 0) { 320 String fact = s.substring(0, i); 321 String value = s.substring(i + 1); 322 file.addFact(fact, value); 323 } 324 } 325 String s = file.getFact("Size"); 326 if (s != null) { 327 file.setSize(Long.parseLong(s)); 328 } 329 s = file.getFact("Modify"); 330 if (s != null) { 331 Date d = null; 332 try { 333 d = df.parse(s); 334 } catch (ParseException ex) { 335 } 336 if (d != null) { 337 file.setLastModified(d); 338 } 339 } 340 s = file.getFact("Create"); 341 if (s != null) { 342 Date d = null; 343 try { 344 d = df.parse(s); 345 } catch (ParseException ex) { 346 } 347 if (d != null) { 348 file.setCreated(d); 349 } 350 } 351 s = file.getFact("Type"); 352 if (s != null) { 353 if (s.equalsIgnoreCase("file")) { 354 file.setType(FtpDirEntry.Type.FILE); 355 } 356 if (s.equalsIgnoreCase("dir")) { 357 file.setType(FtpDirEntry.Type.DIR); 358 } 359 if (s.equalsIgnoreCase("cdir")) { 360 file.setType(FtpDirEntry.Type.CDIR); 361 } 362 if (s.equalsIgnoreCase("pdir")) { 363 file.setType(FtpDirEntry.Type.PDIR); 364 } 365 } 366 return file; 367 } 368 }; 369 private FtpDirParser parser = new DefaultParser(); 370 private FtpDirParser mlsxParser = new MLSxParser(); 371 private static Pattern transPat = null; 372 getTransferSize()373 private void getTransferSize() { 374 lastTransSize = -1; 375 /** 376 * If it's a start of data transfer response, let's try to extract 377 * the size from the response string. Usually it looks like that: 378 * 379 * 150 Opening BINARY mode data connection for foo (6701 bytes). 380 */ 381 String response = getLastResponseString(); 382 if (transPat == null) { 383 transPat = Pattern.compile("150 Opening .*\\((\\d+) bytes\\)."); 384 } 385 Matcher m = transPat.matcher(response); 386 if (m.find()) { 387 String s = m.group(1); 388 lastTransSize = Long.parseLong(s); 389 } 390 } 391 392 /** 393 * extract the created file name from the response string: 394 * 226 Transfer complete (unique file name:toto.txt.1). 395 * Usually happens when a STOU (store unique) command had been issued. 396 */ getTransferName()397 private void getTransferName() { 398 lastFileName = null; 399 String response = getLastResponseString(); 400 int i = response.indexOf("unique file name:"); 401 int e = response.lastIndexOf(')'); 402 if (i >= 0) { 403 i += 17; // Length of "unique file name:" 404 lastFileName = response.substring(i, e); 405 } 406 } 407 408 /** 409 * Pulls the response from the server and returns the code as a 410 * number. Returns -1 on failure. 411 */ readServerResponse()412 private int readServerResponse() throws IOException { 413 StringBuffer replyBuf = new StringBuffer(32); 414 int c; 415 int continuingCode = -1; 416 int code; 417 String response; 418 419 serverResponse.setSize(0); 420 while (true) { 421 while ((c = in.read()) != -1) { 422 if (c == '\r') { 423 if ((c = in.read()) != '\n') { 424 replyBuf.append('\r'); 425 } 426 } 427 replyBuf.append((char) c); 428 if (c == '\n') { 429 break; 430 } 431 } 432 response = replyBuf.toString(); 433 replyBuf.setLength(0); 434 if (logger.isLoggable(PlatformLogger.FINEST)) { 435 logger.finest("Server [" + serverAddr + "] --> " + response); 436 } 437 438 if (response.length() == 0) { 439 code = -1; 440 } else { 441 try { 442 code = Integer.parseInt(response.substring(0, 3)); 443 } catch (NumberFormatException e) { 444 code = -1; 445 } catch (StringIndexOutOfBoundsException e) { 446 /* this line doesn't contain a response code, so 447 we just completely ignore it */ 448 continue; 449 } 450 } 451 serverResponse.addElement(response); 452 if (continuingCode != -1) { 453 /* we've seen a ###- sequence */ 454 if (code != continuingCode || 455 (response.length() >= 4 && response.charAt(3) == '-')) { 456 continue; 457 } else { 458 /* seen the end of code sequence */ 459 continuingCode = -1; 460 break; 461 } 462 } else if (response.length() >= 4 && response.charAt(3) == '-') { 463 continuingCode = code; 464 continue; 465 } else { 466 break; 467 } 468 } 469 470 return code; 471 } 472 473 /** Sends command <i>cmd</i> to the server. */ sendServer(String cmd)474 private void sendServer(String cmd) { 475 out.print(cmd); 476 if (logger.isLoggable(PlatformLogger.FINEST)) { 477 logger.finest("Server [" + serverAddr + "] <-- " + cmd); 478 } 479 } 480 481 /** converts the server response into a string. */ getResponseString()482 private String getResponseString() { 483 return serverResponse.elementAt(0); 484 } 485 486 /** Returns all server response strings. */ getResponseStrings()487 private Vector<String> getResponseStrings() { 488 return serverResponse; 489 } 490 491 /** 492 * Read the reply from the FTP server. 493 * 494 * @return <code>true</code> if the command was successful 495 * @throws IOException if an error occured 496 */ readReply()497 private boolean readReply() throws IOException { 498 lastReplyCode = FtpReplyCode.find(readServerResponse()); 499 500 if (lastReplyCode.isPositivePreliminary()) { 501 replyPending = true; 502 return true; 503 } 504 if (lastReplyCode.isPositiveCompletion() || lastReplyCode.isPositiveIntermediate()) { 505 if (lastReplyCode == FtpReplyCode.CLOSING_DATA_CONNECTION) { 506 getTransferName(); 507 } 508 return true; 509 } 510 return false; 511 } 512 513 /** 514 * Sends a command to the FTP server and returns the error code 515 * (which can be a "success") sent by the server. 516 * 517 * @param cmd 518 * @return <code>true</code> if the command was successful 519 * @throws IOException 520 */ issueCommand(String cmd)521 private boolean issueCommand(String cmd) throws IOException { 522 if (!isConnected()) { 523 throw new IllegalStateException("Not connected"); 524 } 525 if (replyPending) { 526 try { 527 completePending(); 528 } catch (sun.net.ftp.FtpProtocolException e) { 529 // ignore... 530 } 531 } 532 sendServer(cmd + "\r\n"); 533 return readReply(); 534 } 535 536 /** 537 * Send a command to the FTP server and check for success. 538 * 539 * @param cmd String containing the command 540 * 541 * @throws FtpProtocolException if an error occured 542 */ issueCommandCheck(String cmd)543 private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 544 if (!issueCommand(cmd)) { 545 throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode()); 546 } 547 } 548 private static Pattern epsvPat = null; 549 private static Pattern pasvPat = null; 550 551 /** 552 * Opens a "PASSIVE" connection with the server and returns the connected 553 * <code>Socket</code>. 554 * 555 * @return the connected <code>Socket</code> 556 * @throws IOException if the connection was unsuccessful. 557 */ openPassiveDataConnection(String cmd)558 private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 559 String serverAnswer; 560 int port; 561 InetSocketAddress dest = null; 562 563 /** 564 * Here is the idea: 565 * 566 * - First we want to try the new (and IPv6 compatible) EPSV command 567 * But since we want to be nice with NAT software, we'll issue the 568 * EPSV ALL command first. 569 * EPSV is documented in RFC2428 570 * - If EPSV fails, then we fall back to the older, yet ok, PASV 571 * - If PASV fails as well, then we throw an exception and the calling 572 * method will have to try the EPRT or PORT command 573 */ 574 if (issueCommand("EPSV ALL")) { 575 // We can safely use EPSV commands 576 issueCommandCheck("EPSV"); 577 serverAnswer = getResponseString(); 578 579 // The response string from a EPSV command will contain the port number 580 // the format will be : 581 // 229 Entering Extended PASSIVE Mode (|||58210|) 582 // 583 // So we'll use the regular expresions package to parse the output. 584 585 if (epsvPat == null) { 586 epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)"); 587 } 588 Matcher m = epsvPat.matcher(serverAnswer); 589 if (!m.find()) { 590 throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer); 591 } 592 // Yay! Let's extract the port number 593 String s = m.group(1); 594 port = Integer.parseInt(s); 595 InetAddress add = server.getInetAddress(); 596 if (add != null) { 597 dest = new InetSocketAddress(add, port); 598 } else { 599 // This means we used an Unresolved address to connect in 600 // the first place. Most likely because the proxy is doing 601 // the name resolution for us, so let's keep using unresolved 602 // address. 603 dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port); 604 } 605 } else { 606 // EPSV ALL failed, so Let's try the regular PASV cmd 607 issueCommandCheck("PASV"); 608 serverAnswer = getResponseString(); 609 610 // Let's parse the response String to get the IP & port to connect 611 // to. The String should be in the following format : 612 // 613 // 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2) 614 // 615 // Note that the two parenthesis are optional 616 // 617 // The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2 618 // 619 // The regular expression is a bit more complex this time, because 620 // the parenthesis are optionals and we have to use 3 groups. 621 622 if (pasvPat == null) { 623 pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?"); 624 } 625 Matcher m = pasvPat.matcher(serverAnswer); 626 if (!m.find()) { 627 throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer); 628 } 629 // Get port number out of group 2 & 3 630 port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8); 631 // IP address is simple 632 String s = m.group(1).replace(',', '.'); 633 dest = new InetSocketAddress(s, port); 634 } 635 // Got everything, let's open the socket! 636 Socket s; 637 if (proxy != null) { 638 if (proxy.type() == Proxy.Type.SOCKS) { 639 s = AccessController.doPrivileged( 640 new PrivilegedAction<Socket>() { 641 642 public Socket run() { 643 return new Socket(proxy); 644 } 645 }); 646 } else { 647 s = new Socket(Proxy.NO_PROXY); 648 } 649 } else { 650 s = new Socket(); 651 } 652 653 InetAddress serverAddress = AccessController.doPrivileged( 654 new PrivilegedAction<InetAddress>() { 655 @Override 656 public InetAddress run() { 657 return server.getLocalAddress(); 658 } 659 }); 660 661 // Bind the socket to the same address as the control channel. This 662 // is needed in case of multi-homed systems. 663 s.bind(new InetSocketAddress(serverAddress, 0)); 664 665 if (connectTimeout >= 0) { 666 s.connect(dest, connectTimeout); 667 } else { 668 if (defaultConnectTimeout > 0) { 669 s.connect(dest, defaultConnectTimeout); 670 } else { 671 s.connect(dest); 672 } 673 } 674 if (readTimeout >= 0) { 675 s.setSoTimeout(readTimeout); 676 } else if (defaultSoTimeout > 0) { 677 s.setSoTimeout(defaultSoTimeout); 678 } 679 if (useCrypto) { 680 try { 681 s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true); 682 } catch (Exception e) { 683 throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e); 684 } 685 } 686 if (!issueCommand(cmd)) { 687 s.close(); 688 if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) { 689 // Ensure backward compatibility 690 throw new FileNotFoundException(cmd); 691 } 692 throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode()); 693 } 694 return s; 695 } 696 697 /** 698 * Opens a data connection with the server according to the set mode 699 * (ACTIVE or PASSIVE) then send the command passed as an argument. 700 * 701 * @param cmd the <code>String</code> containing the command to execute 702 * @return the connected <code>Socket</code> 703 * @throws IOException if the connection or command failed 704 */ openDataConnection(String cmd)705 private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 706 Socket clientSocket; 707 708 if (passiveMode) { 709 try { 710 return openPassiveDataConnection(cmd); 711 } catch (sun.net.ftp.FtpProtocolException e) { 712 // If Passive mode failed, fall back on PORT 713 // Otherwise throw exception 714 String errmsg = e.getMessage(); 715 if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) { 716 throw e; 717 } 718 } 719 } 720 ServerSocket portSocket; 721 InetAddress myAddress; 722 String portCmd; 723 724 if (proxy != null && proxy.type() == Proxy.Type.SOCKS) { 725 // We're behind a firewall and the passive mode fail, 726 // since we can't accept a connection through SOCKS (yet) 727 // throw an exception 728 throw new sun.net.ftp.FtpProtocolException("Passive mode failed"); 729 } 730 // Bind the ServerSocket to the same address as the control channel 731 // This is needed for multi-homed systems 732 portSocket = new ServerSocket(0, 1, server.getLocalAddress()); 733 try { 734 myAddress = portSocket.getInetAddress(); 735 if (myAddress.isAnyLocalAddress()) { 736 myAddress = server.getLocalAddress(); 737 } 738 // Let's try the new, IPv6 compatible EPRT command 739 // See RFC2428 for specifics 740 // Some FTP servers (like the one on Solaris) are bugged, they 741 // will accept the EPRT command but then, the subsequent command 742 // (e.g. RETR) will fail, so we have to check BOTH results (the 743 // EPRT cmd then the actual command) to decide wether we should 744 // fall back on the older PORT command. 745 portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" + 746 myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|"; 747 if (!issueCommand(portCmd) || !issueCommand(cmd)) { 748 // The EPRT command failed, let's fall back to good old PORT 749 portCmd = "PORT "; 750 byte[] addr = myAddress.getAddress(); 751 752 /* append host addr */ 753 for (int i = 0; i < addr.length; i++) { 754 portCmd = portCmd + (addr[i] & 0xFF) + ","; 755 } 756 757 /* append port number */ 758 portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff); 759 issueCommandCheck(portCmd); 760 issueCommandCheck(cmd); 761 } 762 // Either the EPRT or the PORT command was successful 763 // Let's create the client socket 764 if (connectTimeout >= 0) { 765 portSocket.setSoTimeout(connectTimeout); 766 } else { 767 if (defaultConnectTimeout > 0) { 768 portSocket.setSoTimeout(defaultConnectTimeout); 769 } 770 } 771 clientSocket = portSocket.accept(); 772 if (readTimeout >= 0) { 773 clientSocket.setSoTimeout(readTimeout); 774 } else { 775 if (defaultSoTimeout > 0) { 776 clientSocket.setSoTimeout(defaultSoTimeout); 777 } 778 } 779 } finally { 780 portSocket.close(); 781 } 782 if (useCrypto) { 783 try { 784 clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true); 785 } catch (Exception ex) { 786 throw new IOException(ex.getLocalizedMessage()); 787 } 788 } 789 return clientSocket; 790 } 791 createInputStream(InputStream in)792 private InputStream createInputStream(InputStream in) { 793 if (type == TransferType.ASCII) { 794 return new sun.net.TelnetInputStream(in, false); 795 } 796 return in; 797 } 798 createOutputStream(OutputStream out)799 private OutputStream createOutputStream(OutputStream out) { 800 if (type == TransferType.ASCII) { 801 return new sun.net.TelnetOutputStream(out, false); 802 } 803 return out; 804 } 805 806 /** 807 * Creates an instance of FtpClient. The client is not connected to any 808 * server yet. 809 * 810 */ FtpClient()811 protected FtpClient() { 812 } 813 814 /** 815 * Creates an instance of FtpClient. The client is not connected to any 816 * server yet. 817 * 818 */ create()819 public static sun.net.ftp.FtpClient create() { 820 return new FtpClient(); 821 } 822 823 /** 824 * Set the transfer mode to <I>passive</I>. In that mode, data connections 825 * are established by having the client connect to the server. 826 * This is the recommended default mode as it will work best through 827 * firewalls and NATs. 828 * 829 * @return This FtpClient 830 * @see #setActiveMode() 831 */ enablePassiveMode(boolean passive)832 public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) { 833 // Only passive mode used in JDK. See Bug 8010784. 834 // passiveMode = passive; 835 return this; 836 } 837 838 /** 839 * Gets the current transfer mode. 840 * 841 * @return the current <code>FtpTransferMode</code> 842 */ isPassiveModeEnabled()843 public boolean isPassiveModeEnabled() { 844 return passiveMode; 845 } 846 847 /** 848 * Sets the timeout value to use when connecting to the server, 849 * 850 * @param timeout the timeout value, in milliseconds, to use for the connect 851 * operation. A value of zero or less, means use the default timeout. 852 * 853 * @return This FtpClient 854 */ setConnectTimeout(int timeout)855 public sun.net.ftp.FtpClient setConnectTimeout(int timeout) { 856 connectTimeout = timeout; 857 return this; 858 } 859 860 /** 861 * Returns the current connection timeout value. 862 * 863 * @return the value, in milliseconds, of the current connect timeout. 864 * @see #setConnectTimeout(int) 865 */ getConnectTimeout()866 public int getConnectTimeout() { 867 return connectTimeout; 868 } 869 870 /** 871 * Sets the timeout value to use when reading from the server, 872 * 873 * @param timeout the timeout value, in milliseconds, to use for the read 874 * operation. A value of zero or less, means use the default timeout. 875 * @return This FtpClient 876 */ setReadTimeout(int timeout)877 public sun.net.ftp.FtpClient setReadTimeout(int timeout) { 878 readTimeout = timeout; 879 return this; 880 } 881 882 /** 883 * Returns the current read timeout value. 884 * 885 * @return the value, in milliseconds, of the current read timeout. 886 * @see #setReadTimeout(int) 887 */ getReadTimeout()888 public int getReadTimeout() { 889 return readTimeout; 890 } 891 setProxy(Proxy p)892 public sun.net.ftp.FtpClient setProxy(Proxy p) { 893 proxy = p; 894 return this; 895 } 896 897 /** 898 * Get the proxy of this FtpClient 899 * 900 * @return the <code>Proxy</code>, this client is using, or <code>null</code> 901 * if none is used. 902 * @see #setProxy(Proxy) 903 */ getProxy()904 public Proxy getProxy() { 905 return proxy; 906 } 907 908 /** 909 * Connects to the specified destination. 910 * 911 * @param dest the <code>InetSocketAddress</code> to connect to. 912 * @throws IOException if the connection fails. 913 */ tryConnect(InetSocketAddress dest, int timeout)914 private void tryConnect(InetSocketAddress dest, int timeout) throws IOException { 915 if (isConnected()) { 916 disconnect(); 917 } 918 server = doConnect(dest, timeout); 919 try { 920 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 921 true, encoding); 922 } catch (UnsupportedEncodingException e) { 923 throw new InternalError(encoding + "encoding not found"); 924 } 925 in = new BufferedInputStream(server.getInputStream()); 926 } 927 doConnect(InetSocketAddress dest, int timeout)928 private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException { 929 Socket s; 930 if (proxy != null) { 931 if (proxy.type() == Proxy.Type.SOCKS) { 932 s = AccessController.doPrivileged( 933 new PrivilegedAction<Socket>() { 934 935 public Socket run() { 936 return new Socket(proxy); 937 } 938 }); 939 } else { 940 s = new Socket(Proxy.NO_PROXY); 941 } 942 } else { 943 s = new Socket(); 944 } 945 // Instance specific timeouts do have priority, that means 946 // connectTimeout & readTimeout (-1 means not set) 947 // Then global default timeouts 948 // Then no timeout. 949 if (timeout >= 0) { 950 s.connect(dest, timeout); 951 } else { 952 if (connectTimeout >= 0) { 953 s.connect(dest, connectTimeout); 954 } else { 955 if (defaultConnectTimeout > 0) { 956 s.connect(dest, defaultConnectTimeout); 957 } else { 958 s.connect(dest); 959 } 960 } 961 } 962 if (readTimeout >= 0) { 963 s.setSoTimeout(readTimeout); 964 } else if (defaultSoTimeout > 0) { 965 s.setSoTimeout(defaultSoTimeout); 966 } 967 return s; 968 } 969 disconnect()970 private void disconnect() throws IOException { 971 if (isConnected()) { 972 server.close(); 973 } 974 server = null; 975 in = null; 976 out = null; 977 lastTransSize = -1; 978 lastFileName = null; 979 restartOffset = 0; 980 welcomeMsg = null; 981 lastReplyCode = null; 982 serverResponse.setSize(0); 983 } 984 985 /** 986 * Tests whether this client is connected or not to a server. 987 * 988 * @return <code>true</code> if the client is connected. 989 */ isConnected()990 public boolean isConnected() { 991 return server != null; 992 } 993 getServerAddress()994 public SocketAddress getServerAddress() { 995 return server == null ? null : server.getRemoteSocketAddress(); 996 } 997 connect(SocketAddress dest)998 public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException { 999 return connect(dest, -1); 1000 } 1001 1002 /** 1003 * Connects the FtpClient to the specified destination. 1004 * 1005 * @param dest the address of the destination server 1006 * @throws IOException if connection failed. 1007 */ connect(SocketAddress dest, int timeout)1008 public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException { 1009 if (!(dest instanceof InetSocketAddress)) { 1010 throw new IllegalArgumentException("Wrong address type"); 1011 } 1012 serverAddr = (InetSocketAddress) dest; 1013 tryConnect(serverAddr, timeout); 1014 if (!readReply()) { 1015 throw new sun.net.ftp.FtpProtocolException("Welcome message: " + 1016 getResponseString(), lastReplyCode); 1017 } 1018 welcomeMsg = getResponseString().substring(4); 1019 return this; 1020 } 1021 tryLogin(String user, char[] password)1022 private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException { 1023 issueCommandCheck("USER " + user); 1024 1025 /* 1026 * Checks for "331 User name okay, need password." answer 1027 */ 1028 if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) { 1029 if ((password != null) && (password.length > 0)) { 1030 issueCommandCheck("PASS " + String.valueOf(password)); 1031 } 1032 } 1033 } 1034 1035 /** 1036 * Attempts to log on the server with the specified user name and password. 1037 * 1038 * @param user The user name 1039 * @param password The password for that user 1040 * @return <code>true</code> if the login was successful. 1041 * @throws IOException if an error occured during the transmission 1042 */ login(String user, char[] password)1043 public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException { 1044 if (!isConnected()) { 1045 throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE); 1046 } 1047 if (user == null || user.length() == 0) { 1048 throw new IllegalArgumentException("User name can't be null or empty"); 1049 } 1050 tryLogin(user, password); 1051 1052 // keep the welcome message around so we can 1053 // put it in the resulting HTML page. 1054 String l; 1055 StringBuffer sb = new StringBuffer(); 1056 for (int i = 0; i < serverResponse.size(); i++) { 1057 l = serverResponse.elementAt(i); 1058 if (l != null) { 1059 if (l.length() >= 4 && l.startsWith("230")) { 1060 // get rid of the "230-" prefix 1061 l = l.substring(4); 1062 } 1063 sb.append(l); 1064 } 1065 } 1066 welcomeMsg = sb.toString(); 1067 loggedIn = true; 1068 return this; 1069 } 1070 1071 /** 1072 * Attempts to log on the server with the specified user name, password and 1073 * account name. 1074 * 1075 * @param user The user name 1076 * @param password The password for that user. 1077 * @param account The account name for that user. 1078 * @return <code>true</code> if the login was successful. 1079 * @throws IOException if an error occurs during the transmission. 1080 */ login(String user, char[] password, String account)1081 public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException { 1082 1083 if (!isConnected()) { 1084 throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE); 1085 } 1086 if (user == null || user.length() == 0) { 1087 throw new IllegalArgumentException("User name can't be null or empty"); 1088 } 1089 tryLogin(user, password); 1090 1091 /* 1092 * Checks for "332 Need account for login." answer 1093 */ 1094 if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) { 1095 issueCommandCheck("ACCT " + account); 1096 } 1097 1098 // keep the welcome message around so we can 1099 // put it in the resulting HTML page. 1100 StringBuffer sb = new StringBuffer(); 1101 if (serverResponse != null) { 1102 for (String l : serverResponse) { 1103 if (l != null) { 1104 if (l.length() >= 4 && l.startsWith("230")) { 1105 // get rid of the "230-" prefix 1106 l = l.substring(4); 1107 } 1108 sb.append(l); 1109 } 1110 } 1111 } 1112 welcomeMsg = sb.toString(); 1113 loggedIn = true; 1114 return this; 1115 } 1116 1117 /** 1118 * Logs out the current user. This is in effect terminates the current 1119 * session and the connection to the server will be closed. 1120 * 1121 */ close()1122 public void close() throws IOException { 1123 if (isConnected()) { 1124 issueCommand("QUIT"); 1125 loggedIn = false; 1126 } 1127 disconnect(); 1128 } 1129 1130 /** 1131 * Checks whether the client is logged in to the server or not. 1132 * 1133 * @return <code>true</code> if the client has already completed a login. 1134 */ isLoggedIn()1135 public boolean isLoggedIn() { 1136 return loggedIn; 1137 } 1138 1139 /** 1140 * Changes to a specific directory on a remote FTP server 1141 * 1142 * @param remoteDirectory path of the directory to CD to. 1143 * @return <code>true</code> if the operation was successful. 1144 * @exception <code>FtpProtocolException</code> 1145 */ changeDirectory(String remoteDirectory)1146 public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException { 1147 if (remoteDirectory == null || "".equals(remoteDirectory)) { 1148 throw new IllegalArgumentException("directory can't be null or empty"); 1149 } 1150 1151 issueCommandCheck("CWD " + remoteDirectory); 1152 return this; 1153 } 1154 1155 /** 1156 * Changes to the parent directory, sending the CDUP command to the server. 1157 * 1158 * @return <code>true</code> if the command was successful. 1159 * @throws IOException 1160 */ changeToParentDirectory()1161 public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException { 1162 issueCommandCheck("CDUP"); 1163 return this; 1164 } 1165 1166 /** 1167 * Returns the server current working directory, or <code>null</code> if 1168 * the PWD command failed. 1169 * 1170 * @return a <code>String</code> containing the current working directory, 1171 * or <code>null</code> 1172 * @throws IOException 1173 */ getWorkingDirectory()1174 public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException { 1175 issueCommandCheck("PWD"); 1176 /* 1177 * answer will be of the following format : 1178 * 1179 * 257 "/" is current directory. 1180 */ 1181 String answ = getResponseString(); 1182 if (!answ.startsWith("257")) { 1183 return null; 1184 } 1185 return answ.substring(5, answ.lastIndexOf('"')); 1186 } 1187 1188 /** 1189 * Sets the restart offset to the specified value. That value will be 1190 * sent through a <code>REST</code> command to server before a file 1191 * transfer and has the effect of resuming a file transfer from the 1192 * specified point. After a transfer the restart offset is set back to 1193 * zero. 1194 * 1195 * @param offset the offset in the remote file at which to start the next 1196 * transfer. This must be a value greater than or equal to zero. 1197 * @throws IllegalArgumentException if the offset is negative. 1198 */ setRestartOffset(long offset)1199 public sun.net.ftp.FtpClient setRestartOffset(long offset) { 1200 if (offset < 0) { 1201 throw new IllegalArgumentException("offset can't be negative"); 1202 } 1203 restartOffset = offset; 1204 return this; 1205 } 1206 1207 /** 1208 * Retrieves a file from the ftp server and writes it to the specified 1209 * <code>OutputStream</code>. 1210 * If the restart offset was set, then a <code>REST</code> command will be 1211 * sent before the RETR in order to restart the tranfer from the specified 1212 * offset. 1213 * The <code>OutputStream</code> is not closed by this method at the end 1214 * of the transfer. 1215 * 1216 * @param name a <code>String<code> containing the name of the file to 1217 * retreive from the server. 1218 * @param local the <code>OutputStream</code> the file should be written to. 1219 * @throws IOException if the transfer fails. 1220 */ getFile(String name, OutputStream local)1221 public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException { 1222 int mtu = 1500; 1223 if (restartOffset > 0) { 1224 Socket s; 1225 try { 1226 s = openDataConnection("REST " + restartOffset); 1227 } finally { 1228 restartOffset = 0; 1229 } 1230 issueCommandCheck("RETR " + name); 1231 getTransferSize(); 1232 InputStream remote = createInputStream(s.getInputStream()); 1233 byte[] buf = new byte[mtu * 10]; 1234 int l; 1235 while ((l = remote.read(buf)) >= 0) { 1236 if (l > 0) { 1237 local.write(buf, 0, l); 1238 } 1239 } 1240 remote.close(); 1241 } else { 1242 Socket s = openDataConnection("RETR " + name); 1243 getTransferSize(); 1244 InputStream remote = createInputStream(s.getInputStream()); 1245 byte[] buf = new byte[mtu * 10]; 1246 int l; 1247 while ((l = remote.read(buf)) >= 0) { 1248 if (l > 0) { 1249 local.write(buf, 0, l); 1250 } 1251 } 1252 remote.close(); 1253 } 1254 return completePending(); 1255 } 1256 1257 /** 1258 * Retrieves a file from the ftp server, using the RETR command, and 1259 * returns the InputStream from* the established data connection. 1260 * {@link #completePending()} <b>has</b> to be called once the application 1261 * is done reading from the returned stream. 1262 * 1263 * @param name the name of the remote file 1264 * @return the {@link java.io.InputStream} from the data connection, or 1265 * <code>null</code> if the command was unsuccessful. 1266 * @throws IOException if an error occured during the transmission. 1267 */ getFileStream(String name)1268 public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1269 Socket s; 1270 if (restartOffset > 0) { 1271 try { 1272 s = openDataConnection("REST " + restartOffset); 1273 } finally { 1274 restartOffset = 0; 1275 } 1276 if (s == null) { 1277 return null; 1278 } 1279 issueCommandCheck("RETR " + name); 1280 getTransferSize(); 1281 return createInputStream(s.getInputStream()); 1282 } 1283 1284 s = openDataConnection("RETR " + name); 1285 if (s == null) { 1286 return null; 1287 } 1288 getTransferSize(); 1289 return createInputStream(s.getInputStream()); 1290 } 1291 1292 /** 1293 * Transfers a file from the client to the server (aka a <I>put</I>) 1294 * by sending the STOR or STOU command, depending on the 1295 * <code>unique</code> argument, and returns the <code>OutputStream</code> 1296 * from the established data connection. 1297 * {@link #completePending()} <b>has</b> to be called once the application 1298 * is finished writing to the stream. 1299 * 1300 * A new file is created at the server site if the file specified does 1301 * not already exist. 1302 * 1303 * If <code>unique</code> is set to <code>true</code>, the resultant file 1304 * is to be created under a name unique to that directory, meaning 1305 * it will not overwrite an existing file, instead the server will 1306 * generate a new, unique, file name. 1307 * The name of the remote file can be retrieved, after completion of the 1308 * transfer, by calling {@link #getLastFileName()}. 1309 * 1310 * @param name the name of the remote file to write. 1311 * @param unique <code>true</code> if the remote files should be unique, 1312 * in which case the STOU command will be used. 1313 * @return the {@link java.io.OutputStream} from the data connection or 1314 * <code>null</code> if the command was unsuccessful. 1315 * @throws IOException if an error occured during the transmission. 1316 */ putFileStream(String name, boolean unique)1317 public OutputStream putFileStream(String name, boolean unique) 1318 throws sun.net.ftp.FtpProtocolException, IOException 1319 { 1320 String cmd = unique ? "STOU " : "STOR "; 1321 Socket s = openDataConnection(cmd + name); 1322 if (s == null) { 1323 return null; 1324 } 1325 boolean bm = (type == TransferType.BINARY); 1326 return new sun.net.TelnetOutputStream(s.getOutputStream(), bm); 1327 } 1328 1329 /** 1330 * Transfers a file from the client to the server (aka a <I>put</I>) 1331 * by sending the STOR command. The content of the <code>InputStream</code> 1332 * passed in argument is written into the remote file, overwriting any 1333 * existing data. 1334 * 1335 * A new file is created at the server site if the file specified does 1336 * not already exist. 1337 * 1338 * @param name the name of the remote file to write. 1339 * @param local the <code>InputStream</code> that points to the data to 1340 * transfer. 1341 * @param unique <code>true</code> if the remote file should be unique 1342 * (i.e. not already existing), <code>false</code> otherwise. 1343 * @return <code>true</code> if the transfer was successful. 1344 * @throws IOException if an error occured during the transmission. 1345 * @see #getLastFileName() 1346 */ putFile(String name, InputStream local, boolean unique)1347 public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException { 1348 String cmd = unique ? "STOU " : "STOR "; 1349 int mtu = 1500; 1350 if (type == TransferType.BINARY) { 1351 Socket s = openDataConnection(cmd + name); 1352 OutputStream remote = createOutputStream(s.getOutputStream()); 1353 byte[] buf = new byte[mtu * 10]; 1354 int l; 1355 while ((l = local.read(buf)) >= 0) { 1356 if (l > 0) { 1357 remote.write(buf, 0, l); 1358 } 1359 } 1360 remote.close(); 1361 } 1362 return completePending(); 1363 } 1364 1365 /** 1366 * Sends the APPE command to the server in order to transfer a data stream 1367 * passed in argument and append it to the content of the specified remote 1368 * file. 1369 * 1370 * @param name A <code>String</code> containing the name of the remote file 1371 * to append to. 1372 * @param local The <code>InputStream</code> providing access to the data 1373 * to be appended. 1374 * @return <code>true</code> if the transfer was successful. 1375 * @throws IOException if an error occured during the transmission. 1376 */ appendFile(String name, InputStream local)1377 public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException { 1378 int mtu = 1500; 1379 Socket s = openDataConnection("APPE " + name); 1380 OutputStream remote = createOutputStream(s.getOutputStream()); 1381 byte[] buf = new byte[mtu * 10]; 1382 int l; 1383 while ((l = local.read(buf)) >= 0) { 1384 if (l > 0) { 1385 remote.write(buf, 0, l); 1386 } 1387 } 1388 remote.close(); 1389 return completePending(); 1390 } 1391 1392 /** 1393 * Renames a file on the server. 1394 * 1395 * @param from the name of the file being renamed 1396 * @param to the new name for the file 1397 * @throws IOException if the command fails 1398 */ rename(String from, String to)1399 public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException { 1400 issueCommandCheck("RNFR " + from); 1401 issueCommandCheck("RNTO " + to); 1402 return this; 1403 } 1404 1405 /** 1406 * Deletes a file on the server. 1407 * 1408 * @param name a <code>String</code> containing the name of the file 1409 * to delete. 1410 * @return <code>true</code> if the command was successful 1411 * @throws IOException if an error occured during the exchange 1412 */ deleteFile(String name)1413 public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1414 issueCommandCheck("DELE " + name); 1415 return this; 1416 } 1417 1418 /** 1419 * Creates a new directory on the server. 1420 * 1421 * @param name a <code>String</code> containing the name of the directory 1422 * to create. 1423 * @return <code>true</code> if the operation was successful. 1424 * @throws IOException if an error occured during the exchange 1425 */ makeDirectory(String name)1426 public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1427 issueCommandCheck("MKD " + name); 1428 return this; 1429 } 1430 1431 /** 1432 * Removes a directory on the server. 1433 * 1434 * @param name a <code>String</code> containing the name of the directory 1435 * to remove. 1436 * 1437 * @return <code>true</code> if the operation was successful. 1438 * @throws IOException if an error occured during the exchange. 1439 */ removeDirectory(String name)1440 public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1441 issueCommandCheck("RMD " + name); 1442 return this; 1443 } 1444 1445 /** 1446 * Sends a No-operation command. It's useful for testing the connection 1447 * status or as a <I>keep alive</I> mechanism. 1448 * 1449 * @throws FtpProtocolException if the command fails 1450 */ noop()1451 public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException { 1452 issueCommandCheck("NOOP"); 1453 return this; 1454 } 1455 1456 /** 1457 * Sends the STAT command to the server. 1458 * This can be used while a data connection is open to get a status 1459 * on the current transfer, in that case the parameter should be 1460 * <code>null</code>. 1461 * If used between file transfers, it may have a pathname as argument 1462 * in which case it will work as the LIST command except no data 1463 * connection will be created. 1464 * 1465 * @param name an optional <code>String</code> containing the pathname 1466 * the STAT command should apply to. 1467 * @return the response from the server or <code>null</code> if the 1468 * command failed. 1469 * @throws IOException if an error occured during the transmission. 1470 */ getStatus(String name)1471 public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1472 issueCommandCheck((name == null ? "STAT" : "STAT " + name)); 1473 /* 1474 * A typical response will be: 1475 * 213-status of t32.gif: 1476 * -rw-r--r-- 1 jcc staff 247445 Feb 17 1998 t32.gif 1477 * 213 End of Status 1478 * 1479 * or 1480 * 1481 * 211-jsn FTP server status: 1482 * Version wu-2.6.2+Sun 1483 * Connected to localhost (::1) 1484 * Logged in as jccollet 1485 * TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream 1486 * No data connection 1487 * 0 data bytes received in 0 files 1488 * 0 data bytes transmitted in 0 files 1489 * 0 data bytes total in 0 files 1490 * 53 traffic bytes received in 0 transfers 1491 * 485 traffic bytes transmitted in 0 transfers 1492 * 587 traffic bytes total in 0 transfers 1493 * 211 End of status 1494 * 1495 * So we need to remove the 1st and last line 1496 */ 1497 Vector<String> resp = getResponseStrings(); 1498 StringBuffer sb = new StringBuffer(); 1499 for (int i = 1; i < resp.size() - 1; i++) { 1500 sb.append(resp.get(i)); 1501 } 1502 return sb.toString(); 1503 } 1504 1505 /** 1506 * Sends the FEAT command to the server and returns the list of supported 1507 * features in the form of strings. 1508 * 1509 * The features are the supported commands, like AUTH TLS, PROT or PASV. 1510 * See the RFCs for a complete list. 1511 * 1512 * Note that not all FTP servers support that command, in which case 1513 * the method will return <code>null</code> 1514 * 1515 * @return a <code>List</code> of <code>Strings</code> describing the 1516 * supported additional features, or <code>null</code> 1517 * if the command is not supported. 1518 * @throws IOException if an error occurs during the transmission. 1519 */ getFeatures()1520 public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException { 1521 /* 1522 * The FEAT command, when implemented will return something like: 1523 * 1524 * 211-Features: 1525 * AUTH TLS 1526 * PBSZ 1527 * PROT 1528 * EPSV 1529 * EPRT 1530 * PASV 1531 * REST STREAM 1532 * 211 END 1533 */ 1534 ArrayList<String> features = new ArrayList<String>(); 1535 issueCommandCheck("FEAT"); 1536 Vector<String> resp = getResponseStrings(); 1537 // Note that we start at index 1 to skip the 1st line (211-...) 1538 // and we stop before the last line. 1539 for (int i = 1; i < resp.size() - 1; i++) { 1540 String s = resp.get(i); 1541 // Get rid of leading space and trailing newline 1542 features.add(s.substring(1, s.length() - 1)); 1543 } 1544 return features; 1545 } 1546 1547 /** 1548 * sends the ABOR command to the server. 1549 * It tells the server to stop the previous command or transfer. 1550 * 1551 * @return <code>true</code> if the command was successful. 1552 * @throws IOException if an error occured during the transmission. 1553 */ abort()1554 public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException { 1555 issueCommandCheck("ABOR"); 1556 // TODO: Must check the ReplyCode: 1557 /* 1558 * From the RFC: 1559 * There are two cases for the server upon receipt of this 1560 * command: (1) the FTP service command was already completed, 1561 * or (2) the FTP service command is still in progress. 1562 * In the first case, the server closes the data connection 1563 * (if it is open) and responds with a 226 reply, indicating 1564 * that the abort command was successfully processed. 1565 * In the second case, the server aborts the FTP service in 1566 * progress and closes the data connection, returning a 426 1567 * reply to indicate that the service request terminated 1568 * abnormally. The server then sends a 226 reply, 1569 * indicating that the abort command was successfully 1570 * processed. 1571 */ 1572 1573 1574 return this; 1575 } 1576 1577 /** 1578 * Some methods do not wait until completion before returning, so this 1579 * method can be called to wait until completion. This is typically the case 1580 * with commands that trigger a transfer like {@link #getFileStream(String)}. 1581 * So this method should be called before accessing information related to 1582 * such a command. 1583 * <p>This method will actually block reading on the command channel for a 1584 * notification from the server that the command is finished. Such a 1585 * notification often carries extra information concerning the completion 1586 * of the pending action (e.g. number of bytes transfered).</p> 1587 * <p>Note that this will return true immediately if no command or action 1588 * is pending</p> 1589 * <p>It should be also noted that most methods issuing commands to the ftp 1590 * server will call this method if a previous command is pending. 1591 * <p>Example of use: 1592 * <pre> 1593 * InputStream in = cl.getFileStream("file"); 1594 * ... 1595 * cl.completePending(); 1596 * long size = cl.getLastTransferSize(); 1597 * </pre> 1598 * On the other hand, it's not necessary in a case like: 1599 * <pre> 1600 * InputStream in = cl.getFileStream("file"); 1601 * // read content 1602 * ... 1603 * cl.logout(); 1604 * </pre> 1605 * <p>Since {@link #logout()} will call completePending() if necessary.</p> 1606 * @return <code>true</code> if the completion was successful or if no 1607 * action was pending. 1608 * @throws IOException 1609 */ completePending()1610 public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException { 1611 while (replyPending) { 1612 replyPending = false; 1613 if (!readReply()) { 1614 throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode); 1615 } 1616 } 1617 return this; 1618 } 1619 1620 /** 1621 * Reinitializes the USER parameters on the FTP server 1622 * 1623 * @throws FtpProtocolException if the command fails 1624 */ reInit()1625 public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException { 1626 issueCommandCheck("REIN"); 1627 loggedIn = false; 1628 if (useCrypto) { 1629 if (server instanceof SSLSocket) { 1630 javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession(); 1631 session.invalidate(); 1632 // Restore previous socket and streams 1633 server = oldSocket; 1634 oldSocket = null; 1635 try { 1636 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 1637 true, encoding); 1638 } catch (UnsupportedEncodingException e) { 1639 throw new InternalError(encoding + "encoding not found"); 1640 } 1641 in = new BufferedInputStream(server.getInputStream()); 1642 } 1643 } 1644 useCrypto = false; 1645 return this; 1646 } 1647 1648 /** 1649 * Changes the transfer type (binary, ascii, ebcdic) and issue the 1650 * proper command (e.g. TYPE A) to the server. 1651 * 1652 * @param type the <code>FtpTransferType</code> to use. 1653 * @return This FtpClient 1654 * @throws IOException if an error occurs during transmission. 1655 */ setType(TransferType type)1656 public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException { 1657 String cmd = "NOOP"; 1658 1659 this.type = type; 1660 if (type == TransferType.ASCII) { 1661 cmd = "TYPE A"; 1662 } 1663 if (type == TransferType.BINARY) { 1664 cmd = "TYPE I"; 1665 } 1666 if (type == TransferType.EBCDIC) { 1667 cmd = "TYPE E"; 1668 } 1669 issueCommandCheck(cmd); 1670 return this; 1671 } 1672 1673 /** 1674 * Issues a LIST command to the server to get the current directory 1675 * listing, and returns the InputStream from the data connection. 1676 * {@link #completePending()} <b>has</b> to be called once the application 1677 * is finished writing to the stream. 1678 * 1679 * @param path the pathname of the directory to list, or <code>null</code> 1680 * for the current working directory. 1681 * @return the <code>InputStream</code> from the resulting data connection 1682 * @throws IOException if an error occurs during the transmission. 1683 * @see #changeDirectory(String) 1684 * @see #listFiles(String) 1685 */ list(String path)1686 public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1687 Socket s; 1688 s = openDataConnection(path == null ? "LIST" : "LIST " + path); 1689 if (s != null) { 1690 return createInputStream(s.getInputStream()); 1691 } 1692 return null; 1693 } 1694 1695 /** 1696 * Issues a NLST path command to server to get the specified directory 1697 * content. It differs from {@link #list(String)} method by the fact that 1698 * it will only list the file names which would make the parsing of the 1699 * somewhat easier. 1700 * 1701 * {@link #completePending()} <b>has</b> to be called once the application 1702 * is finished writing to the stream. 1703 * 1704 * @param path a <code>String</code> containing the pathname of the 1705 * directory to list or <code>null</code> for the current working 1706 * directory. 1707 * @return the <code>InputStream</code> from the resulting data connection 1708 * @throws IOException if an error occurs during the transmission. 1709 */ nameList(String path)1710 public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1711 Socket s; 1712 s = openDataConnection("NLST " + path); 1713 if (s != null) { 1714 return createInputStream(s.getInputStream()); 1715 } 1716 return null; 1717 } 1718 1719 /** 1720 * Issues the SIZE [path] command to the server to get the size of a 1721 * specific file on the server. 1722 * Note that this command may not be supported by the server. In which 1723 * case -1 will be returned. 1724 * 1725 * @param path a <code>String</code> containing the pathname of the 1726 * file. 1727 * @return a <code>long</code> containing the size of the file or -1 if 1728 * the server returned an error, which can be checked with 1729 * {@link #getLastReplyCode()}. 1730 * @throws IOException if an error occurs during the transmission. 1731 */ getSize(String path)1732 public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1733 if (path == null || path.length() == 0) { 1734 throw new IllegalArgumentException("path can't be null or empty"); 1735 } 1736 issueCommandCheck("SIZE " + path); 1737 if (lastReplyCode == FtpReplyCode.FILE_STATUS) { 1738 String s = getResponseString(); 1739 s = s.substring(4, s.length() - 1); 1740 return Long.parseLong(s); 1741 } 1742 return -1; 1743 } 1744 private static String[] MDTMformats = { 1745 "yyyyMMddHHmmss.SSS", 1746 "yyyyMMddHHmmss" 1747 }; 1748 private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length]; 1749 1750 static { 1751 for (int i = 0; i < MDTMformats.length; i++) { 1752 dateFormats[i] = new SimpleDateFormat(MDTMformats[i]); 1753 dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT")); 1754 } 1755 } 1756 1757 /** 1758 * Issues the MDTM [path] command to the server to get the modification 1759 * time of a specific file on the server. 1760 * Note that this command may not be supported by the server, in which 1761 * case <code>null</code> will be returned. 1762 * 1763 * @param path a <code>String</code> containing the pathname of the file. 1764 * @return a <code>Date</code> representing the last modification time 1765 * or <code>null</code> if the server returned an error, which 1766 * can be checked with {@link #getLastReplyCode()}. 1767 * @throws IOException if an error occurs during the transmission. 1768 */ getLastModified(String path)1769 public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1770 issueCommandCheck("MDTM " + path); 1771 if (lastReplyCode == FtpReplyCode.FILE_STATUS) { 1772 String s = getResponseString().substring(4); 1773 Date d = null; 1774 for (SimpleDateFormat dateFormat : dateFormats) { 1775 try { 1776 d = dateFormat.parse(s); 1777 } catch (ParseException ex) { 1778 } 1779 if (d != null) { 1780 return d; 1781 } 1782 } 1783 } 1784 return null; 1785 } 1786 1787 /** 1788 * Sets the parser used to handle the directory output to the specified 1789 * one. By default the parser is set to one that can handle most FTP 1790 * servers output (Unix base mostly). However it may be necessary for 1791 * and application to provide its own parser due to some uncommon 1792 * output format. 1793 * 1794 * @param p The <code>FtpDirParser</code> to use. 1795 * @see #listFiles(String) 1796 */ setDirParser(FtpDirParser p)1797 public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) { 1798 parser = p; 1799 return this; 1800 } 1801 1802 private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable { 1803 1804 private BufferedReader in = null; 1805 private FtpDirEntry nextFile = null; 1806 private FtpDirParser fparser = null; 1807 private boolean eof = false; 1808 FtpFileIterator(FtpDirParser p, BufferedReader in)1809 public FtpFileIterator(FtpDirParser p, BufferedReader in) { 1810 this.in = in; 1811 this.fparser = p; 1812 readNext(); 1813 } 1814 readNext()1815 private void readNext() { 1816 nextFile = null; 1817 if (eof) { 1818 return; 1819 } 1820 String line = null; 1821 try { 1822 do { 1823 line = in.readLine(); 1824 if (line != null) { 1825 nextFile = fparser.parseLine(line); 1826 if (nextFile != null) { 1827 return; 1828 } 1829 } 1830 } while (line != null); 1831 in.close(); 1832 } catch (IOException iOException) { 1833 } 1834 eof = true; 1835 } 1836 hasNext()1837 public boolean hasNext() { 1838 return nextFile != null; 1839 } 1840 next()1841 public FtpDirEntry next() { 1842 FtpDirEntry ret = nextFile; 1843 readNext(); 1844 return ret; 1845 } 1846 remove()1847 public void remove() { 1848 throw new UnsupportedOperationException("Not supported yet."); 1849 } 1850 close()1851 public void close() throws IOException { 1852 if (in != null && !eof) { 1853 in.close(); 1854 } 1855 eof = true; 1856 nextFile = null; 1857 } 1858 } 1859 1860 /** 1861 * Issues a MLSD command to the server to get the specified directory 1862 * listing and applies the current parser to create an Iterator of 1863 * {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a 1864 * {@link java.io.Closeable}. 1865 * If the server doesn't support the MLSD command, the LIST command is used 1866 * instead. 1867 * 1868 * {@link #completePending()} <b>has</b> to be called once the application 1869 * is finished iterating through the files. 1870 * 1871 * @param path the pathname of the directory to list or <code>null</code> 1872 * for the current working directoty. 1873 * @return a <code>Iterator</code> of files or <code>null</code> if the 1874 * command failed. 1875 * @throws IOException if an error occured during the transmission 1876 * @see #setDirParser(FtpDirParser) 1877 * @see #changeDirectory(String) 1878 */ listFiles(String path)1879 public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1880 Socket s = null; 1881 BufferedReader sin = null; 1882 try { 1883 s = openDataConnection(path == null ? "MLSD" : "MLSD " + path); 1884 } catch (sun.net.ftp.FtpProtocolException FtpException) { 1885 // The server doesn't understand new MLSD command, ignore and fall 1886 // back to LIST 1887 } 1888 1889 if (s != null) { 1890 sin = new BufferedReader(new InputStreamReader(s.getInputStream())); 1891 return new FtpFileIterator(mlsxParser, sin); 1892 } else { 1893 s = openDataConnection(path == null ? "LIST" : "LIST " + path); 1894 if (s != null) { 1895 sin = new BufferedReader(new InputStreamReader(s.getInputStream())); 1896 return new FtpFileIterator(parser, sin); 1897 } 1898 } 1899 return null; 1900 } 1901 sendSecurityData(byte[] buf)1902 private boolean sendSecurityData(byte[] buf) throws IOException { 1903 BASE64Encoder encoder = new BASE64Encoder(); 1904 String s = encoder.encode(buf); 1905 return issueCommand("ADAT " + s); 1906 } 1907 getSecurityData()1908 private byte[] getSecurityData() { 1909 String s = getLastResponseString(); 1910 if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) { 1911 BASE64Decoder decoder = new BASE64Decoder(); 1912 try { 1913 // Need to get rid of the leading '315 ADAT=' 1914 // and the trailing newline 1915 return decoder.decodeBuffer(s.substring(9, s.length() - 1)); 1916 } catch (IOException e) { 1917 // 1918 } 1919 } 1920 return null; 1921 } 1922 1923 /** 1924 * Attempts to use Kerberos GSSAPI as an authentication mechanism with the 1925 * ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if 1926 * it is accepted by the server, will followup with <code>ADAT</code> 1927 * command to exchange the various tokens until authentification is 1928 * successful. This conforms to Appendix I of RFC 2228. 1929 * 1930 * @return <code>true</code> if authentication was successful. 1931 * @throws IOException if an error occurs during the transmission. 1932 */ useKerberos()1933 public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException { 1934 /* 1935 * Comment out for the moment since it's not in use and would create 1936 * needless cross-package links. 1937 * 1938 issueCommandCheck("AUTH GSSAPI"); 1939 if (lastReplyCode != FtpReplyCode.NEED_ADAT) 1940 throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server"); 1941 try { 1942 GSSManager manager = GSSManager.getInstance(); 1943 GSSName name = manager.createName("SERVICE:ftp@"+ 1944 serverAddr.getHostName(), null); 1945 GSSContext context = manager.createContext(name, null, null, 1946 GSSContext.DEFAULT_LIFETIME); 1947 context.requestMutualAuth(true); 1948 context.requestReplayDet(true); 1949 context.requestSequenceDet(true); 1950 context.requestCredDeleg(true); 1951 byte []inToken = new byte[0]; 1952 while (!context.isEstablished()) { 1953 byte[] outToken 1954 = context.initSecContext(inToken, 0, inToken.length); 1955 // send the output token if generated 1956 if (outToken != null) { 1957 if (sendSecurityData(outToken)) { 1958 inToken = getSecurityData(); 1959 } 1960 } 1961 } 1962 loggedIn = true; 1963 } catch (GSSException e) { 1964 1965 } 1966 */ 1967 return this; 1968 } 1969 1970 /** 1971 * Returns the Welcome string the server sent during initial connection. 1972 * 1973 * @return a <code>String</code> containing the message the server 1974 * returned during connection or <code>null</code>. 1975 */ getWelcomeMsg()1976 public String getWelcomeMsg() { 1977 return welcomeMsg; 1978 } 1979 1980 /** 1981 * Returns the last reply code sent by the server. 1982 * 1983 * @return the lastReplyCode 1984 */ getLastReplyCode()1985 public FtpReplyCode getLastReplyCode() { 1986 return lastReplyCode; 1987 } 1988 1989 /** 1990 * Returns the last response string sent by the server. 1991 * 1992 * @return the message string, which can be quite long, last returned 1993 * by the server. 1994 */ getLastResponseString()1995 public String getLastResponseString() { 1996 StringBuffer sb = new StringBuffer(); 1997 if (serverResponse != null) { 1998 for (String l : serverResponse) { 1999 if (l != null) { 2000 sb.append(l); 2001 } 2002 } 2003 } 2004 return sb.toString(); 2005 } 2006 2007 /** 2008 * Returns, when available, the size of the latest started transfer. 2009 * This is retreived by parsing the response string received as an initial 2010 * response to a RETR or similar request. 2011 * 2012 * @return the size of the latest transfer or -1 if either there was no 2013 * transfer or the information was unavailable. 2014 */ getLastTransferSize()2015 public long getLastTransferSize() { 2016 return lastTransSize; 2017 } 2018 2019 /** 2020 * Returns, when available, the remote name of the last transfered file. 2021 * This is mainly useful for "put" operation when the unique flag was 2022 * set since it allows to recover the unique file name created on the 2023 * server which may be different from the one submitted with the command. 2024 * 2025 * @return the name the latest transfered file remote name, or 2026 * <code>null</code> if that information is unavailable. 2027 */ getLastFileName()2028 public String getLastFileName() { 2029 return lastFileName; 2030 } 2031 2032 /** 2033 * Attempts to switch to a secure, encrypted connection. This is done by 2034 * sending the "AUTH TLS" command. 2035 * <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p> 2036 * If successful this will establish a secure command channel with the 2037 * server, it will also make it so that all other transfers (e.g. a RETR 2038 * command) will be done over an encrypted channel as well unless a 2039 * {@link #reInit()} command or a {@link #endSecureSession()} command is issued. 2040 * 2041 * @return <code>true</code> if the operation was successful. 2042 * @throws IOException if an error occured during the transmission. 2043 * @see #endSecureSession() 2044 */ startSecureSession()2045 public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException { 2046 if (!isConnected()) { 2047 throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE); 2048 } 2049 if (sslFact == null) { 2050 try { 2051 sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault(); 2052 } catch (Exception e) { 2053 throw new IOException(e.getLocalizedMessage()); 2054 } 2055 } 2056 issueCommandCheck("AUTH TLS"); 2057 Socket s = null; 2058 try { 2059 s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true); 2060 } catch (javax.net.ssl.SSLException ssle) { 2061 try { 2062 disconnect(); 2063 } catch (Exception e) { 2064 } 2065 throw ssle; 2066 } 2067 // Remember underlying socket so we can restore it later 2068 oldSocket = server; 2069 server = s; 2070 try { 2071 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 2072 true, encoding); 2073 } catch (UnsupportedEncodingException e) { 2074 throw new InternalError(encoding + "encoding not found"); 2075 } 2076 in = new BufferedInputStream(server.getInputStream()); 2077 2078 issueCommandCheck("PBSZ 0"); 2079 issueCommandCheck("PROT P"); 2080 useCrypto = true; 2081 return this; 2082 } 2083 2084 /** 2085 * Sends a <code>CCC</code> command followed by a <code>PROT C</code> 2086 * command to the server terminating an encrypted session and reverting 2087 * back to a non crypted transmission. 2088 * 2089 * @return <code>true</code> if the operation was successful. 2090 * @throws IOException if an error occured during transmission. 2091 * @see #startSecureSession() 2092 */ endSecureSession()2093 public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException { 2094 if (!useCrypto) { 2095 return this; 2096 } 2097 2098 issueCommandCheck("CCC"); 2099 issueCommandCheck("PROT C"); 2100 useCrypto = false; 2101 // Restore previous socket and streams 2102 server = oldSocket; 2103 oldSocket = null; 2104 try { 2105 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 2106 true, encoding); 2107 } catch (UnsupportedEncodingException e) { 2108 throw new InternalError(encoding + "encoding not found"); 2109 } 2110 in = new BufferedInputStream(server.getInputStream()); 2111 2112 return this; 2113 } 2114 2115 /** 2116 * Sends the "Allocate" (ALLO) command to the server telling it to 2117 * pre-allocate the specified number of bytes for the next transfer. 2118 * 2119 * @param size The number of bytes to allocate. 2120 * @return <code>true</code> if the operation was successful. 2121 * @throws IOException if an error occured during the transmission. 2122 */ allocate(long size)2123 public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException { 2124 issueCommandCheck("ALLO " + size); 2125 return this; 2126 } 2127 2128 /** 2129 * Sends the "Structure Mount" (SMNT) command to the server. This let the 2130 * user mount a different file system data structure without altering his 2131 * login or accounting information. 2132 * 2133 * @param struct a <code>String</code> containing the name of the 2134 * structure to mount. 2135 * @return <code>true</code> if the operation was successful. 2136 * @throws IOException if an error occured during the transmission. 2137 */ structureMount(String struct)2138 public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException { 2139 issueCommandCheck("SMNT " + struct); 2140 return this; 2141 } 2142 2143 /** 2144 * Sends a SYST (System) command to the server and returns the String 2145 * sent back by the server describing the operating system at the 2146 * server. 2147 * 2148 * @return a <code>String</code> describing the OS, or <code>null</code> 2149 * if the operation was not successful. 2150 * @throws IOException if an error occured during the transmission. 2151 */ getSystem()2152 public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException { 2153 issueCommandCheck("SYST"); 2154 /* 2155 * 215 UNIX Type: L8 Version: SUNOS 2156 */ 2157 String resp = getResponseString(); 2158 // Get rid of the leading code and blank 2159 return resp.substring(4); 2160 } 2161 2162 /** 2163 * Sends the HELP command to the server, with an optional command, like 2164 * SITE, and returns the text sent back by the server. 2165 * 2166 * @param cmd the command for which the help is requested or 2167 * <code>null</code> for the general help 2168 * @return a <code>String</code> containing the text sent back by the 2169 * server, or <code>null</code> if the command failed. 2170 * @throws IOException if an error occured during transmission 2171 */ getHelp(String cmd)2172 public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 2173 issueCommandCheck("HELP " + cmd); 2174 /** 2175 * 2176 * HELP 2177 * 214-The following commands are implemented. 2178 * USER EPRT STRU ALLO DELE SYST RMD MDTM ADAT 2179 * PASS EPSV MODE REST CWD STAT PWD PROT 2180 * QUIT LPRT RETR RNFR LIST HELP CDUP PBSZ 2181 * PORT LPSV STOR RNTO NLST NOOP STOU AUTH 2182 * PASV TYPE APPE ABOR SITE MKD SIZE CCC 2183 * 214 Direct comments to ftp-bugs@jsn. 2184 * 2185 * HELP SITE 2186 * 214-The following SITE commands are implemented. 2187 * UMASK HELP GROUPS 2188 * IDLE ALIAS CHECKMETHOD 2189 * CHMOD CDPATH CHECKSUM 2190 * 214 Direct comments to ftp-bugs@jsn. 2191 */ 2192 Vector<String> resp = getResponseStrings(); 2193 if (resp.size() == 1) { 2194 // Single line response 2195 return resp.get(0).substring(4); 2196 } 2197 // on multiple lines answers, like the ones above, remove 1st and last 2198 // line, concat the the others. 2199 StringBuffer sb = new StringBuffer(); 2200 for (int i = 1; i < resp.size() - 1; i++) { 2201 sb.append(resp.get(i).substring(3)); 2202 } 2203 return sb.toString(); 2204 } 2205 2206 /** 2207 * Sends the SITE command to the server. This is used by the server 2208 * to provide services specific to his system that are essential 2209 * to file transfer. 2210 * 2211 * @param cmd the command to be sent. 2212 * @return <code>true</code> if the command was successful. 2213 * @throws IOException if an error occured during transmission 2214 */ siteCmd(String cmd)2215 public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 2216 issueCommandCheck("SITE " + cmd); 2217 return this; 2218 } 2219 } 2220