Skip to content

Add optional support for sessions without client cookies #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
70 changes: 68 additions & 2 deletions web/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import os, time, datetime, random, base64
import os.path
from cStringIO import StringIO
from urlparse import urlsplit, urlunsplit
try:
import cPickle as pickle
except ImportError:
Expand Down Expand Up @@ -33,7 +35,8 @@
'secret_key': 'fLjUfxqXtfNoIldA0A0J',
'expired_message': 'Session expired',
'httponly': True,
'secure': False
'secure': False,
'cookieless': False,
})

class SessionExpired(web.HTTPError):
Expand Down Expand Up @@ -83,17 +86,25 @@ def _processor(self, handler):
self._load()

try:
return handler()
response = handler()
finally:
self._save()

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
cookie_domain = self._config.cookie_domain
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
Expand Down Expand Up @@ -135,6 +146,61 @@ def _save(self):
self.store[self.session_id] = dict(self._data)
else:
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
Expand Down