Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Active FTP and Passive FTP Client in C#

0.00/5 (No votes)
14 May 2011 1  
This C# FTP library is a solution I had to come up with for a client; it supports both Passive FTP and Active FTP.

DISCLAIMER

This code is older code which I wrote a long time ago. This code is designed as an example of Passive and Active FTP. Therefore this code is not how professional production code should be written. Professional production code should be properly unit tested and if I were to write again, it would be using TDD or BDD.

Introduction

This C# FTP library is a solution I had to come up with for a client, it supports both Passive FTP and Active FTP. It does this by using an interface abstraction. So it enables you to use either Active FTP or Passive FTP without changing any code. The files included in the project are:

  • ActiveFTPImpl.cs – The implementation of the Active FTP
  • PassiveFTPImpl.cs – The implementation of the passive FTP
  • IFTPClient.cs – The abstraction that can be coded against

This is different from many FTP clients because most other clients make you change the implementation of the API if you change the client.

Using the API

For your reference, below I have listed the API for the abstraction.

public interface IFTPClient : IDisposable 
{
    void ChangeDirectory(string sDir);
    void CreateDirectory(string sDirectory);
    void DeleteFile(string sName, bool fIgnoreNotExist);
    string[] GetDirectoryList();
    void GetFile(string sName, System.IO.Stream stream);
    string[] GetFullDirectoryList();
    System.IO.Stream GetSendFileStream(string sName);
    void Logon(string sUser, string sPwd);
    void Open(string sHost, bool bEnableSSL);
    void Rename(string sFrom, string sTo);
    void SendFile(string sName, System.IO.Stream stream);
    void SetControlClear();
    bool SSLEnabled { get; } 
    bool SendFile(string sName, byte[] file);
    bool SendFileAsByte();
}

Here Is An Example Usage

Please note* you actually use the API like you would use passive FTP client. The active FTP client is made to look like a passive FTP client.

IFTPClient FTPClient;
FTPClient = Globals.GetFTPClient();
FTPClient.Open( Globals.Config.sFTPServer, false );
FTPClient.Logon( Globals.Config.sFTPUser, Globals.Config.sFTPPassword );
FTPClient.ChangeDirectory( Globals.Config.sFTPPath );
System.IO.MemoryStream msFTP = new MemoryStream();
FTPClient.GetFile( "xx.xx", msFTP ); 
FTPClient.ChangeDirectory( Globals.Config.sFTPPath + “/Moved” );
m_FTPClient.SendFile( "xx.xx", msFTP );
FTPClient.ChangeDirectory( Globals.Config.sFTPPath );

Diving into the Implementations

There is a huge difference in Active FTP vs Passive FTP in how they work under the covers.

Active FTP

Let’s start with Active FTP. In the ‘Logon’ or ‘Open’ methods, they don’t actually do a lot. They just set properties in the class. This is due to the way Active FTP works. It requires a new connection every time an operation is required.

public void Logon(string sUser, string sPwd)
{
    m_creds = new NetworkCredential(sUser, sPwd);
}
       public void Open(string sHost, bool bEnableSSL)
{
    m_host = sHost;
    m_ssl = bEnableSSL;
}

Then on the ActiveFTP client, when you Send or Receive, a new connection is made using the credentials supplied in the early methods. Taking the SendFile method for example, it makes the connection to the FTP Server on the fly when you call the method. It uses the built in .NET FtpWebRequest object.

public bool SendFile(string sName, byte[] file)
{
    FtpWebRequest reqFTP = pv_GetFTPRequest
	("ftp://" + m_host + "/" + m_dir + "/" + sName);
    reqFTP.Method = WebRequestMethods.Ftp.UploadFile;
    reqFTP.ContentLength = file.LongLength;
    using (Stream reqStr = reqFTP.GetRequestStream())
    {
        reqStr.Write(file, 0, file.Length);
    }
    return true;
}

Passive FTP

Passive FTP is technically different from Active FTP because Passive FTP creates a connection which is consistently open so when you call the open and logon commands, this actually makes a connection to the server.

public void Open(string sHost, bool bEnableSSL)
{
    m_bSupportsEPSV = true;     	// assume server supports EPSV command 
				// till we know otherwise

    m_ControlClient = new System.Net.Sockets.TcpClient(sHost, 21);
    m_sHost = sHost;
    m_ControlClient.ReceiveTimeout = 30000;
    m_ControlStream = m_ControlClient.GetStream();
    m_RawStreamReader = new System.IO.StreamReader(m_ControlStream, Encoding.ASCII);
    m_RawStreamWriter = new System.IO.StreamWriter(m_ControlStream, Encoding.ASCII);

    m_StreamReader = m_RawStreamReader;
    m_StreamWriter = m_RawStreamWriter;

    pv_CheckResponse(2, 2);

    if (bEnableSSL)
    {
        pv_SendCmd("AUTH", "TLS");

        pv_CheckResponse(2, 3);

        m_SSLControlStream = new System.Net.Security.SslStream(m_ControlStream, true,
            new System.Net.Security.RemoteCertificateValidationCallback(
            pv_CertValidationCallback));
        m_SSLControlStream.AuthenticateAsClient(sHost);

        m_SSLStreamReader = 
		new System.IO.StreamReader(m_SSLControlStream, Encoding.ASCII);
        m_SSLStreamWriter = 
		new System.IO.StreamWriter(m_SSLControlStream, Encoding.ASCII);

              m_StreamReader = m_SSLStreamReader;
        m_StreamWriter = m_SSLStreamWriter;

        pv_SendCmd("PBSZ", "0");
        pv_CheckResponse(2);

        pv_SendCmd("PROT", "P");
        pv_CheckResponse(2);

        m_bSSLEnabled = true;
    }
}

// logs
public void Logon(string sUser, string sPwd)
{
    pv_SendCmd("USER", sUser);
    pv_CheckResponse(3, 3);
    // note, should also check for 232 (pwd not required)

    pv_SendCmd("PASS", sPwd);
    pv_CheckResponse(2, 3);
}

As you can see above, there are two main methods in the passive FTP implementation. SendCmd and CheckResponse.

private void pv_SendCmd(string sCommand, string sParam)
{
    m_TraceSource.TraceInformation("Cmd: {0}{1}", sCommand, sParam);

    m_StreamWriter.Write(sCommand);
    if (sParam != null)
    {
        m_StreamWriter.Write(' ');
        m_StreamWriter.WriteLine(sParam);
    }
    else
        m_StreamWriter.WriteLine();
    m_StreamWriter.Flush();
}

private void pv_CheckResponse(int? niDigit1, int? niDigit2, int? niDigit3)
{
    int iResponseCode;
    string sResponse;

    pv_GetResponse(out iResponseCode, out sResponse);

    int iResp1, iResp2, iResp3;
    iResp1 = iResponseCode / 100;
    int iTemp = iResponseCode % 100;
    iResp2 = iTemp / 10;
    iTemp %= 10;
    iResp3 = iTemp;

    //  System.Console.WriteLine( "Response code {0}", iResponseCode );
    //  System.Console.WriteLine( "Expecting {0} {1} {2}", niDigit1, niDigit2, niDigit3 );

    if ((niDigit1.HasValue && (niDigit1.Value != iResp1))
        || (niDigit2.HasValue && (niDigit2.Value != iResp2))
        || (niDigit3.HasValue && (niDigit3.Value != iResp3))
        )
    {
        System.Text.StringBuilder msg = new StringBuilder();

        msg.AppendFormat("Unexpected response code {0} " + 
            "from ftp server.  Expected ", iResponseCode);
        if (niDigit1.HasValue)
            msg.Append(niDigit1.Value);
           else
            msg.Append('x');
        if (niDigit2.HasValue)
            msg.Append(niDigit2.Value);
        else
            msg.Append('x');
        if (niDigit3.HasValue)
            msg.Append(niDigit3.Value);
        else
            msg.Append('x');

        throw new Exception(msg.ToString());
    }
}

private void pv_GetResponse(out int iCode, out string sResponse)
{
    string sLine = m_StreamReader.ReadLine();

    m_TraceSource.TraceInformation("Resp: {0}", sLine);

    // response should be of format 'nnn-', or 'nnn ' for multiline response
    if (sLine.Length < 4)
        throw new Exception("Unexpected response from ftp server: " + sLine);

    try
    {
        string sCode = sLine.Substring(0, 3);
        iCode = System.Convert.ToInt32(sCode);
        sResponse = sLine.Substring(4);

        if (sLine[3] == '-')
        {
            while ((sLine.Length < 4)
                   || (sLine[0] == ' ')
                   || (sLine[3] != '-'))
            {
                // multi-line response, keep reading till we get final response
                sLine = m_StreamReader.ReadLine();
            }

            if (sLine.Length < 4)
                throw new Exception("Unexpected response from ftp server: " + sLine);
        }
    }
    catch (Exception e)
    {
        throw new Exception("Unexpected response from ftp server", e);
    }
}

Taking a look at GetFile, you can see it builds on top of the command listed before.

public void GetFile(string sName, System.IO.Stream stream)
{
    pv_EnterPassiveMode();

    pv_SendCmd("RETR", sName);

    pv_EstablishDataConnection();

    byte[] abBuffer = new byte[4096];
    int iBytes = m_DataStream.Read(abBuffer, 0, abBuffer.Length);
    while (iBytes != 0)
    {
        stream.Write(abBuffer, 0, iBytes);
        iBytes = m_DataStream.Read(abBuffer, 0, abBuffer.Length);
    }

    pv_CloseDataConnectionAndCheckResponse();
}

The best way to learn this is to download the sample code and have a dig around.

History

  • 14th May, 2011: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here