Server Side Cookies

Summary

Web pages implement Cookies: an API for storing string values keyed by names:

This web page dicusses implementing a Cookie-like facility as a CGI (written in C). That is, something like the familiar cookie API, but with the data stored on the web server rather than the browser, so cookie changes affect all future visits to the web page for everyone.

Introduction

Web browsers implement "cookies" - little name and value pairs of data: give it a name, get back a value. That way, when the user comes back to a web page, it can restore its previous settings. But, browsers store the cookies in a cookie text file on the browser's computer. Move to different computer, look at the same web page, and your cookies aren't there. That's fine if that's what you want, but sometimes you'd like to make a change on one compute and see the change on a different computer.

The classical solution to this problem is just re-upload a modified web page. But that user interface sucks: if you want to make controlled minor changes, want to forbid major changes (in this application), and don't want to set up software beyond a web browser, it may not make sense to use a separate uploading application. You might use an on-line word processor like Google Documents, but that makes it too easy to make large changes to the document, changes you might not, in all cases, want.

CGI - Common Gateway Interface: A spec for writing programs that can be called as subroutines by web servers.

The Problem

My wife had, on her previous smartphone, a shopping list application. She had a master list of all the items she'd bought. She could run down it checking off the items to buy this time. She'd sync the list to her phone, view the list sorted in the aisle order of her favorite supermarket, and quickly buy the items on her list, checking them off as she went.

She changed to an iPhone, and asked me to re-implement the shopping list as a web page, so she could modify it sitting at her desktop computer, then view it on the iPhone.

The Web Host

Our web hosting company uses the Apache web server. It allows us to have CGIs on our web site. The CGIs must live in a folder named “cgi-bin” and must have a “.cgi” extension. When it runs my CGI, it runs it using my username, so it has access to my files and only my files. The host provides ftp access for moving files, but it also provides shell access and a C compiler, so I could write a CGI in C, upload the source, and compile it on the web host machine. This lets me test using the copy of Apache, pre-installed in OS X, which reads from /Library/Webserver/Documents/.

The Program - preliminaries

Before you spend your time writing CGIs, verify that you’ll be able to get the to run CGIs from two webservers: your development machine, and the machine of your hosting company.

Verify that you can get any CGI to work at all, by compiling the following C program, naming the result “HelloWorld.cgi”, and putting in the cgi-bin folder of your production web server and your test we server. For example, on Macintosh OS X, put HelloWorld.cgi inside the folder /Library/Webserver/CGI-Executables/, turn on Personal Web Sharing in System Preferences, and see if using a browser to look at http://localhost/cgi-bin/HelloWorld.cgi returns a web page saying: “Hello World”.

#include <stdio.h>
int main(){
  printf("Content-type: text/text\n\nHello, World!\n") ;
  return 0;
}

Common problem: the machine language of the program is downloaded, or worse dumped out, into the browser, instead of the program executing. If that happens, you'll need to see the Apache how-to page on .htaccesss and CGI access for more information.

Note that, Apache is configured by default on OS X to look in /Library/Webserver/CGI-Executables, not /Library/Webserver/Documents/cgi-bin/, as you would expect, for CGIs for the machine-global web site http://localhost, and it isn't configured for CGIs in the per-user sites at all.

The Apache configuration lives in

The Program - Server Side

Details of the CGI, the server side program.

The Program - Client Side

We’ll be using the Javascript primitive: XMLHTTPRequest to talk to our CGI. Even though it’s called XMLHTTPRequest, it appears to let us send and receive arbitrary text without any problem, at least for Firefox and Safari. Which is good, because we'll be sending and receiving not XML, but Javascript. See JSON.org for more information on using Javascript fragments as a communications protocol.

We use eval() to convert a raw text buffer into a javascript object. You’d think this would be a security hole, but it isn’t that bad. XMLHTTPRequest is only fetching from our own server, and if someone can spoof the DNS to get the browser to read bad javascript from our domain, well we’ve already lost: the bad guys could just send the bad javascript at the start. But, there is no way for the user of the web page to get something to round trip through the server that will mess up other users.

Store

The Javascript to assign a value to a name is straightforward.

function SetServerCookie(name, value) {
  var http = new XMLHttpRequest();
  var url = "http://example.com/cgi-bin/servercookie.cgi?"; 
  http.open('POST', url, true);
  var sendBody = name + '=' + JSONEscapeString(cValue);
  http.send(sendBody);
}

That JSONEscapeString just puts in the backslash escapes and surrounds the string with double quotes.


function JSONEscapeString(s) {
  return '"' + s.split('\\').
                 join('\\\\').
                 split('\"').
                 join('\\\"') + '"';
}

Fetch

The Javascript to fetch the value for a name is little more complex: We request the value frm the server, but the reply may take awhile, so we must provide a callback function. We create the callback function as needed so we can bind a few local variables. That way, when the server reply makes the callback run, it will have the data it needs. This way, we can multiple request to the server active simultaneously, and they won't step on each other.

GetServerCookie itself will call a function with the value of name as a parameter.

so if, with Client side cookies you wrote:

function Foo(){
  var cookieVal = GetCookie('myName');
  if (cookieVal) {
    UseCookie(cookieVal);
  }
}
With server-side cookies, you'd rewrite this as:

function Foo(){
  GetServerCookie('myName', UseCookie);
}
(If the cookie isn't set, GetServerCookie will call UseCookie('').)


// single char lowercase name, call func with value.
function GetServerCookie(name, func){
  var http = new XMLHttpRequest();
  var url = "http://example.com/cgi-bin/servercookie.cgi?"; // The server-side script
  http.open('GET', url + name, true);
  http.onreadystatechange = function(){ HandleHttpFetch(http, func); };
  http.send(null);
}
and the helper routine, HandleHttpFetch is:
function HandleHttpFetch(http, func){
  if (4 == http.readyState) {
    func(eval(http.responseText));
  }
}

i.e., when the reply is complete, eval the reply text to get a string object, then pass it to func.

The Web Host

Create a starter cookies.txt file, so the CGI will modifying an existing file rather than creating one from scratch. It can be empty. Make sure you have the protections on the cookies.txt set correctly for your web server. In my personal configuration, the copy on my local machine had to be set to the owner www while the copy on my hosting service had to be set to my normal account.

Pick a location for your cookies.txt that is not readable for regular web pages. That makes it less likely that your server cookies will be used in a way you don't expect (like indexed by a search engine.)

Further Work

This is a CGI, not a FastCGI. The difference is that a CGI starts from scratch on each HTTP request, reading in the entire file. A FastCGI runs until it quits, waiting for messages from the web server.

These cookies are totally public. A more sophisticated program might use a client-side cookie to hold a session id, put the session id on the http URL, and the CL that receives it could decide to continue the session, or force the user to log in again.

Conclusion

This page has presented straight C code for implementing simple, global, server side cookies.