Building Your Own Basic CGI Program. Okay, I don't intend for this to be any kind of replacement for buying a couple of books and doing your own homework. What I hope this is, is a chance for you to take what you already know and maybe with the aid of this mold that into a useable base of knowledge. Also, this tutorial is primarily for those just getting into Perl and CGI. I'm going to assume you know a little about both. Meaning, you know that Perl is a programming language and that CGI is a means in which a client "talks" to a server and vice versa (in the plainest English I could think of!). What I'd like to suggest to you, is that you go out and get yourself a book or two on Perl. There are several excellent texts devoted to just learning Perl. One such book is "Learning Perl" by Randall Schwartz. Its published by O'Reilly Associates, and you can get it at any good book store. When it comes to learning Perl, there really are no short cuts. For my basic intro to Perl, check out the my Opinions on Perl. I tried to encapsulate a simple introduction to the language. Many have done it better than I have, so look around. Okay. First. People always ask, "what do you actually create a program in?" The answer is really simple: What ever you want. I happen to use Wordpad. A simple word processing program. All you need is the ability to type text in and save it as a ASCII text file. _________________________________________________________________ So, pop open your text editor of your choice and start with a fresh page. The first line of many Perl programs is the "pound bang" line. That looks like this: #! This is a special syntax used on Unix machines. This is the item that tells the operating system that it must send this data to an interpreter. What follows after the pound bang line is the path to the interpreter you want to use. In this case the Perl interpreter, which on many machines is located in the: /usr/bin/ directory. So, your pound bang line should look something like this: #!/usr/bin/perl If you don't know where Perl is on your system, at the command line, simply type: which perl. The machine will give you the path. Okay, now we have to decide what we want this program to do. Planning is essential when writing a program, and the more planning you do, the better your program will be. So, right now, lets decide what this program will do. We must be as specific as possible. We can't just say "uh, lets make it an order form program" That isn't enough. We must plan each procedure this program must perform. So, I've taken the liberty (since this is your first program, he he!) of deciding for you: We'll call this program "mylog" And here's what it will do: 1. mylog will control an HTML form that asks for a user's name, e-mail and ask for some comments. 2. mylog will decode and parse that input from the form. 3. mylog will return the user a thank you screen using some of the user's input. 4. mylog will send you (the maintainer) an e-mail reporting that someone filled in that form. 5. mylog will create/append a log file, writing each entry in a formatted, logical manner, so that you can user that information. Okay, lets take this step by step. The first thing we need, is a quick little HTML form. I've taken the liberty and created one for you. Just cut and paste this into another new text file and save it, then upload it to your space. MyLog Entry Form Please fill in the below information
####this is a comment. erase this. Make sure the "action" statement is pointed to where you keep your executable scripts#### Your Name:
Your e-mail:
Comments:

Okay, I'm sure you've seen this all before, but there is one thing that is very important with forms, and that is the "name" and if you have checkboxes or radio buttons, the "value" fields are as equally important. Make sure you name the "name" and/or "value" fields with names you can remember and names that make sense! So, we've got our form, now we need the program that is going to "drive" that form. You should have your text editor at the ready with a fresh page. First we enter in our pound bang line: #!/usr/bin/perl Side not: whenever you see a pound sign # in a Perl program, that means that what ever is after the # is a comment, meaning the interpreter ignores it, unless! an exclamation mark follows it. On the next line, I like to comment what the program is. # MyLog CGI program to process form input from mylog.html notice I started that line with a # The interpreter will ignore that line. All right. Now we get into some of the dirty work. Remember in our outline of what our program has to do we said that mylog must send e-mail, create/append a log file? That means we have to do some work with files and use an external program (the e-mail program on your machine). So, as good programming, the first thing we do is tell Perl where all this good stuff is! Now, Perl handles every thing as data, no matter if its a process or a directory path. And Perl handles data in primarily three methods: in scalar variables, in arrays and associative arrays. We'll get into each one later on, but right now we have to concern ourselves with scalar variables. This is the most basic method in which Perl handles data. Since we are dealing with singular items (handling a file and sending e-mail) we are going to store the information on both functions in two scalar variables. The first one we'll call "logfile" and the second we'll call "mailprog" We express scalar variables by putting a dollar sign before the name we assign to them: $logfile $mailprog So, what do we know? We know that the scalar variable (I'm just going to refer them as variables) $logfile is the variable that will "hold" our information on our log file and that $mailprog will hold the information on our e-mail program. We aren't done there. We must "define" what $logfile and $mailprog are. We just can't let them sit there. Perl is not a mind reader!! So, we must tell Perl what they are. $logfile = "/path/to/where/your/space/mylog/mylog.log"; So, what do you think this says? Basically what this says is that $logfile is a file called "mylog.log" We have to put the entire path to mylog.log because we have to tell Perl where it EXACTLY is! You'll also notice that you'll have to create a directory within your space called "mylog" I did this just so everything is kept nice and tidy! Now, we have to define $mailprog. You'll remember that $mailprog is the variable that tells Perl about our e-mail program. Basically what that is, is simply where it is. $mailprog = "/usr/sbin/sendmail"; Just like $logfile, $mailprog tells Perl where something is. In this case we are telling Perl that our mail program (called sendmail) is in the directory: /usr/sbin. side note: as a matter of practice. when ever you're dealing with files or programs, its best to put the path statements in double quotes. You'll also notice that each line ends with a semi-colon. This tells Perl that it is done with this line and to go onto the next. So lets do a quick review. What do we have so far? Well our program should look like this (I hope!): #!/usr/bin/perl # MyLog CGI program to process form input from mylog.html # Location of our logfile $logfile = "/usr/home/upstate/public_html/mylog/mylog.log"; # Location of our sendmail e-mail program $mailprog = "/usr/sbin/sendmail"; This is what we have so far! Pretty neat huh? Okay, lets get on with it. Now that we've defined these two variables there is one more that needs to be defined. This one is quite important, and directly relates to our first procedure we outline: parsing our form input. There is an external Perl program called the: cgi-lib.pl file. It stands for CGI Library. It is a library in the sense that it has many resource contained in one place which you may use to accomplish various tasks. cgi-lb.pl most popular function is gathering, decoding/parsing input from a form. Lets take a second here and talk about the mechanics of what happens when you click on that "submit" button. First all of the information that you entered in the text boxes, radio buttons or text areas, is all sent to the server. It is sent to the server in a format called URI encoded data. What that is, basically, is that your input is sent to the server in one long string with the name/value pairs delimited by ampersands (&) The name/value pairs are from the form itself, that's why its important for you to name them logically. Because you're going to have to know them in order to write your program. Okay, not only does URI encoded data come to the server as one long string, but it also picks up some nasty hexadecimal stuff. That hex stuff is quite important. See, if you have a forward slash (/) or a percent sign (%) or any other weird character, they have to be translated to their hexadecimal counterparts so the server doesn't get confused. So, now that the server has this one long string, its quite useless to us. So, this is where cgi-lib steps in. It gather's that URI encoded data and first splits it into a variable called $name and one called $value. Then it decodes all of the hexadecimal material, and translates it back to its ASCII form. Then once it does that it places the name/value pairs into an Associative array called "in" (associative arrays are data types that handle complex forms of data). So, now we have all of our form input (the name/value pairs) in our associative array called "in" I'll get to how we use that data in a second. Okay, so we know we need the cgi-lib.pl to decode our form input. but how do we do it???? Its rather simple. First we tell Perl where our cgi-lib.pl file is. I usually just create a variable called: library $library = "/usr/home/upstate/public_html/cgi-bin"; Now what we must do is tell Perl we are going to use cgi-lib.pl. We do this using one word: require then a statement saying what do we require: require "$library/cgi-lib.pl"; There now cgi-lib.pl has been assumed by our mylog.cgi program! But that's not it. We have to "call" what functions we need to use from the cgi-lib.pl file. That is done by "referencing" one of cgi-lib's functions. Remember I said its like a library. Well, in order to use the library, you have to know what you're looking for. So, to "call" the function in the cgi-lib, we just have to add this line: &ReadParse; This is the function that does all of what I talked about earlier. Okay, lets do another quick review. Make sure you're all up to speed! Here's what we should have by now: #!/usr/bin/perl #MyLog CGI program to process form input from mylog.html # Location of log file $logfile = "/usr/home/upstate/public_html/mylog/mylog.log"; # Location of e-mail program $mailprog = '/usr/sbin/sendmail'; # Location of our cgi-lib.pl file $library = "/usr/home/upstate/public_html/cgi-bin"; # Tell Perl we require cgi-lib.pl require "$library/cgi-lib.pl"; # Call cgi-lib's function to decode/parse our form input &ReadParse; side note: the ampersand (&) is the Perl syntax for calling a sub routine. A sub routine is a block of code that handles one area of the program. The nice thing with Perl is, you can call a sub routine any where in the program, and only right the routine once! So, you can have a sub routine that checks for a certain phrase in a text file and you might need to check it a number of times you just need to make a reference to the sub routine that does the checking. Allright. Now, we're almost done. So, we've got our form input placed into our associative array called in. Now we need to use that input! Lets remember one of the procedure's of myform. It was to return an html thanking the user for filing out the form. This will be the first time we use some of that input. The first thing we must do if we intend to return a page to the user is tell their browser what we're sending them. This is done simply by sending them the HTTP header, which simply tells their browser what the content will be. In our case it is text/html. So, we simply express this by writing this: print "Content-type: text/html\n\n"; the "print" command is Perl's standard output syntax. The "Content-type: text/html\n\n" is the header information. the \n\n is Perl's way of returning two new lines. This is crucial. When you send header information to a browser, you must return two new lines before you actually get into the content. Now that we have told our user's browser what its getting lets send it the goods! We do this by a series of "print" statements. We actually print an HTML page. Which looks like this: print "Thank you!</title</head>\n"; print "<body>\n"; print "Thanks $in{'usrname'} for filling out my form!<br>\n"; print "I'll keep in touch!\n"; print "</body></html>\n"; There, you'll notice we put a \n at the end of each line. Also I introduced something new, the $in{'usrname'} Do you recognize the 'usrname' part? Well you should, that's from our form! See, we made a reference to our associative array. The one that holds our form input. the 'usrname' is called a key. It represents a section in our associative array. the $in is simply naming the associative array. So, in simple English $in{'usrname'} equals the value from our form which is the user's name! If we wanted to tell the user that we know their e-mail address, we could do that by saying: $in{'email'} We can use any part of our input from the form just by referencing the corresponding key from our associative array. Okay, we've made sent our thank you's. Now lets get on with the business part. We want to write all of the user's input from the form into a file so that we can maintain a list of all those who have filled out our form. Perl is quite logical. You must do things in logical order for things to work. So, the first thing we must do if we want to work with a file is... open it! So lets do that: open(FILE, ">$logfile") || die "I can't open $logfile\n"; Allright. First lets take this line apart. The "open" command does just what it says, opens a file. You must assign a file that Perl is going to use what is called a filehandle, that is usually typed in all upper case. I simply called it FILE. Then we must tell Perl what file. The > tells Perl to append a file, or if it isn't there to just create the file. You remember that $logfile is the path and name of our log file. Then we add the: || die "I can't open $logfile\n"; the die is a simple method of killing the process if Perl can't find your file. When ever you are going to open a file or a process on your machine. Always include this, so nothing weird happens if you entered anything incorrectly! Okay, our file is open, now we want to write our information. We do that using the print command again. But... we don't want to print the output to the standard output (the screen) we want to write it to our file. So we tell it to print to our filehandle: print FILE "Someone filled out your form\n"; print FILE "Here is there information:\n"; print FILE "Name: $in{'usrname'}\n"; print FILE "E-mail: $in{'email'}\n"; print FILE "Comments: $in{'comments'}\n"; print FILE "\n"; Now, we close our file: close(FILE); There, this fully illustrates how we make our references to our associative array: in. So, if you wanted more form inputs on your form say you wanted to add an address field, and you named that field: address. The associative array reference would be: $in{'address'} Does that make sense??? That's why I said its important to name your field names in a logical way! I can't count the number of times I've forgotten what I named a particular name in a form! Okay, we're moving now. The next thing we want to do is, send ourselves some e-mail. You'll see a lot of CGI programs do this. And I usually build this into many of my programs. This is mainly for conveyance. I hate to log in, traverse through the file system to look at my log file. So, I just have it send me an e-mail with the information in it. Call me lazy! Okay, remember I said that Perl treats everything has a piece of data, that includes other programs. Remember we told Perl where and what our e-mail program was. We treat this just like it was an ordinary file, in one respect, and we do something slightly different than if it was just a regular file. We must take our input and send it to another program where it does its own thing. This process is called "piping" We "pipe" data to another program via this little thing: | I'll show you how it works: open(MAIL "| $mailprog -t") || die "I can't open $mailprog\n"; There, we opened our "file" and assigned it a filehandle. But instead of treating it as a regular file, we opened a "pipe" to our sendmail program. Also we added the die statement just incase! Now, we handle everything as if it was a normal file: print MAIL "To: Your Name <youremail@yourhost.com>\n"; print MAIL "From: $in{'usrname'} <$in{'email'}>\n"; print MAIL "Subject: My Log report\n"; print MAIL "Someone filled out your form\n"; print MAIL "It was filled out by: $in{'usrname'}\n"; print MAIL "Here is what they said:\n"; print MAIL "$in{'comments'}\n"; close(MAIL) There, you'll notice the To: From: and Subject: lines. This is so sendmail can properly format the message. You'll also notice the absence of the semi-colon after the: close(MAIL) that's because it is our last line in our program. So we can leave them off. So, that's it. Here's what the finished product should looks like: #!/usr/bin/perl #MyLog CGI program to process form input from mylog.html # Location of log file $logfile = "/usr/home/upstate/public_html/mylog/mylog.log"; # Location of e-mail program $mailprog = "/usr/sbin/sendmail"; # Location of our cgi-lib.pl file $library = "/usr/home/upstate/public_html/cgi-bin"; # Tell Perl we require cgi-lib.pl require "$library/cgi-lib.pl"; # Call cgi-lib's function to decode/parse our form input &ReadParse; # Print the http header print "Content-type: text/html\n\n"; # Send the user a thank you print "<html><head><title>Thank you!</title</head>\n"; print "<body>\n"; print "Thanks $in{'usrname'} for filling out my form!<br>\n"; print "I'll keep in touch!\n"; print "</body></html>\n"; # Open the log file and write the data open(FILE, ">$logfile") || die "I can't open $logfile\n"; print FILE "Someone filled out your form\n"; print FILE "Here is there information:\n"; print FILE "Name: $in{'usrname'}\n"; print FILE "E-mail: $in{'email'}\n"; print FILE "Comments: $in{'comments'}\n"; print FILE "\n"; close(FILE); # Open the sendmail program and pipe the data open(MAIL "| $mailprog -t") || die "I can't open $mailprog\n"; print MAIL "To: Your Name <youremail@yourhost.com>\n"; print MAIL "From: $in{'usrname'} <$in{'email'}>\n"; print MAIL "Subject: My Log report\n"; print MAIL "Someone filled out your form\n"; print MAIL "It was filled out by: $in{'usrname'}\n"; print MAIL "Here is what they said:\n"; print MAIL "$in{'comments'}\n"; close(MAIL) There that's it! Some closing thoughts. Make sure to comment your program heavily. This one didn't really require it. But for each section in your program, make sure you have a # and comment what that section does. That way in a month down the road you can easily remember what everything does! Okay, now save your text file call it: mylog.cgi and upload it to your cgi-bin. Chmod it to 755. Create the directory: mylog and chmod that to 777. This gives permission to write to the directory. Make sure to upload the mylog.html. Now try it out! Oh make sure you have cgi-lib.pl in place, and chmod that to 755! Well, if I left something out, or you need something clarified, just drop me a line at: dave@upstatepress.com This tutorial was written by: Dave Palmer ---------- Building Your Own CGI Program (a relatively complex one!) 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") { ¬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'}!!!\n"; print "\n"; print "

Thanks $FORM{'usrname'}

\n"; print "

Thanks for signing my guestbook. Your entry has been added!\n"; print "Click here to see your entry

\n"; print "

Note: you may need to reload your browser

\n"; print "\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 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 = ; close(FILE); $sizefile = @file; open(FILE, ">$guestbook") || die "I can't!\n"; for($a=0; $a<=$sizefile; $sizefile++) { $_ = $file[$a]; if(//) { print FILE "\n"; print FILE "

$FORM{'usrname'}
\n": if ($FORM{'city'} ) { print FILE "$FORM{'city'}"; } if ($FORM{'state'} ) { print FILE "$FORM{'state'}\n"; } if ($FORM{'url'} ) { print FILE "$FORM{'url'}\n"; } print FILE "$FORM{'comments'}

\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 = ; 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: 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: So, we ask Perl to try to find in our guestbook.html by using another "if" process: if (//) { Then, print 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 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 "\n"; If Perl does not find 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 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 = ; 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(//) { print FILE "\n"; print FILE "

$FORM{'usrname'}
\n": if ($FORM{'city'} ) { print FILE "$FORM{'city'}"; } if ($FORM{'state'} ) { print FILE "$FORM{'state'}\n"; } if ($FORM{'url'} ) { print FILE "$FORM{'url'}\n"; } print FILE "$FORM{'comments'}

\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 ¬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 "You didn't enter your name!\n"; print "\n"; print "

You did not enter your name.

\n"; print "

Please enter your name in the space provided below

\n"; print "
\n"; print "

Your name:

\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\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 "You didn't enter your e-mail!\n"; print "\n"; print "

You did not enter your e-mail address

\n"; print "

Please enter your it in the space provided below

\n"; print "
\n"; print "

Your e-mail:

\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\n"; exit; } sub missing_comments { print "Content-type: text/html\n\n"; print "You didn't enter any comments\n"; print "\n"; print "

You did not enter any comments

\n"; print "

Please enter some comments in the space below

\n"; print "
\n"; print "

Comments:

\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\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 \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 = ; 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(//) { print FILE "\n"; print FILE "

$FORM{'usrname'}
\n": if ($FORM{'city'} ) { print FILE "$FORM{'city'}"; } if ($FORM{'state'} ) { print FILE "$FORM{'state'}\n"; } if ($FORM{'url'} ) { print FILE "$FORM{'url'}\n"; } print FILE "$FORM{'comments'}

\n"; } else { print $_; } } close(FILE); ################ # Sub routines # ################ sub missing_name { print "Content-type: text/html\n\n"; print "You didn't enter your name!\n"; print "\n"; print "

You did not enter your name.

\n"; print "

Please enter your name in the space provided below

\n"; print "
\n"; print "

Your name:

\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\n"; exit; } sub missing_email { print "Content-type: text/html\n\n"; print "You didn't enter your e-mail!\n"; print "\n"; print "

You did not enter your e-mail address

\n"; print "

Please enter your it in the space provided below

\n"; print "
\n"; print "

Your e-mail:

\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\n"; exit; } sub missing_comments { print "Content-type: text/html\n\n"; print "You didn't enter comments\n"; print "\n"; print "

You did not enter any comments

\n"; print "

Please enter some comments in the space below

\n"; print "
\n"; print "

Comments:

\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\n"; exit; } sub notify { open(MAIL, "| $mailprog -t) || die "I can't open sendmail\n"; print MAIL "To: My Name \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); } 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 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!!! Tutorial written by: Dave Palmer ©199