 |
| WinINet library ..top |
 |
 |
MSDN: The Microsoftฎ Win32ฎ Internet (WinINet) application programming interface (API) provides stand-alone client applications with easy access to standard Internet protocols, such as Gopher, FTP, and HTTP, abstracting the protocols into a high-level interface that is familiar to Win32 developers.
The minimum operation system supporting this library is Windows 95 and Windows NT 4.0 with the Internet Explorer installed of version 3.0 and higher. The library file Wininet.dll is normally located in Windows System32 folder and doesn't need to be installed on users' computers. |
 |
 |
| Visual FoxPro is single-threaded application ..top |
 |
 |
| All calls to FTP server are performed within the process of the application hosting the FTP class. If anything goes wrong with the FTP server, the FoxPro application may get frozen. |
 |
 |
| Opening access to WinINet functions ..top |
 |
 |
| To access WinINet functions the library has to be initialized as follows: |
 |
 |
#DEFINE INTERNET_OPEN_TYPE_DIRECT 1
PRIVATE hInternet
hInternet = InternetOpen("FoxFtp", INTERNET_OPEN_TYPE_DIRECT, 0,0,0)
|
 |
 |
InternetOpen is the first WinINet function called by an application. It tells the WinInet DLL to initialize internal data structures and prepare for future calls from the application. When the application finishes using the Internet functions, it should call InternetCloseHandle to free the handle and any associated resources.
The InternetOpen returns a valid handle that the application passes to subsequent WinINet functions. If InternetOpen fails, it returns 0.
"FoxFtp" is optional user agent string. You may use any appropriate string like name of your application, your company name etc.
In case of a proxy server this function is declared in slightly different way: |
 |
 |
DECLARE INTEGER InternetOpen IN wininet;
STRING sAgent, INTEGER lAccessType,;
STRING sProxyName, STRING sProxyBypass,;
INTEGER lFlags
|
 |
 |
| After the calling application has finished using the hInternet handle returned by InternetOpen, it must be closed using the InternetCloseHandle function: |
 |
 |
= InternetCloseHandle(m.hInternet)
|
 |
 |
| Connecting to FTP server ..top |
 |
 |
| InternetConnect function creates connection to a given FTP server. It requires valid host name, user name and password for the FTP server and several flags: |
 |
 |
#DEFINE INTERNET_INVALID_PORT_NUMBER 0
#DEFINE INTERNET_SERVICE_FTP 1
#DEFINE INTERNET_FLAG_PASSIVE 0x08000000
hConnection = InternetConnect(m.hInternet, m.host,;
INTERNET_INVALID_PORT_NUMBER,;
m.usr, m.cPsw, INTERNET_SERVICE_FTP, m.nFlags, 0)
|
 |
 |
| The host value is either server name like "ftp.mycompany.com" or IP address, for example, "11.0.1.45". |
 |
 |
| The user name and password don't need to be explained. Get them from FTP server admin or use anonymous access to connect to public FTP servers. |
 |
 |
| There are two choices for nFlags parameter. Use zero for active data connection and INTERNET_FLAG_PASSIVE for passive data connection. |
 |
 |
| A few words about active and passive FTP data connections ..top |
 |
 |
FTP server transfers data and commands through separate ports. Speaking very generally the active data connection is characterized by using predefined ports 20 and 21 used on the server side. As well the server initiates data connection to client's data port.
During the passive data connection the client initiates connection to port 21 on the server and then requests the number of data port (by issuing the PASV command). Now the client initiates data connection to this port to transfer data.
By choosing corresponding flag when calling the InternetConnect function you define whether the connection is going to be active or passive.
To know more about FTP ports and data connections read Active FTP vs. Passive FTP, a Definitive Explanation article by Jay Ribak on Slacksite.Com |
 |
 |
| Security ..top |
 |
 |
Note that data transferred between local computer and FTP server is not encrypted including the user name and password used to initiate connection with the server. It's especially easy to demonstrate when you have a packet sniffer installed on your computer. I use EtherDetect, and it's not a promotional suggestion because it's rather expensive.
So don't be surprised when it shows your password in plain text. It's just a remainder that TCP packets on the way to and from FTP server can be read by another person. FTPS and HTTPS protocols are more suitable for secure data transfers, and don't forget to add PGP encryption on top :). |
 |
 |
| Closing connection to FTP server ..top |
 |
 |
| This is as easy as |
 |
 |
= InternetCloseHandle(m.hConnection)
|
 |
 |
| Handling Errors ..top |
 |
 |
| GetLastError is used to retrieve the error code for all of the Win32 Internet functions. If ERROR_INTERNET_EXTENDED_ERROR is returned, there is a string or buffer containing a detailed error message. Call the InternetGetLastResponseInfo function to retrieve the extended error text. |
 |
 |
| Finding the name of current directory ..top |
 |
 |
| FtpGetCurrentDirectory retrieves current directory for the FTP session: |
 |
 |
LOCAL cBuffer, nBufsize
nBufsize=260
cBuffer = REPLICATE(CHR(0), nBufsize)
= FtpGetCurrentDirectory(m.hConnection, @cBuffer, @nBufsize)
RETURN SUBSTR(m.cBuffer, 1, m.nBufsize)
|
 |
 |
Note that prior to calling this code the WinINet library must be initialized and connection to FTP server established. For the sake of simplicity all checks are omitted.
As with many other API calls, two input parameters are passed to the function by reference. Prior to the call cBuffer and nBufsize must be initialized with corresponding string and numeric values. cBuffer must be large enough to accomodate any possible directory name. |
 |
 |
| Changing current directory ..top |
 |
 |
| FtpSetCurrentDirectory changes to a different working directory on the FTP server. |
 |
 |
= FtpSetCurrentDirectory(m.hConnection, 'myfiles/images')
|
 |
 |
The lpszDirectory parameter can be either partially or fully qualified file names relative to the current directory.
WinINet functions require input string parameters to be null-terminated, i.e. ended with Chr(0). Still regular FoxPro string format is sufficient. You don't have to add Chr(0) to it, though you may consider this option. |
 |
 |
| Getting list of files and subfolders in a folder ..top |
 |
 |
WinINet uses familiar FindFirst/FindNext approach. In this case names of two functions are FtpFindFirstFile and InternetFindNextFile. The first one gets the enumeration handle. The second function is called continuously with this handle as a parameter until no more files left. InternetCloseHandle is used to close the enumeration handle, which is important part of this procedure.
Only one active enumeration handle per FTP session can be created, which means you can not enumerate files in two directories at the same time. Keep this in mind when trying to create a recursive procedure on FTP file tree.
There is certain challenge in parsing resulting WIN32_FIND_DATA structures: |
 |
 |
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[MAX_PATH];
TCHAR cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;
|
 |
 |
Declare lpFindFileData as STRING @ in both the FtpFindFirstFile and InternetFindNextFile. Initialize the buffer with something like REPLICATE(Chr(0), 320), get it back from the calls and scan character after character.
Take into account sizes of each member of this structure. DWORD occupies 4 bytes, MAX_PATH is Win32 constant and equal to 260, FILETIME structure occupies 8 bytes: |
 |
 |
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME;
|
 |
 |
| DWORDs in the returned buffer are presented with 4-byte substrings. There is simple procedure that converts them into FoxPro numeric values: |
 |
 |
FUNCTION dword2num(lcBuffer)
RETURN Asc(SUBSTR(lcBuffer, 1,1)) + ;
BitLShift(Asc(SUBSTR(lcBuffer, 2,1)), 8) +;
BitLShift(Asc(SUBSTR(lcBuffer, 3,1)), 16) +;
BitLShift(Asc(SUBSTR(lcBuffer, 4,1)), 24)
|
 |
 |
| An alternative way of getting the list of files and folders is using the FtpCommand function with lpszCommand="LIST". This one returns the list in plain ASCII format: |
 |
 |
drwx--x--x 21 user group 4096 Feb 7 00:02 nightly.0
drwx--x--x 21 user group 4096 Feb 6 00:02 nightly.1
drwx--x--x 22 user group 4096 Jan 28 00:00 nightly.10
drwx--x--x 22 user group 4096 Jan 27 00:00 nightly.11
|
 |
 |
| Note that some FTP servers return only last-modified time for files and folders. |
 |
 |
| Renaming files and folders ..top |
 |
 |
| You must have sufficient access level to the FTP server to rename files on it. Provide the FtpRenameFile with the connection handle and two file names. |
 |
 |
FtpRenameFile(m.hConnection, m.cOldName, m.cNewName)
|
 |
 |
Both file names can be either partially or fully qualified relative to the current directory.
|
 |
 |
| Removing files and folders ..top |
 |
 |
| You must have sufficient access level to an FTP server to delete files on it. FtpDeleteFile and FtpRemoveDirectory have identical interface: |
 |
 |
= FtpDeleteFile(m.hConnection, m.cRemoteFile)
= FtpRemoveDirectory(m.hConnection, m.cRemoteFolder)
|
 |
 |
| Creating a directory ..top |
 |
 |
| FtpCreateDirectory creates a new directory on the FTP server: |
 |
 |
= FtpCreateDirectory(m.hConnection, m.cFolder)
|
 |
 |
| Binary and ASCII transfer modes ..top |
 |
 |
When downloading and uploading files on FTP server make sure you pick proper transfer mode. Select ASCII mode for text files, like PRG, TXT, CSV, HTML, ASP etc., and binary mode for all other files, like EXE, DLL, DOC, XLS.
Text files most often have no formatting embedded and include alphabet characters, numbers, punctuation and some special characters. |
 |
 |
#DEFINE FTP_TRANSFER_TYPE_ASCII 1
#DEFINE FTP_TRANSFER_TYPE_BINARY 2
|
 |
 |
Executable file transferred with ASCII mode most likely will be corrupted. Otherwise an ASCII file transferred with Binary mode will stay readable. Though you may have problem with line-feed characters.
If you are not sure about proper transfer mode for a file, pick the Binary one. |
 |
 |
| Uploading files to FTP server ..top |
 |
 |
You have an option of either sending the file in one piece with the FtpPutFile function or uploading a succession of smaller size chunks.
This is how one-call way works: |
 |
 |
IF FtpPutFile(m.hConnection, cLocalFile, cRemoteFile,;
FTP_TRANSFER_TYPE_BINARY, 0) = 0
RETURN .F.
ELSE
RETURN .T.
ENDIF
|
 |
 |
The second way involves calls to three WinINet functions: FtpOpenFile, InternetWriteFile and InternetCloseHandle. In exchnage to some coding efforts you get an opportunity to attach a progress bar to the uploading process, suspend and interrupt the transfer.
Use GENERIC_WRITE attribute with FtpOpenFile. Low-level file functions FOPEN(), FREAD(), FEOF() and FCLOSE() should be applied to local file. |
 |
 |
| Downloading files from FTP server ..top |
 |
 |
| The uploading functions are very much like a mirror reflection of the donwloading functions. FtpGetFile retrieves a file from the FTP server and stores it under the specified file name on local computer. |
 |
 |
IF FtpGetFile(m.hConnection, m.cRemoteFile, m.cLocalFile,;
0, FILE_ATTRIBUTE_NORMAL, FTP_TRANSFER_TYPE_BINARY, 0) = 0
RETURN .F.
ELSE
RETURN .T.
ENDIF
|
 |
 |
To upload file in chunks use FtpOpenFile with GENERIC_READ attribute, InternetReadFile and InternetCloseHandle WinINet calls. Use InternetSetFilePointer to resume interrupted file transfer. This function sets a file position for InternetReadFile. However some FTP servers may not support random access.
Low-level file functions FCREATE(), FOPEN(), FWRITE() and FCLOSE() should be applied to local file. |
 |
 |
| Displaying progress status when downloading and uploading files ..top |
 |
 |
Using InternetReadFile and InternetWriteFile to transfer files in chunks between local computer and FTP server allows to attach some kind of a progress bar to the process.
Another option is calling WinINet functions asynchronously. It involves a callback function, which can not be programmed in solely FoxPro code and requires external library. There's MSDN article describing this issue in more details: Calling WinINet Functions Asynchronously. |
 |
 |
| Using FtpCommand ..top |
 |
 |
FtpCommand sends commands directly to an FTP server. There is a set of low-level FTP commands like SYST, HELP, QUIT, LIST, PASV and many others. Control FTP commands maintain server settings while Data FTP commands are responsible for data transfers.
InternetGetLastResponseInfo returns result of sending a command to FTP server.
Control FTP commands do not use data port (as you know FTP connection uses two ports). Data FTP commands use both ports and also require InternetReadFile or InternetWriteFile calls depending on data transfer direction.
Take a look at list of raw FTP commands. Hopefully the link is not broken while you are reading this lines. Otherwise search "raw FTP commands" on the Internet. Personally I like NOOP command.
|
 |
 |
| A few words about Unix and Windows FTP Servers ..top |
 |
 |
Two small notes, which can save some time for you:
Unix/Linux filenames are case-sensitive (myfile.gif and myfile.Gif relate to two separate files)
no "." and ".." folders on Windows FTP servers |
 |
 |
| Windows Ftp utility ..top |
 |
 |
This is DOS-like (or Unix-like, if you like) application with rather an archaic way of typing commands in a console window. A familiar way to FoxPro programmers. The utility is easy to use, and it exists virtually on any Windows computer. Usually the FTP.EXE file resides in the System32 directory.
Type ftp from DOS propmpt or from Run in Windows Start Menu. Bye command close the console window.
Another great thing this app has is batch mode: you can execute prepared scripts including all available FTP commands.
|
 |
 |
| Links ..top |
 |
 |
RFC 959 - File Transfer Protocol
Active FTP vs. Passive FTP, a Definitive Explanation
FTP Return Codes in Numeric Order
List of raw FTP commands |
 |
 |
| Oct 16, 2003. News2News |