using System;
using System.Text;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Diagnostics;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
namespace PCFtp
{
public class FTP
{
#region Variables
private static int BUFFER_SIZE = 512;
private static Encoding ASCII = Encoding.ASCII;
private bool _doVerbose = false;
private string _ftpServer = "10.85.34.75"; //string.Empty;
private string _ftpPath = ".";
private string _ftpUsername = "user_upload";//string.Empty;
private string _ftpPassword = "WebCo123";//string.Empty;
private string statusMessage = string.Empty;
private string result = string.Empty;
private int _ftpPort = 21;
private int bytes = 0;
private int _statusCode = 0;
private bool _isLoggedIn = false;
private bool _isBinary = false;
private Byte[] buffer = new Byte[BUFFER_SIZE];
private Socket ftpSocket = null;
private int _timeOut = 10;
#endregion
#region Class Properties
#region Read/Write Properties
///
/// Display all communications to the debug log
///
public bool DoVerbose
{
get { return _doVerbose; }
set { _doVerbose = value; }
}
///
/// FTP Server port to use, default is usually 21
///
public int FtpPort
{
get { return _ftpPort; }
set { _ftpPort = value; }
}
///
/// Timeout waiting for a response from server, in seconds.
///
public int TimeoutValue
{
get { return _timeOut; }
set { _timeOut = value; }
}
///
/// Name of the FTP server we wish to connect to
///
///
public string FtpServer
{
get { return _ftpServer; }
set { _ftpServer = value; }
}
///
/// The remote port we wish to connect through
///
///
public int RemotePort
{
get { return _ftpPort; }
set { _ftpPort = value; }
}
///
/// The working directory
///
public string FtpPath
{
get { return _ftpPath; }
set { _ftpPath = value; }
}
///
/// Server username
///
public string FtpUsername
{
get { return _ftpUsername; }
set { _ftpUsername = value; }
}
///
/// Server password
///
public string FtpPassword
{
get { return _ftpPassword; }
set { _ftpPassword = value; }
}
///
/// If the value of mode is true, set
/// binary mode for downloads, else, Ascii mode.
///
public bool IsBinary
{
get { return _isBinary; }
set
{
//if _isBinary already exit
if (_isBinary == value) return;
//check the value being passed
//if it's true send the command
//for binary download
if (value)
Execute("TYPE I");
else
//otherwise stay in Ascii mode
Execute("TYPE A");
//now check the status code, if
//its not 200 throw an exception
if (_statusCode != 200)
{
throw new FtpException(result.Substring(4));
}
}
}
#endregion
#region ReadOnly Properties
///
/// determine if the user is logged in
///
public bool IsLoggedIn
{
get { return _isLoggedIn; }
}
///
/// returns the status code of the command
///
public int StatusCode
{
get { return _statusCode; }
}
#endregion
#endregion
#region Class Methods
#region FTPFtpLogin
///
/// method to log in to the remote ftp server
///
public void FtpLogin()
{
//check if the connection is currently open
if (_isLoggedIn)
{
//its open so we need to close it
CloseConnection();
}
//message that we're connection to the server
Debug.WriteLine("Opening connection to " + _ftpServer, "FtpClient");
//create our ip address object
IPAddress remoteAddress = null;
//create our end point object
IPEndPoint addrEndPoint = null;
try
{
//create our ftp socket
ftpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//retrieve the server ip
remoteAddress = Dns.GetHostEntry(_ftpServer).AddressList[0];
//set the endpoint value
addrEndPoint = new IPEndPoint(remoteAddress, _ftpPort);
//connect to the ftp server
ftpSocket.Connect(addrEndPoint);
}
catch (Exception ex)
{
// since an error happened, we need to
//close the connection and throw an exception
if (ftpSocket != null && ftpSocket.Connected)
{
ftpSocket.Close();
}
throw new FtpException("Couldn't connect to remote server", ex);
}
//read the host response
readResponse();
//check for a status code of 220
if (_statusCode != 220)
{
//failed so close the connection
CloseConnection();
//throw an exception
throw new FtpException(result.Substring(4));
}
//execute the USER ftp command (sends the username)
Execute("USER " + _ftpUsername);
//check the returned status code
if (!(_statusCode == 331 || _statusCode == 230))
{
//not what we were looking for so
//logout and throw an exception
LogOut();
throw new FtpException(result.Substring(4));
}
//if the status code isnt 230
if (_statusCode != 230)
{
//execute the PASS ftp command (sends the password)
Execute("PASS " + _ftpPassword);
//check the returned status code
if (!(_statusCode == 230 || _statusCode == 202))
{
//not what we were looking for so
//logout and throw an exception
LogOut();
throw new FtpException(result.Substring(4));
}
}
//we made it this far so we're logged in
_isLoggedIn = true;
//verbose the login message
Debug.WriteLine("Connected to " + _ftpServer, "FtpClient");
//set the initial working directory
ChangeWorkingDirectory(_ftpPath);
}
#endregion
#region CloseConnection
///
/// method to close the connection
///
public void CloseConnection()
{
//display the closing message
Debug.WriteLine("Closing connection to " + _ftpServer, "FtpClient");
//check to see if the connection is still active
//if it is then execute the ftp quit command
//which terminates the connection
if (ftpSocket != null)
{
Execute("QUIT");
}
//log the user out
LogOut();
}
#endregion
#region ListFiles
///
/// Return a string array containing the remote directory's file list.
///
///
///
public string[] ListFiles(string mask)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//create new socket
Socket dataSocket = OpenSocketForTransfer();
//execute the ftp nlst command, which
//returns a list of files on the remote server
Execute("NLST " + mask);
//check the return code, we're looking for
//either 150 or 125, otherwise the command failed
if (!(_statusCode == 150 || _statusCode == 125))
{
//failed, throw an exception
throw new FtpException(result.Substring(4));
}
//set the message to empty
statusMessage = "";
//create a timeout value based on our timeout property
DateTime timeout = DateTime.Now.AddSeconds(_timeOut);
//loop while out timeout value is
//greater than the current time
while (timeout > DateTime.Now)
{
//retrieve the data from the host
int bytes = dataSocket.Receive(buffer, buffer.Length, 0);
//convert it to Ascii format
statusMessage += ASCII.GetString(buffer, 0, bytes);
//exit the method is nothing is returned
if (bytes < buffer.Length) break;
}
//throw the returned message into a string array
string[] msg = statusMessage.Replace("\r", "").Split('\n');
//close the socket connection
dataSocket.Close();
//check the return message
if (statusMessage.IndexOf("No such file or directory") != -1)
//return an empty message
msg = new string[] { };
//read the host's response
readResponse();
//if we didnt receive a status code of 226
//then the process failed
if (_statusCode != 226)
//return an empty message
msg = new string[] { };
// throw new FtpException(result.Substring(4));
return msg;
}
#endregion
#region GetFileSize
///
/// Method to retrieve the size of the file based
/// on the name provided
///
/// Name of the file to get the size of
/// The files size
public long GetFileSize(string file)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//execute the size command, which
//returns the files size as a decimal number
Execute("SIZE " + file);
long fileSize = 0;
//check our returning status code
//if it's not 213 the command failed
if (_statusCode == 213)
{
//set the file size
fileSize = long.Parse(result.Substring(4));
}
else
{
//command failed so throw an exception
throw new FtpException(result.Substring(4));
}
//return the file size
return fileSize;
}
#endregion
#region DownloadFile
///
/// Download a remote file to a local file name which can include
/// a path, and set the resume flag. The local file name will be
/// created or overwritten, but the path must exist.
///
/// File on the server to download
/// Name of the file on the local machine
///
public void DownloadFile(string ftpFile, string localFile, Boolean resume)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
IsBinary = true;
//display a downloading file message
Debug.WriteLine("Downloading file " + ftpFile + " from " + _ftpServer + "/" + _ftpPath, "FtpClient");
//check if a local file name was provided
//if not then set its value to the ftp file name
if (localFile.Equals(""))
{
localFile = ftpFile;
}
//create our filestream object
FileStream output = null;
//check to see if the local file exists
//if it doesnt then create the file
//otherwise overwrite it
if (!File.Exists(localFile))
{
//create the new file
output = File.Create(localFile);
}
else
{
//overwrite the existsing file
output = new FileStream(localFile, FileMode.Open);
}
//create our new socket for the transfer
Socket dataSocket = OpenSocketForTransfer();
//create our resume point
long resumeOffset = 0;
//if resume was set to true
if (resume)
{
//set the value of our resume variable
resumeOffset = output.Length;
//check if its value is greater than 0 (zero)
if (resumeOffset > 0)
{
//execute our rest command, which sets the
//resume point for the download in case
//the download is interrupted
Execute("REST " + resumeOffset);
//check the status code, if not a 350
//code then c
if (_statusCode != 350)
{
//Server dosnt support resuming
resumeOffset = 0;
Debug.WriteLine("Resuming not supported:" + result.Substring(4), "FtpClient");
}
else
{
Debug.WriteLine("Resuming at offset " + resumeOffset, "FtpClient");
//seek to the interrupted point
output.Seek(resumeOffset, SeekOrigin.Begin);
}
}
}
//execute out retr command
//which starts the file transfer
Execute("RETR " + ftpFile);
//check the status code, we need 150 or 125
//otherwise the download failed
if (_statusCode != 150 && _statusCode != 125)
{
//throw an FtpException
throw new FtpException(result.Substring(4));
}
//set a timeout value
DateTime timeout = DateTime.Now.AddSeconds(_timeOut);
//check the timeout value against the current time
//if its less then download the file
while (timeout > DateTime.Now)
{
//receive the binary data from the socket
bytes = dataSocket.Receive(buffer, buffer.Length, 0);
//write the file
output.Write(buffer, 0, bytes);
//make sure the file is greater than
//zero in size, if not exit the method
if (bytes <= 0)
{
break;
}
}
//close our stream
output.Close();
//check to see if the socket is still open,
//if it is then close it
if (dataSocket.Connected)
{
dataSocket.Close();
}
//read the host's response
readResponse();
//we're looking for a status code of 226 or 250,
//if that isnt returned the download failed
if (_statusCode != 226 && _statusCode != 250)
{
throw new FtpException(result.Substring(4));
}
}
#endregion
#region UploadFile
///
/// Upload a file and set the resume flag.
///
///
///
public void UploadFile(string fileName, bool resume)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
Socket dataSocket = null;
long resumeOffset = 0;
//if resume is true
if (resume)
{
try
{
//set _isBinary to true
IsBinary = true;
//get the size of the file
resumeOffset = GetFileSize(Path.GetFileName(fileName));
}
catch (Exception)
{
// file not exist
resumeOffset = 0;
}
}
// open stream to read file
FileStream input = new FileStream(fileName, FileMode.Open);
//if resume is true
//and the size of the file read is
//less than the initial value
if (resume && input.Length < resumeOffset)
{
// different file size
Debug.WriteLine("Overwriting " + fileName, "FtpClient");
resumeOffset = 0;
}
else if (resume && input.Length == resumeOffset)
{
// file done
input.Close();
Debug.WriteLine("Skipping completed " + fileName + " - turn resume off to not detect.", "FtpClient");
return;
}
//now create our socket needed for
//the file transfer
dataSocket = OpenSocketForTransfer();
//if the file size is greater than 0
if (resumeOffset > 0)
{
//execute the rest command, which
//sets the point the resume will occurr
//if the upload is interrupted
Execute("REST " + resumeOffset);
//check the status code, if it's not
//350 the resume isnt supported by the server
if (_statusCode != 350)
{
Debug.WriteLine("Resuming not supported", "FtpClient");
resumeOffset = 0;
}
}
//execute the store ftp command (starts the transfer of the file)
Execute("STOR " + Path.GetFileName(fileName));
//check the status code, we need a
//value of 150 or 125, otherwise throw an exception
if (_statusCode != 125 && _statusCode != 150)
{
throw new FtpException(result.Substring(4));
}
//now check the resumeOffset value,
//if its not zero then we need to resume
//the upload process where it ended
if (resumeOffset != 0)
{
//let the user know the upload is resuming
Debug.WriteLine("Resuming at offset " + resumeOffset, "FtpClient");
//use the Seek method to get to where the upload ended
input.Seek(resumeOffset, SeekOrigin.Begin);
}
//let the user know the uploading has begun
Debug.WriteLine("Uploading file " + fileName + " to " + _ftpPath, "FtpClient");
//upload the file
while ((bytes = input.Read(buffer, 0, buffer.Length)) > 0)
{
dataSocket.Send(buffer, bytes, 0);
}
//close our reader
input.Close();
//check to see if the socket is still connected
//if it is then disconnect it
if (dataSocket.Connected)
{
dataSocket.Close();
}
//read the host's response
readResponse();
//checking for a successful upload code (226 or 250)
//if not either then throw an exception
if (_statusCode != 226 && _statusCode != 250)
{
throw new FtpException(result.Substring(4));
}
}
#endregion
#region UploadDirectory
///
/// Upload a directory and its file contents
///
/// Path of the directory to upload
/// Whether to recurse sub directories
/// Only upload files of the given mask(i.e;'*.*','*.jpg', ect..)
public void UploadDirectory(string dirPath, bool recursive, string fileMask)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
string[] directories = dirPath.Replace("/", @"\").Split('\\');
string rootDirectory = directories[directories.Length - 1];
// make the root dir if it does not exist
if (ListFiles(rootDirectory).Length < 1)
{
CreateDirectory(rootDirectory);
}
//make the new directory the working directory
ChangeWorkingDirectory(rootDirectory);
//loop through the files in the directory
foreach (string file in Directory.GetFiles(dirPath, fileMask))
{
//upload each file
UploadFile(file, true);
}
//check if recusrsive was specified
if (recursive)
{
//since recursive is true, we loop through all the
//directories in the directory provided
foreach (string directory in Directory.GetDirectories(dirPath))
{
//upload each directory
UploadDirectory(directory, recursive, fileMask);
}
}
//change working directory back to root level
ChangeWorkingDirectory("..");
}
#endregion
#region DeleteFile
///
/// method to delete a file from the FTP server.
///
/// File to delete
public void DeleteFile(string file)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//execute the delete command
Execute("DELE " + file);
//check for a status code of 250, if
//not then throw an exception
if (_statusCode != 250)
{
throw new FtpException(result.Substring(4));
}
Debug.WriteLine("Deleted file " + file, "FtpClient");
}
#endregion
#region RenameFile
///
/// Rename a file on the remote FTP server.
///
/// File to rename
/// New name of the file
/// setting to false will throw exception if it exists
public void RenameFile(string oldName, string newName, bool replace)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//execute the rename from command
Execute("RNFR " + oldName);
//check for a status code of 350
if (_statusCode != 350)
{
throw new FtpException(result.Substring(4));
}
//if they didnt choose to replace the file, and a
//file with that name already exists then throw an exception
if (!replace && ListFiles(newName).Length > 0)
{
throw new FtpException("File already exists");
}
//execute the rename to command
Execute("RNTO " + newName);
//check for a status code of 250, if
//not then throw an exception
if (_statusCode != 250)
{
throw new FtpException(result.Substring(4));
}
//write the successful message out
Debug.WriteLine("Renamed file " + oldName + " to " + newName, "FtpClient");
}
#endregion
#region CreateDirectory
///
/// Create a directory on the remote FTP server.
///
/// Name of the directory to create
public void CreateDirectory(string dirName)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//check to make sure a directory name was supplied
if (dirName == null || dirName.Equals(".") || dirName.Length == 0)
{
//no directory was provided so throw an exception
//and break out of the method
throw new FtpException("A directory name wasn't provided. Please provide one and try your request again.");
}
//execute the make directory command
Execute("MKD " + dirName);
//check for a status code of 250 or 257
if (_statusCode != 250 && _statusCode != 257)
{
//operation failed, throw an exception
throw new FtpException(result.Substring(4));
}
Debug.WriteLine("Created directory " + dirName, "FtpClient");
}
#endregion
#region RemoveDirectory
///
/// Delete a directory on the remote FTP server.
///
///
public void RemoveDirectory(string dirName)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//check to make sure a directory name was supplied
if (dirName == null || dirName.Equals(".") || dirName.Length == 0)
{
//no directory was provided so throw an exception
//and break out of the method
throw new FtpException("A directory name wasn't provided. Please provide one and try your request again.");
}
//execute the remove directory command
Execute("RMD " + dirName);
//check for a status code of 250
if (_statusCode != 250)
{
throw new FtpException(result.Substring(4));
}
//we made it this far so print the name
//of the removed directory to the window
Debug.WriteLine("Removed directory " + dirName, "FtpClient");
}
#endregion
#region ChangeWorkingDirectory
///
/// Change the current working directory on the remote FTP server.
///
///
public void ChangeWorkingDirectory(string dirName)
{
//check to make sure a directory name was supplied
if (dirName == null || dirName.Equals(".") || dirName.Length == 0)
{
//no directory was provided so throw an exception
//and break out of the method
throw new FtpException("A directory name wasn't provided. Please provide one and try your request again.");
}
//before we can change the directory we need
//to make sure the user is logged in
if (!_isLoggedIn)
{
//FtpLogin();
throw new FtpException("You need to log in before you can perform this operation");
}
//execute the CWD command = Change Working Directory
Execute("CWD " + dirName);
//check for a return status code of 250
if (_statusCode != 250)
{
//operation failed, throw an exception
throw new FtpException(result.Substring(4));
}
//execute the PWD command
//Print Working Directory
Execute("PWD");
//check for a status code of 250
if (_statusCode != 257)
{
//operation failed, throw an exception
throw new FtpException(result.Substring(4));
}
// we made it this far so retrieve the
//directory from the host response
_ftpPath = statusMessage.Split('"')[1];
Debug.WriteLine("Current directory is " + _ftpPath, "FtpClient");
}
#endregion
#region readResponse
///
///
///
private void readResponse()
{
statusMessage = "";
result = ParseHostResponse();
_statusCode = int.Parse(result.Substring(0,3));
}
#endregion
#region ParseHostResponse
///
/// Method to parse the response from the remote host
///
///
private string ParseHostResponse()
{
while(true)
{
//retrieve the host response and convert it to
//a byte array
bytes = ftpSocket.Receive(buffer,buffer.Length, 0);
//decode the byte array and set the
//statusMessage to its value
statusMessage += ASCII.GetString(buffer,0,bytes);
//check the size of the byte array
if ( bytes < buffer.Length )
{
break;
}
}
//split the host response
string[] msg = statusMessage.Split('\n');
//check the length of the response
if (statusMessage.Length > 2)
statusMessage = msg[msg.Length - 2];
else
statusMessage = msg[0];
//check for a space in the host response, if it exists return
//the message to the client
if (!statusMessage.Substring(3,1).Equals(" ")) return ParseHostResponse();
//check if the user selected verbose Debugging
if (_doVerbose)
{
//loop through the message from the host
for(int i = 0; i < msg.Length - 1; i++)
{
//write each line out to the window
Debug.Write( msg[i], "FtpClient" );
}
}
//return the message
return statusMessage;
}
#endregion
#region Execute
///
/// method to send the ftp commands to the remove server
///
/// the command to execute
private void Execute(String msg)
{
//check to see if verbose debugging is enabled
//if so write the command to the window
if (_doVerbose) Debug.WriteLine(msg,"FtpClient");
//convert the command to a byte array
Byte[] cmdBytes = Encoding.ASCII.GetBytes((msg + "\r\n").ToCharArray());
//send the command to the host
ftpSocket.Send(cmdBytes, cmdBytes.Length, 0);
//read the returned response
readResponse();
}
#endregion
#region OpenSocketForTransfer
///
/// when doing data transfers, we need to open another socket for it.
///
/// Connected socket
private Socket OpenSocketForTransfer()
{
//send the PASV command (Passive command)
Execute("PASV");
//check the status code, if it
//isnt 227 (successful) then throw an exception
if (_statusCode != 227)
{
throw new FtpException(result.Substring(4));
}
//find the index of the opening "("
//and the closing ")". The return
//message from the server, if successful, has
//the IP and port number for the client in
//enclosed in "(" & ")"
int idx1 = result.IndexOf('(');
int idx2 = result.IndexOf(')');
//now we need to get everything in the parenthesis
string ipData = result.Substring((idx1+1),(idx2-idx1)-1);
//create new integer array with size of 6
//the returning message is in 6 segments
int[] msgSegments = new int[6];
//get the length of the message
int msgLength = ipData.Length;
int partCount = 0;
string buffer = "";
//now we need to loop through the host response
for (int i = 0; i < msgLength && partCount <= 6; i++)
{
//convert each character to a char
char chr = char.Parse( ipData.Substring(i,1) );
//check to see if the current character is numeric
if (char.IsDigit(chr))
{
//since its a number we add it to our buffer variable
buffer+=chr;
}
//now we need to check for the
//comma seperating the digits
else if (chr != ',')
{
//no comma so throw an exception
throw new FtpException("Malformed PASV result: " + result);
}
else
{
//check to see if the current character is a comma
//or if the counter + 1 equals the host response length
if (chr == ',' || i + 1 == msgLength)
{
try
{
//since its one of the 2 we add it to the
//current index of the message segments
msgSegments[partCount++] = int.Parse(buffer);
buffer = "";
}
//handle any exceptions thrown
catch (Exception ex)
{
throw new FtpException("Malformed PASV result (not supported?): " + result, ex);
}
}
}
}
//now we assemble the IP address returned from the host
string ipAddress = msgSegments[0] + "."+ msgSegments[1]+ "." + msgSegments[2] + "." + msgSegments[3];
//the last 2 segments are the port we need to use
int port = (msgSegments[4] << 8) + msgSegments[5];
Socket tranferSocket = null;
IPEndPoint ipEndPoint = null;
try
{
//create our new socket for transfering data
tranferSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
ipEndPoint = new IPEndPoint(Dns.GetHostEntry(ipAddress).AddressList[0], port);
tranferSocket.Connect(ipEndPoint);
}
catch(Exception ex)
{
// doubtfull....
if ( tranferSocket != null && tranferSocket.Connected ) tranferSocket.Close();
//throw an FtpException
throw new FtpException("Can't connect to remote server", ex);
}
//return the socket
return tranferSocket;
}
#endregion
#region LogOut
///
/// method to release and remove any sockets left open
///
private void LogOut()
{
//check to see if the sock is non existant
if ( ftpSocket!=null )
{
//since its not we need to
//close it and dispose of it
ftpSocket.Close();
ftpSocket = null;
}
//log the user out
_isLoggedIn = false;
}
#endregion
#region Destructor
///
/// Destuctor
///
~FTP()
{
LogOut();
}
#endregion
#endregion
}
}