Building Your Own CGI Program (a relatively complex one!)

[Introduction | Getting Started | Parsing and Decoding | Associative Arrays | Error Checking and Working with Output | Writing to an Existing File | Sub Routines | Closing thoughts]


Introduction

So, did you make through Tutorial 1??? I hope so! Also, I'd have to say you're one dedicated learner! Jeeez, to tolerate my confusing banter!

Okay, so where are we? We learned how to write a CGI program that does some simple, yet some rather useful functions. Lets take a second to review.

We have a program called mylog.cgi and it does (I hope!):
1. Parses input from a form, and uses cgi-lib.pl to decode that input
2. Sends the user a thank you screen
3. Formats the user's input into a text file mylog.cgi creates
4. Sends us an e-mail notifying us of the new entry to our log file

If you'll remember, the first thing we did, even before we wrote a single character of code, was to plan. So, lets do that now. Lets do a quick thumbnail sketch of what we want this program to do.

Our program will be a guestbook. These are very popular, and you see them all over the place. So, lets write our own! Here's what we want it to do:

1. Parse our input from a form the user fills out
2. Send the user a confirmation page
3. Format the user's input to a separate HTML file. We'll have a feature that will allow the user to enter his URL and we'll create a link to it in his entry. We'll also ask for his e-mail and create a mailto link in his entry as well.
4. We will check the user's input, and do some error checking routines. This is important because if they forget to fill out a field, our program will inform them of this, and ask that they fill in the missing field. We'll even provide them with a field in which to do so (so they don't have to go back).
5. We have the program notify us when ever someone adds to our guestbook, but, only if we want it to!
6. We'll send the user a thank you e-mail (Thanks for signing our guest book!)

So, you'll see we have our work cut out for us! You'll also notice I'm going to be introducing some new concepts. But stick with it! If you went through Tutorial 1, you learned the key concepts. We are just going to expand on what you know!


 

Getting Started

First, launch your favorite text editor, and start with a nice fresh page.

Lets do it!

Lets start with our first line. If you remember, the first line in a Perl program (written for Unix machines, which this is) you need your "pound bang" line. So, go ahead and type that:

#!/usr/bin/perl

Now, before we go any further, lets add our first comment. Telling us what the hell this thing is!

# My Guestbook CGI program!

Okay, now just like mylog.cgi, the first order of business is we need to define a few variables. We are going to need several external files and processes for this program to work. So, we do that by assigning this information to scalar variables.

The first thing we'll tell Perl is the location of our actual guestbook page. This is the page where the user's input is placed. This file will be a regular HTML file.
$guestbook = "/home/your_name/public_html/guestbook/guestbook.html";

You'll notice that in your public_html directory, you'll have to create a directory called "guestbook" and inside that directory will be your guestbook.html file. Notice that the path is in double quotes, and the line ends with the semi-colon.

Okay, now we'll tell Perl what the URL of our guestbook.html file is. We are doing this because we want this URL to be linked on any of the return pages our program may return. I'll go into further detail a bit later on.
$guestbook_url = "http://www.yourhost.com/~yourname/guestbook/guestbook.html";

Now lets tell Perl the URL of our guestbook.cgi file. We do this because we are going to have to know this if the user misses a form field. We'll simply give them another opportunity to fill in the missing field. So, if we give them that chance, we'll have to tell Perl where our guestbook.cgi file is for the <form action...> statement.
$guest_cgi_url = "http://www.yourhost.com/cgi-bin/guestbook.cgi";

Lets give ourselves some options. Above in the thumbnail sketch I had mentioned we may want to send an e-mail to ourselves and to the user who just made an entry into the guestbook. You know, like a "Hi, thanks for signing our guestbook..." Not everyone wants this, so we'll give ourselves the option:
$notify = "yes"; #or set to "no"
$thanks = "yes"; #or set to "no"

If we want Perl to send an e-mail there's another piece of information we have to tell Perl. The location of our e-mail program (usually sendmail):
$mailprog = '/usr/sbin/sendmail';

(You'll notice I put this in single quotes. This is done when you are referencing a process)

Okay lets review. Lets check our code, and see what we have:

#!/usr/bin/perl
# My Guestbook CGI program!

$guestbook = "/home/your_name/public_html/guestbook/guestbook.html";
$guestbook_url = "http://www.yourhost.com/~yourname/guestbook/guestbook.html";
$guest_cgi_url = "http://www.yourhost.com/cgi-bin/guestbook.cgi";
$notify = "yes"; #or set to "no"
$thanks = "yes"; #or set to "no"
$mailprog = '/usr/sbin/sendmail';

There, this is what we have so far. Basically we've told Perl where some key files are located, so that when we need these files, we can just call our variable. So, lets get on with the business end of this program.

 

Parsing and Decoding

Now, you'll remember that parsing and decoding the form input is crucial to any CGI program that accepts input from a user. You'll also remember from our first tutorial that we used a file called: cgi-lib.pl to do this for us. Well, we've grown up, and we decided that we like to do things ourselves, so we are! We are going to write a procedure to first get our input, then decode it, and then place it into an associative array so that we can use the input. So, lets do this!

First, we want to get our input, we do this using Perl's "read" function. We "read" the input into a variable. We also tell Perl to "read" only what is given to it in the environmental variable called Content Length. So, we express this like this:
read(STDIN, $input, $ENV{'CONTENT_LENGTH'});

The STDIN is Perl's way of saying "Standard Input" If you send input from a form using the POST method (which this program is designer for) input is passed to the server via the Standard Input. If you use the GET method, input is passed via the Query String.

Okay, next thing we have to do is separate our input. You'll remember from the first tutorial that when the server first gets our input, its in one long string. And each form field is delimited by an ampersand (FormField1=some input&FormField2=more input&FormField3=...) Obviously we can't use this one long string, it's just too confusing. Actually, in real life its more confusing than this! Remember that any "?" "/" "\" "@" "$" and any other special character has to be translated into their hexidecimal equivelent before the server gets a hold of it.

So, one thing at a time. First lets split up our string of input, and place it into an array (remember an array can hold multiple pieces of data in a list format) So, the procedure to split this data is this:
@pairs = split(/&/, $input);

Okay, I know this is probably a little confusing, and actually it is. In Perl you often have to read backwards. See, the variable $input (which is the variable we placed our string of input into) is being "split" (split is a Perl function). We just tell Perl what we want to split the data on, and so we tell Perl to split it on the ampersand "&" because, remember, that our input string is delimited by ampersands! So, we do this, and we assign this stuff to a new array called @pairs. So, now we have our input data in this array called @pairs. Our data is still pretty useless. Sure, we've split things up, but now we need to decode this data, and create an associative array. To put it simply, an associative array is a data type that is specific to Perl. There isn't another language out there that has a data construct similar to Perl's associative array. An associative array is a data type that can hold many pieces of data, and can keep each individual piece of data separate. In an associative array you have what are called "keys." Keys are basically the components of the associative array. Of course this is an over simplified explanation, but it should suffice for our examples.

Lets get on with it. Lets decode this input in our @pairs array. Let me just present the code to you, then I'll explain it line by line:
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);

$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

$FORM{$name} = $value;
}

Okay, confused yet! No, actually what this means is exactly what I said above. With these 8 lines of code we accomplished a great deal. We first "looped" through our array called @pairs. We do this using the Perl function "foreach" What "foreach" does, is it loops through an array and places it contents into a variable called $pair. It loops through untill there is nothing more to loop through! Then we split our data again, we split our input on the equals sign ("=") because, remember that our input string, the name of the form fields equals the value (FormField=some input). So, we created to new variables, $name and $value. The $name variable is on the left, so anything split on the left side of the "=" will be placed into $name, and vise versa for the $value variable. Okay, now that we did that, then we did some translating. Remember in our input string that there are no spaces in the input string, so multiple words in the input string actually look like this: FormField=some+input+we+entered So, we need to translate (using Perl's tr function) to translate the "+" sign into white space: tr/+/ / In the first set of forward slashes we tell Perl what's going to be translated, then in the second set, we tell Perl what we want to translate it into. The next bit, we substitute (using Perl's "s/" function) all of our hexidecimal stuff. We substitute it and convert everything back to a nice ASCII format. I won't go through what each of those commands mean.

 

Associative Arrays

Then, the very last thing we do, is we create our associative array called %FORM. We don't actually "see" this associative array, but we know its there because we actually built it by saying this:
$FORM{$name} = $value

So, we built our associative array, we've assigned it "keys" (the $name variable, so anything that was split on the left side of the "=" sign is now a 'key' in the associative array, and the value of that key is $value, or what was split on the right side of the "=" sign)

Just to depart for a second, let me just illustrate what the above means. So, in our form for our guestbook, we will have a form field that asks for the person's name. And that form field is called: "usrname." After all is said and done, we have this, for this one instance:
$FORM{'usrname'} = Some Name

Lets take a second and review our code. Check your work, and see if you came up with this:

#!/usr/bin/perl
# My Guestbook CGI program!

$guestbook = "/home/your_name/public_html/guestbook/guestbook.html";
$guestbook_url = "http://www.yourhost.com/~yourname/guestbook/guestbook.html";
$guest_cgi_url = "http://www.yourhost.com/cgi-bin/guestbook.cgi";
$notify = "yes"; #or set to "no"
$thanks = "yes"; #or set to "no"
$mailprog = '/usr/sbin/sendmail';

#Get our form input
read(STDIN, $input, $ENV{'CONTENT_LENGTH'});

#split our input and create our array @pairs
@pairs = split(/&/, $input);

#loop through @pairs and place the data in $name and $value
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);

#translate all "+" into white space and decode hex stuff
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

#Create our associative array
$FORM{$name} = $value;
}

(side note: you'll notice that I indented the code. This isn't neccessary, I just do this to keep things organized. This is considered good coding! Also you'll note the additions of the comments, the # sign then some comments. This is good coding as well. Now that our program is building, we should comment it, so we can stay on top of our code!)

 

Error Checking and Working with Output

Okay, now we are ready to do some serious work!

The very first thing we want to do after decoding our input, is we want to make sure all of our required form fields are filled out. If not, we should send the user an error message, and an opportunity to make amends! So, right now lets decide what form fields we will have, and which ones will be required for our guestbook.

Form fields Form field names
Full Name

E-mail

Homepage

City

State

Comments

"usrname"

"email"

"url"

"city"

"state"

"comments"

required fields
are shown in bold.

Okay, so we've decided that "usrname", "email", and "comments" are required (afterall, what's a guestbook without comments and the person's name!). What we have to do is, use a simple decision making construct in Perl. There are many of these decision making constructs in Perl (these are things that set up scenerios and decide if we want something to happen or not to happen, some of these include "if" "else" "ifelse" "unless" and they're a few more. We are going to use "unless" to see if something is false, but if its true, then we can proceed. Let me just get this block of code out of the way:
&missing_name unless $FORM{'usrname'};
&missing_email unless $FORM{'email'};
&missing_comments unless $FORM{'comments'};

This basically says 3 things. If $FORM{'usrname'} or $FORM{'email'} or $FORM{'comments'} are empty, then Perl will go to our sub routines (remember, subroutines are referenced using the ampersand. They are defined usually at the bottom of the program, which is what we are going to do) So, we have 3 separate error routines that we will have to define (missing_name, missing_email and missing_comments). That is the extent of our error checking procedures, now lets check some of our options.

Remember way up at the top of our code we set some e-mail options? Well, now we want to act on what we set for those options. In our first option, called $notify, we said that if this is "yes" it will allow us to receive an e-mail notifying us of any new entries. We also have $thanks. If this is set to "yes" then this would allow our program to send an automated "thank you" to the person who just signed the guestbook. Well, since Perl doesn't really know what the word "yes" or "no" means, we have to tell Perl what they mean. We do this by using one of our decision making tools, "if".
if ($notify eq "yes") {
&notify;
}

if ($thanks eq "yes") {
&thanks;
}

This is rather self explanitory, and another reason why Perl really is easy to learn, because it is rather logical. Basically, what this says is, if $notify is (or equals, eq) to "yes" then do the sub routine [which we will define at the bottom] &notify.) The same with the $thanks option. Be aware of the syntax though. The conditions of your "if" must be in ( ) and the "then" code must be inside { }. (this generally goes for all of Perl's functions, like foreach, if, else, elseif etc.)

Okay, lets review our code again. Next up we are going to write our entry to our HTML file, and this is complex.

#!/usr/bin/perl
# My Guestbook CGI program!

$guestbook = "/home/your_name/public_html/guestbook/guestbook.html";
$guestbook_url = "http://www.yourhost.com/~yourname/guestbook/guestbook.html";
$guest_cgi_url = "http://www.yourhost.com/cgi-bin/guestbook.cgi";
$notify = "yes"; #or set to "no"
$thanks = "yes"; #or set to "no"
$mailprog = '/usr/sbin/sendmail';

#Get our form input
read(STDIN, $input, $ENV{'CONTENT_LENGTH'});

#split our input and create our array @pairs
@pairs = split(/&/, $input);

#loop through @pairs and place the data in $name and $value
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);

#translate all "+" into white space and decode hex stuff
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

#Create our associative array
$FORM{$name} = $value;
}

#Lets do some error checking. Make sure they filled out the required fields
&missing_name unless $FORM{'usrname'};
&missing_email unless $FORM{'email'};
&missing_comments unless $FORM{'comments'};

#Now, lets check our options for sending e-mail
if ($notify eq "yes") {
&notify;
}

if ($thanks eq "yes") {
&thanks;
}

Lets now take our input, and send a thank you message to our user, and then we'll write our data to our HTML file (the guestbook.html file).

Remember, anytime we want to "print" anything to the browser, we must tell the browser what it is. We do this by sending the Content-type part of the HTTP header. So, lets do this:
print "Content-type: text/html\n\n";

Now, lets print a short little thank you message they will see just after they click on the submit button, assuming they filled our required fields in. If they didn't, they will be returned with an error message.
print "<html><head><title>Thanks $FORM{'usrname'}!!!</title></head>\n";
print "<body>\n";
print "<p><h1>Thanks $FORM{'usrname'}</h1></p>\n";
print "<p>Thanks for signing my guestbook. Your entry has been added!\n";
print "<a href=\"$guestbook_url\">Click here to see your entry</a></p>\n";
print "<p><h6>Note: you may need to reload your browser</h6></p>\n";
print "</body></html>\n";

The above is just basic HTML, but there are some rules you must follow when you have Perl create your HTML page. The most important is that you "escape" any double quotes in your HTML code. You'll notice I did that with the <a href> tag. Use a back slash before each instance of a double quote. If you don't, Perl will just give you a bad time! Also, remember from the last lesson, that each printed line, should end with Perl's newline: \n.

 

Writing to an Existing File

Now, that we've done that, we should now do the actual work of the guestbook. This will require several operations, and the introduction of some new concepts. This will also illustrate just how perfectly suited Perl is with handeling a lot text, and doing so quickly. Here's what this next block of code is going to do:

open our guestbook.html file
"suck" our guestbook.html file into an array
We are then going to loop through our array using Perl's "for" function
We are going to count the number of lines in our guestbook.html file, then increment that number
We will then re-open our guestbook.html file, and append to it using Perl's ">" in our open(FILE) statement
We are going to check for a special comment in our guestbook.html file, and if we find it (which we should!) we will then print our new entry

I'll just dish out the code here:
open(FILE, "$guestbook) || die "I can't open $guestbook\n";

@file = <FILE>;
close(FILE);

$sizefile = @file;

open(FILE, ">$guestbook") || die "I can't!\n";
for($a=0; $a<=$sizefile; $sizefile++) {

$_ = $file[$a];

if(/<!--begin-->/) {
print FILE "<!--begin-->\n";
print FILE "<p><a href=\"mailto:$FORM{'email'}\">$FORM{'usrname'}</a><br>\n":

if ($FORM{'city'} ) {
print FILE "$FORM{'city'}";
}

if ($FORM{'state'} ) {
print FILE "$FORM{'state'}\n";
}

if ($FORM{'url'} ) {
print FILE "<a href=\"$FORM{'url'}\">$FORM{'url'}</a>\n";
}

print FILE "$FORM{'comments'}</p>\n";

} else {
print $_;

}

}

close(FILE);

Okay, so what do we have here? Well, first we have to open our guestbook.html file. Then the very next line:
@file = <FILE>;

We put the contents of our filehandle (remember, when Perl works with files, we assign them "filehandles") into a new array called @file. Then we close our file. The next thing we do is put the contents of @file (which is the entire guestbook.html) into a single variable called $sizefile. We do this so we can count the number of lines. Now that we've done that, we re-open our guestbook.html file, but we add Perl's append ">" sign. This means we're not just going to read our file, but we're going to write something to it. So, we do this:
open(FILE, ">$guestbook") || die "I can't\n";

(remember when we give Perl a chance to kill itself if it can't find the file its looking for, using the || [means "or"] die. So, that whole line basically says: Do Or Die)

Next we use "for" to loop through our $sizefile variable, checking the number of lines, and adding the new lines:
for ($a=0; $a<=$sizefile; $sizefile++) {

Then everything between for's { } is what we are going to add. We told Perl that we are going to increment with the last condition in the "for" statement: $sizefile++ Everything prior to that simply sets up "for's" "index" variable: $a to set a couple of conditions. Like the first one sets $a to zero each time the program is executed. Then we ask "for" to say that $a is greater than or equal to $sizefile, then once that is set, we can then increment the number of lines of $sizefile.

The next bunch of lines are what we want to print to our guestbook. So, we use Perl's "print" statement. The big difference with this is, we are not printing to the standard output. We are printing to a file, so we need to tell Perl this, but saying "print to our filehandle, FILE" That's done just like this:
print FILE "whatever\n";

Before we do that though, we want to make sure our special HTML comment is present in our guestbook.html file. Lets take a break from coding, and talk about our HTML file. We can put whatever we want int our guestbook.html file. We can make it look however we want, and put anything we want. There's only one rule we must follow, according to our program. We need to have a special line that will identify to Perl that "yes, you can print to me, and print right here in this spot" We use an HTML comment tag. Something that is invisible to the browser, but is not invisible to Perl. HTML comments look like this: <!--comment inside these brackets--> You can use comments in anything, and actually its a good idea to comment your HTML, just like its a good idea to comment your code. But, for our guestbook.html file, we need to designate a special identifier. We'll call it "begin" So, in our guestbook.html file we need a comment called "begin" So, it will look like this:
<!--begin-->

So, we ask Perl to try to find <!--begin--> in our guestbook.html by using another "if" process:
if (/<!--begin-->/) {
Then, print <!--begin-->
and then print the rest of the stuff.
}

Perl uses the forwar slash to match whatever is inside a pair of forward slashes " / / " So, if Perl finds <!--begin--> it will print the new entry. We just have to make sure to print out our comment again, so the very first print statement is:
print FILE "<!--begin-->\n";

If Perl does not find <!--begin--> then it prints everything to $_ which is to say that our output gets printed into oblivion! So, make sure that your HTML comment is present in your HTML file, and make sure that the first thing you print is the comment, because if you don't, then you'll only be allowed to enter one guest and that's it, your guestbook will not receieve any new entries!

The next series of lines are a bunch of "if" statements. Basically this is for cleanliness. What we're saying is, if our optional form fields are not filled it, they will not be printed (which if they are left blank, they would just be blank lines in our guestbook, which is rather messy looking) So, all we are doing is checking the "truth" of each optional form field. The "truth" is, that they contain something, and if they do, they will be printed. If they are "false" or empty, they will not be printed.

Then after we've printed our output to our guestbook file, we have our "else" statement if Perl did not find our <!--begin--> comment. As I said before, everything is printed to $_ and lost forever! Then, last but not least we close our FILE.

There, that's a lot of stuff, but if you think about it, we didn't have to write a lot of code to perform many functions. So, lets review our code. Check yours and make sure it looks something like this:

 

#!/usr/bin/perl
# My Guestbook CGI program!

$guestbook = "/home/your_name/public_html/guestbook/guestbook.html";
$guestbook_url = "http://www.yourhost.com/~yourname/guestbook/guestbook.html";
$guest_cgi_url = "http://www.yourhost.com/cgi-bin/guestbook.cgi";
$notify = "yes"; #or set to "no"
$thanks = "yes"; #or set to "no"
$mailprog = '/usr/sbin/sendmail';

#Get our form input
read(STDIN, $input, $ENV{'CONTENT_LENGTH'});

#split our input and create our array @pairs
@pairs = split(/&/, $input);

#loop through @pairs and place the data in $name and $value
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);

#translate all "+" into white space and decode hex stuff
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

#Create our associative array
$FORM{$name} = $value;
}

#Lets do some error checking. Make sure they filled out the required fields
&missing_name unless $FORM{'usrname'};
&missing_email unless $FORM{'email'};
&missing_comments unless $FORM{'comments'};

#Now, lets check our options for sending e-mail
if ($notify eq "yes") {
&notify;
}

if ($thanks eq "yes") {
&thanks;
}

#open our guestbook.html file
open(FILE, "$guestbook) || die "I can't open $guestbook\n";

#put the contents into @file
@file = <FILE>;
close(FILE);

$sizefile = @file;

#reopen our guestbook file, and get ready to write to it
open(FILE, ">$guestbook") || die "I can't!\n";

#loop through our file, and imcrement the number of lines.
for($a=0; $a<=$sizefile; $sizefile++) {

#put the contents of our file into $_ just in case we don't #find our comment below
$_ = $file[$a];

#Now, look through to find our comment, and if we do, print #our new entry
if(/<!--begin-->/) {
print FILE "<!--begin-->\n";
print FILE "<p><a href=\"mailto:$FORM{'email'}\">$FORM{'usrname'}</a><br>\n":

if ($FORM{'city'} ) {
print FILE "$FORM{'city'}";
}

if ($FORM{'state'} ) {
print FILE "$FORM{'state'}\n";
}

if ($FORM{'url'} ) {
print FILE "<a href=\"$FORM{'url'}\">$FORM{'url'}</a>\n";
}

print FILE "$FORM{'comments'}</p>\n";

} else {
print $_;

}

}

close(FILE);

 

Sub Routines

Okay, we're almost home! This is starting to look like a serious program!

The last thing we have to do is, define our sub routines: &missing_name, &missing_email, &missing_comments &notify, &thanks. So, lets do it.

Usually its considered good coding to comment where your sub routines begin. I usually just do this:
############
# Sub routines #
############

Okay, you'll remember from our first lesson that when we actually define a sub routine, we use the following format:
sub name_of_routine {
block of code
}

The "sub" tells Perl that this is a sub routine, and that the "name_of_routine" is what we indentified with the ampersand (&missing_name). We are going to make use of HTML's hidden form variables. If the user forgets to enter their name, they will be returned with a form that will ask for their name. The rest of the information they filled in, in the first form is entered in hidden form fields, that will then be passed to the script once all of the required form fields are filled in.

Lets do our error message routines first:
sub missing_name {
print "Content-type: text/html\n\n";
print "<html><head><title>You didn't enter your name!</title></head>\n";
print "<body>\n";
print "<p><h1>You did not enter your name.</h1></p>\n";
print "<p> Please enter your name in the space provided below</p>\n";
print "<form action=\"$guestbook_cgi_url\" method=POST>\n";
print "<p>Your name: <input type=text name=\"usrname\"></p>\n";
print "<input type=hidden name=\"email\" value=\"$email\">\n";
print "<input type=hidden name=\"comments\" value=\"$comments\">\n";
print "<input type=hidden name=\"city\" value=\"$city\">\n";
print "<input type=hidden name=\"state\" value=\"$state\">\n";
print "<input type=hidden name=\"homepage\" value=\"$url\">\n";
print "<input type=submit value=\"submit\">\n";
print "</form></body></html>\n";
exit;
}

There are a couple of very important things to consider when returning forms to a user. One is to always escape (using the back slash) each double quote, and to make sure that you use the exact same names in your hidden form fields. If not, then the input will just be lost. Also, with an error routine such as this, we always kill the rest of the script with the "exit" command. If we didn't add this, then sure, the user would get this message, but the rest of the script would still run, and we would have a serious mess on our hands! So, all we need are 2 more routines just like this, but ask for the missing e-mail or missing comments.

Just a quick note. If they forget 2 or all 3 required fields, that's okay. Perl will just return the error message for each missing field. And once all of them have been satisfied, the program will continue.

Lets just bang out the other two error sub routines:
sub missing_email {
print "Content-type: text/html\n\n";
print "<html><head><title>You didn't enter your e-mail!</title></head>\n";
print "<body>\n";
print "<p><h1>You did not enter your e-mail address</h1></p>\n";
print "<p> Please enter your it in the space provided below</p>\n";
print "<form action=\"$guestbook_cgi_url\" method=POST>\n";
print "<p>Your e-mail: <input type=text name=\"email\"></p>\n";
print "<input type=hidden name=\"usrname\" value=\"$usrname\">\n";
print "<input type=hidden name=\"comments\" value=\"$comments\">\n";
print "<input type=hidden name=\"city\" value=\"$city\">\n";
print "<input type=hidden name=\"state\" value=\"$state\">\n";
print "<input type=hidden name=\"homepage\" value=\"$url\">\n";
print "<input type=submit value=\"submit\">\n";
print "</form></body></html>\n";
exit;
}

sub missing_comments {
print "Content-type: text/html\n\n";
print "<html><head><title>You didn't enter any comments</title></head>\n";
print "<body>\n";
print "<p><h1>You did not enter any comments</h1></p>\n";
print "<p> Please enter some comments in the space below</p>\n";
print "<form action=\"$guestbook_cgi_url\" method=POST>\n";
print "<p>Comments:<br> <textarea cols=40 rows=4 name=\"comments\"></textarea></p>\n";
print "<input type=hidden name=\"email\" value=\"$email\">\n";
print "<input type=hidden name=\"usrname\" value=\"$usrname\">\n";
print "<input type=hidden name=\"city\" value=\"$city\">\n";
print "<input type=hidden name=\"state\" value=\"$state\">\n";
print "<input type=hidden name=\"homepage\" value=\"$url\">\n";
print "<input type=submit value=\"submit\">\n";
print "</form></body></html>\n";
exit;
}

Okay, now that we've gotten that out of the way, now we just have to define our other two sub routines, &thanks and &notify. If you remember, these are the two sub routines that send an e-mail to the user thanking them, and to you notifying you of a new entry, respectively. So, if we're going to send an e-mail we have to tell Perl that we are going to open a process (the e-mail program) You'll remember that we did this in our first lesson. We use Perl's "open" function, and create a filehandle so that we can "pipe" our output to our sendmail program. We use Perl's pipe " | " to send data to another process (or program). We can' t just print the output. Lets do this:
open(MAIL, "| $mailprog -t") || die "I can't open sendmail\n";

Now that we set up our filehandle and our pipe (remember we defined $mailprog as the path to sendmail) we can get on with the business of giving sendmail our output. Remember though, that this is a subroutine, and as such, we must write it as the above error routines:
sub notify {
open(MAIL, "| $mailprog -t) || die "I can't open sendmail\n";
print MAIL "To: My Name <[email protected]>\n";
print MAIL "From: [email protected]\n";
print MAIL "Subject: $FORM{'usrname'} added a new entry to your guestbook\n";
print MAIL "$FORM{'usrname'} added a new entry to your guestbook\n";
print MAIL "$guestbook_url\n";
close(MAIL);
}

sub thanks {
open(MAIL, "| $mailprog -t) || die "I can't open sendmail\n";
print MAIL "To: $FORM{'usrname'} <$FORM{'email'}>\n";
print MAIL "From: Your Name\n";
print MAIL "Subject: Thanks!!!\n";
print MAIL "Thanks $FORM{'usrname'} for adding your entry to my guestbook!\n";
print MAIL "I hope you'll stop by sometime again! $guestbook_url\n";
close(MAIL);
}

You'll notice that in the &notify sub routine the "To:" line is to yourself, and in the &thanks sub routine, we want to send it to who ever added the entry, so we make a reference to our assocative array. When ever you want to use any piece of information the user entered, all you have to do, once you create the associative array (as you did in section "Associative Arrays") you can then use that information by referencing the appropriate "keys".

Well, that's it! Lets review the final code. Check your code and make sure you have something that looks like this:

#!/usr/bin/perl
# My Guestbook CGI program!

$guestbook = "/home/your_name/public_html/guestbook/guestbook.html";
$guestbook_url = "http://www.yourhost.com/~yourname/guestbook/guestbook.html";
$guest_cgi_url = "http://www.yourhost.com/cgi-bin/guestbook.cgi";
$notify = "yes"; #or set to "no"
$thanks = "yes"; #or set to "no"
$mailprog = '/usr/sbin/sendmail';

#Get our form input
read(STDIN, $input, $ENV{'CONTENT_LENGTH'});

#split our input and create our array @pairs
@pairs = split(/&/, $input);

#loop through @pairs and place the data in $name and $value
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);

#translate all "+" into white space and decode hex stuff
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

#Create our associative array
$FORM{$name} = $value;
}

#Lets do some error checking. Make sure they filled out the required fields
&missing_name unless $FORM{'usrname'};
&missing_email unless $FORM{'email'};
&missing_comments unless $FORM{'comments'};

#Now, lets check our options for sending e-mail
if ($notify eq "yes") {
&notify;
}

if ($thanks eq "yes") {
&thanks;
}

#open our guestbook.html file
open(FILE, "$guestbook) || die "I can't open $guestbook\n";

#put the contents into @file
@file = <FILE>;
close(FILE);

$sizefile = @file;

#reopen our guestbook file, and get ready to write to it
open(FILE, ">$guestbook") || die "I can't!\n";

#loop through our file, and imcrement the number of lines.
for($a=0; $a<=$sizefile; $sizefile++) {

#put the contents of our file into $_ just in case we don't #find our comment below
$_ = $file[$a];

#Now, look through to find our comment, and if we do, print #our new entry
if(/<!--begin-->/) {
print FILE "<!--begin-->\n";
print FILE "<p><a href=\"mailto:$FORM{'email'}\">$FORM{'usrname'}</a><br>\n":

if ($FORM{'city'} ) {
print FILE "$FORM{'city'}";
}

if ($FORM{'state'} ) {
print FILE "$FORM{'state'}\n";
}

if ($FORM{'url'} ) {
print FILE "<a href=\"$FORM{'url'}\">$FORM{'url'}</a>\n";
}

print FILE "$FORM{'comments'}</p>\n";

} else {
print $_;

}

}

close(FILE);

################
# Sub routines #
################

sub missing_name {
print "Content-type: text/html\n\n";
print "<html><head><title>You didn't enter your name!</title></head>\n";
print "<body>\n";
print "<p><h1>You did not enter your name.</h1></p>\n";
print "<p> Please enter your name in the space provided below</p>\n";
print "<form action=\"$guestbook_cgi_url\" method=POST>\n";
print "<p>Your name: <input type=text name=\"usrname\"></p>\n";
print "<input type=hidden name=\"email\" value=\"$email\">\n";
print "<input type=hidden name=\"comments\" value=\"$comments\">\n";
print "<input type=hidden name=\"city\" value=\"$city\">\n";
print "<input type=hidden name=\"state\" value=\"$state\">\n";
print "<input type=hidden name=\"homepage\" value=\"$url\">\n";
print "<input type=submit value=\"submit\">\n";
print "</form></body></html>\n";
exit;
}

sub missing_email {
print "Content-type: text/html\n\n";
print "<html><head><title>You didn't enter your e-mail!</title></head>\n";
print "<body>\n";
print "<p><h1>You did not enter your e-mail address</h1></p>\n";
print "<p> Please enter your it in the space provided below</p>\n";
print "<form action=\"$guestbook_cgi_url\" method=POST>\n";
print "<p>Your e-mail: <input type=text name=\"email\"></p>\n";
print "<input type=hidden name=\"usrname\" value=\"$usrname\">\n";
print "<input type=hidden name=\"comments\" value=\"$comments\">\n";
print "<input type=hidden name=\"city\" value=\"$city\">\n";
print "<input type=hidden name=\"state\" value=\"$state\">\n";
print "<input type=hidden name=\"homepage\" value=\"$url\">\n";
print "<input type=submit value=\"submit\">\n";
print "</form></body></html>\n";
exit;
}

sub missing_comments {
print "Content-type: text/html\n\n";
print "<html><head><title>You didn't enter comments</title></head>\n"; print "<body>\n";
print "<p><h1>You did not enter any comments</h1></p>\n";
print "<p> Please enter some comments in the space below</p>\n";
print "<form action=\"$guestbook_cgi_url\" method=POST>\n";
print "<p>Comments:<br><textarea cols=40 rows=4 name=\"comments\">\n"; print "</textarea></p>\n";
print "<input type=hidden name=\"email\" value=\"$email\">\n";
print "<input type=hidden name=\"usrname\" value=\"$usrname\">\n";
print "<input type=hidden name=\"city\" value=\"$city\">\n";
print "<input type=hidden name=\"state\" value=\"$state\">\n";
print "<input type=hidden name=\"homepage\" value=\"$url\">\n";
print "<input type=submit value=\"submit\">\n";
print "</form></body></html>\n";
exit;
}

sub notify {
open(MAIL, "| $mailprog -t) || die "I can't open sendmail\n";
print MAIL "To: My Name <[email protected]>\n";
print MAIL "From: [email protected]\n";
print MAIL "Subject: $FORM{'usrname'} signed your guestbook\n";
print MAIL "$FORM{'usrname'} added a new entry to your guestbook\n";
print MAIL "$guestbook_url\n";
close(MAIL);
}

sub thanks {
open(MAIL, "| $mailprog -t) || die "I can't open sendmail\n";
print MAIL "To: $FORM{'usrname'} <$FORM{'email'}>\n";
print MAIL "From: Your Name\n";
print MAIL "Subject: Thanks!!!\n";
print MAIL "Thanks $FORM{'usrname'} for signing my guestbook!\n";
print MAIL "I hope you'll stop by sometime again! $guestbook_url\n";
close(MAIL);
}

 

Closing Thoughts

Wow! You made it to the end! Good job!!! Before I let you go and play, let me just do a quick run down on the installation of this thing.

First, you'll need to HTML pages. One page should be your entry form (using the same names as we discussed in "Error Checking and Working with Output"). The other HTML page is your actual guestbook.html file (the actual guestbook page) Remember, this guestbook.html file is the one with the special <!--begin--> comment.

Second, you'll need to create a directory in your place called 'guestbook' and chmod it to 777. This enables Perl to read, write and execute anything that sits in this directory.

Third, place the guestbook.html file in this directory, and chmod it to 777 as well.

Fourth, upload your newly created guestbook program to your cgi-bin, and chmod it to 755. Also, you will of course need to change the variables at the top of our code ($guestbook $guestbook_url and $guestbook_cgi_url) all so they conform to your system. You'll also want to check the location of sendmail and Perl itself. (Do this by telnet'ing into your place, and type: 'which perl' and 'which sendmail' This will tell you the location of each program)

You should be all set to go!

Okay, I'd like to close this by saying that, I showed you only ONE way to do this. The motto of Perl is "there's more than one way to do anything" So, having said that I urge you to take this code apart, re-arrange things, experiement. Take what you have learned here and apply it to different tasks. Pick up a couple of Perl books and learn different things so that you can add on to existing code, like the one I just walked you through. The learning doesn't stop just because you know how to do one or two tricks. Actually, your learning is just beginning. I have just begun myself. I learn new techniques and new ways of doing things everyday. I am, not by a long shot, completed with my Perl learning. There is so much more to do, writing a guestbook is just a stepping stone. Now, stop reading my horrible banter, and get out there and LEARN!!!

 

Return Home