Package net.sf.jftp.net

Source Code of net.sf.jftp.net.FtpConnection

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
package net.sf.jftp.net;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.JLabel;
import javax.swing.JOptionPane;

import net.sf.jftp.config.LoadSet;
import net.sf.jftp.config.SaveSet;
import net.sf.jftp.config.Settings;
import net.sf.jftp.system.StringUtils;
import net.sf.jftp.system.logging.Log;


/**
* All control transactions are made here.
* See doc/FtpDownload.java for examples and note
* that this class is event-driven and not Exception-based!
* You might want to take a look at ConnectionListener, too.
*
* @author  David Hansmann, hansmann.d@debitel.net
*/
public class FtpConnection implements BasicConnection, FtpConstants
{
  private static final boolean TESTMODE = false;

  public final static String ASCII = "A";
  public final static String BINARY = "I";
  public final static String EBCDIC = "E";
  public final static String L8 = "L 8";
  public final static String STREAM = "S";
  public final static String BLOCKED = "B";
  public final static String COMPRESSED = "C";

  // everything starting with this will be treated
  // as a negative response
  private final static String NEGATIVE = "5";
  private final static String NEGATIVE2 = "4";
  // everything starting with this will be treated
  // as a positive response
  private final static String POSITIVE = "2";
  // for resuming
  private final static String PROCEED = "3"; //"350";
  private final static char MORE_LINES_APPENDED = '-';
  //*** 1.44: if LIST is not set, then load it from advanced settings
  //***       And if the file for it is not found, then create it
  //***       with LIST -laL as the default
  public static String LIST_DEFAULT = "LIST -laL";
  public static String LIST = "default";

  private static boolean useStream = true;
  private static boolean useBlocked = true;
  private static boolean useCompressed = true;

  // active ftp ports
  private static int porta = 5; // 5 * 256 + 1 = 1281
  private static int portb = 1;

  /**
   *        Used to determine the type of transfer.
   */
  public boolean hasUploaded = false;
  public boolean work = true;
  private boolean msg = true;
  private boolean ok = true;
  private String pwd = "";
  private String initCWD = Settings.defaultDir;
  private String[] loginAck = new String[] { FTP331_USER_OK_NEED_PASSWORD, FTP230_LOGGED_IN };
  private String osType = "";
  private String dataType;
  private FtpTransfer lastTransfer = null;
  private boolean modeStreamSet = false;
  private DataConnection dcon = null;
  private Vector<ConnectionListener> listeners = new Vector<ConnectionListener>();
  private ConnectionHandler handler = new ConnectionHandler();
  private FtpKeepAliveThread keepAliveThread = null;

  // directory downloaded to
  private String localPath = null;
  /** the host */
  private String host = "";
  /** the user */
  private String username;
  /** the password */
  private String password;
  /** the port */
  private int port = 21;
  /** the InputStream of the control connection */
  private BufferedReader in;
  /** the OutputStream of the control connection */
  private JConnection jcon;

  /** we are disconnected */
  private boolean connected = false;
  private boolean shortProgress = false;
  private boolean isDirUpload = false;
  private String baseFile;
  private int fileCount;
  private String typeNow = "";
  private String crlf = null;

  /**
   * May contain date listing
   */
  public Vector<Date> dateVector = new Vector<Date>();
  public Vector<String> currentListing = new Vector<String>();
  public Vector<String> currentFiles = new Vector<String>();
  public Vector<String> currentSizes = new Vector<String>();
  public Vector<String> currentPerms = new Vector<String>();

  /**
   * Create an instance with a given host
   *
   * @param host The host to connect to
   */
  public FtpConnection(String host)
  {
    this.host = host;

    File f = new File(".");
    setLocalPath(f.getAbsolutePath());
  }

  /**
   * Create an instance with a given host, port and initial remote working directory
   *
   * @param host The host to connect to
   * @param port The port used for the control connection, usually 21
   * @param initCWD The initial remote working directory
   */
  public FtpConnection(String host, int port, String initCWD)
  {
    this.host = host;
    this.port = port;
    this.initCWD = initCWD;

    File f = new File(".");
    setLocalPath(f.getAbsolutePath());
  }

  /**
   * Connect to the server an log in.
   *
   * Does also fire directory update and connection initialized event if successful or
   * connection failed event if failed.
   *
   * @param username The username
   * @param password  The password
   * @param initCWD The initial remote working directory
   * @param crlfx \n, \r, \r\n, CR, LF or CRLF - line break style of the server
   * @return  WRONG_LOGIN_DATA, OFFLINE, GENERIC_FAILED or LOGIN_OK status code
   */
  public FtpConnection(String host, int port, String initCWD, String crlfx)
  {
    this.host = host;
    this.port = port;
    this.initCWD = initCWD;

    if(crlfx != null) {
      if(crlfx.equals("\n") || crlfx.equals("\r") || crlfx.equals("\r\n")) this.crlf = crlfx;
      crlfx = crlfx.trim().toUpperCase();
      if(crlfx.equals("LF")) this.crlf = "\n";
      else if(crlfx.equals("CR")) this.crlf = "\r";
      else if(crlfx.equals("CRLF")) this.crlf = "\r\n";
    }
    //Log.out("\n\nCRLF:---"+crlf+"---\n\n");

    File f = new File(".");
    setLocalPath(f.getAbsolutePath());
  }

  /**
   * Connect to the server an log in.
   *
   * Does also fire directory update and connection initialized event if successful or
   * connection failed event if failed.
   *
   * @param username The username
   * @param password  The password
   * @return  WRONG_LOGIN_DATA, OFFLINE, GENERIC_FAILED or LOGIN_OK status code
   */
  public int login(String username, String password)
  {
    this.username = username;
    this.password = password;

    int status = LOGIN_OK;

    if(msg)
    {
      Log.debug("Connecting to " + host);
    }

    jcon = new JConnection(host, port);

    if(jcon.isThere())
    {
      in = jcon.getReader();

      if(getLine(POSITIVE) == null)//FTP220_SERVICE_READY) == null)
      {
        ok = false;
        Log.debug("Server closed Connection, maybe too many users...");
        status = OFFLINE;
      }

      if(msg)
      {
        Log.debug("Connection established...");
      }

      jcon.send(USER + " " + username);

      if(!getLine(loginAck).startsWith(POSITIVE))//FTP230_LOGGED_IN))
      {
        jcon.send(PASS + " " + password);

        if(success(POSITIVE))//FTP230_LOGGED_IN))
        {
          if(msg)
          {
            Log.debug("Logged in...");
          }
        }
        else
        {
          Log.debug("Wrong password!");
          ok = false;
          status = WRONG_LOGIN_DATA;
        }
      }

      // end if(jcon)...
    }
    else
    {
      if(msg)
      {
        Log.debug("FTP not available!");
        ok = false;
        status = GENERIC_FAILED;
      }
    }

    if(ok)
    {
      connected = true;
      system();

      //if(getOsType().indexOf("MVS") < 0)
      binary();

      if(initCWD.trim().equals(Settings.defaultDir) ||
          !chdirNoRefresh(initCWD))
      {
        //System.out.println("default dir...");
        //if(!chdirNoRefresh(getPWD()))
        //{
        //System.out.println("FtpConnection should not do this...");
        //  if(!chdirNoRefresh("~"))
        //  {
        //      chdirNoRefresh("/");
        //  }
        //}
        updatePWD();
      }

      if(Settings.ftpKeepAlive) {
        keepAliveThread = new FtpKeepAliveThread(this);
      }

      //Array of length 6 created, as that is maximum LoadSet size (need
      //public variable with that constant there for it to refer to?)
      String[] advSettings = new String[6];

      if(getOsType().indexOf("OS/2") >= 0)
      {
        LIST_DEFAULT = "LIST";
      }

      if(LIST.equals("default"))
      {
        //just get the first item (somehow it knows first is the
        //FTP list command)
        advSettings = LoadSet.loadSet(Settings.adv_settings);

        //*** IF FILE NOT FOUND, CREATE IT AND SET IT TO LIST_DEFAULT
        if(advSettings == null)
        {
          LIST = LIST_DEFAULT;

          new SaveSet(Settings.adv_settings, LIST);
        }
        else
        {
          LIST = advSettings[0];

          if(LIST == null)
          {
            LIST = LIST_DEFAULT;
          }
        }
      }

      if(getOsType().indexOf("MVS") >= 0)
      {
        LIST = "LIST";
      }

      //***
      fireDirectoryUpdate(this);
      fireConnectionInitialized(this);
    }
    else
    {
      fireConnectionFailed(this, new Integer(status).toString());
    }

    return status;
  }

  public FtpKeepAliveThread getKeepAliveThread() {
    return keepAliveThread;
  }

  /**
   * Sort the filesizes an return an array.
   *
   * The Array should be in sync with the other sort*-Methods
   *
   * @return An String-array of sizes containing the size or -1 if failed or 0 for directories
   */
  public String[] sortSize()
  {
    try
    {
      currentSizes.removeAllElements();

      for(int i=0; i<currentListing.size(); i++)
      {
        String tmp = (String) currentListing.get(i);

        // ------------- VMS override -------------------
        if(getOsType().indexOf("VMS") >= 0)
        {
          if(tmp.indexOf(";") < 0)
          {
            continue;
          }

          currentSizes.add("-1");

          continue;
        }

        // ------------- MVS override -------------------
        if(getOsType().indexOf("MVS") >= 0)
        {
          if(tmp.startsWith("Volume") || (tmp.indexOf(" ") < 0))
          {
            continue;
          }

          currentSizes.add("-1");

          continue;
        }

        // ------------------------------------------------
        else if(getOsType().indexOf("WINDOW") >= 0)
        {
          StringTokenizer to = new StringTokenizer(tmp, " ", false);

          if(to.countTokens() >= 4)
          {
            to.nextToken();
            to.nextToken();

            String size = to.nextToken();

            if(size.equals("<DIR>"))
            {
              currentSizes.add("0");
            }
            else
            {
              try
              {
                Long.parseLong(size);
                currentSizes.add(size);
              }
              catch(Exception ex)
              {
                currentSizes.add("-1");
                Log.out("WARNING: filesize can not be determined - value is " +
                    size);
              }
            }
          }
        }

        // ------------------------------------------------
        else if(getOsType().indexOf("OS/2") >= 0)
        {
          StringTokenizer to = new StringTokenizer(tmp, " ", false);
          tmp = giveSize(to, 0);
          Log.out("OS/2 parser (size): " + tmp);
          currentSizes.add(tmp);
        }
        else
        {
          //Log.out("\n\nparser\n\n");
          StringTokenizer to = new StringTokenizer(tmp, " ", false);

          if(to.countTokens() >= 8) // unix
          {
            tmp = giveSize(to, 4);
            currentSizes.add(tmp);

            //Log.out(tmp);
          }
          else if(to.countTokens() == 7) // unix, too
          {
            Log.out("WARNING: 7 token backup size parser activated!");
            tmp = giveSize(to, 4);
            currentSizes.add(tmp);
          }
          else
          {
            Log.debug("cannot parse line: " + tmp);
          }
        }
      }
    }
    catch(Exception ex)
    {
      Log.debug(ex.toString() + " @FtpConnection::sortSize#1");
      return new String[0];
    }

    return toArray(currentSizes);
  }

  private String[] toArray(Vector<String> in) {
    String ret[] = new String[in.size()];

    for(int i=0; i<in.size(); i++) {
      ret[i] = (String) in.get(i);
    }

    return ret;
  }

  /**
   * Sort the permissions an return an array.
   *
   * The Array should be in sync with the other sort*-Methods
   *
   * @param file The file containing the raw server listing, usually Settings.ls_out
   * @return An int-array of sizes containing W, R or DENIED for each file
   */
  public int[] getPermissions()
  {
    try
    {
      currentPerms.removeAllElements();

      for(int i=0; i<currentListing.size(); i++)
      {
        String tmp = (String) currentListing.get(i);

        // ------------- VMS override -------------------
        if(getOsType().indexOf("VMS") >= 0)
        {
          if(tmp.indexOf(";") < 0)
          {
            continue;
          }

          currentPerms.add("r");

          continue;
        }

        // ------------- MVS override -------------------
        if(getOsType().indexOf("MVS") >= 0)
        {
          if(tmp.startsWith("Volume"))
          {
            continue;
          }

          currentPerms.add("r");

          continue;
        }

        // ------------------------------------------------

        StringTokenizer to = new StringTokenizer(tmp.trim(), " ", false);

        if(!(to.countTokens() > 3) ||
            (tmp.startsWith("/") && (tmp.indexOf("denied") > 0)))
        {
          continue;
        }

        tmp = to.nextToken();

        if(tmp.length() != 10)
        {
          //System.out.println(tmp + " - e");
          return null; // exotic bug, hardlinks are not found or something ("/bin/ls: dir: no such file or directy" in ls output) - we have no permissions then
        }

        char ur = tmp.charAt(1);
        char uw = tmp.charAt(2);
        char ar = tmp.charAt(7);
        char aw = tmp.charAt(8);

        to.nextToken();

        String user = to.nextToken();

        //System.out.println(""+ur+":"+ar+":"+tmp);
        if(aw == 'w')
        {
          currentPerms.add("w");
        }
        else if(user.equals(username) && (uw == 'w'))
        {
          currentPerms.add("w");
        }
        else if(ar == 'r')
        {
          currentPerms.add("r");
        }
        else if(user.equals(username) && (ur == 'r'))
        {
          currentPerms.add("r");
        }
        else
        {
          //System.out.println(ur+":"+ar+":"+user+":"+username+":");
          currentPerms.add("n");
        }
      }
    }
    catch(Exception ex)
    {
      Log.debug(ex.toString() + " @FtpConnection::getPermissions#1");
    }

    int[] ret = new int[currentPerms.size()];

    for(int i=0; i<currentPerms.size(); i++) {
      String fx = (String) currentPerms.get(i);

      if(fx.equals("w"))
      {
        ret[i] = W;
      }
      else if(fx.equals("n"))
      {
        ret[i] = DENIED;
      }
      else
      {
        ret[i] = R;
      }
      //System.out.println(ret[i]);
    }

    return ret;
  }

  /**
   * Sort the filenames of the current working dir an return an array.
   *
   * The Array should be in sync with the other sort*-Methods
   *
   * @return An String-array of sizes containing the name of the file (symlinks are resolved)
   */
  public String[] sortLs()
  {
    try
    {
      boolean newUnixDateStyle = false;
      dateVector = new Vector<Date>();
      currentFiles.removeAllElements();

      for(int i=0; i<currentListing.size(); i++)
      {
        String tmp = (String) currentListing.get(i);

        // ------------------- VMS override --------------------
        if(getOsType().indexOf("VMS") >= 0)
        {
          int x = tmp.indexOf(";");

          if(x < 0)
          {
            continue;
          }

          tmp = tmp.substring(0, x);

          if(tmp.endsWith("DIR"))
          {
            currentFiles.add(tmp.substring(0, tmp.lastIndexOf(".")) +
            "/");
          }
          else
          {
            currentFiles.add(tmp);
          }

          //Log.out("listing - (vms parser): " + tmp);

          continue;
        }
        else if(getOsType().indexOf("OS/2") >= 0)
        {
          StringTokenizer to2 = new StringTokenizer(tmp, " ", true);

          if(giveFile(to2, 2).indexOf("DIR") >= 0)
          {
            to2 = new StringTokenizer(tmp, " ", true);
            tmp = giveFile(to2, 5);
            tmp = tmp + "/";
          }
          else
          {
            to2 = new StringTokenizer(tmp, " ", true);

            if(giveFile(to2, 1).indexOf("DIR") >= 0)
            {
              //Log.out("OS/2 parser (DIRFIX): " + tmp);
              to2 = new StringTokenizer(tmp, " ", true);
              tmp = giveFile(to2, 4);
              tmp = tmp + "/";
            }
            else
            {
              to2 = new StringTokenizer(tmp, " ", true);
              tmp = giveFile(to2, 4);
            }
          }

          Log.out("OS/2 parser: " + tmp);
          currentFiles.add(tmp);

          continue;
        }

        // -------------------------------------------------------
        // ------------------- MVS override --------------------
        if(getOsType().indexOf("MVS") >= 0)
        {
          if(tmp.startsWith("Volume") || (tmp.indexOf(" ") < 0))
          {
            Log.out("->" + tmp);

            continue;
          }

          String f = tmp.substring(tmp.lastIndexOf(" ")).trim();
          String isDir = tmp.substring(tmp.lastIndexOf(" ") - 5,
              tmp.lastIndexOf(" "));

          if(isDir.indexOf("PO") >= 0)
          {
            currentFiles.add(f + "/");
          }
          else
          {
            currentFiles.add(f);
          }

          Log.out("listing - (mvs parser): " + tmp + " -> " + f);
          continue;
        }
        else if(getOsType().indexOf("OS/2") >= 0)
        {
          StringTokenizer to2 = new StringTokenizer(tmp, " ", true);

          if(giveFile(to2, 2).indexOf("DIR") >= 0)
          {
            to2 = new StringTokenizer(tmp, " ", true);
            tmp = giveFile(to2, 5);
            tmp = tmp + "/";
          }
          else
          {
            to2 = new StringTokenizer(tmp, " ", true);

            if(giveFile(to2, 1).indexOf("DIR") >= 0)
            {
              //Log.out("OS/2 parser (DIRFIX): " + tmp);
              to2 = new StringTokenizer(tmp, " ", true);
              tmp = giveFile(to2, 4);
              tmp = tmp + "/";
            }
            else
            {
              to2 = new StringTokenizer(tmp, " ", true);
              tmp = giveFile(to2, 4);
            }
          }

          Log.out("OS/2 parser: " + tmp);
          currentFiles.add(tmp);

          continue;
        }

        // -------------------------------------------------------
        // ------------------- Unix, Windows and others -----
        boolean isDir = false;
        boolean isLink = false;

        if(tmp.startsWith("d") || (tmp.indexOf("<DIR>") >= 0))
        {
          isDir = true;
        }

        if(tmp.startsWith("l"))
        {
          isLink = true;
        }

        if(tmp.startsWith("/") && (tmp.indexOf("denied") > 0))
        {
          continue;
        }

        StringTokenizer to = new StringTokenizer(tmp, " ", false);
        StringTokenizer to2 = new StringTokenizer(tmp, " ", true);

        int tokens = to.countTokens();

        //Log.out("listing: tokens: " + tokens + " - " + tmp);
        boolean set = false;

        //-----preparsing-----
        int lcount = 0;

        if(!newUnixDateStyle)
        {
          try
          {
            StringTokenizer tx = new StringTokenizer(tmp, " ", false);
            tx.nextToken();
            tx.nextToken();
            tx.nextToken();
            tx.nextToken();
            tx.nextToken();

            String date = tx.nextToken();
            int x = date.indexOf("-");
            int y = date.lastIndexOf("-");

            if(y > x)
            {
              Log.debug("Using new Unix date parser");
              newUnixDateStyle = true;
            }
          }
          catch(Exception ex)
          {
            Log.out("could not preparse line: " + tmp);
            lcount++;

            //ex.printStackTrace();
          }
        }

        //--------------------
        //Log.out("tmp: " + tmp);
        if(newUnixDateStyle) // unix, too
        {
          set = true;

          //Log.out("listing - (unix parser #2, token 7): " + tmp);
          //------- date parser testing ---------
          try
          {
            //Log.out(">>> date parser: " + tmp);
            StringTokenizer to3 = new StringTokenizer(tmp, " ", true);
            String date = giveFile(to3, 5);
            String date2 = date.substring(date.indexOf("-") + 1);
            date2 = date2.substring(date.indexOf("-") + 2);

            //Log.out("date1: "+date+"/"+"date2: "+date2); 
            String t = date;
            String y = t.substring(0, t.indexOf("-"));
            String m = t.substring(t.indexOf("-") + 1,
                t.indexOf("-") + 3);
            String m1 = t.substring(t.indexOf("-") + 1);
            String day = m1.substring(m1.indexOf("-") + 1,
                m1.indexOf("-") + 3);
            String h = date2.substring(0, date2.indexOf(":"));
            String min = date2.substring(date2.indexOf(":") + 1,
                date2.indexOf(":") + 3);

            //Log.out("day:"+day+"year:"+y+"mon:"+m+"hour:"+h+"m:"+min);
            Calendar c = new GregorianCalendar();
            c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day));
            c.set(Calendar.YEAR, Integer.parseInt(y));
            c.set(Calendar.MONTH, Integer.parseInt(m) - 1);
            c.set(Calendar.HOUR, Integer.parseInt(h));
            c.set(Calendar.MINUTE, Integer.parseInt(min));

            dateVector.add(c.getTime());

            //Log.out("+++ date: \"" + d.toString() + "\"");
          }
          catch(Exception ex)
          {
            ex.printStackTrace();

            //set = false;
          }

          // -----------------------------
          tmp = giveFile(to2, 7);

          if(lcount > 1)
          {
            dateVector = new Vector<Date>();
          }
        }
        else if(tokens > 8) // unix
        {
          set = true;
          tmp = giveFile(to2, 8);

          //Log.out("listing - (unix parser, token 8): " + tmp);
        }
        else if(tokens > 3) // windows
        {
          set = true;
          tmp = giveFile(to2, 3);

          if(tmp.startsWith("<error>"))
          {
            tmp = giveFile(to2, 4);

            //Log.out("listing - (windows parser, token 4): " + tmp);
          }
          else
          {
            //Log.out("listing - (windows parser, token 3): " + tmp);
          }
        }

        if(tmp.startsWith("<error>") || !set)
        {
          if(tokens < 3)
          {
            continue;
          }

          tmp = giveFile(to2, tokens);
          Log.out("listing - WARNING: listing style unknown, using last token as filename!");
          Log.out("listing - this may work but may also fail, filesizes and permissions will probably fail, too...");
          Log.out("listing - please send us a feature request with the serveraddress to let us support this listing style :)");
          Log.out("listing - (backup parser, token " + tokens +
              " ): " + tmp);
        }

        if(isDir)
        {
          tmp = tmp + "/";
        }
        else if(isLink)
        {
          tmp = tmp + "###";
        }

        currentFiles.add(tmp);
        // -------------------------------------------------------------
      }

      String[] files = toArray(currentFiles);

      for(int i = 0; i < files.length; i++)
      {
        files[i] = parseSymlink(files[i]);

        //System.out.println("> " + files[i]+":"+dateVector.elementAt(i));
        //System.out.println("> " + files.length+":"+dateVector.size());
      }

      return files;
    }
    catch(Exception ex)
    {
      Log.debug(ex.toString() + " @FtpConnection::sortLs");
      ex.printStackTrace();
    }

    //------- date parser test ------

    /*
        String[] x = (String[]) dateVector.toArray();
        for(int i=0; i<x.length; i++)
        {
                Log.out(":"+x[i]);
        }
     */

    //-------------------------------
    return new String[0];
  }

  /** get a filename */
  private String giveFile(StringTokenizer to, int lev)
  {

    for(int i = 0; i < lev; i++)
    {
      if(to.hasMoreTokens())
      {
        String t = to.nextToken();

        //Log.out("> "+t);
        if(t.equals(" ") || t.equals("\t"))
        {
          // -

          /*
                       while(to.hasMoreTokens())
                       {
                               t2 = to.nextToken();
                           if(!t.equals(" ") && !t.equals("\t")) break;
                       }
           */
          i--;
        }
      }
    }

    String tmp = "<error>";

    /*
        if(t2 != null)
        {
                tmp = t2;
        }
     */
    while(tmp.equals(" ") || tmp.equals("\t") || tmp.equals("<error>"))
    {
      if(to.hasMoreTokens())
      {
        tmp = to.nextToken();
      }
      else
      {
        break;
      }
    }

    while(to.hasMoreTokens())
    {
      tmp = tmp + to.nextToken();
    }

    //Log.out(">>> "+tmp);
    return tmp;
  }

  /** get a filesize */
  private String giveSize(StringTokenizer to, int lev)
  {
    for(int i = 0; i < lev; i++)
    {
      if(to.hasMoreTokens())
      {
        if(to.nextToken().equals(" "))
        {
          i--;
        }
      }
    }

    while(to.hasMoreTokens())
    {
      String tmp = to.nextToken();

      if(!tmp.equals(" "))
      {
        try
        {
          Integer.parseInt(tmp);
        }
        catch(NumberFormatException ex)
        {
          return "-1";
        }

        return tmp;
      }
    }

    return "-2";
  }

  /**
   * Try to determine the os-type.
   * (Used internally to check how to parse the ls-output)
   *
   * @return A Part of the SYST comman server response
   */
  public String getOsType()
  {
    if(TESTMODE)
    {
      return "MVS";
    }
    else
    {
      return osType;
    }
  }

  private void setOsType(String os)
  {
    osType = os.toUpperCase();
  }

  private int getPasvPort(String str)
  {
    int start = str.lastIndexOf(",");
    String lo = "";
    start++;

    while(start < str.length())
    {
      char c = str.charAt(start);

      if(!Character.isDigit(c))
      {
        break;
      }

      lo = lo + c;
      start++;
    }

    System.out.println("\n\n\n"+str);

    String hi = "";
    start = str.lastIndexOf(",");
    start--;

    while(true)
    {
      char c = str.charAt(start);

      if(!Character.isDigit(c))
      {
        break;
      }

      hi = c + hi;
      start--;
    }

    //System.out.print(hi+":"+lo+" - ");
    return ((Integer.parseInt(hi) * 256) + Integer.parseInt(lo));
  }

  private int getActivePort()
  {
    return (getPortA() * 256) + getPortB();
  }

  /** parse a symlink */
  private String parseSymlink(String file)
  {
    if(file.indexOf("->") >= 0)
    {
      //System.out.print(file+" :-> symlink converted to:");
      file = file.substring(0, file.indexOf("->")).trim();
      file = file + "###";

      //System.out.println(file);
    }

    return file;
  }

  /** parse a symlink and cut the back */
  private String parseSymlinkBack(String file)
  {
    if(file.indexOf("->") >= 0)
    {
      //System.out.print(file+" :-> symlink converted to:");
      file = file.substring(file.indexOf("->") + 2).trim();

      //System.out.println(file);
    }

    return file;
  }

  /** test for symlink */
  private boolean isSymlink(String file)
  {
    if(file.indexOf("->") >= 0)
    {
      return true;
    }

    return false;
  }

  /** Download a file or directory.
   * Uses multithreading if enabled and does not block.
   *
   * @param file The file to download
   * @return An int-statuscode, see constants defined in this class for details
   */
  public int handleDownload(String file)
  {
    if(Settings.getEnableMultiThreading())
    {
      Log.out("spawning new thread for this download.");

      FtpTransfer t = new FtpTransfer(host, port, getLocalPath(),
          getCachedPWD(), file, username,
          password, Transfer.DOWNLOAD,
          handler, listeners, crlf);
      lastTransfer = t;

      return NEW_TRANSFER_SPAWNED;
    }
    else
    {
      Log.out("multithreading is completely disabled.");

      return download(file);
    }
  }

  /**
   * Download a file or directory, block until finished.
   *
   * @param file The file to download
   * @return An int returncode
   */
  public int download(String file)
  {
    //Log.out("ftp download started:" + this);

    int stat;

    if(file.endsWith("/"))
    {
      shortProgress = true;
      fileCount = 0;
      baseFile = file;
      dataType = DataConnection.GETDIR;

      stat = downloadDir(file);

      //pause(100);
      fireActionFinished(this);
      fireProgressUpdate(baseFile,
          DataConnection.DFINISHED + ":" + fileCount, -1);
      shortProgress = false;
    }
    else
    {
      dataType = DataConnection.GET;

      stat = rawDownload(file);

      if(Settings.enableFtpDelays) {
        try
        {
          Thread.sleep(100);
        } catch(Exception ex) {}
      }

      fireActionFinished(this);
    }

    if(Settings.enableFtpDelays) {
      try
      {
        Thread.sleep(400);
      }
      catch(Exception ex)
      {
      }
    }

    return stat;
  }

  /**
   * Get download InputStream.
   *
   * @param file The file to download
   * @return An InputStream
   */
  public InputStream getDownloadInputStream(String file)
  {
    Log.out("ftp stream download started:" + this);
    file = parse(file);

    try
    {
      int p = 0;
      dataType = DataConnection.GET;
      file = StringUtils.getFile(file);

      String path = getLocalPath() + file;

      //BufferedReader in = jcon.getReader();

      modeStream();
      p = negotiatePort();

      dcon = new DataConnection(this, p, host, path, dataType, false, true);

      while(!dcon.isThere())
      {
        pause(10);
      }

      jcon.send(RETR + " " + file);

      return dcon.getInputStream();
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
      Log.debug(ex.toString() +
          " @FtpConnection::getDownloadInputStream");

      return null;
    }
  }

  private int rawDownload(String file)
  {
    file = parse(file);

    //String path = file;
    try
    {
      int p = 0;

      //if(isRelative(file)) path = getLocalPath() + file;
      file = StringUtils.getFile(file);

      String path = getLocalPath() + file;

      //System.out.println(file + " : " + path);
      //BufferedReader in = jcon.getReader();
      modeStream();

      //binary();
      p = negotiatePort();

      File f = new File(path);

      //System.out.println("path: "+path);
      boolean resume = false;

      if(f.exists() && Settings.enableResuming)
      {
        jcon.send(REST + " " + f.length());

        if(getLine(PROCEED) != null)
        {
          resume = true;
        }
      }

      dcon = new DataConnection(this, p, host, path, dataType, resume); //, new Updater());

      while(!dcon.isThere())
      {
        pause(10);
      }

      jcon.send(RETR + " " + file);

      String line = getLine(POSITIVE);
      Log.debug(line);

      // file has been created even if not downloaded, delete it
      if(line.startsWith(NEGATIVE))
      {
        File f2 = new File(path);

        if(f2.exists() && (f2.length() == 0))
        {
          f2.delete();
        }

        return PERMISSION_DENIED;
      }
      else if(!line.startsWith(POSITIVE) && !line.startsWith(PROCEED))
      {
        return TRANSFER_FAILED;
      }

      // we need to block since some ftp-servers do not want the
      // refresh command that dirpanel sends otherwise
      while(!dcon.finished)
      {
        pause(10);
      }
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
      Log.debug(ex.toString() + " @FtpConnection::download");

      return TRANSFER_FAILED;
    }

    return TRANSFER_SUCCESSFUL;
  }

  private int downloadDir(String dir)
  {
    if(!dir.endsWith("/"))
    {
      dir = dir + "/";
    }

    if(StringUtils.isRelative(dir))
    {
      dir = getCachedPWD() + dir;
    }

    String path = getLocalPath() + StringUtils.getDir(dir);

    //System.out.println("path: "+path);
    String pwd = getCachedPWD() + StringUtils.getDir(dir);

    String oldDir = getLocalPath();
    String oldPwd = getCachedPWD();

    File f = new File(path);

    if(!f.exists())
    {
      if(!f.mkdir())
      {
        Log.debug("Can't create directory: " + dir);
      }
      else
      {
        Log.debug("Created directory...");
      }
    }

    setLocalPath(path);

    if(!chdirNoRefresh(pwd))
    {
      return CHDIR_FAILED;
    }

    try
    {
      list();
    }
    catch(IOException ex)
    {
      return PERMISSION_DENIED;
    }

    String[] tmp = sortLs();

    setLocalPath(path);

    for(int i = 0; i < tmp.length; i++)
    {
      if(tmp[i].endsWith("/"))
      {
        if(tmp[i].trim().equals("../") || tmp[i].trim().equals("./"))
        {
          Log.debug("Skipping " + tmp[i].trim());
        }
        else
        {
          if(!work)
          {
            return TRANSFER_STOPPED;
          }

          if(downloadDir(tmp[i]) < 0)
          {
            ; // return TRANSFER_FAILED;
          }
        }
      }
      else
      {
        //System.out.println( "file: " + getLocalPath() + tmp[i] + "\n\n");
        if(!work)
        {
          return TRANSFER_STOPPED;
        }

        fileCount++;

        if(rawDownload(getLocalPath() + tmp[i]) < 0)
        {
          ; // return TRANSFER_FAILED;
        }
      }
    }

    chdirNoRefresh(oldPwd);
    setLocalPath(oldDir);

    return TRANSFER_SUCCESSFUL;
  }

  /** Upload a file or directory.
   * Uses multithreading if enabled and does not block.
   *
   * @param file The file to upload
   * @return An int-statuscode, NEW_TRANSFER_SPAWNED,TRANSFER_FAILED or TRANSFER_SUCCESSFUL
   */
  public int handleUpload(String file)
  {
    return handleUpload(file, null);
  }

  /** Upload a file or directory.
   * Uses multithreading if enabled and does not block.
   *
   * @param file The file to upload
   * @param realName The file to rename the uploaded file to
   * @return An int-statuscode, NEW_TRANSFER_SPAWNED,TRANSFER_FAILED or TRANSFER_SUCCESSFUL
   */
  public int handleUpload(String file, String realName)
  {
    if(Settings.getEnableMultiThreading() &&
        (!Settings.getNoUploadMultiThreading()))
    {
      Log.out("spawning new thread for this upload.");

      FtpTransfer t;

      if(realName != null)
      {
        t = new FtpTransfer(host, port, getLocalPath(), getCachedPWD(),
            file, username, password, Transfer.UPLOAD,
            handler, listeners, realName, crlf);
      }
      else
      {
        t = new FtpTransfer(host, port, getLocalPath(), getCachedPWD(),
            file, username, password, Transfer.UPLOAD,
            handler, listeners, crlf);
      }

      lastTransfer = t;

      return NEW_TRANSFER_SPAWNED;
    }
    else
    {
      if(Settings.getNoUploadMultiThreading())
      {
        Log.out("upload multithreading is disabled.");
      }
      else
      {
        Log.out("multithreading is completely disabled.");
      }

      return (realName == null) ? upload(file) : upload(file, realName);
    }
  }

  /**
   * Upload a file or directory, block until finished.
   *
   * @param file The file to upload
   * @return An int returncode
   */
  public int upload(String file)
  {
    return upload(file, file);
  }

  /**
   * Upload and a file or directory under a given name, block until finished.
   * Note that setting realName does not affect directory transfers
   *
   * @param file The file to upload
   * @param realName The file to rename the uploaded file to
   * @return An int responsecode
   */
  public int upload(String file, String realName)
  {
    return upload(file, realName, null);
  }

  /**
   * Upload from an InputStream, block until finished.
   *
   * @param file The file to upload
   * @param in InputStream to read from
   * @return An int responsecode
   */
  public int upload(String file, InputStream in)
  {
    return upload(file, file, in);
  }

  /**
   * Upload and a file or directory under a given name, block until finished.
   * Note that setting realName does not affect directory transfers
   *
   * @param file The file to upload
   * @param realName The file to rename the uploaded file to
   * @param in InputStream to read from
   * @return An int responsecode
   */
  public int upload(String file, String realName, InputStream in)
  {
    hasUploaded = true;
    Log.out("ftp upload started: " + this);

    int stat;

    if((in == null) && new File(file).isDirectory())
    {
      shortProgress = true;
      fileCount = 0;
      baseFile = file;
      dataType = DataConnection.PUTDIR;
      isDirUpload = true;

      stat = uploadDir(file);

      shortProgress = false;

      //System.out.println(fileCount + ":" + baseFile);
      fireProgressUpdate(baseFile,
          DataConnection.DFINISHED + ":" + fileCount, -1);

      fireActionFinished(this);
      fireDirectoryUpdate(this);
    }
    else
    {
      dataType = DataConnection.PUT;
      stat = rawUpload(file, realName, in);

      try
      {
        Thread.sleep(100);
      }
      catch(Exception ex)
      {
      }

      fireActionFinished(this);
      fireDirectoryUpdate(this);
    }

    try
    {
      Thread.sleep(500);
    }
    catch(Exception ex)
    {
    }

    return stat;
  }

  private int rawUpload(String file)
  {
    return rawUpload(file, file);
  }

  private int rawUpload(String file, String realName)
  {
    return rawUpload(file, realName, null);
  }

  /** uploads a file */
  private int rawUpload(String file, String realName, InputStream in)
  {
    if(!file.equals(realName))
    {
      Log.debug("File: " + file + ", stored as " + realName);
    }
    else
    {
      Log.debug("File: " + file);
      realName = null;
    }

    file = parse(file);

    String path = file;

    try
    {
      int p = 0;

      if(StringUtils.isRelative(file))
      {
        path = getLocalPath() + file;
      }

      file = StringUtils.getFile(file);

      //BufferedReader in = jcon.getReader();
      boolean resume = false;
      String size = "0";

      if(Settings.enableUploadResuming && (in == null))
      {
        list();

        String[] ls = sortLs();
        String[] sizes = sortSize();

        if((ls == null) || (sizes == null))
        {
          Log.out(">>> ls out of sync (skipping resume check)");
        }
        else
        {
          for(int i = 0; i < ls.length; i++)
          {
            //Log.out(ls[i] + ":" + sizes[i]);
            if(realName != null)
            {
              if(ls[i].equals(realName))
              {
                resume = true;
                size = sizes[i];

                break;
              }
            }
            else
            {
              if(ls[i].equals(file))
              {
                resume = true;
                size = sizes[i];
              }
            }
          }

          File f = new File(path);

          if(f.exists() && (f.length() <= Integer.parseInt(size)))
          {
            Log.out("skipping resuming, file is <= filelength");
            resume = false;
          }
          else if(f.exists() && Integer.parseInt(size) > 0)
          {
            if(!Settings.noUploadResumingQuestion) {
              if(JOptionPane.showConfirmDialog(new JLabel(), "A file smaller than the one to be uploaded already exists on the server,\n do you want to resume the upload?", "Resume upload?", JOptionPane.YES_NO_OPTION)
                  != JOptionPane.OK_OPTION) {
                resume = false;
              }
            }
            Log.out("resume: " + resume + ", size: " + size);
          }
          else {
            Log.out("resume: " + resume + ", size: " + size);
          }
        }
      }

      modeStream();

      //binary();
      p = negotiatePort();

      if(resume && Settings.enableUploadResuming)
      {
        jcon.send(REST + " " + size);

        if(getLine(PROCEED) == null)
        {
          resume = false;
        }
      }

      dcon = new DataConnection(this, p, host, path, dataType, resume,
          Integer.parseInt(size), in); //, new Updater());

      while(!dcon.isThere())
      {
        pause(10);
      }

      //System.out.println(path + " : " + file);
      if(realName != null)
      {
        jcon.send(STOR + " " + realName);
      }
      else
      {
        jcon.send(STOR + " " + file);
      }

      String tmp = getLine(POSITIVE);

      if(!tmp.startsWith(POSITIVE) && !tmp.startsWith(PROCEED))
      {
        return TRANSFER_FAILED;
      }

      Log.debug(tmp);

      // we need to block since some ftp-servers do not want the
      // refresh command that dirpanel sends otherwise
      while(!dcon.finished)
      {
        pause(10);
      }
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
      Log.debug(ex.toString() + " @FtpConnection::upload");

      return TRANSFER_FAILED;
    }

    return TRANSFER_SUCCESSFUL;
  }

  private int uploadDir(String dir)
  {
    //System.out.println("up");
    if(dir.endsWith("\\"))
    {
      System.out.println("Something's wrong with the selected directory - please report this bug!");
    }

    if(!dir.endsWith("/"))
    {
      dir = dir + "/";
    }

    if(StringUtils.isRelative(dir))
    {
      dir = getLocalPath() + dir;
    }

    String single = StringUtils.getDir(dir);
    //String path = dir.substring(0, dir.indexOf(single));

    String remoteDir = getCachedPWD() + single; //StringUtils.removeStart(dir,path);
    String oldDir = getCachedPWD();

    if(Settings.safeMode)
    {
      noop();
    }

    boolean successful = mkdir(remoteDir);

    if(!successful)
    {
      return MKDIR_FAILED;
    }

    if(Settings.safeMode)
    {
      noop();
    }

    chdirNoRefresh(remoteDir);

    File f2 = new File(dir);
    String[] tmp = f2.list();

    for(int i = 0; i < tmp.length; i++)
    {
      String res = dir + tmp[i];
      File f3 = new File(res);

      if(f3.isDirectory())
      {
        if(!work)
        {
          return TRANSFER_STOPPED;
        }

        uploadDir(res);
      }
      else
      {
        //System.out.println(">>>\nlp: "+path+single + "\nrp: ");
        //System.out.println(remoteDir +"\nres: "+res);
        if(!work)
        {
          return TRANSFER_STOPPED;
        }

        fileCount++;

        if(rawUpload(res) < 0)
        {
          ; //return TRANSFER_STOPPED;
        }
      }
    }

    chdirNoRefresh(oldDir);

    return TRANSFER_SUCCESSFUL;
  }

  private String parse(String file)
  {
    file = parseSymlink(file);

    return file;
  }

  /** recursive delete remote directory */
  private int cleanDir(String dir, String path)
  {
    if(dir.equals(""))
    {
      return 0;
    }

    if(!dir.endsWith("/"))
    {
      dir = dir + "/";
    }

    String remoteDir = StringUtils.removeStart(dir, path);

    String oldDir = pwd;
    chdirNoRefresh(pwd + remoteDir);

    try
    {
      list();
    }
    catch(IOException ex)
    {
      // probably we don't have permission to ls here
      return PERMISSION_DENIED;
    }

    String[] tmp = sortLs();
    chdirNoRefresh(oldDir);

    if(tmp == null)
    {
      return GENERIC_FAILED;
    }

    for(int i = 0; i < tmp.length; i++)
    {
      Log.out("cleanDir: " + tmp);

      if(isSymlink(tmp[i]))
      {
        Log.debug("WARNING: Skipping symlink, remove failed.");
        Log.debug("This is necessary to prevent possible data loss when removing those symlinks.");

        tmp[i] = null;
      }

      if(tmp[i] != null)
      {
        tmp[i] = parseSymlink(tmp[i]);
      }

      if((tmp[i] == null) || tmp[i].equals("./") || tmp[i].equals("../"))
      {
        //System.out.println(tmp[i]+"\n\n\n");
        continue;
      }

      //System.out.println(tmp[i]);
      //pause(500);
      if(tmp[i].endsWith("/"))
      {
        //   System.out.println(dir);
        cleanDir(dir + tmp[i], path);

        int x = removeFileOrDir(dir + tmp[i]);

        if(x < 0)
        {
          return x;
        }
      }
      else
      {
        //   System.out.println(dir+tmp[i]);
        int x = removeFileOrDir(dir + tmp[i]);

        if(x < 0)
        {
          return x;
        }
      }
    }

    return REMOVE_SUCCESSFUL;
  }

  /**
   * Remove a remote file or directory.
   *
   * @param file The file to remove
   * @return REMOVE_SUCCESSFUL, REMOVE_FAILED, PERMISSION_DENIED or  GENERIC_FAILED
   */
  public int removeFileOrDir(String file)
  {
    if(file == null)
    {
      return 0;
    }

    if(file.trim().equals(".") || file.trim().equals(".."))
    {
      Log.debug("ERROR: Catching attempt to delete . or .. directories");

      return GENERIC_FAILED;
    }

    if(isSymlink(file))
    {
      Log.debug("WARNING: Skipping symlink, remove failed.");
      Log.debug("This is necessary to prevent possible data loss when removing those symlinks.");

      return REMOVE_FAILED;
    }

    file = parseSymlink(file);

    if(file.endsWith("/"))
    {
      int ret = cleanDir(file, getCachedPWD());

      if(ret < 0)
      {
        return ret;
      }

      jcon.send(RMD + " " + file);
    }
    else
    {
      jcon.send(DELE + " " + file);
    }

    if(success(POSITIVE))//FTP250_COMPLETED))
    {
      return REMOVE_SUCCESSFUL;
    }
    else
    {
      return REMOVE_FAILED;
    }
  }

  /**
   * Disconnect from the server.
   * The connection is marked ad closed even if the server does not respond correctly -
   * if it fails for any reason the connection should just time out
   *
   */
  public void disconnect()
  {
    jcon.send(QUIT);
    getLine(POSITIVE);//FTP221_SERVICE_CLOSING);
    connected = false;
  }

  /**
   * Execute a remote command.
   * Sends noops before and after the command
   *
   *  @param cmd The raw command that is to be sent to the server
   */
  public void sendRawCommand(String cmd)
  {
    noop();
    Log.clearCache();
    jcon.send(cmd);
    noop();
  }

  /**
   * Reads the response until line found or error.
   * (Used internally)
   *
   * @param until The String the response line hast to start with, 2 for succesful return codes for example
   */
  public String getLine(String until)
  {
    return getLine(new String[] { until });
  }

  /**
   * Reads the response until line found or error.
   * (Used internally)
   */
  public String getLine(String[] until)
  {
    BufferedReader in = null;

    try
    {
      in = jcon.getReader();
    }
    catch(Exception ex)
    {
      Log.debug(ex.toString() + " @FtpConnection::getLine");
    }

    //String resultString = null;
    String tmp;

    while(true)
    {
      try
      {
        tmp = in.readLine();

        if(tmp == null)
        {
          break;
        }

        Log.debug(tmp);

        if(((tmp.startsWith(NEGATIVE) || (tmp.startsWith(NEGATIVE2))) &&
            (tmp.charAt(3) != MORE_LINES_APPENDED)) ||
            tmp.startsWith(PROCEED))
        {
          return tmp;
        }
        else
        {
          for(int i = 0; i < until.length; i++)
          {
            if(tmp.startsWith(until[i]))
            {
              //if(resultString == null) resultString = tmp;
              //else resultString = resultString + "\n" + tmp;
              if(tmp.charAt(3) != MORE_LINES_APPENDED)
              {
                return tmp;
              }
            }
          }
        }
      }
      catch(Exception ex)
      {
        Log.debug(ex.toString() + " @FtpConnection::getLine");

        break;
      }
    }

    return null;
  }

  /**
   * Check if login() was successful.
   *
   * @return True if connected, false otherwise
   */
  public boolean isConnected()
  {
    return connected;
  }

  /**
   * Get the current remote directory.
   *
   * @return The server's CWD
   */
  public String getPWD()
  {
    if(connected)
    {
      updatePWD();
    }

    if(TESTMODE)
    {
      return "HUGO.user.";
    }

    return pwd;
  }

  /**
   * Returns current remote directory (cached)
   *
   * @return The cached CWD
   */
  public String getCachedPWD()
  {
    return pwd;
  }

  /**
   * updates PWD
   */
  private void updatePWD()
  {
    jcon.send(PWD);

    String tmp = getLine(POSITIVE);//FTP257_PATH_CREATED);

    if(tmp == null)
    {
      return;
    }

    if(TESTMODE)
    {
      tmp = "\"'T759F.'\"";
    }

    String x1 = tmp;

    if(x1.indexOf("\"") >= 0) {
      x1 = tmp.substring(tmp.indexOf("\"") + 1);
      x1 = x1.substring(0, x1.indexOf("\""));
    }

    if(getOsType().indexOf("MVS") >= 0)
    {
      //if(x1.indexOf("'") == 0) {
      //        String x2 = x1.substring(1, x1.lastIndexOf("'"));
      //        x1 = x2;
      //}
      Log.out("pwd: " + x1);
    }
    else if(!x1.endsWith("/"))
    {
      x1 = x1 + "/";
    }

    pwd = x1;
  }

  /**
   * Change directory unparsed.
   * Use chdir instead if you do not parse the dir correctly...
   *
   * @param dirName The raw directory name
   * @return True if successful, false otherwise
   */
  public boolean chdirRaw(String dirName)
  {
    jcon.send(CWD + " " + dirName);

    return success(POSITIVE);//FTP250_COMPLETED);
  }

  private boolean success(String op)
  {
    String tmp = getLine(op);

    if((tmp != null) && tmp.startsWith(op))
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  /**
   * Change to the parent of the current working directory.
   * The CDUP command is a special case of CWD, and is included
   * to simplify the implementation of programs for transferring
   * directory trees between operating systems having different
   * syntaxes for naming the parent directory.
   *
   * @return True if successful, false otherwise
   */
  public boolean cdup()
  {
    jcon.send(CDUP);

    return success(POSITIVE);//FTP200_OK);
  }

  /**
   * Create a directory with the given name.
   *
   * @param dirName The name of the directory to create
   * @return True if successful, false otherwise
   */
  public boolean mkdir(String dirName)
  {
    jcon.send(MKD + " " + dirName);

    boolean ret = success(POSITIVE); // Filezille server bugfix, was: FTP257_PATH_CREATED);
    Log.out("mkdir(" + dirName + ")  returned: " + ret);

    //*** Added 03/20/2005
    fireDirectoryUpdate(this);

    //*** 
    return ret;
  }

  private int negotiatePort() throws IOException
  {
    String tmp = "";

    if(Settings.getFtpPasvMode())
    {
      jcon.send(PASV);

      tmp = getLine(FTP227_ENTERING_PASSIVE_MODE);

      if((tmp != null) && !tmp.startsWith(NEGATIVE))
      {
        return getPasvPort(tmp);
      }
    }

    tmp = getActivePortCmd();
    jcon.send(tmp);
    getLine(FTP200_OK);

    return getActivePort();
  }

  /**
   * List remote directory.
   * Note that you have to get the output using the
   * sort*-methods.
   *
   * @param outfile The file to save the output to, usually Settings.ls_out
   */
  public void list() throws IOException
  {
    String oldType = "";

    try
    {
      //BufferedReader in = jcon.getReader();

      int p = 0;

      modeStream();

      oldType = getTypeNow();
      ascii();

      p = negotiatePort();
      dcon = new DataConnection(this, p, host, null, DataConnection.GET, false, true); //,null);

      while(dcon.getInputStream() == null)
      {
        //System.out.print("#");
        pause(10);
      }
      BufferedReader input = new BufferedReader(new InputStreamReader(dcon.getInputStream()));

      jcon.send(LIST);

      String line;
      currentListing.removeAllElements();

      while((line = input.readLine()) != null) {
        //System.out.println("-> "+line);
        if(!line.trim().equals("")) {
          currentListing.add(line);
        }
      }

      getLine(POSITIVE); //FTP226_CLOSING_DATA_REQUEST_SUCCESSFUL);
      input.close();

      if(!oldType.equals(ASCII))
      {
        type(oldType);
      }
    }
    catch(Exception ex)
    {
      Log.debug("Cannot list remote directory!");

      if(!oldType.equals(ASCII))
      {
        type(oldType);
      }

      ex.printStackTrace();
      throw new IOException(ex.getMessage());
    }
  }

  /**
   * Parses directory and does a chdir().
   * Does also fire a directory update event.
   *
   * @return True is successful, false otherwise
   */
  public boolean chdir(String p)
  {
    //Log.out("Change directory to: "+p);
    //String tmpPath = p;

    boolean tmp = chdirWork(p);

    if(!tmp)
    {
      return false;
    }
    else
    {
      fireDirectoryUpdate(this);

      return true;
    }
  }

  /**
   * Parses directory and does a chdir(), but does not send an update signal
   *
   * @return True is successful, false otherwise
   */
  public boolean chdirNoRefresh(String p)
  {
    return chdirWork(p);
  }

  private boolean chdirWork(String p)
  {
    if(Settings.safeMode)
    {
      noop();
    }

    p = parseSymlinkBack(p);

    if(getOsType().indexOf("OS/2") >= 0)
    {
      return chdirRaw(p);
    }
    else if(getOsType().indexOf("MVS") >= 0)
    {
      boolean cdup = false;

      System.out.print("MVS parser: "+p+" -> ")

      //TODO: handle relative unix paths
      if(!getPWD().startsWith("/")) //return chdirRaw(p);
      {
        if(p.endsWith("..")) {
          cdup = true;
        }
        //TODO let dirlister form correct syntax

        p = p.replaceAll("/", "");
        boolean ew = p.startsWith("'");
        String tmp = null;

        if(p.startsWith("'") && !p.endsWith("'"))
        {
          if(cdup){
            p = p.substring(0,p.length()-4);
            if(p.indexOf(".") > 0) {
              p = p.substring(0,p.lastIndexOf("."))
            }
            p += "'";
          }

          tmp = p.substring(0, p.lastIndexOf("'"));
          p = p.substring(p.lastIndexOf("'")+1);
          ew = false;
        }     
        else if(cdup){
          p = p.substring(0,p.length()-3);
          if(p.indexOf(".") > 0) {
            p = p.substring(0,p.lastIndexOf("."))
          }
        }

        if(p.indexOf(".") > 0 && !ew)
        {
          p = p.substring(0, p.indexOf("."));
        }

        if(tmp != null) p = tmp+p+"'";

        System.out.println(p);
        Log.out("MVS parser: " + p);

        return chdirRaw(p);
      }
    }

    try
    {
      // check if we don't have a relative path
      if(!p.startsWith("/") && !p.startsWith("~"))
      {
        p = pwd + p;
      }

      // argh, we should document that!
      if(p.endsWith(".."))
      {
        boolean home = p.startsWith("~");
        StringTokenizer stok = new StringTokenizer(p, "/");

        //System.out.println("path p :"+p +" Tokens: "+stok.countTokens());
        if(stok.countTokens() > 2)
        {
          String pold1 = "";
          String pold2 = "";
          String pnew = "";

          while(stok.hasMoreTokens())
          {
            pold1 = pold2;
            pold2 = pnew;
            pnew = pnew + "/" + stok.nextToken();
          }

          p = pold1;
        }
        else
        {
          p = "/";
        }

        if(home)
        {
          p = p.substring(1);
        }
      }

      //System.out.println("path: "+p);
      if(!chdirRaw(p))
      {
        return false;
      }
    }
    catch(Exception ex)
    {
      Log.debug("(remote) Can not get pathname! " + pwd + ":" + p);
      ex.printStackTrace();

      return false;
    }

    //fireDirectoryUpdate(this);

    // --------------------------------------
    updatePWD();


    return true;
  }

  /**
   * Waits a specified amount of time
   *
   * @param time The time to sleep in milliseconds
   */
  public void pause(int time)
  {
    try
    {
      Thread.sleep(time);
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
    }
  }

  /**
   * Return the local working directory
   *
   * @return The local CWD
   */
  public String getLocalPath()
  {
    return localPath;
  }

  /**
   * Set the local working directory
   *
   * @param newPath The new local CWD
   * @return Always true in the current implementation
   */
  public boolean setLocalPath(String newPath)
  {
    localPath = newPath;

    if(!localPath.endsWith("/"))
    {
      localPath = localPath + "/";
    }

    return true;
  }

  /**
   * Checks wheter a file exists.
   *
   * @param file the name of the file
   * @return An int-code: CHDIR_FAILED, PERMISSION_DENIED (no listing allowed), R, W or DENIED
   */
  public int exists(String file)
  {
    String dir = null;
    String tmpPWD = getCachedPWD();

    if(file.indexOf("/") >= 0)
    {
      dir = file.substring(0, file.lastIndexOf("/") + 1);
      Log.out("checking dir: " + dir);

      if(!chdir(dir))
      {
        return CHDIR_FAILED;
      }
    }

    try
    {
      list();
    }
    catch(IOException ex)
    {
      ex.printStackTrace();

      return PERMISSION_DENIED;
    }

    String f = file.substring(file.lastIndexOf("/") + 1);
    Log.out("checking file: " + f);

    String[] files = sortLs();
    int[] perms = getPermissions();

    int y = -1;

    for(int x = 0; x < files.length; x++)
    {
      if(files[x].equals(f))
      {
        y = x;

        break;
      }
    }

    if(y == -1)
    {
      return FILE_NOT_FOUND;
    }

    if(dir != null)
    {
      chdir(tmpPWD);
    }

    return perms[y];
  }

  /** Moves or renames remote file.
   * @param from remote file to move or rename.
   * @param to new file name.
   * @return <code>true</code> if completed successfully; <code>false</code> otherwise.
   */
  public boolean rename(String from, String to)
  {
    jcon.send("RNFR " + from);

    if(success(RC350))
    {
      jcon.send("RNTO " + to);

      if(success(POSITIVE))//FTP250_COMPLETED))
      {
        return true;
      }
    }

    return false;
  }

  /**
   * Get the port.
   *
   * @return The port used by the FTP control connection
   */
  public int getPort()
  {
    return port;
  }

  private int getPortA()
  {
    return porta;
  }

  private int getPortB()
  {
    return portb;
  }

  /*
    private void incrementPort()
    {
        portb++;
    }
   */

  /*
    private String getActivePortCmd() throws UnknownHostException, IOException
    {
        InetAddress ipaddr = jcon.getLocalAddress();
        String ip = ipaddr.getHostAddress().replace('.', ',');

        incrementPort();

        int a = getPortA();
        int b = getPortB();
        String ret = "PORT " + ip + "," + a + "," + b;

        //System.out.println(ret);
        return ret;
    }
   */

  private String getActivePortCmd() throws UnknownHostException, IOException
  {
    InetAddress ipaddr = jcon.getLocalAddress();
    String ip = ipaddr.getHostAddress().replace('.', ',');

    ServerSocket aSock = new ServerSocket(0);
    int availPort = aSock.getLocalPort();
    aSock.close();
    porta = availPort/256;
    portb = availPort%256;
    String ret = "PORT " + ip + "," + porta + "," + portb;

    //    System.out.println(ret);
    return ret;
  }

  /**
   * Tell server we want binary data connections.
   */
  public void binary()
  { // possible responses 200, 500, 501, 504, 421 and 530
    type(BINARY);
  }

  /**
   * Tell server we want ascii data connections.
   */
  public void ascii()
  {
    type(ASCII);
  }

  /**
   * Set type (L8,I,A for example).
   *
   * @return True if the type was changed successfully, false otherwise
   */
  public boolean type(String code)
  {
    jcon.send(TYPE + " " + code);

    String tmp = getLine(POSITIVE);//FTP200_OK);

    if((tmp != null) && tmp.startsWith(POSITIVE))//FTP200_OK))
    {
      typeNow = code;

      return true;
    }

    return false;
  }

  /**
   * Returns the type used.
   *
   * @return The type String, I, A, L8 for example
   */
  public String getTypeNow()
  {
    return typeNow;
  }

  /**
   * Do nothing, but flush buffers
   */
  public void noop()
  {
    jcon.send(NOOP);
    getLine(POSITIVE);//FTP200_OK);
  }

  /**
   * Try to abort the transfer.
   */
  public void abort()
  {
    jcon.send(ABOR);
    getLine(POSITIVE); // 226
  }

  /**
   * This command is used to find out the type of operating
   * system at the server. The reply shall have as its first
   * word one of the system names listed in the current version
   * of the Assigned Numbers document (RFC 943).
   *
   * @return The server response of the SYST command
   */
  public String system()
  { // possible responses 215, 500, 501, 502, and 421
    jcon.send(SYST);

    String response = getLine(POSITIVE);//FTP215_SYSTEM_TYPE);

    if(response != null)
    {
      //StringTokenizer st = new StringTokenizer(response);
      //if (st.countTokens() >= 2)
      //{
      //     st.nextToken();
      //     String os = st.nextToken();
      //     setOsType(os);
      //}
      setOsType(response);
    }
    else
    {
      setOsType("UNIX");
    }

    return response;
  }

  /**
   * Set mode to Stream.
   * Used internally.
   */
  public void modeStream()
  {
    if(useStream && !modeStreamSet)
    {
      String ret = mode(STREAM);

      if((ret != null) && ret.startsWith(NEGATIVE))
      {
        useStream = false;
      }
      else
      {
        modeStreamSet = true;
      }
    }
  }

  /**
   * Unsupported at this time.
   */
  public void modeBlocked()
  {
    if(useBlocked)
    {
      if(mode(BLOCKED).startsWith(NEGATIVE))
      {
        useBlocked = false;
      }
    }
  }

  /*
   * Unsupported at this time.
   */
  public void modeCompressed()
  {
    if(useCompressed)
    {
      if(mode(COMPRESSED).startsWith(NEGATIVE))
      {
        useCompressed = false;
      }
    }
  }

  /**
   * Set mode manually.
   *
   * @param code The mode code wanted, I, A, L8 for example
   * @return The server's response to the request
   */
  public String mode(String code)
  {
    jcon.send(MODE + " " + code);

    String ret = "";

    try
    {
      ret = getLine(POSITIVE);//FTP200_OK);
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
    }

    return ret;
  }

  /**
   * Return the host used.
   *
   * @return The remote host of this connection
   */
  public String getHost()
  {
    return host;
  }

  /**
   * Return the username.
   *
   * @return The username used by this connection
   */
  public String getUsername()
  {
    return username;
  }

  /**
   * Return the password.
   *
   * @return The password used by this connection
   */
  public String getPassword()
  {
    return password;
  }

  /**
   * Return the DataConnection.
   * Should be necessary only for complex actions.
   *
   * @return The DataConnection object currently used by this conenction
   */
  public DataConnection getDataConnection()
  {
    return dcon;
  }

  /**
   * Add a ConnectionListener.
   * The Listener is notified about transfers and the connection state.
   * @param l A ConnectionListener object
   */
  public void addConnectionListener(ConnectionListener l)
  {
    listeners.add(l);
  }

  /**
   * Add a Vector of ConnectionListeners.
   * Each Listener is notified about transfers and the connection state.
   *
   * @param l A Vector containig ConnectionListener objects
   */
  public void setConnectionListeners(Vector<ConnectionListener> l)
  {
    listeners = l;
  }

  /**
   * Remote directory has changed.
   *
   * @param con The FtpConnection calling the event
   */
  public void fireDirectoryUpdate(FtpConnection con)
  {
    if(listeners == null)
    {
      return;
    }
    else
    {
      for(int i = 0; i < listeners.size(); i++)
      {
        ((ConnectionListener) listeners.elementAt(i)).updateRemoteDirectory(con);
      }
    }
  }

  /**
   * Progress update.
   * The transfer is active and makes progress, so a signal is send
   * each few kb.
   *
   * @param file The file transferred
   * @param type the type of event, DataConnection.GETDIR for example
   * @param bytes The number of bytes transferred so far
   */
  public void fireProgressUpdate(String file, String type, long bytes)
  {
    //System.out.println(listener);
    if(listeners == null)
    {
      return;
    }
    else
    {
      for(int i = 0; i < listeners.size(); i++)
      {
        ConnectionListener listener = (ConnectionListener) listeners.elementAt(i);

        if(shortProgress && Settings.shortProgress)
        {
          if(type.startsWith(DataConnection.DFINISHED))
          {
            listener.updateProgress(baseFile,
                DataConnection.DFINISHED + ":" +
                fileCount, bytes);
          }
          else if(isDirUpload)
          {
            listener.updateProgress(baseFile,
                DataConnection.PUTDIR + ":" +
                fileCount, bytes);
          }
          else
          {
            listener.updateProgress(baseFile,
                DataConnection.GETDIR + ":" +
                fileCount, bytes);
          }
        }
        else
        {
          listener.updateProgress(file, type, bytes);
        }
      }
    }
  }

  /**
   * Connection is there and user logged in
   *
   * @param con The FtpConnection calling the event
   */
  public void fireConnectionInitialized(FtpConnection con)
  {
    if(listeners == null)
    {
      return;
    }
    else
    {
      for(int i = 0; i < listeners.size(); i++)
      {
        ((ConnectionListener) listeners.elementAt(i)).connectionInitialized(con);
      }
    }
  }

  /**
   * We are not logged in for some reason
   *
   * @param con The FtpConnection calling the event
   * @param why The login() response code
   */
  public void fireConnectionFailed(FtpConnection con, String why)
  {
    if(listeners == null)
    {
      return;
    }
    else
    {
      for(int i = 0; i < listeners.size(); i++)
      {
        ((ConnectionListener) listeners.elementAt(i)).connectionFailed(con,
            why);
      }
    }
  }

  /**
   * Transfer is done
   *
   * @param con The FtpConnection calling the event
   */
  public void fireActionFinished(FtpConnection con)
  {
    if(listeners == null)
    {
      return;
    }
    else
    {
      for(int i = 0; i < listeners.size(); i++)
      {
        ((ConnectionListener) listeners.elementAt(i)).actionFinished(con);
      }
    }
  }

  /**
   * Return the ConnectionHandler.
   *
   * @return The connection handler of this instance
   */
  public ConnectionHandler getConnectionHandler()
  {
    return handler;
  }

  /**
   * Set the ConnectionHandler.
   *
   * @param h The connection handler that is to be used by this connection
   */
  public void setConnectionHandler(ConnectionHandler h)
  {
    handler = h;
  }

  /**
   * Get the last FtpTransfer object spawned.
   *
   */
  public FtpTransfer getLastInitiatedTransfer()
  {
    return lastTransfer;
  }

  /**
   * Abort the last spawned transfer.
   *
   */
  public void abortTransfer()
  {
    try
    {
      DataConnection dcon = lastTransfer.getDataConnection();

      if(dcon == null)
      {
        Log.out("nothing to abort");

        return;
      }

      dcon.getCon().work = false;
      dcon.sock.close();
    }
    catch(Exception ex)
    {
      Log.debug("Exception during abort: " + ex.toString());
      ex.printStackTrace();
    }
  }

  public Date[] sortDates()
  {
    if(dateVector.size() > 0)
    {
      return (Date[]) dateVector.toArray();
    }
    else
    {
      return null;
    }
  }

  public String getCRLF() {
    return crlf;
  }

  public BufferedReader getIn() {
    return in;
  }

  public void setIn(BufferedReader in) {
    this.in = in;
  }

  public BufferedReader getCommandInputReader() {
    return jcon.getIn();
  }

  public OutputStream getCommandOutputStream() {
    return jcon.getOut();
  }

}
TOP

Related Classes of net.sf.jftp.net.FtpConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.