Chapter 6. FTP: Communicating with File Servers

Background

Anybody with experience in Web design knows that in order to put the site “live,” the Web page files need to be sent to a Web server provided by your hosting company or ISP. Most people never get to see the physical machine that their Web site is hosted on, and their only contact with it is through a file transfer protocol, or FTP, program such as cuteFTP or smartFTP.

FTP is the most common cross-platform file transfer mechanism between computers over the Internet. FTP software is freely available for all major operating systems, including Windows, UNIX, and Mac OS X. This cross-platform interoperability is very important for Web site development because most Web designers work on Windows and most Web servers run from UNIX, Linux, and Netware OS.

FTP as defined in RFC 1350 supersedes an older protocol known as trivial file transfer protocol (TFTP). This system is very seldom used on the Internet, but it can be used for procedures such as diskless booting on a network. It has no authentication facilities.

Microsoft file sharing

A competing technology developed by Microsoft is the Common Internet File (CIF) system. This is the native file-sharing protocol of Windows 2000 and XP. It is an extension of the earlier server message block (SMB) protocol used in prior versions of Windows. It is used to provide for the network drive functionality and print sharing. It is more secure than FTP, because of NTLM encryption, and generally faster; however, non-Windows implementations are not commonplace, but do exist for VMS and UNIX. The protocol is largely proprietary, which is often a deterrent to non-Microsoft developers.

Windows file sharing is most commonplace within office networks, where many employees share a printer or a central repository for files. From a programmer’s perspective, it is an ideal technology to use as a once-off solution at a company where all of the system users would be on the same internal network. If, for instance, an architecture firm were looking for a central repository for drawings, network share would be ideal because it requires no programming. The equivalent system using FTP would be slower, more awkward, and less secure; however, if the same firm wanted to share drawings with other firms, then FTP would be more suitable because of its interoperability and ease of deployment on Internet (rather than intranet) environments.

The terms NETBIOS and NETBEUI are the more correct names for Microsoft file and print sharing. A flavor of NETBIOS, NBT runs over IP, but all other forms are not based on IP addresses; they use NETBIOS host-names. These hostnames are resolved into physical addresses in one of four ways. They can broadcast the request on the network (B-Node). Alternately, they may query a WINS server (P-Node). Using a combination of these methods, by broadcasting before querying, is M-Node operation, and the reverse is H-Node operation.

Netware file sharing

This is somewhat of a dinosaur of file-transfer mechanisms, but it regularly appears in networks that have been in place for decades. It is, however, one of the fastest file transfer protocols over internal networks. It is built on top of the Internetworking packet exchange / Sequenced Packet Exchange (IPX/SPX) protocols and is thus nonroutable. Translators are available to convert these packets to TCP/IP, but the performance factor is lost.

The Netware system (also referred to as IntranetWare) is centered on a central Netware server. This server runs the Novell operating system, which is started from a bootstrap DOS application. The server hosts the Netware directory service (NDS), which is used to control authentication and privileges. Older Novell servers (3.x) use a bindery instead of NDS. The difference between the two systems is that the NDS is a relational database and can replicate among other servers, whereas the bindery cannot.

Novell clients are available for almost any platform, from DOS and Windows to Macintosh and UNIX. The clients locate the server by using the Novell core protocol (NCP). When a remote file server is found, it is mapped to a local drive on the client’s machine.

There is no native support for interoperating with Netware in .NET, and it is no small undertaking to integrate a .NET application with a Novell network. If you have to do so, look at the DOS command-line interfaces to the network, or failing that, try interfacing at the IPX level using raw sockets.

An overview of FTP

FTP operates on two ports: 21, the control socket, and a data socket, which can exist on port 20 or some other, high port. The definitive description of the protocol is found in RFC 959 at www.ietf.org/rfc/rfc959.txt.

Like the email protocols, the commands that flow between client and server are quite human readable and are broken up into lines, like English text; however, it is not feasible to upload or download files using FTP through telnet. If, however, you require a simple batch program to perform a routine upload of files to an FTP server, it may save time to look at the FTP.exe utility.

The FTP utility is a DOS-based program with a command-line interface. It can, however, accept script files with the –s command-line parameter, such that it can run autonomously. To try this utility, create a file named script.ftp containing the following text:

open www.eej.ulst.ac.uk
anonymous
[email protected]
cd lib
get libtermcap.so.2.0.8
quit

This FTP script file will open a connection to a server named www.eej.ulst.ac.uk. Log in anonymously, navigate to the lib folder, and download a file named libtermcap.so.2.0.8, and then exit. The downloaded file will be stored in the current local directory.

To run the script as shown in Figure 6.1, go to the command prompt, navigate to the location where script.ftp was saved, and then type the following keywords:

ftp -s:script.ftp
FTP MS-DOS utility.

Figure 6.1. FTP MS-DOS utility.

The technique of using the FTP utility is not the best-practice means of transferring files, but it is a simple and straightforward way to perform routine uploads when aesthetics and performance are not important. To leverage FTP from within a .NET application properly, it is necessary to be well-acquainted with the FTP protocol at a socket level, which is not all that dissimilar to learning to use the FTP utility command-line interface.

The FTP protocol facilitates more than uploading and downloading: It must also be able to accommodate all manner of file-manipulation tasks. This includes deleting, renaming, and navigating through folders. You cannot, however, edit the contents of files using FTP, unless you replace them completely.

Commands issued from client to server take the form

<keyword> <parameter> <enter>

Commands from server to client take the form:

<status code> <human and/or computer readable message>
<enter>

Table 6.1 lists the main groups for status codes.

Table 6.1. FTP status codes.

Status code range

Meaning

1xx

Positive preliminary reply. The command has begun on the server.

2xx

Positive completion reply. The command has been completed successfully.

3xx

Positive intermediate reply. The command has been accepted, but no action has been taken.

4xx

Transient negative completion reply. The command has been denied, but can be reissued later.

5xx

Permanent negative completion reply. The command has been denied and should not be reissued.

When you open an FTP connection to a server using an FTP client, you sometimes will be shown the raw data being sent to and from the server on the command socket. The text may resemble the following:

220 Serv-U FTP-Server v2.5k for WinSock ready...
    USER secret
331 User name okay, need password.
    PASS (hidden)
230 User logged in, proceed.
    PWD
257 "/" is current directory.
    TYPE A
200 Type set to A.
    PASV
227 Entering Passive Mode (212,17,38,3,11,144)
    LIST -aL
150 Opening ASCII mode data connection for /bin/ls.
226 Transfer complete.

This is a dump of the traffic on the command port. The data port is not shown.

How FTP uses ports

In the email protocols, sections of data of variable length (i.e., emails) could be suffixed with <enter>.<enter> to mark the end of the data. If this character sequence is detected within the body of the email, it could be removed before sending without any real degradation of the legibility of the email; however, in FTP, an executable file could quite easily have this sequence of characters embedded within it, and the removal of those characters could cause the file to corrupt.

To avoid this problem, port 21 is used to send and receive commands and responses, each terminated by an <enter>. When variable length data is sent between client and server, such as files or directory listings, a temporary connection is opened on port 20, the data is transferred, and the port is closed again. In most real-world FTP client implementations, however, the FTP client may be behind a firewall, so the server should do all the serving and the client should do all the requesting.

Passive-mode FTP is where the client instructs the server to listen on a port other than the default data port. The client will then connect to this port and use it for uploading and downloading as usual.

The response to the PASV command will always include a bracketed list of six numbers separated by commas. The first four digit groups represent the IP address of the server, and the final two groups represent the port the server is listening on for its data connection.

In the previous example, the four digits are 212,17,38,3,11,144. This means that the server is located at IP address 212.17.38.3 and listening on port 2960 (11 × 256 + 144).

The server will begin listening on the port as soon as it receives the PASV command. It will return a 227 message to indicate that it has begun listening on this port. Once the client connects to this port, the server will return a 150 message. If the client does not connect to the port in a timely fashion (a few seconds), the server will issue a 425 timeout message. The server will send the requested data on that port and close the connection once all of the data is sent, and then issue a 226 message.

The same process happens in reverse when uploading to the server. In this case, the PASV command is issued, and the client connects to the port specified by the server. The client then places the contents of the file on the new socket and closes the connection once the file is sent.

The FTP handshake

In the same way, FTP uses a basic authentication mechanism: It accepts a username and password in plain text, which can be seen by anyone using a protocol analyzer at any point between the client and the server. FTP over SSL is recommended when a Web site carries information of substantial value.

An FTP server may allow anonymous access. This is where the username is set to anonymous and the password can be anything. This is the default setup of the Microsoft FTP service.

When you connect to an FTP server on port 21, the server will respond as follows:

220 <some message><enter>

Using the same format as the POP3 handshake, the next commands to send are USER and PASS (in that order). The USER command is of this format:

USER <username><enter>

The server will generally respond with 331 and request a password, whether there is any record of that user on the system or not. This is to make brute-force attacks more difficult.

331 <some message><enter>

The PASS command must then be sent:

PASS <password><enter>

The server will either respond with a 530 message for a failed login or 230 for a successful login.

230 <some message><enter>

At this point, the user should have access to the FTP server. Depending on the privileges set on the FTP server, the user will be able to read or write operations within a limited section of the remote computer’s disk drives.

Some FTP servers will disconnect inactive users to save resources. Therefore, a periodic NOOP command will keep the FTP server from closing the connection. A NOOP command has no effect on the server beyond this task.

NOOP<enter>
200 <message><enter>

To close the connection, the client may simply close the underlying TCP connection, or issue a QUIT command as follows:

QUIT<enter>
221 <message><enter>

Navigating folders

In order to navigate around a remote computer’s file system, you need to know what files and subfolders are contained within each folder.

Like files, this data is returned on the data socket. The process for receiving folder listings is as follows:

  • Client issues LIST command.

  • Server waits for data socket to be created. A timeout will occur with a 425 response. Otherwise, a 125 response is received.

  • Server transfers file data, as illustrated below.

  • Server closes data connection and issues a 226 response on the control socket.

On the Windows FTP service, the default directory listing style is DOS. A listing would resemble the following:

01-18-03  03:22PM                    0 ge.bmp
01-18-03  11:40PM                  733 Project1.vbp
01-18-03  05:00PM                 2498 Readme.txt
01-18-03  03:40PM       <DIR>          wat

The five columns are last modified date, time, folder or file, size, and name, respectively.

For UNIX FTP servers, the directory listing style is in this format:

d---rw-rw-   1 user   group        0 Jan 18  2003 .
d---rw-rw-   1 user   group        0 Jan 18  2003 ..
----rw-rw-   1 user   group        0 Jan 18 15:22 ge.bmp
----rw-rw-   1 user   group      733 Jan 18 23:40 Project1.vbp
----rw-rw-   1 user   group     2498 Jan 18 17:00 Readme.txt
d---rw-rw-   1 user   group        0 Jan 18  2003 wat

Note

The Cerberus FTP server for Windows (www.cerberusftp.com) will also return file data in a UNIX format. The directory listing style is interchangeable in IIS.

This is an unfortunate lack of standardization, but something that developers must be aware of. A quick-and-dirty approach is to read the last word at the end of each line and assume it to be a file if there is a period in it.

A more foolproof implementation is to issue a SYST command to the server and read the response, either 215 UNIX<version><enter> or 215 Windows<version><enter>. Alternately, the NLST command may be used to receive a list of files (only) from the server.

The folder system in FTP is navigated in much the same way as in DOS. To move to a subfolder, the client issues CWD /<foldername><enter>, to which the server replies 250 for success or 550 for failure. To move to the parent folder, the client issues CDUP.

To retrieve the current folder, the client may issue PWD, to which the server replies:

257 "<folder name>"<message><enter>

FTP command reference

Following is a comprehensive list of FTP commands as would be issued by a client.

Table 6.2. FTP commands.

FTP Command

Action

RETR

Downloads

STOR

Uploads

STOU

Uploads, where the server chooses the name of the remote file; this name is specified in the 250 response

APPE

Appends

REST

Restarts file transfer at a specified position

RNFR

Renames a file (RNFR <old name>); must be followed by RNTO

RNTO

Renames a file (RNTO <new name>); must be preceded by RNFR

ABOR

Aborts the current data transfer

DELE

Deletes the specified file

RMD

Deletes the specified folder

MKD

Creates a new folder

PWD

Responds with the current working directory

LIST

Responds with the contents of the current working directory in human-readable format

NLST

Responds with a list of files in the current working directory

SITE

Provides proprietary FTP services

SYST

Responds with the name of the operating system (or the OS being emulated)

STAT

Responds with the status of a data transfer

HELP

Responds with human-readable text with information about the server

NOOP

No effect

USER

Specifies the username of the user

PASS

Specifies the password of the user

TYPE

Indicates the format of the data, either A for ASCII, E for EBCDIC, I for Binary, or L n to select a custom byte size (where n is the length of the byte)

Implementing FTP

To access an FTP server, you need to know its IP address and have a user-name and password with it. Most ISPs provide you with a small amount of Web space on their servers when you sign up, and you should be able to get these details if you call your ISP.

Some versions of Windows come with an option to install an FTP server. Click Control Panel→Add/Remove Programs→Add or Remove Windows Components→Internet Information Services→Details→FTP Service (Figure 6.2).

Windows: Add or remove components for IIS.

Figure 6.2. Windows: Add or remove components for IIS.

To administer the FTP server once it is installed, click Control Panel→Administrative Tools→Internet Information Services→FTP Site→Default FTP site. Then right-click and go to Properties.

Click on the Home Directory tab (Figure 6.3). This is where you can set the FTP site directory path, which is where uploaded FTP files are stored on your local hard disk. For the purposes of the code examples in this chapter, you should check both the read and write options.

FTP site administration.

Figure 6.3. FTP site administration.

To test out your FTP server, type ftp://localhost into Internet Explorer. You can download various FTP clients from the Internet (e.g., smartFTP, www.smartftp.com, or cuteFTP, www.globalscape.com).

Implementing FTP with the Internet Transfer Control

A full implementation of FTP is quite an undertaking. It may be worthwhile to consider the Microsoft Internet Transfer Control if you need to perform this task. It is a legacy COM control (and thus carries a lot of overhead for .NET applications). Native .NET components are available commercially from Dart and IP*Works.

Having said that, for many applications you don’t need an all-singing, all-dancing implementation of FTP to get your job done. If you are writing a feature to an application to perform a scheduled upload of files to a server, you probably don’t want to confuse the user with details of the remote computer’s directory structure. All you may need is a few lines of code to transfer the file to a predetermined location.

Create a new Windows application project in Visual Studio .NET as usual, and draw two textboxes, one named tbServer and the other tbFile. Add two buttons, btnBrowse and btnUpload. You will also require an Open File Dialog control named openFileDialog.

Click on the Browse button, and add the following code:

C#

private void btnBrowse_Click(object sender, System.EventArgs
e)
{
  openfileDialog.ShowDialog();
  tbFile.Text = openfileDialog.FileName;
}

VB.NET

Private  Sub btnBrowse_Click(ByVal sender As Object, _
 ByVal e As System.EventArgs) Handles btnBrowse.Click
  openfileDialog.ShowDialog()
  tbFile.Text = openfileDialog.FileName
End Sub

Now, double-click on the Upload button, and add this code:

C#

private void btnUpload_Click(object sender, System.EventArgs
e)
{
  FileInfo thisFile = new FileInfo(tbFile.Text);
  Type ITC;
  object[] parameter= new object[2];
  object ITCObject;

  ITC = Type.GetTypeFromProgID("InetCtls.Inet");
  ITCObject = Activator.CreateInstance(ITC);
  parameter[0] = (string)tbServer.Text;
  parameter[1] = (string)"PUT " + thisFile.FullName + " /" +
                   thisFile.Name;
  ITC.InvokeMember("execute", BindingFlags.InvokeMethod,
                     null, ITCObject, parameter);
}

VB.NET

Private Sub btnUpload_Click(ByVal sender As Object, _
 ByVal e As System.EventArgs) Handles btnUpload.Click
  Dim thisFile As FileInfo = New FileInfo(tbFile.Text)
    Dim ITC As Type
    Dim parameter() As Object = New Object(1) {}
    Dim ITCObject As Object

    ITC = Type.GetTypeFromProgID("InetCtls.Inet")
    ITCObject = Activator.CreateInstance(ITC)

    parameter(0) = CType(tbServer.Text, String)
    parameter(1) = CType("PUT " + thisFile.FullName + _
       " /" + thisFile.Name, String)
    ITC.InvokeMember("execute", BindingFlags.InvokeMethod, _
       Nothing, ITCObject, parameter)
End Sub

As mentioned earlier, the Internet Transfer Control (ITC) is a legacy COM control rather than a native .NET control. In Chapter 1, the Internet Explorer component (which was also COM) was used as part of an application to form a custom Web browser. This time, instead of including the COM control in the project by right-clicking on the toolbox and adding it there, we call the COM control directly through code.

This is slightly more complex, but offers the advantage of late binding (i.e., the object is loaded at run time rather than compile time). This gives the benefit of fault tolerance; in case the external COM control is accidentally deleted, the host application will still operate, albeit with degraded functionality. Late binding does incur a performance penalty because the code will need to determine the object’s supported methods and types at run time by interrogating the object’s IDispatch interface. The environment would already know the object’s interface if it had been early bound. Late binding is not strictly required for use with the ITC, but it is useful to learn new techniques.

Every COM control has a unique programmatic ID, or ProgID. This string is stored in the registry and is in the format <project name>.<Class name>.<version>. In this instance, the programmatic ID is InetCtls.Inet, with no version number.

The Activator creates an instance of the class at run time. At design time, there is no way of knowing the methods and properties of the object; therefore, the return value is of the generic object type. In order to call methods on an object that has unknown type (at design time at least), we use the InvokeMember method.

In order to invoke the execute method on the object and pass two parameters to it, we need first to define the two parameters. The first is the FTP address to which the object will connect. The second is the FTP command that the object will execute on the server. These two parameters are cast to strings and stored in an array. Finally, the InvokeMember method is called, passing the method name as the first parameter and the parameters to be sent to the COM control as the last parameter.

You will also need the relevant namespaces:

C#

using System.IO;
using System.Reflection;
using System.Threading;

VB.NET

Imports System.IO
Imports System.Reflection
Imports System.Threading

To test this piece of code, first ensure that your FTP server is running. After you have checked this, run the application from Visual Studio .NET. Type the IP address, username, and password into the Server textbox in the standard URL format (i.e., ftp://username:[email protected]). Choose a file from your hard disk by pressing the Browse button, then press Upload (Figure 6.4). Now check your FTP root to ensure that the file is there.

FTP client using COM.

Figure 6.4. FTP client using COM.

There is an important limitation in the ITC; that is that the file must be in the old DOS 8.3 format, where C:program filesmyile.txt becomes c:progra~1myfile.txt. Please note that you must have write access to the root of the FTP server; otherwise, the code example will not work.

The second parameter passed to the execute method of the ITC determines the action to be performed on the remote FTP server. Table 6.3 lists the possible actions.

Table 6.3. FTP command usage.

FTP command

Action

DIR /anyFolder

Retrieves the directory listing tree from the specified folder at the remote machine; listing can be retrieved using the GetChunk method

CDanyFolder

Moves to the specified folder on the remote machine

CDUP

Moves to the parent folder (if one exists) on the remote machine

GETanyFolder/anyFile.txt c:anyFile.txt

Downloads a remote file to a local file

PUT c:anyFile.txt anyFolder/anyFile.txt

Uploads a local file to a remote file

MKDIR /anyFolderName

Creates a directory on the remote machine

RMDIR anyFolderName

Removes a directory from the remote machine

RENAME oldFileName.txt newFileName.txt

Changes the name of a file on the remote machine

SIZE /anyFile.txt

Retrieves the size of a specified file

QUIT

Closes the connection to the FTP server

A more substantial implementation of FTP

The ITC has several limitations, contains quite a few well-known bugs, and is far from being a high-performance implementation. Furthermore, it is not native to .NET, and many software houses will demand that a .NET project is 100% managed code.

By following the code on the next few pages, you will have a full-fledged FTP client, with the ability to browse a remote file system, upload, and download.

Start a new project in Visual Studio .NET and add two forms, frmLogon and frmMain. On the Logon form, draw four textboxes: tbServer, tbUsername, tbPassword, and tbStatus. The latter should be set with multiline=true and greyed out appropriately. A button, btnLogon, should also be added.

On the Main form, draw two list boxes: lbFiles and lbFolders. Add a textbox named tbStatus in the same style as in the Logon form. Add three buttons: btnUpload, btnDownload, and btnRoot. Also add an File Open Dialog control named OpenFileDialog and a Save File Dialog control named SaveFileDialog.

In the Main form, add a few public variables:

C#

public frmLogon LogonForm = new frmLogon();
public NetworkStream NetStrm;
public string RemotePath = "";
public string server = "";

VB.NET

Public LogonForm As frmLogon = New frmLogon()
Public NetStrm As NetworkStream
Public RemotePath As String = ""
Public server As String = ""

In the Logon form, add the following public variable:

C#

public frmMain MainForm;

VB.NET

Public MainForm as frmMain

The call to new frmLogon() does not make the Logon form visible; instead, it is used to help ensure that only one instance of the Logon form occurs. The NetworkStream variable is used to represent the command socket connection. The data connection is less permanent and does not need to be defined globally. The two strings maintain information about where the server is and what the working folder is on the server.

Double-click on the Main form and add these lines:

C#

private void frmMain_Load(object sender, System.EventArgs e)
{
  LogonForm.MainForm = this;
  LogonForm.Visible = true;
}

VB.NET

Private  Sub frmMain_Load(ByVal sender As Object, _
ByVal e As System.EventArgs)
  LogonForm.MainForm = Me
  LogonForm.Visible = True
End Sub

This shows the Logon form and provides a means for the Logon form to call public methods on this particular instance of the Main form.

On the Logon from, comment out the Dispose method to ensure that it cannot be deleted from memory unless the Main form is closed. You may need to expand the “Windows Form Designer generated code” region to view this method.

C#

protected override void Dispose( bool disposing )
{ }

VB.NET

Protected Overrides Sub Dispose(ByVal disposing As Boolean)
End Sub

On the logon form, click on the Logon button, and enter the following code:

C#

private void btnLogon_Click(object sender, System.EventArgs
e)
{
  TcpClient clientSocket = new TcpClient(tbServer.Text,21);

  MainForm.NetStrm = clientSocket.GetStream();
  StreamReader RdStrm= new StreamReader(MainForm.NetStrm);
  tbStatus.Text = RdStrm.ReadLine();

  tbStatus.Text = MainForm.sendFTPcmd("USER "+
  tbUsername.Text + "
");
  tbStatus.Text = MainForm.sendFTPcmd("PASS "+
  tbPassword.Text+ "
");

  if (tbStatus.Text.Substring(0,3)!="230")
  {
    MessageBox.Show ("Failed to log in");
  }
  else
  {
    MainForm.server = tbServer.Text;
    MainForm.getRemoteFolders();
    MainForm.Text += "[logged in]";
    Visible=false;
  }
}

VB.NET

Private  Sub btnLogon_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)  Handles btnLogon.Click
  Dim clientSocket As TcpClient =  New _
  TcpClient(tbServer.Text,21)
  MainForm.NetStrm = clientSocket.GetStream()
  Dim RdStrm As StreamReader =  New _
  StreamReader(MainForm.NetStrm)
  tbStatus.Text = RdStrm.ReadLine()

  tbStatus.Text = MainForm.sendFTPcmd("USER "+ _
  tbUsername.Text + vbcrlf)
  tbStatus.Text = MainForm.sendFTPcmd("PASS "+ _
  tbPassword.Text+ vbcrlf)
  If tbStatus.Text.Substring(0,3)<>"230" Then
    MessageBox.Show ("Failed to log in")
  Else
    MainForm.server = tbServer.Text
    MainForm.getRemoteFolders()
    MainForm.Text += "[logged in]"
    Visible=False
  End If
End Sub

This code opens a TCP connection to the FTP server on port 21. Once the connection has been made, a stream to the remote host is established. This stream is then extended through a StreamReader. The welcome message from the server is read in and displayed on-screen.

The program then attempts to log in using the username and password supplied. If the FTP server responds with a 230 message, then a message box is displayed. Otherwise, the getRemoteFolders() method is called on the main form, a “Logged in” message appears in the form caption, and the logon form disappears.

The function sendFTPcmd() has not yet been implemented, so insert this code into the Main form:

C#

public string sendFTPcmd(string cmd)
{
  byte[] szData;
  string returnedData = "";
  StreamReader RdStrm= new StreamReader(NetStrm);
  szData = Encoding.ASCII.GetBytes(cmd.ToCharArray());
  NetStrm.Write(szData,0,szData.Length);
  tbStatus.Text += "
Sent:" + cmd;
  returnedData = RdStrm.ReadLine();
  tbStatus.Text += "
Rcvd:" + returnedData;
  return returnedData;
}

VB.NET

Public Function sendFTPcmd(ByVal cmd As String) As String
  Dim szData() As Byte
  Dim ReturnedData As String =  ""
  Dim RdStrm As StreamReader =  New StreamReader(NetStrm)
  szData = Encoding.ASCII.GetBytes(cmd.ToCharArray())
  NetStrm.Write(szData,0,szData.Length)
  tbStatus.Text += vbcrlf +"Sent:" + cmd
  ReturnedData = RdStrm.ReadLine()
  tbStatus.Text += vbcrlf +"Rcvd:" + ReturnedData
  Return ReturnedData
End Function

This code sends a string on the command socket via the public NetworkStream NetStrm. This stream is passed to the constructor of a StreamReader to facilitate easier reading of the stream. The string passed into the function is converted to a character array and sent over the wire using the Write method. The outgoing command is printed on screen. The StreamReader reads incoming data up to the end-of-line character and then displays this on-screen. Finally, the data received is returned to the calling function.

As seen earlier, when the Logon button is pressed, a call is made to getRemoteFolders(). We can now implement this in the Main form:

C#

public void getRemoteFolders()
{
  string[] filesAndFolders;
  string fileOrFolder;
  string folderList="";
  int lastSpace=0;
  folderList =
  Encoding.ASCII.GetString(sendPassiveFTPcmd("LIST
"));
  lbFiles.Items.Clear();
  lbFolders.Items.Clear();
  filesAndFolders = folderList.Split("
".ToCharArray());
  for(int i=0;i<filesAndFolders.GetUpperBound(0);i++)
  {
    if (filesAndFolders[i].StartsWith("-") ||
    filesAndFolders[i].StartsWith("d"))
    {
      lastSpace=59; // UNIX format
    }
    else
    {
      lastSpace=39; // DOS format
    }
    fileOrFolder =  filesAndFolders[i].Substring(lastSpace);
    if (fileOrFolder.IndexOf(".")!=-1)
    {
      lbFiles.Items.Add(fileOrFolder.Trim());
    }
    else
    {
      lbFolders.Items.Add(fileOrFolder.Trim());
    }
  }
}

VB.NET

Public  Sub getRemoteFolders()
  Dim filesAndFolders() As String
  Dim fileOrFolder As String
  Dim folderList As String = ""
  Dim lastSpace As Integer = 0
  folderList = Encoding.ASCII.GetString(sendPassiveFTPcmd _
    ("LIST" + vbCrLf))
  lbFiles.Items.Clear()
  lbFolders.Items.Clear()
  filesAndFolders = folderList.Split(vbCr.ToCharArray())
  Dim i As Integer
  For i = 0 To filesAndFolders.GetUpperBound(0) - 1
    If filesAndFolders(i).StartsWith("-") Or _
    filesAndFolders(i).StartsWith("d") Then
      lastSpace = 59 ' UNIX format
    Else
     lastSpace = 39 ' DOS format
     End If
    fileOrFolder = filesAndFolders(i).Substring(lastSpace)
    If fileOrFolder.IndexOf(".") <> -1 Then
      lbFiles.Items.Add(fileOrFolder.Trim())
    Else
      lbFolders.Items.Add(fileOrFolder.Trim())
    End If
  Next
End Sub

This uses the quick-and-dirty method of pulling file and folder details out of the file data received from the FTP server. It issues the LIST FTP command via a passive connection. The passive connection is required because the data returned could potentially be quite large and contains many lines of information.

The data returned is then split into lines by delimiting the string by the end-of-line character and applying the Split method. Going through these lines one by one, everything is stripped off that precedes the final space. If the remaining string contains a period, then it is added to the file list; if not, it is added to the folder list.

In order to receive data from a passive connection, we need to implement the sendPassiveFTPcmd() function:

C#

public byte[] sendPassiveFTPcmd(string cmd)
{
  byte[] szData;
  System.Collections.ArrayList al = new ArrayList();
  byte[] RecvBytes = new byte[Byte.MaxValue];
  Int32 bytes;
  Int32 totalLength=0;
  szData =
  System.Text.Encoding.ASCII.GetBytes(cmd.ToCharArray());
  NetworkStream passiveConnection;
  passiveConnection = createPassiveConnection();
  tbStatus.Text += "
Sent:" + cmd;
  StreamReader commandStream= new StreamReader(NetStrm);
  NetStrm.Write(szData,0,szData.Length);
  while(true)
  {
    bytes = passiveConnection.Read(RecvBytes,
    0,RecvBytes.Length);
    if (bytes<=0) break;
    totalLength += bytes;
    al.AddRange(RecvBytes);
  }
  al = al.GetRange(0,totalLength);
  tbStatus.Text+="
Rcvd:"+commandStream.ReadLine(); // 125
  tbStatus.Text+="
Rcvd:"+commandStream.ReadLine(); // 226
  return (byte[])al.ToArray((new byte()).GetType());
}

VB.NET

Public Function sendPassiveFTPcmd(ByVal cmd As String) _
 As Byte()
  Dim szData() As Byte
  Dim al As New System.Collections.ArrayList
  Dim bytes As Int32
  Dim RecvBytes(Byte.MaxValue) As Byte
  szData = _
  System.Text.Encoding.ASCII.GetBytes(cmd.ToCharArray())
  Dim totalLength As Int32 = 0
  Dim passiveConnection As NetworkStream
  passiveConnection = createPassiveConnection()
  tbStatus.Text += vbCrLf + "Sent:" + cmd
  Dim commandStream As StreamReader = New _
  StreamReader(NetStrm)
  NetStrm.Write(szData, 0, szData.Length)
  Do While (True)
    bytes = passiveConnection.Read(RecvBytes, 0, _
    RecvBytes.Length)
    If bytes <= 0 Then Exit Do
    totalLength += bytes
    al.AddRange(RecvBytes)
  Loop
  al = al.GetRange(0, totalLength)
  tbStatus.Text += vbCrLf + "Rcvd:" + _
  commandStream.ReadLine() ' 125
  tbStatus.Text += vbCrLf + "Rcvd:" + _
  commandStream.ReadLine() ' 226
  Return CType(al.ToArray((New Byte).GetType()), Byte())
End Function

This code requests a passive connection to the server via the createPassiveConnection() function. It then sends the string on the command socket, and then reads everything sent on the passive connection until the server closes it. This data is then returned to the calling function. Any data sent back on the command socket is more or less ignored, apart from some on-screen reporting.

The next step is to implement the createPassiveConnection() function:

C#

private NetworkStream createPassiveConnection()
{
  string[] commaSeperatedValues;
  int highByte =0;
  int lowByte =0;
  int passivePort =0;
  string response="";
  response = sendFTPcmd("PASV
");
  // 227 Entering Passive Mode (127,0,0,1,4,147).
  commaSeperatedValues = response.Split(",".ToCharArray());
  highByte = Convert.ToInt16(commaSeperatedValues[4]) * 256;
  commaSeperatedValues[5] =
  commaSeperatedValues[5].Substring(0,
  commaSeperatedValues[5].IndexOf(")"));
  lowByte = Convert.ToInt16(commaSeperatedValues[5]);
  passivePort = lowByte + highByte;
  TcpClient clientSocket = new TcpClient(server,passivePort);
  NetworkStream pasvStrm = clientSocket.GetStream();
  return pasvStrm;
}

VB.NET

Private Function createPassiveConnection() As NetworkStream
  Dim commaSeperatedValues() As String
  Dim highByte As Integer = 0
  Dim lowByte As Integer = 0
  Dim passivePort As Integer = 0
  Dim response As String = ""
  response = sendFTPcmd("PASV"+vbCrLf)
  ' 227 Entering Passive Mode (127,0,0,1,4,147).
  commaSeperatedValues = response.Split(",".ToCharArray())
  highByte = Convert.ToInt16(commaSeperatedValues(4)) * 256
  commaSeperatedValues(5) = _
  commaSeperatedValues(5).Substring(0, _
  commaSeperatedValues(5).IndexOf(")"))
  lowByte = Convert.ToInt16(commaSeperatedValues(5))
  passivePort = lowByte + highByte
  Dim clientSocket As TcpClient= New _
  TcpClient(server,passivePort)
  Dim pasvStrm As NetworkStream=clientSocket.GetStream()
  Return pasvStrm
End Function

This function issues a PASV command on the command socket to the server. The received data should resemble the following:

227 Entering Passive Mode (127,0,0,1,4,147)

The final two numbers indicate the port number of the socket that the server has just begun to listen on. In this case, it is 1171 (4 × 256 + 147). To extract these numbers, the string is first split into smaller strings, which are divided up using the comma character in the Split method.

The low-order byte of the new port number is followed by a closed bracket. This bracket must be stripped off before it will convert to an integer. To do this, the IndexOf method locates the superfluous bracket, and Substring removes everything following the final digit.

Once the passive port has been determined, the function then opens a TCP connection to the server on this port. The resultant NertworkStream is sent back to the calling function.

At this point, you can compile and run the code from Visual Studio .NET and check that you can log onto any FTP server and view the root directory listing. The next step is to add the folder-browsing capabilities. We can implement the event that is fired when a user clicks on a folder in the folder list. Click on the folder list, and type this code:

C#

private void lbFolders_SelectedIndexChanged(object sender,
System.EventArgs e)
{
  RemotePath += "/" + lbFolders.SelectedItem.ToString();
  sendFTPcmd("CWD /" + RemotePath +"
");
  getRemoteFolders();
}

VB.NET

Private  Sub lbFolders_SelectedIndexChanged(ByVal _
sender As Object, ByVal e As System.EventArgs) Handles _
 lbFolders.SelectedIndexChanged
  RemotePath += "/" + lbFolders.SelectedItem.ToString()
  sendFTPcmd("CWD /" + RemotePath + vbcrlf)
  getRemoteFolders()
End Sub

The purpose of building up the RemotePath variable is that when the next list of folders is shown, the application must remember that each listing corresponds to /folder/subfolder rather than /subfolder. The folder and file lists are refreshed once the operation is complete. We could, of course, have used the PWD command to get the path from the server, but it is probably easier and quicker to store this information locally. To instruct the FTP server to move to a working directory, the CWD command is used.

This gives a user the means to drill down directories, but no means of returning back up. In this case, we can double-click on btnRoot:

C#

private void btnRoot_Click(object sender, System.EventArgs e)
{
  RemotePath = "/";
  sendFTPcmd("CWD /
");
  getRemoteFolders();
}

VB.NET

Private  Sub btnRoot_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
  RemotePath = "/"
  sendFTPcmd("CWD /" + vbcrlf)
  getRemoteFolders()
End Sub

This resets the working folder to the root and sends a command to the FTP server to return to the FTP root. It then refreshes the file and folder lists. Again, the CWD command is issued to instruct the FTP server to move working folders.

Now, to implement the core purpose of this application, double-click the Upload button:

C#

private void btnUpload_Click(object sender, System.EventArgs
e)
{
  openFileDialog.ShowDialog();
  NetworkStream passiveConnection;
  FileInfo fileParse = new FileInfo(openFileDialog.FileName);
  FileStream fs = new
  FileStream(openFileDialog.FileName,FileMode.Open);
  byte[] fileData = new byte[fs.Length];
  fs.Read(fileData,0,(int)fs.Length);
  passiveConnection = createPassiveConnection();
  string cmd = "STOR " + fileParse.Name + "
";
  tbStatus.Text += "
Sent:" + cmd;
  string response = sendFTPcmd(cmd);
  tbStatus.Text += "
Rcvd:" + response;
  passiveConnection.Write(fileData,0,(int)fs.Length);
  passiveConnection.Close();
  MessageBox.Show("Uploaded");
  tbStatus.Text += "
Rcvd:" + new
  StreamReader(NetStrm).ReadLine();  getRemoteFolders();
}

VB.NET

Private  Sub btnUpload_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
  openFileDialog.ShowDialog()
  Dim passiveConnection As NetworkStream
  Dim fileParse As FileInfo = New _
  FileInfo(openFileDialog.FileName)
  Dim fs As New FileStream(openFileDialog.FileName, _
  FileMode.Open)
  Dim fileData(fs.Length) As Byte
  fs.Read(fileData, 0, fs.Length)
  passiveConnection = createPassiveConnection()
  Dim cmd As String = "STOR " + fileParse.Name + vbCrLf
  tbStatus.Text += vbCrLf + "Sent:" + cmd
  Dim response As String = sendFTPcmd(cmd)
  tbStatus.Text += vbCrLf + "Rcvd:" + response
  passiveConnection.Write(fileData, 0, fs.Length)
  passiveConnection.Close()
  MessageBox.Show("Uploaded")
  tbStatus.Text += vbCrLf + "Rcvd:" + New _
  StreamReader(NetStrm).ReadLine()
  getRemoteFolders()
End Sub

This function opens the standard File Open dialog and then reads the contents of the specified file into a string by passing the filename to a StreamReader, and reading to the end of the file while storing the data in the fileData string.

A passive connection is then opened to the server, and a STOR command is issued on the command socket. Once the server has responded to the STOR command, the file contents are placed on the passive connection. Once all of the data is sent, the connection is closed. A message is displayed on screen, and the file and folder list is refreshed.

Note that you do not have to pass the local path of the file to the FTP server because the file will be stored in the current working folder. The filename, minus its path, is obtained from the FileName property of the FileInfo class.

Finally, the download functionality can be implemented by clicking on the Download button:

C#

private void btnDownload_Click(object sender,
System.EventArgs e)
{
  byte[] fileData;
  saveFileDialog.ShowDialog();
  fileData = sendPassiveFTPcmd(
         "RETR " + lbFiles.SelectedItem.ToString()+ "
");
  FileStream fs = new FileStream(
            saveFileDialog.FileName,FileMode.CreateNew);
  fs.Write(fileData,0,fileData.Length);
  fs.Close();
  MessageBox.Show("Downloaded");
}

VB.NET

Private  Sub btnDownload_Click(ByVal sender As Object, _
 ByVal e As System.EventArgs)
  Dim fileData As Byte()
  saveFileDialog.ShowDialog()
  fileData = sendPassiveFTPcmd( _
   "RETR " + lbFiles.SelectedItem.ToString() + vbCrLf)
  Dim fs As FileStream = New FileStream( _
    saveFileDialog.FileName, FileMode.CreateNew)
  fs.Write(fileData, 0, fileData.Length)
  fs.Close()
  MessageBox.Show("Downloaded")
End Sub

This event opens the standard File Save dialog window. The file to be downloaded is the one that is currently selected in the files list. A RETR command is issued via a passive connection. The returned data is the contents of the remote file. In order to write this data to the local disk, a new FileStream is created. The FileStream constructor includes the name of the local file. The data to be written to disk is converted from a string into a byte array and then passed to the write method of the stream. A message is shown to signify the end of the operation.

Add these namespaces to both forms, and we’re ready to go:

C#

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;

VB.NET

Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.IO

To test the application, ensure that you have an FTP server running, and then execute the application from Visual Studio .NET. Note that when compiling this application, if you are prompted for “Sub Main” (VB.NET), please select the Main form for this purpose. When the application is running, you may enter your FTP account details into the Logon window (Figure 6.5) and press Logon.

FTP client Logon dialog.

Figure 6.5. FTP client Logon dialog.

On the next screen (Figure 6.6), you can navigate around the remote computer’s file system by clicking on the folder names in the list provided. To return to where you started, press the root button.

FTP client file-management dialog.

Figure 6.6. FTP client file-management dialog.

To upload a file, click Upload, then select a file. Wait until you receive a message box, and then you should see the file in the files list. To download a file, click on a file in the files list, and then press Save. Choose a destination for the file (e.g., the desktop). When you press OK, wait until you see a message box, and then you should see the file on the desktop.

FTP support in .NET 2.0

In the .NET Framework version 2.0 (Whidbey), FTP support is included in the WebClient class, thus negating the need to use either socket-level programming or COM objects. The following code illustrates the simplicity of this new feature:

C#

public void downloadFile()
{
 string filename = "ftp://ms.com/files/dotnetfx.exe";
 WebClient client = new WebClient();
 client.DownloadFile(filename,"dotnetfx.exe");
}

VB.NET

Sub downloadFile()
 Dim Filename as String = "ftp://ms.com/file/dotnetfx.exe"
 Dim client as new WebClient
 Client.DownloadFile(filename,"dotnetfx.exe")
End sub

Conclusion

This chapter gave a brief overview of Microsoft file sharing and two implementations of FTP clients in .NET: one for a five-minute solution and one as a substantial, flexible implementation of the protocol.

The next chapter deals with the practical issues faced by developers working with real-world networks. It is designed to help solve network problems for individual scenarios and provide tips and tricks to keep your software from crashing on unusual network setups.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.240.119