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