Previous Section | Table of Contents | Next Section

Running a form-to-email gateway

Now that you're able to run real, honest-to-goodness CGI scripts, let's make something useful: a Web-form-to-email gateway. This is probably one of the most common uses of CGI scripts, since it satisfies a very common need: You have an HTML form that you want users to fill out, and you want the contents of that form sent to you as an email message.

This section of the tutorial covers:

Checking for CGI.pm

We're going to make life easy for ourselves by writing this script using something called CGI.pm. CGI.pm is a "Perl module," which is basically a chunk of prewritten Perl code that you can pull into your script to do lots of useful magic. CGI.pm, as you might guess, is a module specifically designed to do CGI sorts of things. It was created by a really smart guy named Lincoln Stein, and it's really, really popular with people who write CGI scripts.

First, let's check to see if CGI.pm is already installed on your ISP's machine. Open up hello.pl (your "Hello, world!" script from earlier) and add a line so it looks like this:

#!/usr/local/bin/perl

# hello.pl - my first perl script!

use CGI;

print "Hello, world!\n";

That line that says "use CGI;" (without a trailing ".pm") is what pulls in the CGI.pm module for you. Save the script (which means saving it and then FTPing it to your Unix machine if you're writing it on your local PC), then run it from the command line. If you see:

catlow:/u1/j/jbc> ./hello.pl
Hello, world!

then CGI.pm is already installed, and you're a happy camper. If, however, you see something like:

catlow:/u1/j/jbc> ./hello.pl
Can't locate CGI.pm in @INC at hello.pl line 5.
BEGIN failed--compilation aborted at hello.pl line 5.

then CGI.pm is not installed as part of your copy of perl, and you need to get it installed.

One approach to this is to ask your ISP to do it, and if they won't, get another ISP. (Isn't it great how glibly I keep offering that advice?) Alternately, you can download and install CGI.pm somewhere under your own user directory or in your own Web space, and then add the following code to all your scripts:

use lib '/home/your/private/dir';
use CGI;

with '/home/your/private/dir' being replaced with the full pathname of the directory where you installed CGI.pm.

You can find out how to do this by reading the installation section of Lincoln's excellent CGI.pm documentation at:

http://stein.cshl.org/WWW/software/CGI/cgi_docs.html

Return to the top of the page

Creating the HTML form

Among the things Lincoln explains in that documentation is how you can use CGI.pm not only to process the output of HTML forms, but to actually produce those forms in the first place. With this approach, you just send a user to your CGI script, and the first time the user invokes the script it delivers an HTML form with all the values set to their defaults. Then, when the form is submitted back to the same script, it takes the data supplied by the user and does whatever you want done with it.

CGI.pm has lots of nifty features like that, but they tend to be a bit overwhelming for beginners, so in this case we're going to take a more straightforward approach, and simply create our form as a standard HTML page, then submit it to our CGI script for processing.

So, here's an HTML form you can use for this demonstration. I'm not going to bother explaining what's going on with the table tags and form elements in this; good tutorials on those are available elsewhere. Copy this form, or make your own with whatever fields you want, and stick it on the Web somewhere.

<HTML>

<HEAD>
<TITLE>Sample Form</TITLE>
</HEAD>

<BODY>

<H1>Sample Form</H1>

<P>Please fill out this form and submit it. Thank you.</P>

<FORM ACTION="mail_form.cgi" METHOD="POST">

<TABLE>

<TR>
<TD ALIGN="right"><STRONG>My name:</STRONG></TD>
<TD><INPUT NAME="01_name" SIZE=30></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>Address:</STRONG></TD>
<TD><INPUT NAME="02_address" SIZE=30></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>City:</STRONG></TD>
<TD><INPUT NAME="03_city" SIZE=30></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>State:</STRONG></TD>
<TD><INPUT NAME="04_state" SIZE=2></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>Zip:</STRONG></TD>
<TD><INPUT NAME="05_zip" SIZE=10></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>Country:</STRONG></TD>
<TD><INPUT NAME="06_country" SIZE=10 VALUE="USA"></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>My email:</STRONG></TD>
<TD><INPUT NAME="07_email" SIZE=30></TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>My favorite color:
</STRONG></TD>
<TD>
<TABLE BGCOLOR="#CCCCFF" BORDER><TR><TD>
<TABLE>
<TR>
<TD><INPUT NAME="08_color" TYPE="radio" VALUE="red">
</TD>
<TD>Red</TD>
</TR>
<TR>
<TD><INPUT NAME="08_color" TYPE="radio" VALUE="green">
</TD>
<TD>Green</TD>
</TR>
<TR>
<TD><INPUT NAME="08_color" TYPE="radio" VALUE="blue">
</TD>
<TD>Blue</TD>
</TR>
</TABLE>
</TD></TR></TABLE>
</TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>Movies I liked:</STRONG>
</TD>
<TD>
<TABLE BGCOLOR="#CCCCFF" BORDER><TR><TD>
<TABLE>
<TR>
<TD><INPUT NAME="09_movies" TYPE="checkbox" 
VALUE="Blade Runner"></TD>
<TD><EM>Blade Runner</EM></TD>
</TR>
<TR>
<TD><INPUT NAME="09_movies" TYPE="checkbox" 
VALUE="Pulp Fiction"></TD>
<TD><EM>Pulp Fiction</EM></TD>
</TR>
<TR>
<TD><INPUT NAME="09_movies" TYPE="checkbox" 
VALUE="Full Metal Jacket"></TD>
<TD><EM>Full Metal Jacket</EM></TD>
</TR>
</TABLE>
</TD></TR></TABLE>
</TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>When I grow up I want 
to be a(n):</STRONG></TD>
<TD>
<SELECT NAME="10_grow_up">
<OPTION>Astronaut
<OPTION>Fireman
<OPTION>CGI programmer
</SELECT>
</TD>
</TR>

<TR>
<TD ALIGN="right"><STRONG>My opinion on cucumber 
sandwiches is:</STRONG></TD>
<TD>
<TEXTAREA NAME="11_sandwiches" ROWS=5 COLS=20 WRAP="virtual">
</TEXTAREA>
</TD>
</TR>

<TR><TD COLSPAN=2> </TD></TR>
<TR>
<TD> </TD>
<TD><INPUT TYPE="submit"> <INPUT TYPE="reset">
</TD>
</TR>
</TABLE>

</FORM>
</BODY>
</HTML>

Return to the top of the page

The <FORM> tag's ACTION attribute

When an HTML form is submitted, the contents of all the form's fields are bundled up and handed off to whatever script is specified in the ACTION attribute of the <FORM> tag. In the example page above, the form is handed off to a script called "mail_form.cgi" located in the same directory as the form itself.

If we wanted to, we could have put some path information into the ACTION attribute and handed the form off to a script in a different directory. We could even have given a full URL ("http://www.somewhere.com/somepath/somescript.cgi") and handed the form's contents off to a script on a completely different server. That's what allows people to make things like the SearchPlex, which is just a collection of forms copied from different servers.

But I'm digressing. The point is, the ACTION attribute of the <FORM> tag is what determines where the form data goes when the form is submitted.

Return to the top of the page

The mail_form.cgi script

Here's a simple script that will take the output of a Web form, bundle it up into an email message, and mail it off to someone. Go ahead and create this script, and stick it in a suitable location on your Web server. If you can execute CGI scripts anywhere, you can stick it in the same directory as your HTML form. If you need to put your scripts in a special location, stick it there, and then be sure to modify the ACTION attribute of the <FORM> tag to point to it properly. For example, if you needed to put the script in a top-level directory on your server called cgi-bin, you would edit the FORM tag to read:

<FORM ACTION="/cgi-bin/mail_form.cgi" TYPE="POST">

Anyway, here's the script:

#!/usr/local/bin/perl -w

# mail_form.cgi

# bundle up form output and mail it to the specified address

# This program is copyright 1999 by John Callender.

# This program is free and open software. You may use, copy, modify,
# distribute and sell this program (and any modified variants) in any way
# you wish, provided you do not restrict others from doing the same.

# configuration:

$sendmail = '/usr/sbin/sendmail'; # where is sendmail?
$recipient = 'forms@lies.com'; # who gets the form data?
$sender = 'Anonymous User <forms@lies.com>'; # default sender?
$site_name = 'my site'; # name of site to return to afterwards
$site_url = '/begperl/'; # URL of site to return to afterwards

# script proper begins...

use CGI;
$query = new CGI;

# bundle up form submissions into a mail_body

foreach $field (sort ($query->param)) {
    foreach $value ($query->param($field)) {
        $mail_body .= "$field: $value\n";
    }
}

# set an appropriate From: address

if (($email = $query->param('07_email')) and
    ($query->param('07_email') =~ /@/)) {

    # the user supplied something that looks like
    # an email address

    if ($name = $query->param('01_name')) {
        # the user supplied a name
        $name =~ s/"//g; # lose any double-quotes in name
        $sender = "\"$name\" <$email>";
    } else {
        # user did not supply a name
        $sender = "$email";
    }
}

# send the email message

open(MAIL, "|$sendmail -oi -t") or die "Can't open pipe to $sendmail: $!\n";
print MAIL "To: $recipient\n";
print MAIL "From: $sender\n"; 
print MAIL "Subject: Sample Web Form Submission\n\n";
print MAIL "$mail_body";
close(MAIL) or die "Can't close pipe to $sendmail: $!\n";

# now show the thank-you screen

print "Content-type: text/html\n\n";
print <<"EOF";
<HTML>
<HEAD>
<TITLE>Thank you</TITLE>
</HEAD>

<BODY>

<H1>Thank you</H1>

<P>Thank you for your form submission. You will be hearing 
from me shortly.</P>

<P>Return to 
<A HREF="$site_url">$site_name</A>.</P>

</BODY>
</HTML>
EOF

Return to the top of the page

Warnings via perl's -w switch

Let's go through the script section by section. First comes the usual top-of-the-script stuff, with one change: the shebang line now has a trailing "-w":

#!/usr/local/bin/perl -w

What that -w does is to turn on Perl's "warnings" feature, which causes the script to die with a useful error message if certain unexpected things appear to be going on in the script. We haven't bothered with it before this, since our other scripts have been so short and simple, but this one is complex enough that it's probably worth turning it on.

With that said, you should realize that the -w feature is mainly a tool to help you while you're writing the script. Once the script is written and working properly, there shouldn't be any warnings.

Note: The next thing in the script is a copyright notice and statement about the terms under which you may use it. I didn't bother with that at first, but people kept asking "Do you mind if I use your script to do X?" so I put this part in there. If you have questions about that, send me an email at jbc@west.net.

Return to the top of the page

Scalar variables

Next comes the configuration section:

# configuration:

$sendmail = '/usr/sbin/sendmail'; # where is sendmail?
$recipient = 'forms@lies.com'; # who gets the form data?
$sender = '"Anonymous User" <forms@lies.com>'; # default sender?
$site_name = 'my site'; # name of site to return to afterwards
$site_url = '/begperl/'; # URL of site to return to afterwards

Each of these lines stores some text in a Perl variable. Later on in the script, we will use these variables to plug the text they contain into various places. We do the initilization of the variables up at the top of the script, though, because that makes it easy to modify the script later on if something needs to be changed.

In Perl, variables whose names begin with a dollar sign ($) are used to store a single something: a single number or a single chunk of text (which programmers call a "string"). Programmers call these single-something containers "scalar" variables. The equal sign ( = ) in Perl is the "assignment operator," because it lets you assign something - either a quote-delimited string of text or a number - to a variable. That's what we're doing with the five lines in this script's configuration section.

Return to the top of the page

Configuration values

The first line in the configuration section is where you give the location on your system where the Unix sendmail program can be found. The script will use this information later on to send the message containing the form data. Use the "which" command at the Unix command line to find out where on your system sendmail is located:

catlow:/u1/j/jbc> which sendmail
/usr/sbin/sendmail

and put the full path inside the single quotes on the right side of the equal sign to assign it to the $sendmail variable:

$sendmail = '/usr/sbin/sendmail';

Calling the variable "$sendmail" doesn't do anything special within Perl, by the way. Perl doesn't know how I'm planning to use this variable, and just treats it as a storage location. I could have called it $mail_program_location or $walnuts or anything else I wanted (as long as the variable began with a $ followed by a letter or an underscore character), but $sendmail was concise and easy to remember, which will make life simpler later on.

If you don't have sendmail on your system (everybody now): Get another ISP.

The next configuration line lets me set the email address I want the form contents mailed to. Just stick your email address inside the single quotes on the righthand side of the assignment.

Some form-to-email-gateway scripts that I've seen, by the way, let you set this address in the form itself, where it is stored as a hidden form field. The thinking with this is that it will be easier for non-programmers to adapt the script for their own use, since they won't have to monkey with the script itself in order to send the form output to their particular email address. But this turns out to be a really bad idea from a security standpoint, because anyone on the Internet can then hijack the script by feeding in a form with a bogus email address stored in it. This is how unsuspecting Webmasters find themselves getting visits from the Secret Service after some practical joker uses their script to send threatening email to the President's cat.

The next configuration line lets us set a default value for the email's From: header. Assuming that the user filling out our form supplies an email address, we'll replace the variable's contents with that address later on, but we need something to stick in there for now in case the user doesn't supply one. It should probably be your own email address, so the error message will bounce back to you if the mail can't be delivered for some reason.

Finally, we have two variables ($site_name and $site_url) that are used to construct a link for the user to click on in the "Thank You" page we display after the form submission has been processed.

Return to the top of the page

foreach loops

The next part of the script introduces one of those things that seem really obvious to programmers, but which can cause non-programmers some confusion until they figure out what's going on. I'm talking about loops. In this case, a loop created with Perl's foreach function.

The idea with a loop is to make a block of programming code that performs a specific set of actions, then feed it a series of thingies for processing. This ends up being really efficient for the programmer, since he or she only has to write the code block once, but can make the program run thousands or millions of thingies through it. The illusion of power this produces is part of why people become addicted to programming and stay up all night debugging code.

Anyway, take a look at the following and see if you can figure out what's going on:

# bundle up form submissions into a mail_body

foreach $field (sort ($query->param)) {
    foreach $value ($query->param($field)) {
        $mail_body .= "$field: $value\n";
    }
}

A few hints of explanation:

The ($query->param) in the first line is a bit of CGI.pm magic that produces a list of the names of all the form fields submitted to the script.

Putting "sort" in front of it causes the list to be sorted in alphabetical order. Since I did that trick of naming my form fields like "01_something", "02_something_else", sorting them alphabetically has the effect of arranging them in the same order they were in in the form.

Putting "foreach $field" in front of all that means I'm going to step through the block of code that follows for each of those sorted field names, with the name being stored in the $field variable each time through the block.

The opening curly brace ( { ) at the end of that line matches up with the closing curly brace ( } ) four lines later to define the block of code that's going to be executed each time through the loop. Making all your curly braces match up correctly is the single hardest thing about programming, but programmers won't tell you that because they want you to think that what they do is much more dark and mysterious.

To make things more interesting, there's another foreach loop nested inside the first one. I'll bet you already figured out what it's doing: It takes each of the field names and uses another bit of CGI.pm magic ($query->param($field)) to produce a list of values corresponding to that field name.

I did it this way, by the way, instead of assuming that each field name would only have a single corresponding value, because of checkbox groups. If there is a checkbox group in the form (as there is in my sample), one field name can have several different values associated with it. Using this foreach function, I can process each of the values individually.

Finally, at the heart of all this foreach'ing, we have the one line of Perl that does the actual work:

        $mail_body .= "$field: $value\n";

At first it looks like we're just assigning the current field name and value (followed by a newline: \n) to a variable called $mail_body, and that's true; that's what we're doing, but with a difference: Instead of using the normal assignment operator ( = ), we're using the concatenation assignment operator ( .= ), which means that we're appending the stuff inside the quotes to the end of whatever is already in the $mail_body variable. If we used the = operator, we would actually be replacing whatever was in the $mail_body variable each time we assigned to it, and when we got done looping through all our foreach's all we would have in there would be the last "$field: $value\n" pair. With the .= operator, we're building up a $mail_body variable that contains all of our submitted form data, with each "field: value" pair on a separate line.

Return to the top of the page

if statements

The next section of the script introduces another common programming construct: the if statement. An if statement is similar to the foreach loop we just looked at, in that it allows you to do something special with a block of code. What an if statement allows you to do is to make execution of the code conditional upon the outcome of a test.

if statements in Perl look something like this:

if (test) {
    do something...
}

Other, fancier versions of Perl's if statement look like this:

if (test) {
    do something...
} else {
    do something else...
}

if (test) {
    do something...
} elsif (another test) {
    do something else...
} elsif (yet another test) {
    do some other something else...
} else {
    do still some other something else...
}

and so on. The trickiest part for beginners (besides getting all those curly braces in the right place) is understanding how Perl evaluates those (test) statements for "truth."

The most basic sort of truth test Perl does is to look at the expression inside the parentheses, and if it evaluates to nothing (""), zero (0), or the special value called "undef" (meaning the variable has not been defined), then it's "false." Anything else is "true." Here are some examples:

if (1) { 
    print "true!";
} else {
    print "false!";
}

The above chunk of Perl will print "true!", because the test evaluates to the number 1, a true value.

if (0) { 
    print "true!";
} else {
    print "false!";
}

The above chunk of Perl will print "false!", because the test evaluates to the number 0, a false value.

$walnuts = 1;
if ($walnuts) {
    print "true!";
} else {
    print "false!";
}

The above chunk of Perl will print "true!", because Perl evaluates the test ($walnuts) by replacing the variable with whatever has been assigned to it (the number 1, in this case).

$walnuts = 0;
if ($walnuts) {
    print "true!";
} else {
    print "false!";
}

Likewise, the above chunk of Perl will print "false!", because $walnuts evaluates to the number zero (0), which is false.

$walnuts = 1;
if ($rutabegas) {
    print "true!";
} else {
    print "false!";
}

The above chunk of Perl will print "false!", because $rutabegas has not been defined yet, so it evaluates to "undef", which is false.

$rutabegas = 0;
$walnuts = 1;
if ($rutabegas = $walnuts) {
    print "true!";
} else {
    print "false!";
}

Did I trick you? The above chunk will print "true!", because when an assignment takes place inside the (test) parentheses, Perl evaluates to true or false based on the value of whatever it was that got assigned. In this case, the number 1, which had been previously assigned to $walnuts, was assigned to $rutabegas, and therefore evaluated to "true."

The trick here was that you might have thought that the line

if ($rutabegas = $walnuts) {

means the same thing that it does when you read it in English; i.e. "if the contents of the $rutabegas variable is equal to the contents of the $walnuts variable, then..." Remember, the = operator in Perl is the assignment operator; it just takes whatever is on the right side of it and sticks it into whatever is on the left side of it.

You can write a logical test in Perl to see if two things are numerically equal to each other, but you need to use a special logical operator: "==". That's right: two equal signs next to each other. This tests for "numeric equality." You would use it like this:

$rutabegas = 0;
$walnuts = 1;
if ($rutabegas == $walnuts) {
    print "true!";
} else {
    print "false!";
}

which, this time, would print "false!", because the value in $rutabegas is not numerically equal to the value in $walnuts.

There are lots of other logical operators you can use to do lots of other nifty sorts of tests, but we don't need them for the current script, so I'm going to give your tiny brains a rest and not explain them in detail. Isn't that nice of me? :-) If you're curious, though, here's a list of the more commonly used ones:

==!=numeric equality, inequality
eqnestring equality, inequality
<>numeric less than, greater than
ltgtstring less than, greater than
<=>=numeric less than or equal to, greater than or equal to
legestring less than or equal to, greater than or equal to
and, or, notmean more or less what you think they do
()can be nested inside the test parentheses to group logical elements. For example: if (($john eq 'smart') or (($john eq 'rich') and ($fred eq 'smart'))) { do something }.

Anyway, let's return to our form-to-email script:

# set an appropriate From: address

if ($email = $query->param('07_email')) {
    # the user supplied an email address
    if ($name = $query->param('01_name')) {
        # the user supplied a name
        $name =~ s/"//g; # lose any double-quotes in name
        $sender = "\"$name\" <$email>";
    } else {
        # user did not supply a name
        $sender = "$email";
    }
}

So, we're using an if statement to test whether or not the user supplied an email address in the form (via the CGI.pm magic of $query->param('07_email')), and if he or she did, we use an if-else statement nested inside it to test whether he or she supplied a name. The upshot of all this if-ing and else-ing is that if the user did supply an email address (and optionally a name to go with it), we replace the default value previously assigned to the $sender variable with whatever it was that the user supplied.

There was one other bit of cleverness in there: the following line:

        $name =~ s/"//g; # lose any double-quotes in name

As the comment says, this causes any double-quote characters ( " ) in the $name variable to be deleted, which we need to do because we're going to delimit the name with double-quotes, and internal double-quotes will mess this up. We need to do that delimiting, by the way, because if we don't, someone with a name like "Thurston Howell, III" will cause problems when sendmail tries to split the name into two separate addresses at the comma. It only took me a year of seeing random errors in my form-to-email messages to realize that...

Anyway, the =~ operator in the line above is a way to connect a variable to a substitution operation. The substitution operation ( s/"//g ) in turn is based on Perl's "regular expressions," which I'm not going to talk about now, since they figure prominently in a subsequent part of the tutorial.

Return to the top of the page

File handles and piped output

Let's look at the next section of the script:

# send the email message

open(MAIL, "|$sendmail -oi -t") or die "Can't open pipe to $sendmail: $!\n";
print MAIL "To: $recipient\n";
print MAIL "From: $sender\n"; 
print MAIL "Subject: Sample Web Form Submission\n\n";
print MAIL "$mail_body";
close(MAIL) or die "Can't close pipe to $sendmail: $!\n";

Previously, when we wanted to have our program spit something out, we just used a "print" command, and whatever we printed went to our screen (if we were running the script from the Unix command line) or to our Web browser (if we were running the script as a CGI script, and had output a suitable Content-type header beforehand). In each case we were printing to what Unix users call "standard output" or "STDOUT," which is the default destination that the print command prints to.

We don't have to print to STDOUT, however. We can also send our output somewhere else using something called a "filehandle." Doing so is a three-step process:

  1. Open the filehandle.
  2. Print to the filehandle.
  3. Close the filehandle.

The most common use for printing to a filehandle is to let your script store its output in a file. What we're doing here involves a less-common use: using a filehandle to send the script's output to another program, so that the other program can do something for us. In this case, we're sending our output to the Unix sendmail program, so sendmail can, well, send some mail.

Programmers call this "piping" our output to another program, and the symbol that represents it in the command for opening the filehandle is the vertical bar (that is, the character you get when you type a shifted backslash, up above your keyboard's "Enter" key), which sort of looks like a pipe ( | ). Here's the command in which we "open a pipe" to sendmail:

open(MAIL, "|$sendmail -oi -t") or die "Can't open pipe to $sendmail: $!\n";

What we have here is Perl's open function, then, inside the parentheses, the name of the filehandle (by convention, filehandle names should be ALL CAPS), followed by a comma and a quote-delimited string containing the pipe symbol, and then the name of the command we want to pipe our output to. In this case, we're piping to the path and command name we previously stored in the $sendmail variable, along with some "-oi -t" command line switches that we need to supply to sendmail for lucky special reasons you don't especially need to understand at the moment.

If all this is making no sense whatsoever don't worry about it; just make sure you copy the line exactly and everything should work okay.

Return to the top of the page

die statements

Oh, and that "or die..." thing. It's a really, really good idea to stick an "or die..." statement like this in your Perl script every time the script is trying to open a filehandle or execute an external command of some sort. The reason for that is, these sorts of actions represent weak links in your script, where something can easily go wrong even though the script itself is functioning properly. Let's say you get your script working and everything's going great, but then someone comes along and moves the sendmail program on your Unix server, such that Perl can't find it when it tries to open this pipe. Without an "or die..." statement, Perl will try to open the pipe, fail, and continue on as if nothing had happened. You, and the users of your CGI scripts, will never know there's something wrong -- except that you won't be getting any emails.

With the "or die..." statement there, however, the script will try to open the pipe, and when it fails the script will jump over and execute whatever comes after the "or", which in this case means the "die" statement. The "die" statement makes the script stop dead in its tracks and print an error message to a special place called "standard error" (or "STDERR"), which is your screen in the case of scripts you run from the command line, or the Web server's error log in the case of a CGI script. The error message that gets printed is whatever comes after the "die" statement, so by making it a meaningful message like "Can't open pipe to sendmail" you make life a lot easier for whomever has to track down and fix the problem. The "$!" we included in the error message, by the way, is a special Perl variable that contains the text of whatever specific error message was returned by your system when Perl tried to do whatever it was that failed.

A CGI script that "dies" in this fashion will return an error message to the user at the other end (a 500 error, probably, or maybe a "document contains no data" error), which you might think is a bad thing, but which realistically is better than just letting them think everything was processed normally. You can't count on the user to let you know about the problem, though, even if your 500 error message displays an email address and a request that they contact you; my experience has been that at least 90-95% of Web users will just leave if they encounter an error like this. So it's really up to you to scan through your server's error log from time to time to make sure nothing weird is showing up in there.

Return to the top of the page

Odds and ends

So, anyway, we open the pipe to sendmail, then print to that filehandle by using a print command followed by the name of the filehandle we want to print to (MAIL). In this case, we print out some header fields that sendmail expects to see, finishing up with the message body that we've previously stored in the $mail_body variable. Finally, we close the filehandle.

I threw an "or die..." statement on the close statement, by the way, more because it's a good habit to get into than because this script is ever likely to fail at that point. I never used to bother doing the "or die..." thing on close statements, until the day my ISP's disk filled up, and a script I had written to automatically re-write a big chunk of my Web site went happily about its business, opening filehandles to all the site's files and printing updated content to them, then failing (silently) when it tried to close the filehandle and got a "disk full" error. The end result: A site full of empty (that is to say, deleted) files and a new habit of mine to always check the success or failure of my close statements.

The last part of the script just delivers a "Thank You" HTML page that will display to the user after their form has been processed. The only thing tricky here is that we use the $site_url and $site_name variables defined at the beginning of the script to give users a link they can use to leave the Thank You page.

# now show the thank-you screen

print "Content-type: text/html\n\n";
print <<"EOF";
<HTML>
<HEAD>
<TITLE>Thank you</TITLE>
</HEAD>

<BODY>

<H1>Thank you</H1>

<P>Thank you for your form submission. 
You will be hearing from me shortly.</P>

<P>Return to <A HREF="$site_url">$site_name</A>.</P>

</BODY>
</HTML>
EOF

Return to the top of the page

Testing the script

Once you have created the HTML form, created the script, and checked the permissions on everything, there's nothing left to do but fill out the form and submit it. If everything works right, you should get the Thank You screen:

Thank you

Thank you for your form submission. You will be hearing from me shortly.

Return to my site.

A few minutes later, if you check your email, you should have a message like this waiting for you:

Subject: Sample Web Form Submission
   Date: Wed, 23 Sep 1998 15:00:34 -0700
   From: "John Callender" <jbc@west.net>
     To: forms@lies.com

01_name: John Callender
02_address: 1234 Any St.
03_city: Anytown
04_state: CA
05_zip: 91234
06_country: USA
07_email: jbc@west.net
08_color: blue
09_movies: Blade Runner
09_movies: Pulp Fiction
09_movies: Full Metal Jacket
10_grow_up: CGI programmer
11_sandwiches: They're actually not bad if you use lots of peanut
butter.

If this doesn't work for you (and it probably won't the first time), get busy checking your error logs and testing your script from the command line and doing all the other things you need to do to track down and eliminate your bugs.

One thing you might see if you try to run mail_form.cgi from the command line, by the way, is this: Once you've got it so the Perl intepreter is happy with the syntax (that is, you've got all the curly braces in the right place and so on), you might see this weird-sounding message:

catlow:/w1/l/lies/begperl> ./mail_form.cgi
(offline mode: enter name=value pairs on standard input)

What that is, is CGI.pm giving you a chance to input data on the command line to correspond to the data that would have been submitted from the HTML form if you ran this as a CGI script. It's basically a testing and debugging tool.

Just type Control-D (which you might see abbreviated as ^D in some places), which is the end-of-file marker in Unix, and your script will go ahead and run the way it would have if CGI.pm hadn't intervened to let you input your test data.

Return to the top of the page

Previous Section | Table of Contents | Next Section


John Callender
jbc@west.net
http://www.west.net/~jbc/