Source for gnu.inet.ftp.FTPConnection

   1: /*
   2:  * FTPConnection.java
   3:  * Copyright (C) 2003 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.ftp;
  40: 
  41: import java.io.BufferedInputStream;
  42: import java.io.BufferedOutputStream;
  43: import java.io.InputStream;
  44: import java.io.IOException;
  45: import java.io.OutputStream;
  46: import java.net.BindException;
  47: import java.net.InetAddress;
  48: import java.net.InetSocketAddress;
  49: import java.net.ProtocolException;
  50: import java.net.Socket;
  51: import java.net.UnknownHostException;
  52: import java.security.GeneralSecurityException;
  53: import java.util.ArrayList;
  54: import java.util.List;
  55: 
  56: import javax.net.ssl.SSLContext;
  57: import javax.net.ssl.SSLSocket;
  58: import javax.net.ssl.SSLSocketFactory;
  59: import javax.net.ssl.TrustManager;
  60: 
  61: import gnu.inet.util.CRLFInputStream;
  62: import gnu.inet.util.CRLFOutputStream;
  63: import gnu.inet.util.EmptyX509TrustManager;
  64: import gnu.inet.util.LineInputStream;
  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 <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  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:     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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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: }