[Chapter 7] Advanced Form Applications
Author: Shishir Gundavaram
Date: Tue Aug 26 16:37:56 1997

#

| Chapter 7 |

# 7. Advanced Form Applications

Contents:
Guestbook
Survey/Poll and Pie Graphs
Quiz/Test Form Application
Security

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:

- Guestbook: A form interface for users to leave comments on a particular Web page for other people to see. The concepts behind the guestbook are very simple: Present a form to the user to fill out, process the form information, and store it in a file.

- Poll or a Survey: A CGI program that allows you to solicit opinions from users and present them with a dynamically created pie graph illustrating the up-to-date results. This application involves displaying a form and manipulating and storing the form data into a format that we can read easily and quickly at a later time. When the user elects to see the current results, we simply read in all of the data and graph it.

- Quiz/Test: A unique interface that shows you how to "extend" HTML by adding new tags! This CGI application reads the specified data file consisting of tags to create quizzes (as well as regular HTML), formats it to HTML, and sends it to the browser. It will also correct the quiz once the user completes it.

## 7.1 Guestbook

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 http://your.machine/cgi-bin/guestbook.pl?add, using the `GET` method, will present a form for visitors to enter information.

- A URL of http://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 http://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.

#### Figure 7.1: Guestbook form

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.

#### Figure 7.2: Guestbook 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.

Animation |  | Survey/Poll and Pie Graphs