Four different CGI
applications are presented in this chapter, all of which use queries
and form information to produce some interesting documents with
hypertext and graphics. These applications include:
One of the most common
applications on the Web is a guestbook. It is simply a form that
allows visitors to enter some information about themselves. This
information is placed in a file for everyone to see. Here are the
steps that need to be taken to create a guestbook:
- Display a form with such fields as
name, email address, and comments
- Write a CGI program to decode the form
- Place the information in a file
The program begins as follows:
#!/usr/local/bin/perl
$webmaster = "shishir\@bu\.edu";
$method = $ENV{'REQUEST_METHOD'};
$script = $ENV{'SCRIPT_NAME'};
$query = $ENV{'QUERY_STRING'};
$document_root = "/usr/local/bin/httpd_1.4.2/public";
$guest_file = "/guestbook.html";
$full_path = $document_root . $guest_file;
In this initialization code,
the document_root variable is the directory
that contains your HTML files. Set this variable
to the value of DocumentRoot, as defined in
the srm.conf configuration file. The guest_file
variable contains the relative path to the guestbook file, relative
to DocumentRoot. And full_path
represents the full path to the guestbook file. It is very important
to separate the full path from the relative path, as you will see
in a moment.
$exclusive_lock = 2;
$unlock = 8;
The lock definitions are stored in the exclusive_lock
and unlock variables, respectively.
if ($method eq "GET") {
if ($query eq "add") {
This program is coded slightly
differently from the programs that you have seen in this book. Let's
first see how this program can be accessed:
- A URL of https://your.machine/cgi-bin/guestbook.pl?add,
using the GET method, will present a form for
visitors to enter information.
- A URL of https://your.machine/cgi-bin/guestbook.pl,
using the GET method, will display the actual
guestbook file. (The user can also see the guestbook file by opening
that file directly, e.g., by accessing https://your.machine/guestbook.html.)
- When the form is submitted using the POST
method, this program decodes the information, and outputs a thank-you
message.
As you can see, this
program is very versatile. It handles all tasks of the guestbook.
You could just as easily split the program into its constituents:
an HTML form, a program to display the guestbook
(optional), and a program to decode the form information. There
are advantages either way. Combining all tasks into the single program
ensures that all components of the program are in one place, and
files cannot be accidentally misplaced. On the other hand, separating
them ensures that each component of the guestbook is independent,
and can be modified without risking the integrity of the other components.
It is matter of personal preference.
$date_time = &get_date_time();
The get_date_time subroutine displays
the current date and time.
&MIME_header ("text/html", "Shishir Gundavaram's Guestbook");
The MIME_header
subroutine outputs a chosen MIME header, and
sets the title of the document to the user-specified argument. The
only reason for the subroutine is to make the program more compact.
print <<End_Of_Guestbook_Form;
This is a guestbook CGI script that allows people to leave some
information for others to see. Please enter all requested
information, <B>and</B> if you have a WWW server, enter the address
so a hypertext link can be created.
<P>
The current time is: $date_time
<HR>
First, an introductory message is displayed, along with the
current date and time. (You cannot call subroutines from within
print "blocks," so the get_date_time subroutine
to get the date and time was called earlier and placed in the date_time
variable.).
<FORM METHOD="POST">
<PRE>
<EM>Full Name</EM>: <INPUT TYPE="text" NAME="name" SIZE=40>
<EM>Email Address</EM>: <INPUT TYPE="text" NAME="from" SIZE=40>
<EM>WWW Server</EM>: <INPUT TYPE="text" NAME="www" SIZE=40>
</PRE>
<P>
<EM>Please enter the information that you'd like to add:</EM><BR>
<TEXTAREA ROWS=3 COLS=60 NAME="comments"></TEXTAREA><P>
<INPUT TYPE="submit" VALUE="Add to Guestbook">
<INPUT TYPE="reset" VALUE="Clear Information"><BR>
<P>
</FORM>
<HR>
End_Of_Guestbook_Form
As you can see, there is no ACTION
attribute to the <FORM>
tag. By omitting the ACTION attribute, the browser
defaults to sending the completed form to the current CGI program.
The METHOD is set to POST--as
we'll see later, this is how the guestbook program will know the
form has been completed.
The various elements that comprise
a form are output. The <PRE> tags align the text fields. Figure 7.1 shows how a completed form is rendered by Netscape Navigator.
If there was no query specified, the guestbook
data file is displayed for output.
} else {
if ( open(GUESTBOOK, "<" . $full_path) ) {
flock (GUESTBOOK, $exclusive_lock);
The full_path
variable contains the full path to the guestbook file. The main
reason for storing the relative path and full path separately is
that hypertext anchors need the relative path, while the full path
is needed to open the file. Before you open any file, it is always
a good idea to check that the file can be opened.
&MIME_header ("text/html", "Here is my guestbook!");
while (<GUESTBOOK>) {
print;
}
flock (GUESTBOOK, $unlock);
close(GUESTBOOK);
The loop iterates
through each line of the file and displays it to standard output.
Figure 7.2 shows the output.
} else {
&return_error (500, "Guestbook File Error",
"Cannot read from the guestbook file [$full_path].");
}
}
If there were
any problems opening the file, an error message is sent to the client.
The return_error subroutine is the same as
the one presented in Chapter 4, Forms and CGI.
Remember the "add" form, in which the <FORM>
tag used a METHOD of POST?
Here's where the form is processed. If the request method is POST,
it means that the user filled out the form, and submitted it back
to this program.
} elsif ($method eq "POST") {
if ( open (GUESTBOOK, ">>" . $full_path) ) {
flock (GUESTBOOK, $exclusive_lock);
$date_time = &get_date_time();
&parse_form_data (*FORM);
Now we add the
new entry to the guestbook. First, the program checks to see if
it can write to the guestbook file. If there are no errors, the
file is opened in append mode, and exclusively locked. The form
information is decoded and placed in the FORM
associative array. The parse_form_data subroutine
in this program is slightly different than the one we've previously
encountered in Chapter 4, Forms and CGI; it does not
check for GET requests, since the program only
uses it for POST.
$FORM{'name'} = "Anonymous User" if !$FORM{'name'};
$FORM{'from'} = $ENV{'REMOTE_HOST'} if !$FORM{'from'};
Above is a construct
you might not have seen before. It is a simpler way of saying:
if (!$FORM{'name'}) {
$FORM{'name'} = "Anonymous User";
}
if (!$FORM{'from'}) {
$FORM{'from'}=$ENV{'REMOTE_HOST'};
}
In other words, the form variables name
and from are checked for valid information.
If the fields are empty, default information is stored.
$FORM{'comments'} =~ s/\n/<BR>/g;
The information that the user entered in the <TEXTAREA>
field is stored in comments. Every newline character is replaced
by the HTML break tag. This ensures that the
information is displayed correctly. Note that if the user enters
HTML code (or SSI directives) as part of the
comments, the code will be interpreted. This could be dangerous.
See Chapter 9, Gateways, Databases, and Search/Index Utilities, for an intricate regular expression that
"escapes" HTML code.
print GUESTBOOK <<End_Of_Write;
<P>
<B>$date_time:</B><BR>
Message from <EM>$FORM{'name'}</EM> at <EM>$FORM{'from'}</EM>:
<P>
$FORM{'comments'}
End_Of_Write
The user name, host, and comments, along with the current
date and time, are written to the guestbook file.
if ($FORM{'www'}) {
print GUESTBOOK <<End_of_Web_Address;
<P>
$FORM{'name'} can also be reached at:
<A HREF="$FORM{'www'}">$FORM{'www'}</A>
End_of_Web_Address
}
print GUESTBOOK "<P><HR>";
If an HTTP
address was provided by the user, it is also displayed.
flock (GUESTBOOK, $unlock);
close(GUESTBOOK);
The file is unlocked
and closed. It is very important to unlock and close the guestbook
file to ensure that other people can access it.
Finally,
if all goes well, a thank-you message is displayed, as well as links
to view the guestbook.
&MIME_header ("text/html", "Thank You!");
print <<End_of_Thanks;
Thanks for visiting my guestbook. If you would like to see the guestbook,
click <A HREF="$guest_file">here</A> (actual guestbook HTML file),
or <A HREF="$script">here</A> (guestbook script without a query).
End_of_Thanks
If the program cannot write to the guestbook file, an error
message is generated. Another error is sent if an invalid request
method is used to access this CGI program.
} else {
&return_error (500, "Guestbook File Error",
"Cannot write to the guestbook file [$full_path].")
}
} else {
&return_error (500, "Server Error",
"Server uses unsupported method");
}
exit(0);
The MIME_header subroutine simply displays
a MIME header, as well as a title and heading
for the document. If the third argument is not specified, the heading
will be the same as the title.
sub MIME_header
{
local ($mime_type, $title_string, $header) = @_;
if (!$header) {
$header = $title_string;
}
print "Content-type: ", $mime_type, "\n\n";
print "<HTML>", "\n";
print "<HEAD><TITLE>", $title_string, "</TITLE></HEAD>", "\n";
print "<BODY>", "\n";
print "<H1>", $header, "</H1>";
print "<HR>";
}
The get_date_time
subroutine returns the current date and time.
sub get_date_time
{
local ($months, $weekdays, $ampm, $time_string);
$months = "January/February/March/April/May/June/July/" .
"August/September/October/November/December";
$weekdays = "Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday";
local ($sec, $min, $hour, $day, $nmonth, $year, $wday, $yday, $isdst)
= localtime(time);
The localtime function returns a nine-element
array, which consists of the time, the date, and the present time
zone. In previous examples, we were using only the first three elements
of this array; in this example, we're assigning all nine.
if ($hour > 12) {
$hour -= 12;
$ampm = "pm";
} else {
$ampm = "am";
}
if ($hour == 0) {
$hour = 12;
}
$year += 1900;
$week = (split("/", $weekdays))[$wday];
$month = (split("/", $months))[$nmonth];
The week
and the numerical month returned by the localtime
function are zero based. The week variable
is set to the alphanumeric weekday name by retrieving the string
corresponding to the numerical weekday from the variable weekdays.
The same process is repeated to determine the alphanumeric month
name.
$time_string = sprintf("%s, %s %s, %s - %02d:%02d:%02d %s",
$week, $month, $day, $year,
$hour, $min, $sec, $ampm);
return ($time_string);
}
Finally, the date returned by the get_date_time
subroutine is in the form of:
Friday, August 18, 1995 - 02:07:45 pm
The last subroutine in the guestbook application is parse_form_data.
sub parse_form_data
{
local (*FORM_DATA) = @_;
local ( $request_method, $post_info, @key_value_pairs,
$key_value, $key, $value);
read (STDIN, $post_info, $ENV{'CONTENT_LENGTH'});
@key_value_pairs = split (/&/, $post_info);
foreach $key_value (@key_value_pairs) {
($key, $value) = split (/=/, $key_value);
$value =~ tr/+/ /;
$value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
if (defined($FORM_DATA{$key})) {
$FORM_DATA{$key} = join ("\0", $FORM_DATA{$key}, $value);
} else {
$FORM_DATA{$key} = $value;
}
}
}
As mentioned earlier,
this subroutine does not check for GET requests.
There is no need to do so, because the loop in the main program
does the needed checking.