summaryrefslogtreecommitdiff
path: root/FTPClient.cs
diff options
context:
space:
mode:
Diffstat (limited to 'FTPClient.cs')
-rw-r--r--FTPClient.cs871
1 files changed, 871 insertions, 0 deletions
diff --git a/FTPClient.cs b/FTPClient.cs
new file mode 100644
index 0000000..0aabbbf
--- /dev/null
+++ b/FTPClient.cs
@@ -0,0 +1,871 @@
+// Noddybox.Net - Provides simple server interaction classes
+// Copyright (C) 2003 Ian Cowburn
+//
+// 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.
+//
+// $Id$
+//
+
+namespace Noddybox.Net
+{
+ using System;
+ using System.Text;
+ using System.IO;
+ using System.Net;
+ using System.Net.Sockets;
+ using System.Collections;
+ using System.Collections.Specialized;
+
+
+ /// <summary>
+ /// Provides a simple FTP client interface.
+ /// </summary>
+ /// <remarks>
+ /// created by - ianc
+ /// created on - 04/09/2003 00:06:57
+ /// </remarks>
+ public class FTPClient
+ {
+ // -------------------------------------------------
+ // - PUBLIC MEMBERS -
+ // -------------------------------------------------
+
+ /// <summary>
+ /// Transfer type
+ /// </summary>
+ public enum TransferType
+ {
+ /// <summary>
+ /// ASCII Data
+ /// </summary>
+ ASCII,
+
+ /// <summary>
+ /// Binary (image) Data
+ /// </summary>
+ Binary
+ };
+
+ /// <summary>
+ /// Default constructor
+ /// </summary>
+ public FTPClient(string server_address)
+ {
+ DebugOut("Server = " + server_address);
+
+ m_passive=false;
+ m_connected=true;
+ m_welcome="";
+ m_system="unknown";
+ m_type=TransferType.Binary;
+
+ m_passiveAddress=Dns.Resolve("localhost").AddressList[0];
+
+ m_lastCode=-1;
+
+ try
+ {
+ m_encode=new ASCIIEncoding();
+
+ m_server=new ServerDialog();
+
+ ServerDialog.Reply r;
+
+ m_commandClient=new TcpClient(server_address,21);
+ m_command=m_commandClient.GetStream();
+
+ r=BareResult();
+
+ DebugOut("On connection got code " + r.Code.ToString());
+
+ if (r.Code!=220)
+ {
+ m_connected=false;
+ m_commandClient=null;
+ m_command=null;
+ }
+ }
+ catch (SocketException e)
+ {
+ DebugOut("Caught " + e.ToString());
+ m_connected=false;
+ }
+ }
+
+ /// <summary>
+ /// Sets timeouts for the command connection
+ /// </summary>
+ public void SetTimeouts(int recv_timeout,
+ int send_timeout)
+ {
+ m_recvTimeout=recv_timeout;
+ m_sendTimeout=send_timeout;
+
+ if (m_connected)
+ {
+ m_commandClient.ReceiveTimeout=recv_timeout;
+ m_commandClient.SendTimeout=send_timeout;
+ }
+ }
+
+ /// <summary>
+ /// Set/get whether to use passive mode
+ /// </summary>
+ public bool Passive
+ {
+ get {return m_passive;}
+
+ set
+ {
+ m_passive=value;
+ }
+ }
+
+ /// <summary>
+ /// Set/get the IP address to offer in non-passive mode
+ /// </summary>
+ public IPAddress NonPassiveIP
+ {
+ get {return m_passiveAddress;}
+
+ set
+ {
+ m_passiveAddress=value;
+ }
+ }
+
+ /// <summary>
+ /// Logon to the FTP server
+ /// </summary>
+ public bool Logon(string username,string password)
+ {
+ ServerDialog.Reply r;
+
+ r=SendCommand("USER "+username);
+
+ if (r.Code==331)
+ {
+ r=SendCommand("PASS "+password);
+ }
+
+ if (r.Code==230)
+ {
+ SendCommand("SYST");
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Whether connected to the FTP server
+ /// </summary>
+ public bool Connected
+ {
+ get {return m_connected;}
+ }
+
+ /// <summary>
+ /// The welcome text after logging on
+ /// </summary>
+ public string Welcome
+ {
+ get {return m_welcome;}
+ }
+
+ /// <summary>
+ /// The system type. Note the entire system response is
+ /// returned. If you want just the first word, string.split()
+ /// it.
+ /// </summary>
+ public string System
+ {
+ get {return m_system;}
+ }
+
+ /// <summary>
+ /// The transfer type
+ /// </summary>
+ public TransferType Type
+ {
+ get
+ {
+ return m_type;
+ }
+
+ set
+ {
+ m_type=value;
+
+ switch(m_type)
+ {
+ case TransferType.ASCII:
+ SendCommand("TYPE A");
+ break;
+
+ case TransferType.Binary:
+ SendCommand("TYPE I");
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get the current working directory
+ /// </summary>
+ public string Pwd
+ {
+ get
+ {
+ string pwd="";
+
+ ServerDialog.Reply r=SendCommand("PWD");
+
+ if (r.Code==257)
+ {
+ pwd=ParseQuote(r.Text);
+ }
+
+ return pwd;
+ }
+ }
+
+ /// <summary>
+ /// Change directory to the parent directory
+ /// </summary>
+ public bool CDUp()
+ {
+ return SendCommand("CDUP").Code==250;
+ }
+
+ /// <summary>
+ /// Change directory
+ /// </summary>
+ public bool CD(string dir)
+ {
+ return SendCommand("CWD "+dir).Code==250;
+ }
+
+ /// <summary>
+ /// Get a list of the filenames in the current directory
+ /// </summary>
+ public StringCollection Dir()
+ {
+ StringCollection l=new StringCollection();
+ ServerDialog.Reply r;
+ TransferType old_type=m_type;
+
+ PrepareData();
+
+ Type=TransferType.ASCII;
+
+ r=SendCommand("NLST");
+
+ if (r.Code==150 || r.Code==125)
+ {
+ OpenData();
+
+ string s=GetDataString();
+ string fn="";
+
+ foreach (char c in s)
+ {
+ if (c=='\n')
+ {
+ l.Add(fn.TrimEnd(null));
+ fn="";
+ }
+ else
+ {
+ fn+=c;
+ }
+ }
+
+ CloseData();
+ }
+
+ BareResult();
+
+ Type=old_type;
+
+ return l;
+ }
+
+ /// <summary>
+ /// Get a detailed list of the filenames in the current directory.
+ /// The list returned is made up of FTPFile classes.
+ /// </summary>
+ public FTPFileCollection DirEx()
+ {
+ StringCollection l=Dir();
+ FTPFileCollection al=new FTPFileCollection();
+
+ foreach(string s in l)
+ {
+ FTPFile.FileType type=FTPFile.FileType.File;
+ ulong size;
+
+ // See if it's a directory by seeing if CD works
+ //
+ if (CD(s))
+ {
+ type=FTPFile.FileType.Dir;
+ size=0;
+ CDUp();
+ }
+ else
+ {
+ ServerDialog.Reply r;
+
+ // Get the file size
+ //
+ r=SendCommand("SIZE "+s);
+
+ if (r.Code==213)
+ {
+ size=Convert.ToUInt64(r.Text);
+ }
+ else
+ {
+ size=UInt64.MaxValue;
+ }
+ }
+
+ al.Add(new FTPFile(s,type,size));
+ }
+
+ al.Sort();
+
+ return al;
+ }
+
+ /// <summary>
+ /// Delete a specified file
+ /// </summary>
+ public bool Delete(string name)
+ {
+ return SendCommand("DELE "+name).Code==250;
+ }
+
+ /// <summary>
+ /// Delete a specified directory
+ /// </summary>
+ public bool Rmdir(string name)
+ {
+ return SendCommand("RMD "+name).Code==250;
+ }
+
+ /// <summary>
+ /// Create a specified directory
+ /// </summary>
+ public bool Mkdir(string name)
+ {
+ return SendCommand("MKD "+name).Code==257;
+ }
+
+ /// <summary>
+ /// Rename a specified file
+ /// </summary>
+ public bool Rename(string old_name, string new_name)
+ {
+ ServerDialog.Reply r;
+
+ r=SendCommand("RNFR "+old_name);
+
+ if (r.Code!=350)
+ {
+ return false;
+ }
+
+ r=SendCommand("RNTO "+new_name);
+
+ if (r.Code!=250)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Gets a file as a byte array
+ /// </summary>
+ public byte[] Get(string name)
+ {
+ ArrayList al=new ArrayList();
+
+ PrepareData();
+
+ ServerDialog.Reply r=SendCommand("RETR "+name);
+
+ if (r.Code==150 || r.Code==125)
+ {
+ OpenData();
+ GetData(al);
+ CloseData();
+ }
+ else
+ {
+ return null;
+ }
+
+ BareResult();
+
+ // Surely there must be a better way...
+ //
+ int count=al.Count;
+ byte[] file=new byte[count];
+
+ for(int f=0;f<count;f++)
+ {
+ file[f]=(byte)al[f];
+ }
+
+ return file;
+ }
+
+ /// <summary>
+ /// Gets a file and stores it in a local file
+ /// </summary>
+ public bool Get(string name, string local_name)
+ {
+ byte[] data=Get(name);
+
+ if (data==null)
+ {
+ return false;
+ }
+
+ FileStream file=new FileStream(local_name,FileMode.Create);
+ BinaryWriter bin=new BinaryWriter(file);
+
+ bin.Write(data);
+
+ bin.Close();
+
+ return true;
+ }
+
+ /// <summary>
+ /// Puts a file as a byte array
+ /// </summary>
+ public bool Put(string name, byte[] data)
+ {
+ PrepareData();
+
+ ServerDialog.Reply r=SendCommand("STOR "+name);
+
+ if (r.Code==150 || r.Code==125)
+ {
+ OpenData();
+ PutData(data);
+ CloseData();
+ }
+ else
+ {
+ return false;
+ }
+
+ r=BareResult();
+
+ return r.Code==226;
+ }
+
+ /// <summary>
+ /// Puts a file and from a local file
+ /// </summary>
+ public bool Put(string name, string local_name)
+ {
+ if (!File.Exists(local_name))
+ {
+ return false;
+ }
+
+ FileInfo info=new FileInfo(local_name);
+
+ FileStream file=new FileStream(local_name,FileMode.Open,FileAccess.Read);
+ BinaryReader bin=new BinaryReader(file);
+
+ byte[] data=bin.ReadBytes((int)info.Length);
+ bin.Close();
+
+ return Put(name,data);
+ }
+
+ /// <summary>
+ /// The last code returned by the server (see RFC 959)
+ /// </summary>
+ public int LastCode
+ {
+ get {return m_lastCode;}
+ }
+
+ /// <summary>
+ /// Quit and disconnect from the server.
+ /// </summary>
+ public void Quit()
+ {
+ if (m_connected)
+ {
+ SendCommand("QUIT");
+ m_command.Close();
+ }
+ }
+
+ // -------------------------------------------------
+ // - PRIVATE MEMBERS -
+ // -------------------------------------------------
+
+ private bool m_connected;
+
+ private TcpClient m_commandClient;
+
+ private Socket m_data;
+
+ private Socket m_nonPassive;
+
+ private IPEndPoint m_dataAddr;
+
+ private NetworkStream m_command;
+
+ private string m_welcome;
+
+ private string m_system;
+
+ private int m_lastCode;
+
+ private ServerDialog m_server;
+
+ private int m_recvTimeout;
+ private int m_sendTimeout;
+
+ private ASCIIEncoding m_encode;
+
+ private bool m_passive;
+
+ private TransferType m_type;
+
+ private IPAddress m_passiveAddress;
+
+ private void DebugOut(string s)
+ {
+ Debug.Out(this,s);
+ }
+
+ private string ParseQuote(string s)
+ {
+ string r="";
+ bool in_quote=false;
+
+ foreach (char c in s)
+ {
+ if (c=='\"')
+ {
+ in_quote=!in_quote;
+ }
+ else
+ {
+ if (in_quote)
+ {
+ r+=c;
+ }
+ }
+ }
+
+ return r;
+ }
+
+ private IPEndPoint ParseAddress(string s)
+ {
+ string r="";
+ bool in_quote=false;
+
+ foreach (char c in s)
+ {
+ if (c=='(' || c==')')
+ {
+ in_quote=!in_quote;
+ }
+ else
+ {
+ if (in_quote)
+ {
+ r+=c;
+ }
+ }
+ }
+
+ char[] sep=new char[1] {','};
+ string[] arg=r.Split(sep);
+
+ if (arg.Length!=6)
+ {
+ throw new NetException(NetException.Type.FTP_BadReplyAddress);
+ }
+
+ int port=Convert.ToInt32(arg[4])*256+Convert.ToInt32(arg[5]);
+
+ IPEndPoint ip=new IPEndPoint(IPAddress.Parse(arg[0] + "." +
+ arg[1] + "." +
+ arg[2] + "." +
+ arg[3]),
+ port);
+
+ return ip;
+ }
+
+ private void PrepareData()
+ {
+ if (m_passive)
+ {
+ ServerDialog.Reply r=SendCommand("PASV");
+
+ if (r.Code!=227)
+ {
+ throw new NetException(NetException.Type.FTP_NoPassive);
+ }
+
+ m_dataAddr=ParseAddress(r.Text);
+
+ DebugOut("PASV told to use " + m_dataAddr.ToString());
+
+ m_data=new Socket(AddressFamily.InterNetwork,
+ SocketType.Stream,
+ ProtocolType.Tcp);
+
+ m_data.Connect(m_dataAddr);
+ }
+ else
+ {
+ if (m_nonPassive==null)
+ {
+ try
+ {
+ m_nonPassive=new Socket(AddressFamily.InterNetwork,
+ SocketType.Stream,
+ ProtocolType.Tcp);
+
+ if (m_passiveAddress==IPAddress.None)
+ {
+ IPAddress[] host=Dns.Resolve(Dns.GetHostName()).AddressList;
+
+ if (host.Length==0)
+ {
+ throw new NetException(NetException.Type.ALL_NoLocalIP);
+ }
+
+ m_passiveAddress=host[0];
+ }
+
+ IPEndPoint ep=new IPEndPoint(m_passiveAddress,0);
+
+ m_nonPassive.Bind(ep);
+
+ ep=(IPEndPoint)m_nonPassive.LocalEndPoint;
+
+ byte[] a=ep.Address.GetAddressBytes();
+ int p=ep.Port;
+
+ int p1=p>>8;
+ int p2=p&0xff;
+
+ string portCmd="PORT " +
+ a[0].ToString() + "," +
+ a[1].ToString() + "," +
+ a[2].ToString() + "," +
+ a[3].ToString() + "," +
+ p1.ToString() + "," +
+ p2.ToString();
+
+ ServerDialog.Reply r=SendCommand(portCmd);
+
+ if (r.Code!=200)
+ {
+ m_nonPassive.Close();
+ m_nonPassive=null;
+ throw new NetException
+ (NetException.Type.FTP_NonPassiveProblem);
+ }
+
+ m_nonPassive.Listen(5);
+ }
+ catch (SocketException e)
+ {
+ m_nonPassive=null;
+ throw e;
+ }
+ }
+ }
+ }
+
+ private void OpenData()
+ {
+ if (m_passive)
+ {
+ // Connection already created by PrepareData()
+ }
+ else
+ {
+ // TODO : This could actually block indefinitely
+ //
+ m_data=m_nonPassive.Accept();
+
+ if (m_data==null)
+ {
+ throw new NetException
+ (NetException.Type.FTP_NonPassiveProblem);
+ }
+ }
+ }
+
+ private void CloseData()
+ {
+ DebugOut("Closing data connection");
+
+ m_data.Shutdown(SocketShutdown.Both);
+ m_data.Close();
+ m_data=null;
+
+ if (!m_passive)
+ {
+ DebugOut("Closing non-passive listener");
+
+ m_nonPassive.Close();
+ m_nonPassive=null;
+ }
+ }
+
+ private string GetDataString()
+ {
+ byte[] buff=new byte[1024];
+ string result="";
+
+ while(true)
+ {
+ int len=m_data.Receive(buff,buff.Length,SocketFlags.None);
+
+ DebugOut("Read " + len.ToString() + " bytes from data connection");
+
+ if (len==0)
+ {
+ break;
+ }
+
+ result+=m_encode.GetString(buff,0,len);
+ }
+
+ DebugOut("Read string '" + result + "' data connection");
+
+ return result;
+ }
+
+ private void GetData(ArrayList al)
+ {
+ byte[] buff=new byte[1024];
+
+ while(true)
+ {
+ int len=m_data.Receive(buff,buff.Length,SocketFlags.None);
+
+ DebugOut("Read " + len.ToString() + " bytes from data connection");
+
+ if (len==0)
+ {
+ break;
+ }
+
+ // This whole idea needs sorting - this will be painful
+ //
+ for(int f=0;f<len;f++)
+ {
+ al.Add(buff[f]);
+ }
+ }
+ }
+
+ private void PutData(byte[] data)
+ {
+ int len=0;
+
+ while(len<data.Length)
+ {
+ len+=m_data.Send(data,len,data.Length-len,SocketFlags.None);
+ }
+ }
+
+ private ServerDialog.Reply SendCommand(string command)
+ {
+ ServerDialog.Reply r=new ServerDialog.Reply();
+
+ command+="\r\n";
+
+ if (m_connected)
+ {
+ DebugOut("Sending command " + command);
+
+ r=m_server.Send(m_command,command);
+
+ DebugOut("Got reply code " + r.Code.ToString());
+ DebugOut("Got reply text " + r.Text);
+
+ StoreResult(r);
+ }
+
+ return r;
+ }
+
+ private ServerDialog.Reply BareResult()
+ {
+ ServerDialog.Reply r=new ServerDialog.Reply();
+
+ if (m_connected)
+ {
+ r=m_server.Send(m_command,null);
+
+ DebugOut("Got reply code " + r.Code.ToString());
+ DebugOut("Got reply text " + r.Text);
+
+ StoreResult(r);
+ }
+
+ return r;
+ }
+
+ private void StoreResult(ServerDialog.Reply r)
+ {
+ if (r.Code>-1)
+ {
+ m_lastCode=r.Code;
+ }
+
+ switch(r.Code)
+ {
+ case 215:
+ m_system=r.Text;
+ break;
+
+ case 230:
+ m_welcome=r.Text;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}