1 /* FTPConnection.java -- 2 Copyright (C) 2003, 2004 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package gnu.java.net.protocol.ftp; 40 41 import gnu.java.net.CRLFInputStream; 42 import gnu.java.net.CRLFOutputStream; 43 import gnu.java.net.EmptyX509TrustManager; 44 import gnu.java.net.LineInputStream; 45 46 import java.io.BufferedInputStream; 47 import java.io.BufferedOutputStream; 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.io.OutputStream; 51 import java.net.BindException; 52 import java.net.InetAddress; 53 import java.net.InetSocketAddress; 54 import java.net.ProtocolException; 55 import java.net.Socket; 56 import java.net.UnknownHostException; 57 import java.security.GeneralSecurityException; 58 import java.util.ArrayList; 59 import java.util.List; 60 61 import javax.net.ssl.SSLContext; 62 import javax.net.ssl.SSLSocket; 63 import javax.net.ssl.SSLSocketFactory; 64 import javax.net.ssl.TrustManager; 65 66 /** 67 * An FTP client connection, or PI. 68 * This implements RFC 959, with the following exceptions: 69 * <ul> 70 * <li>STAT, HELP, SITE, SMNT, and ACCT commands are not supported.</li> 71 * <li>the TYPE command does not allow alternatives to the default bytesize 72 * (Non-print), and local bytesize is not supported.</li> 73 * </ul> 74 * 75 * @author Chris Burdess (dog@gnu.org) 76 */ 77 public class FTPConnection 78 { 79 80 /** 81 * The default FTP transmission control port. 82 */ 83 public static final int FTP_PORT = 21; 84 85 /** 86 * The FTP data port. 87 */ 88 public static final int FTP_DATA_PORT = 20; 89 90 // -- FTP vocabulary -- 91 protected static final String USER = "USER"; 92 protected static final String PASS = "PASS"; 93 protected static final String ACCT = "ACCT"; 94 protected static final String CWD = "CWD"; 95 protected static final String CDUP = "CDUP"; 96 protected static final String SMNT = "SMNT"; 97 protected static final String REIN = "REIN"; 98 protected static final String QUIT = "QUIT"; 99 100 protected static final String PORT = "PORT"; 101 protected static final String PASV = "PASV"; 102 protected static final String TYPE = "TYPE"; 103 protected static final String STRU = "STRU"; 104 protected static final String MODE = "MODE"; 105 106 protected static final String RETR = "RETR"; 107 protected static final String STOR = "STOR"; 108 protected static final String STOU = "STOU"; 109 protected static final String APPE = "APPE"; 110 protected static final String ALLO = "ALLO"; 111 protected static final String REST = "REST"; 112 protected static final String RNFR = "RNFR"; 113 protected static final String RNTO = "RNTO"; 114 protected static final String ABOR = "ABOR"; 115 protected static final String DELE = "DELE"; 116 protected static final String RMD = "RMD"; 117 protected static final String MKD = "MKD"; 118 protected static final String PWD = "PWD"; 119 protected static final String LIST = "LIST"; 120 protected static final String NLST = "NLST"; 121 protected static final String SITE = "SITE"; 122 protected static final String SYST = "SYST"; 123 protected static final String STAT = "STAT"; 124 protected static final String HELP = "HELP"; 125 protected static final String NOOP = "NOOP"; 126 127 protected static final String AUTH = "AUTH"; 128 protected static final String PBSZ = "PBSZ"; 129 protected static final String PROT = "PROT"; 130 protected static final String CCC = "CCC"; 131 protected static final String TLS = "TLS"; 132 133 public static final int TYPE_ASCII = 1; 134 public static final int TYPE_EBCDIC = 2; 135 public static final int TYPE_BINARY = 3; 136 137 public static final int STRUCTURE_FILE = 1; 138 public static final int STRUCTURE_RECORD = 2; 139 public static final int STRUCTURE_PAGE = 3; 140 141 public static final int MODE_STREAM = 1; 142 public static final int MODE_BLOCK = 2; 143 public static final int MODE_COMPRESSED = 3; 144 145 // -- Telnet constants -- 146 private static final String US_ASCII = "US-ASCII"; 147 148 /** 149 * The socket used to communicate with the server. 150 */ 151 protected Socket socket; 152 153 /** 154 * The socket input stream. 155 */ 156 protected LineInputStream in; 157 158 /** 159 * The socket output stream. 160 */ 161 protected CRLFOutputStream out; 162 163 /** 164 * The timeout when attempting to connect a socket. 165 */ 166 protected int connectionTimeout; 167 168 /** 169 * The read timeout on sockets. 170 */ 171 protected int timeout; 172 173 /** 174 * If true, print debugging information. 175 */ 176 protected boolean debug; 177 178 /** 179 * The current data transfer process in use by this connection. 180 */ 181 protected DTP dtp; 182 183 /** 184 * The current representation type. 185 */ 186 protected int representationType = TYPE_ASCII; 187 188 /** 189 * The current file structure type. 190 */ 191 protected int fileStructure = STRUCTURE_FILE; 192 193 /** 194 * The current transfer mode. 195 */ 196 protected int transferMode = MODE_STREAM; 197 198 /** 199 * If true, use passive mode. 200 */ 201 protected boolean passive = false; 202 203 /** 204 * Creates a new connection to the server using the default port. 205 * @param hostname the hostname of the server to connect to 206 */ FTPConnection(String hostname)207 public FTPConnection(String hostname) 208 throws UnknownHostException, IOException 209 { 210 this(hostname, -1, 0, 0, false); 211 } 212 213 /** 214 * Creates a new connection to the server. 215 * @param hostname the hostname of the server to connect to 216 * @param port the port to connect to(if <=0, use default port) 217 */ FTPConnection(String hostname, int port)218 public FTPConnection(String hostname, int port) 219 throws UnknownHostException, IOException 220 { 221 this(hostname, port, 0, 0, false); 222 } 223 224 /** 225 * Creates a new connection to the server. 226 * @param hostname the hostname of the server to connect to 227 * @param port the port to connect to(if <=0, use default port) 228 * @param connectionTimeout the connection timeout, in milliseconds 229 * @param timeout the I/O timeout, in milliseconds 230 * @param debug print debugging information 231 */ FTPConnection(String hostname, int port, int connectionTimeout, int timeout, boolean debug)232 public FTPConnection(String hostname, int port, 233 int connectionTimeout, int timeout, boolean debug) 234 throws UnknownHostException, IOException 235 { 236 this.connectionTimeout = connectionTimeout; 237 this.timeout = timeout; 238 this.debug = debug; 239 if (port <= 0) 240 { 241 port = FTP_PORT; 242 } 243 244 // Set up socket 245 socket = new Socket(); 246 InetSocketAddress address = new InetSocketAddress(hostname, port); 247 if (connectionTimeout > 0) 248 { 249 socket.connect(address, connectionTimeout); 250 } 251 else 252 { 253 socket.connect(address); 254 } 255 if (timeout > 0) 256 { 257 socket.setSoTimeout(timeout); 258 } 259 260 InputStream in = socket.getInputStream(); 261 in = new BufferedInputStream(in); 262 in = new CRLFInputStream(in); 263 this.in = new LineInputStream(in); 264 OutputStream out = socket.getOutputStream(); 265 out = new BufferedOutputStream(out); 266 this.out = new CRLFOutputStream(out); 267 268 // Read greeting 269 FTPResponse response = getResponse(); 270 switch (response.getCode()) 271 { 272 case 220: // hello 273 break; 274 default: 275 throw new FTPException(response); 276 } 277 } 278 279 /** 280 * Authenticate using the specified username and password. 281 * If the username suffices for the server, the password will not be used 282 * and may be null. 283 * @param username the username 284 * @param password the optional password 285 * @return true on success, false otherwise 286 */ authenticate(String username, String password)287 public boolean authenticate(String username, String password) 288 throws IOException 289 { 290 String cmd = USER + ' ' + username; 291 send(cmd); 292 FTPResponse response = getResponse(); 293 switch (response.getCode()) 294 { 295 case 230: // User logged in 296 return true; 297 case 331: // User name okay, need password 298 break; 299 case 332: // Need account for login 300 case 530: // No such user 301 return false; 302 default: 303 throw new FTPException(response); 304 } 305 cmd = PASS + ' ' + password; 306 send(cmd); 307 response = getResponse(); 308 switch (response.getCode()) 309 { 310 case 230: // User logged in 311 case 202: // Superfluous 312 return true; 313 case 332: // Need account for login 314 case 530: // Bad password 315 return false; 316 default: 317 throw new FTPException(response); 318 } 319 } 320 321 /** 322 * Negotiates TLS over the current connection. 323 * See IETF draft-murray-auth-ftp-ssl-15.txt for details. 324 * @param confidential whether to provide confidentiality for the 325 * connection 326 */ starttls(boolean confidential)327 public boolean starttls(boolean confidential) 328 throws IOException 329 { 330 return starttls(confidential, new EmptyX509TrustManager()); 331 } 332 333 /** 334 * Negotiates TLS over the current connection. 335 * See IETF draft-murray-auth-ftp-ssl-15.txt for details. 336 * @param confidential whether to provide confidentiality for the 337 * connection 338 * @param tm the trust manager used to validate the server certificate. 339 */ starttls(boolean confidential, TrustManager tm)340 public boolean starttls(boolean confidential, TrustManager tm) 341 throws IOException 342 { 343 try 344 { 345 // Use SSLSocketFactory to negotiate a TLS session and wrap the 346 // current socket. 347 SSLContext context = SSLContext.getInstance("TLS"); 348 // We don't require strong validation of the server certificate 349 TrustManager[] trust = new TrustManager[] { tm }; 350 context.init(null, trust, null); 351 SSLSocketFactory factory = context.getSocketFactory(); 352 353 send(AUTH + ' ' + TLS); 354 FTPResponse response = getResponse(); 355 switch (response.getCode()) 356 { 357 case 500: 358 case 502: 359 case 504: 360 case 534: 361 case 431: 362 return false; 363 case 234: 364 break; 365 default: 366 throw new FTPException(response); 367 } 368 369 String hostname = socket.getInetAddress().getHostName(); 370 int port = socket.getPort(); 371 SSLSocket ss = 372 (SSLSocket) factory.createSocket(socket, hostname, port, true); 373 String[] protocols = { "TLSv1", "SSLv3" }; 374 ss.setEnabledProtocols(protocols); 375 ss.setUseClientMode(true); 376 ss.startHandshake(); 377 378 // PBSZ:PROT sequence 379 send(PBSZ + ' ' + Integer.MAX_VALUE); 380 response = getResponse(); 381 switch (response.getCode()) 382 { 383 case 501: // syntax error 384 case 503: // not authenticated 385 return false; 386 case 200: 387 break; 388 default: 389 throw new FTPException(response); 390 } 391 send(PROT + ' ' +(confidential ? 'P' : 'C')); 392 response = getResponse(); 393 switch (response.getCode()) 394 { 395 case 503: // not authenticated 396 case 504: // invalid level 397 case 536: // level not supported 398 return false; 399 case 200: 400 break; 401 default: 402 throw new FTPException(response); 403 } 404 405 if (confidential) 406 { 407 // Set up streams 408 InputStream in = ss.getInputStream(); 409 in = new BufferedInputStream(in); 410 in = new CRLFInputStream(in); 411 this.in = new LineInputStream(in); 412 OutputStream out = ss.getOutputStream(); 413 out = new BufferedOutputStream(out); 414 this.out = new CRLFOutputStream(out); 415 } 416 return true; 417 } 418 catch (GeneralSecurityException e) 419 { 420 return false; 421 } 422 } 423 424 /** 425 * Changes directory to the specified path. 426 * @param path an absolute or relative pathname 427 * @return true on success, false if the specified path does not exist 428 */ changeWorkingDirectory(String path)429 public boolean changeWorkingDirectory(String path) 430 throws IOException 431 { 432 String cmd = CWD + ' ' + path; 433 send(cmd); 434 FTPResponse response = getResponse(); 435 switch (response.getCode()) 436 { 437 case 250: 438 return true; 439 case 550: 440 return false; 441 default: 442 throw new FTPException(response); 443 } 444 } 445 446 /** 447 * Changes directory to the parent of the current working directory. 448 * @return true on success, false otherwise 449 */ changeToParentDirectory()450 public boolean changeToParentDirectory() 451 throws IOException 452 { 453 send(CDUP); 454 FTPResponse response = getResponse(); 455 switch (response.getCode()) 456 { 457 case 250: 458 return true; 459 case 550: 460 return false; 461 default: 462 throw new FTPException(response); 463 } 464 } 465 466 /** 467 * Terminates an authenticated login. 468 * If file transfer is in progress, it remains active for result response 469 * only. 470 */ reinitialize()471 public void reinitialize() 472 throws IOException 473 { 474 send(REIN); 475 FTPResponse response = getResponse(); 476 switch (response.getCode()) 477 { 478 case 220: 479 if (dtp != null) 480 { 481 dtp.complete(); 482 dtp = null; 483 } 484 break; 485 default: 486 throw new FTPException(response); 487 } 488 } 489 490 /** 491 * Terminates the control connection. 492 * The file transfer connection remains open for result response only. 493 * This connection is invalid and no further commands may be issued. 494 */ logout()495 public void logout() 496 throws IOException 497 { 498 send(QUIT); 499 try 500 { 501 getResponse(); // not required 502 } 503 catch (IOException e) 504 { 505 } 506 if (dtp != null) 507 { 508 dtp.complete(); 509 dtp = null; 510 } 511 try 512 { 513 socket.close(); 514 } 515 catch (IOException e) 516 { 517 } 518 } 519 520 /** 521 * Initialise the data transfer process. 522 */ initialiseDTP()523 protected void initialiseDTP() 524 throws IOException 525 { 526 if (dtp != null) 527 { 528 dtp.complete(); 529 dtp = null; 530 } 531 532 InetAddress localhost = socket.getLocalAddress(); 533 if (passive) 534 { 535 send(PASV); 536 FTPResponse response = getResponse(); 537 switch (response.getCode()) 538 { 539 case 227: 540 String message = response.getMessage(); 541 try 542 { 543 int start = message.indexOf(','); 544 char c = message.charAt(start - 1); 545 while (c >= 0x30 && c <= 0x39) 546 { 547 c = message.charAt((--start) - 1); 548 } 549 int mid1 = start; 550 for (int i = 0; i < 4; i++) 551 { 552 mid1 = message.indexOf(',', mid1 + 1); 553 } 554 int mid2 = message.indexOf(',', mid1 + 1); 555 if (mid1 == -1 || mid2 < mid1) 556 { 557 throw new ProtocolException("Malformed 227: " + 558 message); 559 } 560 int end = mid2; 561 c = message.charAt(end + 1); 562 while (c >= 0x30 && c <= 0x39) 563 { 564 c = message.charAt((++end) + 1); 565 } 566 567 String address = 568 message.substring(start, mid1).replace(',', '.'); 569 int port_hi = 570 Integer.parseInt(message.substring(mid1 + 1, mid2)); 571 int port_lo = 572 Integer.parseInt(message.substring(mid2 + 1, end + 1)); 573 int port = (port_hi << 8) | port_lo; 574 575 /*System.out.println("Entering passive mode: " + address + 576 ":" + port);*/ 577 dtp = new PassiveModeDTP(address, port, localhost, 578 connectionTimeout, timeout); 579 break; 580 } 581 catch (ArrayIndexOutOfBoundsException e) 582 { 583 throw new ProtocolException(e.getMessage() + ": " + 584 message); 585 } 586 catch (NumberFormatException e) 587 { 588 throw new ProtocolException(e.getMessage() + ": " + 589 message); 590 } 591 default: 592 throw new FTPException(response); 593 } 594 } 595 else 596 { 597 // Get the local port 598 int port = socket.getLocalPort() + 1; 599 int tries = 0; 600 // Bind the active mode DTP 601 while (dtp == null) 602 { 603 try 604 { 605 dtp = new ActiveModeDTP(localhost, port, 606 connectionTimeout, timeout); 607 /*System.out.println("Listening on: " + port);*/ 608 } 609 catch (BindException e) 610 { 611 port++; 612 tries++; 613 if (tries > 9) 614 { 615 throw e; 616 } 617 } 618 } 619 620 // Send PORT command 621 StringBuffer buf = new StringBuffer(PORT); 622 buf.append(' '); 623 // Construct the address/port string form 624 byte[] address = localhost.getAddress(); 625 for (int i = 0; i < address.length; i++) 626 { 627 int a =(int) address[i]; 628 if (a < 0) 629 { 630 a += 0x100; 631 } 632 buf.append(a); 633 buf.append(','); 634 } 635 int port_hi =(port & 0xff00) >> 8; 636 int port_lo =(port & 0x00ff); 637 buf.append(port_hi); 638 buf.append(','); 639 buf.append(port_lo); 640 send(buf.toString()); 641 // Get response 642 FTPResponse response = getResponse(); 643 switch (response.getCode()) 644 { 645 case 200: // OK 646 break; 647 default: 648 dtp.abort(); 649 dtp = null; 650 throw new FTPException(response); 651 } 652 } 653 dtp.setTransferMode(transferMode); 654 } 655 656 /** 657 * Set passive mode. 658 * @param flag true if we should use passive mode, false otherwise 659 */ setPassive(boolean flag)660 public void setPassive(boolean flag) 661 throws IOException 662 { 663 if (passive != flag) 664 { 665 passive = flag; 666 initialiseDTP(); 667 } 668 } 669 670 /** 671 * Returns the current representation type of the transfer data. 672 * @return TYPE_ASCII, TYPE_EBCDIC, or TYPE_BINARY 673 */ getRepresentationType()674 public int getRepresentationType() 675 { 676 return representationType; 677 } 678 679 /** 680 * Sets the desired representation type of the transfer data. 681 * @param type TYPE_ASCII, TYPE_EBCDIC, or TYPE_BINARY 682 */ setRepresentationType(int type)683 public void setRepresentationType(int type) 684 throws IOException 685 { 686 StringBuffer buf = new StringBuffer(TYPE); 687 buf.append(' '); 688 switch (type) 689 { 690 case TYPE_ASCII: 691 buf.append('A'); 692 break; 693 case TYPE_EBCDIC: 694 buf.append('E'); 695 break; 696 case TYPE_BINARY: 697 buf.append('I'); 698 break; 699 default: 700 throw new IllegalArgumentException(Integer.toString(type)); 701 } 702 //buf.append(' '); 703 //buf.append('N'); 704 send(buf.toString()); 705 FTPResponse response = getResponse(); 706 switch (response.getCode()) 707 { 708 case 200: 709 representationType = type; 710 break; 711 default: 712 throw new FTPException(response); 713 } 714 } 715 716 /** 717 * Returns the current file structure type. 718 * @return STRUCTURE_FILE, STRUCTURE_RECORD, or STRUCTURE_PAGE 719 */ getFileStructure()720 public int getFileStructure() 721 { 722 return fileStructure; 723 } 724 725 /** 726 * Sets the desired file structure type. 727 * @param structure STRUCTURE_FILE, STRUCTURE_RECORD, or STRUCTURE_PAGE 728 */ setFileStructure(int structure)729 public void setFileStructure(int structure) 730 throws IOException 731 { 732 StringBuffer buf = new StringBuffer(STRU); 733 buf.append(' '); 734 switch (structure) 735 { 736 case STRUCTURE_FILE: 737 buf.append('F'); 738 break; 739 case STRUCTURE_RECORD: 740 buf.append('R'); 741 break; 742 case STRUCTURE_PAGE: 743 buf.append('P'); 744 break; 745 default: 746 throw new IllegalArgumentException(Integer.toString(structure)); 747 } 748 send(buf.toString()); 749 FTPResponse response = getResponse(); 750 switch (response.getCode()) 751 { 752 case 200: 753 fileStructure = structure; 754 break; 755 default: 756 throw new FTPException(response); 757 } 758 } 759 760 /** 761 * Returns the current transfer mode. 762 * @return MODE_STREAM, MODE_BLOCK, or MODE_COMPRESSED 763 */ getTransferMode()764 public int getTransferMode() 765 { 766 return transferMode; 767 } 768 769 /** 770 * Sets the desired transfer mode. 771 * @param mode MODE_STREAM, MODE_BLOCK, or MODE_COMPRESSED 772 */ setTransferMode(int mode)773 public void setTransferMode(int mode) 774 throws IOException 775 { 776 StringBuffer buf = new StringBuffer(MODE); 777 buf.append(' '); 778 switch (mode) 779 { 780 case MODE_STREAM: 781 buf.append('S'); 782 break; 783 case MODE_BLOCK: 784 buf.append('B'); 785 break; 786 case MODE_COMPRESSED: 787 buf.append('C'); 788 break; 789 default: 790 throw new IllegalArgumentException(Integer.toString(mode)); 791 } 792 send(buf.toString()); 793 FTPResponse response = getResponse(); 794 switch (response.getCode()) 795 { 796 case 200: 797 transferMode = mode; 798 if (dtp != null) 799 { 800 dtp.setTransferMode(mode); 801 } 802 break; 803 default: 804 throw new FTPException(response); 805 } 806 } 807 808 /** 809 * Retrieves the specified file. 810 * @param filename the filename of the file to retrieve 811 * @return an InputStream containing the file content 812 */ retrieve(String filename)813 public InputStream retrieve(String filename) 814 throws IOException 815 { 816 if (dtp == null || transferMode == MODE_STREAM) 817 { 818 initialiseDTP(); 819 } 820 /* 821 int size = -1; 822 String cmd = SIZE + ' ' + filename; 823 send(cmd); 824 FTPResponse response = getResponse(); 825 switch (response.getCode()) 826 { 827 case 213: 828 size = Integer.parseInt(response.getMessage()); 829 break; 830 case 550: // File not found 831 default: 832 throw new FTPException(response); 833 } 834 */ 835 String cmd = RETR + ' ' + filename; 836 send(cmd); 837 FTPResponse response = getResponse(); 838 switch (response.getCode()) 839 { 840 case 125: // Data connection already open; transfer starting 841 case 150: // File status okay; about to open data connection 842 return dtp.getInputStream(); 843 default: 844 throw new FTPException(response); 845 } 846 } 847 848 /** 849 * Returns a stream for uploading a file. 850 * If a file with the same filename already exists on the server, it will 851 * be overwritten. 852 * @param filename the name of the file to save the content as 853 * @return an OutputStream to write the file data to 854 */ store(String filename)855 public OutputStream store(String filename) 856 throws IOException 857 { 858 if (dtp == null || transferMode == MODE_STREAM) 859 { 860 initialiseDTP(); 861 } 862 String cmd = STOR + ' ' + filename; 863 send(cmd); 864 FTPResponse response = getResponse(); 865 switch (response.getCode()) 866 { 867 case 125: // Data connection already open; transfer starting 868 case 150: // File status okay; about to open data connection 869 return dtp.getOutputStream(); 870 default: 871 throw new FTPException(response); 872 } 873 } 874 875 /** 876 * Returns a stream for uploading a file. 877 * If a file with the same filename already exists on the server, the 878 * content specified will be appended to the existing file. 879 * @param filename the name of the file to save the content as 880 * @return an OutputStream to write the file data to 881 */ append(String filename)882 public OutputStream append(String filename) 883 throws IOException 884 { 885 if (dtp == null || transferMode == MODE_STREAM) 886 { 887 initialiseDTP(); 888 } 889 String cmd = APPE + ' ' + filename; 890 send(cmd); 891 FTPResponse response = getResponse(); 892 switch (response.getCode()) 893 { 894 case 125: // Data connection already open; transfer starting 895 case 150: // File status okay; about to open data connection 896 return dtp.getOutputStream(); 897 default: 898 throw new FTPException(response); 899 } 900 } 901 902 /** 903 * This command may be required by some servers to reserve sufficient 904 * storage to accommodate the new file to be transferred. 905 * It should be immediately followed by a <code>store</code> or 906 * <code>append</code>. 907 * @param size the number of bytes of storage to allocate 908 */ allocate(long size)909 public void allocate(long size) 910 throws IOException 911 { 912 String cmd = ALLO + ' ' + size; 913 send(cmd); 914 FTPResponse response = getResponse(); 915 switch (response.getCode()) 916 { 917 case 200: // OK 918 case 202: // Superfluous 919 break; 920 default: 921 throw new FTPException(response); 922 } 923 } 924 925 /** 926 * Renames a file. 927 * @param oldName the current name of the file 928 * @param newName the new name 929 * @return true if successful, false otherwise 930 */ rename(String oldName, String newName)931 public boolean rename(String oldName, String newName) 932 throws IOException 933 { 934 String cmd = RNFR + ' ' + oldName; 935 send(cmd); 936 FTPResponse response = getResponse(); 937 switch (response.getCode()) 938 { 939 case 450: // File unavailable 940 case 550: // File not found 941 return false; 942 case 350: // Pending 943 break; 944 default: 945 throw new FTPException(response); 946 } 947 cmd = RNTO + ' ' + newName; 948 send(cmd); 949 response = getResponse(); 950 switch (response.getCode()) 951 { 952 case 250: // OK 953 return true; 954 case 450: 955 case 550: 956 return false; 957 default: 958 throw new FTPException(response); 959 } 960 } 961 962 /** 963 * Aborts the transfer in progress. 964 * @return true if a transfer was in progress, false otherwise 965 */ abort()966 public boolean abort() 967 throws IOException 968 { 969 send(ABOR); 970 FTPResponse response = getResponse(); 971 // Abort client DTP 972 if (dtp != null) 973 { 974 dtp.abort(); 975 } 976 switch (response.getCode()) 977 { 978 case 226: // successful abort 979 return false; 980 case 426: // interrupted 981 response = getResponse(); 982 if (response.getCode() == 226) 983 { 984 return true; 985 } 986 // Otherwise fall through to throw exception 987 default: 988 throw new FTPException(response); 989 } 990 } 991 992 /** 993 * Causes the file specified to be deleted at the server site. 994 * @param filename the file to delete 995 */ delete(String filename)996 public boolean delete(String filename) 997 throws IOException 998 { 999 String cmd = DELE + ' ' + filename; 1000 send(cmd); 1001 FTPResponse response = getResponse(); 1002 switch (response.getCode()) 1003 { 1004 case 250: // OK 1005 return true; 1006 case 450: // File unavailable 1007 case 550: // File not found 1008 return false; 1009 default: 1010 throw new FTPException(response); 1011 } 1012 } 1013 1014 /** 1015 * Causes the directory specified to be deleted. 1016 * This may be an absolute or relative pathname. 1017 * @param pathname the directory to delete 1018 */ removeDirectory(String pathname)1019 public boolean removeDirectory(String pathname) 1020 throws IOException 1021 { 1022 String cmd = RMD + ' ' + pathname; 1023 send(cmd); 1024 FTPResponse response = getResponse(); 1025 switch (response.getCode()) 1026 { 1027 case 250: // OK 1028 return true; 1029 case 550: // File not found 1030 return false; 1031 default: 1032 throw new FTPException(response); 1033 } 1034 } 1035 1036 /** 1037 * Causes the directory specified to be created at the server site. 1038 * This may be an absolute or relative pathname. 1039 * @param pathname the directory to create 1040 */ makeDirectory(String pathname)1041 public boolean makeDirectory(String pathname) 1042 throws IOException 1043 { 1044 String cmd = MKD + ' ' + pathname; 1045 send(cmd); 1046 FTPResponse response = getResponse(); 1047 switch (response.getCode()) 1048 { 1049 case 257: // Directory created 1050 return true; 1051 case 550: // File not found 1052 return false; 1053 default: 1054 throw new FTPException(response); 1055 } 1056 } 1057 1058 /** 1059 * Returns the current working directory. 1060 */ getWorkingDirectory()1061 public String getWorkingDirectory() 1062 throws IOException 1063 { 1064 send(PWD); 1065 FTPResponse response = getResponse(); 1066 switch (response.getCode()) 1067 { 1068 case 257: 1069 String message = response.getMessage(); 1070 if (message.charAt(0) == '"') 1071 { 1072 int end = message.indexOf('"', 1); 1073 if (end == -1) 1074 { 1075 throw new ProtocolException(message); 1076 } 1077 return message.substring(1, end); 1078 } 1079 else 1080 { 1081 int end = message.indexOf(' '); 1082 if (end == -1) 1083 { 1084 return message; 1085 } 1086 else 1087 { 1088 return message.substring(0, end); 1089 } 1090 } 1091 default: 1092 throw new FTPException(response); 1093 } 1094 } 1095 1096 /** 1097 * Returns a listing of information about the specified pathname. 1098 * If the pathname specifies a directory or other group of files, the 1099 * server should transfer a list of files in the specified directory. 1100 * If the pathname specifies a file then the server should send current 1101 * information on the file. A null argument implies the user's 1102 * current working or default directory. 1103 * @param pathname the context pathname, or null 1104 */ list(String pathname)1105 public InputStream list(String pathname) 1106 throws IOException 1107 { 1108 if (dtp == null || transferMode == MODE_STREAM) 1109 { 1110 initialiseDTP(); 1111 } 1112 if (pathname == null) 1113 { 1114 send(LIST); 1115 } 1116 else 1117 { 1118 String cmd = LIST + ' ' + pathname; 1119 send(cmd); 1120 } 1121 FTPResponse response = getResponse(); 1122 switch (response.getCode()) 1123 { 1124 case 125: // Data connection already open; transfer starting 1125 case 150: // File status okay; about to open data connection 1126 return dtp.getInputStream(); 1127 default: 1128 throw new FTPException(response); 1129 } 1130 } 1131 1132 /** 1133 * Returns a directory listing. The pathname should specify a 1134 * directory or other system-specific file group descriptor; a null 1135 * argument implies the user's current working or default directory. 1136 * @param pathname the directory pathname, or null 1137 * @return a list of filenames(strings) 1138 */ nameList(String pathname)1139 public List nameList(String pathname) 1140 throws IOException 1141 { 1142 if (dtp == null || transferMode == MODE_STREAM) 1143 { 1144 initialiseDTP(); 1145 } 1146 if (pathname == null) 1147 { 1148 send(NLST); 1149 } 1150 else 1151 { 1152 String cmd = NLST + ' ' + pathname; 1153 send(cmd); 1154 } 1155 FTPResponse response = getResponse(); 1156 switch (response.getCode()) 1157 { 1158 case 125: // Data connection already open; transfer starting 1159 case 150: // File status okay; about to open data connection 1160 InputStream in = dtp.getInputStream(); 1161 in = new BufferedInputStream(in); 1162 in = new CRLFInputStream(in); // TODO ensure that TYPE is correct 1163 LineInputStream li = new LineInputStream(in); 1164 List ret = new ArrayList(); 1165 for (String line = li.readLine(); 1166 line != null; 1167 line = li.readLine()) 1168 { 1169 ret.add(line); 1170 } 1171 li.close(); 1172 return ret; 1173 default: 1174 throw new FTPException(response); 1175 } 1176 } 1177 1178 /** 1179 * Returns the type of operating system at the server. 1180 */ system()1181 public String system() 1182 throws IOException 1183 { 1184 send(SYST); 1185 FTPResponse response = getResponse(); 1186 switch (response.getCode()) 1187 { 1188 case 215: 1189 String message = response.getMessage(); 1190 int end = message.indexOf(' '); 1191 if (end == -1) 1192 { 1193 return message; 1194 } 1195 else 1196 { 1197 return message.substring(0, end); 1198 } 1199 default: 1200 throw new FTPException(response); 1201 } 1202 } 1203 1204 /** 1205 * Does nothing. 1206 * This method can be used to ensure that the connection does not time 1207 * out. 1208 */ noop()1209 public void noop() 1210 throws IOException 1211 { 1212 send(NOOP); 1213 FTPResponse response = getResponse(); 1214 switch (response.getCode()) 1215 { 1216 case 200: 1217 break; 1218 default: 1219 throw new FTPException(response); 1220 } 1221 } 1222 1223 // -- I/O -- 1224 1225 /** 1226 * Sends the specified command line to the server. 1227 * The CRLF sequence is automatically appended. 1228 * @param cmd the command line to send 1229 */ send(String cmd)1230 protected void send(String cmd) 1231 throws IOException 1232 { 1233 byte[] data = cmd.getBytes(US_ASCII); 1234 out.write(data); 1235 out.writeln(); 1236 out.flush(); 1237 } 1238 1239 /** 1240 * Reads the next response from the server. 1241 * If the server sends the "transfer complete" code, this is handled here, 1242 * and the next response is passed to the caller. 1243 */ getResponse()1244 protected FTPResponse getResponse() 1245 throws IOException 1246 { 1247 FTPResponse response = readResponse(); 1248 if (response.getCode() == 226) 1249 { 1250 if (dtp != null) 1251 { 1252 dtp.transferComplete(); 1253 } 1254 response = readResponse(); 1255 } 1256 return response; 1257 } 1258 1259 /** 1260 * Reads and parses the next response from the server. 1261 */ readResponse()1262 protected FTPResponse readResponse() 1263 throws IOException 1264 { 1265 String line = in.readLine(); 1266 if (line == null) 1267 { 1268 throw new ProtocolException( "EOF"); 1269 } 1270 if (line.length() < 4) 1271 { 1272 throw new ProtocolException(line); 1273 } 1274 int code = parseCode(line); 1275 if (code == -1) 1276 { 1277 throw new ProtocolException(line); 1278 } 1279 char c = line.charAt(3); 1280 if (c == ' ') 1281 { 1282 return new FTPResponse(code, line.substring(4)); 1283 } 1284 else if (c == '-') 1285 { 1286 StringBuffer buf = new StringBuffer(line.substring(4)); 1287 buf.append('\n'); 1288 while(true) 1289 { 1290 line = in.readLine(); 1291 if (line == null) 1292 { 1293 throw new ProtocolException("EOF"); 1294 } 1295 if (line.length() >= 4 && 1296 line.charAt(3) == ' ' && 1297 parseCode(line) == code) 1298 { 1299 return new FTPResponse(code, line.substring(4), 1300 buf.toString()); 1301 } 1302 else 1303 { 1304 buf.append(line); 1305 buf.append('\n'); 1306 } 1307 } 1308 } 1309 else 1310 { 1311 throw new ProtocolException(line); 1312 } 1313 } 1314 1315 /* 1316 * Parses the 3-digit numeric code at the beginning of the given line. 1317 * Returns -1 on failure. 1318 */ parseCode(String line)1319 static final int parseCode(String line) 1320 { 1321 char[] c = { line.charAt(0), line.charAt(1), line.charAt(2) }; 1322 int ret = 0; 1323 for (int i = 0; i < 3; i++) 1324 { 1325 int digit =((int) c[i]) - 0x30; 1326 if (digit < 0 || digit > 9) 1327 { 1328 return -1; 1329 } 1330 // Computing integer powers is way too expensive in Java! 1331 switch (i) 1332 { 1333 case 0: 1334 ret +=(100 * digit); 1335 break; 1336 case 1: 1337 ret +=(10 * digit); 1338 break; 1339 case 2: 1340 ret += digit; 1341 break; 1342 } 1343 } 1344 return ret; 1345 } 1346 1347 } 1348 1349