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