Chapter 4. Cookies

Lincoln D. Stein

A cookie is just a name=value pair, much like the named parameters used in the CGI query string and discussed in CGI Programming. When a web server or CGI script wants to save some state information, it creates a cookie or two and sends them to the browser inside the HTTP header. The browser keeps track of all the cookies sent to it by a particular server, and stores them in an on-disk database so that the cookies persist even when the browser is closed and reopened later. The next time the browser connects to a web site, it searches its database for all cookies that belong to that server and transmits them back to the server inside the HTTP header.

Cookies can be permanent or set to expire after a number of hours or days. They can be made site-wide, so that the cookie is available to every URL on your site, or restricted to a partial URL path. You can also set a flag in the cookie so that it’s only transmitted when the browser and server are communicating via a secure protocol such as SSL. You can even create promiscuous cookies that are sent to every server in a particular Internet domain.

The idea is simple but powerful. If a CGI script needs to save a small amount of state information, such as the user’s preferred background color, it can be stored directly in a cookie. If lots of information needs to be stored, you can keep the information in a database on the server’s side and use the cookie to record a session key or user ID. Cookies now have their own standard (RFC 2109) and are accepted by all major browsers.

Creating Cookies

So how do you create a cookie? If you use the CGI.pm library, it’s a piece of cake:

0 #!/usr/bin/perl
1
2 use CGI qw(:standard);
3
4 $cookie1 = cookie( -name => 'regular',
5                   -value => 'chocolate chip');
6 $cookie2 = cookie( -name => 'high fiber',
7                   -value => 'oatmeal raisin');
8 print header(-cookie => [$cookie1, $cookie2]);

Line 2 loads the CGI library and imports the :standard set of function calls. This allows you to call all of the CGI object’s methods without explicitly creating a CGI instance—a default CGI object is created for you behind the scenes. Lines 4 through 7 create two new cookies using the CGI cookie method. The last step is to incorporate the cookies into the document’s HTTP header. We do this in line 8 by printing out the results of the header method, passing it the -cookie parameter along with an array reference containing the two cookies.

When we run this script from the command line, the result is:

Set-cookie: regular=chocolate%20chip
Set-cookie: high%20fiber=oatmeal%20raisin
Content-type: text/html

As you can see, CGI.pm translates each space into %20, as the HTTP cookie specification prohibits whitespace and certain other characters such as the semicolon. (It also places an upper limit of a few kilobytes on the size of a cookie, so don’t try to store the text of Hamlet in one.) When the browser sees these two cookies it squirrels them away and returns them to your script the next time it needs a document from your server.

Retrieving Cookies

To retrieve the value of a cookie sent to you by the browser, use cookie without a -value parameter:

0 #!/usr/bin/perl
1
2 use CGI qw(:standard);
3
4 $regular    = cookie('regular');
5 $high_fiber = cookie('high fiber');
6
7 print header(-type => 'text/plain'),
8 "The regular cookie is $regular.\n",
9 "The high fiber cookie is $high_fiber.";

In this example, lines 4 and 5 retrieve the two cookies by name. Lines 7 through 9 print out an HTTP header (containing no cookie this time), and two lines of text. The output of this script, when viewed in a browser, would be:

The regular cookie is chocolate chip.
The high fiber cookie is oatmeal raisin.

The cookie method is fairly flexible. You can save entire arrays as cookies by giving the -value parameter an array reference:

$c = cookie( -name => 'specials',
            -value => ['oatmeal', 'chocolate chip','alfalfa']);

Or you can save and restore entire hashes:

$c = cookie(-name => 'prices', -value => {        'oatmeal' => '$0.50',
                                           'chocolate_chip' => '$1.25',
                                                  'alfalfa' => 'free'   });

Later you can recover the two cookies this way:

@specials = cookie('specials');
%prices   = cookie('prices');

By default, browsers will remember cookies only until they exit, and will only send the cookie out to scripts with a URL path that’s similar to the script that generated it. If you want them to remember the cookie for a longer period of time, you can pass an -expires parameter containing the cookie’s shelf life to the cookie function. To change the URL path over which the cookie is valid, pass its value in -path:

$c = cookie(-name => 'regular',           -value => 'oatmeal raisin',
            -path => '/cgi-bin/bakery', -expires => '+3d');

This cookie will expire in three days’ time (+3d). Other cookie parameters allow you to adjust the domain names and URL paths that trigger the browser to send a cookie, and to turn on cookie secure mode. The -path parameter shown here tells the browser to send the cookie to every program in /cgi-bin/bakery.

Example 4-1 is a CGI script called configure.cgi that generates pages such as Figure 4-1. When you call this script’s URL, you are presented with the fill-out form shown above. You can change the page’s background color, the text size and color, and even customize it with your name. The next time you visit this page (even if you’ve closed the browser and come back to the page weeks later), it remembers all of these values and builds a page based on them.

Example 4-1. The configure.cgi script
00 #!/usr/bin/perl
01
02 use CGI qw(:standard :html3);
03
04 # Some constants to use in our form.
05 @colors = qw/aqua black blue fuchsia gray green lime maroon navy olive
                purple red silver teal white yellow/;
06 @sizes = ("<default>", 1..7);
07
08 # Recover the "preferences" cookie.
09 %preferences = cookie('preferences');
10
11 # If the user wants to change the name or background color, they can
12 foreach ('text', 'background', 'name', 'size') {
13     $preferences{$_} = param($_) || $preferences{$_};
14 }
15
16 # Set some defaults
17 $preferences{background} = $preferences{background} || 'silver';
18 $preferences{text}       = $preferences{text} || 'black';
19
20 # Refresh the cookie so that it doesn't expire.
21 $the_cookie = cookie( -name => 'preferences',
22                      -value => \%preferences,
23                       -path => '/',
24                    -expires => '+30d');
25 print header(-cookie => $the_cookie);
26
27 # Adjust the title to incorporate the user's name, if provided.
28 $title = $preferences{name} ? "Welcome back, $preferences{name}!"
                               : "Customizable Page";
29
30 # Create the HTML page, controlling the background color and font size.
31 #
32 print start_html(  -title => $title,
33                  -bgcolor => $preferences{background},
34                     -text => $preferences{text});
35
36 print basefont({SIZE=>$preferences{size}}) if $preferences{size} > 0;
37
38 print h1($title),<<END;
39 You can change the appearance of this page by submitting
40 the fill-out form below. If you return to this page any time
41 within 30 days, your preferences will be restored.
42 END
43 ;
44 # Create the form.
45 print hr,
46       start_form,
47
48       "Your first name: ",
49       textfield(   -name => 'name',
50                 -default => $preferences{name},
51                    -size => 30), br,
52       table(
53             TR(
54                td("Preferred"),
55                td("Page color:"),
56                td(popup_menu(   -name => 'background',
57                               -values => \@colors,
58                              -default => $preferences{background})
59                  )
60               ),
61             TR(
62                td(''),
63                td("Text color:"),
64                td(popup_menu(   -name => 'text',
65                               -values => \@colors,
66                              -default => $preferences{text})
67                  )
68               ),
69             TR(
70                td(''),
71                td("Font size:"),
72                td(popup_menu(   -name => 'size',
73                               -values => \@sizes,
74                              -default => $preferences{size})
75                  )
76               )
77            ),
78       submit(-label => 'Set preferences'),
79       end_form,
80       hr;
81
82 print a({HREF => "/"}, 'Go to the home page');
A cookie-aware web page
Figure 4-1. A cookie-aware web page

This script recognizes four CGI parameters used to change the configuration:

background

Set the background color.

text

Set the text color.

size

Set the size to the indicated value (1–7).

name

Set the username.

Usually these parameters are sent to the script via the fill out form that it generates, but you could set them from within a URL this way:

/cgi-bin/configure.pl?background=silver&text=blue&name=Stein

Let’s walk through the code. Line 2 imports the CGI library, bringing in both the standard method calls and a number of methods that generate HTML3-specific tags. Next we define a set of background colors and sizes. The choice of colors may seem capricious, but it’s not: These are the background colors defined by the HTML 3.2 standard, and they’re based on the original colors used by the IBM VGA graphics display.

Line 9 is where we recover the user’s previous preferences, if any. We use the cookie method to fetch a cookie named “preferences”, and store its value in a like-named hash.

In lines 12 through 14, we fetch the CGI parameters named text, background, name, and size. If any of them are set, it indicates that the user wants to change the corresponding value saved in the browser’s cookie. We store changed parameters in the %preferences hash, replacing the original values.

Line 17 and 18 set the text and background colors to reasonable defaults if they can’t be found in either the cookie or the CGI script parameters.

Lines 21 through 25 generate the page’s HTTP header. First, we use the cookie method to create the cookie containing the user’s preferences. We set the expiration date for the cookie for 30 days in the future so that the cookie will be removed from the browser’s database if the user doesn’t return to this page within that time. We also set the optional -path parameter to /. This makes the cookie valid over our entire site so that it’s available to every URL the browser fetches. Although we don’t take advantage of this yet, it’s useful if we later decide that these preferences should have a site-wide effect. Lastly, we emit the HTTP header with the -cookie parameter set.

In lines 30 to 36 we begin the HTML page. To make it personalizable, we base the page title on the user’s name. If it’s set, the title and level 1 header both become “Welcome back <name>!” Otherwise, the title becomes an impersonal “Customizable page.” Line 32 calls the start_html method to create the top part of the HTML page. It sets the title, the background color and the text color based on the values in the %preferences array. Line 36 sets the text size by calling the basefont method. This simply generates a <BASEFONT> HTML tag with an appropriate SIZE attribute.

Lines 38 and up generate the content of the page. There’s a brief introduction to the page, followed by the fill-out form used to change the settings. All the HTML is generated using CGI.pm “shortcuts,” in which tags are generated by like-named method calls. For example, the hr method generates the HTML tag <HR>. As shown in the first column in this series, we start the fill-out form with a call to start_form, create the various form elements with calls to textfield, popup_menu, and submit, and close the form with end_form.

When I first wrote this script, the popup menus and popup menus in the form didn’t line up well. Because all the elements were slightly different widths, everything was crooked. To fix this problem, I used the common trick of placing the form elements inside an invisible HTML3 table. Assigning each element to its own cell forces the fields to line up. You can see how I did this in lines 52 through 77, where I define a table using a set of CGI.pm shortcuts. An outer call to table generates the surrounding <TABLE> and </TABLE> tags. Within this are a series of TR methods, each of which generates a <TR> tag. (In order to avoid conflict with Perl’s built-in tr/// operator, this is one instance where CGI.pm uses uppercase rather than lowercase shortcut names.) Within each TR call, in turn, there are several td calls that generate the <TD> (table data) cells of the HTML table.

Fortunately, my text editor auto-indents nicely, making it easy to see the HTML structure.

On a real site, of course, you’d want the user’s preferences to affect all pages, not just one. This isn’t a major undertaking; many modern web servers now allow you to designate a script that preprocesses all files of a certain type. You can create a variation on the script shown here that takes an HTML document and inserts the appropriate <BASEFONT> and <BODY> tags based on the cookie preferences. Now, just configure the server to pass all HTML documents through this script, and you’re set.

In the next article, Doug MacEachern and I introduce mod_perl, a Perl interpreter embedded inside the Apache web server.

Get Web, Graphics & Perl/Tk Programming now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.