Поиск в интернет-магазине Bolero
<Каталог сайтов - Найдётся всё yandex-rambler.ru Приглашение!
Искать на Озоне

Ozon.ru Ozon.ru

Поиск в интернет-магазине Bolero
Назад в будущее


Chapter 22 -- Simple Order Entry

Chapter 22

Simple Order Entry


CONTENTS


How simple is simple order entry? Surprisingly simple. A small program that parsed an order form was the first CGI I ever wrote. In fact, I would be willing to guess that a program of this sort was the most common "first CGI" of the majority of people who have ever written a CGI program. After all, the idea of being able to sell your products to anyone, anywhere is one of the most appealing aspects of the World Wide Web to business. It's probably one of the primary reasons businesses get Web sites in the first place.

Therefore, the need certainly exists for this type of program (and people who can write them), and this need continues to grow every day. If you are currently writing only HTML, your stock will certainly go up when you announce that you can now process order forms with CGIs. The really good news about this is that learning to write this sort of CGI is not very difficult at all.

More experienced programmers may find the pace of this chapter a little bit tedious. This chapter is geared toward the beginning CGI and Perl programmer. A step-by-step analysis of almost every line of code is given. If your are an experienced programmer, you might want to jump ahead to the code listing to extract the information you are looking for.

What This Chapter Covers

This chapter covers the basics of extracting information from on-line forms, processing that information, checking it for errors, and mailing away to someone who cares. Think of simple orders as those annoying cards that fall out of magazines. They're short and to the point, not terribly impressive, but they get the job done.

In this chapter, you learn the following:

  • How to decode form data
  • How to check for common errors in form input
  • How to embed HTML in your CGI
  • How to use sendmail
  • How to handle security issues

More complex features dealing with order taking are be discussed in Chapter 23, "Shopping Carts." What you learn in this chapter is expanded upon there to include customer tracking, simple database integration, and dynamic form creation.

Forms and the Data They Produce

I'm going to assume that by this point, you are reasonably familiar with designing forms in HTML. Listing 22.1 shows a typical simple form. It should be pretty comparable to any form you might have to parse. I've used a number of different INPUT types to drive home the fact that no matter what types of fields you use in your forms, the format of the data produced is pretty much the same.


Listing 22.1. Sample form HTML.
<FORM METHOD=POST ACTION="order.cgi">
<PRE>
Name:              <INPUT TYPE="text" SIZE=25 NAME="applicant">
Address:        <INPUT TYPE="text" SIZE=25 NAME="address">
City:            <INPUT TYPE="text" SIZE=25 NAME="city">
State/Provice:        <INPUT TYPE="text" SIZE=25 NAME="state">
Zip Code/Postal Code:    <INPUT TYPE="text" SIZE=25 NAME="zipcode">
Country:        <INPUT TYPE="text" SIZE=25 NAME="country">
Email:             <INPUT TYPE="text" SIZE=25 NAME="email">
</PRE>

<b>I would like to Order the following magazines at Super Low Prices!</b><br>
<INPUT TYPE="checkbox" NAME="zines"
VALUE="Spaceships of the Rich & Famous">Spaceships of the Rich & Famous<br>
<INPUT TYPE="checkbox" NAME="zines"
VALUE="Cooking with Soylent Green">Cooking with Soylent Green<br>
<INPUT TYPE="checkbox" NAME="zines"
VALUE="Asteroid Living">Asteroid Living<br>

<P>
<b>Choose your Free Gift:</b> <select NAME="gift">
<OPTION>Universal Translator
<OPTION>TriCorder
<OPTION>Phaser
</SELECT>

<p>
<b'Ýyment Method:</b>
<br>
<INPUT TYPE="radio" NAME="payment_method"
VALUE="FooBar_Charge_Card" chECKED>FooBar Charge Card<br>
<INPUT TYPE="radio" NAME="payment_method"
VALUE="Frontiers_Credit_Card">Frontiers Credit Card<br>
<INPUT TYPE="radio" NAME="payment_method" VALUE="COD">C.O.D.<br>
<INPUT TYPE="radio" NAME="payment_method" VALUE="Check">Check<br>
<INPUT TYPE="radio" NAME="payment_method"
VALUE="Money_Order no 0">Money Order<br>

<p>
<b>Card Number</b> (if applicable):
<INPUT TYPE="text" SIZE=12 NAME="card_number">

<p>
<TEXTAREA NAME="suggestions" COLS=60 ROWS=5>
Please suggest ways we can improve this service.
</TEXTAREA>

<p>
<INPUT TYPE="submit" VALUE="Send Those Magazines!"><INPUT TYPE="reset"
>
</FORM>

Figure 22.1 shows an example of an order form.

Figure 22.1: FooBar Frontier Goods.

Note
Not having the ability to program a CGI has not stopped a great number of people from putting their order forms on the Internet. Instead of indicating a CGI in the ACTION= form header, they simply use ACTION=mailto:recipient. Let's take a look at the results from doing this with our (admittedly simple) form:
Date sent:         Fri, 14 Jun 1996 04:46:47 -0400
From:              WWW-Server
To:                  ken.hunt@anadas.com
Subject:              Form posted from Mozilla
applicant=John+Doe&Address=520+Main+St.+Apt.%23204&city=Cicily&state=Alaska&
zipcode=90210&country=USA&email=jdoe@KBHR.org&zines=Cooking+With+Soylent+Green
&zines=Asteroid+Living&gift=TriCorder&payment_method=Frontiers+Credit+Card&
card_number=123456789123&suggestions=Can+you+offer+%22Asteroid+Living+For+Kids
%22%3F%OD%OA
As you can see, the results are not impossible to deal with. In fact, with some effort and a lot of patience, you could almost live with the output, and many people do. There is simply no reason to live like this, though, considering the tools we have at our disposal.
There are literally thousands of forms set up with mailto: on the Internet. Next time you are surfing around and find a simple form, check to see if they are simply mailing to themselves. Maybe you can offer them your newfound CGI programming ability.

The FORM Tag

Each form indicates a METHOD and an ACTION in the form description. The action indicates the location of the CGI that will be used to process the form; the method indicates how the data will be transferred.

<FORM METHOD=POST ACTION="order.cgi">

In this form, we are using the POST method to send the form output to order.cgi, a program located in the same directory as the HTML file that executes it.

Caution
On some systems (in fact on many), you are allowed to execute CGIs located only in the cgi-bin directory. Depending on system security and your privileges, you may or may not have permission to write to that directory. Some system administrators want to be able to check each and every CGI on their system themselves before they mount them for the world to use. This can make trying to write and debug CGIs incredibly difficult. Unfortunately, there is no easy solution to this problem, short of begging the system administrator for more privileges or switching to a system that is more lenient. Make sure you take the time to find out who has permission to mount CGIs on your system.

Methods

The two methods for sending information to a CGI from a form are GET and POST.

In the GET method, information will be appended to the URL in ACTION. GET is quicker than POST but produces long, unattractive URLs. GET is ideally suited to many applications on the Internet such as search engines because speed is critical, and the information-appended URL makes it easy to bookmark the output of the CGI. When information is passed through this method, the data (sans preceding URL) is stored in the environment variable QUERY_STRING.

The POST method, which is the preferred method for our type of application, doesn't produce those nasty-looking URLs. Information entered in the form will go to STDIN (standard input). There is no delimiter or marker such as End Of File sent here, but the length of the input string is stored in the environment variable CONTENT_LENGTH.

Environment Variables

The browser is constantly sending environment variables to the server. Environment variables contain information about what type of software is being run, the IP address of the person viewing a page, and much more. CGIs allow you to access those variables. Most environment variables aren't particularly useful to you at this time, but there are a few that you definitely need. Table 22.1 lists the environment variables you will be using in this chapter.

Table 22.1. Environment variables used in form processing.
CONTENT_LENGTH The length of input going to the CGI. You will need to know this variable to read data submitted via METHOD=POST.
QUERY_STRING Data submitted via METHOD=GET will be contained in here.
REQUEST_METHOD Tells us which format is being used to transfer the data: for our purposes, GET or POST.

Perl automatically puts all the environment variables into the associative array %ENV. In order to determine the value of any environment variable, use $ENV{Variable_name}.

Tip
In order to find out which environment variables your browser is sending, use this quick and dirty little CGI:
#!/usr/bin/perl


print "Content-type: text/HTML\n\n";
foreach (keys %ENV){
print "$_=$ENV{$_}";
}

What the Raw Data Looks Like

Let's take a closer look at the raw data produced by simply e-mailing the form output with mailto:. This data is in the same form as that which will be passed to the CGI:

applicant=John+Doe&address=520+Main+St.+Apt.%23204&city=Cicily&state=Alaska&zipcode=9021
0&country=USA&email=jdoe@KBHR.org&zines=Cooking+With+Soylent+Green&zines=Asteroid+Living
&gift=TriCorder&payment_method=Frontiers+Credit+Card&card_number=123456789123&suggestion
s=Can+you+offer+%22Asteroid+Living+For+Kids%22%3F%0D%0A

The data is URL encoded. The name/value pairs established by the form are separated by ampersands (&),spaces have been turned into pluses (+), and some characters are expressed in HEX format (%xx). Our first objective will be to make this data readable.

Processing the Data with Perl

Perl specializes in string handling and manipulation. This makes it the language of choice for a great many CGI applications. Another powerful feature of Perl is associative arrays, which allow us to reference elements of an array by using a string instead of by index.

Note
Some tools already exist to aid you in form processing as well as other functions performed with CGIs. Cgi-lib.pl, a library of Perl functions by Stephen Brenner, gives you a resource that will make processing forms a lot easier. The latest version of Perl 5 has a CGI.pm library of on-the-fly form parsing and processing features built in. Because these tools are not as widely available as standard Perl 4, all the code in this chapter is written in standard Perl 4 for maximum portability.

A Simple Parsing CGI

This Perl program reads the order form we've submitted with METHOD=POST and displays the name/value pairs on-screen. This code should work with any form submitted via METHOD=POST.

#!/usr/bin/perl
print "Content-type: text/html\n\n";

read(STDIN,$input,$ENV{'CONTENT_LENGTH'});

@input=split (/&/,$input);

foreach $i (0 .. $#input) {
$input[$i]=~ s/\+/ /g;
($name, $value)=split(/=/,$input[$i],2);
$input{$name} .=$value;
}

print <<EOT;
<HTML><HEAD>
<TITLE>Order Output</TITLE>
</HEAD>

<BODY>
EOT

foreach (keys %input) {
print "$_=$input{$_}<br>\n";
}

print <<EOT;
</BODY>
</HTML>
EOT

Let's take a closer look at this code step by step.

#!/usr/bin/perl
print "Content-type: text/html\n\n";

These are the first two lines of just about any CGI written in Perl. The first line simply states that the CGI is a Perl program and indicates the path for the Perl interpreter. You need to know where the Perl interpreter has been installed on your system. You can generally find this out by typing

which perl

from your shell. If you still can't find the proper path, ask your system administrator.

The second line of code lets the world know that this particular Perl program will be producing HTML.

Because we are using METHOD=POST, the information from the form is being passed to the CGI via STDIN (standard input). The form data will be read into the variable $input. We need to use the environment variable CONTENT_LENGTH to determine the length of the input stream we will be reading.

read(STDIN,$input,$ENV{'CONTENT_LENGTH'});

Caution
Whenever you are using associative arrays, make sure you remember to use curly brackets {} to enclose the name of the array element you are referencing. Unless your screen display is particularly good, the difference between curly brackets {} and round brackets () can be difficult to see.

As you saw earlier in the use of a mailto: tag to pipe the output of the form, the name/value pairs are separated by ampersands (&).Take the input string, $input, and break it into an array, separating elements at ampersands. Each element of this array will contain a name/value pair separated by an equal sign. It should be noted that the ampersands are removed from the input string during this process.

@input=split (/&/,$input);

We will now cycle through our input array (@input), making the data more readable, separating the name/value pairs, and putting them into an associative array. In Perl the number of elements in an array is contained by the variable $#array_name.

foreach $i (0 .. $#input) {

In the URL encoding process used to transmit the form data, all the spaces have been converted to pluses (+). Perform a global substitution on each element of @input replacing + with a blank space. It is important to precede the + with a backslash \ in this instance because the default use of + is as a wild card match. The g indicates that this is a "global" replace: all + signs will be replaced; not just the first match.

$input[$i]=~ s/\+/ /g;

Note
Perl's substitution command s is a very powerful tool and is one of the things that makes Perl the language of choice for a great deal of CGI programming.
To perform a substitution on a string, the format is as follows:
$string=~ s/PATTERN/REPLACEMENT/[g][i][e]
The g option substitutes globally; all references to PATTERN within $string will be replaced. If g is not indicated, only the first reference to PATTERN will be replaced. The i option indicates that the substitution will be insensitive to case: PATTERN will also match PatTern, and both will be replaced. The e option allows you to match and replace an evaluated expression. You'll see an example of this when we get rid of the HEX codes sent to our CGI in the section "Parsing the Data: Round 2."

We will now split the name/value pair into the scalar variables $name and $value and place the values in an associative array for easier reference. Because name and value are separated by an equal sign (=), we separate the variables on that character. Because we know how many fields we're breaking each element into, indicate 2 as the LIMIT parameter. Note that this wasn't possible in our previous use of the split function because we didn't know how many times we would be spitting the STDIN stream.

The fact that we are putting these variables into an associative array is indicated by assigning the elements of that array using curly brackets. Notice that we are not simply assigning values to the associative array but appending them by using .=; the reason for this will become clear shortly.

($name, $value)=split(/=/,$input[$i],2);
$input{$name} .=$value;
}

With Perl, you can easily embed HTML within your CGIs because you can indicate you want whole lists of text printed. This is standard HTML file header information. I use EOT to indicate "End Of Text," but you can use any unique string you wish.

print <<EOT;
<HTML><HEAD>
<TITLE>Order Output</TITLE>
</HEAD>

<BODY>
EOT

Caution
Many commands in Perl accept a list as their parameter, such as print<<EOT; if you are indenting your code, make sure that you do not indent the line containing the EOT marker. Perl will not recognize an End of List marker unless it is identical to the marker for which it is looking. That means you cannot use preceding or trailing spaces or tabs.

We will now print the contents of the associative array %input. The element names of an associative array are referred to as keys. The keys of the associative array are stored, conveniently, in keys. In each iteration of the foreach loop, the current key is stored in the special variable $_. The print statement contains both an HTML linebreak (<br>) and a Perl linebreak (\n). This will force a break in both the screen output and in the source listing.

foreach (keys %input) {
print "$_=$input{$_}<br>\n";
}

The rest of the code simply cleans up the bottom of our HTML.

The Output of the Simple CGI

By taking a look at the data produced by this simple program example, you can already see a vast improvement in readability. This is some typical data that would be displayed after pressing the submit button on our form:

state=CA
card_number=123456789
country=USA
address=520 Main St. %23204
email=ken.hunt@westbevhigh.edu
city=Beverly Hills
zines=Cooking With Soylent GreenAsteroid Living
gift=TriCorder
suggestions=Can you offer %22Asteroid Living for Kids%22 Magazine%3F%0D%0A
applicant=Ken Hunt
payment_method=Frontiers Credit Card
zipcode=90210

Parsing the Data: Round 2

A few problems are still evident in this data. It's in a fairly random order, the two magazines indicated by the checkboxes in our form have been concatenated into one zines variable by our use of the appending operator, and some characters are still in the form of HEX codes.

The random order results from the manner in which the associative array hash tables are stored by Perl. We could have them listed in alphabetical order by sorting the keys in our foreach loop (not that alphabetical order is necessarily any more helpful in this case).

foreach (sort keys %input)

In cases such as the zines where it's possible to have more than one value per variable, we need a method of separating those values. We'll do this when we're building the associative array by checking to see if a particular variable is already defined. If so, we'll throw a comma (,) on the end of the value before we append the next value.

$input{$name} .=',' if (defined($input{$name}));

The HEX codes are listed in the URL encoded data using the format %xx where xx is a hexadecimal number. We convert sequences like this into characters by using an evaluated substitution.

$input[$i]=~ s/%(..)/pack("c",hex($1))/ge;

Implementing these three changes, the output now looks like this:

address=520 Main St. #204
applicant=Ken Hunt
card_number=123456789
city=Beverly Hills
country=USA
email=ken.hunt@westbevhigh.edu
gift=TriCorder
payment_method=Frontiers Credit Card
state=CA
suggestions=Can you offer "Asteroid Living for Kids" Magazine?
zines=Cooking With Soylent Green,Asteroid Living
zipcode=90210

Accepting Forms by METHOD=GET

Let's not forget that the codes we have written in the preceding will work with any set of data posted from a form via METHOD=POST. It's not difficult to adjust our code so that either POST or GET can be used.

Remembering our environment variables, the method being used to submit the form is stored in the environment variable REQUEST_METHOD. For METHOD=GET, the data passed by the form is displayed in the URL following a ?, and the data itself is stored in the environment variable QUERY_STRING.

Putting all of our changes together (see Listing 22.2), we have a program that can be used to parse the data from any form and display it to the screen.


Listing 22.2. A general use form parser.
#!/usr/bin/perl
print "Content-type: text/html\n\n";

if ($ENV{'REQUEST_METHOD'} eq "GET") {
$input=$ENV{'QUERY_STRING'};
}
elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN,$input,$ENV{'CONTENT_LENGTH'});
}
else {
print('Request method Unknown');
exit;
}

@input=split (/&/,$input);

foreach $i (0 .. $#input) {
$input[$i]=~ s/\+/ /g;
$input[$i]=~ s/%(..)/pack("c",hex($1))/ge;
($name, $value)=split(/=/,$input[$i],2);
$input{$name} .='~' if defined($input{$name});
$input{$name} .=$value;
}

print <<EOT;
<HTML><HEAD>
<TITLE>Order Output</TITLE>
</HEAD>

<BODY>
EOT

foreach (sort keys %input) {
print "$_=$input{$_}<br>\n";
}

print <<EOT;
</BODY>
</HTML
>
EOT

Note
If we had been using the available cgi-lib.pl library, we could have written a program quite similar to the general form parser and much smaller by writing
#!/usr/bin/perl
require "cgi-lib.pl";

&PrintHeader;
&ReadParse(*input);
&HtmlTop("Order Form Output");
&PrintVariables(% input);
&HtmlBot
But we wouldn't have learned very much about Perl. The cgi-lib.pl library is a powerful suite of tools and makes the programmer's job a whole lot easier. Once you have a handle on how Perl programs work, I highly recommend using them. To find out more about the powerful and ever-growing cgi-lib.pl library, visit the cgi-lib.pl Web site at
http://www.bio.com.ac.uk/cgi-lib/

Checking for Errors

At the moment, we still really don't have a processed order form. All we have is a method of displaying form output on the screen. What we really need is to get that information off to a human being who processes orders and collects money.

In order to save that person time, though, we should make sure that the information in the order form is as correct and complete as possible. For instance, you should check to make sure that all of the fields have been filled in and that the correct number of digits has been entered for the type of credit card the customer has indicated. This is the point in the development of this program where it becomes specific to the form we are parsing. As far as error checking goes, every form will have different needs.

In our order form, there are not very many boxes that are optional information; each box must contain data, except for the card_number box, which needs to be filled in only if the customer is using a credit card. If it does contain information, it must be in a specific form.

First, let's just record any empty data fields. We will check for empties while creating the associative array. If a field is empty, we will add the name of the field to an errors array (@errors).

push (@errors,"$name") if $value eq '';

Then, if there are errors, we will divert to a subroutine that will tell the user which information is missing and ask the user to go back and enter the missing data:

if ($#errors !=-1) {
&printerrors(*errors)
}
else {
foreach (sort keys %input) {
print "$_=$input{$_}<br>\n";
}
}

Note
Note that if an array is empty, the size of that array, in this case $#errors, will be -1, not zero. $#array_name is not a count of the elements in @array_name but rather the index of the last element in @array_name. Because Perl indexes arrays starting at element 0, an array with a single element would produce $#array_name=0. Further note that *errors is the glob of errors variables; therefore, any variable named "errors" whether it be a scalar array or associative array will be included (@errors, $errors, $#errors, %errors, and so on).

This is the printerrors subroutine. The special variable @_ contains the variable list sent to the subroutine. local assigns the variables locally. local(*errors) could have been local(*foo) or local(*bar) or anything else as long as the naming and use of local variables is consistent within this block.

sub printerrors {
local (*errors)=@_;

print <<EOT;
<h2>Your Order could not be processed because the following
information was either not supplied or was in an incorrect format.<h2>
<b>
EOT

print join('<br>',@errors), "</b><br>\n";
print "Please go back and complete the order form.";
}

Because we are capturing all empty data lines, an error will be reported if nothing is entered in the card_number cell, even if the customer has indicated payment will be made by cash or check. Let's check this cell by itself to make sure that an error isn't reported where one is not and that even if there is information in the cell, it conforms to the proper format for the indicated credit card. The method we are currently using also will not capture unentered data for radio buttons or checkboxes where none are checked. This is because in these cases, no information, not even the variable name, has been sent by the form.

Embedding Information in the Form

Tip
Here's one of my personal secrets. In order to make things easier on myself when it comes to sorting and storing information, I often embed some information in the form itself.
This is a trick I have found particularly useful time and time again. Here, in the value field for payment_type, I have included the name of the method of payment and the length of the card_number. This makes it much easier to check if the length of the card_number field is correct. You can also use this technique to embed information like price, model number, credit limits, or any other data you might need.

<b>Payment Method:</b>
<br>
<INPUT TYPE="radio" NAME="payment_method"
VALUE="FooBar_Charge_Card 8" chECKED>FooBar Charge Card<br>
<INPUT TYPE="radio" NAME="payment_method"
VALUE="Frontiers_Credit_Card 6">Frontiers Credit Card<br>
<INPUT TYPE="radio" NAME="payment_method" VALUE="COD 0">C.O.D.<br>
<INPUT TYPE="radio" NAME="payment_method" VALUE="Check 0">Check<br>
<INPUT TYPE="radio" NAME="payment_method"
VALUE="Money_Order 0">Money Order<br>

The information we have embedded in the value for payment_method is parsed out and placed into the variables $method and $number_size. Using Perl's powerful pattern matching by regular expression features (\w+) matches an entire word and (\d) matches a single digit.

if ($name eq 'card_number') {
($method, $number_size)=$input{payment_method}=~ /(\w+) (\d)/;
if ($value eq '') {
push (@errors,$name) if ($number_size > 0);
}
else {
push (@errors,$name) if ($number_size=0);
push (@errors,$name) if (length($value) !=$number_size);
}
}
else {
push (@errors,$name) if $value eq '';
}

push (@errors,'zines') unless defined $input{zines};

What to Do with All This Data?

Now that we are reasonably sure we have all the data we need in a correct form, we have to decide what to do with it. There are three possible ways to handle the data: display it to the screen, e-mail it to someone who handles orders, or save the results to a file. We will do all three.

This subroutine prints the order information to the screen in a nice little form letter thanking the customer for their order:

sub printorder  {
local (*input)=@_;
print <<EOT;
<h2>Thank you $input{applicant}.</h2>
The following order has been placed. Thank you for shopping the Frontier.
<pre>
<b>Address:</b>

<b>$input{applicant}</b>
$input{address}
$input{city}, $input{state}
$input{zipcode}
$input{country}<p>
<b>email:</b> $input{email}

<b>Magazines Ordered:</b> $input{zines}
<b>Free Gift:</b> $input{gift}
<b>Payment by:</b> $input{payment_method}, $input{card_number}<p>

<b>Comments:</b>
$input{suggestions}<br>

</pre>

<p>
EOT

}

This subroutine uses the sendmail feature found on UNIX systems to e-mail the order form to the person responsible for new orders. There are two things in particular you should note about this subroutine.

The first is that it is necessary to place a backslash (\) before the @ symbol in the e-mail address of the recipient because Perl interprets @ as the beginning of an array variable.

The second is the method by which sendmail is called. The method used here pipes output into the sendmail program directly. For security reasons, this is greatly preferable to calling sendmail by using the system function. More on this in the next section.

sub emailorder {
local(*input)=@_;
$neworders="ken.hunt\@anadas.com";

open (MAIL, "|/usr/sbin/sendmail -t ");
print MAIL <<EOM;
To: $neworders
Subject: Order from Website

The following order has been submitted:
Name:     $input{applicant}
Address:  $input{address}
          $input{city}, $input{state}
          $input{zipcode}
          $input{country}

email:    $input{email}

'Zines:   $input{zines}
Free Gift:$input{gift}

Paying By:$input{payment_method}, $input{card_number}

Comments: $input{suggestions}

EOM
close (MAIL);
}

The Things You Keep

As long as we are collecting all this valuable information, we might as well save some of it. One useful thing to save might be a list of names and e-mail addresses of people who have filled out on-line orders so we could send them updates when we have new products to offer. It's always a good idea to save data like this in either tab or comma separated format because those formats are widely supported by spreadsheet and database programs for reading in information.

The following code snippet can be inserted any time after you have read in the data.

open (OUTFILE, ">>email_list.txt");
print OUTFILE "$input{applicant},$input{email}\n";
close (OUTFILE);

Listing 22.3 shows the order processor.


Listing 22.3. Our order processor with all features implemented.
#!/usr/bin/perl
print "Content-type: text/html\n\n";


if ($ENV{'REQUEST_METHOD'} eq "GET") {
$input=$ENV{'QUERY_STRING'};
}
elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN,$input,$ENV{'CONTENT_LENGTH'});
}
else {
print('Request method Unknown');
exit;
}

@input=split (/&/,$input);

foreach $i (0 .. $#input) {
$input[$i]=~ s/\+/ /g;
$input[$i]=~ s/%(..)/pack("c",hex($1))/ge;
($name, $value)=split(/=/,$input[$i],2);
$input{$name} .=',' if defined($input{$name});
$input{$name} .=$value;
if ($name eq 'card_number') {
($method, $number_size)=
$input{payment_method}=~ /(\w+) (\d)$/;
$method=~ s/\_/ /g;
$input{payment_method}=$method;
if ($value eq '') {
push (@errors,$name) if ($number_needed eq 'yes');
}
else {
push (@errors,$name) if ($number_needed eq 'no');
push (@errors,$name) if (length($value) !=$number_size);
}
}
else {
push (@errors,$name) if $value eq '';
}
}

push (@errors,'zines') unless defined $input{zines};

print <<EOT;
<HTML><HEAD>
<TITLE>Order Output</TITLE>
</HEAD>

<BODY>
EOT


if ($#errors !=-1) {
&printerrors(*errors);
}
else {
&printorder(*input);
&emailorder(*input);
}

open (OUTFILE, ">>email_list.txt");
print OUTFILE "$input{applicant}, $input{email}\n";
close (OUTFILE);

print <<EOT;
</BODY>
</HTML>
EOT

sub printerrors {
local (*errors)=@_;
print <<EOT;
Your Order could not be processed because the following
Information was either not supplied or was in an incorrect format.
<p><b>
EOT

print join('<br>',@errors), "<p></b>\n";

print "Please go back and complete the order form.";

}

sub printorder  {
local (*input)=@_;
print <<EOT;
<h2>Thank you $input{applicant}.</h2>
The following order has been placed. Thank you for shopping the Frontier.
<pre>
<b>Address:</b>

<b>$input{applicant}</b>
$input{address}
$input{city}, $input{state}
$input{zipcode}
$input{country}<p>
<b>email:</b> $input{email}

<b>Magazines Ordered:</b> $input{zines}
<b>Free Gift:</b> $input{gift}
<b>Payment by:</b> $input{payment_method}, $input{card_number}<p>

<b>Comments:</b>
$input{suggestions}<br>

</pre>

<p>
EOT

}

sub emailorder {
local(*input)=@_;
$neworders="ken.hunt\@anadas.com";

open (MAIL, "|/usr/sbin/sendmail -t ");
print MAIL <<EOM;
To: $neworders
Subject: Order from Website

The following order has been submitted:
Name:     $input{applicant}
Address:  $input{address}
          $input{city}, $input{state}
          $input{zipcode}
          $input{country}

email:    $input{email}

'Zines:   $input{zines}
Free Gift:$input{gift}

Paying By:$input{payment_method}, $input{card_number}

Comments: $input{suggestions}

EOM
close (MAIL);
}

Security Issues

Second only to the hysteria in the media about the power of the Internet and the World Wide Web is the paranoia concerning the vulnerability of information transmitted via the Internet. This paranoia is not without base; there are certain precautions that everyone should take and all of which CGI programmers should be aware. There are also some important issues concerning the security of CGI scripts.

Transaction Security

It seems that almost everyone is frightened that if they so much as think about their credit card number while on the Internet, within minutes it will probably be used by a score of hackers to phone Fiji. It seems strange to me that these very same people don't think twice about using their credit card at a gas station where it is just as vulnerable. In fact, it's a lot easier for a gas station attendant to steal your credit card number than it is for a hacker to steal your number over the Internet.

At the same time, it's always better to be safe than sorry. Some of the basic precautions you should take to ensure a maximum level of security are the following:

  • Run a server that supports RSA encryption.
  • Don't store customers' credit card numbers on your system in an insecure area and keep the file in a uuencoded format.
  • Don't e-mail or otherwise transmit secure data without using encryption.

CGI Security

Another aspect of security that is often overlooked is the security of CGIs themselves. By allowing the entire world to send input to our machines, we open ourselves to the possibility that they might try to send us some pretty nasty stuff. The most common way this is done is by sending unexpected UNIX shell commands that get access to the system through functions that interact with the shell itself.

The best way to avoid leaving your system open is to never trust that the data users send you is the information you expect. You should use the error checking techniques outlined in this chapter to keep a close eye on all the incoming information before you call dangerous applications such as sendmail.

For more information about security issues, check out Chapter 9, "Security."

For an excellent resource about CGI security on the Web, check out the following site:

http://www.cerf.net/~paulp/cgi-security

Summary

I remember one of my computer science professors once telling me that the vast majority of computer programming isn't about solving big problems; it's about solving a whole series of small problems. That comment is certainly very true of the problem we tackled in this chapter.

In a nutshell, simple order entry consists of the following:

  • Getting data from a Web page
  • Parsing that data to make it readable
  • Checking the data for obvious errors or omissions
  • Sending the parsed data to the right person or file
  • Thanking the user for his or her input

Perl makes these tasks, which mainly focus on text processing, very simple. In particular, there are three features in Perl of which we have made extensive use:

  • The s function, Perl's powerful substitution command, used throughout this chapter to parse data.
  • Associative arrays, Perl's answer to "linked lists" used in languages like C. They allow easy reference to the elements within an array by allowing us to call them by name.
  • Printing lists was exemplified in this chapter by the print <<EOT; statements. This feature makes generating HTML on-the-fly quite easy.

You should now have a good grasp on all of the major issues surrounding simple order entry.



Хостинг от uCoz