Contents:
Hidden Fields
CGI Side Includes
Netscape Persistent Cookies
One of the problems with the current HTTP protocol is its inability to maintain state. In other words, the protocol provides no way to access data from previous requests.
Imagine an ordering (or "shopping cart") system on the Web. You present the user with several forms listing the numerous products that can be ordered. The system keeps track of what the user ordered. Finally, it displays all of the user's selections. This type of system needs to somehow store the information-or "state"-so that it can be accessed at a later time.
For example, suppose you ask the user for his or her address in the first form. If you need this information in a later form, you don't want to ask all over again. Instead, you want to find a way for that address to be accessible to a later form, but transparent to the user. This is the most basic problem of using multiple forms-maintaining "state" from one form to another-and thus deserves special attention in this book.
There are several different strategies we'll explore for maintaining state. They include:
In Chapter 10, Gateways to Internet Information Servers, we also discuss a fourth approach, which is to develop a specialized "cookie server" to maintain information associated with a single user. In this chapter, however, we'll restrict ourselves to the more straightforward mechanisms.
As mentioned in Chapter 4, Forms and CGI, hidden fields allow you to store "hidden" information within a form. These fields are not displayed by the client. However, if the user selects the "View Source" option in the browser, the entire form is visible, including the hidden fields. Hidden fields are therefore not meant for security (since anyone can see them), but just for passing information to and from forms transparently.
Here is an example of two hidden fields that store author information within a form:
<FORM ACTION="/cgi-bin/test.pl" METHOD="POST"> . . <INPUT TYPE="hidden" NAME="author" VALUE="Larry Bird"> <INPUT TYPE="hidden" NAME="company" VALUE="Boston Celtics"> . . </FORM>
When the form is submitted, the information within the hidden fields is encoded, as the client passes all the fields to the server in the same exact manner. As far as the CGI program is concerned, there is no difference between hidden fields and regular, visible fields.
One thing to note is that certain browsers may not be able to handle hidden fields correctly.
A simple way to use hidden fields for maintaing state involves writing the information from a form as hidden field information into its successive form. Here is a simple first form:
<FORM ACTION="/cgi-bin/test.pl" METHOD="POST"> Name: <INPUT TYPE="text" NAME="01 Full Name" SIZE=40> <BR> EMail: <INPUT TYPE="text" NAME="02 EMail" SIZE=40> <BR> <INPUT TYPE="submit" VALUE="Submit the survey"> <INPUT TYPE="reset" VALUE="Clear all fields"> </FORM>
When this form is submitted, the program retrieves the information and creates a dynamic second form, based on the first form, like this:
<FORM ACTION="/cgi-bin/test.pl" METHOD="POST"> <INPUT TYPE="hidden" NAME="01 Full Name" VALUE="Shishir Gundavaram"> <INPUT TYPE="hidden" NAME="02 EMail" VALUE="shishir@acs.bu.edu"> What is your favorite WWW browser? <BR> Browser: <INPUT TYPE="text" NAME="03 Browser" SIZE=40> <BR> <INPUT TYPE="submit" VALUE="Submit the survey"> <INPUT TYPE="reset" VALUE="Clear all fields"> </FORM>
As you can see, the two fields, along with the user information, are inserted into the second form. The main advantage of such a process is that there is no need for magic cookies and temporary files. On the other hand, the disadvantage is that the form information is appended repeatedly to successive forms, creating large forms. This could result in possible performance problems.
Let's look at an example using this technique. Here is the first form:
<HTML> <HEAD><TITLE>Welcome to the CGI Shopping Cart</TITLE></HEAD> <BODY> <H1>CGI Shopping Cart</H1> Welcome! Thanks for stopping by the CGI Shopping Cart. Here is a list of some of our products. We hope you like them, and please visit again. <FORM ACTION="/cgi-bin/shopping.pl/catalog.html" METHOD="POST"> <HR> What is your full name: <BR> <INPUT TYPE="text" NAME="01 Full Name" SIZE=40> <P> What is your e-mail address: <BR> <INPUT TYPE="text" NAME="02 Email" SIZE=40> <P> <INPUT TYPE="submit" VALUE="Submit and Retrieve Catalog"> <INPUT TYPE="reset" VALUE="Clear all fields"> </FORM> </BODY></HTML>
The most important thing to note here is the extra path information passed to the program. This filename represents the next form to be displayed. The two fields in this form will be "hidden" in /catalog.html. Now, here is the second form:
<HTML> <HEAD><TITLE>Welcome to the CGI Shopping Cart</TITLE></HEAD> <BODY> <H1>CGI Shopping Cart</H1> Thanks for visiting out server. Here is a catalog of some of our books. Make your selections and press the submit buttons. Note: multiple selections are allowed. <HR> <FORM ACTION="/cgi-bin/shopping.pl" METHOD="POST"> <H2>Books on Networking</H2> <SELECT NAME="03 Networking Books" SIZE=3 MULTIPLE> <OPTION SELECTED>Managing Internet Information Services <OPTION>TCP/IP Network Administration <OPTION>Linux Network Administrator's Guide <OPTION>Managing UUCP and Usenet <OPTION>The USENET Handbook </SELECT> <HR> <H2>UNIX related Books</H2> <SELECT NAME="04 UNIX Books" SIZE=3 MULTIPLE> <OPTION SELECTED>Learning the UNIX Operating System <OPTION>Learning the Korn Shell <OPTION>UNIX Power Tools <OPTION>Learning Perl <OPTION>Programming Perl <OPTION>Learning the GNU Emacs </SELECT> <INPUT TYPE="submit" VALUE="Submit the selection"> <INPUT TYPE="reset" VALUE="Clear all fields"> </FORM> </BODY></HTML>
The ACTION attribute does not contain extra path information. This represents the last form in the "shopping cart." Also note the fact that there is a scrolled list that allows multiple selections. The program displays any form element that has multiple selection in a unique way.
The program begins as follows:
#!/usr/local/bin/perl $webmaster = "shishir\@bu\.edu"; $document_root = "/home/shishir/httpd_1.4.2/public"; $request_method = $ENV{'REQUEST_METHOD'}; $form_file = $ENV{'PATH_INFO'}; $full_path = $document_root . $form_file; $exclusive_lock = 2; $unlock = 8; if ($request_method eq "GET") { if ($form_file) { &display_file (); } else { &return_error (500, "CGI Shopping Cart Error", "An initial form must be specified."); }
If the program was requested with the GET protocol and extra path information, the display_file subroutine is called to output the form. The program should be accessed with the following URL:
http://your.machine/cgi-bin/shopping.pl/start.html
where /start.html represents the first form. If no path information is specified, an error message is returned.
} elsif ($request_method eq "POST") { &parse_form_data (*STATE); if ($form_file) { &parse_file (); } else { &thank_you (); }
If extra path information is passed to this program with the POST method, the parse_file subroutine is invoked. This subroutine inserts the information from the previous form(s) into the current form as hidden fields. Remember, the form information is stored in the STATE associative array. On the other hand, if no path information is specified, it is the end of the data collection process. The thank_yousubroutine displays the information from all the forms.
} else { &return_error (500, "Server Error", "Server uses unsupported method"); } exit (0);
The display_file subroutine simply outputs the first form to standard output.
sub display_file { open (FILE, "<" . $full_path) || &return_error (500, "CGI Shopping Cart Error", "Cannot read from the form file [$full_path]."); flock (FILE, $exclusive_lock); print "Content-type: text/html", "\n\n"; while (<FILE>) { print; } flock (FILE, $unlock); close (FILE); }
The parse_file subroutine inserts information from previous forms into the current form, as hidden fields.
sub parse_file { local ($key, $value); open (FILE, "<" . $full_path) || &return_error (500, "CGI Shopping Cart Error", "Cannot read from the form file [$full_path]."); flock (FILE, $exclusive_lock); print "Content-type: text/html", "\n\n"; while (<FILE>) { if (/<\s*form\s*.*>/i) { print; foreach $key (sort (keys %STATE)) { $value = $STATE{$key}; print <<End_of_Hidden; <INPUT TYPE="hidden" NAME="$key" VALUE="$value"> End_of_Hidden }
The file specified by PATH_INFO is opened. The while loop iterates through the file one line at a time. The regular expression checks for the <FORM> tag within the document. If it is found, the line containing the tag is displayed. Also, the foreach construct iterates through all of the key-value form pairs, and outputs a hidden field for each one.
} else { print; } }
If the <FORM> tag is not found, the line from the file is output verbatim.
flock (FILE, $unlock); close (FILE); }
The thank_you subroutine thanks the user and displays the data he or she selected.
sub thank_you { local ($key, $value, @all_values); print <<Thanks; Content-type: text/html <HTML> <HEAD><TITLE>Thank You!</TITLE></HEAD> <BODY> <H1>Thank You!</H1> Thank you again for using our service. Here are the items that you selected: <HR> <P> Thanks
This subroutine formats and displays the information stored in the STATE associative array, which represents the combined data from all the forms.
foreach $key (sort (keys %STATE)) { $value = $STATE{$key}; $key =~ s/^\d+\s//; if ($value =~ /\0/) { print "<B>", $key, "</B>", "<BR>", "\n"; $value =~ s/\0/<BR>\n/g; print $value, "<BR>", "\n";
If a particular value contains a null string, it is replaced with " <BR>" followed by a newline character. As a result, the multiple values are displayed properly.
} else { print $key, ": ", $value, "<BR>", "\n"; } } print "<HR>", "\n"; print "</BODY></HTML>", "\n"; }
The parse_form_data subroutine is similar to the one used in the "survey" program above, except it does not handle any query information.
sub parse_form_data { local (*FORM_DATA) = @_; local ($query_string, @key_value_pairs, $key_value, $key, $value); read (STDIN, $query_string, $ENV{'CONTENT_LENGTH'}); @key_value_pairs = split (/&/, $query_string); foreach $key_value (@key_value_pairs) { ($key, $value) = split (/=/, $key_value); $key =~ tr/+/ /; $value =~ tr/+/ /; $key =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; $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; } } }