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.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:    */
 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 &lt;=0, use default port)
 217:    */
 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 &lt;=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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 429:   public boolean changeWorkingDirectory(String path)
 430:     throws IOException
 431:   {
 432:     // Do nothing if the path is empty.
 433:     if (path.length() == 0)
 434:       return true;
 435:     String cmd = CWD + ' ' + path;
 436:     send(cmd);
 437:     FTPResponse response = getResponse();
 438:     switch (response.getCode())
 439:       {
 440:       case 250:
 441:         return true;
 442:       case 550:
 443:         return false;
 444:       default:
 445:         throw new FTPException(response);
 446:       }
 447:   }
 448:   
 449:   /**
 450:    * Changes directory to the parent of the current working directory.
 451:    * @return true on success, false otherwise
 452:    */
 453:   public boolean changeToParentDirectory()
 454:     throws IOException
 455:   {
 456:     send(CDUP);
 457:     FTPResponse response = getResponse();
 458:     switch (response.getCode())
 459:       {
 460:       case 250:
 461:         return true;
 462:       case 550:
 463:         return false;
 464:       default:
 465:         throw new FTPException(response);
 466:       }
 467:   }
 468: 
 469:   /**
 470:    * Terminates an authenticated login.
 471:    * If file transfer is in progress, it remains active for result response
 472:    * only.
 473:    */
 474:   public void reinitialize()
 475:     throws IOException
 476:   {
 477:     send(REIN);
 478:     FTPResponse response = getResponse();
 479:     switch (response.getCode())
 480:       {
 481:       case 220:
 482:         if (dtp != null)
 483:           {
 484:             dtp.complete();
 485:             dtp = null;
 486:           }
 487:         break;
 488:       default:
 489:         throw new FTPException(response);
 490:       }
 491:   }
 492: 
 493:   /**
 494:    * Terminates the control connection.
 495:    * The file transfer connection remains open for result response only.
 496:    * This connection is invalid and no further commands may be issued.
 497:    */
 498:   public void logout()
 499:     throws IOException
 500:   {
 501:     send(QUIT);
 502:     try
 503:       {
 504:         getResponse();            // not required
 505:       }
 506:     catch (IOException e)
 507:       {
 508:       }
 509:     if (dtp != null)
 510:       {
 511:         dtp.complete();
 512:         dtp = null;
 513:       }
 514:     try
 515:       {
 516:         socket.close();
 517:       }
 518:     catch (IOException e)
 519:       {
 520:       }
 521:   }
 522:   
 523:   /**
 524:    * Initialise the data transfer process.
 525:    */
 526:   protected void initialiseDTP()
 527:     throws IOException
 528:   {
 529:     if (dtp != null)
 530:       {
 531:         dtp.complete();
 532:         dtp = null;
 533:       }
 534:     
 535:     InetAddress localhost = socket.getLocalAddress();
 536:     if (passive)
 537:       {
 538:         send(PASV);
 539:         FTPResponse response = getResponse();
 540:         switch (response.getCode())
 541:           {
 542:           case 227:
 543:             String message = response.getMessage();
 544:             try
 545:               {
 546:                 int start = message.indexOf(',');
 547:                 char c = message.charAt(start - 1);
 548:                 while (c >= 0x30 && c <= 0x39)
 549:                   {
 550:                     c = message.charAt((--start) - 1);
 551:                   }
 552:                 int mid1 = start;
 553:                 for (int i = 0; i < 4; i++)
 554:                   {
 555:                     mid1 = message.indexOf(',', mid1 + 1);
 556:                   }
 557:                 int mid2 = message.indexOf(',', mid1 + 1);
 558:                 if (mid1 == -1 || mid2 < mid1)
 559:                   {
 560:                     throw new ProtocolException("Malformed 227: " +
 561:                                                  message);
 562:                   }
 563:                 int end = mid2;
 564:                 c = message.charAt(end + 1);
 565:                 while (c >= 0x30 && c <= 0x39)
 566:                   {
 567:                     c = message.charAt((++end) + 1);
 568:                   }
 569:                 
 570:                 String address =
 571:                   message.substring(start, mid1).replace(',', '.');
 572:                 int port_hi =
 573:                   Integer.parseInt(message.substring(mid1 + 1, mid2));
 574:                 int port_lo =
 575:                   Integer.parseInt(message.substring(mid2 + 1, end + 1));
 576:                 int port = (port_hi << 8) | port_lo;
 577:                 
 578:                 /*System.out.println("Entering passive mode: " + address +
 579:                   ":" + port);*/
 580:                 dtp = new PassiveModeDTP(address, port, localhost,
 581:                                           connectionTimeout, timeout);
 582:                 break;
 583:               }
 584:             catch (ArrayIndexOutOfBoundsException e)
 585:               {
 586:                 throw new ProtocolException(e.getMessage() + ": " +
 587:                                              message);
 588:               }
 589:             catch (NumberFormatException e)
 590:               {
 591:                 throw new ProtocolException(e.getMessage() + ": " +
 592:                                              message);
 593:               }
 594:           default:
 595:             throw new FTPException(response);
 596:           }
 597:       }
 598:     else
 599:       {
 600:         // Get the local port
 601:         int port = socket.getLocalPort() + 1;
 602:         int tries = 0;
 603:         // Bind the active mode DTP
 604:         while (dtp == null)
 605:           {
 606:             try
 607:               {
 608:                 dtp = new ActiveModeDTP(localhost, port,
 609:                                          connectionTimeout, timeout);
 610:                 /*System.out.println("Listening on: " + port);*/
 611:               }
 612:             catch (BindException e)
 613:               {
 614:                 port++;
 615:                 tries++;
 616:                 if (tries > 9)
 617:                   {
 618:                     throw e;
 619:                   }
 620:               }
 621:           }
 622:         
 623:         // Send PORT command
 624:         StringBuffer buf = new StringBuffer(PORT);
 625:         buf.append(' ');
 626:         // Construct the address/port string form
 627:         byte[] address = localhost.getAddress();
 628:         for (int i = 0; i < address.length; i++)
 629:           {
 630:             int a =(int) address[i];
 631:             if (a < 0)
 632:               {
 633:                 a += 0x100;
 634:               }
 635:             buf.append(a);
 636:             buf.append(',');
 637:           }
 638:         int port_hi =(port & 0xff00) >> 8;
 639:         int port_lo =(port & 0x00ff);
 640:         buf.append(port_hi);
 641:         buf.append(',');
 642:         buf.append(port_lo);
 643:         send(buf.toString());
 644:         // Get response
 645:         FTPResponse response = getResponse();
 646:         switch (response.getCode())
 647:           {
 648:           case 200:                // OK
 649:             break;
 650:           default:
 651:             dtp.abort();
 652:             dtp = null;
 653:             throw new FTPException(response);
 654:           }
 655:       }
 656:     dtp.setTransferMode(transferMode);
 657:   }
 658:   
 659:   /**
 660:    * Set passive mode.
 661:    * @param flag true if we should use passive mode, false otherwise
 662:    */
 663:   public void setPassive(boolean flag)
 664:     throws IOException
 665:   {
 666:     if (passive != flag)
 667:       {
 668:         passive = flag;
 669:         initialiseDTP();
 670:       }
 671:   }
 672:   
 673:   /**
 674:    * Returns the current representation type of the transfer data.
 675:    * @return TYPE_ASCII, TYPE_EBCDIC, or TYPE_BINARY
 676:    */
 677:   public int getRepresentationType()
 678:   {
 679:     return representationType;
 680:   }
 681: 
 682:   /**
 683:    * Sets the desired representation type of the transfer data.
 684:    * @param type TYPE_ASCII, TYPE_EBCDIC, or TYPE_BINARY
 685:    */
 686:   public void setRepresentationType(int type)
 687:     throws IOException
 688:   {
 689:     StringBuffer buf = new StringBuffer(TYPE);
 690:     buf.append(' ');
 691:     switch (type)
 692:       {
 693:       case TYPE_ASCII:
 694:         buf.append('A');
 695:         break;
 696:       case TYPE_EBCDIC:
 697:         buf.append('E');
 698:         break;
 699:       case TYPE_BINARY:
 700:         buf.append('I');
 701:         break;
 702:       default:
 703:         throw new IllegalArgumentException(Integer.toString(type));
 704:       }
 705:     //buf.append(' ');
 706:     //buf.append('N');
 707:     send(buf.toString());
 708:     FTPResponse response = getResponse();
 709:     switch (response.getCode())
 710:       {
 711:       case 200:
 712:         representationType = type;
 713:         break;
 714:       default:
 715:         throw new FTPException(response);
 716:       }
 717:   }
 718: 
 719:   /**
 720:    * Returns the current file structure type.
 721:    * @return STRUCTURE_FILE, STRUCTURE_RECORD, or STRUCTURE_PAGE
 722:    */
 723:   public int getFileStructure()
 724:   {
 725:     return fileStructure;
 726:   }
 727: 
 728:   /**
 729:    * Sets the desired file structure type.
 730:    * @param structure STRUCTURE_FILE, STRUCTURE_RECORD, or STRUCTURE_PAGE
 731:    */
 732:   public void setFileStructure(int structure)
 733:     throws IOException
 734:   {
 735:     StringBuffer buf = new StringBuffer(STRU);
 736:     buf.append(' ');
 737:     switch (structure)
 738:       {
 739:       case STRUCTURE_FILE:
 740:         buf.append('F');
 741:         break;
 742:       case STRUCTURE_RECORD:
 743:         buf.append('R');
 744:         break;
 745:       case STRUCTURE_PAGE:
 746:         buf.append('P');
 747:         break;
 748:       default:
 749:         throw new IllegalArgumentException(Integer.toString(structure));
 750:       }
 751:     send(buf.toString());
 752:     FTPResponse response = getResponse();
 753:     switch (response.getCode())
 754:       {
 755:       case 200:
 756:         fileStructure = structure;
 757:         break;
 758:       default:
 759:         throw new FTPException(response);
 760:       }
 761:   }
 762: 
 763:   /**
 764:    * Returns the current transfer mode.
 765:    * @return MODE_STREAM, MODE_BLOCK, or MODE_COMPRESSED
 766:    */
 767:   public int getTransferMode()
 768:   {
 769:     return transferMode;
 770:   }
 771: 
 772:   /**
 773:    * Sets the desired transfer mode.
 774:    * @param mode MODE_STREAM, MODE_BLOCK, or MODE_COMPRESSED
 775:    */
 776:   public void setTransferMode(int mode)
 777:     throws IOException
 778:   {
 779:     StringBuffer buf = new StringBuffer(MODE);
 780:     buf.append(' ');
 781:     switch (mode)
 782:       {
 783:       case MODE_STREAM:
 784:         buf.append('S');
 785:         break;
 786:       case MODE_BLOCK:
 787:         buf.append('B');
 788:         break;
 789:       case MODE_COMPRESSED:
 790:         buf.append('C');
 791:         break;
 792:       default:
 793:         throw new IllegalArgumentException(Integer.toString(mode));
 794:       }
 795:     send(buf.toString());
 796:     FTPResponse response = getResponse();
 797:     switch (response.getCode())
 798:       {
 799:       case 200:
 800:         transferMode = mode;
 801:         if (dtp != null)
 802:           {
 803:             dtp.setTransferMode(mode);
 804:           }
 805:         break;
 806:       default:
 807:         throw new FTPException(response);
 808:       }
 809:   }
 810:   
 811:   /**
 812:    * Retrieves the specified file.
 813:    * @param filename the filename of the file to retrieve
 814:    * @return an InputStream containing the file content
 815:    */
 816:   public InputStream retrieve(String filename)
 817:     throws IOException
 818:   {
 819:     if (dtp == null || transferMode == MODE_STREAM)
 820:       {
 821:         initialiseDTP();
 822:       }
 823:     /*
 824:        int size = -1;
 825:        String cmd = SIZE + ' ' + filename;
 826:        send(cmd);
 827:        FTPResponse response = getResponse();
 828:        switch (response.getCode())
 829:        {
 830:        case 213:
 831:        size = Integer.parseInt(response.getMessage());
 832:        break;
 833:        case 550: // File not found
 834:        default:
 835:        throw new FTPException(response);
 836:        }
 837:      */
 838:     String cmd = RETR + ' ' + filename;
 839:     send(cmd);
 840:     FTPResponse response = getResponse();
 841:     switch (response.getCode())
 842:       {
 843:       case 125:                  // Data connection already open; transfer starting
 844:       case 150:                  // File status okay; about to open data connection
 845:         return dtp.getInputStream();
 846:       default:
 847:         throw new FTPException(response);
 848:       }
 849:   }
 850:   
 851:   /**
 852:    * Returns a stream for uploading a file.
 853:    * If a file with the same filename already exists on the server, it will
 854:    * be overwritten.
 855:    * @param filename the name of the file to save the content as
 856:    * @return an OutputStream to write the file data to
 857:    */
 858:   public OutputStream store(String filename)
 859:     throws IOException
 860:   {
 861:     if (dtp == null || transferMode == MODE_STREAM)
 862:       {
 863:         initialiseDTP();
 864:       }
 865:     String cmd = STOR + ' ' + filename;
 866:     send(cmd);
 867:     FTPResponse response = getResponse();
 868:     switch (response.getCode())
 869:       {
 870:       case 125:                  // Data connection already open; transfer starting
 871:       case 150:                  // File status okay; about to open data connection
 872:         return dtp.getOutputStream();
 873:       default:
 874:         throw new FTPException(response);
 875:       }
 876:   }
 877: 
 878:   /**
 879:    * Returns a stream for uploading a file.
 880:    * If a file with the same filename already exists on the server, the
 881:    * content specified will be appended to the existing file.
 882:    * @param filename the name of the file to save the content as
 883:    * @return an OutputStream to write the file data to
 884:    */
 885:   public OutputStream append(String filename)
 886:     throws IOException
 887:   {
 888:     if (dtp == null || transferMode == MODE_STREAM)
 889:       {
 890:         initialiseDTP();
 891:       }
 892:     String cmd = APPE + ' ' + filename;
 893:     send(cmd);
 894:     FTPResponse response = getResponse();
 895:     switch (response.getCode())
 896:       {
 897:       case 125:                  // Data connection already open; transfer starting
 898:       case 150:                  // File status okay; about to open data connection
 899:         return dtp.getOutputStream();
 900:       default:
 901:         throw new FTPException(response);
 902:       }
 903:   }
 904:   
 905:   /**
 906:    * This command may be required by some servers to reserve sufficient
 907:    * storage to accommodate the new file to be transferred.
 908:    * It should be immediately followed by a <code>store</code> or
 909:    * <code>append</code>.
 910:    * @param size the number of bytes of storage to allocate
 911:    */
 912:   public void allocate(long size)
 913:     throws IOException
 914:   {
 915:     String cmd = ALLO + ' ' + size;
 916:     send(cmd);
 917:     FTPResponse response = getResponse();
 918:     switch (response.getCode())
 919:       {
 920:       case 200:                  // OK
 921:       case 202:                  // Superfluous
 922:         break;
 923:       default:
 924:         throw new FTPException(response);
 925:       }
 926:   }
 927:   
 928:   /**
 929:    * Renames a file.
 930:    * @param oldName the current name of the file
 931:    * @param newName the new name
 932:    * @return true if successful, false otherwise
 933:    */
 934:   public boolean rename(String oldName, String newName)
 935:     throws IOException
 936:   {
 937:     String cmd = RNFR + ' ' + oldName;
 938:     send(cmd);
 939:     FTPResponse response = getResponse();
 940:     switch (response.getCode())
 941:       {
 942:       case 450:                  // File unavailable
 943:       case 550:                  // File not found
 944:         return false;
 945:       case 350:                 // Pending
 946:         break;
 947:       default:
 948:         throw new FTPException(response);
 949:       }
 950:     cmd = RNTO + ' ' + newName;
 951:     send(cmd);
 952:     response = getResponse();
 953:     switch (response.getCode())
 954:       {
 955:       case 250:                  // OK
 956:         return true;
 957:       case 450:
 958:       case 550:
 959:         return false;
 960:       default:
 961:         throw new FTPException(response);
 962:       }
 963:   }
 964:   
 965:   /**
 966:    * Aborts the transfer in progress.
 967:    * @return true if a transfer was in progress, false otherwise
 968:    */
 969:   public boolean abort()
 970:     throws IOException
 971:   {
 972:     send(ABOR);
 973:     FTPResponse response = getResponse();
 974:     // Abort client DTP
 975:     if (dtp != null)
 976:       {
 977:         dtp.abort();
 978:       }
 979:     switch (response.getCode())
 980:       {
 981:       case 226:                  // successful abort
 982:         return false;
 983:       case 426:                 // interrupted
 984:         response = getResponse();
 985:         if (response.getCode() == 226)
 986:           {
 987:             return true;
 988:           }
 989:         // Otherwise fall through to throw exception
 990:       default:
 991:         throw new FTPException(response);
 992:       }
 993:   }
 994:   
 995:   /**
 996:    * Causes the file specified to be deleted at the server site.
 997:    * @param filename the file to delete
 998:    */
 999:   public boolean delete(String filename)
1000:     throws IOException
1001:   {
1002:     String cmd = DELE + ' ' + filename;
1003:     send(cmd);
1004:     FTPResponse response = getResponse();
1005:     switch (response.getCode())
1006:       {
1007:       case 250:                  // OK
1008:         return true;
1009:       case 450:                 // File unavailable
1010:       case 550:                 // File not found
1011:         return false;
1012:       default:
1013:         throw new FTPException(response);
1014:       }
1015:   }
1016:   
1017:   /**
1018:    * Causes the directory specified to be deleted.
1019:    * This may be an absolute or relative pathname.
1020:    * @param pathname the directory to delete
1021:    */
1022:   public boolean removeDirectory(String pathname)
1023:     throws IOException
1024:   {
1025:     String cmd = RMD + ' ' + pathname;
1026:     send(cmd);
1027:     FTPResponse response = getResponse();
1028:     switch (response.getCode())
1029:       {
1030:       case 250:                  // OK
1031:         return true;
1032:       case 550:                 // File not found
1033:         return false;
1034:       default:
1035:         throw new FTPException(response);
1036:       }
1037:   }
1038: 
1039:   /**
1040:    * Causes the directory specified to be created at the server site.
1041:    * This may be an absolute or relative pathname.
1042:    * @param pathname the directory to create
1043:    */
1044:   public boolean makeDirectory(String pathname)
1045:     throws IOException
1046:   {
1047:     String cmd = MKD + ' ' + pathname;
1048:     send(cmd);
1049:     FTPResponse response = getResponse();
1050:     switch (response.getCode())
1051:       {
1052:       case 257:                  // Directory created
1053:         return true;
1054:       case 550:                 // File not found
1055:         return false;
1056:       default:
1057:         throw new FTPException(response);
1058:       }
1059:   }
1060:   
1061:   /**
1062:    * Returns the current working directory.
1063:    */
1064:   public String getWorkingDirectory()
1065:     throws IOException
1066:   {
1067:     send(PWD);
1068:     FTPResponse response = getResponse();
1069:     switch (response.getCode())
1070:       {
1071:       case 257:
1072:         String message = response.getMessage();
1073:         if (message.charAt(0) == '"')
1074:           {
1075:             int end = message.indexOf('"', 1);
1076:             if (end == -1)
1077:               {
1078:                 throw new ProtocolException(message);
1079:               }
1080:             return message.substring(1, end);
1081:           }
1082:         else
1083:           {
1084:             int end = message.indexOf(' ');
1085:             if (end == -1)
1086:               {
1087:                 return message;
1088:               }
1089:             else
1090:               {
1091:                 return message.substring(0, end);
1092:               }
1093:           }
1094:       default:
1095:         throw new FTPException(response);
1096:       }
1097:   }
1098:   
1099:   /**
1100:    * Returns a listing of information about the specified pathname.
1101:    * If the pathname specifies a directory or other group of files, the
1102:    * server should transfer a list of files in the specified directory.
1103:    * If the pathname specifies a file then the server should send current
1104:    * information on the file.  A null argument implies the user's
1105:    * current working or default directory.
1106:    * @param pathname the context pathname, or null
1107:    */
1108:   public InputStream list(String pathname)
1109:     throws IOException
1110:   {
1111:     if (dtp == null || transferMode == MODE_STREAM)
1112:       {
1113:         initialiseDTP();
1114:       }
1115:     if (pathname == null)
1116:       {
1117:         send(LIST);
1118:       }
1119:     else
1120:       {
1121:         String cmd = LIST + ' ' + pathname;
1122:         send(cmd);
1123:       }
1124:     FTPResponse response = getResponse();
1125:     switch (response.getCode())
1126:       {
1127:       case 125:                  // Data connection already open; transfer starting
1128:       case 150:                  // File status okay; about to open data connection
1129:         return dtp.getInputStream();
1130:       default:
1131:         throw new FTPException(response);
1132:       }
1133:   }
1134:   
1135:   /**
1136:    * Returns a directory listing. The pathname should specify a
1137:    * directory or other system-specific file group descriptor; a null
1138:    * argument implies the user's current working or default directory.
1139:    * @param pathname the directory pathname, or null
1140:    * @return a list of filenames(strings)
1141:    */
1142:   public List<String> nameList(String pathname)
1143:     throws IOException
1144:   {
1145:     if (dtp == null || transferMode == MODE_STREAM)
1146:       {
1147:         initialiseDTP();
1148:       }
1149:     if (pathname == null)
1150:       {
1151:         send(NLST);
1152:       }
1153:     else
1154:       {
1155:         String cmd = NLST + ' ' + pathname;
1156:         send(cmd);
1157:       }
1158:     FTPResponse response = getResponse();
1159:     switch (response.getCode())
1160:       {
1161:       case 125:                  // Data connection already open; transfer starting
1162:       case 150:                  // File status okay; about to open data connection
1163:         InputStream in = dtp.getInputStream();
1164:         in = new BufferedInputStream(in);
1165:         in = new CRLFInputStream(in);     // TODO ensure that TYPE is correct
1166:         LineInputStream li = new LineInputStream(in);
1167:         ArrayList<String> ret = new ArrayList<String>();
1168:         for (String line = li.readLine();
1169:              line != null;
1170:              line = li.readLine())
1171:           {
1172:             ret.add(line);
1173:           }
1174:         li.close();
1175:         return ret;
1176:       default:
1177:         throw new FTPException(response);
1178:       }
1179:   }
1180:   
1181:   /**
1182:    * Returns the type of operating system at the server.
1183:    */
1184:   public String system()
1185:     throws IOException
1186:   {
1187:     send(SYST);
1188:     FTPResponse response = getResponse();
1189:     switch (response.getCode())
1190:       {
1191:       case 215:
1192:         String message = response.getMessage();
1193:         int end = message.indexOf(' ');
1194:         if (end == -1)
1195:           {
1196:             return message;
1197:           }
1198:         else
1199:           {
1200:             return message.substring(0, end);
1201:           }
1202:       default:
1203:         throw new FTPException(response);
1204:       }
1205:   }
1206:   
1207:   /**
1208:    * Does nothing.
1209:    * This method can be used to ensure that the connection does not time
1210:    * out.
1211:    */
1212:   public void noop()
1213:     throws IOException
1214:   {
1215:     send(NOOP);
1216:     FTPResponse response = getResponse();
1217:     switch (response.getCode())
1218:       {
1219:       case 200:
1220:         break;
1221:       default:
1222:         throw new FTPException(response);
1223:       }
1224:   }
1225: 
1226:   // -- I/O --
1227: 
1228:   /**
1229:    * Sends the specified command line to the server.
1230:    * The CRLF sequence is automatically appended.
1231:    * @param cmd the command line to send
1232:    */
1233:   protected void send(String cmd)
1234:     throws IOException
1235:   {
1236:     byte[] data = cmd.getBytes(US_ASCII);
1237:     out.write(data);
1238:     out.writeln();
1239:     out.flush();
1240:   }
1241: 
1242:   /**
1243:    * Reads the next response from the server.
1244:    * If the server sends the "transfer complete" code, this is handled here,
1245:    * and the next response is passed to the caller.
1246:    */
1247:   protected FTPResponse getResponse()
1248:     throws IOException
1249:   {
1250:     FTPResponse response = readResponse();
1251:     if (response.getCode() == 226)
1252:       {
1253:         if (dtp != null)
1254:           {
1255:             dtp.transferComplete();
1256:           }
1257:         response = readResponse();
1258:       }
1259:     return response;
1260:   }
1261: 
1262:   /**
1263:    * Reads and parses the next response from the server.
1264:    */
1265:   protected FTPResponse readResponse()
1266:     throws IOException
1267:   {
1268:     String line = in.readLine();
1269:     if (line == null)
1270:       {
1271:         throw new ProtocolException( "EOF");
1272:       }
1273:     if (line.length() < 4)
1274:       {
1275:         throw new ProtocolException(line);
1276:       }
1277:     int code = parseCode(line);
1278:     if (code == -1)
1279:       {
1280:         throw new ProtocolException(line);
1281:       }
1282:     char c = line.charAt(3);
1283:     if (c == ' ')
1284:       {
1285:         return new FTPResponse(code, line.substring(4));
1286:       }
1287:     else if (c == '-')
1288:       {
1289:         StringBuffer buf = new StringBuffer(line.substring(4));
1290:         buf.append('\n');
1291:         while(true)
1292:           {
1293:             line = in.readLine();
1294:             if (line == null)
1295:               {
1296:                 throw new ProtocolException("EOF");
1297:               }
1298:             if (line.length() >= 4 &&
1299:                 line.charAt(3) == ' ' &&
1300:                 parseCode(line) == code)
1301:               {
1302:                 return new FTPResponse(code, line.substring(4),
1303:                                         buf.toString());
1304:               }
1305:             else
1306:               {
1307:                 buf.append(line);
1308:                 buf.append('\n');
1309:               }
1310:           }
1311:       }
1312:     else
1313:       {
1314:         throw new ProtocolException(line);
1315:       }
1316:   }
1317:   
1318:   /*
1319:    * Parses the 3-digit numeric code at the beginning of the given line.
1320:    * Returns -1 on failure.
1321:    */
1322:   static final int parseCode(String line)
1323:   {
1324:     char[] c = { line.charAt(0), line.charAt(1), line.charAt(2) };
1325:     int ret = 0;
1326:     for (int i = 0; i < 3; i++)
1327:       {
1328:         int digit =((int) c[i]) - 0x30;
1329:         if (digit < 0 || digit > 9)
1330:           {
1331:             return -1;
1332:           }
1333:         // Computing integer powers is way too expensive in Java!
1334:         switch (i)
1335:           {
1336:           case 0:
1337:             ret +=(100 * digit);
1338:             break;
1339:           case 1:
1340:             ret +=(10 * digit);
1341:             break;
1342:           case 2:
1343:             ret += digit;
1344:             break;
1345:           }
1346:       }
1347:     return ret;
1348:   }
1349: 
1350: }