In this tutorial I’ll show you how to create a simple form mailer CGI script in Perl. A form mailer script allows you to add feedback forms to your site. Visitors fill in the forms, and the information they fill in is emailed to you.
CGI form handling can be quite an involved process. Luckily the Perl CGI module, CGI.pm
, can do most of the tricky stuff for you! We’ll use CGI.pm
in this tutorial to build a nice simple form mailer.
Creating the form
The first thing to do is to build the HTML form that your visitors can fill in. We’re going to create a very simple form that just has fields for the user’s email address and their comments; however, once you’ve mastered the technique, you can easily add as many fields as you need to the form.
If you’re not familiar with creating HTML forms, you may like to read our HTML Forms tutorial first.
Here is the HTML for our simple form:
<html>
<head></head>
<body>
<form action="/cgi-bin/form_mailer.cgi" method="post">
<p>Your Email Address:<br>
<input type="text" name="email_address" size="40"></p>
<p>Your Comments:<br>
<textarea name="comments" cols="50" rows="10"></textarea>
<p><input type="submit" name="send" value="Send Email"></p>
</form>
</body>
</html>
Note that our form will send two parameters to our script: one called email_address
and one called comments
.
Call this page something like mailform.html
, and upload it to your site somewhere.
Now let’s build the CGI script itself!
Using CGI.pm
CGI.pm
is a Perl library module. It does a lot of the CGI “nitty-gritty” for you, which makes writing CGI scripts in Perl a lot easier!
Most Web servers these days tend to come with the CGI.pm module already installed. However, if you need to install it yourself, you can grab it here.
To use the CGI module in your scripts, you first tell Perl that you want to use it. This is done as follows:
use CGI;
Easy! π
The next step is to create a CGI
object. The CGI library is object-oriented, which means that all the functions and data to do with CGI are accessed through an object.
You can create the CGI object like this:
my $query = new CGI;
This creates a new CGI object and assigns a variable, $query
, to point to it.
Note that this Perl variable name has a dollar sign ($
) before it. This tells Perl that it is a scalar variable. Scalar variables can be used to store things like object pointers, numbers, and strings of text. (For more info see this tutorial.)
Note also that we are declaring the variable to be a private variable using the my
operator. This ensures that the variable will only exist within this CGI script. This is a good practice to get into, and will help you to avoid problems when developing more complex Perl scripts in the future!
Printing out the HTTP header
You may remember from the Your first CGI script tutorial that CGI scripts need to output a Content-type
HTTP header before anything else. It would be a good idea to print this out from our script now.
Luckily, the CGI object provides a handy way to output the Content-type
header:
print $query->header ( );
(You can specify the content type between the ( )
if required, e.g. $query->header(-type=>'text/plain')
; however, if you leave the brackets empty then it defaults to a content type of text/html
, which is usually what you want.)
Getting the form information
Now we have our CGI object, $query
, we can use it to retrieve the information that the user filled in. The information is passed to CGI scripts as parameters. To access a parameter using the CGI object, we use the following syntax:
$variable_name = $query->param("parameter_name");
So, if we want to retrieve the values of our two form parameters and store them in Perl variables, we can use something like this:
my $email_address = $query->param("email_address");
my $comments = $query->param("comments");
This code takes the values from our two form parameters, email_address
and comments
, and stores them in two Perl variables, $email_address
and $comments
.
Filtering the input
Whenever accepting data from an “untrusted” source, such as the our form, we should filter the data to ensure it doesn’t contain anything malicious. For example, without filtering the data in the $email_address
and $comments
variables, it would be quite easy for a hacker to use our form mailer script to send out spam to thousands of people!
To filter our data, let’s write a couple of simple filter functions:
sub filter_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
return $field;
}
sub filter_header_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
$field =~ s/[\0\n\r\|\!\/\<\>\^\$\%\*\&]+/ /g;
return $field;
}
These functions strip out anything in the data that could be used to trick our script into sending spam. filter_field
is good for general data (such as our $comments
variable), while filter_header_field
is a bit more thorough and is suited to header data, such as the “from:” and “to:” email addresses (which are the most vulnerable to hacking).
We’ll place these functions at the end of our script, and call them as follows:
$email_address = filter_header_field ( $email_address );
$comments = filter_field ( $comments );
Our input values have now been filtered against hacking attempts.
Emailing the results
Now that we have the values of our two form fields, and we know that those values won’t contain any malicious text, it’s time to email them to the webmaster (i.e. you!).
Most UNIX and Linux Web servers include a handy program called sendmail
that you can use to send emails from your CGI scripts. (If you have a Windows server or hosting account, you’ll need to ask your Web hosting company how to send emails from your CGI scripts, as the technique varies from server to server.)
To send the email, we open a pipe to the sendmail
program, then output our email (including header information such as the sender, recipient and email subject) to the pipe:
open ( MAIL, "| /usr/lib/sendmail -t" );
print MAIL "From: $email_address\n";
print MAIL "To: webmaster\@yourdomain.com\n";
print MAIL "Subject: Form Submission\n\n";
print MAIL "$comments\n";
print MAIL "\n.\n";
close ( MAIL );
You’ll need to change 2 things in this block of code:
- Change
/usr/lib/sendmail
to your actual path tosendmail
. (Often/usr/lib/sendmail
will work fine, but if it doesn’t, check with your Web hosting company to find the correct path for your server.) - Change
webmaster\@yourdomain.com
to your actual email address (the address you’d like the form results to be sent to). Note that the\
before the@
symbol is required – more about this in a later tutorial!
By the way, the line:
print MAIL "\n.\n";
prints a dot (.) on a line on its own at the end of the email. This tells sendmail
that we’ve finished outputting the email message. sendmail
will then send the email.
Thanking the visitor
Finally, let’s display a short page to the visitor, thanking them for filling in the form:
print <<END_HTML;
<html>
<head></head>
<body>Thanks for filling in our form!</body>
</html>
END_HTML
The finished script
That’s all there is to it! Let’s put it all together so you can see the whole script. We’ve added comments in the code (the lines beginning with #
) to help you work out which bit does what:
#!/usr/bin/perl
use CGI;
# Create the CGI object
my $query = new CGI;
# Output the HTTP header
print $query->header ( );
# Capture the form results
my $email_address = $query->param("email_address");
my $comments = $query->param("comments");
# Filter the form results
$email_address = filter_header_field ( $email_address );
$comments = filter_field ( $comments );
# Email the form results
open ( MAIL, "| /usr/lib/sendmail -t" );
print MAIL "From: $email_address\n";
print MAIL "To: webmaster\@yourdomain.com\n";
print MAIL "Subject: Form Submission\n\n";
print MAIL "$comments\n";
print MAIL "\n.\n";
close ( MAIL );
# Thank the user
print <<END_HTML;
<html>
<head></head>
<body>Thanks for filling in our form!</body>
</html>
END_HTML
# Functions for filtering user input
sub filter_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
return $field;
}
sub filter_header_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
$field =~ s/[\0\n\r\|\!\/\<\>\^\$\%\*\&]+/ /g;
return $field;
}
Call this file form_mailer.cgi
and upload it to your CGI scripts folder (usually cgi-bin
) on your server. Don’t forget to upload in ASCII mode, and to set execute permission on the file after you’ve uploaded it! (For more info on these points, read the Your first CGI script tutorial.)
Check your HTML form to make sure that the URL of your CGI script in the <form>
tag is correct.
Finally you’re ready to test your form mailer! Enter the URL of your form in your browser’s address bar, then fill in your email address and some comments, and press the Send Email button. If all goes well, you should shortly receive your comments in an email!
Troubleshooting the script
If you get the dreaded Internal Server Error message, don’t panic! It’s probably a simple problem. Make sure your paths to Perl and sendmail are correct, and that you uploaded your file in ASCII format and set the correct permissions. If you still have problems, check that your script doesn’t have any typos in, and that it looks like the version in our “The finished script” section above.
It’s also a good idea to work through our Troubleshooting CGI scripts tutorial.
If your script still doesn’t work, there may be some configuration problem with your server – ask your web hosting company’s technical support staff for help.
The script works fine and I receive an e-mail but the problem is when I open it it’s blank (absolutely no text). Any suggestions??
Hi tjstab, and welcome!
Maybe your ‘comments’ form field name is different to the parameter name in your script?
If you can post your script and HTML form here then I can take a look for you.
Cheers,
Matt
Finally, I set up a contact form that works! Thank you for your article!
Instead of the “end html” message, can I have the submitter directed to a different html document? Is there a way to insert a link instead of a message?
Thank you
@Will: Sorry for the delay in replying (Xmas and all that!).
Yes, you could include a link in the output HTML, eg:
Or you could issue a redirect to take the user to the new page automatically:
http://search.cpan.org/dist/CGI.pm/lib/CGI.pm#GENERATING_A_REDIRECTION_HEADER
Hope that helps π
Matt
Thanks! I think I’ll try that redirect issue.
I appreciate you help.
Sincerely,
Will
Matt,
I tried adding a second contact form, and made a second cgi file, but named it differently. And then of course I specified the second file in my form action=” ” . But it doesn’t work.
Do I have to name the cgi file, “form_mailer.cgi” in order for this to work?
Thanks,
Will
@Will: Sounds like you did the right things. What error are you getting?
I get Internal Server Error – “The server encountered an internal error or misconfiguration and was unable to complete your request.”
Here’s the cgi file:
Matt,
I have it figured out. I had forgot to set file permissions on the second cgi file I uploaded.
Thank you
@Will: Glad it’s working now. π
First of all I want to thank you for this tutorial π
I have used it for numerous form, applications etc, and it works just fine until I needed to send a copy of the application back to the user :p
So I want to take the email address of the user from the form and use it for a CC in the script…I thought it would be a simple task :p but I was wrong again π
I start with getting the form data:
Then I do a little check (and I also try to add the necessary @ and n at the end) :
and finally I print it to the mail:
So here’s what happens:
I get the mail fine for my main recipient, but the CC should be webform2010@gmail.com but ends up as webform2010@gmail.com@myservermailerdemon.com :p
I am slightly slipping into madness here π I have tried changing a lot of things, like switching from a line feed n to the actual characters, etc but nothing works π
Please o’wise one, do you have any inputs on this problem?
Thanks in advance π
[Edited by kingjazz on 07-Jun-11 17:15]
@kingjazz: Hmm, it could be an anti-spam mechanism inside your mail server. Is it escaping the ‘@’ symbol like that? (@)
Maybe try just sending 2 copies of the email, one to the main recipient and another to the CC recipient (using TO: instead of CC:)?
Also your regex might be messing things up (not sure). Try commenting it out and see if it works maybe?
Matt
Hi, thanks for that fantastic and very simple explanation.
I think I’ve done everything right, and I’m hoping what I’m about to ask is reasonably simple.
When I press ‘send’ I get this error:
“Not Found
The requested document was not found on this server.
Web Server at massageyou.co.nz”
the URL points to http://www.massageyou.co.nz/cgi-bin/formmailer.cgi as per the HTML. My CGI script is in the cgi-bin of my hosting account. However, my site is in the HTTPDocs folder of my hosting account. So I think the problem is one of two things:
1) My path is wrong. In which case, is it safe for me to move the cgi-bin folder into my HTTPDocs folder? Or is that likely to destroy things in the background that I can’t fix? Or, do you know what the path might be? (Sorry, I know the path could be anything, but I’m just assuming that my hosting account is set up in a fairly standard way.
2) I’m doing something else wrong – in which case – any ideas?
Thanks.
Tom
[Edited by tomnorman on 08-Aug-11 03:35]
@tomnorman: Check with your hosting company to see what setup they recommend for running Perl CGI scripts. It may be that you need to set certain permissions, use a different file extension such as .pl, or, as you say, move your cgi-bin folder into your document root.
Hi,
Thanks for this tutorial.
I tried running this form mailer from my local system and i got this error.
The server encountered an internal error or misconfiguration and was unable to complete your request.
I don’t know what to do.
I can feel it, I am insanely close! Unfortunately i am having the same issue as tjstab and am getting a blank email…
here’s the form code:
and here’s the cgi:
any help would be GREATLY appreciated!
Thank you!
@imokhaijoseph: Use CGI::Carp to view the exact error message:
http://perldoc.perl.org/CGI/Carp.html
@cordovad88: You seem to have missed out the lines of code relating to the comments field. This is why your message is blank.
eg:
is missing.
Thanks for the quick reply!
i added the lines to the code but.. no luck… any other ideas?
Thanks so much for your help.
@cordovad88: By “no luck” you mean you’re still getting a blank email?
Your form doesn’t seem to have a “comments” field in it either. You’ve also renamed the “email_address” field to “mail” in your form, which will break the script.
Hi, Can you explain how the filters work? (breakdown of the code) Thank you!
@brookln: filter_field should be self-explanatory. The last regular expression in filter_header_field filters out various unwanted characters that shouldn’t be in email headers: NULLs (\0), line feeds (\n), carriage returns (\r) and so on.