Team LiB
Previous Section Next Section

Navigation Assistance with Cookies

Browser cookies are the subject of much myth and misunderstanding. While popular wisdom has it that they’re detrimental to user privacy, the truth is that while cookies can certainly be abused, they’re an almost indispensable tool when it comes to Web programming.

The main value of cookies comes from the fact that HTTP is a stateless protocol. There is no way to maintain connection or user information across multiple requests to the same server by the same client (unless you wish to keep state on the server—for example, using a database). Netscape addressed this issue in the early stages of the Web with the introduction of cookies. A cookie is a small piece of text data set by a Web server that resides on the client’s machine. Once it’s been set, the client automatically returns the cookie to the Web server with each request that it makes. This allows the server to place values it wishes to “remember” in the cookie, and have access to them when creating a response.

During each transaction, the server has the opportunity to modify or delete any cookies it has already set and also has, of course, the ability to set new cookies. The most common application of this technology is the identification of individual users. Typically, a site will have a user log in and will then set a cookie containing the appropriate username. From that point on, whenever the user makes a request to that particular site, the browser sends the username cookie in addition to the usual information to the server. The server can then keep track of which user it is serving pages to and modify its behavior accordingly. This is how many Web-based e-mail systems “know” that you are logged in.

There are several parts to each cookie, many of them optional. The syntax for setting cookies is

name=value [; expires=date] [; domain=domain] [; path=path] [; secure]

The tokens enclosed in brackets are optional and may appear in any order. The semantics of the tokens are described in Table 16-1.

Table 16-1: The Anatomy of a Cookie

Token

Description

Example

name=value

Sets the cookie named name to the string value.

username=fritz

expires=date

Sets the expiration date of the cookie to date. The date string is given in Internet standard GMT format. To format a Date to this specification you can use the toGMTString() method of Date instances.

expires=Sun, 01-Dec-2002 08:00:00 GMT

domain=domain

Sets the domain for the cookie to domain, which must correspond (with certain flexibility) to the domain of the server setting the cookie. The cookie will be returned only when making a request of this domain.

domain=www.javascriptref.com

path=path

String indicating the subset of paths at the domain for which the cookie will be returned.

path=/users/thomas/

secure

Indicates that the cookie is only to be returned over a secure (HTTPS) connection.

secure

Cookies that are set without the expires field are called session cookies. They derive their name from the fact that they are kept for only the current browser session; they are destroyed when the user quits the browser. Cookies that are not session cookies are called persistent cookies because the browser keeps them until their expiration date is reached, at which time they are discarded.

Note 

Some people refer to session cookies as memory cookies and persistent cookies as disk cookies.

When a user connects to a site, the browser checks its list of cookies for a match. A match is determined by examination of the URL of the current request. If the domain and path in a cookie match the given URL (in some loose sense), the cookie’s name=value token is sent to the server. If multiple cookies match, the browser includes each match in a semicolon-separated string. For example, it might return

username=fritz; favoritecolor=green; prefersmenus=yes

Be aware that we are glossing over some subtleties with regard to how the browser determines a match. Full details are found at http://home.netscape.com/newsref/std/cookie_spec.html. Several RFCs (2109, 2965, and especially 2964) also have bearing on cookie technology, but the Netscape specification is the one widely used.

Cookies in JavaScript

One nice thing about cookies is that nearly every browser in existence with JavaScript support also provides scripts access to cookies. Cookies are exposed as the cookie property of the Document object. This property is both readable and writeable.

Setting Cookies

When you assign a string to document.cookie, the browser parses it as a cookie and adds it to its list of cookies. For example,

document.cookie = "username=fritz; expires=Sun, 01-Dec-2005 08:00:00 GMT;
 path=/home";

sets a persistent cookie named username with value “fritz” that expires in 2005 and will be sent whenever a request is made for a file under the “/home” directory on the current Web server. Whenever you omit the optional cookie fields (like secure or domain), the browser fills them in automatically with reasonable defaults—for example, the domain of the current URL and path to the current document. It is possible, but not recommended, to set multiple cookies of the same name with differing paths. If you do so, then both values may be returned in the cookie string, and if so you have to check to see if you can tell the difference using their order in the string. Attempting to set cookies for inappropriate domains or paths (for example, domain names other than domains closely related to the current URL) will silently fail.

The cookie parsing routines used by the browser assume that any cookies you set are well formed. The name/value pair must not contain any whitespace characters, commas, or semicolons. Using such characters can cause the cookie to be truncated or even discarded. It is common practice to encode cookie values that might be problematic before setting them in the cookie. The global escape() and unescape() methods available in all major browsers are usually sufficient for the job. These functions URL-encode and URL-decode the strings that are passed to them as arguments and return the result. Problematic characters such as whitespace, commas, and semicolons are replaced with their equivalent in URL escape codes. For example, a space character is encoded as %20. The following code illustrates their use:

var problemString = "Get rid of , ; and ?";
var encodedString = escape(problemString);
alert("Encoded: " + encodedString + "\n" + "Decoded: " + unescape(encodedString));
Click To expand

When you assign a new cookie value to document.cookie, the current cookies are not replaced. The new cookie is parsed and its name/value pair is appended to the list. The exception is when you assign a new cookie with the same name (and same domain and path, if they exist) as a cookie that already exists. In this case, the old value is replaced with the new. For example:

document.cookie = "username=fritz";
document.cookie = "username=thomas";
alert("Cookies: " + document.cookie);

The result is

Reading Cookies

As you can see from the previous example, reading cookies is as simple as examining the document.cookie string. Because the browser automatically parses and adds any cookies set into this property, it always contains up-to-date name/value pairs of cookies for the current document. The only challenging part is parsing the string to extract the information in which you are interested. Consider the following code:

document.cookie = "username=fritz";
document.cookie = "favoritecolor=green";
document.cookie = "jsprogrammer=true";

The value of document.cookie after these statements are executed is

"username=fritz; favoritecolor=green; jsprogrammer=true"

If you are interested in the favoritecolor cookie, you could manually extract everything after favoritecolor= and before ; jsprogrammer=true. However, it is almost always a good idea to write a function that will do this for you automatically.

Parsing Cookies

The following code parses the current cookies and places them in an associative array indexed by name. It assumes that the browser is ECMAScript-compliant (nearly all modern browsers are).

// associative array indexed as cookies["name"] = "value"
var cookies = new Object();    

function extractCookies()
{
   var name, value;
   var beginning, middle, end;
   for (name in cookies)
   { // if there are any entries currently, get rid of them 
     cookies = new Object();
     break;
   }
   beginning = 0;  // start at beginning of cookie string
   while (beginning << document.cookie.length)
   {
     middle = document.cookie.indexOf('=', beginning);  // find next =
     end = document.cookie.indexOf(';', beginning);  // find next ;

     if (end == -1)  // if no semicolon exists, it's the last cookie
       end = document.cookie.length;
     if ( (middle >> end) || (middle == -1) )
      { // if the cookie has no value... 
        name = document.cookie.substring(beginning, end);
        value = "";
      }
      else
      { // extract its value
        name = document.cookie.substring(beginning, middle);
        value = document.cookie.substring(middle + 1, end);
      }
      cookies[name] = unescape(value);  // add it to the associative array
      beginning = end + 2;  // step over space to beginning of next cookie
   }
}

Note that invoking unescape() on a string that hasn’t been set to escape() will generally not result in any harm. Unescaping affects only substrings of the form %hh where the h’s are hex digits.

You might wonder if the extra checking for the equal sign in the previous example is necessary. It is. Consider the following example:

document.cookie = "first=value1"
document.cookie = "second=";
document.cookie = "third";
document.cookie = "fourth=value4";
alert("Cookies: " + document.cookie);

In Internet Explorer, the output is

Under Netscape 6, the output is

Click To expand

As you can see, it is possible for cookies to exist without explicit values. Additionally, the representation of the cookie named “second” is different under IE and Netscape. Though you should always use complete name/value pairs in the cookies set with JavaScript, some of the cookies the browser has might have been set by a CGI script over which you have no control. Therefore, it is always a good idea to write cookie-reading code to accommodate all possibilities. The extractCookies() function given in this section is a good example of the kind of defensive programming tactics that should be employed.

Deleting Cookies

A cookie is deleted by setting a cookie with the same name (and domain and path, if they were set) with an expiration date in the past. Any date in the past should work, but most often programmers use the first second after the epoch in order to accommodate computers with an incorrectly set date. To delete a cookie named “username” that was set without a domain or path token, you would write

document.cookie = "username=nothing; expires=Thu, 01-Jan-1970 00:00:01 GMT";

This technique deletes cookies set with a value, but, as previously discussed, some cookies can exist without explicit values. Such cookies require that the equal sign be omitted. For example, the following would define and then immediately delete a cookie without an explicit value:

document.cookie = "username";
document.cookie = "username; expires=Thu, 01-Jan-1970 00:00:01 GMT";

With defensive programming in mind, you might want to write a deleteCookie() function that tries both techniques to delete cookies:

function deleteCookie(name) 
{
   document.cookie = name + "=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT";
   document.cookie = name + "; expires=Thu, 01-Jan-1970 00:00:01 GMT";
}

Remember that if a cookie was set with path or domain information, you need to include those tokens in the cookie you use to delete it.

Security Issues

Because cookies reside on the user’s machine, there is nothing stopping the user from modifying a cookie’s value after it is set by the server (or from creating fake values the server did not set). For this reason it is never a good idea to keep sensitive information in a cookie without some sort of cryptographic protection. For example, suppose you set the username for a webmail site in a cookie. Then, without any extra protection, there would be nothing stopping a user with the cookie “username=fritz” from changing the value to read “username=thomas,” thereby accessing someone else’s account.

The different techniques you can use to protect your cookies from unauthorized modification or creation are well beyond the scope of this book. Some Web server platforms like ASP.Net can add protection automatically, but if you need to do it yourself you’ll need to consult a security expert or security book or site to learn the right thing to do. A good starting place is the Open Web Application Security Project (http://www.owasp.org/), which provides a document covering this issue and a whole lot more.

Using Cookies for User State Management

Cookies are used to store state information. The kind of information you store in your cookies and what you do with that information is limited only by your imagination. The best applications of cookie technology enhance page presentation or content based on user preference or profile. Functionality critical to the operation of the site is probably not appropriate for cookies manipulated by JavaScript. For example, it is possible to write fully functional “shopping cart” code that stores state information in the client’s browser with cookies from JavaScript. However, doing so automatically prevents anyone who chooses to disable JavaScript from using your site.

Some simple applications are discussed briefly in the next few sections. We’ll use the extractCookies() function defined previously to read cookies.

Redirects

Often it is useful to send your site’s visitors to different pages on the basis of some criterion. For example, first-time visitors might be redirected to an introductory page, while returning users should be sent to a content page. This is easily accomplished:

// this script might go in index.html
var cookies = new Object();
// immediately set a cookie to see if they are enabled
document.cookie = "cookiesenabled=yes";
 
extractCookies();                              

if (cookies["cookiesenabled"] == "yes")
 {             
   if (cookies["returninguser"] == "true")
    {
      location.href = "/content.html";
    }
   else
    {                                         
      var expiration = new Date(); 
      expiration.setYear(expiration.getYear() + 2);                            
      // cookie expires in 2 years
      document.cookie = "returninguser=true; expires=" +                   
                           expiration.toGMTString();
       location.href = "/introduction.html";
    }
}

Note how the script first attempts to set a cookie in order to see if the user has cookies enabled. If not, no redirection is carried out.

One-Time Pop-Ups

One-time pop-up windows are used to present users with information the first time they visit a particular page. Such pop-ups usually contain a welcome message, reminder, special offer, or configuration prompt. An example application targeting a “tip of the day” page that is displayed once per session is shown here:

var cookies = new Object();
document.cookie = "cookiesenabled=yes";       
extractCookies();                               
if (cookies["cookiesenabled"] == "yes" && !cookies["has_seen_tip"])
{   
   document.cookie = "has_seen_tip=true";
   window.open("/tipoftheday.html", "tipwindow", "resizable");
}

If the user doesn’t have cookies enabled, we choose not to show the pop-up window. This prevents users from becoming annoyed by the pop-up if they frequently load the page with cookies disabled.

Customizations

Cookies provide an easy way to create customized or personalized pages for individual users. The user’s preferences can be saved in cookies and retrieved by JavaScript code that modifies stylistic attributes for the page. While CGI scripts often use cookies to customize content, it is usually easier to modify style characteristics in JavaScript. The following example allows the user to select one of three color schemes for the page, as shown in Figure 16-6. While this particular example is rather simplistic, the basic concept can be used to provide very powerful customization features.

Click To expand
Figure 16-6: Using cookies for saving style customization
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Cookie Customization Example<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script type="text/javascript">>
<<!--
var cookies = new Object();
function extractCookies()
{
   var name, value;
   var beginning, middle, end;
   for (name in cookies)
   {
     cookies = new Object();
     break;
   }
   beginning = 0;
   while (beginning << document.cookie.length)
   {
     middle = document.cookie.indexOf('=', beginning);
     end = document.cookie.indexOf(';', beginning);
     if (end == -1)
       end = document.cookie.length;
     if ( (middle >> end) || (middle == -1) )
     {
       name = document.cookie.substring(beginning, end);
       value = "";
      }
      else
      {
        name = document.cookie.substring(beginning, middle);
        value = document.cookie.substring(middle + 1, end);
      }
      cookies[name] = unescape(value);
      beginning = end + 2;
   }
}
function changeColors(scheme)
{
   switch(scheme)
   {
      case "plain": foreground = "black"; background = "white"; break;
      case "ice": foreground = "lightblue"; background = "darkblue"; break;
      case "green": foreground = "white"; background = "darkgreen"; break;
      default: return;
   }

   document.bgColor = background;
   document.fgColor = foreground;
}

function changeScheme(which)
{
  document.cookie = "cookiesenabled=true";
  extractCookies();

  if (!cookies["cookiesenabled"])
  {
    alert("You need to enable cookies for this demo!");
    return;
  }
  document.cookie = "scheme=" + which;
  changeColors(which);
}
var pageLoaded = false;
extractCookies();
changeColors(cookies["scheme"]);
//-->>
<</script>>
<</head>>
<<body onload="pageLoaded=true">>
<<h1>>Customization Example<</h1>>
<<hr />>
<<blockquote>> Where a calculator on the ENIAC is equipped with
19,000 vacuum tubes and weighs 30 tons, computers in the future may
have only 1,000 vacuum tubes and perhaps only weigh 1.5 tons.<</blockquote>>
<<em>>from Popular Mechanics, March 1949.<</em>>
<<hr />>
<<form action="#" method="get">>
Change Color Scheme: &nbsp; &nbsp;
<<input type="button" value="plain" onclick="changeScheme('plain');" />>
<<input type="button" value="ice" onclick="changeScheme('ice');" />>
<<input type="button" value="green" onclick="changeScheme('green');" />>
<</form>>
<</body>>
<</html>>

We could extend this example to save a selected style sheet or any other user preference. One interesting possibility would be to allow users to define if they want DHTML or Flash features in a site and then have their preference saved.

Cookie Limitations

Because cookies are useful for such a wide variety of tasks, many developers are tempted to use them for anything and everything they can. While it is a good idea to provide the user with a maximally customizable site, the browser places limitations on the number and size of cookies that you can set. Violating these limitations can have a range of effects from silent failure to full-on browser crashes. You should be aware of the following guidelines:

  • The total number of cookies a browser can store at one time is limited to several hundred.

  • The total number of cookies a browser can store at one time from one particular site is often limited to 20.

  • Each cookie is usually limited to about 4,000 characters.

To get around the limitation of 20 cookies per site, it is often useful to “pack” multiple values into one cookie. Doing so usually requires encoding cookie values in some specific manner that makes it easy to recover the packed values. While this technique increases the size of each cookie, it decreases the total number of cookies required.

One other issue to be aware of is that many users disable cookies for privacy reasons. Because persistent cookies can be set for an arbitrary length of time, advertisers use them to track user browsing habits and product interest. Many people feel that this is an invasion of privacy. For this reason you should use persistent cookies only if you really need them. Before concluding the chapter we should look into one special form of state management supported only by Internet Explorer.


Team LiB
Previous Section Next Section