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