// 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; } } } }