summaryrefslogtreecommitdiff
path: root/Noddybox.WindowsPhone.FTP/FTPClient.cs
diff options
context:
space:
mode:
authorIan C <ianc@noddybox.co.uk>2012-10-01 20:59:12 +0000
committerIan C <ianc@noddybox.co.uk>2012-10-01 20:59:12 +0000
commite078a2887cf2dc61da49ce302324ff1485f95af9 (patch)
tree800c783a47576f7950497e5c8b7194ea439ae57a /Noddybox.WindowsPhone.FTP/FTPClient.cs
parentb228a31e12d8f30ed1f4bf03136cad3c41b1de45 (diff)
Added non-working code for an attempt at FTPHEADmaster
Diffstat (limited to 'Noddybox.WindowsPhone.FTP/FTPClient.cs')
-rw-r--r--Noddybox.WindowsPhone.FTP/FTPClient.cs828
1 files changed, 828 insertions, 0 deletions
diff --git a/Noddybox.WindowsPhone.FTP/FTPClient.cs b/Noddybox.WindowsPhone.FTP/FTPClient.cs
new file mode 100644
index 0000000..1ae13ce
--- /dev/null
+++ b/Noddybox.WindowsPhone.FTP/FTPClient.cs
@@ -0,0 +1,828 @@
+// 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 <http://www.gnu.org/licenses/>.
+//
+// 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
+{
+ /// <summary>
+ /// Provides a simple FTP client interface. Currently only supports IP4.
+ /// </summary>
+ 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)
+ {
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Sets timeouts for the command connection
+ /// </summary>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Set/get whether to use passive mode
+ /// </summary>
+ public bool Passive
+ {
+ get {return passive;}
+
+ set
+ {
+ passive=value;
+ }
+ }
+
+ /// <summary>
+ /// Set/get the IP address to offer in non-passive mode
+ /// </summary>
+ public IPAddress NonPassiveIP
+ {
+ get {return passiveAddress;}
+
+ set
+ {
+ 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 connected;}
+ }
+
+ /// <summary>
+ /// The welcome text after logging on
+ /// </summary>
+ public string Welcome
+ {
+ get {return 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 system;}
+ }
+
+ /// <summary>
+ /// The transfer type
+ /// </summary>
+ 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;
+ }
+ }
+ }
+
+ /// <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=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 lastCode;}
+ }
+
+ /// <summary>
+ /// Quit and disconnect from the server.
+ /// </summary>
+ 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<len;f++)
+ {
+ al.Add(buff[f]);
+ }
+ }
+ }
+
+ private void PutData(byte[] data)
+ {
+ int len=0;
+
+ while(len<data.Length)
+ {
+ len+=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 (connected)
+ {
+ r=server.Send(command,command);
+ StoreResult(r);
+ }
+
+ return r;
+ }
+
+ private ServerDialog.Reply BareResult()
+ {
+ ServerDialog.Reply r=new ServerDialog.Reply();
+
+ if (connected)
+ {
+ r=server.Send(command,null);
+ StoreResult(r);
+ }
+
+ return r;
+ }
+
+ private void StoreResult(ServerDialog.Reply r)
+ {
+ if (r.Code>-1)
+ {
+ lastCode=r.Code;
+ }
+
+ switch(r.Code)
+ {
+ case 215:
+ system = r.Text;
+ break;
+
+ case 230:
+ welcome = r.Text;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}