Script Library: 1238 scripts
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
View scriptLicenseDownload documentation as: HTML or editable
Download scriptHistoryOther scripts by: sunanda

Documentation for: cookie-example.r


December 2003
{To be completed later]

1. Introduction

This script is slightly more than an example of how to set and retrieve a cookie. In miniature, it shows most of the major things you need to do to create a website that can respond to specific users.

These notes fill out some of the details that may not be obvious.

Most of the code here is taken from actual code used to run, so this is all practical advice and no irrelevant theory.


2. Notes about functions

2.1 send-output

To write a webpage, you simply print HTML commands.

But before the <HTML> there can be HTTP headers.

In fact, there must be at least one: the content-type. For a standard HTML page, the content-type will be text/html. If you are displaying anything else (like a PDF), then you need a different content type.

The headers end with a blank line -- hence the ^/ in the line:

   print "Content-type: text/html^/"

2.2 clear-place-holders

We use a scheme with HTML templates that have place-holders-- places where weíll insert values or other HTML.

This function removes any left-over place-holders before we print the page.

Our place-holders start and end with two exclamation marks. In retrospect, that wasnít such a great idea. It would have been better to use some sort of pseudo-tag take like [user-name], but they would have been harder to clear.

2.3 defuse-cgi-field

Any data that a user has entered via a form has to be treated with some suspicion. Validation is obvious -- if you are expecting a number -- you need to check that it is a number. How you do that safely is another matter.

If you are going to redisplay the data, you need to make sure that it does not contain any HTML or Javascript commands. If it does, you could end up causing something unfortunate to happen in the userís browser.

Hence defusing the input fields. We convert the tag characters (greater-than and less than signs) to their equivalent numeric entities. Theyíll display okay on any browser, but they wonít be treated as active formatting elements.

How likely is it that someone will attack your website this way? Well, when our feedback form went live, we werenít defusing the entered fields. When we (the Librarians) looked at our second ever feedback message, it contained an XSS (cross-system attack). It wasnít very dangerous -- just popped up a Javascript dialog box. But it was an attack that defusing the input fields would have avoided.

2.4 read-cgi

This is a standard bit of code, published by RT somewhere in the Core Guide to CGI programming. It hides the complexity of get or post and returns a block with pairs of values for the CGI fields, eg:

    [field1: "value of field 1" field2: "value of field2"]

That takes care of half of the complexity of getting your CGI fields into words for later validation and processing. But only half, as it canít return fields that are not present. That is dealt with a bit further down in the sample script with code that would look like this in a more real example:

    cgi-object: make object! [field1: 20 field2: "No" field3: "update"]
    cgi-object: construct/with decode-cgi cgi-input cgi-object

In the above, there are three CGI fields expected, and the object will contain them all, with default values for any that were not present in the input data.

2.5 cookie

This is a little function that either reads a cookie or sets it.

More on cookies further down.

2.6 read-user-record-from-cookie

2.7 write-user-record

2.8 Web-page

3. Mainline processing notes

3.1 if error? cgi-error: try

The whole of the code is wrapped in this error block. Without this, it is possible an error will leave the user with a blank screen or a server error message. Not very helpful, not very useful.

The cgi-error handler on REBOL. org works this way too. In addition to displaying an apology message, it also writes all relevant details to an error log file (so the Library team can diagnose and fix the error) and sends an email alert so we know itís happened.

3.2 if any [none? cgi-input "" = cgi-input]

Read-cgi behaves slightly differently under different servers (or possibly on different platforms).

This double test for none and for "" works on all platforms to detect that there were no CGI fields.

3.3 create object with all fields

This creates an object, mapping the CGI fields to the object. This is an easy way to access the CGI fields later.

If we had more than one CGI field, they may not all be there for any one message. The easy way around that is to create the object with default values for all fields, and then map the CGI fields to it:

 cgi-object: make object! [
    input-text: ""
    update-flag: "no"
    user-name: none"
 cgi-object: construct/with decode-cgi cgi-input cgi-object

We can then access these fields, even if they did not occur in the input message:

    if cgi-object/update-flag = "yes" [....]
    if not none? cgi-object/user-name [....]

That removes the need for the test check if required fields are present in this example. But you may have a case where it is important to distinguish between no field being present, and that fieldís null value.

4. Setting a cookie

A cookie is just some value that you create on the server and send to the browser (or other user agent). When a URL is sent by the browser to the same server it will supply the cookie (or cookies if you have set more than one)

In this example, we are using the cookie to identify the user. So we need a unique value for each cookie. The example creates the unique value by doing a checksum on the current time.

4.1 Warning about the example

This is a very bad way to create a unique value:

  • It is possible that two requests happen at the same moment. Thatís unlikely, as the server probably has a timer granularity of better than 1/1000th of a second. But handle several thousand requests a day, and it will happen sooner or later.
  • A bad guy knows someone signed on around 17:01, they could try every possible time value around that moment until they had stumbled across a value that the server recognises as a valid cookie. They could then masquerade as that user.

4.2 Saving user data

We use the cookie value as a direct key to a file. That file contains an object that holds all the "persistent" data for that user.