Extending the abilities of a web server is often done in interpreter languages like Perl or Python. If the server runs under Windows, a compiled Visual Basic program can be used alternatively, improving speed and security. Our first example is written for conventional HTTP servers like Apache or Microsoft IIS, using the STDIN and STDOUT mechanism. The second one is for the HTTP server of Shamrock NetMail, searching a text file for a phrase and displaying the result. Security considerations are discussed, too.
Most web servers use file handles for standard-in and standard-out for passing form data to a CGI program (Common Gateway Interface), and for getting the answer page from it. The sample program shows how to do this in Visual Basic. Note that this program does not contain a VB form, but a module with Main( ) instead. This reduces the overhead significantly since no window is required for this application.
The program can be called with something like
to pass values from an HTML page using the POST method via STDIN, or
for the GET method using a query string (like http://domain/cgi-bin/testcgi.exe?q=Test) which can be used if the resulting URL length is less then 1000 characters.
Our test program simply echoes the entered form values to an answer page. Since native Basic commands like Open and Close cannot be used for STDIN and STDOUT, a couple of Windows API functions must be declared instead. The server passes the request method (GET or POST) and, in case of POST, the input length as environment variables.
'TESTCGI.BAS: VB CGI program based on STDIN/STDOUT Option Explicit: DefLng A-Z 'HTML header and footer for answer page Const HtmlHdr$ = "Content-Type: text/html" & vbCrLf & vbCrLf & _ "<html><head>" & vbCrLf & "<title>CGI</title></head><body>" & vbCrLf Const HtmlFtr$=vbCrLf & "</body></html>" & vbCrLf 'Windows API functions Const STD_INPUT_HANDLE = -10& Const STD_OUTPUT_HANDLE = -11& Declare Function GetStdHandle Lib "kernel32" (ByVal h&) Declare Function ReadFile Lib "kernel32" (ByVal hFile, ByVal _ Buffer As Any, ByVal NumberOfBytes, BytesRead, ByVal Overlapped) Declare Function WriteFile Lib "kernel32" (ByVal hFile, ByVal _ Buffer As Any, ByVal NumberOfBytes, BytesWritten,ByVal Overlapped) Sub Main() Dim e$, g$, r&, h& g$ = Environ$("REQUEST_METHOD") If g$ = "GET" Then e$ = Environ$("QUERY_STRING") 'Method is GET Else h = GetStdHandle(STD_INPUT_HANDLE) 'If POST, use STDIN e$ = String$(Val(Environ$("CONTENT_LENGTH")),0) ReadFile h, e$, Len(e$), r, 0 End If g$=HtmlHdr & "<p>Method: " & g$ & "<br>Input: " & e$ & "</p>" & HtmlFtr h = GetStdHandle(STD_OUTPUT_HANDLE): WriteFile h, g$, Len(g$), r, 0 Exit Sub
All form parameters from the request page are typically passed url-encoded (delimited by &), and the text of the answer page must use HTML entities for special characters like <, >, & and ". Decode$( ) and HtmlEsc( ) in the next sample show how to achieve this. The only exception to the url-encoded format is when the form contains a file selection box for file uploads. In this case, the parameters are not escaped, and a boundary is defined in the first line which then delimits all following parameters and file contents, see the sample below.
'NMTEST.EXE: CGI sample for NetMail Option Explicit: DefLng A-Z Sub Main() Dim i, j, k, lines, rdy As Boolean Dim e$, f1$, f2$, fi$, search$,pair$ On Error GoTo fail fi$ = "C:\TEST.TXT" '<--- YOUR FILE! e$ = Command$: i = InStr(e$,""" """) If i Then 'Split commandline f1$ = Mid$(e$, 2, i - 2) f2$ = Mid$(e$, i + 3, Len(e$)-i-3) 'Analyze form data Open f2$ For Input As 1: j = 1 Line Input #1, e$ 'Ignore MIME type Line Input #1, e$: Close 1 'Content Do: i = InStr(j, e$, "&") If i Then pair$=Mid$(e$, j, i - j): j = i+1 Else pair$ = Mid$(e$, j) End If k = InStr(pair$, "=") 'name=value? If k Then If Left$(pair$,k-1)="Search" Then search$=Decode$(Mid$(pair$,k+1)) End If End If Loop Until i = 0 'Create output file Open f1$ For Output As 1 Print#1,"<html><head><title>Find"; Print#1,"</title></head><body>"; Print#1, "<h3>Results</h3><pre>" If LenB(search$) = 0 Then Print#1,"No search string entered" ElseIf LenB(Dir$(fi$)) = 0 Then Print #1, fi$ & " does not exist" Else:Open fi$ For Input Shared As 2 While Not EOF(2) And Not rdy Line Input #2, e$ If InStr(1, e$, search$, 1) Then HtmlEsc e$: Print #1, e$ lines = lines + 1 If lines >= 500 Then 'Max. lines Print #1, "...": rdy = True End If End If Wend: Close 2 Print #1, "Found lines: " & lines End If Print #1,"</pre></body></html>" Close 1 End If fail: 'Errors go here End Sub Private Function Decode$(g$) 'Decodes an URL-encoded string Dim i, j, f$, e$ e$ = g$: Replace e$, "+", " ": j = 1 Do: i = InStr(j, e$, "%") If i Then f$ = f$ & Mid$(e$, j, i - j) & _ Chr$(Val("&H" & Mid$(e$, i+1, 2))) j = i + 3 Else: f$ = f$ & Mid$(e$, j) End If Loop Until i = 0: Decode$ = f$ End Function Private Sub HtmlEsc(g$) 'Replaces special char's by entities Replace g$, "&", "&" Replace g$, "<", "<" Replace g$, ">", ">" Replace g$, """", """ End Sub Private Sub Replace(e$, f$, g$) 'Replaces string f$ in e$ by g$ Dim i, j, k j = 1: k = Len(g$) - Len(f$) + 1 Do: i = InStr(j, e$, f$) If i Then e$ = Left$(e$, i - 1) & g$ _ & Mid$(e$, i + Len(f$)): j = i + k End If Loop Until i = 0 Or j < 1 End Sub
The answer must contain a content-type as the very first line, followed by two CRLFs. The server will use this in the HTTP header it sends to the client browser. In most cases it will be text/html as above.
The HTTP server in the Mailer module of NetMail, primarily used for the webmail interface, also has a CGI interface (Mailer version 2.11 or higher). However, the implementation differs somewhat from Apache and other servers to make application programming even easier:
"Answer pathname" "Form data file"
The sample program searches a predefined text file for a phrase entered in an HTML form which looks like this:
<input type="text" name="Search">
<input type="submit" value="Find!"></form>
When a user enters "Basic" and clicks on the submit button in this form, a file is written by the NetMail server with a two-line content like this (a space was encoded as "+" in this sample!):
As in the first example above, no VB form is used since we do not need a window. Instead, the following code is a VB module with Main( ) as its entry point. Note that we do not need a single Windows API declaration here. - To optimize performance, it is useful to keep another small VB program in memory which uses the same (typically large) run-time DLLs so that they are not unloaded by Windows.
Though no explicit sample program for uploading files is given here, it must be mentioned that this requires a special "enctype" parameter in the HTML form tag:
<input type="text" name="Name">
<input type="file" name="File">
<input type="submit" value="Upload"></form>
Now, when someone enters "Jim" as his name and uploads a short 1-line text file t.txt, the resulting temporary NetMail file looks like this:
Content-Disposition: form-data; name="Name"
Content-Disposition: form-data; name="File"; filename="t.txt"
This is the content of the uploaded file.
This looks quite similar to MIME-encoded e-mails with several attachments, separated by a random boundary string, but there is a big difference: The browser does not convert binary files using the BASE64 algorithm, they remain binary.
CGI programs are potential causes of security threats if they are not implemented carefully. One scenario which should be considered in all possible variations is that an attacker writes an HTML file on his local hard disk with the form "action" parameter pointing to your CGI program in the web.
For instance, if the CGI program does not use hardcoded filenames but gets a pathname from a form field of the requesting page, an attacker could access virtually any file on your server and even on network shares. The same is true for allowing shell commands, of course. Even if the path is hardcoded and only the filename is a form field, one must be aware that names like ../bin/somefile might be used to access files in other directories, so parsing names for ".." is a must.
When the CGI program calls another program such as sendmail, it must ensure that no redirection or pipe symbols like <, >, | can be passed to the commandline of this program, e.g. as a part of an address entered in a form field. It is best to allow only characters which are typically part of an e-mail address.
If a form is used to send e-mails, it must also be avoided that a spammer is able to use the CGI program as a mail relay, sending his unwanted marketing messages to third parties. The logical consequence is that an e-mail form must never contain the destination address in any of its fields.
The NetMail HTTP server has some built-in limitations regarding the cgi-bin folder for security reasons:
Entering "CGI security" into an Internet search engine will show many more aspects of potential security threats. Unfortunately, not all web developers are aware of them. For example, thousands of spam mails are relayed by insecure CGI scripts every day, and many servers are defaced through security holes.
© 04/2006 Shamrock Software GmbH