GEHC JavaScript Class 4 Cookies Cookies ------- What are cookies? o tiny bit of information stored on the client machine by the server o cookie is "owned" by a single server - a cookie set by one server can only be read by that server o can be set for a specific host within a domain name o can also be set for a specific path on a host o simple key/value pair o cookies can expire after a set period of time The data stored in a cookie is typically as simple as a user name, but can be as complex as an encrypted string of data or a unique ID used to access a much larger record in a server's database. The downside of cookies is that they can be abused, though most of the security holes in cookies have been plugged. The biggest issue to using cookies on a site is that many users are frightened by them and turn them off. Why "cookie?" ------------- In programming in general, a "magic cookie" is a bit of data used to pass information from one program to another. When Netscape added the capability to Navigator, they used the term "cookie," and it stuck. Why cookies? ------------ Web pages, by their nature, are "stateless." This means that generally speaking, one web page has no idea other web pages exist, and a web page cannot tell anything about a visitor, including if that person has ever visited before. Worse, one web page cannot tell another web page anything about what the user has done. We can get around the need for cookies by using CGI, which allows us to build web pages based on previous user activity. But even with CGI, cookies can be very useful, because they allow a site to store data that can be retrieved not just in the next instant, but the next day, the next month, the next year. They can also store data set using a trusted system, such as a login page, and rely on that data as long as the cookie is readable. Cookie limits? -------------- The limits for cookies vary, but it would be best to keep cookies short in spite of the upper limits. The standard says that a browser need not keep any more than the last 300 cookies, with no more than 20 cookies per server, and no more than 4K of data per cookie. According to the spec, cookies after the 300 cookie limit should be deleted. Data past the 4K limit should be trimmed. Assuming that a user accepts cookies, you can assume that your cookie will be present during a session on your site, but beyond that, all bets are off. Your site must be able to handle a browser losing your cookie. Best, your site must be able to work without any cookies at all. Example: USConstitution.net message boards - cookies are used to store the user's name, their email address, and their web site. But none of these are required to use the site, so a user who does not accept cookies can still use the site. If they want to post messages, they will have to retype all of their data. If they lose their cookie, the user can still post messages, they will just have to retype their data and a new cookie will be created. Basic point to remember: Cookies set by your site can only be read by your site. Cookies from other sites cannot be accessed from your site. Setting a cookie ---------------- We are going to use JavaScript to set cookies. The syntax for setting a cookie is: document.cookie = "key=value"; or, maybe: document.cookie = "key=" + document.theform.username.value; ** JS4-1 - set a simple cookie In this example, we do two things. First, we set a cookie by saying document.cookie = "username=Steve"; Then we print the cookie back out again, using document.cookie in the document.write method. Note that we set document.cookie like we set a string, and in many instances, document.cookie is like a string, but it also differs in many ways. We'll get to those. Now, because we can put user data in the value of our cookie, we probably need to do some validation on it. There are certain characters that are not allowed in a cookie. Fortunately, JS supplies us with a nice function used to convert data into proper cookie format. escape() document.cookie = "username=" + escape(document.theform.username.value); Not properly escaping the value can lead to invalid cookies. Note that we do not normally escape the key for a cookie, because we are setting the cookie name ourselves. If for some reason, we wanted the user to set the cookie name, we would want to escape it, too: document.cookie = escape(document.theform.cookiekey.value) + "=" + escape(document.theform.cookieval.value); The escaping does what is called "URL-encoding," a simple encoding method that gets used a lot in JS and CGI. In URL-encoding, all non-alphanumeric characters are changed into a format like this: %XY where X and Y are hexadecimal digits. (space) => %20 ! => %21 " => %22 and so on ** JS4-2 - set a cookie using escape() JS also supplies an unescape() function, which we will see in action shortly. There are a few other components to a cookie that we can set. The first is an expiration date. We can access dates with JS's Date object. We will look at Date in a lot more detail next week. Note: if you do not set an expiration date, the cookie expires when the browser closes. To set an expiration date, we must supply the date along with the key/value pair, all in the same line. The general format is: key=value; expires=(date string) A semicolon and a space separate the key/value pair and the expires value. The format for the expiration date must look like this: Wed, 13 Feb 2002 18:45:02 GMT Fortunately, the Date object can build us a string just like that. The toGMTString() method of the Date object gives us this info. The toUTCString() method is the preferred method, but is available in JS 1.2 and higher only. Eventually you'll want to use just the UTC method, but for now, the GMT method works fine. To expire a cookie a year from now: var now = new Date; // Create a Date object, with "now's" information var oneyear = 31536000000; // number of milliseconds in one year now.setTime(now.getTime() + oneyear); This sets the time in the "now" object to now plus one year (actually, 365 days from now, but close enough). Now, we can set a cookie to expire a year from now: document.cookie = "username=" + escape(document.theform.username.value) + "; expires=" + now.toGMTString(); Note again that the format is very important - all of pieces must be set at the same time. You cannot say: document.cookie = "username=" + escape(document.theform.username.value); document.cookie += "; expires=" + now.toGMTString(); You can also force a cookie to expire right away, by setting the expiration date in the past: now.setTime(now.getTime() - 1); // set now to one millisecond ago ** JS4-3 - shows what a cookie setting would look like with the expiration date, but does not actually set a cookie The reason I did this is because when you print the cookie, you do not see the cookie's expiration date. If the expiration date has passed, the cookie goes away. The expiration date is not accessible to the browser. Also shows the use of toGMTString and toUTCString. In MSIE5 and 6, and Netscape 4.7 and 6.2, there is no difference. ** JS4-4 - sets the cookie as in 4-3, then has a link to a "showcookie" page All this does is set the cookie to expire a year from now. The showcookie page we will get to in a little bit. ** JS4-5 - illustrates expiring a cookie right away - go to the showcookie page to see that the cookie is gone. The document.cookie property ---------------------------- As mentioned, document.cookie is a lot like a string, but it is special. When you do: var string = "Steve"; string = "Mount"; In the end, the string variable is equal just to "Mount". Not so the cookie. document.cookie = "key1=1"; // sets a cookie document.cookie = "key2=2"; // sets another cookie, first one still exists If you print document.cookie now, the output is "key1=1; key2=2" ** JS4-6 - illustrates setting multiple cookies. Here, two values, key1 and key2, are set with different values. You can see from the output, or by clicking on showcookie, that both cookies now exist. You can also see that the cookies are stored in what is essentially one long string. To access the cookies, we need to pull one cookie at a time out of that string. To do this, we will create a sorely lacking function, called getCookie. getCookie will search the document.cookie value for a key and, if found, return its value. The cookie string is generally built like this: key=value; key=value; key=value function getCookie(cookiename) { var i, j; var key = cookiename + "="; var cookies = document.cookie.split("; "); for (i=0; i=cookies.length; i++) { j = cookies[i].indexOf(key); if (j == 0) break; } if (i == cookies.length) return(null); var keyval = cookies[i].split("="); return(unescape(keyval[1])); } function getCookie(cookiename) We will call this getCookie, and it will accept one parameter, a string named cookiename. cookiename is the name of the key for one single cookie. var key = cookiename + "="; We're going to need to find, in the cookie itself, the key and the equal sign following it, so we set up a new variable with that data in it. var cookies = document.cookie.split("; "); The split method of a string returns an Array object. What we're saying here is to return an array of strings that were separated by the "; " string. The for loop is used to search the resulting array for the one specific key we're looking for. cookies is an array of strings, so we can use the indexOf method to search within each string in the array. What we're looking for is the key string we set above, and we're looking for that key to be at the exact beginning of the string (for an indexOf value of 0). If it is 0, then we break out of the for loop. if (i == cookies.length) return(null); If the cookie was not found in the cookie string, then we want to return something, and null, a JS keyword, is a good value to return. If the key was found, then we want to extract the value, so we use split again to split the string into the key and the value. keyval is now a new array of two strings. keyval[1] holds the value. We unescape the value, and return the value. ** JS4-7 - uses getCookie, through a function called printCookie Take a look at showcookie2.html, which is a variation on the getCookie function. Here, instead of looking for a single cookie, we want to show all cookies. Again we split the string, and then we can print the resulting array one bit at a time. The biggest problem with the getCookie code is that it uses arrays, which are not available in all versions of JS. ** JS4-8 - uses code that will work with any version of JS, all the way back to JS 1.0. The code, which I'll leave for you to explore on your own, uses string functions only and never sets up an array. Such code is very rarely needed today, but will come in handy if you do need it. Session variables ----------------- One improvement on cookies that Groupcast uses is the concept of a session variable - or, as it is known, a GPP property. These variables rely on two basic parts. First, it uses frames, something we have not talked about yet. Second, it relies on associative arrays, something else we have not talked about yet. In a framed site, you have a master page that defines the frames for the site. All of the frames contain pages that run independently of one another, but which are related - and they can refer to each other. ** JS4-9 Load this page and look first at the browser view source. This will show you the code for the master page. Then right-click view source on each frame to see the code for each frame's page. Note how they all call functions in each other's JS. The top sets a couple of functions and creates a global array. The frames fill that array and check its contents, and can also call functions in the top and in each other. array["key"] = value; This is an example of an "Associative array". The key is not a number, but instead a random string. This makes for some very interesting possibilities. Using this with a framed site allows the equivalent of session cookies.