Documention for: cookie-example.r Created by: sunanda on: 8-Dec-2003 Format: text/editable Downloaded on: 29-Mar-2024 Cookie-example Sunanda December 2003 {To be completed later] ===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 REBOL.org, so this is all practical advice and no irrelevant theory. =toc ===Notes about functions ---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^/" ---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. ---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. ---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. --- cookie This is a little function that either reads a cookie or sets it. More on cookies further down. ---read-user-record-from-cookie ---write-user-record ---Web-page ===Mainline processing notes ---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. ---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. --- 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. ===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. ---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. ---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.