// This file is part of the Noddybox.WindowsPhone C# suite.
//
// Noddybox.Emulation 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 3 of the License, or
// (at your option) any later version.
//
// Noddybox.Emulation 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 Noddybox.Emulation. If not, see .
//
// Copyright (c) 2012 Ian Cowburn
//
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
namespace Noddybox.WindowsPhone.Net
{
///
/// Provides a simple FTP client interface. Currently only supports IP4.
///
public class FTPClient
{
// -------------------------------------------------
// - PUBLIC MEMBERS -
// -------------------------------------------------
///
/// Transfer type
///
public enum TransferType
{
///
/// ASCII Data
///
ASCII,
///
/// Binary (image) Data
///
Binary
};
///
/// Default constructor
///
public FTPClient(string server_address)
{
passive = false;
connected = true;
welcome = "";
system = "unknown";
type = TransferType.Binary;
passiveAddress = IPAddress.Loopback;
lastCode=-1;
try
{
encode = new ASCIIEncoding();
server = new ServerDialog();
ServerDialog.Reply r;
commandClient = new TcpClient(server_address,21);
command = commandClient.GetStream();
r = BareResult();
if (r.Code != 220)
{
connected=false;
commandClient=null;
command=null;
}
}
catch (SocketException e)
{
connected=false;
}
}
///
/// Sets timeouts for the command connection
///
public void SetTimeouts(int recv_timeout,
int send_timeout)
{
recvTimeout=recv_timeout;
sendTimeout=send_timeout;
if (connected)
{
commandClient.ReceiveTimeout=recv_timeout;
commandClient.SendTimeout=send_timeout;
}
}
///
/// Set/get whether to use passive mode
///
public bool Passive
{
get {return passive;}
set
{
passive=value;
}
}
///
/// Set/get the IP address to offer in non-passive mode
///
public IPAddress NonPassiveIP
{
get {return passiveAddress;}
set
{
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 connected;}
}
///
/// The welcome text after logging on
///
public string Welcome
{
get {return 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 system;}
}
///
/// The transfer type
///
public TransferType Type
{
get
{
return type;
}
set
{
type=value;
switch(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=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 lastCode;}
}
///
/// Quit and disconnect from the server.
///
public void Quit()
{
if (connected)
{
SendCommand("QUIT");
command.Close();
}
}
// -------------------------------------------------
// - PRIVATE MEMBERS -
// -------------------------------------------------
private bool connected;
private TcpClient commandClient;
private Socket data;
private Socket nonPassive;
private IPEndPoint dataAddr;
private NetworkStream command;
private string welcome;
private string system;
private int lastCode;
private ServerDialog server;
private int recvTimeout;
private int sendTimeout;
private ASCIIEncoding encode;
private bool passive;
private TransferType type;
private IPAddress passiveAddress;
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 (passive)
{
ServerDialog.Reply r=SendCommand("PASV");
if (r.Code!=227)
{
throw new NetException(NetException.Type.FTP_NoPassive);
}
dataAddr=ParseAddress(r.Text);
DebugOut("PASV told to use " + dataAddr.ToString());
data=new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
data.Connect(dataAddr);
}
else
{
if (nonPassive==null)
{
try
{
nonPassive=new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
if (passiveAddress==IPAddress.None)
{
IPAddress[] host=Dns.Resolve(Dns.GetHostName()).AddressList;
if (host.Length==0)
{
throw new NetException(NetException.Type.ALL_NoLocalIP);
}
passiveAddress=host[0];
}
IPEndPoint ep=new IPEndPoint(passiveAddress,0);
nonPassive.Bind(ep);
ep=(IPEndPoint)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)
{
nonPassive.Close();
nonPassive=null;
throw new NetException
(NetException.Type.FTP_NonPassiveProblem);
}
nonPassive.Listen(5);
}
catch (SocketException e)
{
nonPassive=null;
throw e;
}
}
}
}
private void OpenData()
{
if (passive)
{
// Connection already created by PrepareData()
}
else
{
// TODO : This could actually block indefinitely
//
data=nonPassive.Accept();
if (data==null)
{
throw new NetException
(NetException.Type.FTP_NonPassiveProblem);
}
}
}
private void CloseData()
{
DebugOut("Closing data connection");
data.Shutdown(SocketShutdown.Both);
data.Close();
data=null;
if (!passive)
{
DebugOut("Closing non-passive listener");
nonPassive.Close();
nonPassive=null;
}
}
private string GetDataString()
{
byte[] buff=new byte[1024];
string result="";
while(true)
{
int len=data.Receive(buff,buff.Length,SocketFlags.None);
DebugOut("Read " + len.ToString() + " bytes from data connection");
if (len==0)
{
break;
}
result+=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=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)
{
lastCode=r.Code;
}
switch(r.Code)
{
case 215:
system = r.Text;
break;
case 230:
welcome = r.Text;
break;
default:
break;
}
}
}
}