This is a cautionary tale on the pitfalls of storing UI state in the session of a webapp.
Very occasionally we have a need to send out a bulk email to a bunch of people. For example: we had several hundred requests expressing genuine interest in The Commentator, one of our April Fools jokes this year, and we sent out a well-humoured email to them explaining it was a joke.
To help manage these mail-outs, we found and installed a nice little cgi webapp. And since I'm currently tracking a handful of people who are participating in the alpha program for FishEye 1.1 (with SVN support), I figured it would save me some time by just using this existing webapp.
Now this webapp is fairly slick: it has a nice-looking UI, plenty of features, and lots of different admin pages you can fiddle with. I went ahead and setup my alpha-program list, and composed an email announcing that the next alpha build was available. At the same time -- in a different browser window -- I opened up the admin pages for the Commentator list so I could check what settings we had used there. Switching back to the email-composition page, I pressed "submit".
And suddenly 300 Commentator-hopefuls get an email about the FishEye 1.1 alpha.
And I became a spammer for a day.
And if you have ever written a webapp in-anger, then I'm sure you can guess what went on.
Upon entering the "fisheye-alpha" part of the site, it associated my session (or, more likely in this case, a cookie) with the "fisheye-alpha" list. When it presented the email-compose page, it titled the page "fisheye-alpha compose" because, obviously, that is what I was doing. However, when in another window I started clicking through the "commentator" section, my session became associated with the "commentator" list.
When I then submitted the email form, the browser posted to the webapp the email subject and body. But there was no hidden parameter saying for which mail-list this email was for. Instead, the webapp looked up the mail-list from my session, which was the "commentator" list, and sent them the mail.
The pain! The guilt!
So the moral of this story is simple. If you are using both session-state and request-data when handling a page submit, make sure the scope of the session-state matches the scope of the page.
Imagine some data-collection that is spread over a sequence pages. You can store the data from previous pages in the session: but don't store them globally in the session. Instead associate them with this instance of the page-sequence. Make up a unique id for this instance on the first page of the sequence, put the page-data in a hashmap (indexed by this id) in the session, and use that id has a hidden parameter on every page.
It's not that hard. And you might be surprised: make the scope of page-data explicit in your webapp, and it may actually make your code easier to follow.