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