next up previous
Next: About this document Up: No Title Previous: No Title

Lab Assignment #6

The due date is 11:30 a.m. on Friday, February 26.

1. An error subroutine

This subroutine, called Error_Stop, should be added to your existing file cgi_helper (at the end of the file but before the line 1;). It is to be used by CGI scripts in place of die ..., since the message in die ... doesn't get back to the browser. See the notes below.

sub Error_Stop 
#    Example:  &Error_Stop(401,"Your password is invalid.")
{
    my ($status,$explanation) = @_;
    %err_name = (400=>"Bad Request", 401=>"Unauthorized",
        403=>"Forbidden", 404=>"Not Found",
        500=>"Internal Error", 501=>"Not Implemented");
    $err_name{$status}  or  $status = 500;
    print "Content-type: text/html\n";
    print "Status: ", $status, " ", $err_name{$status}, "\n\n";
    print <<"---";
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4/0 Final//EN">
<HTML>
<HEAD> <TITLE> CGI error </TITLE> </HEAD>
<BODY>
<H1> $err_name{$status} </H1>
<H2> $explanation </H2>
</BODY>
</HTML>
---
    exit(1);
}

2. A subroutine to read form data--limited version

This subroutine, called Get_Form_Data, should also be added to your existing file cgi_helper , at the end. This version is ``limited'' because it won't handle fields with multiple values. Don't submit this version, since you'll submit a better version in Part 3.

This subroutine has no parameters (no arguments), but it does return a value, containing all the form data. This value should be a hash with the field names as keys. Why? Here's the reason for using a hash: The program using this subroutine will be your CGI program that goes along with the HTML form. The CGI program needs to get the values of specific fields, such as ``email'' and ``opinion''. A hash is exactly the right thing for associating values with strings such as these field names. Your CGI script can use the subroutine this way:

require 'cgi_helper'; include the helper file
...
%form_data = &Get_Form_Data; get all the data in one hash
...
and now the program can access values such as $form_data{"email"}
and $form_data{"opinion"} for any purpose.

The basic outline for the limited version of the subroutine is this:

To test Get_Form_Data, make a Perl script test_get.cgi that repeats back the data. The following will do, after the usual first several script lines:

require 'cgi_helper';
&CGI_Header("CGI form data test");
my %form_data = &Get_Form_Data;
foreach $name (sort keys %form_data)
{
    print "$name = $form_data{$name}<BR>\n";
}
&CGI_Ender;

Why is the <BR> needed?

You can run the test program several ways:

This version does not need to be submitted. Do make a copy of this version of cgi_helper under a different name for safekeeping, in case you mess up Problem 3 and need to start over.

3. A better subroutine to read form data

Make this subroutine by editing the previous one. Then submit the whole file cgi_helper .

As mentioned, the limited version won't work on menus and scrolled lists with multiple values allowed. The trouble is that if a user chooses more than one option field, then the query string will have several ``name=value'' pairs with the same name, for example,
computer=Macintosh
computer=UNIX+system (before decoding)

Then in the loop you have written, the command $data{$name} = $value (after decoding) results in first setting $data{"computer"} to ``Macintosh'' and then a moment later resetting it to ``UNIX system''. The Macintosh answer is lost!

How can you fit several values into the one string $data{"computer} ? A good way is to join them together. What join separator symbol should you use? You need one that cannot appear in any ordinary string. There are at least three candidates: (1) a tab character \t, since in Netscape typing a tab moves you to the next field instead of being put in the field value, (2) a \0 character, which is ASCII ``NUL'', and (3) $;, which is a nonprinting character that Perl provides for just this purpose. Probably (3) is best, but don't confuse the semicolon with the end of a Perl command.

Now in Get_Form_Data you need to change the part where it says
$data{$name} = $value; . Use if...else... with the defined() function to do one thing or another depending on whether $data{$name} is already defined or not. If it is (i.e., the field name has been seen before), then you need to stick on the new value using the separator:

$data{$name} = $data{$name} . $; . $value; (since dot puts strings together), or more neatly,

$data{$name} .= $; . $value;

If it isn't defined then use $data{$name} = $value as before.

Notes:

Now to test this revised subroutine, you need to modify the previous test program, which assumed that each string has only one value. Where you get the data and print it, instead do this:

my @values = split $;, $form_data{$name};
print "$name = ", join("|",@values), "\n";

This will show the different values separated by | . If there is only one value then there won't be any | and the output will look the same as before.

Now try the previous test methods.

When you use Get_Form_Data in future CGI scripts, there is one more issue to think about: Checkboxes return no value if they are not checked. In the earlier sample form, for example, there was a checkbox named ``student''. If it is checked you will get student=on and if is not checked you get nothing. In that case, after using

%form_data = &Get_Form_Data;

the value $form_data{"student"} will be undefined. But that's easy to deal with; in your CGI program you can use something like

$student_status = $form_data{"student"} ? "yes" : "no";

The reason this works is that an undefined string counts as false, while if the string is defined it's value is ``on'', which counts as true (since it's not empty or 0).

4. A CGI form with response

Modify your own HTML form from the previous assignment by changing its ACTION from thanks.cgi to response.cgi . Then write a CGI script response.cgi that checks the form and, if it's OK, repeats back the information to the user in a nice format. Take into account checkboxes that may not be checked, possible multiple answers, etc. For non-critical fields that are empty, instead of the empty string repeat back ``(no response)''.

In checking the form, make sure specific ``critical fields'' are not empty, for example a name field or whatever else seems important to be filled in.

If a critical field is empty, then your program should output a ``virtual form'' that is the same as your original HTML form except for two changes:

First, it should include a message saying which critical fields were not completed.

Second, it should show any data that was filled in, by inserting default values, as appropriate. Different kinds of field tags may work differently for default values.

For the virtual form, you'll probably want to use ``Here documents'' with double-quoted end line and then put strings in it such as $opinion . Fields with no response should show as empty.

The idea is that the user can make a second attempt at the form. If it is still wrong, a third will be needed, etc.

Submit both your form and CGI program.


next up previous
Next: About this document Up: No Title Previous: No Title

Kirby A. Baker
Fri Feb 19 15:30:50 PST 1999