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