A third way of maintaining state is to use Netscape persistent cookies. One of the features of the Netscape Navigator browser is the capability to store information on the client side. It does this by accepting a new Set-Cookie header from CGI programs, and passing that information back using a HTTP_COOKIE environment variable. We won't show a complete example, but we'll illustrate briefly.
A program that stores the information on the client side might begin as follows:
#!/usr/local/bin/perl ($key, $value) = split(/=/, $ENV{'QUERY_STRING'}); print "Content-type: text/html", "\n"; print "Set-Cookie: $key=$value; expires=Sat, 26-Aug-95 15:45:30 GMT; path=/; domain=bu.edu", "\n\n";
The cookie header requires the key/value information to be encoded.
. . . exit (0);
The Set-Cookie header sets one cookie on the client side, where a key is equal to a value. The expires attribute allows you to set an expiration date for the cookie. The path attribute specifies the subset of URLs that the cookie is valid for. In this case, the cookie is valid and can be retrieved by any program served from the document root hierarchy. Finally, the domain attribute sets the domain for which the cookie is valid. For example, say a cookie labeled "Parts" is set with a domain attribute of "bu.edu". If the user accesses a URL in another domain that tries to retrieve the cookie "Parts," it will be unable to do so. You can also use the attribute secure to instruct the browser to send a cookie only on a secure channel (e.g., Netscape's HTTPS server). All of these attributes are optional.
Now, how does a program access the stored cookies? When a certain document is accessed by the user, the browser will send the cookie information--provided that it is valid to do so--as the environment variable HTTP_COOKIE. For example, if the user requests a document for which the cookie is valid before the cookie expiration date, the following information might be stored in HTTP_COOKIE:
Full%20Name=Shishir%20Gundavaram; Specification=CGI%20Book
Cookies are separated from the next by the " ; " delimiter. To decode this information and place it into an associative array, we can use the following subroutine:
sub parse_client_cookies { local (*COOKIE_DATA) = @_; local (@key_value_pairs, $key_value, $key, $value); @key_value_pairs = split (/;\s/, $ENV{'HTTP_COOKIE'}); foreach $key_value (@key_value_pairs) { ($key, $value) = split (/=/, $key_value); $key =~ tr/+/ /; $value =~ tr/+/ /; $key =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg; if (defined($FORM_DATA{$key})) { $FORM_DATA{$key} = join ("\0", $FORM_DATA{$key}, $value); } else { $FORM_DATA{$key} = $value; } } }
This subroutine is very similar to the one we have been using to decode form information. You can set more than one cookie at a time, for example:
print "Set-Cookie: Computer=SUN; path=/", "\n"; print "Set-Cookie: Computer=AIX; path=/images", "\n";
Now, if the user requests the URL in the path /images, HTTP_COOKIE will contain:
Computer=SUN; Computer=AIX
There are a couple of disadvantages with this client-side approach to storing information. First, the technique only works for Netscape Navigator browsers. Second, there are restrictions placed on the cookie size and number of cookies. The information contained in each cookie cannot exceed 4KB, and only 20 cookies are allowed per domain. A total of 300 cookies can be stored by each user.