1 /* 2 * Copyright (c) 2009, 2017, 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 sun.net.ftp.FtpProtocolException { 523 if (!isConnected()) { 524 throw new IllegalStateException("Not connected"); 525 } 526 if (replyPending) { 527 try { 528 completePending(); 529 } catch (sun.net.ftp.FtpProtocolException e) { 530 // ignore... 531 } 532 } 533 if (cmd.indexOf('\n') != -1) { 534 sun.net.ftp.FtpProtocolException ex 535 = new sun.net.ftp.FtpProtocolException("Illegal FTP command"); 536 ex.initCause(new IllegalArgumentException("Illegal carriage return")); 537 throw ex; 538 } 539 sendServer(cmd + "\r\n"); 540 return readReply(); 541 } 542 543 /** 544 * Send a command to the FTP server and check for success. 545 * 546 * @param cmd String containing the command 547 * 548 * @throws FtpProtocolException if an error occured 549 */ issueCommandCheck(String cmd)550 private void issueCommandCheck(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 551 if (!issueCommand(cmd)) { 552 throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode()); 553 } 554 } 555 private static Pattern epsvPat = null; 556 private static Pattern pasvPat = null; 557 558 /** 559 * Opens a "PASSIVE" connection with the server and returns the connected 560 * <code>Socket</code>. 561 * 562 * @return the connected <code>Socket</code> 563 * @throws IOException if the connection was unsuccessful. 564 */ openPassiveDataConnection(String cmd)565 private Socket openPassiveDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 566 String serverAnswer; 567 int port; 568 InetSocketAddress dest = null; 569 570 /** 571 * Here is the idea: 572 * 573 * - First we want to try the new (and IPv6 compatible) EPSV command 574 * But since we want to be nice with NAT software, we'll issue the 575 * EPSV ALL command first. 576 * EPSV is documented in RFC2428 577 * - If EPSV fails, then we fall back to the older, yet ok, PASV 578 * - If PASV fails as well, then we throw an exception and the calling 579 * method will have to try the EPRT or PORT command 580 */ 581 if (issueCommand("EPSV ALL")) { 582 // We can safely use EPSV commands 583 issueCommandCheck("EPSV"); 584 serverAnswer = getResponseString(); 585 586 // The response string from a EPSV command will contain the port number 587 // the format will be : 588 // 229 Entering Extended PASSIVE Mode (|||58210|) 589 // 590 // So we'll use the regular expresions package to parse the output. 591 592 if (epsvPat == null) { 593 epsvPat = Pattern.compile("^229 .* \\(\\|\\|\\|(\\d+)\\|\\)"); 594 } 595 Matcher m = epsvPat.matcher(serverAnswer); 596 if (!m.find()) { 597 throw new sun.net.ftp.FtpProtocolException("EPSV failed : " + serverAnswer); 598 } 599 // Yay! Let's extract the port number 600 String s = m.group(1); 601 port = Integer.parseInt(s); 602 InetAddress add = server.getInetAddress(); 603 if (add != null) { 604 dest = new InetSocketAddress(add, port); 605 } else { 606 // This means we used an Unresolved address to connect in 607 // the first place. Most likely because the proxy is doing 608 // the name resolution for us, so let's keep using unresolved 609 // address. 610 dest = InetSocketAddress.createUnresolved(serverAddr.getHostName(), port); 611 } 612 } else { 613 // EPSV ALL failed, so Let's try the regular PASV cmd 614 issueCommandCheck("PASV"); 615 serverAnswer = getResponseString(); 616 617 // Let's parse the response String to get the IP & port to connect 618 // to. The String should be in the following format : 619 // 620 // 227 Entering PASSIVE Mode (A1,A2,A3,A4,p1,p2) 621 // 622 // Note that the two parenthesis are optional 623 // 624 // The IP address is A1.A2.A3.A4 and the port is p1 * 256 + p2 625 // 626 // The regular expression is a bit more complex this time, because 627 // the parenthesis are optionals and we have to use 3 groups. 628 629 if (pasvPat == null) { 630 pasvPat = Pattern.compile("227 .* \\(?(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)?"); 631 } 632 Matcher m = pasvPat.matcher(serverAnswer); 633 if (!m.find()) { 634 throw new sun.net.ftp.FtpProtocolException("PASV failed : " + serverAnswer); 635 } 636 // Get port number out of group 2 & 3 637 port = Integer.parseInt(m.group(3)) + (Integer.parseInt(m.group(2)) << 8); 638 // IP address is simple 639 String s = m.group(1).replace(',', '.'); 640 dest = new InetSocketAddress(s, port); 641 } 642 // Got everything, let's open the socket! 643 Socket s; 644 if (proxy != null) { 645 if (proxy.type() == Proxy.Type.SOCKS) { 646 s = AccessController.doPrivileged( 647 new PrivilegedAction<Socket>() { 648 649 public Socket run() { 650 return new Socket(proxy); 651 } 652 }); 653 } else { 654 s = new Socket(Proxy.NO_PROXY); 655 } 656 } else { 657 s = new Socket(); 658 } 659 660 InetAddress serverAddress = AccessController.doPrivileged( 661 new PrivilegedAction<InetAddress>() { 662 @Override 663 public InetAddress run() { 664 return server.getLocalAddress(); 665 } 666 }); 667 668 // Bind the socket to the same address as the control channel. This 669 // is needed in case of multi-homed systems. 670 s.bind(new InetSocketAddress(serverAddress, 0)); 671 672 if (connectTimeout >= 0) { 673 s.connect(dest, connectTimeout); 674 } else { 675 if (defaultConnectTimeout > 0) { 676 s.connect(dest, defaultConnectTimeout); 677 } else { 678 s.connect(dest); 679 } 680 } 681 if (readTimeout >= 0) { 682 s.setSoTimeout(readTimeout); 683 } else if (defaultSoTimeout > 0) { 684 s.setSoTimeout(defaultSoTimeout); 685 } 686 if (useCrypto) { 687 try { 688 s = sslFact.createSocket(s, dest.getHostName(), dest.getPort(), true); 689 } catch (Exception e) { 690 throw new sun.net.ftp.FtpProtocolException("Can't open secure data channel: " + e); 691 } 692 } 693 if (!issueCommand(cmd)) { 694 s.close(); 695 if (getLastReplyCode() == FtpReplyCode.FILE_UNAVAILABLE) { 696 // Ensure backward compatibility 697 throw new FileNotFoundException(cmd); 698 } 699 throw new sun.net.ftp.FtpProtocolException(cmd + ":" + getResponseString(), getLastReplyCode()); 700 } 701 return s; 702 } 703 704 /** 705 * Opens a data connection with the server according to the set mode 706 * (ACTIVE or PASSIVE) then send the command passed as an argument. 707 * 708 * @param cmd the <code>String</code> containing the command to execute 709 * @return the connected <code>Socket</code> 710 * @throws IOException if the connection or command failed 711 */ openDataConnection(String cmd)712 private Socket openDataConnection(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 713 Socket clientSocket; 714 715 if (passiveMode) { 716 try { 717 return openPassiveDataConnection(cmd); 718 } catch (sun.net.ftp.FtpProtocolException e) { 719 // If Passive mode failed, fall back on PORT 720 // Otherwise throw exception 721 String errmsg = e.getMessage(); 722 if (!errmsg.startsWith("PASV") && !errmsg.startsWith("EPSV")) { 723 throw e; 724 } 725 } 726 } 727 ServerSocket portSocket; 728 InetAddress myAddress; 729 String portCmd; 730 731 if (proxy != null && proxy.type() == Proxy.Type.SOCKS) { 732 // We're behind a firewall and the passive mode fail, 733 // since we can't accept a connection through SOCKS (yet) 734 // throw an exception 735 throw new sun.net.ftp.FtpProtocolException("Passive mode failed"); 736 } 737 // Bind the ServerSocket to the same address as the control channel 738 // This is needed for multi-homed systems 739 portSocket = new ServerSocket(0, 1, server.getLocalAddress()); 740 try { 741 myAddress = portSocket.getInetAddress(); 742 if (myAddress.isAnyLocalAddress()) { 743 myAddress = server.getLocalAddress(); 744 } 745 // Let's try the new, IPv6 compatible EPRT command 746 // See RFC2428 for specifics 747 // Some FTP servers (like the one on Solaris) are bugged, they 748 // will accept the EPRT command but then, the subsequent command 749 // (e.g. RETR) will fail, so we have to check BOTH results (the 750 // EPRT cmd then the actual command) to decide wether we should 751 // fall back on the older PORT command. 752 portCmd = "EPRT |" + ((myAddress instanceof Inet6Address) ? "2" : "1") + "|" + 753 myAddress.getHostAddress() + "|" + portSocket.getLocalPort() + "|"; 754 if (!issueCommand(portCmd) || !issueCommand(cmd)) { 755 // The EPRT command failed, let's fall back to good old PORT 756 portCmd = "PORT "; 757 byte[] addr = myAddress.getAddress(); 758 759 /* append host addr */ 760 for (int i = 0; i < addr.length; i++) { 761 portCmd = portCmd + (addr[i] & 0xFF) + ","; 762 } 763 764 /* append port number */ 765 portCmd = portCmd + ((portSocket.getLocalPort() >>> 8) & 0xff) + "," + (portSocket.getLocalPort() & 0xff); 766 issueCommandCheck(portCmd); 767 issueCommandCheck(cmd); 768 } 769 // Either the EPRT or the PORT command was successful 770 // Let's create the client socket 771 if (connectTimeout >= 0) { 772 portSocket.setSoTimeout(connectTimeout); 773 } else { 774 if (defaultConnectTimeout > 0) { 775 portSocket.setSoTimeout(defaultConnectTimeout); 776 } 777 } 778 clientSocket = portSocket.accept(); 779 if (readTimeout >= 0) { 780 clientSocket.setSoTimeout(readTimeout); 781 } else { 782 if (defaultSoTimeout > 0) { 783 clientSocket.setSoTimeout(defaultSoTimeout); 784 } 785 } 786 } finally { 787 portSocket.close(); 788 } 789 if (useCrypto) { 790 try { 791 clientSocket = sslFact.createSocket(clientSocket, serverAddr.getHostName(), serverAddr.getPort(), true); 792 } catch (Exception ex) { 793 throw new IOException(ex.getLocalizedMessage()); 794 } 795 } 796 return clientSocket; 797 } 798 createInputStream(InputStream in)799 private InputStream createInputStream(InputStream in) { 800 if (type == TransferType.ASCII) { 801 return new sun.net.TelnetInputStream(in, false); 802 } 803 return in; 804 } 805 createOutputStream(OutputStream out)806 private OutputStream createOutputStream(OutputStream out) { 807 if (type == TransferType.ASCII) { 808 return new sun.net.TelnetOutputStream(out, false); 809 } 810 return out; 811 } 812 813 /** 814 * Creates an instance of FtpClient. The client is not connected to any 815 * server yet. 816 * 817 */ FtpClient()818 protected FtpClient() { 819 } 820 821 /** 822 * Creates an instance of FtpClient. The client is not connected to any 823 * server yet. 824 * 825 */ create()826 public static sun.net.ftp.FtpClient create() { 827 return new FtpClient(); 828 } 829 830 /** 831 * Set the transfer mode to <I>passive</I>. In that mode, data connections 832 * are established by having the client connect to the server. 833 * This is the recommended default mode as it will work best through 834 * firewalls and NATs. 835 * 836 * @return This FtpClient 837 * @see #setActiveMode() 838 */ enablePassiveMode(boolean passive)839 public sun.net.ftp.FtpClient enablePassiveMode(boolean passive) { 840 // Only passive mode used in JDK. See Bug 8010784. 841 // passiveMode = passive; 842 return this; 843 } 844 845 /** 846 * Gets the current transfer mode. 847 * 848 * @return the current <code>FtpTransferMode</code> 849 */ isPassiveModeEnabled()850 public boolean isPassiveModeEnabled() { 851 return passiveMode; 852 } 853 854 /** 855 * Sets the timeout value to use when connecting to the server, 856 * 857 * @param timeout the timeout value, in milliseconds, to use for the connect 858 * operation. A value of zero or less, means use the default timeout. 859 * 860 * @return This FtpClient 861 */ setConnectTimeout(int timeout)862 public sun.net.ftp.FtpClient setConnectTimeout(int timeout) { 863 connectTimeout = timeout; 864 return this; 865 } 866 867 /** 868 * Returns the current connection timeout value. 869 * 870 * @return the value, in milliseconds, of the current connect timeout. 871 * @see #setConnectTimeout(int) 872 */ getConnectTimeout()873 public int getConnectTimeout() { 874 return connectTimeout; 875 } 876 877 /** 878 * Sets the timeout value to use when reading from the server, 879 * 880 * @param timeout the timeout value, in milliseconds, to use for the read 881 * operation. A value of zero or less, means use the default timeout. 882 * @return This FtpClient 883 */ setReadTimeout(int timeout)884 public sun.net.ftp.FtpClient setReadTimeout(int timeout) { 885 readTimeout = timeout; 886 return this; 887 } 888 889 /** 890 * Returns the current read timeout value. 891 * 892 * @return the value, in milliseconds, of the current read timeout. 893 * @see #setReadTimeout(int) 894 */ getReadTimeout()895 public int getReadTimeout() { 896 return readTimeout; 897 } 898 setProxy(Proxy p)899 public sun.net.ftp.FtpClient setProxy(Proxy p) { 900 proxy = p; 901 return this; 902 } 903 904 /** 905 * Get the proxy of this FtpClient 906 * 907 * @return the <code>Proxy</code>, this client is using, or <code>null</code> 908 * if none is used. 909 * @see #setProxy(Proxy) 910 */ getProxy()911 public Proxy getProxy() { 912 return proxy; 913 } 914 915 /** 916 * Connects to the specified destination. 917 * 918 * @param dest the <code>InetSocketAddress</code> to connect to. 919 * @throws IOException if the connection fails. 920 */ tryConnect(InetSocketAddress dest, int timeout)921 private void tryConnect(InetSocketAddress dest, int timeout) throws IOException { 922 if (isConnected()) { 923 disconnect(); 924 } 925 server = doConnect(dest, timeout); 926 try { 927 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 928 true, encoding); 929 } catch (UnsupportedEncodingException e) { 930 throw new InternalError(encoding + "encoding not found"); 931 } 932 in = new BufferedInputStream(server.getInputStream()); 933 } 934 doConnect(InetSocketAddress dest, int timeout)935 private Socket doConnect(InetSocketAddress dest, int timeout) throws IOException { 936 Socket s; 937 if (proxy != null) { 938 if (proxy.type() == Proxy.Type.SOCKS) { 939 s = AccessController.doPrivileged( 940 new PrivilegedAction<Socket>() { 941 942 public Socket run() { 943 return new Socket(proxy); 944 } 945 }); 946 } else { 947 s = new Socket(Proxy.NO_PROXY); 948 } 949 } else { 950 s = new Socket(); 951 } 952 // Instance specific timeouts do have priority, that means 953 // connectTimeout & readTimeout (-1 means not set) 954 // Then global default timeouts 955 // Then no timeout. 956 if (timeout >= 0) { 957 s.connect(dest, timeout); 958 } else { 959 if (connectTimeout >= 0) { 960 s.connect(dest, connectTimeout); 961 } else { 962 if (defaultConnectTimeout > 0) { 963 s.connect(dest, defaultConnectTimeout); 964 } else { 965 s.connect(dest); 966 } 967 } 968 } 969 if (readTimeout >= 0) { 970 s.setSoTimeout(readTimeout); 971 } else if (defaultSoTimeout > 0) { 972 s.setSoTimeout(defaultSoTimeout); 973 } 974 return s; 975 } 976 disconnect()977 private void disconnect() throws IOException { 978 if (isConnected()) { 979 server.close(); 980 } 981 server = null; 982 in = null; 983 out = null; 984 lastTransSize = -1; 985 lastFileName = null; 986 restartOffset = 0; 987 welcomeMsg = null; 988 lastReplyCode = null; 989 serverResponse.setSize(0); 990 } 991 992 /** 993 * Tests whether this client is connected or not to a server. 994 * 995 * @return <code>true</code> if the client is connected. 996 */ isConnected()997 public boolean isConnected() { 998 return server != null; 999 } 1000 getServerAddress()1001 public SocketAddress getServerAddress() { 1002 return server == null ? null : server.getRemoteSocketAddress(); 1003 } 1004 connect(SocketAddress dest)1005 public sun.net.ftp.FtpClient connect(SocketAddress dest) throws sun.net.ftp.FtpProtocolException, IOException { 1006 return connect(dest, -1); 1007 } 1008 1009 /** 1010 * Connects the FtpClient to the specified destination. 1011 * 1012 * @param dest the address of the destination server 1013 * @throws IOException if connection failed. 1014 */ connect(SocketAddress dest, int timeout)1015 public sun.net.ftp.FtpClient connect(SocketAddress dest, int timeout) throws sun.net.ftp.FtpProtocolException, IOException { 1016 if (!(dest instanceof InetSocketAddress)) { 1017 throw new IllegalArgumentException("Wrong address type"); 1018 } 1019 serverAddr = (InetSocketAddress) dest; 1020 tryConnect(serverAddr, timeout); 1021 if (!readReply()) { 1022 throw new sun.net.ftp.FtpProtocolException("Welcome message: " + 1023 getResponseString(), lastReplyCode); 1024 } 1025 welcomeMsg = getResponseString().substring(4); 1026 return this; 1027 } 1028 tryLogin(String user, char[] password)1029 private void tryLogin(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException { 1030 issueCommandCheck("USER " + user); 1031 1032 /* 1033 * Checks for "331 User name okay, need password." answer 1034 */ 1035 if (lastReplyCode == FtpReplyCode.NEED_PASSWORD) { 1036 if ((password != null) && (password.length > 0)) { 1037 issueCommandCheck("PASS " + String.valueOf(password)); 1038 } 1039 } 1040 } 1041 1042 /** 1043 * Attempts to log on the server with the specified user name and password. 1044 * 1045 * @param user The user name 1046 * @param password The password for that user 1047 * @return <code>true</code> if the login was successful. 1048 * @throws IOException if an error occured during the transmission 1049 */ login(String user, char[] password)1050 public sun.net.ftp.FtpClient login(String user, char[] password) throws sun.net.ftp.FtpProtocolException, IOException { 1051 if (!isConnected()) { 1052 throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE); 1053 } 1054 if (user == null || user.length() == 0) { 1055 throw new IllegalArgumentException("User name can't be null or empty"); 1056 } 1057 tryLogin(user, password); 1058 1059 // keep the welcome message around so we can 1060 // put it in the resulting HTML page. 1061 String l; 1062 StringBuffer sb = new StringBuffer(); 1063 for (int i = 0; i < serverResponse.size(); i++) { 1064 l = serverResponse.elementAt(i); 1065 if (l != null) { 1066 if (l.length() >= 4 && l.startsWith("230")) { 1067 // get rid of the "230-" prefix 1068 l = l.substring(4); 1069 } 1070 sb.append(l); 1071 } 1072 } 1073 welcomeMsg = sb.toString(); 1074 loggedIn = true; 1075 return this; 1076 } 1077 1078 /** 1079 * Attempts to log on the server with the specified user name, password and 1080 * account name. 1081 * 1082 * @param user The user name 1083 * @param password The password for that user. 1084 * @param account The account name for that user. 1085 * @return <code>true</code> if the login was successful. 1086 * @throws IOException if an error occurs during the transmission. 1087 */ login(String user, char[] password, String account)1088 public sun.net.ftp.FtpClient login(String user, char[] password, String account) throws sun.net.ftp.FtpProtocolException, IOException { 1089 1090 if (!isConnected()) { 1091 throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE); 1092 } 1093 if (user == null || user.length() == 0) { 1094 throw new IllegalArgumentException("User name can't be null or empty"); 1095 } 1096 tryLogin(user, password); 1097 1098 /* 1099 * Checks for "332 Need account for login." answer 1100 */ 1101 if (lastReplyCode == FtpReplyCode.NEED_ACCOUNT) { 1102 issueCommandCheck("ACCT " + account); 1103 } 1104 1105 // keep the welcome message around so we can 1106 // put it in the resulting HTML page. 1107 StringBuffer sb = new StringBuffer(); 1108 if (serverResponse != null) { 1109 for (String l : serverResponse) { 1110 if (l != null) { 1111 if (l.length() >= 4 && l.startsWith("230")) { 1112 // get rid of the "230-" prefix 1113 l = l.substring(4); 1114 } 1115 sb.append(l); 1116 } 1117 } 1118 } 1119 welcomeMsg = sb.toString(); 1120 loggedIn = true; 1121 return this; 1122 } 1123 1124 /** 1125 * Logs out the current user. This is in effect terminates the current 1126 * session and the connection to the server will be closed. 1127 * 1128 */ close()1129 public void close() throws IOException { 1130 if (isConnected()) { 1131 try { 1132 issueCommand("QUIT"); 1133 } catch (FtpProtocolException e) { 1134 } 1135 loggedIn = false; 1136 } 1137 disconnect(); 1138 } 1139 1140 /** 1141 * Checks whether the client is logged in to the server or not. 1142 * 1143 * @return <code>true</code> if the client has already completed a login. 1144 */ isLoggedIn()1145 public boolean isLoggedIn() { 1146 return loggedIn; 1147 } 1148 1149 /** 1150 * Changes to a specific directory on a remote FTP server 1151 * 1152 * @param remoteDirectory path of the directory to CD to. 1153 * @return <code>true</code> if the operation was successful. 1154 * @exception <code>FtpProtocolException</code> 1155 */ changeDirectory(String remoteDirectory)1156 public sun.net.ftp.FtpClient changeDirectory(String remoteDirectory) throws sun.net.ftp.FtpProtocolException, IOException { 1157 if (remoteDirectory == null || "".equals(remoteDirectory)) { 1158 throw new IllegalArgumentException("directory can't be null or empty"); 1159 } 1160 1161 issueCommandCheck("CWD " + remoteDirectory); 1162 return this; 1163 } 1164 1165 /** 1166 * Changes to the parent directory, sending the CDUP command to the server. 1167 * 1168 * @return <code>true</code> if the command was successful. 1169 * @throws IOException 1170 */ changeToParentDirectory()1171 public sun.net.ftp.FtpClient changeToParentDirectory() throws sun.net.ftp.FtpProtocolException, IOException { 1172 issueCommandCheck("CDUP"); 1173 return this; 1174 } 1175 1176 /** 1177 * Returns the server current working directory, or <code>null</code> if 1178 * the PWD command failed. 1179 * 1180 * @return a <code>String</code> containing the current working directory, 1181 * or <code>null</code> 1182 * @throws IOException 1183 */ getWorkingDirectory()1184 public String getWorkingDirectory() throws sun.net.ftp.FtpProtocolException, IOException { 1185 issueCommandCheck("PWD"); 1186 /* 1187 * answer will be of the following format : 1188 * 1189 * 257 "/" is current directory. 1190 */ 1191 String answ = getResponseString(); 1192 if (!answ.startsWith("257")) { 1193 return null; 1194 } 1195 return answ.substring(5, answ.lastIndexOf('"')); 1196 } 1197 1198 /** 1199 * Sets the restart offset to the specified value. That value will be 1200 * sent through a <code>REST</code> command to server before a file 1201 * transfer and has the effect of resuming a file transfer from the 1202 * specified point. After a transfer the restart offset is set back to 1203 * zero. 1204 * 1205 * @param offset the offset in the remote file at which to start the next 1206 * transfer. This must be a value greater than or equal to zero. 1207 * @throws IllegalArgumentException if the offset is negative. 1208 */ setRestartOffset(long offset)1209 public sun.net.ftp.FtpClient setRestartOffset(long offset) { 1210 if (offset < 0) { 1211 throw new IllegalArgumentException("offset can't be negative"); 1212 } 1213 restartOffset = offset; 1214 return this; 1215 } 1216 1217 /** 1218 * Retrieves a file from the ftp server and writes it to the specified 1219 * <code>OutputStream</code>. 1220 * If the restart offset was set, then a <code>REST</code> command will be 1221 * sent before the RETR in order to restart the tranfer from the specified 1222 * offset. 1223 * The <code>OutputStream</code> is not closed by this method at the end 1224 * of the transfer. 1225 * 1226 * @param name a <code>String<code> containing the name of the file to 1227 * retreive from the server. 1228 * @param local the <code>OutputStream</code> the file should be written to. 1229 * @throws IOException if the transfer fails. 1230 */ getFile(String name, OutputStream local)1231 public sun.net.ftp.FtpClient getFile(String name, OutputStream local) throws sun.net.ftp.FtpProtocolException, IOException { 1232 int mtu = 1500; 1233 if (restartOffset > 0) { 1234 Socket s; 1235 try { 1236 s = openDataConnection("REST " + restartOffset); 1237 } finally { 1238 restartOffset = 0; 1239 } 1240 issueCommandCheck("RETR " + name); 1241 getTransferSize(); 1242 InputStream remote = createInputStream(s.getInputStream()); 1243 byte[] buf = new byte[mtu * 10]; 1244 int l; 1245 while ((l = remote.read(buf)) >= 0) { 1246 if (l > 0) { 1247 local.write(buf, 0, l); 1248 } 1249 } 1250 remote.close(); 1251 } else { 1252 Socket s = openDataConnection("RETR " + name); 1253 getTransferSize(); 1254 InputStream remote = createInputStream(s.getInputStream()); 1255 byte[] buf = new byte[mtu * 10]; 1256 int l; 1257 while ((l = remote.read(buf)) >= 0) { 1258 if (l > 0) { 1259 local.write(buf, 0, l); 1260 } 1261 } 1262 remote.close(); 1263 } 1264 return completePending(); 1265 } 1266 1267 /** 1268 * Retrieves a file from the ftp server, using the RETR command, and 1269 * returns the InputStream from* the established data connection. 1270 * {@link #completePending()} <b>has</b> to be called once the application 1271 * is done reading from the returned stream. 1272 * 1273 * @param name the name of the remote file 1274 * @return the {@link java.io.InputStream} from the data connection, or 1275 * <code>null</code> if the command was unsuccessful. 1276 * @throws IOException if an error occured during the transmission. 1277 */ getFileStream(String name)1278 public InputStream getFileStream(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1279 Socket s; 1280 if (restartOffset > 0) { 1281 try { 1282 s = openDataConnection("REST " + restartOffset); 1283 } finally { 1284 restartOffset = 0; 1285 } 1286 if (s == null) { 1287 return null; 1288 } 1289 issueCommandCheck("RETR " + name); 1290 getTransferSize(); 1291 return createInputStream(s.getInputStream()); 1292 } 1293 1294 s = openDataConnection("RETR " + name); 1295 if (s == null) { 1296 return null; 1297 } 1298 getTransferSize(); 1299 return createInputStream(s.getInputStream()); 1300 } 1301 1302 /** 1303 * Transfers a file from the client to the server (aka a <I>put</I>) 1304 * by sending the STOR or STOU command, depending on the 1305 * <code>unique</code> argument, and returns the <code>OutputStream</code> 1306 * from the established data connection. 1307 * {@link #completePending()} <b>has</b> to be called once the application 1308 * is finished writing to the stream. 1309 * 1310 * A new file is created at the server site if the file specified does 1311 * not already exist. 1312 * 1313 * If <code>unique</code> is set to <code>true</code>, the resultant file 1314 * is to be created under a name unique to that directory, meaning 1315 * it will not overwrite an existing file, instead the server will 1316 * generate a new, unique, file name. 1317 * The name of the remote file can be retrieved, after completion of the 1318 * transfer, by calling {@link #getLastFileName()}. 1319 * 1320 * @param name the name of the remote file to write. 1321 * @param unique <code>true</code> if the remote files should be unique, 1322 * in which case the STOU command will be used. 1323 * @return the {@link java.io.OutputStream} from the data connection or 1324 * <code>null</code> if the command was unsuccessful. 1325 * @throws IOException if an error occured during the transmission. 1326 */ putFileStream(String name, boolean unique)1327 public OutputStream putFileStream(String name, boolean unique) 1328 throws sun.net.ftp.FtpProtocolException, IOException 1329 { 1330 String cmd = unique ? "STOU " : "STOR "; 1331 Socket s = openDataConnection(cmd + name); 1332 if (s == null) { 1333 return null; 1334 } 1335 boolean bm = (type == TransferType.BINARY); 1336 return new sun.net.TelnetOutputStream(s.getOutputStream(), bm); 1337 } 1338 1339 /** 1340 * Transfers a file from the client to the server (aka a <I>put</I>) 1341 * by sending the STOR command. The content of the <code>InputStream</code> 1342 * passed in argument is written into the remote file, overwriting any 1343 * existing data. 1344 * 1345 * A new file is created at the server site if the file specified does 1346 * not already exist. 1347 * 1348 * @param name the name of the remote file to write. 1349 * @param local the <code>InputStream</code> that points to the data to 1350 * transfer. 1351 * @param unique <code>true</code> if the remote file should be unique 1352 * (i.e. not already existing), <code>false</code> otherwise. 1353 * @return <code>true</code> if the transfer was successful. 1354 * @throws IOException if an error occured during the transmission. 1355 * @see #getLastFileName() 1356 */ putFile(String name, InputStream local, boolean unique)1357 public sun.net.ftp.FtpClient putFile(String name, InputStream local, boolean unique) throws sun.net.ftp.FtpProtocolException, IOException { 1358 String cmd = unique ? "STOU " : "STOR "; 1359 int mtu = 1500; 1360 if (type == TransferType.BINARY) { 1361 Socket s = openDataConnection(cmd + name); 1362 OutputStream remote = createOutputStream(s.getOutputStream()); 1363 byte[] buf = new byte[mtu * 10]; 1364 int l; 1365 while ((l = local.read(buf)) >= 0) { 1366 if (l > 0) { 1367 remote.write(buf, 0, l); 1368 } 1369 } 1370 remote.close(); 1371 } 1372 return completePending(); 1373 } 1374 1375 /** 1376 * Sends the APPE command to the server in order to transfer a data stream 1377 * passed in argument and append it to the content of the specified remote 1378 * file. 1379 * 1380 * @param name A <code>String</code> containing the name of the remote file 1381 * to append to. 1382 * @param local The <code>InputStream</code> providing access to the data 1383 * to be appended. 1384 * @return <code>true</code> if the transfer was successful. 1385 * @throws IOException if an error occured during the transmission. 1386 */ appendFile(String name, InputStream local)1387 public sun.net.ftp.FtpClient appendFile(String name, InputStream local) throws sun.net.ftp.FtpProtocolException, IOException { 1388 int mtu = 1500; 1389 Socket s = openDataConnection("APPE " + name); 1390 OutputStream remote = createOutputStream(s.getOutputStream()); 1391 byte[] buf = new byte[mtu * 10]; 1392 int l; 1393 while ((l = local.read(buf)) >= 0) { 1394 if (l > 0) { 1395 remote.write(buf, 0, l); 1396 } 1397 } 1398 remote.close(); 1399 return completePending(); 1400 } 1401 1402 /** 1403 * Renames a file on the server. 1404 * 1405 * @param from the name of the file being renamed 1406 * @param to the new name for the file 1407 * @throws IOException if the command fails 1408 */ rename(String from, String to)1409 public sun.net.ftp.FtpClient rename(String from, String to) throws sun.net.ftp.FtpProtocolException, IOException { 1410 issueCommandCheck("RNFR " + from); 1411 issueCommandCheck("RNTO " + to); 1412 return this; 1413 } 1414 1415 /** 1416 * Deletes a file on the server. 1417 * 1418 * @param name a <code>String</code> containing the name of the file 1419 * to delete. 1420 * @return <code>true</code> if the command was successful 1421 * @throws IOException if an error occured during the exchange 1422 */ deleteFile(String name)1423 public sun.net.ftp.FtpClient deleteFile(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1424 issueCommandCheck("DELE " + name); 1425 return this; 1426 } 1427 1428 /** 1429 * Creates a new directory on the server. 1430 * 1431 * @param name a <code>String</code> containing the name of the directory 1432 * to create. 1433 * @return <code>true</code> if the operation was successful. 1434 * @throws IOException if an error occured during the exchange 1435 */ makeDirectory(String name)1436 public sun.net.ftp.FtpClient makeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1437 issueCommandCheck("MKD " + name); 1438 return this; 1439 } 1440 1441 /** 1442 * Removes a directory on the server. 1443 * 1444 * @param name a <code>String</code> containing the name of the directory 1445 * to remove. 1446 * 1447 * @return <code>true</code> if the operation was successful. 1448 * @throws IOException if an error occured during the exchange. 1449 */ removeDirectory(String name)1450 public sun.net.ftp.FtpClient removeDirectory(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1451 issueCommandCheck("RMD " + name); 1452 return this; 1453 } 1454 1455 /** 1456 * Sends a No-operation command. It's useful for testing the connection 1457 * status or as a <I>keep alive</I> mechanism. 1458 * 1459 * @throws FtpProtocolException if the command fails 1460 */ noop()1461 public sun.net.ftp.FtpClient noop() throws sun.net.ftp.FtpProtocolException, IOException { 1462 issueCommandCheck("NOOP"); 1463 return this; 1464 } 1465 1466 /** 1467 * Sends the STAT command to the server. 1468 * This can be used while a data connection is open to get a status 1469 * on the current transfer, in that case the parameter should be 1470 * <code>null</code>. 1471 * If used between file transfers, it may have a pathname as argument 1472 * in which case it will work as the LIST command except no data 1473 * connection will be created. 1474 * 1475 * @param name an optional <code>String</code> containing the pathname 1476 * the STAT command should apply to. 1477 * @return the response from the server or <code>null</code> if the 1478 * command failed. 1479 * @throws IOException if an error occured during the transmission. 1480 */ getStatus(String name)1481 public String getStatus(String name) throws sun.net.ftp.FtpProtocolException, IOException { 1482 issueCommandCheck((name == null ? "STAT" : "STAT " + name)); 1483 /* 1484 * A typical response will be: 1485 * 213-status of t32.gif: 1486 * -rw-r--r-- 1 jcc staff 247445 Feb 17 1998 t32.gif 1487 * 213 End of Status 1488 * 1489 * or 1490 * 1491 * 211-jsn FTP server status: 1492 * Version wu-2.6.2+Sun 1493 * Connected to localhost (::1) 1494 * Logged in as jccollet 1495 * TYPE: ASCII, FORM: Nonprint; STRUcture: File; transfer MODE: Stream 1496 * No data connection 1497 * 0 data bytes received in 0 files 1498 * 0 data bytes transmitted in 0 files 1499 * 0 data bytes total in 0 files 1500 * 53 traffic bytes received in 0 transfers 1501 * 485 traffic bytes transmitted in 0 transfers 1502 * 587 traffic bytes total in 0 transfers 1503 * 211 End of status 1504 * 1505 * So we need to remove the 1st and last line 1506 */ 1507 Vector<String> resp = getResponseStrings(); 1508 StringBuffer sb = new StringBuffer(); 1509 for (int i = 1; i < resp.size() - 1; i++) { 1510 sb.append(resp.get(i)); 1511 } 1512 return sb.toString(); 1513 } 1514 1515 /** 1516 * Sends the FEAT command to the server and returns the list of supported 1517 * features in the form of strings. 1518 * 1519 * The features are the supported commands, like AUTH TLS, PROT or PASV. 1520 * See the RFCs for a complete list. 1521 * 1522 * Note that not all FTP servers support that command, in which case 1523 * the method will return <code>null</code> 1524 * 1525 * @return a <code>List</code> of <code>Strings</code> describing the 1526 * supported additional features, or <code>null</code> 1527 * if the command is not supported. 1528 * @throws IOException if an error occurs during the transmission. 1529 */ getFeatures()1530 public List<String> getFeatures() throws sun.net.ftp.FtpProtocolException, IOException { 1531 /* 1532 * The FEAT command, when implemented will return something like: 1533 * 1534 * 211-Features: 1535 * AUTH TLS 1536 * PBSZ 1537 * PROT 1538 * EPSV 1539 * EPRT 1540 * PASV 1541 * REST STREAM 1542 * 211 END 1543 */ 1544 ArrayList<String> features = new ArrayList<String>(); 1545 issueCommandCheck("FEAT"); 1546 Vector<String> resp = getResponseStrings(); 1547 // Note that we start at index 1 to skip the 1st line (211-...) 1548 // and we stop before the last line. 1549 for (int i = 1; i < resp.size() - 1; i++) { 1550 String s = resp.get(i); 1551 // Get rid of leading space and trailing newline 1552 features.add(s.substring(1, s.length() - 1)); 1553 } 1554 return features; 1555 } 1556 1557 /** 1558 * sends the ABOR command to the server. 1559 * It tells the server to stop the previous command or transfer. 1560 * 1561 * @return <code>true</code> if the command was successful. 1562 * @throws IOException if an error occured during the transmission. 1563 */ abort()1564 public sun.net.ftp.FtpClient abort() throws sun.net.ftp.FtpProtocolException, IOException { 1565 issueCommandCheck("ABOR"); 1566 // TODO: Must check the ReplyCode: 1567 /* 1568 * From the RFC: 1569 * There are two cases for the server upon receipt of this 1570 * command: (1) the FTP service command was already completed, 1571 * or (2) the FTP service command is still in progress. 1572 * In the first case, the server closes the data connection 1573 * (if it is open) and responds with a 226 reply, indicating 1574 * that the abort command was successfully processed. 1575 * In the second case, the server aborts the FTP service in 1576 * progress and closes the data connection, returning a 426 1577 * reply to indicate that the service request terminated 1578 * abnormally. The server then sends a 226 reply, 1579 * indicating that the abort command was successfully 1580 * processed. 1581 */ 1582 1583 1584 return this; 1585 } 1586 1587 /** 1588 * Some methods do not wait until completion before returning, so this 1589 * method can be called to wait until completion. This is typically the case 1590 * with commands that trigger a transfer like {@link #getFileStream(String)}. 1591 * So this method should be called before accessing information related to 1592 * such a command. 1593 * <p>This method will actually block reading on the command channel for a 1594 * notification from the server that the command is finished. Such a 1595 * notification often carries extra information concerning the completion 1596 * of the pending action (e.g. number of bytes transfered).</p> 1597 * <p>Note that this will return true immediately if no command or action 1598 * is pending</p> 1599 * <p>It should be also noted that most methods issuing commands to the ftp 1600 * server will call this method if a previous command is pending. 1601 * <p>Example of use: 1602 * <pre> 1603 * InputStream in = cl.getFileStream("file"); 1604 * ... 1605 * cl.completePending(); 1606 * long size = cl.getLastTransferSize(); 1607 * </pre> 1608 * On the other hand, it's not necessary in a case like: 1609 * <pre> 1610 * InputStream in = cl.getFileStream("file"); 1611 * // read content 1612 * ... 1613 * cl.logout(); 1614 * </pre> 1615 * <p>Since {@link #logout()} will call completePending() if necessary.</p> 1616 * @return <code>true</code> if the completion was successful or if no 1617 * action was pending. 1618 * @throws IOException 1619 */ completePending()1620 public sun.net.ftp.FtpClient completePending() throws sun.net.ftp.FtpProtocolException, IOException { 1621 while (replyPending) { 1622 replyPending = false; 1623 if (!readReply()) { 1624 throw new sun.net.ftp.FtpProtocolException(getLastResponseString(), lastReplyCode); 1625 } 1626 } 1627 return this; 1628 } 1629 1630 /** 1631 * Reinitializes the USER parameters on the FTP server 1632 * 1633 * @throws FtpProtocolException if the command fails 1634 */ reInit()1635 public sun.net.ftp.FtpClient reInit() throws sun.net.ftp.FtpProtocolException, IOException { 1636 issueCommandCheck("REIN"); 1637 loggedIn = false; 1638 if (useCrypto) { 1639 if (server instanceof SSLSocket) { 1640 javax.net.ssl.SSLSession session = ((SSLSocket) server).getSession(); 1641 session.invalidate(); 1642 // Restore previous socket and streams 1643 server = oldSocket; 1644 oldSocket = null; 1645 try { 1646 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 1647 true, encoding); 1648 } catch (UnsupportedEncodingException e) { 1649 throw new InternalError(encoding + "encoding not found"); 1650 } 1651 in = new BufferedInputStream(server.getInputStream()); 1652 } 1653 } 1654 useCrypto = false; 1655 return this; 1656 } 1657 1658 /** 1659 * Changes the transfer type (binary, ascii, ebcdic) and issue the 1660 * proper command (e.g. TYPE A) to the server. 1661 * 1662 * @param type the <code>FtpTransferType</code> to use. 1663 * @return This FtpClient 1664 * @throws IOException if an error occurs during transmission. 1665 */ setType(TransferType type)1666 public sun.net.ftp.FtpClient setType(TransferType type) throws sun.net.ftp.FtpProtocolException, IOException { 1667 String cmd = "NOOP"; 1668 1669 this.type = type; 1670 if (type == TransferType.ASCII) { 1671 cmd = "TYPE A"; 1672 } 1673 if (type == TransferType.BINARY) { 1674 cmd = "TYPE I"; 1675 } 1676 if (type == TransferType.EBCDIC) { 1677 cmd = "TYPE E"; 1678 } 1679 issueCommandCheck(cmd); 1680 return this; 1681 } 1682 1683 /** 1684 * Issues a LIST command to the server to get the current directory 1685 * listing, and returns the InputStream from the data connection. 1686 * {@link #completePending()} <b>has</b> to be called once the application 1687 * is finished writing to the stream. 1688 * 1689 * @param path the pathname of the directory to list, or <code>null</code> 1690 * for the current working directory. 1691 * @return the <code>InputStream</code> from the resulting data connection 1692 * @throws IOException if an error occurs during the transmission. 1693 * @see #changeDirectory(String) 1694 * @see #listFiles(String) 1695 */ list(String path)1696 public InputStream list(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1697 Socket s; 1698 s = openDataConnection(path == null ? "LIST" : "LIST " + path); 1699 if (s != null) { 1700 return createInputStream(s.getInputStream()); 1701 } 1702 return null; 1703 } 1704 1705 /** 1706 * Issues a NLST path command to server to get the specified directory 1707 * content. It differs from {@link #list(String)} method by the fact that 1708 * it will only list the file names which would make the parsing of the 1709 * somewhat easier. 1710 * 1711 * {@link #completePending()} <b>has</b> to be called once the application 1712 * is finished writing to the stream. 1713 * 1714 * @param path a <code>String</code> containing the pathname of the 1715 * directory to list or <code>null</code> for the current working 1716 * directory. 1717 * @return the <code>InputStream</code> from the resulting data connection 1718 * @throws IOException if an error occurs during the transmission. 1719 */ nameList(String path)1720 public InputStream nameList(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1721 Socket s; 1722 s = openDataConnection("NLST " + path); 1723 if (s != null) { 1724 return createInputStream(s.getInputStream()); 1725 } 1726 return null; 1727 } 1728 1729 /** 1730 * Issues the SIZE [path] command to the server to get the size of a 1731 * specific file on the server. 1732 * Note that this command may not be supported by the server. In which 1733 * case -1 will be returned. 1734 * 1735 * @param path a <code>String</code> containing the pathname of the 1736 * file. 1737 * @return a <code>long</code> containing the size of the file or -1 if 1738 * the server returned an error, which can be checked with 1739 * {@link #getLastReplyCode()}. 1740 * @throws IOException if an error occurs during the transmission. 1741 */ getSize(String path)1742 public long getSize(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1743 if (path == null || path.length() == 0) { 1744 throw new IllegalArgumentException("path can't be null or empty"); 1745 } 1746 issueCommandCheck("SIZE " + path); 1747 if (lastReplyCode == FtpReplyCode.FILE_STATUS) { 1748 String s = getResponseString(); 1749 s = s.substring(4, s.length() - 1); 1750 return Long.parseLong(s); 1751 } 1752 return -1; 1753 } 1754 private static String[] MDTMformats = { 1755 "yyyyMMddHHmmss.SSS", 1756 "yyyyMMddHHmmss" 1757 }; 1758 private static SimpleDateFormat[] dateFormats = new SimpleDateFormat[MDTMformats.length]; 1759 1760 static { 1761 for (int i = 0; i < MDTMformats.length; i++) { 1762 dateFormats[i] = new SimpleDateFormat(MDTMformats[i]); 1763 dateFormats[i].setTimeZone(TimeZone.getTimeZone("GMT")); 1764 } 1765 } 1766 1767 /** 1768 * Issues the MDTM [path] command to the server to get the modification 1769 * time of a specific file on the server. 1770 * Note that this command may not be supported by the server, in which 1771 * case <code>null</code> will be returned. 1772 * 1773 * @param path a <code>String</code> containing the pathname of the file. 1774 * @return a <code>Date</code> representing the last modification time 1775 * or <code>null</code> if the server returned an error, which 1776 * can be checked with {@link #getLastReplyCode()}. 1777 * @throws IOException if an error occurs during the transmission. 1778 */ getLastModified(String path)1779 public Date getLastModified(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1780 issueCommandCheck("MDTM " + path); 1781 if (lastReplyCode == FtpReplyCode.FILE_STATUS) { 1782 String s = getResponseString().substring(4); 1783 Date d = null; 1784 for (SimpleDateFormat dateFormat : dateFormats) { 1785 try { 1786 d = dateFormat.parse(s); 1787 } catch (ParseException ex) { 1788 } 1789 if (d != null) { 1790 return d; 1791 } 1792 } 1793 } 1794 return null; 1795 } 1796 1797 /** 1798 * Sets the parser used to handle the directory output to the specified 1799 * one. By default the parser is set to one that can handle most FTP 1800 * servers output (Unix base mostly). However it may be necessary for 1801 * and application to provide its own parser due to some uncommon 1802 * output format. 1803 * 1804 * @param p The <code>FtpDirParser</code> to use. 1805 * @see #listFiles(String) 1806 */ setDirParser(FtpDirParser p)1807 public sun.net.ftp.FtpClient setDirParser(FtpDirParser p) { 1808 parser = p; 1809 return this; 1810 } 1811 1812 private class FtpFileIterator implements Iterator<FtpDirEntry>, Closeable { 1813 1814 private BufferedReader in = null; 1815 private FtpDirEntry nextFile = null; 1816 private FtpDirParser fparser = null; 1817 private boolean eof = false; 1818 FtpFileIterator(FtpDirParser p, BufferedReader in)1819 public FtpFileIterator(FtpDirParser p, BufferedReader in) { 1820 this.in = in; 1821 this.fparser = p; 1822 readNext(); 1823 } 1824 readNext()1825 private void readNext() { 1826 nextFile = null; 1827 if (eof) { 1828 return; 1829 } 1830 String line = null; 1831 try { 1832 do { 1833 line = in.readLine(); 1834 if (line != null) { 1835 nextFile = fparser.parseLine(line); 1836 if (nextFile != null) { 1837 return; 1838 } 1839 } 1840 } while (line != null); 1841 in.close(); 1842 } catch (IOException iOException) { 1843 } 1844 eof = true; 1845 } 1846 hasNext()1847 public boolean hasNext() { 1848 return nextFile != null; 1849 } 1850 next()1851 public FtpDirEntry next() { 1852 FtpDirEntry ret = nextFile; 1853 readNext(); 1854 return ret; 1855 } 1856 remove()1857 public void remove() { 1858 throw new UnsupportedOperationException("Not supported yet."); 1859 } 1860 close()1861 public void close() throws IOException { 1862 if (in != null && !eof) { 1863 in.close(); 1864 } 1865 eof = true; 1866 nextFile = null; 1867 } 1868 } 1869 1870 /** 1871 * Issues a MLSD command to the server to get the specified directory 1872 * listing and applies the current parser to create an Iterator of 1873 * {@link java.net.ftp.FtpDirEntry}. Note that the Iterator returned is also a 1874 * {@link java.io.Closeable}. 1875 * If the server doesn't support the MLSD command, the LIST command is used 1876 * instead. 1877 * 1878 * {@link #completePending()} <b>has</b> to be called once the application 1879 * is finished iterating through the files. 1880 * 1881 * @param path the pathname of the directory to list or <code>null</code> 1882 * for the current working directoty. 1883 * @return a <code>Iterator</code> of files or <code>null</code> if the 1884 * command failed. 1885 * @throws IOException if an error occured during the transmission 1886 * @see #setDirParser(FtpDirParser) 1887 * @see #changeDirectory(String) 1888 */ listFiles(String path)1889 public Iterator<FtpDirEntry> listFiles(String path) throws sun.net.ftp.FtpProtocolException, IOException { 1890 Socket s = null; 1891 BufferedReader sin = null; 1892 try { 1893 s = openDataConnection(path == null ? "MLSD" : "MLSD " + path); 1894 } catch (sun.net.ftp.FtpProtocolException FtpException) { 1895 // The server doesn't understand new MLSD command, ignore and fall 1896 // back to LIST 1897 } 1898 1899 if (s != null) { 1900 sin = new BufferedReader(new InputStreamReader(s.getInputStream())); 1901 return new FtpFileIterator(mlsxParser, sin); 1902 } else { 1903 s = openDataConnection(path == null ? "LIST" : "LIST " + path); 1904 if (s != null) { 1905 sin = new BufferedReader(new InputStreamReader(s.getInputStream())); 1906 return new FtpFileIterator(parser, sin); 1907 } 1908 } 1909 return null; 1910 } 1911 sendSecurityData(byte[] buf)1912 private boolean sendSecurityData(byte[] buf) throws IOException, 1913 sun.net.ftp.FtpProtocolException { 1914 BASE64Encoder encoder = new BASE64Encoder(); 1915 String s = encoder.encode(buf); 1916 return issueCommand("ADAT " + s); 1917 } 1918 getSecurityData()1919 private byte[] getSecurityData() { 1920 String s = getLastResponseString(); 1921 if (s.substring(4, 9).equalsIgnoreCase("ADAT=")) { 1922 BASE64Decoder decoder = new BASE64Decoder(); 1923 try { 1924 // Need to get rid of the leading '315 ADAT=' 1925 // and the trailing newline 1926 return decoder.decodeBuffer(s.substring(9, s.length() - 1)); 1927 } catch (IOException e) { 1928 // 1929 } 1930 } 1931 return null; 1932 } 1933 1934 /** 1935 * Attempts to use Kerberos GSSAPI as an authentication mechanism with the 1936 * ftp server. This will issue an <code>AUTH GSSAPI</code> command, and if 1937 * it is accepted by the server, will followup with <code>ADAT</code> 1938 * command to exchange the various tokens until authentification is 1939 * successful. This conforms to Appendix I of RFC 2228. 1940 * 1941 * @return <code>true</code> if authentication was successful. 1942 * @throws IOException if an error occurs during the transmission. 1943 */ useKerberos()1944 public sun.net.ftp.FtpClient useKerberos() throws sun.net.ftp.FtpProtocolException, IOException { 1945 /* 1946 * Comment out for the moment since it's not in use and would create 1947 * needless cross-package links. 1948 * 1949 issueCommandCheck("AUTH GSSAPI"); 1950 if (lastReplyCode != FtpReplyCode.NEED_ADAT) 1951 throw new sun.net.ftp.FtpProtocolException("Unexpected reply from server"); 1952 try { 1953 GSSManager manager = GSSManager.getInstance(); 1954 GSSName name = manager.createName("SERVICE:ftp@"+ 1955 serverAddr.getHostName(), null); 1956 GSSContext context = manager.createContext(name, null, null, 1957 GSSContext.DEFAULT_LIFETIME); 1958 context.requestMutualAuth(true); 1959 context.requestReplayDet(true); 1960 context.requestSequenceDet(true); 1961 context.requestCredDeleg(true); 1962 byte []inToken = new byte[0]; 1963 while (!context.isEstablished()) { 1964 byte[] outToken 1965 = context.initSecContext(inToken, 0, inToken.length); 1966 // send the output token if generated 1967 if (outToken != null) { 1968 if (sendSecurityData(outToken)) { 1969 inToken = getSecurityData(); 1970 } 1971 } 1972 } 1973 loggedIn = true; 1974 } catch (GSSException e) { 1975 1976 } 1977 */ 1978 return this; 1979 } 1980 1981 /** 1982 * Returns the Welcome string the server sent during initial connection. 1983 * 1984 * @return a <code>String</code> containing the message the server 1985 * returned during connection or <code>null</code>. 1986 */ getWelcomeMsg()1987 public String getWelcomeMsg() { 1988 return welcomeMsg; 1989 } 1990 1991 /** 1992 * Returns the last reply code sent by the server. 1993 * 1994 * @return the lastReplyCode 1995 */ getLastReplyCode()1996 public FtpReplyCode getLastReplyCode() { 1997 return lastReplyCode; 1998 } 1999 2000 /** 2001 * Returns the last response string sent by the server. 2002 * 2003 * @return the message string, which can be quite long, last returned 2004 * by the server. 2005 */ getLastResponseString()2006 public String getLastResponseString() { 2007 StringBuffer sb = new StringBuffer(); 2008 if (serverResponse != null) { 2009 for (String l : serverResponse) { 2010 if (l != null) { 2011 sb.append(l); 2012 } 2013 } 2014 } 2015 return sb.toString(); 2016 } 2017 2018 /** 2019 * Returns, when available, the size of the latest started transfer. 2020 * This is retreived by parsing the response string received as an initial 2021 * response to a RETR or similar request. 2022 * 2023 * @return the size of the latest transfer or -1 if either there was no 2024 * transfer or the information was unavailable. 2025 */ getLastTransferSize()2026 public long getLastTransferSize() { 2027 return lastTransSize; 2028 } 2029 2030 /** 2031 * Returns, when available, the remote name of the last transfered file. 2032 * This is mainly useful for "put" operation when the unique flag was 2033 * set since it allows to recover the unique file name created on the 2034 * server which may be different from the one submitted with the command. 2035 * 2036 * @return the name the latest transfered file remote name, or 2037 * <code>null</code> if that information is unavailable. 2038 */ getLastFileName()2039 public String getLastFileName() { 2040 return lastFileName; 2041 } 2042 2043 /** 2044 * Attempts to switch to a secure, encrypted connection. This is done by 2045 * sending the "AUTH TLS" command. 2046 * <p>See <a href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</a></p> 2047 * If successful this will establish a secure command channel with the 2048 * server, it will also make it so that all other transfers (e.g. a RETR 2049 * command) will be done over an encrypted channel as well unless a 2050 * {@link #reInit()} command or a {@link #endSecureSession()} command is issued. 2051 * 2052 * @return <code>true</code> if the operation was successful. 2053 * @throws IOException if an error occured during the transmission. 2054 * @see #endSecureSession() 2055 */ startSecureSession()2056 public sun.net.ftp.FtpClient startSecureSession() throws sun.net.ftp.FtpProtocolException, IOException { 2057 if (!isConnected()) { 2058 throw new sun.net.ftp.FtpProtocolException("Not connected yet", FtpReplyCode.BAD_SEQUENCE); 2059 } 2060 if (sslFact == null) { 2061 try { 2062 sslFact = (SSLSocketFactory) SSLSocketFactory.getDefault(); 2063 } catch (Exception e) { 2064 throw new IOException(e.getLocalizedMessage()); 2065 } 2066 } 2067 issueCommandCheck("AUTH TLS"); 2068 Socket s = null; 2069 try { 2070 s = sslFact.createSocket(server, serverAddr.getHostName(), serverAddr.getPort(), true); 2071 } catch (javax.net.ssl.SSLException ssle) { 2072 try { 2073 disconnect(); 2074 } catch (Exception e) { 2075 } 2076 throw ssle; 2077 } 2078 // Remember underlying socket so we can restore it later 2079 oldSocket = server; 2080 server = s; 2081 try { 2082 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 2083 true, encoding); 2084 } catch (UnsupportedEncodingException e) { 2085 throw new InternalError(encoding + "encoding not found"); 2086 } 2087 in = new BufferedInputStream(server.getInputStream()); 2088 2089 issueCommandCheck("PBSZ 0"); 2090 issueCommandCheck("PROT P"); 2091 useCrypto = true; 2092 return this; 2093 } 2094 2095 /** 2096 * Sends a <code>CCC</code> command followed by a <code>PROT C</code> 2097 * command to the server terminating an encrypted session and reverting 2098 * back to a non crypted transmission. 2099 * 2100 * @return <code>true</code> if the operation was successful. 2101 * @throws IOException if an error occured during transmission. 2102 * @see #startSecureSession() 2103 */ endSecureSession()2104 public sun.net.ftp.FtpClient endSecureSession() throws sun.net.ftp.FtpProtocolException, IOException { 2105 if (!useCrypto) { 2106 return this; 2107 } 2108 2109 issueCommandCheck("CCC"); 2110 issueCommandCheck("PROT C"); 2111 useCrypto = false; 2112 // Restore previous socket and streams 2113 server = oldSocket; 2114 oldSocket = null; 2115 try { 2116 out = new PrintStream(new BufferedOutputStream(server.getOutputStream()), 2117 true, encoding); 2118 } catch (UnsupportedEncodingException e) { 2119 throw new InternalError(encoding + "encoding not found"); 2120 } 2121 in = new BufferedInputStream(server.getInputStream()); 2122 2123 return this; 2124 } 2125 2126 /** 2127 * Sends the "Allocate" (ALLO) command to the server telling it to 2128 * pre-allocate the specified number of bytes for the next transfer. 2129 * 2130 * @param size The number of bytes to allocate. 2131 * @return <code>true</code> if the operation was successful. 2132 * @throws IOException if an error occured during the transmission. 2133 */ allocate(long size)2134 public sun.net.ftp.FtpClient allocate(long size) throws sun.net.ftp.FtpProtocolException, IOException { 2135 issueCommandCheck("ALLO " + size); 2136 return this; 2137 } 2138 2139 /** 2140 * Sends the "Structure Mount" (SMNT) command to the server. This let the 2141 * user mount a different file system data structure without altering his 2142 * login or accounting information. 2143 * 2144 * @param struct a <code>String</code> containing the name of the 2145 * structure to mount. 2146 * @return <code>true</code> if the operation was successful. 2147 * @throws IOException if an error occured during the transmission. 2148 */ structureMount(String struct)2149 public sun.net.ftp.FtpClient structureMount(String struct) throws sun.net.ftp.FtpProtocolException, IOException { 2150 issueCommandCheck("SMNT " + struct); 2151 return this; 2152 } 2153 2154 /** 2155 * Sends a SYST (System) command to the server and returns the String 2156 * sent back by the server describing the operating system at the 2157 * server. 2158 * 2159 * @return a <code>String</code> describing the OS, or <code>null</code> 2160 * if the operation was not successful. 2161 * @throws IOException if an error occured during the transmission. 2162 */ getSystem()2163 public String getSystem() throws sun.net.ftp.FtpProtocolException, IOException { 2164 issueCommandCheck("SYST"); 2165 /* 2166 * 215 UNIX Type: L8 Version: SUNOS 2167 */ 2168 String resp = getResponseString(); 2169 // Get rid of the leading code and blank 2170 return resp.substring(4); 2171 } 2172 2173 /** 2174 * Sends the HELP command to the server, with an optional command, like 2175 * SITE, and returns the text sent back by the server. 2176 * 2177 * @param cmd the command for which the help is requested or 2178 * <code>null</code> for the general help 2179 * @return a <code>String</code> containing the text sent back by the 2180 * server, or <code>null</code> if the command failed. 2181 * @throws IOException if an error occured during transmission 2182 */ getHelp(String cmd)2183 public String getHelp(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 2184 issueCommandCheck("HELP " + cmd); 2185 /** 2186 * 2187 * HELP 2188 * 214-The following commands are implemented. 2189 * USER EPRT STRU ALLO DELE SYST RMD MDTM ADAT 2190 * PASS EPSV MODE REST CWD STAT PWD PROT 2191 * QUIT LPRT RETR RNFR LIST HELP CDUP PBSZ 2192 * PORT LPSV STOR RNTO NLST NOOP STOU AUTH 2193 * PASV TYPE APPE ABOR SITE MKD SIZE CCC 2194 * 214 Direct comments to ftp-bugs@jsn. 2195 * 2196 * HELP SITE 2197 * 214-The following SITE commands are implemented. 2198 * UMASK HELP GROUPS 2199 * IDLE ALIAS CHECKMETHOD 2200 * CHMOD CDPATH CHECKSUM 2201 * 214 Direct comments to ftp-bugs@jsn. 2202 */ 2203 Vector<String> resp = getResponseStrings(); 2204 if (resp.size() == 1) { 2205 // Single line response 2206 return resp.get(0).substring(4); 2207 } 2208 // on multiple lines answers, like the ones above, remove 1st and last 2209 // line, concat the the others. 2210 StringBuffer sb = new StringBuffer(); 2211 for (int i = 1; i < resp.size() - 1; i++) { 2212 sb.append(resp.get(i).substring(3)); 2213 } 2214 return sb.toString(); 2215 } 2216 2217 /** 2218 * Sends the SITE command to the server. This is used by the server 2219 * to provide services specific to his system that are essential 2220 * to file transfer. 2221 * 2222 * @param cmd the command to be sent. 2223 * @return <code>true</code> if the command was successful. 2224 * @throws IOException if an error occured during transmission 2225 */ siteCmd(String cmd)2226 public sun.net.ftp.FtpClient siteCmd(String cmd) throws sun.net.ftp.FtpProtocolException, IOException { 2227 issueCommandCheck("SITE " + cmd); 2228 return this; 2229 } 2230 } 2231