Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Add optional support for sessions without client cookies #86

wants to merge 10 commits into from

2 participants


If enabled and the client hasn't sent a session cookie, injects the session ID in the response by adding hidden form fields and adding query parameters to links. Requires lxml to process (X)HTML.


Beautiful soup is a requirement for this to work?


For cookieless sessions, yes (it's not required if you don't want to use cookieless sessions). I can replace it with a different HTML parser if necessary.

I think it'd be more prudent to get it working with python's standard library.

The tools in stdlib aren't really suitable for working with HTML. HTMLParser is very low level, and the XML tools won't handle HTML. I think the options to choose from are BeautifulSoup, html5lib, and lxml.html.

I see. I'll look into the docs and see if I can come up with something. I'm interested in cookie-less solution, but I don't like the idea of having BeautifulSoup in there if I'm not using it elsewhere.

That's a bit misleading, because even if cookieless sessions are enabled it still prefers cookies.

I see. Do you have a way to completely disable cookies?

No, because this approach has security implications and so should really be a last resort. It is of course possible to add, but I don't see why one wouldn't want to use cookies if they are available.


A small aesthetic correction, if I may. I think it'd look better if you say use_cookies = True here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 15, 2010
  1. Import cookieless sessions patch.

    Michael Gorven authored
Commits on Jan 19, 2010
  1. Replace BeautifulSoup with lxml.html.

    Michael Gorven authored
  2. Import lxml in _add_session() so that it's only required if cookieles…

    Michael Gorven authored
    …s sessions are enabled.
  3. Rather check if the form has an action attribute instead of handling …

    Michael Gorven authored
    …None URLs.
Commits on Jul 11, 2011
  1. Merge branch 'master' into cookieless

    Michael Gorven authored
  2. Handle XHTML.

    Michael Gorven authored
  3. Use parse() so that we get the doctype.

    Michael Gorven authored
  4. Respect charset specified in Content-Type if present.

    Michael Gorven authored
  5. Remove big if block.

    Michael Gorven authored
This page is out of date. Refresh to see the latest.
Showing with 68 additions and 2 deletions.
  1. +68 −2 web/
70 web/
@@ -5,6 +5,8 @@
import os, time, datetime, random, base64
import os.path
+from cStringIO import StringIO
+from urlparse import urlsplit, urlunsplit
import cPickle as pickle
except ImportError:
@@ -33,7 +35,8 @@
'secret_key': 'fLjUfxqXtfNoIldA0A0J',
'expired_message': 'Session expired',
'httponly': True,
- 'secure': False
+ 'secure': False,
+ 'cookieless': False,
class SessionExpired(web.HTTPError):
@@ -83,10 +86,15 @@ def _processor(self, handler):
- return handler()
+ response = handler()
+ if self._config.cookieless:
+ response = self._add_session(response)
+ return response
def _load(self):
"""Load the session from the store, by the id from cookie"""
cookie_name = self._config.cookie_name
@@ -94,6 +102,9 @@ def _load(self):
httponly = self._config.httponly
self.session_id = web.cookies().get(cookie_name)
+ if self._config.cookieless and not self.session_id:
+ self.session_id = web.input().get(cookie_name)
# protection against session_id tampering
if self.session_id and not self._valid_session_id(self.session_id):
self.session_id = None
@@ -135,6 +146,61 @@ def _save(self):[self.session_id] = dict(self._data)
self._setcookie(self.session_id, expires=-1)
+ def _is_relative(self, url):
+ split = urlsplit(url)
+ return split[0] == '' and split[1] == ''
+ def _add_session(self, response):
+ cookie_name = self._config.cookie_name
+ # Only process response if client didn't provide a session cookie
+ if web.cookies().get(cookie_name):
+ return response
+ from lxml import html, etree
+ types = {
+ 'application/xhtml+xml': etree,
+ 'application/xml': etree,
+ 'text/html': html,
+ }
+ content_type = None
+ for header, value in web.ctx.get('headers', []):
+ if header.lower() == 'content-type':
+ content_type = value.split(';')[0].lower()
+ content_type_params = dict((k.lower().strip(), v) for k,v in (part.split('=', 1) for part in value.split(';')[1:]))
+ if content_type not in types:
+ return response
+ tree = types[content_type]
+ doc = tree.parse(StringIO(str(response)))
+ tostring_kwargs = {'encoding': content_type_params.get('charset', 'utf-8')}
+ if None in doc.getroot().nsmap:
+ ns = '{%s}' % doc.getroot().nsmap[None]
+ tostring_kwargs['xml_declaration'] = True
+ else:
+ ns = ''
+ # Add hidden input fields to forms
+ for form in doc.iterfind('.//%sform' % ns):
+ if 'action' not in form.attrib or self._is_relative(form.attrib['action']):
+ input = etree.Element('input', type='hidden', name=cookie_name, id=cookie_name, value=self.session_id)
+ form.append(input)
+ # Add query parameters to relative links
+ param = cookie_name + '=' + self.session_id
+ for a in doc.iterfind('.//%sa' % ns):
+ if 'href' in a.attrib and self._is_relative(a.attrib['href']):
+ parts = list(urlsplit(a.attrib['href']))
+ if len(parts[3]) == 0:
+ parts[3] = param
+ else:
+ parts[3] += '&' + param
+ a.attrib['href'] = urlunsplit(parts)
+ return tree.tostring(doc, **tostring_kwargs)
def _setcookie(self, session_id, expires='', **kw):
cookie_name = self._config.cookie_name
Something went wrong with that request. Please try again.