10.2.3 Using Tied Variables in CGI Programs

10.2.3  Using Tied Variables in CGI Programs

 Variables tied to modules that help store values in files can be used with a CGI program, if the need arises. We have seen in Section 10.2 that any type of variable can be tied to a module that implements how it behaves. In particular, as associative array or hash can be linked to a module such as NDBM_File or GDBM_File that ties the variable to a file.

   The following program creates an access counter for use on a Web page. Such a counter is not an uncommon sight on many Web pages. The CGI program counter.pl that follows is invoked with the name of a counter. The same program can keep track of many counter values. A counter’s name and its value are stored in a DBM database. In this program, it is a GDBM database although we can use another type of DBM file if we want. The name of the counter is used as a key. The program follows.

 Program 10.5

#!/usr/bin/perl -T
#counter.pl

use GD;
use CGI qw(:standard);;
use CGI::Carp qw(fatalsToBrowser);
use Untaint; 
use GDBM_File;

#######Set CGI size limit; disable file upload
$CGI::POST_MAX = 1024; #max 1024 bytes posts
$CGI::DISABLE_UPLOADS = 1; 

$query = new CGI;
$name_counter = $query->param(counter);
$name_counter = untaint(qr{^[\d\w.-]+$}, $name_counter);
tie %access, 'GDBM_File', "gdbmcounterdb", GDBM_WRCREAT, 0777;

$freq = $access{$name_counter};
$freq = untaint(qr{^\d+$}, $freq);
$freq++;
$access{$name_counter} = $freq;
$counter = sprintf("%06d", $freq);

untie %access;

#Find the number of digits in the counter
@digits = split(//, $counter);
$length = length($counter);

#Create a new GD image
$im = new GD::Image($length*14-2,20);
#Create colors by allocating RGB
$black = $im->colorAllocate(0, 0, 0);
$white = $im->colorAllocate(255, 255, 255);        
$blue = $im->colorAllocate(0,0,255);
#Create the GD image containing the counter's value
srand();
for ($i=0; $i < $length; $i++)
{ $im->string(gdLargeFont, 2+$i*14, 1+int(rand(3)), $digits[$i], $white);
  if($i < $length-1)
  { $im->filledRectangle(12+$i*14, 0, 13+$i*14, 20, $blue);
  }
}

# print the image to STDOUT, i.e., send to browser
print header("image/jpeg");
binmode STDOUT;
print $im->jpeg;
exit;

The program uses several modules: GD.pm to draw an image; CGI.pm to perform CGI-related tasks;

CGI::Carp.pm to handle errors that may arise during execution of a CGI program; and GDBM_File.pm to tie a hash to a GDBM database file. The CGI.pm module exports a set of functions and constants for performing the usual CGI-related tasks using the :standard keyword. A CGI program can produce many errors, such as syntax errors, run-time errors in the program’s execution, not printing the proper HTTP headers as the initial strings to STDOUT, etc. In particular, if the CGI program has some run-time error before the HTTP headers are printed to STDOUT, the associated error strings are
printed to STDOUT before the HTTP headers, causing trouble at the browser end. To ameliorate such situations, we use the line

 

use CGI::Carp qw(fatalsToBrowser);

 

so that fatal errors in the CGI program are printed to the browser. Without such help, a CGI program may become difficult to debug. One may need to look at the error log entries on the machine where the Web server is running as well. On a Linux machine, the usual location of the error log file is

/var/log/httpd/error_log for the Apache Web server. On a Macintosh OS X machine, the usual location of the error log file is /private/var/log/httpd/error_log. On a Windows machine, the location of the error log file is dependent on where the Web server is installed.    

The program starts by creating a new CGI object. It obtains the only argument sent to it called counter using the following line of code.

 

$name_counter = $query->param(counter);

 

The value is laundered or untainted. The argument is called $name_counter inside the program. Next, the program ties a GDBM file gdbmcounterdb to a hash called %access. The reader is requested to consult documentation on GDBM_File.pm to find out what arguments need to be passed to the tie command in the case of a GDBM file. In particular, GDBM_WRCREAT is an additional argument that specifies that the file should be readable and writeable, and should be created if it does not exist. The last argument provides the permission mode if the file is created.  

The gdbmcounterdb file contains key-value pairs of a hash. If there is a value corresponding to

$name_counter, it is accessed. The value is $freq. The value is incremented to indicate that the corresponding Web page has been accessed one more time, because the CGI program has been called. The new value is recorded in the hash, i.e., the newly incremented value placed in the GDBM file as well. The new value of the counter is written into a string $counter using sprintf. We assume the maximum length of the counter is six digits.

     We use the GD.pm module to create a graphic image on the fly. In this case, the graphic image is in JPEG format although it can be several other formats such as GIF, or PNG. The image contains the value of the appropriate counter in an attractive graphical format. The value of the counter has already been read from the GDBM file using the name as the key. Each digit of the number read is depicted in a small rectangle whose background is blue. The digit itself is an image in white. The numeric calculations in the for loop are needed to position the individual rectangles corresponding to each digit. The GD.pm module and a program that is very similar to the one discussed here is explained in
Section 5.7. Once the program has created an image corresponding to all the decimal digits in the value of the counter, the program creates an HTTP header using the following call.

 

print header("image/jpeg");

 

This header is printed on the STDOUT, and hence sent to the browser. Images are binary objects and the binary mode is ensured by using the following command.

 

binmode STDOUT;

 

The image is printed in a straightforward manner. 

There are several things to note when a DBM file is used in the context of a CGI program. A CGI program is invoked from a Web page, i.e., the CGI program is run by a Web server. Usually a Web server does not have permission to do much. As a result, for the CGI program to run, it must be executable by everyone. The DBM file must be readable and writeable by everyone so that a CGI program can read the value and update it. When tie creates a DBM file, the permission specified is reduced by the umask, the file created may not be readable and writeable by everyone. Hence, the person installing the CGI program has to manually check to see that the DBM file created has the appropriate permissions. If not, the file may have to be created and its permission mode changed by hand. In particular, one should make sure of
the following.

    The CGI program must be executable by everyone. All directories above it must be readable and executable by everyone. This is especially true on Unix or Unix-like machines such as Linux or Macintosh OS X.

    The DBM file must be readable and writeable by everyone. If necessary, one should create the DBM file by hand. The file may need an extension like .db. One needs to find out if the extension is needed by either reading documentation or by experimentation. If necessary, one also needs to change the permission modes on the DBM file by hand so that it is readable and writeable by everyone.   

    The HTTP header must be produced right. There cannot be any run-time errors generated by the program before the HTTP header is produced because the errors are normally printed to STDOUT as well. Thus, it makes sense to print the header generation line right on top of the program, at least during development. Also, one should make sure that the errors are printed to the browser. One should also run the CGI program by hand on the command-line to see if it has syntax or run-time errors. When run by hand, CGI parameters can be passed on the terminal as name=value pairs.   If there are several pairs, one can enter a pair on a line and use Control-C (Control-Z in Windows) to indicate that all
pairs have been entered. One can also use the -c flag to get just a syntax check of the program without execution, and use the -w to get as much warning as possible. Thus, the top line of the program can be changed to

 

 #!/usr/bin/perl -cw

 

for purposes of testing. If the program checks out for syntax and also runs without error from the command-line, but still gives an error when used as a CGI program, one should check the error log kept by the HTTP server. If using an Apache server,the error log is usually stored in the file such as /var/log/httpd/error_log on a Red Hat Linux machine,

/private/var/log/httpd/error_log on a Macintosh OS X machine running BSD Unix.

A call to the CGI program, discussed earlier in this section, on a Web page may look like the following.

 

  <tr> <td>

   Number of visitors (<em>since March 18. 1997</em>):

   <IMG

   SRC="http://pikespeak.uccs.edu/cgi-bin/kalita/counter/counter.pl?counter=kalita-index-page">

   <p>

 

Here, kalita-index-page is the name of the counter. On another Web page, a call may be the following.

 

   Number of visitors (<em>since February 22, 2002</em>):

   <IMG

   SRC="http://pikespeak.uccs.edu/cgi-bin/kalita/counter/counter.pl?counter=testcounter1">

 

 Here, testcounter1 is the name of the second counter. Thus, the same CGI program may be used in as many Web pages as we want; each counter may have a different name. Figure 10.24 shows a portion of the first Web page with the counter value in the form of a JPEG image. It is an in-line image. Thus, we see that the decimal counter value read from the DBM file is automatically converted into an image by the CGI program, and this image sent to STDOUT in binary mode by the CGI program, is displayed on the browser as an attractive image. The CGI program discussed above reads a GDBM file, obtains the value of a certain named counter, updates the values, and creates an image with the counter’s value. The CGI program is run from a Web page. Thus, the CGI program is
actually being run by a Web server.

 

Figure 10.24:  A Counter Image Produced by a CGI Program Using GD.pm in a Web Page

We have a DBM file that contains the values of one or more counters indexed by the names of the counters. Below, we discuss three very simple programs that can be run by the administrator of a Web site to work with the counter database. The program read_counter.pl allows one to see the current values stored in the GDBM file. The program follows.  

 Program 10.6

#!/usr/bin/perl
#read_counters.pl

use GDBM_File;

tie %access, 'GDBM_File', "gdbmcounterdb", GDBM_WRCREAT, 0777;
foreach $key(keys %access){
    print "counter $key has the value $access{$key}\n";
}

untie (%access);

 

An example run of the read_counters.pl program is given below.

 

counter kalita-index-page has the value 10024

counter testcounter1 has the value 4

 

When for some reason, a counter is no longer used, the remove_counter.pl program can be used to remove it from the DBM file. The program is given below.

 

 Program 10.7

#!/usr/bin/perl
#remove_counter.pl

use GDBM_File;

$counter_name = $ARGV[0];
tie %access, 'GDBM_File', "gdbmcounterdb", GDBM_WRCREAT, 0777;
delete $access{$counter_name}; 
print "Deleted $counter_name\n";
untie (%access);

 

An example run is given below.

 

%remove_counter.pl testcounter1

Deleted testcounter1

 

Here, % is the Unix system prompt. After remove_counter.pl is run, we can run read_counters.pl again, and see the following.

 

%read_counters.pl

counter kalita-index-page has the value 10024

 

There is also a program called init_counter.pl that can be used to initialize a new counter or change its value. A counter’s value may need to be initialized for reasons such as several counters on several Web pages being merged into one Web page and hence, several counters being added up to make the value of the new counter; the CGI program being moved to a new server so that the DBM file needs to be created anew on the new server, but the old counter values need to retained; for testing purposes, etc.