Saturday, July 30, 2011

How to create an FTP client in VB.NET

The FTP (File Transfer Protocol) is one of the oldest and most popular ways of transferring files from one computer to another over a TCP network such as the internet. It is a Client/Server based protocol based on cear-text authentication. To get a detailed idea of what FTP is and how it is used, refer to http://en.wikipedia.org/wiki/File_Transfer_Protocol


In .NET,  we implement the FTP functionality using the System.Net namespace. This is a comprehensive namespace which encapsulates functionality to perform various network tasks. These include FTP related tasks like connecting to an FTP server and listing its directory.

In this article, we will learn how to create an FTP Client using the VB.NET language right from scratch.

The IDE I have used is Visual Studio 2008, though you can easily port it to the 2010 version. The complete source-code for this solution is available on Google Code. You can download it here:
 
http://code.google.com/p/ftpexplorer/downloads/detail?name=ftpexplorer_source_1.zip

You can also translate it to C# (with a little bit of effort) since all .NET languages share the same FCL and CLI. There is a C# equivalent for each syntax.

To begin, create a VB solution of the type “Windows Forms Application”. Add a Form to it, then add the below controls to the Form:

  1. A TreeView control – to browse the local file system
  2. A ListView control – to browse the remote ftp structure
  3. A TextBox and a Button to type Url and connect to the FTP server
  4. Two Upload and Download buttons
  5. A multi-line TextBox for logging details
  6. A StatusStrip control with a label on it (Optional)
This will be your FTP client interface where you can do things like connecting to a remote ftp site using your username and password and start uploading/downloading files. The appearance is quite minimalist, and I have abstained from adding many advanced functions like using an SSL connection and applying cryptography, so as not to overwhelm the learners. Here is our little FTP Explorer in action:






The Upload button uploads the selected file on the local system to the remote directory that is currently selected. Similarly, selecting a file on the remote system and clicking the download button downloads the file to the selected local folder.

On the remote system, double-clicking a folder will navigate to that folder. The big multi-line textbox is for logging the ftp events.

On the coding front, I have abstracted the core FTP related functionality in a different class called FtpClient, so that the functionality could be reused or exposed through an API.

The local-system's explorer-tree on the top left side is just filled with the drive letters in the beginning. Later, as the user starts expanding the nodes, they are populated with the sub-folders and files.

Private Sub tvwLocal_AfterExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles tvwLocal.AfterExpand
On Error GoTo em
Dim current As TreeNode = e.Node
'Dim nextFile As String = Dir(current.Name & "\", FileAttribute.Directory)
Dim dirInfo As String() = Directory.GetDirectories(current.Name & "\")
current.Nodes.Clear()
'Do While (nextFile IsNot Nothing AndAlso nextFile.Length > 0)
For Each nextFile As String In dirInfo
Dim dirnode As New TreeNode
dirnode.Text = ExtractFileName(nextFile)
dirnode.Name = nextFile ' current.Name & "\" & nextFile
dirnode.ImageIndex = Images.CLOSED_FOLDER
dirnode.SelectedImageIndex = Images.CLOSED_FOLDER
dirnode.Nodes.Add("*DUMMY*")
'current.Nodes.Add(current.Name & "\" & nextFile, nextFile, Images.CLOSED_FOLDER, Images.CLOSED_FOLDER)
current.Nodes.Add(dirnode)
'Loop
Next
dirInfo = Directory.GetFiles(current.Name & "\")
'nextFile = Dir(current.Name & "\")
For Each nextFile As String In dirInfo
current.Nodes.Add(nextFile, ExtractFileName(nextFile), Images.FILE, Images.FILE)
Next
Exit Sub
em:
If Err.Number <> 52 And Err.Number <> 57 Then
MessageBox.Show(Err.Description)
End If
End Sub

As for the FTP handling, the class constructor takes three arguments: the FTP host, username and password. The most important method in this class is the ListDirectory(). This function sends a LIST request to the remote ftp server and gets the directory and files listing in the form of an ftp-string. It then creates an object of FtpDirectory class which is basically a generic list of FtpFileInfo objects. FtpFileInfo is an important object that reads the ftp-string using regular expressions and categorizes them into a file or a folder.




Public Function ListDirectory(Optional ByVal directory As String = "") As FtpDirectory
On Error GoTo em
Dim url As String
If directory.Length = 0 Then
Me.CurrentDirectory = "/"
'url = Hostname
ElseIf directory = "." Then
'No need to do anything
ElseIf directory = ".." Then
If CurrentDirectory <> "/" Then
'/pub/a
Dim index As Integer = Me.CurrentDirectory.LastIndexOf("/")
CurrentDirectory = CurrentDirectory.Substring(0, index)
If CurrentDirectory.Length = 0 Then CurrentDirectory = "/"
Else
RaiseEvent StatusChanged("Already in the root directory")
'url = Hostname & "/" & CurrentDirectory
End If
Else
Me.CurrentDirectory &= IIf(CurrentDirectory.Length = 1, "", "/") & directory
End If
url = GetCurrentUrl()
RaiseEvent StatusChanged("Logging in with user " & Username)
'return a simple list of filenames in directory
Dim ftp As Net.FtpWebRequest = GetRequest(url)

'Set request to do simple list

ftp.Method = Net.WebRequestMethods.Ftp.ListDirectoryDetails
Dim response As FtpWebResponse = ftp.GetResponse()
Dim sr As New StreamReader(response.GetResponseStream())
Dim str As String = sr.ReadToEnd()
'replace CRLF to CR, remove last instance
str = str.Replace(vbCrLf, vbCr).TrimEnd(Chr(13))
'split the string into a list
If Not Authenticated Then
Authenticated = True
RaiseEvent StatusChanged(response.WelcomeMessage)
RaiseEvent StatusChanged("Login successful...")
End If
RaiseEvent StatusChanged("Current directory is " & CurrentDirectory)
Return New FtpDirectory(str, url)
Exit Function
em:
MessageBox.Show(Err.Description)
End Function


The regular expressions are matched based on six different unix directory listing patterns that are most commonly used.



Public Enum DirectoryEntryTypes
File
Directory
End Enum

Sub New(ByVal line As String, ByVal path As String)
'parse line
Dim m As Match = GetMatchingRegex(line)
If m Is Nothing Then
'failed
Throw New ApplicationException("Unable to parse line: " & line)
Else
Me.FileName = m.Groups("name").Value
Me.Path = path
Me.Size = CLng(m.Groups("size").Value)
Me.Permission = m.Groups("permission").Value
Dim _dir As String = m.Groups("dir").Value
If (_dir <> "" And _dir <> "-") Then
Me.FileType = DirectoryEntryTypes.Directory
Else
Me.FileType = DirectoryEntryTypes.File
End If

Try
Me.FileDateTime = Date.Parse(m.Groups("timestamp").Value)
Catch ex As Exception
Me.FileDateTime = Nothing
End Try
End If
End Sub




Private Function GetMatchingRegex(ByVal line As String) As Match
Dim formats As String() = { _
                    "(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\w+\s+\w+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{4})\s+(?<name>.+)", _
                    "(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\d+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{4})\s+(?<name>.+)", _
                    "(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\d+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{1,2}:\d{2})\s+(?<name>.+)", _
                    "(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\w+\s+\w+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{1,2}:\d{2})\s+(?<name>.+)", _
                    "(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})(\s+)(?<size>(\d+))(\s+)(?<ctbit>(\w+\s\w+))(\s+)(?<size2>(\d+))\s+(?<timestamp>\w+\s+\d+\s+\d{2}:\d{2})\s+(?<name>.+)", _
                    "(?<timestamp>\d{2}\-\d{2}\-\d{2}\s+\d{2}:\d{2}[Aa|Pp][mM])\s+(?<dir>\<\w+\>){0,1}(?<size>\d+){0,1}\s+(?<name>.+)"}Dim rx As Regex, m As Match
For i As Integer = 0 To formats.Length - 1
rx = New Regex(formats(i)) 
m = rx.Match(line) 
If m.Success Then 
Return m 
End If 
Next 
End Function

























For connecting to the remote ftp server, we use the FtpWebRequest class provided by the .NET framework class library through the System.Net namespace. The StatusChange() event of the FtpClient is used to get notifications in the main form where we can display or log this status on a multi-line text-box.

Private Sub client_StatusChanged(ByVal newStatus As String) Handles client.StatusChanged
txtLog.AppendText(newStatus &amp; Environment.NewLine)
Application.DoEvents()
End Sub

The client object's ListDirectory() function is again called when the list view item is double-clicked or an upload is complete

Private Sub ListRemoteDirectory(ByVal directory As String)
lvwRemote.Items.Clear()
'Request = CType(FtpWebRequest.Create(txtUrl.Text), FtpWebRequest)
'Request.Credentials = New NetworkCredential(cred(0), cred(1))
RemoteDir = client.ListDirectory(directory)
LastDirectory = directory
lvwRemote.Items.Add("..", "..")
For Each item As FtpFileInfo In RemoteDir
If item.FileType = FtpFileInfo.DirectoryEntryTypes.Directory Then
lvwRemote.Items.Add(item.FileName, Images.CLOSED_FOLDER)
Else
lvwRemote.Items.Add(item.FileName, Images.FILE)
End If
Next

End Sub

Private Sub lvwRemote_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles lvwRemote.DoubleClick
If lvwRemote.SelectedItems.Count = 0 Then
Dim item As ListViewItem = lvwRemote.SelectedItems(0)
If item.ImageIndex = Images.FILE Then
ListRemoteDirectory(item.Text)
End If
End If
End Sub

The remaining tricks of the trade can be easily understood once you go through the code. For any doubts or queries please contact me. 

..............................................................................................
EDIT: As of today (22-07-2012), I've re-created this project with the following improvements:

1. I've realized that the complexity involved in implementing the FTPWebRequest, deserves a modular approach. Hence, I've developed the FTP library as a class component (DLL) separate from the GUI component. This way, the library can be reused without the burden or overhead of a WinForm.

2. Multi-threading: .NET can certainly leverage the multi-threading capabilities of the newer dual-core processors. I've hence implemented multi-threading by running uploads/downloads on separate threadeds.

3. Event-based: I've included some more events such as uploadCompleted and downloadCompleted to facilitate the end-users of the library.

4. Finally, I've developed the newer version in the C# language, as I'm more used to it in recent years.

You can find the newer version in the codeplex repository here.

The update tutorial for C# is here.

41 comments:

  1. please upload the source code in another place because i can't access it from google code...

    ReplyDelete
  2. Prahlad Yeri, your code work perfectly in VB2008 and VB2010. Thank you! from Brazil.

    ReplyDelete
  3. Hey nice tutorial!
    Could you write some code that will help upload folders too?

    ReplyDelete
  4. Hi!, how to add a progressbar for upload and download file?
    many thanks

    ReplyDelete
  5. Just wondering how would you auto expand the c: drive in the tree view

    ReplyDelete
  6. To auto-expand, use the ExpandAll() method of the treeview control once the tree-nodes are filled.

    @Nick - In order to upload the entire folder, all you have to do is run a loop to get all the files in the FileInfo object, and then call the client.Upload() method for each one of them. I'm upgrading the FTP-Explorer to a new version pretty soon and will be including this functionality.

    A progress-bar and a status-bar will be part of the next build.

    ReplyDelete
  7. Hey, I am new at this. So is there any way to make a ftp server as well? When i try to make it work it says that its unable to connect to remote server.

    ReplyDelete
  8. @Vincristine - Yes it is possible to write even an FTP server in .NET language, but we seldom do it as this feature is already provided in popular web-servers like IIS or Apache Httpd. IIS is a free add-on that ships with Windows OS.
    As for your particular error message, I think that could be due to some firewall or port issue. Check that port numbers 20 & 21 are not used by any other application such as the IIS, and that there are no firewall restrictions.

    Cheers.

    ReplyDelete
  9. HYe prahlad....nice program...but i have and urgent thing to ask of you....why cant i view folder in the remote directory...only files....and why does the download progress unavailable...the file is downloaded but no bytes sent or anything...please help

    ReplyDelete
    Replies
    1. @Kevin - The remote folders are certainly listed, see the ListRemoteDirectory() function in the MainBox form - The files are added to the list-view with a FILE type of icon, whereas folders are added with FOLDER icons. Make sure the folder actually exists in the ftp site you are connecting to.
      As for your second question, yes progress bar is not included yet. This was intended to be a small tutorial program to teach ftp connectivity, and not a professional FTP client! But it will be included in a future version.

      Delete
    2. Hi Prahlad,
      I confirm that the directories are not seen in remote only files, but there are present.

      Delete
  10. prahlad how to create a new directory in this one
    please let me help

    ReplyDelete
  11. There is an issue if you are connecting to a Unix/Linux server. You need to amend the function ListDirectory thus:

    'replace CRLF to CR, remove last instance
    'str = str.Replace(vbCrLf, vbCr).TrimEnd(Chr(13)) ' old line
    str = str.Replace(vbLf, vbCr).TrimEnd(Chr(13)) ' new line

    Listings then work perfectly.

    Obviously it would be preferable not to hardcode the change, but to test str for vbCrLf, if not found then use vbLF in the replace.

    ReplyDelete
    Replies
    1. Thanks for letting me know, Sarah. I've written an enhanced version of this tutorial including other features like multi-threading and a separate DLL library. You can find the new tutorial here: http://prahladyeri.wordpress.com/2012/07/28/how-to-create-an-ftp-client-in-c

      Delete
  12. Hi
    I cant able to download the files that i have uploaded

    ReplyDelete
    Replies
    1. Have you tried with the new version? Moreover, what happens when you download the file? Have you debugged and checked it?

      Delete
    2. Thanks for your reply
      when i try to download the file the three conditions is not satisfy
      end goes to the end if
      (If tvwLocal.SelectedNode IsNot Nothing AndAlso tvwLocal.SelectedNode.ImageIndex = Images.CLOSED_FOLDER AndAlso _
      lvwRemote.SelectedItems(0).ImageIndex = Images.FILE Then)


      http://prahladyeri.wordpress.com/2012/07/28/how-to-create-an-ftp-client-in-c is this your new version?

      Delete
    3. Yes, the wordpress version is the latest one. Try that and see if you are still getting the error.

      Delete
    4. Hi
      in the latest version is there any option to list directories.?????

      Delete
    5. Yes there is. Use the ftper.browse() method to get a list of directories in a remote ftp path. You will have to give this path as a parameter.

      For a ready-made implementation, see the libftp.App project which is also included in the solution. This provides a GUI interface with list-views, buttons, etc. for the basic ftp functionality.

      Delete
    6. Hi
      directories are listing in remote. but in local "C:\ftptest" only listing.. i want to list all directories in my system.. so that only we can upload our files..

      Delete
    7. my need is that i want ftp client ..users can upload files download files, from his systems and to his system.. for that what are the changes i have to made for your code

      Delete
    8. hi
      my need is that users can use this ftp client and upload ,download files from and to systems. for that what are the changes i have to do for you code

      Delete
    9. @afsal - No need to do any code-changes. You can get the latest c# version from http://libftp.codeplex.com/ and upload/download files with the accompanying GUI. If you want to enhance the GUI, you may do so using Visual Studio or Visual C# Express Edition.

      Delete
    10. Hi..
      The latest version is working fine(uploading and downloading). I want to display all local directories. here only listing C:\ftptest

      thank you for your great post

      Delete
    11. replace the line

      this.lblLocalPath.Text = "C:\ftptest\"; with this.lblLocalPath.Text = "C:\\";

      Delete
    12. replace the line this.lblLocalPath.Text = "C:\ftptest\"; with this.lblLocalPath.Text = "C:\\";

      Delete
  13. Is there any option to pass port no and ssh key to make it secure.

    ReplyDelete
  14. Is there any option to enter port no and ssh key to secure ftp in same code.

    ReplyDelete
    Replies
    1. @Santosh, As far as I know, .NET supports only explicit SSL, not implicit. You can enable explicit SSL by setting the FtpWebRequest.EnableSsl property to true. Read these links for more information:

      http://social.msdn.microsoft.com/forums/en-US/netfxnetcom/thread/491341d3-0dd6-4c60-94c9-ca4d4cf3450a/

      http://stackoverflow.com/questions/1842186/does-net-ftpwebrequest-support-both-implicit-ftps-and-explicit-ftpes

      Delete
  15. Where is the link to download vb.net

    ReplyDelete
    Replies
    1. Here is the link for visual studio downloads:

      http://www.microsoft.com/visualstudio/eng/downloads

      Delete
  16. In VB6, how to change Icon in present in title bar?

    ReplyDelete
    Replies
    1. Your VB6 Form will have an Icon property. From the properties window change this icon to the one you want.

      Delete
  17. This comment has been removed by the author.

    ReplyDelete
  18. Hi
    This is very good and very useful.
    Thanks a lot

    ReplyDelete
  19. This is very good and very useful.
    Thanks a lot.

    ReplyDelete
  20. Gracias por compartir. Es mi punto de partida para una aplicación que requiero. Un GRAN PUNTO DE PARTIDA. Me ayuda mucho. Aún me falta, pero espero todo me vaya bien. Gracias, y saludos desde Arequipa, Perú

    ReplyDelete