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