import java.lang.*;
import java.net.*;
import java.io.*;
import java.util.*;

/**
 * Ftp class
 * Capabilities :<BR>
 * - Open a Ftp session (with USER, and PASSWORD) <BR>
 * - Get a file<BR>
 * - See a directory<BR>
 * - Send a command<BR>
 * - See the Ftp's result<BR>
 * TO DO:<BR>
 * - Add all the commands to put files on the server<BR>
 * - etc. <BR>
 * author         Ebele Nicolas (ebele@essi.fr)
 * Note: this is my real first class in java ....<BR>
 *
 * PLEASE FEEL FREE TO SEND ANY COMMENTS (thanks ...)
 *<BR>
 * How to use Example:
 *<PRE>
import java.net.*;
import java.io.*;
import java.util.*;
import java.lang.*;

public class testFtp {

  public static void main(String[] args) {
    
    
    Ftp tFtp = new Ftp("anonymous:toto@nothing.com@ftp.inria.fr"); 
    if (tFtp.Open())
      {
	String list = tFtp.GetList();
	if (list == null)
	  System.err.println("unable to get the fic list");
	else
	  {
	    System.out.println("FicList=" + list);
	    if (!tFtp.GetFile("README"))
	      System.err.println("Get readme failed ....");
	    else
	      {
		if (!tFtp.GetFile("wrong file"))
		  System.err.println("Get wrongfile failed ....");
		else
		  {
		    System.out.println(tFtp.GetResult());
		    tFtp.ClearResult();
		    tFtp.Close();
		  }
	      }
	  }
      }
    else
      System.err.println("unable to open the connection");
  }
}
</pre>

 * @param Url the loctaion site of the Ftp server
 * @version        0.6 Mar 1997
 * @author         Ebele Nicolas (ebele@essi.fr)

*/


class Ftp {
  static final public String ClassName = "Ftp"; 

  protected final int ErrorFtp = 4;

/**
  * The Control port (value 21)
  */
  protected final int PortDefault = 21;
/**
  * The default USER name = "anonymous"
  */
  protected final String UserDefault = "anonymous";
/**
  * The default PASSWORD = "toto@nothing.com"
  */
  protected final String PassDefault = "toto@nothing.com";
/**
  * the control Socket
  */
  protected Socket Connection;
 /**
  * the FTP address
  */ 
  protected String Url;

/**
  * the USER name
  */
  protected String User;
/**
  * the USER password
  */
  protected String Pass;
/**
  * the control port
  */
  protected int Port;
/**
  * the local IP address
  */
  protected String IpAddress;
/**
  * the output stream towards the ftp server
  */
  protected DataOutputStream os;
/**
  * the input stream from the ftp server
  */
  protected DataInputStream is;

/**
  * connection opened ??
  */
  protected boolean Open = false;

/**
  * The result'text from the server
  */
  protected StringBuffer TextRes;

/**
  * The socket for data transfert
  */
  protected ServerSocket serverSocket;

/**
  * Number of tries to open a the dataport 
  */ 
  public int NbTriesDataPort = 10;

/**
  * Number of tries to open the connection
  */ 
  public int NbTriesOpenConnection;

/**
  * Number of tries to log on the remote server
  */ 
  public int NbTriesLogOn;




private boolean OpenServerSocket()
  {
    try {
      if (serverSocket != null)
	serverSocket.close();
    }catch (java.io.IOException e) {
      Init.println(ClassName,"Ftp Class, exception catched (OpenServerSocket):" + e);
    }
    serverSocket = null;
    for(int i=0;i< NbTriesDataPort;i++)
      {
	try {
	  serverSocket =new ServerSocket(0);
	  break; // we managed to open a port
	} catch (Exception e)
	  {
	    Init.println(ClassName, "Ftp Class: Exception catched in opening dataport: "+e);
	    continue;
	  }
      }
    Open = !(serverSocket == null);
    return Open;
  }
  

/** Open the ftp connection
  * @param no param
  * Create the socket
  * Open the streams (input, and output)
  * fill the Open boolean
  * Start the thread to see the server'answers
  * Log on the remote Ftp (send username and the password)  
  */
private void OpenFtp()
  { 
    TextRes = new StringBuffer(1024);
    try {
      Connection = new Socket(Url, Port);
      
      os = new DataOutputStream(Connection.getOutputStream());
      is = new DataInputStream(Connection.getInputStream());
    } catch (Exception e)
      {
	Open = false;
	Init.println(ClassName,"Ftp Class : Exception catched, OpenFtp :" + e);
	TextRes.append("Erreur, impossible d'ouvrir l'adresse:" + Url + "/n");
	return; 
      }
    if (getreply(is) >= ErrorFtp)
      Open = false;
    else {
	Init.println(ClassName,"la");
	Open = true;
	Open = Log_On();
      }
  }



/** Get the local IP address
  * @param no param
  * @return the local IP adress on the form www,xxx,yyy,zzz
  */
private String GetIpAddress()
  {
    InetAddress localhost = null;
    StringBuffer Res = null;
    try {
      localhost = InetAddress.getLocalHost();
      byte b[] = localhost.getAddress();
      
      Res = new StringBuffer(b.length);
      Res.append((int)(b[0]&0x000000ff));   //Cut the sign 
      
      for (int i=1;i<b.length;i++)
	Res.append("," + (int)(b[i]&0x000000ff));
      
    } catch(Exception e) {
      Init.println(ClassName, "Ftp Class : Exception catched, GetIpAddress :" + e);
      return Res.toString();
    }
    Init.println(ClassName, "Ftp Class : Local Ip : " + Res);
    return Res.toString();
  }

/** Parse a String (representing an URl) to find the User and the password
 * @param Url the url to parse
 * @return true if the fields Url, Pass, and User are filled, false if not
 */
private void ParseUrl(String Url)
  {
    int i,j;

    if (Url.toLowerCase().startsWith("ftp://"))
	Url = Url.substring(6); // == sizeof(ftp://);
	
    if ( ((i = Url.lastIndexOf('@')) != -1) // url like this : user:pass[:port]@address ??
	 && ((j =Url.indexOf(':')) != -1) )
      {
	char[] tmp = new char[200];
	
	Url.getChars(0,j,tmp,0);
	User = new String(tmp,0,j);
	int x = Url.lastIndexOf(':');
	if (x != -1 && x != j)    // the port is set in the url
	  {
	    Url.getChars(j+1,x,tmp,0);
	    Pass = new String(tmp,0,x- j -1);
	    Url.getChars(x+1,i,tmp,0);
	    try {
	      Port = Integer.parseInt(new String(tmp,0,i- x -1));
	    } catch (Exception e) { // error in the port format
	      Port = PortDefault;
	    }
	  }
	else
	  {
	    Url.getChars(j+1,i,tmp,0);
	    Pass = new String(tmp,0,i- j -1);
	    Port = PortDefault;
	  }
	Url.getChars(i+1,Url.length(),tmp,0);
	this.Url = new String(tmp,0,Url.length()-i-1);
      }
    else
      {
	User = UserDefault;
	Pass = PassDefault;
	this.Url = Url;
	Port = PortDefault;
      }
    
    Init.println(ClassName,"User="+this.User +" Pass="+this.Pass+" Url="+this.Url+" Port=" + this.Port);

  }
  
/** Construct, and open an Ftp connection
  * @param Url the ftp server address, the form user:pass@adress is accepted
  */
  Ftp(String Url)
  {
    String param = (String)Init.get(ClassName, "config", "NbTries");
    
    NbTriesOpenConnection = param==null?20:Integer.parseInt(param);
    if (NbTriesOpenConnection == 0)
      NbTriesOpenConnection = 10;

    param = (String)Init.get(ClassName, "Config", "NbTriesLog");
    NbTriesLogOn = param==null?10:Integer.parseInt(param);
    Init.println(ClassName, "Param="+param);

    if (NbTriesLogOn == 0)
      NbTriesLogOn = 10;


    this.IpAddress = GetIpAddress();

    Init.println(ClassName,"Logoon debut=" + NbTriesLogOn);
    ParseUrl(Url);
    OpenFtp();
  }

/** Construct, and open an Ftp connection
  * @param Url the ftp server address
  * @param User the username used to log on
  * @param Pass the password used to log on
  */  
  Ftp(String Url,String User, String Pass)
  {
    String param = (String)Init.get(ClassName, "Config", "NbTries");
    NbTriesOpenConnection = param==null?10:Integer.parseInt(param);
    if (NbTriesOpenConnection == 0)
      NbTriesOpenConnection = 10;

    param = (String)Init.get(ClassName, "Config", "NbTriesLog");
    NbTriesLogOn = param==null?10:Integer.parseInt(param);
    
    if (NbTriesLogOn == 0)
      NbTriesLogOn = 10;


    this.Url = Url;
    this.User = User;
    this.Pass = Pass;
    this.Port = PortDefault;
    this.IpAddress = GetIpAddress();
    OpenFtp();
  }
  
/** Construct, and open an Ftp connection
  * @param Url the ftp server address
  * @param Port the control port used for the ftp server
  * @param User the username used to log on
  * @param Pass the password used to log on
  */
  Ftp(String Url, int Port,String User, String Pass)
  {
    String param = (String)Init.get(ClassName, "config", "NbTries");
    NbTriesOpenConnection = param==null?10:Integer.parseInt(param);
    if (NbTriesOpenConnection == 0)
      NbTriesOpenConnection = 10;

    param = (String)Init.get(ClassName, "config", "NbTriesLog");
    NbTriesLogOn = param==null?10:Integer.parseInt(param);
    if (NbTriesLogOn == 0)
      NbTriesLogOn = 10;

    this.Url = Url;
    this.User = User;
    this.Pass = Pass;
    this.Port = Port;
    this.IpAddress = GetIpAddress();
    OpenFtp();
  }

/** Send a command to the ftp server
  * @param Command the command sent to the server
  * @return true if the command have be sent, false if not
  */
public boolean Command(String Command)
  {
    if (Open) {
      try {
	Init.println(ClassName, "Ftp Class : Sending command: " +Command); 
	os.writeBytes(Command+"\n");
	if (getreply(is)  >= ErrorFtp)
	  return false;
	return true;
      }
      catch (Exception e)
	{
	  Init.println(ClassName, "Ftp Class : Exception catched, Command " + Command + " :" + e);
	  return false;
	}
    }
    
    Init.println(ClassName, "Ftp Class : Command, Not Opened ("+ Command + ")");
    
    return false;
  }

/** Send a command to the ftp server
  * @param Command the command sent to the server
  * @param wait, wait for the server answer
  * @return true if the command have be sent, false if not
  */
public boolean Command(String Command, boolean wait)
  {
    if (Open) {
      try {
	Init.println(ClassName, "Ftp Class : Sending command: " +Command); 
	os.writeBytes(Command+"\n");
	if (wait && getreply(is)  >= ErrorFtp)
	  return false;
	return true;
      }
      catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, Command" + Command + " :" + e);
	  return false;
	}
    }
    
    Init.println(ClassName, "Ftp Class : Command, Not Opened ("+ Command + ")");
    
    return false;
  }

/** Get a file from the ftp server
  * @param file the file to get, it is saved on the local machine with the same name
  * @return true if the file have been get, false if not
  */
public boolean GetFile(String file) 
  {
 
    if (Open) {
      try {
	if (!OpenServerSocket())
	  return false;
	int DataPort = serverSocket.getLocalPort();
	Init.println(ClassName,"Ftp Class : getting file= " + file + " dest=" + file + " dataPort=" +DataPort); 

	if (!Command("PORT "+IpAddress+ "," + DataPort / 256 + "," + DataPort % 256))
	  return false;
	Command("RETR " + file, false);
	GetData(file,null);
	if (getreply(is) >= ErrorFtp || getreply(is) >= ErrorFtp) // read the reply twice (for RETR and get data)
	  return false;
	
      } catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, GetFile" + file + " :" + e);
	  return false;
	}
      return true;
    }
    
    Init.println(ClassName,"Ftp Class : Command, Not Opened");     
    return false;
  }
/** Get a file from the ftp server
  * @param file the file to get
  * @param dest the file saved on the local machine
  * @return true if the file have been get, false if not
  */
public boolean GetFile(String file, String dest)
  {
    if (Open) {
      try {
	if (!OpenServerSocket())
	  return false;
	int DataPort = serverSocket.getLocalPort();
	Init.println(ClassName,"Ftp Class : getting file= " + file + " dest=" + dest + " dataPort=" +DataPort); 

	if (!Command("PORT "+IpAddress+ "," + DataPort / 256 + "," + DataPort % 256))
	  return false;

	Command("RETR " + file, false);	
	GetData(dest,null);
	if (getreply(is) >= ErrorFtp || getreply(is) >= ErrorFtp) // read the reply twice (for RETR and get data)
	  return false;
	
      } catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, GetFile" + file + " :" + e);
	  return false;
	}
      
      return true;
    }
    Init.println(ClassName,"Ftp Class : Get File, Not Opened");
    return false;
  }

/** Store a file from the ftp server
  * @param file the file to store, it is saved on the remote machine with the same name
  * @return true if the file have been stored, false if not
  */
public boolean PutFile(String file) 
  {
    if (Open) {
      try {
	if (!OpenServerSocket())
	  return false;
	int DataPort = serverSocket.getLocalPort();

	Init.println(ClassName,"Ftp Class : Store file= " + file + " dest=" + file + " dataPort=" +DataPort); 
	
	if (!Command("PORT "+IpAddress+ "," + DataPort / 256 + "," + DataPort % 256))
	  return false;
	Command("STOR " + file, false);
	PutData(file);
	if (getreply(is) >= ErrorFtp || getreply(is) >= ErrorFtp) // read the reply twice (for RETR and put data)
	  return false;
	
      } catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, StorFile" + file + " :" + e);
	  return false;
	}
      return true;
    }
    
    Init.println(ClassName,"Ftp Class : Command, Not Opened");     
    return false;
  }  

/** Store a file from the ftp server
  * @param file the file to store
  * @param dest the file saved on the remote machine
  * @return true if the file have been stored, false if not
  */
public boolean PutFile(String file, String dest)
  {
     if (Open) {
      try {
	if (!OpenServerSocket())
	  return false;
	int DataPort = serverSocket.getLocalPort();
	Init.println(ClassName,"Ftp Class : Store file= " + file + " dest=" + dest + " dataPort=" +DataPort); 

	if (!Command("PORT "+IpAddress+ "," + DataPort / 256 + "," + DataPort % 256))
	  return false;

	Command("STOR " + dest, false);	
	PutData(file);
	if (getreply(is) >= ErrorFtp || getreply(is) >= ErrorFtp) // read the reply twice (for RETR and put data)
	  return false;
	
      } catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, PutFile" + file + " :" + e);
	  return false;
	}
      return true;
    }
     Init.println(ClassName,"Ftp Class : Get File, Not Opened");
    return false;
  }


/** Get the file'list of the curent directory (ls -la)
  * @param no param
  * @return the string contening the file'list
  */
public String GetFullList()
  {
    return GetFullList(null);
  }
  
  /** Get the file'list of the curent directory (ls -la)
  * @param dir : the path
  * @return the string contening the file'list
  */
public String GetFullList(String dir)
  {
    if (Open) {
      StringBuffer res = new StringBuffer(1024);
      try {
	if (!OpenServerSocket())
	  return null;
	int DataPort = serverSocket.getLocalPort();
	Init.println(ClassName,"Ftp Class : getting list ("+dir+")");

	if (!Command("PORT "+IpAddress+ "," + DataPort / 256 + "," + DataPort % 256))
	  return null;
	if (dir != null && dir.trim().length() >0)
	  Command("LIST "+dir, false);
	else
	  Command("LIST",false);
	
	GetData("",res);
	if (getreply(is) >= ErrorFtp || getreply(is) >= ErrorFtp) // read the reply twice (for NLST and get data)
	  return null;
	
      } catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, list :" + e);
	  return null;
	}

      String s = res.toString();
      if (s.toLowerCase().startsWith("total"))
	{
	  int j = s.indexOf("\n");
	  if (j != -1)
	    s = s.substring(j+1);
	}
      Init.println(ClassName,"res="+s);
      return s;

    }
    Init.println(ClassName,"Ftp Class : List, Not Opened");
    return null;
  }

/** Get the file'list of the curent directory (ls)
  * @param no param
  * @return the string contening the file'list
  */
public String GetList()
  {
    return GetList(null);
  }

/** Get the file'list of the curent directory (ls)
  * @param dir the path
  * @return the string contening the file'list
  */
public String GetList(String dir)
  {
    if (Open) {
      StringBuffer res = new StringBuffer(1024);
      try {
	if (!OpenServerSocket())
	  return null;
	int DataPort = serverSocket.getLocalPort();
	Init.println(ClassName,"Ftp Class : getting list ("+dir+")");
	if (!Command("PORT "+IpAddress+ "," + DataPort / 256 + "," + DataPort % 256))
	  return null;
	if (dir != null && dir.trim().length()>0)
	  Command("NLST "+dir, false); // we don't wait for the reply now
	else
	  Command("NLST",false);

	GetData("",res);
	if (getreply(is) >= ErrorFtp || getreply(is) >= ErrorFtp) // read the reply twice (for NLST and get data)
	  return null;

      } catch (Exception e)
	{
	  Init.println(ClassName,"Ftp Class : Exception catched, list :" + e);
	  return null;
	}
      
      return res.toString();
    }
    Init.println(ClassName,"Ftp Class : List, Not Opened");
    return null;
  }


/** Get the reply from a Ftp Server  (Author : Joseph Szabo, thanks man ...)
  * @param the datainputstream where to read 
  * @return the int corresponding to the error type 
  */ 
private int getreply(DataInputStream is){
    String sockoutput;
    // get reply (intro)
    try {
      do {
	sockoutput = is.readLine();
	TextRes.append(sockoutput + '\n');
	Init.println(ClassName,"GetReply :"+ sockoutput);
      } while(sockoutput != null && 
	      !(Character.isDigit(sockoutput.charAt(0)) &&
		Character.isDigit(sockoutput.charAt(1)) &&
		Character.isDigit(sockoutput.charAt(2)) && 
		sockoutput.charAt(3) == ' '));
      
    } catch (IOException e) {
      Init.println(ClassName,"Ftp Class : Exception cathed. getreply "+e);
      return(ErrorFtp);
    }
    if (sockoutput != null)
      return(Integer.parseInt(sockoutput.substring(0, 1)));
    else
      return 0; // no error ??
  }

/** Change dir to root
  * @param no param
  * @return true if the command have been sent, false if not 
  */
public boolean CD()
  {
    if (Open) {
      if (!Command ("PWD /"))
	return false;
      return true;
    }
    return false;
  }

/** Change the remote dir 
  * @param newdir the new remote directory 
  * @return true if the command have been sent, false if not 
  */
public boolean CD(String newdir)
  {
    if (Open) {
      if (!Command ("PWD "+newdir))
	return false;
      return true;
    }
    return false;
  }

/** Get the result sent by the Ftp server
  * @param no param
  */
public String GetResult()
  { 
    Init.println(ClassName,"Ftp Class : GetResult");
    return TextRes.toString();
  }
  
/** Clear the result sent by the Ftp server
  * @param no param
  */
public void ClearResult()
  {
    Init.println(ClassName,"Ftp Class : ClearResult");
    TextRes.setLength(0);
  }
  
/** Change the connection to bin
  * @param no param
  * @return true if the command have been sent, false if not 
  */
public boolean Bin()
  {
    if (Open) {
      if (!Command("TYPE I"))
	return false;
      return true;
    }
    return false;
  }

/** Change the connection to ascii
  * @return true if the command have been sent, false if not 
  */
public boolean Ascii()
  {
    if (Open) {
      if (!Command("TYPE A"))
	return false;
      return true;
    }
    return false;
  }

/** Close the remote connection
  * @param no param
  * @return true if the command have been sent, false if not 
  */
public boolean Close()
  {
    if (Open) {
      try {
	Command("QUIT");
	is.close();
	os.close();
	if (serverSocket != null)
	  serverSocket.close();
      }
      catch (java.io.IOException e)
	{
	  return false;
	}
    }
  return false;
  } 

/** Log on the ftp server with the curent username, and the curent password
  * @param no param
  */
public boolean Log_On()
    {
      int i;
      Init.println(ClassName," logon="+NbTriesLogOn);
      for(i =0;i<NbTriesLogOn;i++)
	{
	  if (!Command("USER " + User))
	    continue;
	  if (!Command("PASS " + Pass))
	    continue;
	  break;
	}
      if (i >= NbTriesLogOn)
	return false;

      if (!Command("TYPE I"))
	return false;
      return true;
    }

/** Log on the ftp server with a new username, and a new password
  * set the connection to bin 
  * @param user the new user name
  * @param pass the new password
  */
public boolean Log_On(String user, String pass)
  {
    User = user;
    Pass = pass;
    return Log_On();
  }

/** know if there is an opened connection
 * @param no param
 * @return true if a connection is opened, false if not
 */  
public boolean Open()
  {
    return Open;
  }
public String getUrl()
  {
    return Url;
  }

private boolean GetData(String Dest, StringBuffer Res) 
  {
    FileOutputStream fout;
    DataOutputStream fic = null;
    Socket clientSocket = null;
    DataInputStream is;
    
    try {
      Init.println(ClassName,"Ftp Class : GetData, waiting for data-server response");
      clientSocket = serverSocket.accept();
      
      if (Res == null)
	{
	  fout =  new FileOutputStream(Dest);
	  fic = new DataOutputStream(fout);
	}
      
      Init.println(ClassName,"Ftp Class : GetData, socket created");
      
      is = new DataInputStream(clientSocket.getInputStream());
    } catch (IOException e)
	  {
	    Init.println(ClassName,"Ftp Class : GetData, waiting for data-server response");
	    return false;
	  }

    for (;;) {
      try 
	{
	  if (Res != null) {
	    String s = is.readLine();
	    if (s == null)  {
	      clientSocket.close();
	      return true;
	    }
	    Res.append(s+"\n");
	  }
	  else
	    fic.writeByte(is.readByte());
	} catch (EOFException e)
	  {
	    return true;
	  } catch (Exception e)
	    {
	      Init.println(ClassName,"Ftp Class : Get Data Exception catched :"+e);
	      return false;
	    }
    }
  }
  
private boolean PutData(String Source) 
  {
    FileInputStream fout;
    DataInputStream fic = null;
    Socket clientSocket = null;
    DataOutputStream is;
    
    try {
      Init.println(ClassName,"Ftp Class : PutData, waiting for data-server response");
      
      clientSocket = serverSocket.accept();

      fout =  new FileInputStream(Source);
      fic = new DataInputStream(fout);

      
      
      
      Init.println(ClassName,"Ftp Class : PutData, socket created");
      
      is = new DataOutputStream(clientSocket.getOutputStream());
    } catch (IOException e)
	  {
	    Init.println(ClassName,"Ftp Class : PutData, waiting for data-server response");
	    return false;
	  }
    for (;;) {
      try 
	{
	  is.writeByte(fic.readByte());
	} catch (EOFException e)
	  {
	    return true;
	  } catch (Exception e)
	    {
	      Init.println(ClassName,"Ftp Class : PutData Exception catched :"+e);
	      return false;
	    }
    }
  }
/** erreurText
  * @param no param
  * @return a string with the last error
  */ 
public String erreurText()
  {
    String tmp = GetResult();
    ClearResult();
    return tmp;
  }

}

