From a3f6697ffb872f553572627c73261cdd1ce55b67 Mon Sep 17 00:00:00 2001 From: Ian C Date: Fri, 21 Jan 2005 01:03:55 +0000 Subject: Initial checkin --- FTPClient.cs | 871 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 871 insertions(+) create mode 100644 FTPClient.cs (limited to 'FTPClient.cs') 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; + + + /// + /// Provides a simple FTP client interface. + /// + /// + /// created by - ianc + /// created on - 04/09/2003 00:06:57 + /// + public class FTPClient + { + // ------------------------------------------------- + // - PUBLIC MEMBERS - + // ------------------------------------------------- + + /// + /// Transfer type + /// + public enum TransferType + { + /// + /// ASCII Data + /// + ASCII, + + /// + /// Binary (image) Data + /// + Binary + }; + + /// + /// Default constructor + /// + 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; + } + } + + /// + /// Sets timeouts for the command connection + /// + 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; + } + } + + /// + /// Set/get whether to use passive mode + /// + public bool Passive + { + get {return m_passive;} + + set + { + m_passive=value; + } + } + + /// + /// Set/get the IP address to offer in non-passive mode + /// + public IPAddress NonPassiveIP + { + get {return m_passiveAddress;} + + set + { + m_passiveAddress=value; + } + } + + /// + /// Logon to the FTP server + /// + 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; + } + + /// + /// Whether connected to the FTP server + /// + public bool Connected + { + get {return m_connected;} + } + + /// + /// The welcome text after logging on + /// + public string Welcome + { + get {return m_welcome;} + } + + /// + /// The system type. Note the entire system response is + /// returned. If you want just the first word, string.split() + /// it. + /// + public string System + { + get {return m_system;} + } + + /// + /// The transfer type + /// + 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; + } + } + } + + /// + /// Get the current working directory + /// + public string Pwd + { + get + { + string pwd=""; + + ServerDialog.Reply r=SendCommand("PWD"); + + if (r.Code==257) + { + pwd=ParseQuote(r.Text); + } + + return pwd; + } + } + + /// + /// Change directory to the parent directory + /// + public bool CDUp() + { + return SendCommand("CDUP").Code==250; + } + + /// + /// Change directory + /// + public bool CD(string dir) + { + return SendCommand("CWD "+dir).Code==250; + } + + /// + /// Get a list of the filenames in the current directory + /// + 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; + } + + /// + /// Get a detailed list of the filenames in the current directory. + /// The list returned is made up of FTPFile classes. + /// + 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; + } + + /// + /// Delete a specified file + /// + public bool Delete(string name) + { + return SendCommand("DELE "+name).Code==250; + } + + /// + /// Delete a specified directory + /// + public bool Rmdir(string name) + { + return SendCommand("RMD "+name).Code==250; + } + + /// + /// Create a specified directory + /// + public bool Mkdir(string name) + { + return SendCommand("MKD "+name).Code==257; + } + + /// + /// Rename a specified file + /// + 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; + } + + /// + /// Gets a file as a byte array + /// + 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 + /// Gets a file and stores it in a local file + /// + 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; + } + + /// + /// Puts a file as a byte array + /// + 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; + } + + /// + /// Puts a file and from a local file + /// + 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); + } + + /// + /// The last code returned by the server (see RFC 959) + /// + public int LastCode + { + get {return m_lastCode;} + } + + /// + /// Quit and disconnect from the server. + /// + 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-1) + { + m_lastCode=r.Code; + } + + switch(r.Code) + { + case 215: + m_system=r.Text; + break; + + case 230: + m_welcome=r.Text; + break; + + default: + break; + } + } + } +} -- cgit v1.2.3