by Charlie Calvert
This paper focuses on using Delphi with the Internet. There are two specific
technologies examined in this paper:
One of the big developments in the computer world this spring has been the rolling
out of the Microsoft Internet strategy. The days of CGI and third parties supplying
even the most basic Internet tools can at last be put behind us. There will always
be a demand for complex tools from third parties, but now programmers will find
many of the basic Internet tools they need built into the operating system. In short,
with no further investments, you can use existing free Delphi resources to:
Since Delphi can call the entire Windows API, and since we have support for
OCX/ActiveX, this new strategy from Microsoft fits in very neatly with our plans.
They can build the tools, and Delphi programmers can reap the harvest!
This paper contains three big sections, and a number of smaller sections. The big
break down into major topics is as follows:
In general, the WININET and ISAPI sections of this paper are entirely
independent, and you can read them in which ever order you like.
You will need a copy of Microsoft Windows NT 3.51 Server or NT 4.0 Server
with the Internet Information Server loaded in order to use the technology in this
paper. The Internet Information Server ships with NT Server 4.0, and at the time of
this writing users of NT 3.51 can download it from the Microsoft WEB site. In
general, you need a 486 computer with 20 or more megs of RAM to use Windows
NT.
You should also have a second computer equipped with a WEB browser. For the
ISAPI sections of this paper, this second computer can be running any software
that supports WEB browsing. The WININET code will work best if you have a
copy of Windows 95 or Windows NT running on your system. Any reasonable
WEB browser can be used with all of this technology.
The Delphi 2.0 update, released in June of 1996, has most of the materials you
need to get Delphi connected to the Internet. If you don't have this update, or if you
need to find a particular file mentioned in this paper, everything that you need is
also available for free from the WEB. All of the technologies discussed in this
paper work with Delphi 2.0, but do not necessarily work with 16 bit Delphi.
If you need to download information from the WEB, here's where to go to get
started:
http://www.borland.com/TechInfo/delphi/index.html
New versions of Delphi 2.0 ship with WININET.PAS. However, if your copy
doesn't have it, then this Borland WEB page can supply it for you. WININET.PAS
contains manifests, functions, types and prototypes for the Microsoft Windows
Internet Extensions. This means you can now easily add FTP, Gopher and HTTP
support to your programs. Microsoft's WININET.DLL is freely distributable, and
is available from Microsoft if it is not already installed in your Windows/System
or Windows/System32 directory. Here is how to get the Windows help file for
WININET.H:
http://www.microsoft.com/intdev/sdk/docs/wininet/default.htm
In general, it's the INTDEV section of the Microsoft WEB site that is home ground
for MS Internet developers.
Besides WININET and ICP another key technology supported by Delphi is ISAPI.
As stated in the Microsoft documentation, this technology allows you to "Write
server-side scripts and filters to extend the capabilities of Microsoft Internet
Information Server and other ISAPI Web servers." Here's where to go if you want
to find out about the ISAPI specification:
http://www.microsoft.com/intdev/sdk/servapi.htm
A copy of the key ISAPI file, called HTTPEXT.PAS, is appended to the end of
this article.
Microsoft's freely distributable Internet Control Pack (ICP) is a set of
OCX/ActiveX controls you can drop into Delphi applications. (The Delphi 2.0
update ships with these controls.) They provide immediate support for creating
Delphi apps that know how to brows the WEB, as well as make use of FTP,
WINSOCK, and other Internet technologies. If your copy of Delphi didn't ship with
these controls, then before you can use them you need to add some files to the
Delphi LIB directory. The files you need are available at the Borland
INDEX.HTML WEB site listed above. I do not discuss the ICP controls in this
paper, but anyone interested in the technology discussed here should definitely
make sure they have a copy of these controls.
You can download my Pascal utility files called STRBOX.PAS and
MATHBOX.PAS from my web site:
http://users.aol.com/charliecal
In general, it is worth checking this web site for possible updates on information
discussed in this paper.
I am assuming that readers of this paper are familiar with Delphi and Object Pascal, and that they have a basic understanding of the Internet, HTML, WEB Browsers, and WEB servers.
ISAPI is a very easy to use, but extremely powerful technology that allows you to
extend the power of the Internet Information Server. This tool ships with Windows
NT 4.0 Server and allows you to set up WEB, FTP and GOPHER sites on your
servers. It is also compatible with Windows NT 3.51 Server.
In the past, the best way to extend a WEB server was to create CGI applications.
These were powerful tools, but they were limited by their executable format.
When you sent in a CGI based request from a browser to a server the CGI
application in question usually had to be loaded into memory, which took a long
time. Also, the CGI technology could be a bit awkward to use under some
circumstances.
ISAPI is a method of writing DLLs that replace CGI applications. You can also
write filters with ISAPI, but I will not discuss that technology in this paper. ISAPI
has the advantage of being easier to use than CGI, plus it can be much faster and
make much better use of system resources. In particular, the following points help
explain why ISAPI DLLS are better than CGI applications:
As mentioned above, you can write filters with ISAPI. Here, according to the
Microsoft documentation, are some of the things you can do with ISAP filters:
In this paper, however, I will concentrate on writing DLLs that return data sets, or
that simply communicate with the user who is running a browser.
The file HTTPEXT.PAS contains the key declarations used with ISAPI. This file
should ship with versions of Delphi dated June, 1996 or later. It is also available
on the Borland WEB site and is appended to the end of the ISAPI section of this
document. Because this is an NT based technology, you must be using Delphi 2.0
or higher to access this technology. You can't use it from a 16 bit compiler.
HTTPEXT.PAS contains the interface to the ISAPI technology that has been
created by Microsoft. At the time of this writing Delphi has no custom interface for
ISAPI, and I will describe only how to use Microsoft's existing technology.
However, ISAPI is extremely easy to use, and the edition of a custom Delphi
object is not necessary for most users.
There are three functions that can serve as entry points to ISAPI DLLs. The first
two listed below are mandetory, while the third is optional:
When you are creating an ISAPI DLL, you must export the first two of the three
functions listed above. Implementing these two functions is the key to all ISAPI
programming.
These three routines all contain the word Extension. This term is used because
ISAPI DLLs extend the Internet Information Server. (Remember, the Internet
Information Server is Microsoft's WEB server. If you want to turn an NT server
into a WEB server, then this is the tool you use. It ships with NT 4.0, and is
installed automatically during the setup of that operating system.)
ISAPI provides a standard which other server manufactures could follow. For
instance, it would be nice to simplify access to Netscape's complex NSAPI
interface by encapsulating it inside the relative simplicity and elegance of ISAPI.
Here are the declarations for the two mandatory routines:
function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; function HttpExtensionProc(var ECB: TExtensionControlBlock): DWORD; stdcall;
You can always just cut and past the GetExtensionVersion code into your DLLs.
You would need to change the function only slightly when new releases of ISAPI
are made public:
function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Ver.dwExtensionVersion := $00010000; // 1.0 support Ver.lpszExtensionDesc := 'Delphi 2.0 ISAPI DLL'; // Description Result := True; end;
The parameter passed to this function is declared in HTTPEXT.PAS as follows:
PHSE_VERSION_INFO = ^THSE_VERSION_INFO; THSE_VERSION_INFO = packed record dwExtensionVersion: DWORD; lpszExtensionDesc: array[0..HseMaxExtDLLNameLen-1] of Char; end;
HseMaxExtDLLNameLen is declared to have a value of 256. The two fields of the
record are self explanatory, with the first containing the ISAPI version number and
the second holding a user defined string describing the purpose of the DLL.
At the same time that you write the GetExtensionVersion routine, you would want
to add an exports section to the DPR file for your DLL. You might as well export
both mandatory functions at the same time when you write this part of the DLL:
exports GetExtensionVersion, HttpExtensionProc;
That's all you need to do to set up the first of the two mandatory functions in an
ISAPI DLL. The next step, using HttpExtensionProc, is a bit more complex, so I
will treat it in its own section.
The HttpExtensionProc routine is the entry point for the DLL. It serves the same
purpose that the main() routine does in a C program, or that the main begin..end
pair does in a Delphi program.
Here is a very simple example of a GetExtensionVersion routine:
function HttpExtensionProc(var ECB: TExtensionControlBlock):
DWORD; stdcall;
var
ResStr: string;
StrLen: Integer;
begin
ECB.lpszLogData := 'Delphi DLL Log';
ECB.dwHTTPStatusCode := 200;
ResStr := '<HTML><TITLE>Test server result</TITLE>' +
'<H1>Test server results</H1>' +
'<BODY>Hello from ISAPI<BR></BODY>' +
'</HTML>';
ResStr := Format(
'HTTP/1.0 200 OK'#13#10+
'Content-Type: text/html'#13#10+
'Content-Length: %d'#13#10+
'Content:'#13#10#13#10'%s', [Length(ResStr), ResStr]);
StrLen := Length(ResStr);
ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
Result := HSE_STATUS_SUCCESS;
end;
If you queried this DLL from a browser you would get a page back saying:
Test Server Results
Hello from ISAPI
Most of the body of the function is taken up with simple HTML code that provides
basic information. You also need to fill out a few fields of the
TExtensionControlBlock, shown below.
Notice that there is a function pointer in the record called WriteClient. You can
call this function to send information back to the browser. When calling this
function, you use the value in the ConnID field of the TExtensionControl block
record shown below. ConnID is filled out for you automatically when the
HttpExtensionProc function is called.
Before looking at the TExtensionControlBlock function, let me show you a
complete ISAPI DLL that uses the HttpExtensionProc function shown above:
library Isapi1; library Isapi1; uses Windows, SysUtils, HTTPExt; function GetExtensionVersion( var Ver: THSE_VERSION_INFO ): BOOL; stdcall; begin Ver.dwExtensionVersion := $00010000; // We're expecting version 1.0 support Ver.lpszExtensionDesc := 'Written in Delphi 2.0'; Result := True; end; function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ): DWORD; stdcall; var ResStr: string; StrLen: Integer; begin ECB.lpszLogData := 'Delphi DLL Log'; ECB.dwHTTPStatusCode := 200; ResStr := '
' +
'
' +
'
Isapi says hello to DevRel
';
ResStr := Format(
'HTTP/1.0 200 OK'#13#10+
'Content-Type: text/html'#13#10+
'Content-Length: %d'#13#10+
'Content:'#13#10#13#10'%s', [Length(ResStr), ResStr]);
StrLen := Length(ResStr);
ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
Result := HSE_STATUS_SUCCESS;
end;
exports
GetExtensionVersion,
HttpExtensionProc;
begin
end.
To use this DLL, you should copy it into a subdirectory of the scripts directory
beneath your inetsrv directory. On my NT 4.0 machine, that looks like this:
c:\winnt\system32\inetsrv\scripts\mystuff\isapi1.dll
In this case, I have created the directory called MYSTUFF, and it is used solely
for storing ISAPI DLLs that I have created. You milage, may, of course, differ on
your machine, depending on where you put the inetsrv directory and various other
factors.
To call this DLL, you should add the following hyperlink to one of your HTML
pages:
<A HREF="/scripts/mystuff/isapi1.dll" >ISAPI One</A><BR>
When the user clicks on this hyperlink, then the ISAPI1 DLL will be called and the
string "Hello from ISAPI" will appear in the user's browser. If you did not put the
ISAPI1.DLL in the MYSTUFF directory then you should change the above HTML
code to reflect that fact. Notice that the path you assign is relative to the INETSRV
directory, and does not, and should not, contain the entire path to your DLL.
Here is a complete HTML script that will call the ISAPI.DLL:
<HTML> <HEAD> <TITLE>CharlieC Home Page</TITLE> </HEAD> <BODY> <H1>My Home Page </H1> <P> This is the home page for my home computer. <P> <A HREF="/scripts/mystuff/isapi1.dll" >ISAPI One</A><BR> </BODY> </HTML>
Note that if you copy the ISAPI1.DLL into the MYSTUFF directory multiple times
you will need to shut down the WWW portion of the Internet Server before each
copy. The rule is that you can copy the DLL the fist time for free, but after you have
used it, it belongs to the server, and you need to shut the WWW services on the
server down before you can copy an updated version of the file over the first copy.
You can use the Internet Service Manager application to shut down the WWW
services. This application should be in the Microsoft Internet Server group created
in the Explorer/Program Manager at the time of the install of the Internet
Information Server.
By this point in the paper you should have been able to create you first ISAPI DLL
and call it from a WEB browser on a second machine. The rest of the ISAPI
section of this paper explores ISAPI in more depth.
Here is the fairly complex record that is passed as the sole parameter to
HttpExtensionProc:
PExtensionControlBlock = ^TExtensionControlBlock; TExtensionControlBlock = packed record cbSize: DWORD; // = sizeof(TExtensionControlBlock) dwVersion: DWORD; // version info of this spec ConnID: HCONN; // Context Do not modify! dwHttpStatusCode: DWORD; // HTTP Status code // null terminated log info specific to this Extension DLL lpszLogData: array [0..HSE_LOG_BUFFER_LEN-1] of Char; lpszMethod: PChar; // REQUEST_METHOD lpszQueryString: PChar; // QUERY_STRING lpszPathInfo: PChar; // PATH_INFO lpszPathTranslated: PChar; // PATH_TRANSLATED cbTotalBytes: DWORD; // Total bytes from client cbAvailable: DWORD; // Available number of bytes lpbData: Pointer; // pointer to cbAvailable bytes lpszContentType: PChar; // Content type of client data GetServerVariable: TGetServerVariableProc; WriteClient: TWriteClientProc; ReadClient: TReadClientProc; ServerSupportFunction: TServerSupportFunctionProc; end;
Notice that this record contains the ConnID field referenced above, and passed as
the first parameter to WriteClient.
The first parameter of this record is used for version control. It should be set to the
size of the TExtensionControlBlock. If Microsoft changes this structure, then they
can tell which version of the structure they are dealing with by checking the size of
the record as recorded in this field. You should never change any of the first three
fields of this record, they are filled out ahead of time by ISAPI, and can only be
referenced, but not changed, by your program.
The most important field of this record is probably the lpszQueryString, which
contains information about the query passed in from the server. For instance,
suppose you have created a DLL called ISAPI1.DLL. To call this DLL, you would
create an HREF in one of your browser pages that looked like this:
<A HREF="/scripts/mystuff/test1.dll">Test One</A>
If you wanted to query the DLL, you would edit the above line so it looked like
this:
<A HREF="/scripts/mystuff/test1.dll?MyQuery">Test One</A>
Given the second of the two HTML fragments listed above, your DLL would get called with the string "MyQuery" in the lpszQueryString parameter. Notice in particular the use of the question mark, followed by the query string itself.
You could, of course, change the query string at will. For instance, you could
write:
<A HREF="/scripts/mystuff/test1.dll?ServerName">Test One</A>
To this query, the DLL might reply with the name of the server. There are no limits as to what you can pass in this parameter. It could be anything you want, and it is up to you to parse the information from inside the DLL as you like.
When you return information from the server back to the browser, you use the WriteClient function pointer which is part of this record. You don't have to do anything to initialize this pointer; it is passed to you gratis by the Internet Information Server.
Writers of CGI apps will notice that the syntax for sending query strings is familiar. Indeed, ISAPI follows many of the conventions of CGI, and most of the fields in the TExtensionControlBlock are simply borrowed directly from CGI technology.
Another key field in the TExtensionControlBlock is the lpbData field, which contains any additional information sent to you by the browser. For instance, if you have an HTML form with a number of fields in it, the information from these fields will be sent in the pointer called lpbData. The next section of this paper, called 'Getting Information from a Submit Button', focuses on how to handle this situation.
So far I have zeroed in on four key fields of the TExtensionControlBlock:
The best way to get a feeling for how the rest of the fields of TExtensionControlBlock work is simply to mirror them back to yourself in a browser. In other words, you would want to create an HTML page that allows the user to call a custom ISAPI DLL. The purpose of this ISAPI DLL is simply to snag the contents of each of the fields of the TExtensionControlBlock, format them in HTML, and send them back to the browser. This will turn your browser into a somewhat funky debugger that shows each of the fields in the TExtensionControlBlock.
Here is a program, written by Borland employee Danny Thorpe, that performs this
task:
library test1;
uses
Windows,
SysUtils,
HTTPExt;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ):
BOOL; stdcall;
begin
Ver.dwExtensionVersion := $00010000; // 1.0 support
Ver.lpszExtensionDesc := 'A test DLL written in Delphi 2.0';
Result := True;
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ):
DWORD; stdcall;
var
ResStr: string;
StrLen: Integer;
Buf: array [0..1024] of Char;
begin
ECB.lpszLogData := 'Delphi DLL Log';
ECB.dwHTTPStatusCode := 200;
ResStr := Format(
'<HTML><TITLE>Test server result</TITLE>' +
'<H1>Test server results</H1>' +
'Size = %d<BR>'+
'Version = %.8x<BR>'+
'ConnID = %.8x<BR>'+
'Method = %s<BR>' +
'Query = %s<BR>' +
'PathInfo = %s<BR>'+
'PathTranslated = %s<BR>'+
'TotalBytes = %d<BR>'+
'AvailableBytes = %d<BR>'+
'ContentType = %s<BR><BR>'+
'<H1>Some Server Variables</H1>',
[ECB.cbSize, ECB.dwVersion, ECB.ConnID,
ECB.lpszMethod, ECB.lpszQueryString,
ECB.lpszPathInfo, ECB.lpszPathTranslated,
ECB.cbTotalBytes, ECB.cbAvailable,
ECB.lpszContentType]);
with ECB do
begin
StrLen := Sizeof(Buf);
GetServerVariable(ConnID, 'REMOTE_ADDR', @Buf, StrLen);
ResStr := ResStr + 'REMOTE_ADDR = '+Buf+'<BR>';
StrLen := SizeOf(Buf);
GetServerVariable(ConnID, 'REMOTE_HOST', @Buf, StrLen);
ResStr := ResStr + 'Remote_Host = '+Buf+'<BR>';
StrLen := SizeOf(Buf);
GetServerVariable(ConnID, 'REMOTE_USER', @Buf, StrLen);
ResStr := ResStr + 'Remote_User = '+Buf+'<BR>';
StrLen := SizeOf(Buf);
GetServerVariable(ConnID, 'SERVER_NAME', @Buf, StrLen);
ResStr := ResStr + 'SERVER_NAME = '+Buf+'<BR>';
StrLen := SizeOf(Buf);
GetServerVariable(ConnID, 'SERVER_PORT', @Buf, StrLen);
ResStr := ResStr + 'SERVER_PORT = '+Buf+'<BR>';
StrLen := SizeOf(Buf);
GetServerVariable(ConnID, 'SERVER_PROTOCOL', @Buf, StrLen);
ResStr := ResStr + 'SERVER_PROTOCOL = '+Buf+'<BR>';
StrLen := SizeOf(Buf);
GetServerVariable(ConnID, 'SERVER_SOFTWARE', @Buf, StrLen);
ResStr := Format('%sSERVER_SOFTWARE = %s<BR>'+
'ThreadID = %.8x<BR>',[ResStr, Buf, GetCurrentThreadID]);
end;
ResStr := ResStr + '</HTML>';
ResStr := Format(
'HTTP/1.0 200 OK'#13#10+
'Content-Type: text/html'#13#10+
'Content-Length: %d'#13#10+
'Content:'#13#10#13#10'%s', [Length(ResStr), ResStr]);
StrLen := Length(ResStr);
ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
Result := HSE_STATUS_SUCCESS;
end;
exports
GetExtensionVersion,
HttpExtensionProc;
begin
end.
To call this DLL, you should create an HTML script that contains the following
line:
<A HREF="/scripts/mystuff/test1.dll">Test One</A> <BR>
Often you get information sent to you from an HTML form that has a Submit button
on it. As long as this information is shorter than 49 KB, you can assume that it will
be available in the lpbData field of TExetensionControlBlock. Here is how you
would typically read the information from the pointer passed in this field:
var S: string; begin … S := PChar(ECB.lpbData); … end;
If the information passed in this field is larger than 48 KB, then you will need to call ReadClient to get the rest of the information.
If you want to see exactly what information is available in the lpbData field, you
can use the following two functions to echo the data back your WEB browser:
function SetUpResString: string;
begin
Result := '<HTML>' +
'<TITLE>Test server result</TITLE>' +
'<H1>Test server results</H1>' +
'<BODY>lpbData = %s </BODY>' +
'</HTML>';
end;
function HttpExtensionProc(var ECB: TExtensionControlBlock):
DWORD; stdcall;
var
ResStr: string;
StrLen: Integer;
S, S1: string;
begin
ECB.lpszLogData := 'Delphi DLL Log';
ECB.dwHTTPStatusCode := 200;
ResStr := SetUpResString;
S := PChar(ECB.lpbData);
ResStr := Format(ResStr, [S]);
StrLen := Length(ResStr);
ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
Result := HSE_STATUS_SUCCESS;
end;
Assume that you have an HTML form with the following code in it:
<FORM ACTION="/scripts/mystuff/isapi2.dll" METHOD="POST" ENCTYPE="application/x-www-form-urlencoded"> <P> Enter Number to Square: <INPUT NAME="GetSquare" VALUE="" MAXLENGTH="25" SIZE=25> <P> <INPUT TYPE=SUBMIT VALUE="Submit" NAME="GetSquare"> </FORM>
This code will produce a form that contains a text area where you can enter a
number, and a button called submit. The name of the button is "GetSquare". Given
this form, then you can expect the two routines shown above to return the following
string, assuming the user enters the number 23 in the text area on the form:
lpbData = GetSquare=23&GetSquare=Submit
To understand what is happening here, note the BODY of the HTML statement
composed on the server as reflected in the following excerpt from SetUpResString
function shown above:
'<BODY>lpbData = %s </BODY>' +
If you study the code in the HttpExtensionProc function shown above, you will see
that it uses the Format routine to substitute the value of ECB.lpbData for the %s
variable in the piece of code shown immediately before this sentence. (If you don't
understand how Format works, see the Delphi docs.)
Given the form shown above, the value of lpbData sent to the ISAPI DLL when the
user presses the submit button is:
GetSquare=23&GetSquare=Submit
For the sake of clarity, let me repeat that the two routines shown above echo this
information back to the browser in the following string, which you already saw
quoted above:
lpbData = GetSquare=23&GetSquare=Submit
The best way to see this process in action is to run the ISAPI2 program listed
below. ISAPI2 is the same as ISAPI1, but it has the new HttpExtensionProc
function shown above, also the SetUpResString utility function.
library Isapi2;
uses
Windows, SysUtils, HTTPExt;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ):
BOOL; stdcall;
begin
Ver.dwExtensionVersion := $00010000; // 1.0 support
Ver.lpszExtensionDesc := 'DLL written in Delphi 2.0';
Result := True;
end;
function SetUpResString: string;
begin
Result := '<HTML>' +
'<TITLE>Test server result</TITLE>' +
'<H1>Test server results</H1>' +
'<BODY>lpbData = %s </BODY>' +
'</HTML>';
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ):
DWORD; stdcall;
var
ResStr: string;
StrLen: Integer;
S, S1: string;
Len: Integer;
begin
ECB.lpszLogData := 'Delphi DLL Log';
ECB.dwHTTPStatusCode := 200;
ResStr := SetUpResString;
S := PChar(ECB.lpbData);
ResStr := Format(ResStr, [S]);
StrLen := Length(ResStr);
ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
Result := HSE_STATUS_SUCCESS;
end;
exports
GetExtensionVersion,
HttpExtensionProc;
begin
end.
Once you get the information from the form in the lpbData parameter, you can then parse it and return information to the user. For instance, you could extract the number 23 from the above example and then square it and return it to the user. Doing this would in effect allow you to get information from the user, namely a number, then perform a mathematical action on the number, and return the result to the user. This means you are creating dynamic, interactive WEB pages on the fly, which is the current Sin Qua Non of Internet programming!
Here is the complete code for a program that will square a number submitted to it
over the net via a browser:
library Isapi3;
{ This code shows how to take input from the user via a browser,
parse that information, and then return an answer to the user.
In particular, the user submits a number, this code squares it,
and then sends the result back to user.
Here is the form from the browser that submits the information
for parsing:
<FORM ACTION="/scripts/mystuff/isapi2.dll" METHOD="POST"
ENCTYPE="application/x-www-form-urlencoded">
<P>
Enter Number to Square: <INPUT NAME="GetSquare" VALUE=""
MAXLENGTH="25" SIZE=25>
<P>
<INPUT TYPE=SUBMIT VALUE="Submit" NAME="GetSquare">
</FORM>
}
uses
Windows, SysUtils, HTTPExt,
StrBox;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ):
BOOL; stdcall;
begin
Ver.dwExtensionVersion := $00010000; // version 1.0 support
Ver.lpszExtensionDesc := 'ISAPI3.DLL';
Result := True;
end;
// Parse lpbData and retrieve the number the user passed to us.
function ParseData(S: string): Integer;
begin
S := StripLastToken(S, '&');
S := StripFirstToken(S, '=');
Result := StrToInt(S);
end;
function SetUpResString: string;
begin
Result := '<HTML>' +
'<TITLE>Test server result</TITLE>' +
'<H1>Test server results</H1>' +
'<BODY>Answer = %d </BODY>' +
'</HTML>';
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ):
DWORD; stdcall;
var
ResStr: string;
StrLen: Integer;
S, S1: string;
Num: Integer;
begin
ECB.lpszLogData := 'Delphi DLL Log';
ECB.dwHTTPStatusCode := 200;
ResStr := SetUpResString;
S := PChar(ECB.lpbData);
Num := ParseData(S);
Num := Sqr(Num);
ResStr := Format(ResStr, [Num]);
StrLen := Length(ResStr);
ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
Result := HSE_STATUS_SUCCESS;
end;
exports
GetExtensionVersion,
HttpExtensionProc;
begin
end.
This code might receive the following string from the user who presses the submit
button, asking that a number be squared:
GetSquare=5&GetSquare=Submit
Given this input, the code above would return the following string to the user
across the internet:
Answer = 25
In short, the user enters the number 5, and you will return to him the number 25. If
the user submits the number 10, then you can return the number 100. This all
sounds very trivial, but the key issue here is that this activity is taking place on the
internet!
The function that parses the data sent by the user looks like this:
// Parse lpbData and retrieve the number the user passed to us. function ParseData(S: string): Integer; begin S := StripLastToken(S, '&'); S := StripFirstToken(S, '='); Result := StrToInt(S); end;
The StripLastToken and StripFirstToken routines are in the StrBox united
referenced at the beginning of this paper, and included on my web site.
That's most of what I want to say about ISAPI in this paper. This information
should be enough to get you up and running and having some fun with this great
technology. Below I briefly touch on some additional information on the
GetServerVariable and ReadClient routines, that I have experimented with in only
the most limited degree. I have also appended HTTPEXT.PAS to this paper since
you can't get anywhere without that key document.
You can use GetServerVariable to retrieve information from a server just as you
would request information inside a CGI application. Here is an example of calling
the routine:
Len := HseMaxExtDllNameLen;
SetLength(S1, Len);
Dec(Len);
ECB.GetServerVariable(ECB.ConnID,
'CONTENT_LENGTH',
PChar(S1),
Len);
The code first sets the length of the buffer that will hold the information retrieved from the server. It then calls the server and asks for information. For instance, in this case, it asks for the 'Content Length' of the information sent by the server.
The Microsoft documentation informs us that you can pass the following strings in
the second parameter of GetServerVariable:
| AUTH_TYPE | This contains the type of authentication used. For example, if basic authentication is used, the string will be "basic." For NT challenge-response, it will be "NTLM." Other authentication schemes will have other strings. Since new authentication types can be added to the Internet Server, it is not possible to list all the stsring possibilities. If the string is empty, then no authentication is used. |
| CONTENT_LENGTH | The number of bytes which the script can expect to receive from the client. |
| CONTENT_TYPE | The content type of the information supplied in the body of a POST request. |
| PATH_INFO | Additional path information, as given by the client. This consists of the trailing part of the URL after the script name, but before the query string, if any. |
| PATH_TRANSLATED | This is the value of PATH_INFO, but with any virtual path name expanded into a directory specification. |
| QUERY_STRING | The information which follows the '"?" in the URL that referenced this script. |
| REMOTE_ADDR | The IP address of the client or agent of the client (for example, gateway or firewall) that sent the request. |
| REMOTE_HOST | The hostname of the client or agent of the client (for example, gateway or firewall) that sent the request. |
| REMOTE_USER | This contains the username supplied by the client and authenticated by the server. This comes back as an empty string when the user is anonymous (but authenticated). |
| UNMAPPED_REMOTE_USER | This is the username before any ISAPI ApplicationsPI Filter mapped the user making the request to an NT user account (which appears as REMOTE_USER). |
| REQUEST_METHOD | The HTTP request method. |
| SCRIPT_NAME | The name of the script program being executed. |
| SERVER_NAME | The server's hostname, or IP address, as it should appear in self-referencing URLs. |
| SERVER_PORT | The TCP/IP port on which the request was received. |
| SERVER_PORT_SECURE | A string of either zero or 1. If the request is being handled on the secure port, then this will be 1. Otherwise, it will be zero. |
| SERVER_PROTOCOL | The name and version of the information retrieval protocol relating to this request. This is normally HTTP/1.0. |
| SERVER_SOFTWARE | The name and version of the Web server under which the ISAPI ApplicationsPI DLL program is running. |
| ALL_HTTP | All HTTP headers that were not already parsed into one of the previous variables. These variables are of the form HTTP_<header field name>. The headers consist of a null-terminated string with the individual headers separated by line feeds. |
| HTTP_ACCEPT | Special-case HTTP header. Values of the Accept: fields are concatenated and separated by a comma (","). For example, if the following lines are part of the HTTP header: accept: */*; q=0.1 |
| URL (new for vwesion 2.0) | Gives the base portion of the URL. |
Note that many of the pieces of information listed above are automatically passed in the TExtensionControlBlock record. You therefore usually do not need to call GetServerVariable, but you can if you need to, particularly if you want to retrieve information from with ReadClient and need to know how much information to read.
Most of the time, you do not need to call ReadClient. However, if the amount of
data being sent by the browser is larger than 48 KB, then you will need to call
ReadClient to get the rest of the data.
Here is the freely distributable HTTPEXT.PAS file from the Borland WEB site:
{*******************************************************}
{ }
{ Delphi ISAPI Interface }
{ Version 1.0 HTTP Server Extension interface. }
{ }
{ Copyright (c) 1996 Borland International }
{ }
{*******************************************************}
unit HTTPExt;
interface
uses Windows;
const
HSE_VERSION_MAJOR = 1; // major version of this spec
HSE_VERSION_MINOR = 0; // minor version of this spec
HSE_LOG_BUFFER_LEN = 80;
HSE_MAX_EXT_DLL_NAME_LEN = 256;
type
HCONN = THandle;
// the following are the status codes returned by the Extension DLL
const
HSE_STATUS_SUCCESS = 1;
HSE_STATUS_SUCCESS_AND_KEEP_CONN = 2;
HSE_STATUS_PENDING = 3;
HSE_STATUS_ERROR = 4;
// The following are the values to request services with the ServerSupportFunction.
// Values from 0 to 1000 are reserved for future versions of the interface
HSE_REQ_BASE = 0;
HSE_REQ_SEND_URL_REDIRECT_RESP = ( HSE_REQ_BASE + 1 );
HSE_REQ_SEND_URL = ( HSE_REQ_BASE + 2 );
HSE_REQ_SEND_RESPONSE_HEADER = (_HSE_REQ_BASE + 3 );
HSE_REQ_DONE_WITH_SESSION = ( HSE_REQ_BASE + 4 );
HSE_REQ_END_RESERVED = 1000;
//
// These are Microsoft specific extensions
//
HSE_REQ_MAP_URL_TO_PATH = (HSE_REQ_END_RESERVED+1);
HSE_REQ_GET_SSPI_INFO = (HSE_REQ_END_RESERVED+2);
//
// passed to GetExtensionVersion
//
type
PHSE_VERSION_INFO = ^THSE_VERSION_INFO;
THSE_VERSION_INFO = packed record
dwExtensionVersion: DWORD;
lpszExtensionDesc: array [0..HSE_MAX_EXT_DLL_NAME_LEN-1] of Char;
end;
type
TGetServerVariableProc = function ( hConn: HCONN;
VariableName: PChar;
Buffer: Pointer;
var Size: DWORD ): BOOL stdcall;
TWriteClientProc = function ( ConnID: HCONN;
Buffer: Pointer;
var Bytes: DWORD;
dwReserved: DWORD ): BOOL stdcall;
TReadClientProc = function ( ConnID: HCONN;
Buffer: Pointer;
var Size: DWORD ): BOOL stdcall;
TServerSupportFunctionProc = function ( hConn: HCONN;
HSERRequest: DWORD;
Buffer: Pointer;
var Size: DWORD;
var DataType: DWORD ): BOOL stdcall;
//
// passed to extension procedure on a new request
//
type
PEXTENSION_CONTROL_BLOCK = ^TEXTENSION_CONTROL_BLOCK;
TEXTENSION_CONTROL_BLOCK = packed record
cbSize: DWORD; // size of this struct.
dwVersion: DWORD; // version info of this spec
ConnID: HCONN; // Context number not to be modified!
dwHttpStatusCode: DWORD; // HTTP Status code
// null terminated log info specific to this Extension DLL
lpszLogData: array [0..HSE_LOG_BUFFER_LEN-1] of Char;
lpszMethod: PChar; // REQUEST_METHOD
lpszQueryString: PChar; // QUERY_STRING
lpszPathInfo: PChar; // PATH_INFO
lpszPathTranslated: PChar; // PATH_TRANSLATED
cbTotalBytes: DWORD; // Total bytes indicated from client
cbAvailable: DWORD; // Available number of bytes
lpbData: Pointer; // pointer to cbAvailable bytes
lpszContentType: PChar; // Content type of client data
GetServerVariable: TGetServerVariableProc;
WriteClient: TWriteClientProc;
ReadClient: TReadClientProc;
ServerSupportFunction: TServerSupportFunctionProc;
end;
//
// these are the prototypes that must be exported from the extension DLL
//
// function GetExtensionVersion( var Ver: THSE_VERSION_INFO ): BOOL; stdcall;
// function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ): DWORD; stdcall;
// the following type declarations is for the server side
// typedef BOOL (WINAPI * PFN_GETEXTENSIONVERSION)( HSE_VERSION_INFO *pVer );
// typedef DWORD (WINAPI * PFN_HTTPEXTENSIONPROC )( EXTENSION_CONTROL_BLOCK *pECB );
implementation
end.
You are now in the second section of this paper, which covers WININET. As
mentioned above, this section of the paper is totally discreet from the first section.
Let's take a brief look at the code you need to write to use the WININET DLL in an
FTP session. This will not be an exhaustive study, but it should help to get you up
and running. The first thing you need to know about this technology is that some of
the functions in WININET.PAS return a pointer variable declared to be of type
HINTERNET:
var HINTERNET: Pointer;
This pointer acts as a handle to the various Internet services you employ. After
retrieving the handle, you will pass it in as the first parameter to many of the other
WININET function you call throughout the life of a single session.
You need to remember to return the handle to the system when you are through
using it, usually by calling the WININET function called InternetCloseHandle:
function InternetCloseHandle(hInet: HINTERNET): BOOL; stdcall; To get a WININET session started, you call InternetOpen: function InternetOpen(lpszCallerName: PChar; dwAccessType: DWORD; lpszServerName: PChar; nServerPort: INTERNET_PORT; dwFlags: DWORD): HINTERNET; stdcall;
The first parameter is the name of the application opening the session. You can
pass in any string you want in this parameter. Microsoft documentation states that
"This name is used as the user agent in the HTTP protocol." The remaining
parameters can be set to 0 or nil:
var
MyHandle: HINTERNET;
…
begin
MyHandle := InternetOpen('MyApp', 0, nil, 0, 0);
end;
If you want more information on this function download WININET.HLP from
www.microsoft.com.
After opening the session, the next step is connect to server using InternetConnect:
function InternetConnect( hInet: HINTERNET; // Handle from InternetOpen lpszServerName: PChar; // Server: i.e., www.borland.com nServerPort: INTERNET_PORT; // Usually 0 lpszUsername: PChar; // usually anonymous lpszPassword: PChar; // usually your email address dwService: DWORD; // FTP, HTTP, or Gopher? dwFlags: DWORD; // Usually 0 dwContext: DWORD): // User defined number for callback HINTERNET; stdcall;
Here are the three possible, self explanatory, and mutually exclusive, flags that can
be passed in the dwService parameter:
INTERNET_SERVICE_FTP INTERNET_SERVICE_GOPHER INTERNET_SERVICE_HTTP
Here is the option for the dwFlags parameter:
INTERNET_CONNECT_FLAG_PASSIVE
This option is valid only if you passed INTERNET_SERVICE_FTP in the
previous parameter. At this time there are no other valid flags for this parameter.
If the session succeeds InternetOpen returns a valid pointer, otherwise it returns
nil.
After you are connected, you can call the GetCurrentDirectory to retrieve the name
of the current directory:
function TMyFtp.GetCurrentDirectory: string; var Len: Integer; S: string; begin Len := 0; ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len); SetLength(S, Len); ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len); Result := S; end;
This function is declared as follows:
function FtpGetCurrentDirectory( hFtpSession: HINTERNET; // handle from InternetConnect lpszCurrentDirectory: PChar; // directory returned here var lpdwCurrentDirectory: DWORD): // buf size of 2nd parameter BOOL; stdcall; // True on success
If you set the last parameter to zero, then WININET will use this parameter to
return the length of directory string. You can then allocate memory for your string,
and call the function a second time in order to retrieve the directory name. This
process is shown above in the GetCurrentDirectory method. (Notice the call to
SetLength. Delphi requires that you allocate memory for the new long strings in
situations like this! The issue here is that the string will be assigned a value inside
the operating system, not inside your Delphi application. As a result Delphi can't
perform its usual surreptitious strings allocations in these circumstances!)
Here's a set of functions that return the currently available files in a particular
directory:
function GetFindDataStr(FindData: TWin32FindData): string;
var
S: string;
Temp: string;
begin
case FindData.dwFileAttributes of
FILE_ATTRIBUTE_ARCHIVE: S := 'A';
// FILE_ATTRIBUTE_COMPRESSED: S := 'C';
FILE_ATTRIBUTE_DIRECTORY: S := 'D';
FILE_ATTRIBUTE_HIDDEN: S := 'H';
FILE_ATTRIBUTE_NORMAL: S := 'N';
FILE_ATTRIBUTE_READONLY: S := 'R';
FILE_ATTRIBUTE_SYSTEM: S := 'S';
FILE_ATTRIBUTE_TEMPORARY: S := 'T';
else
S := IntToStr(FindData.dwFileAttributes);
end;
S := S + GetDots(75);
Move(FindData.CFilename[0], S[6], StrLen(FindData.CFileName));
Temp := IntToStr(FindData.nFileSizeLow);
Move(Temp[1], S[25], Length(Temp));
Result := S;
end;
function TMyFtp.FindFiles: TStringList;
var
FindData: TWin32FindData;
FindHandle: HInternet;
begin
FindHandle := FtpFindFirstFile(FFtphandle, '*.*',
FindData, 0, 0);
if FindHandle = nil then begin
Result := nil;
Exit;
end;
FCurFiles.Clear;
FCurFiles.Add(GetFindDataStr(FindData));
while InternetFindnextFile(FindHandle, @FindData) do
FCurFiles.Add(GetFindDataStr(FindData));
InternetCloseHandle(Findhandle);
GetCurrentDirectory;
Result := FCurFiles;
end;
The key functions to notice here are ftpFindFirstFile, InternetFindNextFile, and
InternetCloseHandle. You use these functions in a manner similar to that employed
when calling the Delphi functions FindFirst, FindNext, and FindClose. In
particular, you use ftpFindFirstFile to get the first file in a directory. You then call
InterentFindNextFile repeatedly, until the function returns False. After finishing the
session, call InternetCloseHandle to inform the OS that it can deallocate the
memory associated with this process.
I'm not going to explain this process further in this newsletter. If you want more
information, you might look up FindFirst in the Delphi help. One final note: Unlike
the functions mentioned in the previous paragraph, TWin32FindData is not defined
in WININET.PAS, but instead can be found in the WIN32 help file that ships with
Delphi. It is declared in the WINDOWS.PAS file that ships with Delphi.
You can use the ftpGetFile function from WININET.PAS to retrieve a file via
FTP:
function FtpGetFile( hFtpSession: HINTERNET; // Returned by InternetConnect lpszRemoteFile: PChar; // File to get lpszNewFile: PChar; // Where to put it on your PC fFailIfExists: BOOL; // Overwrite existing files? dwFlagsAndAttributes: DWORD; // File attribute-See CreateFile. dwFlags: DWORD; // Binary or ASCII transfer dwContext: DWORD): // Usually zero BOOL stdcall; // True on success
Here is an example of how to use this call:
function TMyFtp.GetFile(FTPFile, NewFile: string): Boolean;
begin
Result := FtpGetFile(FFTPHandle, PChar(FTPFile), PChar(NewFile),
False, File_Attribute_Normal,
Ftp_Transfer_Type_Binary, 0);
end;
To learn about the parameters that can be passed in the dwFlagsAndAttributes
parameter, look up CreateFile in the WIN32 help file that ships with Delphi. The
dwFlags parameter can be set to either Ftp_Transfer_Type_Binary, or
Ftp_Transfer_Type_Ascii.
The following Delphi control gives you a starting place for building a visual tool
for performing Delphi WININET FTP sessions. As is, the control let's you use the
Object Inspector to define the RemoteServer, UserID and Password. The code
also automatically returns the current directory in a TStringList and allows you to
perform file transfers:
unit Ftp1;
{ FTP example using WININET.PAS rather than
an ACTIVEX control. Requires WININET.PAS and
WININET.DLL. WININET.DLL you can get from
Microsoft, WININET.PAS is available from
www.borland.com, or with some versions of
Delphi 2.0.
You might Respond to OnNewDir events as follows:
procedure TForm1.FTP1NewDir(Sender: TObject);
begin
ListBox1.Items := MyFtp1.FindFiles; // Get the directory list
end;
}
interface
uses
Windows, Classes, WinINet,
SysUtils;
type
TMyFtp = class(TComponent)
private
FContext: Integer;
FINet: HInternet;
FFtpHandle: HInternet;
FCurFiles: TStringList;
FServer: string;
FOnNewDir: TNotifyEvent;
FCurDir: string;
FUserID: string;
FPassword: string;
function GetCurrentDirectory: string;
procedure SetUpNewDir;
protected
destructor Destroy; override;
public
constructor Create(AOwner: TComponent); override;
function Connect: Boolean;
function FindFiles: TStringList;
function ChangeDirExact(S: string): Boolean;
function ChangeDirCustom(S: string): Boolean;
function BackOneDir: Boolean;
function GetFile(FTPFile, NewFile: string): Boolean;
function SendFile1(FTPFile, NewFile: string): Boolean;
function SendFile2(FTPFile, NewFile: string): Boolean;
function CustomToFileName(S: string): string;
published
property CurFiles: TStringList read FCurFiles;
property CurDir: string read FCurDir;
property UserID: string read FUserID write FUserID;
property Password: string read FPassword write FPassword;
property Server: string read FServer write FServer;
property OnNewDir: TNotifyEvent read FOnNewDir
write FOnNewDir;
end;
procedure Register;
implementation
uses
Dialogs;
// A few utility functions
function GetFirstToken(S: string; Token: Char): string;
var
Temp: string;
Index: INteger;
begin
Index := Pos(Token, S);
if Index < 1 then begin
GetFirstToken := '';
Exit;
end;
Dec(Index);
SetLength(Temp, Index);
Move(S[1], Temp[1], Index);
GetFirstToken := Temp;
end;
function StripFirstToken(S: string; Ch: Char): string;
var
i, Size: Integer;
begin
i := Pos(Ch, S);
if i = 0 then begin
StripFirstToken := S;
Exit;
end;
Size := (Length(S) - i);
Move(S[i + 1], S[1], Size);
SetLength(S, Size);
StripFirstToken := S;
end;
function ReverseStr(S: string): string;
var
Len: Integer;
Temp: String;
i,j: Integer;
begin
Len := Length(S);
SetLength(Temp, Len);
j := Len;
for i := 1 to Len do begin
Temp[i] := S[j];
dec(j);
end;
ReverseStr := Temp;
end;
function StripLastToken(S: string; Token: Char): string;
var
Temp: string;
Index: INteger;
begin
SetLength(Temp, Length(S));
S := ReverseStr(S);
Index := Pos(Token, S);
Inc(Index);
Move(S[Index], Temp[1], Length(S) - (Index - 1));
SetLength(Temp, Length(S) - (Index - 1));
StripLastToken := ReverseStr(Temp);
end;
procedure Register;
begin
RegisterComponents('Unleash', [TMyFtp]);
end;
constructor TMyFtp.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FCurFiles := TStringList.Create;
FINet := InternetOpen('WinINet1', 0, nil, 0, 0);
end;
destructor TMyFtp.Destroy;
begin
if FINet <> nil then
InternetCloseHandle(FINet);
if FFtpHandle <> nil then
InternetCloseHandle(FFtpHandle);
inherited Destroy;
end;
function TMyFtp.Connect: Boolean;
begin
FContext := 255;
FftpHandle := InternetConnect(FINet, PChar(FServer), 0,
PChar(FUserID), PChar(FPassWord),
Internet_Service_Ftp, 0, FContext);
if FFtpHandle = nil then
Result := False
else begin
SetUpNewDir;
Result := True;
end;
end;
function TMyFtp.GetCurrentDirectory: string;
var
Len: Integer;
S: string;
begin
Len := 0;
ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
SetLength(S, Len);
ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
Result := S;
end;
procedure TMyFtp.SetUpNewDir;
begin
FCurDir := GetCurrentDirectory;
if Assigned(FOnNewDir) then
FOnNewDir(Self);
end;
function GetDots(NumDots: Integer): string;
var
S: string;
i: Integer;
begin
S := '';
for i := 1 to NumDots do
S := S + ' ';
Result := S;
end;
function GetFindDataStr(FindData: TWin32FindData): string;
var
S: string;
Temp: string;
begin
case FindData.dwFileAttributes of
FILE_ATTRIBUTE_ARCHIVE: S := 'A';
// FILE_ATTRIBUTE_COMPRESSED: S := 'C';
FILE_ATTRIBUTE_DIRECTORY: S := 'D';
FILE_ATTRIBUTE_HIDDEN: S := 'H';
FILE_ATTRIBUTE_NORMAL: S := 'N';
FILE_ATTRIBUTE_READONLY: S := 'R';
FILE_ATTRIBUTE_SYSTEM: S := 'S';
FILE_ATTRIBUTE_TEMPORARY: S := 'T';
else
S := IntToStr(FindData.dwFileAttributes);
end;
S := S + GetDots(75);
Move(FindData.CFilename[0], S[6], StrLen(FindData.CFileName));
Temp := IntToStr(FindData.nFileSizeLow);
Move(Temp[1], S[25], Length(Temp));
Result := S;
end;
function TMyFtp.FindFiles: TStringList;
var
FindData: TWin32FindData;
FindHandle: HInternet;
begin
FindHandle := FtpFindFirstFile(FFtphandle, '*.*',
FindData, 0, 0);
if FindHandle = nil then begin
Result := nil;
Exit;
end;
FCurFiles.Clear;
FCurFiles.Add(GetFindDataStr(FindData));
while InternetFindnextFile(FindHandle, @FindData) do
FCurFiles.Add(GetFindDataStr(FindData));
InternetCloseHandle(Findhandle);
GetCurrentDirectory;
Result := FCurFiles;
end;
function TMyFtp.CustomToFileName(S: string): string;
const
PreSize = 6;
var
Temp: string;
TempSize: Integer;
begin
Temp := '';
TempSize := Length(S) - PreSize;
SetLength(Temp, TempSize);
Move(S[PreSize], Temp[1], TempSize);
Temp := GetFirstToken(Temp, ' ');
Result := Temp;
end;
function TMyFtp.BackOneDir: Boolean;
var
S: string;
begin
S := FCurDir;
S := StripLastToken(S, '/');
if S = '/' then begin
Result := False;
Exit;
end;
if S <> '' then begin
ChangeDirExact(S);
Result := True;
end else begin
ChangeDirExact('/');
Result := True;
end;
end;
// Changes to specific directory in S
function TMyFtp.ChangeDirExact(S: string): Boolean;
begin
if S <> '' then
FtpSetCurrentDirectory(FFTPHandle, PChar(S));
Result := True;
FindFiles;
SetUpNewDir;
end;
// Assumes S has been returned by GetFindDataString;
function TMyFtp.ChangeDirCustom(S: string): Boolean;
begin
S := CustomToFileName(S);
if S <> '' then
FtpSetCurrentDirectory(FFTPHandle, PChar(S));
Result := True;
FindFiles;
SetUpNewDir;
end;
function TMyFtp.GetFile(FTPFile, NewFile: string): Boolean;
begin
Result := FtpGetFile(FFTPHandle, PChar(FTPFile), PChar(NewFile),
False, File_Attribute_Normal,
Ftp_Transfer_Type_Binary, 0);
end;
function TMyFtp.SendFile1(FTPFile, NewFile: string): Boolean;
const
Size:DWord = 3000;
var
Transfer: Bool;
Error: DWord;
S: string;
begin
Transfer := FtpPutFile(FFTPHandle, PChar(FTPFile),
PChar(NewFile),
Ftp_Transfer_Type_Binary, 0);
if not Transfer then begin
Error := GetLastError;
ShowMessage(Format('Error Number: %d. Hex: %x',
[Error, Error]));
SetLength(S, Size);
if not InternetGetLastResponseInfo(Error, PChar(S), Size) then
begin
Error := GetLastError;
ShowMessage(Format('Error Number: %d. Hex: %x',
[Error, Error]));
end;
ShowMessage(Format('Error Number: %d. Hex: %x Info: %s',
[Error, Error, S]));
end else
ShowMessage('Success');
Result := Transfer;
end;
function TMyFtp.SendFile2(FTPFile, NewFile: string): Boolean;
var
FHandle: HInternet;
begin
FHandle := FtpOpenFile(FFTPHandle, 'sam.txt', GENERIC_READ,
FTP_TRANSFER_TYPE_BINARY, 0);
if FHandle <> nil then
InternetCloseHandle(FHandle)
else
ShowMessage('Failed');
Result := True;
end;
end.