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