Processing Forms
Dealing with input forms is one of the most useful implementations of CGIs. Instead of your form facelessly sending mail and then leaving the details of what happened behind the scenes all a bit hazy, you can have your forms giving a report of the data submitted and thank the reader for filling out the form. Very professional.
This page was last updated on 2012-08-21
The Goal
Ok, so we’re going to be writing a page of code that will be made up of three separate modules — each with a specific function. The script will:
- Accept submitted data from a HTML form and clean it up.
- Send this information in an email to you.
- Output a thank-you page straight from the script.
To see the code in action, get to grips with our feedback page (you may as well fill it in while you’re there).
The CGI accepts various types of form data and sends your responses to me, while generating a simple HTML page to report back what has transpired. This means the feedback process is complete — there’s closure when your data is sent, and your reader is sure it’s worked.
The HTML
It doesn’t really matter how many form elements you use, or which type they are. The script is designed to be adaptive to the form that calls it. For this example, use this simple block of code in a HTML file.
<form method="post" action="feedback.cgi">
Your name: <input type="text" name="name"><br>
Your email: <input type="text" name="email"><br>
Your comment: <textarea name="comment"></textarea><br>
<input type="submit">
</form>
This gives us three fields — name, email and comment. You’ll want to set the path to your CGI script properly in the action
attribute. Just save an empty feedback.cgi
file somewhere on your site and add in the address (relative or absolute). Don’t change anything else here until we’ve got it running.
The CGI
Now we need to get our hands on the Perl stuff. I won’t force you to write all this out — it’s a pretty common script, so you can just copy and paste it into your own CGI file and modify it afterwards.
Once you’ve got it, save it wherever the form is currently pointing (“feedback.cgi” in our example above). Don’t modify this until all the parts have been explained, except for the “To:” email address. Set this to your own email address, and don’t forget to escape the @-symbol by preceding it with a backslash so it doesn’t crash your script. Now, upload both files and change the CGI permissions with chmod, as explained in the introduction.
Call up the page with the form in your browser and fill in all the fields. Now submit it. If all goes well your thank-you page will appear after you press the button and an email should land in your account a short time later. If not, you may want to check out the section on debugging below and find where it’s all going wrong. Otherwise, proceed on to fully understand this script and begin customising it.
Debugging Perl
Perl is notoriously unhelpful when it comes to uncovering and effacing your errors. There are a number of things you could do wrong that will all end in the same way — you’ll receive an “Internal Server Error” and be told to be on your way. Here’s a list of common mistakes that you may want to check off if you’re having problems:
- Make sure your path to PERL is correct. Check with your host’s help pages to be sure you’re providing the correct file path.
/usr/bin/perl
is the usual path, but sometimes it’s different. - Remember those semicolons! The majority of Perl lines will end in semicolons, so read through your code and be sure none are missing.
- If you’re using external programs (have a look at the script again and you’ll see we’re using a program called
sendmail
in this one), make sure their paths are also correct. While the paths are pretty standard on most servers, some are set up differently to the norm. Again, your host’s help pages should have the information you need. - Make sure all special characters are escaped with a backslash. @-signs and all quotes have to be escaped (written as
\@
) or you’ll get an error. - When working on Windows PCs, you will get a “Premature end of script headers” error when you upload to your (Linux-running) web host. This is because the return character on Windows is different to a return on UNIX-based systems, such as Linux. A good text editor will take care of this for you, but if you’re having a problem, changing the file format from PC to UNIX in your FTP client will probably fix it for you.
Internal Server Errors mean that there’s a bug in your script. If you have access to the server logs, you can see what the error was there. Another way is to add a line of Perl near the top of your code,
use CGI::Carp qw(fatalsToBrowser);
which will route any error message directly to your browser instead of to the log. If you can run CGIs from the command line in Unix, add in flags so that warnings will be pointed out — perl -cw scriptname.cgi. This will report on what line the errors are occurring, making it easy to narrow your search for irritating bugs.
Anatomy of a Script
So now that we have our script running, we can take a better look at what it’s up to. It does look initially confusing, and Perl’s syntax doesn’t make it the most readable language going, but if we take each module on its own it should make perfect sense.
Accept submitted data
The first part of our script takes in the form data and puts it into readable form. When it receives this information first (through the %ENV
hash, remember?), it is just one stream of data, with the fields joined together with ampersands (&), the names and corresponding values are split with equals signs, and the values connected with pluses. For example:
name=Ross+Shannon&email=mail@example.com &comment=Good+site,+man
We have to sort all these connections out.
if ($ENV{'REQUEST_METHOD'} eq 'POST') { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); @pairs = split(/&/, $buffer); # Starts fixing the data up foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/ pack("C", hex($1))/eg; $FORM{$name} = $value; }
Check out the comment I’ve put in the code above. Any line in Perl that begins with a hash mark (#
) will be skipped over by the interpreter. Make sure it doesn’t wrap down onto the next line though — add another mark at the start of each line of the comment.
Right, this code needs some explanation. First, it uses an if-statement to check that the form used “POST” before it executes any other commands. If the method did EQual “POST”, the commands in the block of code bordered by the curly braces is executed. It then takes the data and reads it all in as a string to the variable $buffer
through STDIN
(STanDard INput). We make sure we’ve gotten it all by making use of the CONTENT_LENGTH
variable. The data is then put through a number of filters, split up at each connection that has been placed in en route. An array is set up for the pairs of names and values, and the case of the text is set uniformly. Then the data is wrapped up and ready to be used.
Once it has been run through our filters, we can call each element with the variables $FORM{'name'}
, $FORM{'email'}
or $FORM{'comment'}
; as well as our submit address. If you’ve added another element to the form page, you can get its value simply by putting its name
into position — $FORM{'elementname'}
.
In truth, this bit of the code is fairly difficult. You shouldn’t expect yourself to fully understand everything that’s happening, as it is less than intuitive, but just make sure you have a basic grasp before you go on.
Writing the Email
Right, let’s get busy. Here’s the code we’re addressing in this section:
open (MESSAGE,"| /usr/sbin/sendmail -t"); print MESSAGE "To: you\@example.com\n"; print MESSAGE "From: " . $FORM{name} . ", reader\n"; print MESSAGE "Reply-to: " . $FORM{email} . "(" . $FORM{name} . ")\n"; print MESSAGE "Subject: Feedback from $FORM{name} \n\n"; print MESSAGE "$FORM{name} wrote:\n\n"; print MESSAGE "Comment: $FORM{comment}\n\n"; close (MESSAGE); &thank_you; #method call }
This code begins with the command to open a process. This command can also be used to open and read/write to a file, but that’s for further on. For now, we’re opening the sendmail
process, which comes as part of PERL. Again, make sure you’re using the right path here or your program can’t work. We open this process and give its output a name, here it is MESSAGE
, but it can be anything. The vertical line symbolises the “pipe” we are opening between our data and the sendmail program, and the -t
flag says we’re sending it some text. From now on in this module, we’ll be writing to our virtual file MESSAGE
, and when we’re finished with it we’ll send it through sendmail.
This bit isn’t hard. You’re just using your information to fill in the fields in a normal email message. For instance, our variable $FORM{submitaddress}
gets placed into the To: box in the email, thus addressing the mail to you.
Once all that has been filled in, you can add information to the body of the email. The way I put this body together is just a guide — you can actually write what you want.
Remember, if you have added new form fields to the HTML page, you can access them here just as easily as any of the ones in this example, as they’ve been set up in exactly the same way by the first module.
Once the mail has been written, we close (MESSAGE)
to show that we’re finished with it and that it can now be sent. Finally, we end this module by calling a subroutine, which we’ve called thank_you
. Subroutines are practically the same as functions or methods in other languages. This is the code that will set up our virtual thank-you page. Looking below in the CGI code, you can see that it’s this subroutine that makes up the third module.
Saying Thanks
We’re almost there. Here’s the subroutine code:
sub thank_you { print "Content-type: text/html\n\n"; print <<EndStart; <html> <head> <title>Thank You</title> </head> <body bgcolor="#ffffff" text="#000000"> <h1>Thank You</h1> <p>Your feedback has been received. Thanks for sending it.</p> <hr> EndStart print "<p>You wrote:</p>"; print "<p><em>$FORM{comment}</em></p>\n\n"; print <<EndHTML; </body> </html> EndHTML exit(0); }
First, we declare this block of code as a subroutine called thank_you
. Then we just go and write a simple HTML page, as we did in the first tutorial. Nothing to it.
Here, I’m using a different syntax to print out the lines. Instead of adding print ""
statements around every line, which is laborious and soon becomes confusing and is prone to errors, we state that we want to print out a large piece of HTML with the print <<Section
command. The first time it’s used, we say we want to print each line up to the section identifier “EndHTML” (the name can be anything). Then we write EndHTML further down. This word must be right in at the margin or it won’t work and you’ll get an error. Whenever we want to use some Perl, we stop printing and write the code, then start a new printing block.
Using this method is much faster than the alternative, and you don’t have to escape any quotation marks. @-signs, however, must still be escaped by writing them as \@
, as otherwise you will get a programming error.
sourcetip: Note that the opening print <<Section
command has a semicolon after it, but when Section
comes later, it doesn’t (for some reason). Another common error.
When our page is written (make sure it’s a proper HTML page, with all tags closed etc.; and make sure you’ve put in the content header), we end the subroutine with exit(0)
, which informs the interpreter that the subroutine ended without error, and to send its data to the browser window.
The script is finished. Use it well.