diff options
author | Marc Abramowitz <marc@marc-abramowitz.com> | 2015-04-30 16:42:17 -0700 |
---|---|---|
committer | Marc Abramowitz <marc@marc-abramowitz.com> | 2015-04-30 16:42:17 -0700 |
commit | 12a3f1f4cfa7f88478dc1b0e949fcc095b9fc804 (patch) | |
tree | ddb8079523d846f0b074437fc33fa5e28b508183 /paste/session.py | |
download | paste-git-eliminate_cgi_parse_qsl.tar.gz |
Replace cgi.parse_qsl w/ six.moves.urllib.parse.parse_sqleliminate_cgi_parse_qsl_2eliminate_cgi_parse_qsl
because `cgi.parse_qsl` is deprecated, according to
https://docs.python.org/2/library/cgi.html#cgi.parse_qsl
Diffstat (limited to 'paste/session.py')
-rw-r--r-- | paste/session.py | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/paste/session.py b/paste/session.py new file mode 100644 index 0000000..ae208e7 --- /dev/null +++ b/paste/session.py @@ -0,0 +1,346 @@ +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +""" +Creates a session object in your WSGI environment. + +Use like: + +..code-block:: Python + + environ['paste.session.factory']() + +This will return a dictionary. The contents of this dictionary will +be saved to disk when the request is completed. The session will be +created when you first fetch the session dictionary, and a cookie will +be sent in that case. There's current no way to use sessions without +cookies, and there's no way to delete a session except to clear its +data. + +@@: This doesn't do any locking, and may cause problems when a single +session is accessed concurrently. Also, it loads and saves the +session for each request, with no caching. Also, sessions aren't +expired. +""" + +try: + # Python 3 + from http.cookies import SimpleCookie +except ImportError: + # Python 2 + from Cookie import SimpleCookie +import time +import random +import os +import datetime +import six +import threading +import tempfile + +try: + import cPickle +except ImportError: + import pickle as cPickle +try: + from hashlib import md5 +except ImportError: + from md5 import md5 +from paste import wsgilib +from paste import request + +class SessionMiddleware(object): + + def __init__(self, application, global_conf=None, **factory_kw): + self.application = application + self.factory_kw = factory_kw + + def __call__(self, environ, start_response): + session_factory = SessionFactory(environ, **self.factory_kw) + environ['paste.session.factory'] = session_factory + remember_headers = [] + + def session_start_response(status, headers, exc_info=None): + if not session_factory.created: + remember_headers[:] = [status, headers] + return start_response(status, headers) + headers.append(session_factory.set_cookie_header()) + return start_response(status, headers, exc_info) + + app_iter = self.application(environ, session_start_response) + def start(): + if session_factory.created and remember_headers: + # Tricky bastard used the session after start_response + status, headers = remember_headers + headers.append(session_factory.set_cookie_header()) + exc = ValueError( + "You cannot get the session after content from the " + "app_iter has been returned") + start_response(status, headers, (exc.__class__, exc, None)) + def close(): + if session_factory.used: + session_factory.close() + return wsgilib.add_start_close(app_iter, start, close) + + +class SessionFactory(object): + + + def __init__(self, environ, cookie_name='_SID_', + session_class=None, + session_expiration=60*12, # in minutes + **session_class_kw): + + self.created = False + self.used = False + self.environ = environ + self.cookie_name = cookie_name + self.session = None + self.session_class = session_class or FileSession + self.session_class_kw = session_class_kw + + self.expiration = session_expiration + + def __call__(self): + self.used = True + if self.session is not None: + return self.session.data() + cookies = request.get_cookies(self.environ) + session = None + if self.cookie_name in cookies: + self.sid = cookies[self.cookie_name].value + try: + session = self.session_class(self.sid, create=False, + **self.session_class_kw) + except KeyError: + # Invalid SID + pass + if session is None: + self.created = True + self.sid = self.make_sid() + session = self.session_class(self.sid, create=True, + **self.session_class_kw) + session.clean_up() + self.session = session + return session.data() + + def has_session(self): + if self.session is not None: + return True + cookies = request.get_cookies(self.environ) + if cookies.has_key(self.cookie_name): + return True + return False + + def make_sid(self): + # @@: need better algorithm + return (''.join(['%02d' % x for x in time.localtime(time.time())[:6]]) + + '-' + self.unique_id()) + + def unique_id(self, for_object=None): + """ + Generates an opaque, identifier string that is practically + guaranteed to be unique. If an object is passed, then its + id() is incorporated into the generation. Relies on md5 and + returns a 32 character long string. + """ + r = [time.time(), random.random()] + if hasattr(os, 'times'): + r.append(os.times()) + if for_object is not None: + r.append(id(for_object)) + content = str(r) + if six.PY3: + content = content.encode('utf8') + md5_hash = md5(content) + try: + return md5_hash.hexdigest() + except AttributeError: + # Older versions of Python didn't have hexdigest, so we'll + # do it manually + hexdigest = [] + for char in md5_hash.digest(): + hexdigest.append('%02x' % ord(char)) + return ''.join(hexdigest) + + def set_cookie_header(self): + c = SimpleCookie() + c[self.cookie_name] = self.sid + c[self.cookie_name]['path'] = '/' + + gmt_expiration_time = time.gmtime(time.time() + (self.expiration * 60)) + c[self.cookie_name]['expires'] = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", gmt_expiration_time) + + name, value = str(c).split(': ', 1) + return (name, value) + + def close(self): + if self.session is not None: + self.session.close() + + +last_cleanup = None +cleaning_up = False +cleanup_cycle = datetime.timedelta(seconds=15*60) #15 min + +class FileSession(object): + + def __init__(self, sid, create=False, session_file_path=tempfile.gettempdir(), + chmod=None, + expiration=2880, # in minutes: 48 hours + ): + if chmod and isinstance(chmod, (six.binary_type, six.text_type)): + chmod = int(chmod, 8) + self.chmod = chmod + if not sid: + # Invalid... + raise KeyError + self.session_file_path = session_file_path + self.sid = sid + if not create: + if not os.path.exists(self.filename()): + raise KeyError + self._data = None + + self.expiration = expiration + + + def filename(self): + return os.path.join(self.session_file_path, self.sid) + + def data(self): + if self._data is not None: + return self._data + if os.path.exists(self.filename()): + f = open(self.filename(), 'rb') + self._data = cPickle.load(f) + f.close() + else: + self._data = {} + return self._data + + def close(self): + if self._data is not None: + filename = self.filename() + exists = os.path.exists(filename) + if not self._data: + if exists: + os.unlink(filename) + else: + f = open(filename, 'wb') + cPickle.dump(self._data, f) + f.close() + if not exists and self.chmod: + os.chmod(filename, self.chmod) + + def _clean_up(self): + global cleaning_up + try: + exp_time = datetime.timedelta(seconds=self.expiration*60) + now = datetime.datetime.now() + + #Open every session and check that it isn't too old + for root, dirs, files in os.walk(self.session_file_path): + for f in files: + self._clean_up_file(f, exp_time=exp_time, now=now) + finally: + cleaning_up = False + + def _clean_up_file(self, f, exp_time, now): + t = f.split("-") + if len(t) != 2: + return + t = t[0] + try: + sess_time = datetime.datetime( + int(t[0:4]), + int(t[4:6]), + int(t[6:8]), + int(t[8:10]), + int(t[10:12]), + int(t[12:14])) + except ValueError: + # Probably not a session file at all + return + + if sess_time + exp_time < now: + os.remove(os.path.join(self.session_file_path, f)) + + def clean_up(self): + global last_cleanup, cleanup_cycle, cleaning_up + now = datetime.datetime.now() + + if cleaning_up: + return + + if not last_cleanup or last_cleanup + cleanup_cycle < now: + if not cleaning_up: + cleaning_up = True + try: + last_cleanup = now + t = threading.Thread(target=self._clean_up) + t.start() + except: + # Normally _clean_up should set cleaning_up + # to false, but if something goes wrong starting + # it... + cleaning_up = False + raise + +class _NoDefault(object): + def __repr__(self): + return '<dynamic default>' +NoDefault = _NoDefault() + +def make_session_middleware( + app, global_conf, + session_expiration=NoDefault, + expiration=NoDefault, + cookie_name=NoDefault, + session_file_path=NoDefault, + chmod=NoDefault): + """ + Adds a middleware that handles sessions for your applications. + The session is a peristent dictionary. To get this dictionary + in your application, use ``environ['paste.session.factory']()`` + which returns this persistent dictionary. + + Configuration: + + session_expiration: + The time each session lives, in minutes. This controls + the cookie expiration. Default 12 hours. + + expiration: + The time each session lives on disk. Old sessions are + culled from disk based on this. Default 48 hours. + + cookie_name: + The cookie name used to track the session. Use different + names to avoid session clashes. + + session_file_path: + Sessions are put in this location, default /tmp. + + chmod: + The octal chmod you want to apply to new sessions (e.g., 660 + to make the sessions group readable/writable) + + Each of these also takes from the global configuration. cookie_name + and chmod take from session_cookie_name and session_chmod + """ + if session_expiration is NoDefault: + session_expiration = global_conf.get('session_expiration', 60*12) + session_expiration = int(session_expiration) + if expiration is NoDefault: + expiration = global_conf.get('expiration', 60*48) + expiration = int(expiration) + if cookie_name is NoDefault: + cookie_name = global_conf.get('session_cookie_name', '_SID_') + if session_file_path is NoDefault: + session_file_path = global_conf.get('session_file_path', '/tmp') + if chmod is NoDefault: + chmod = global_conf.get('session_chmod', None) + return SessionMiddleware( + app, session_expiration=session_expiration, + expiration=expiration, cookie_name=cookie_name, + session_file_path=session_file_path, chmod=chmod) |