Source for gnu.java.net.protocol.ftp.FTPConnection

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