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