Send to printer
in Visual Basic by Jeff Niblack
File Transfer Protocol (FTP) is a core protocol in the IP world and is used every day by network administrators, Web developers, and people retrieving their grandkids¡¯ pictures. However, for Active Server Pages (ASP) programmers, FTP functions are non-existent without a third-party server component to provide the functionality.

Guess what? The fine folks at Microsoft have been kind enough to provide us with the nuts and bolts for FTP functions in the WinINet.DLL. This DLL, which ships with Internet Explorer and a few other things, takes care of the low-level tasks outlined in the FTP protocol. All we have to do is wrap up a few of the functions provided so they¡¯re easy to call from our ASP code.

This sounds like a great opportunity to put our Visual Basic (VB) skills to work.

A Little Help From Our Friends

For those who have never written an ActiveX component using VB, there are plenty of resources available on the Web. Here on 15 Seconds, there are a couple of articles you can refer to. COM for ASP Programmers ( http://www.15seconds.com/Issue/971214.htm) covers the basics of building components and is a must read if you¡¯re new to this. Creating a Server Component with Visual Basic ( http://www.15seconds.com/issue/980930.htm ) walks you through the steps of creating components.

As mentioned, Microsoft has been kind enough to provide us with all the FTP functions in the WinINet DLL. The documentation for the WinINet API is located at http://msdn.microsoft.com/developer/sdk/inetsdk/help/itt/wininet/wininet.htm#book_wininet . As with most of the API documentation, it¡¯s intended for C++ programmers. So, if you don¡¯t understand C++ or are easily bored with the way API documentation drones on, keep on a-readin¡¯.

Step by Step

Let¡¯s jump right into it and perform an FTP GET on the file DIRMAP.TXT at FTP.MICROSOFT.COM and storing the file in C:\DIRMAP.TXT. Once again the basic steps are:

  1. Setup the environment with a call to InternetOpen.
  2. Connect to the host by calling the InternetConnect function.
  3. Call the FtpGetFile function to get the file.
  4. Close the handles created in Steps 1 and 2, using the InternetCloseHandle function.

Here¡¯s a closer look at each step.

  1. Setup the environment by calling the InternetOpen function. Here¡¯s the VB-specific declaration for the function call:
    
    Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" _
        (ByVal sAgent As String, ByVal lAccessType As Long, ByVal sProxyName As String, _
        ByVal sProxyBypass As String, ByVal lFlags As Long) As Long
    
    
    The sAgent parameter is used to specify the application or entity calling the WinINet functions. For our purposes, we can put ¡¡ãMyFTP Control¡¡À.

    The lAccessType parameter specifies whether we connect directly to a host or whether we use a proxy server for the connection. If we pass the value 1, we¡¯ll connect directly to the host. If we pass the value 3, we¡¯ll connect via a proxy server. If we pass 0, we¡¯ll connect based on the registry values ProxyEnable, ProxyServer, and ProxyOverride located under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings.

    Rather than using the registry settings, we can use the sProxyName and sProxyBypass parameters to provide the proxy server (or servers) and the list of IP addresses or names that should not be routed to the proxy. The basic format for listing a proxy is ¡¡ãprotocol=protocol://proxy_name:access_port¡¡À. For example, to specify port 21 on Proxy1 as the proxy server, use ¡¡ãftp=ftp://Proxy1:21¡¡À as the sProxyName. To bypass any host that starts with ¡¡ãnov¡¡À, the sProxyBypass string would be ¡¡ãnov*¡¡À.

    Finally, lFlags is used to indicate various options affecting the behavior of the InternetOpen function. For our example, we¡¯ll pass 0.

    So, to open an Internet session without using a proxy, our call would be like this:

    
    lngINet = InternetOpen(¡¡ãMyFTP Control¡¡À, 1, vbNullString, vbNullString, 0)
    
    
    If the function call fails, lngINet will be 0. Otherwise, lngINet holds the value of the handle that we¡¯ll need to pass to the InternetConnect function in the next step.

  2. Connect to the host by calling the InternetConnect function. Here¡¯s the VB-specific declaration for the function call:
    
    Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" _
        (ByVal hInternetSession As Long, ByVal sServerName As String, _
        ByVal nServerPort As Integer, ByVal sUsername As String, _
        ByVal sPassword As String, ByVal lService As Long, _
        ByVal lFlags As Long, ByVal lContext As Long) As Long
    
    
    The first parameter, hInternetSession is the value of the handle returned by the InternetOpen call.

    sServerName is the IP address or host name of the FTP server we¡¯re connecting to.

    nServerPort indicates which port to connect to. For our example, we¡¯ll use the value 0, which indicates the default port (21).

    sUsername and sPassword are used to pass the user name and password, respectively.

    The lService parameter is used to indicate the type of service to access, i.e., HTTP, FTP, etc. We¡¯ll always pass the value of 1, indicating the FTP service.

    If we pass the value x8000000 in the lFlags parameter, the connection will use passive FTP semantics. Otherwise, as in our example, we can pass 0 to use non-passive semantics.

    Finally, lContext is used to identify the application context when using callbacks. Since we¡¯re not using callbacks for our example, we¡¯ll pass 0.

    So here¡¯s the call to connect to the host FTP.MICROSOFT.COM using the anonymous userid:

    
    lngINetConn = InternetConnect(lngINet, ¡¡ãftp.microsoft.com¡¡À, 0, _
        ¡¡ãanonymous¡¡À, ¡¡ãwally@wallyworld.com¡¡À, 1, 0, 0)
    
    
    If the function call fails, lngINetConn will be 0. Otherwise, lngINetConn holds the value of the handle that we¡¯ll need to pass to the FtpGetFile function in the next step.

  3. Now that we¡¯re connected, we need to call the FtpGetFile function. This function takes care of all of the housekeeping and overhead that comes along with reading a file on an FTP server and storing it locally. Here¡¯s the VB-specific declaration for the function call:
    
    Private Declare Function FtpGetFile Lib "wininet.dll" Alias "FtpGetFileA" _
        (ByVal hFtpSession As Long, ByVal lpszRemoteFile As String, _
        ByVal lpszNewFile As String, ByVal fFailIfExists As Boolean, _
        ByVal dwFlagsAndAttributes As Long, ByVal dwFlags As Long, _
        ByVal dwContext As Long) As Boolean
    
    
    The first parameter, hFtpSession is the value of the handle returned by the InternetConnect call.

    lpszRemoteFile and lpszNewFile are the names of the file on the FTP server and the file to create on the local machine, respectively.

    The fFailIfExists flag is either 0 (replace local file) or ¡§C1 (fail if local file already exists).

    dwFlagsAndAttributes can be used to specify file attributes for the local file. For our example, we¡¯ll overlook this and just pass 0.

    We use the dwFlags parameter to specify 1 for transferring the file in ASCII (Type A transfer method) or 2 for transferring the file in Binary (Type I transfer method). Since DIRMAP.TXT is an ASCII text file, we¡¯ll pass the value of 1.

    Finally, lContext is used to identify the application context when using callbacks. Since we¡¯re not using callbacks for our example, we¡¯ll pass 0.

    So here¡¯s the call to get the DIRMAP.TXT file and store it in C:\DIRMAP.TXT. In case the local file already exists, we¡¯ll overwrite it.

    
    blnRC = FtpGetFile(lngINetConn, ¡¡ãdirmap.txt¡¡À, ¡¡ãc:\dirmap.txt¡¡À, 0, 0, 1, 0)
    
    
    If the function call is successful, blnRC will be True, otherwise, blnRC will be False.

  4. At this point, the file has been received and now we¡¯re ready to close the connection and session handles using the InternetCloseHandle call. Here¡¯s the VB-specific declaration for the function call:

    Private Declare Function InternetCloseHandle Lib "wininet.dll" (ByVal hInet As Long) As Integer.

    As indicated, this function has only one parameter, hInet, that is the value of the handle to close or discard. Since, we have handles from the InternetConnection and InternetOpen functions, we¡¯ll need to call this close function twice. In addition, because the InternetConnection handle is dependent on the InternetOpen handle, we¡¯ll need to close these in the reverse order that we created them.

    So here are the calls:

    
    InternetCloseHandle lngINetConn
    InternetCloseHandle lngINet
    
    
    Congrats! In four short steps, we¡¯ve done an FTP Get.

Moving On

What about the FTP functions like Put, Rename, Delete, etc? Well, these functions are just as easy. Let¡¯s take a look at the Put function first.

The basic steps are:

  1. Set up the environment with a call to InternetOpen.
  2. Connect to the host by calling the InternetConnect function.
  3. Call the FtpPutFile function to get the file.
  4. Close the handles created in Steps 1 and 2, using the InternetCloseHandle function.
Looks real similar to our first step-through when we used the FtpGetFile function. In fact, the only difference, is in step 3 where we¡¯ll call FtpPutFile instead. Here¡¯s the VB-specific declaration for the function call:

Private Declare Function FtpPutFile Lib "wininet.dll" Alias "FtpPutFileA" _
    (ByVal hFtpSession As Long, ByVal lpszLocalFile As String, _
    ByVal lpszRemoteFile As String, ByVal dwFlags As Long, _
    ByVal dwContext As Long) As Boolean

The first parameter, hFtpSession is the value of the handle returned by the InternetConnect call.

lpszNewFile and lpszRemoteFile are the names of the file on the local machine and the file to create on the remote host, respectively.

We use the dwFlags parameter to specify 1 for transferring the file in ASCII (Type A transfer method) or 2 for transferring the file in Binary (Type I transfer method). Since DIRMAP.TXT is an ASCII text file, we¡¯ll pass the value of 1.

Finally, lContext is used to identify the application context when using callbacks. Since we¡¯re not using callbacks for our example, we¡¯ll pass 0.

So here¡¯s the call to get the C:\DIRMAP.TXT file and store it as DIRMAP.TXT on an FTP server:


blnRC = FtpPutFile(lngINetConn, ¡¡ãc:\dirmap.txt¡¡À, ¡¡ãdirmap.txt¡¡À, 1, 0)

If the function call is successful, blnRC will be True, otherwise, blnRC will be False.

As you can see, putting files on FTP servers is just as easy as getting files from FTP servers. One word of caution, typically, the ¡¡ãanonymous¡¡À user doesn¡¯t have access rights to create files on FTP servers. So, be sure that the user account used to connect to the FTP server has rights sufficient to create files. Otherwise, the FtpPutFile function call will return False, indicating the Put failed.

Now let¡¯s delete a file named ¡¡ãtest.txt¡¡À by using the FtpDeleteFile function. Again, the function call in Step 3 is the only thing that changes. Here¡¯s the VB-specific declaration for the function call:


Private Declare Function FtpDeleteFile Lib "wininet.dll" Alias "FtpDeleteFileA" _
    (ByVal hFtpSession As Long, ByVal lpszFileName As String) As Boolean

The first parameter, hFtpSession is the value of the handle returned by the InternetConnect call.

lpszFileName is the name of the file on the FTP server that we want to delete.

So, here¡¯s the call to delete TEST.TXT on an FTP server:


blnRC = FtpDeleteFile(lngINetConn, ¡¡ãtest.txt¡¡À)

If the function call is successful, blnRC will be True, otherwise, blnRC will be False.

What¡¯s the Catch?

Nearly all of the FTP functions are the same: set up the environment, connect to the host, perform the FTP task, clean up after ourselves. As with most things, there are exceptions. What about getting a directory listing or reading the file contents without first getting the file? These types of FTP tasks are only marginally more complicated. Let¡¯s take a look at the directory listing problem.

The basic steps for enumerating a directory are still the same. We still have to set up the environment and connect to the FTP server before we do anything else. We also have to clean up the connection and environment handles once we¡¯re done. To actually get the directory contents, we have to use two new functions: FtpFindFirstFile and InternetFindNextFile. The VB-specific declaration for FtpFindFirstFile is:


Private Declare Function FtpFindFirstFile Lib "wininet.dll" Alias "FtpFindFirstFileA" _
    (ByVal hFtpSession As Long, ByVal lpszSearchFile As String, _
    lpFindFileData As WIN32_FIND_DATA, ByVal dwFlags As Long, _
    ByVal dwContent As Long) As Long

The first parameter, hFtpSession is the value of the handle returned by the InternetConnect call.

lpszSearchFile is a directory path or file name on the FTP server. If you specify an empty string, the current directory will be used. In addition, you can also specify wildcards. For example, to list the directory contents in the root directory that begin with ms, use ¡¡ã\ms*¡¡À.

lpFindFileData is a little different from the other parameters we¡¯ve used thus far. The datatype WIN32_FIND_DATA is a user-defined type that holds the received information about the files in the directory. Here¡¯s what the type looks like:


Private Type WIN32_FIND_DATA
        dwFileAttributes As Long
        ftCreationTime As FILETIME
        ftLastAccessTime As FILETIME
        ftLastWriteTime As FILETIME
        nFileSizeHigh As Long
        nFileSizeLow As Long
        dwReserved0 As Long
        dwReserved1 As Long
        cFileName As String * MAX_PATH
        cAlternate As String * 14
End Type

Notice that several of the variables have another user-defined datatype; FILETIME. Here¡¯s the type definition for them:

Private Type FILETIME
        dwLowDateTime As Long
        dwHighDateTime As Long
End Type

For our example, we¡¯ll only use the contents of dwFileAttributes, which holds the file attributes, and cFileName, which holds the file name.

Finally, dwFlags and dsContext aren¡¯t used for our example so we¡¯ll pass 0 for each.

So here¡¯s the call to start a directory listing for the current directory:


lngHINet = FtpFindFirstFile(lngINetConn, "*.*", pData, 0, 0)

If the function fails, 0 is returned. Otherwise, lngHInet is a valid handle that we¡¯ll use to continue with the directory enumeration. In addition, the first file¡¯s name and attributes are stored in the pData variable.

Once the FtpFindFirstFile function is called and returns a valid handle, we¡¯ll make calls to the InternetFindNextFile function until it returns an error of 18 indicating no more files to list. Here¡¯s the VB-specific declaration for InternetFindNextFile:


Private Declare Function InternetFindNextFile Lib "wininet.dll" Alias "InternetFindNextFileA" _
    (ByVal hFind As Long, lpvFindData As WIN32_FIND_DATA) As Long

The first parameter, hFind is the value of the handle returned by the FtpFindFirstFile call.

lpvFindData is the same user-defined type that was used in the FtpFindFirstFile call and is used to store file information.

So here¡¯s the call to get the next file in the directory:


blnRC = InternetFindNextFile(lngHINet, pData)

If the call was successful, blnRC returns True. Otherwise, blnRC is false. To indicate that no more files exist, 18 is returned in the LastDllError of the Err object.

So, referring back to our basic four-step process: Steps 1 and 2 (setting up the environment and connecting to a server) should have already been done. The listing below would be Step 3. The final step, Step 4, would cleanup the environment and connection handles, just as before:


Dim pData As WIN32_FIND_DATA
Dim lngHINet As Long
Dim intError As Integer
Dim strTemp As String
Dim blnRC As Boolean

'init the filename buffer
pData.cFileName = String(260, 0)

'get the first file in the directory...
lngHINet = FtpFindFirstFile(mlngINetConn, "*.*", pData, 0, 0)

'how'd we do?
If lngHINet = 0 Then
    'get the error from the findfirst call
    intError = Err.LastDllError
    
    'is the directory empty?
    If intError <> ERROR_NO_MORE_FILES Then
        'whoa...a real error
        ¡­error handler¡­
    End If
    
Else
    
    'we got some dir info...
    'get the name
    strTemp = Left(pData.cFileName, InStr(1, pData.cFileName, String(1, 0), vbBinaryCompare) - 1)

    ¡­store the file info someplace¡­
            
    'now loop through the rest of the files...
    Do
        'init the filename buffer
        pData.cFileName = String(260, 0)
        
        'get the next item
        blnRC = InternetFindNextFile(lngHINet, pData)
        
        'how'd we do?
        If Not blnRC Then
        
            'get the error from the findnext call
            intError = Err.LastDllError
            
            'no more items
            If intError <> 18 Then
                'whoa...a real error
                ¡­error handler¡­
                
                Exit Do
            
            Else
                
                'no more items...
                Exit Do
                
            End If
            
        Else
            
            'get the last item returned
            strTemp = Left(pData.cFileName, InStr(1, pData.cFileName, String(1, 0), vbBinaryCompare) - 1)
            
            ¡­store the file info someplace¡­

        End If
        
    Loop
    
    'close the handle for the dir listing
    InternetCloseHandle lngHINet
    
End If

So as you can see, even somewhat complicated tasks, such as enumerating directories, are easily framed in our simple four-step process: set up the environment, connect to the host, perform the FTP task, and close the environment and connection handles. Cake baby!

Your Very Own FTP ActiveX Server Component

To complement this article, I¡¯ve included the VB source code for an FTP ActiveX server component. The code consists of 3 class modules; clsITEM.CLS, colITEM.CLS, and FTP.CLS. clsITEM.CLS contains the class definition, with clsITEM as the class name, for holding individual file information when enumerating a directory. colITEM.CLS contains the class definition, with colItem as the class name, for collection of files using the clsITEM class. Finally, FTP.CLS contains the class definition, with ASPFTP as the class name, for all the FTP functions. I¡¯ve also included the project file (FTP_CLASSES.VBP).

Along with the source code, I¡¯ve included sample ASP files for each of the functions and an include file (ASPFTP2.INC) that you can include in each of your ASP files to make some of the function calls easier. For example, here¡¯s the ASP code to use the FTP Get function. The server, userid, password, remote and local file names, type of transfer to use (i.e. ASCII or Binary), and whether to replace the local file if it already exists are entered into a form (not shown) and referred to using the ASP Request object.


<%@ LANGUAGE=VBScript %>
<!--#Include File="aspftp2.inc"-->
<%
'check to see if user submitted form
If Request.Form("GetIt") <> "" Then
	Dim objFTP
	Dim strMsg

	'create reference to object
	Set objFTP = Server.CreateObject("NIBLACK.ASPFTP")

	'set the properties for the connection
	objFTP.sServerName = Request.Form("Server")
	objFTP.sUserID = Request.Form("User_ID")
	objFTP.sPassword = Request.Form("Password")
	
	'connect to the host
	If objFTP.bConnect Then	
		'set the properties for the get function
		objFTP.bOverWrite = Request.Form("OverWrite")
		objFTP.lTransferType = Request.Form("Transfer_Type")

		'now get the file
		If objFTP.bGetFile(Request.Form("Remote_File"), Request.Form("Local_File")) Then
		    'get was successful
		    strMsg = "Get Successful!"
		Else
		    'get failed...let user know
		    strMsg = "Get Failed: " & objFTP.sError
		End If
	Else
		'connection failed...let user know
		strMsg = "Connection Failed: " & objFTP.sError
	End If
	    
	'clean up...
	Set objFTP = Nothing
	
Else
	'default return msg
	strMsg = ""
End If
%>

For many of the FTP functions, I¡¯ve also created ¡¡ãQuick¡¡À methods. Basically, rather than having to set each of properties in your ASP code, you call one of the ¡¡ãQuick¡¡À methods and all of the parameters to complete the task are included in the method call. For example, here¡¯s the ASP code to use the FTP Get function. The server, userid, password, remote and local file names, type of transfer to use (i.e. ASCII or Binary), and whether to replace the local file if it already exists are entered into a form (not shown) and referred to using the ASP Request object.

<%@ LANGUAGE=VBScript %>
<!--#Include File="aspftp2.inc"-->
<%
'check to see if user submitted form
If Request.Form("GetIt") <> "" Then
	Dim objFTP
	Dim strMsg

	'create reference to object
	Set objFTP = Server.CreateObject("NIBLACK.ASPFTP")

	'now get the file
If objFTP.bQGetFile(Request.Form("Server"), Request.Form("User_ID"), _
    Request.Form("Password"), Request.Form("Remote_File"), Request.Form("Local_File"), _
    Request.Form("Transfer_Type"), Request.Form("OverWrite")) Then
	    'get was successful
	    strMsg = "Get Successful!"
	Else
	    'get failed...let user know
	    strMsg = "Get Failed: " & objFTP.sError
	End If
	    
	'clean up...
	Set objFTP = Nothing
	
Else
	'default return msg
	strMsg = ""
End If
%>

So What¡¯s Next?

With the source code provided, you already have an ActiveX server component for FTP functions. Now, we can follow the same basic steps and create a client-side OCX that you can include on your HTML pages to allow users to FTP from their PCs. You might also take a look at some of the other functions provided by the WinINet API. You could use the HTTP functions, for example, to create a robot that pulls back the content of Web sites and indexes all of the pages. Or, create a probe that, on a scheduled basis, checks the status of a Web site or Web page using the HTTP functions. With the nuts and bolts provided by the WinINet API, all of these and more ideas are pretty simple to explore. Have fun!

Download

The source presented in this article is available for download.
[Code Source Download]