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);
}
=>
operator gives a quick way of putting data into a hash.
or
was used insted of ||
because otherwise parentheses would
be needed around $status = 500
. If err_name{$status}
is
undefined then it counts as false so the or
goes on to do the
second part.
exit(1)
is used to return 1 as an exit code.
test_error.cgi
that has the usual first several lines and then
require 'cgi_helper'; &Error_Stop(403,"Don't you dare!");or some other number and explanation. (Use a number different from 500 for the test.) Put this script in your
public_html
directory, executable by you.
test_error.cgi
on a UNIX command line.
http://www.pic.ucla.edu/~myname/test_error.cgi
If you get an error 500, that's the Apache web server telling you the test program is not working!
&CGI_Header()
and &CGI_Ender
since Error_Stop
puts out its own header and
ender information. The header is different from before since a
status line is included.
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:
@_
.
my
to declare the variables you'll need--a string
$query_string
to hold the incoming query string, a hash
%data
to hold the results before you return them, and others.
(Why is it OK to have %data
here and %form_data
in the calling
program?)
$query_string
equal to the combined data string, check it, and take it apart
to get the ``name=value'' pairs. In doing these things:
$ENV{"REQUEST_METHOD"}
is defined. If it isn't,
call&Error_Stop(500,"no request method")
or some such explanation.
$ENV{"REQUEST_METHOD"}
is
one of GET
and POST
. If not, call
&Error_Stop(500,"invalid query method")
or some such
explanation.
POST
, check that $ENV{"CONTENT_LENGTH"}
is defined. If it is not, then call
&Error_Stop(500,"No content length for POST")
or some such explanation.
($name,$value) = ...
, follow that with$data{$name} = $value;
$query_string
does not
contain any characters other than the \w
group and &*%=.+-
.
If it does, that means the user is trying to feed your CGI program
an illegal string. Maybe an error 403 would be appropriate.
For checking for invalid characters, use a regexp built using
[^\w&*%=.+-]
. Notice that the -
has to be at the end so
it doesn't get interpreted as a range, like [A-Z]
. What does
the ^
do?
return %data;
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:
A good substitute would be to modify your test program by setting
some environmental variables before calling Get_Form_Data
.
You could say $ENV{"REQUEST_METHOD"} = "GET";
and supply
a suitable encoded combined data string in a similar way.
But don't forget to remove these before running other kinds of
tests!
http://www.pic.ucla.edu/~myname/test_get.cgi?opinion=no+good&...
Again, this tests only the GET
method.
ACTION
attribute specifies
test_get.cgi
. This is the easiest way to test whether
your subroutine works with POST
as the METHOD
.
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:
join
function since we are given them one at a time; we don't have
the whole list at once.
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.