Recipe 19.9 Managing Cookies

19.9.1 Problem

You want to get or set a cookie to help manage sessions or user preferences.

19.9.2 Solution

Using CGI.pm, retrieve an existing cookie like this:

$preference_value = cookie("preference name");

To prepare a cookie, do this:

$packed_cookie = cookie( -NAME    => "preference name",
                         -VALUE   => "whatever you'd like",
                         -EXPIRES => "+2y");

To save a cookie back to the client browser, you must include it in the HTTP header, probably using either the header or redirect functions:

print header(-COOKIE => $packed_cookie);

19.9.3 Discussion

Cookies store information on the client's browser. If you're using Netscape under Unix, you can inspect your own ~/.netscape/cookies file, although this doesn't show your current cookies. It holds only those cookies present when you last exited the browser. Think of them as per-application user preferences or a way to help with transactions. Benefits of cookies are that they can be shared between several different programs on your server, and they persist even across browser invocations.

However, cookies can be used for dubious purposes like traffic analysis and click tracing. This makes some folks very nervous about who is collecting their personal data and what use will be made of their page viewing habits. Cookies don't travel well, either. If you use a browser at home or in someone else's office, it won't have the cookies from the browser at your office. For this reason, do not expect every browser to accept the cookies you give it. As if that weren't bad enough, browsers can't guarantee they'll keep cookies around forever. Here's an excerpt from the HTTP State Management Mechanism RFC (number 2109):

Because user agents have finite space in which to store cookies, they may also discard older cookies to make space for newer ones, using, for example, a least-recently-used algorithm, along with constraints on the maximum number of cookies that each origin server may set.

While in theory a browser can delete cookies at any time, a browser that freely did so with session cookies or with recently used longer-term cookies would quite annoy its users.

Due to their unreliability, you should probably not place too much faith in cookies. Use them for simple, stateful transactions, and avoid traffic analysis for reasons of privacy.

Example 19-7 is a complete program that remembers the user's last choice.

Example 19-7. ic_cookies
  #!/usr/bin/perl -w
  # ic_cookies - sample CGI script that uses a cookie
  use CGI qw(:standard);
  
  use strict;
  
  my $cookname = "favorite ice cream";
  my $favorite = param("flavor");
  my $tasty    = cookie($cookname) || 'mint';
  
  unless ($favorite) {
      print header( ), start_html("Ice Cookies"), h1("Hello Ice Cream"),
            hr( ), start_form( ),
              p("Please select a flavor: ", textfield("flavor",$tasty)),
                end_form( ), hr( );
      exit;
  }
  
  my $cookie = cookie(
                  -NAME    => $cookname,
                  -VALUE   => $favorite,
                  -EXPIRES => "+2y",
              );
  
  print header(-COOKIE => $cookie),
        start_html("Ice Cookies, #2"),
        h1("Hello Ice Cream"),
        p("You chose as your favorite flavor `$favorite'.");

A more extensible approach is to send a single cookie containing a unique semi-random session identifier (such as sprintf "%x-%x-%x", time( ), $$, int rand 0x10000) and use that to map to a file (or database record) on the server that maintains any state you associate with that session. Just be sure that accessing that file or record doesn't create a race condition, as described in Recipe 19.4. Also be sure to occasionally purge old session files on the server, and gracefully cope with session cookies that no longer exist on the server. There are already Perl modules for intelligently implementing just this kind of server-side data, such as CGI::Session.

19.9.4 See Also

The documentation for the standard CGI module; the documentation for the CGI::Session module; Recipe 19.4; RFC 2109