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]
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!
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.
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.
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
Homepage City State Comments |
"usrname"
"email" "url" "city" "state" "comments" required fields |
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") {
¬ify;
}
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] ¬ify.) 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") {
¬ify;
}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.
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") {
¬ify;
}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);
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 ¬ify, &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 ¬ify. 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 <me@myhost.com>\n";
print MAIL "From: Guestbook@myhost.com\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 ¬ify 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") {
¬ify;
}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 <me@myhost.com>\n";
print MAIL "From: Guestbook@myhost.com\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);
}
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!!!