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