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