Index: third_party/cherrypy/lib/sessions.py |
=================================================================== |
--- third_party/cherrypy/lib/sessions.py (revision 0) |
+++ third_party/cherrypy/lib/sessions.py (revision 0) |
@@ -0,0 +1,871 @@ |
+"""Session implementation for CherryPy. |
+ |
+You need to edit your config file to use sessions. Here's an example:: |
+ |
+ [/] |
+ tools.sessions.on = True |
+ tools.sessions.storage_type = "file" |
+ tools.sessions.storage_path = "/home/site/sessions" |
+ tools.sessions.timeout = 60 |
+ |
+This sets the session to be stored in files in the directory /home/site/sessions, |
+and the session timeout to 60 minutes. If you omit ``storage_type`` the sessions |
+will be saved in RAM. ``tools.sessions.on`` is the only required line for |
+working sessions, the rest are optional. |
+ |
+By default, the session ID is passed in a cookie, so the client's browser must |
+have cookies enabled for your site. |
+ |
+To set data for the current session, use |
+``cherrypy.session['fieldname'] = 'fieldvalue'``; |
+to get data use ``cherrypy.session.get('fieldname')``. |
+ |
+================ |
+Locking sessions |
+================ |
+ |
+By default, the ``'locking'`` mode of sessions is ``'implicit'``, which means |
+the session is locked early and unlocked late. If you want to control when the |
+session data is locked and unlocked, set ``tools.sessions.locking = 'explicit'``. |
+Then call ``cherrypy.session.acquire_lock()`` and ``cherrypy.session.release_lock()``. |
+Regardless of which mode you use, the session is guaranteed to be unlocked when |
+the request is complete. |
+ |
+================= |
+Expiring Sessions |
+================= |
+ |
+You can force a session to expire with :func:`cherrypy.lib.sessions.expire`. |
+Simply call that function at the point you want the session to expire, and it |
+will cause the session cookie to expire client-side. |
+ |
+=========================== |
+Session Fixation Protection |
+=========================== |
+ |
+If CherryPy receives, via a request cookie, a session id that it does not |
+recognize, it will reject that id and create a new one to return in the |
+response cookie. This `helps prevent session fixation attacks |
+<http://en.wikipedia.org/wiki/Session_fixation#Regenerate_SID_on_each_request>`_. |
+However, CherryPy "recognizes" a session id by looking up the saved session |
+data for that id. Therefore, if you never save any session data, |
+**you will get a new session id for every request**. |
+ |
+================ |
+Sharing Sessions |
+================ |
+ |
+If you run multiple instances of CherryPy (for example via mod_python behind |
+Apache prefork), you most likely cannot use the RAM session backend, since each |
+instance of CherryPy will have its own memory space. Use a different backend |
+instead, and verify that all instances are pointing at the same file or db |
+location. Alternately, you might try a load balancer which makes sessions |
+"sticky". Google is your friend, there. |
+ |
+================ |
+Expiration Dates |
+================ |
+ |
+The response cookie will possess an expiration date to inform the client at |
+which point to stop sending the cookie back in requests. If the server time |
+and client time differ, expect sessions to be unreliable. **Make sure the |
+system time of your server is accurate**. |
+ |
+CherryPy defaults to a 60-minute session timeout, which also applies to the |
+cookie which is sent to the client. Unfortunately, some versions of Safari |
+("4 public beta" on Windows XP at least) appear to have a bug in their parsing |
+of the GMT expiration date--they appear to interpret the date as one hour in |
+the past. Sixty minutes minus one hour is pretty close to zero, so you may |
+experience this bug as a new session id for every request, unless the requests |
+are less than one second apart. To fix, try increasing the session.timeout. |
+ |
+On the other extreme, some users report Firefox sending cookies after their |
+expiration date, although this was on a system with an inaccurate system time. |
+Maybe FF doesn't trust system time. |
+""" |
+ |
+import datetime |
+import os |
+import random |
+import time |
+import threading |
+import types |
+from warnings import warn |
+ |
+import cherrypy |
+from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr |
+from cherrypy.lib import httputil |
+ |
+ |
+missing = object() |
+ |
+class Session(object): |
+ """A CherryPy dict-like Session object (one per request).""" |
+ |
+ _id = None |
+ |
+ id_observers = None |
+ "A list of callbacks to which to pass new id's." |
+ |
+ def _get_id(self): |
+ return self._id |
+ def _set_id(self, value): |
+ self._id = value |
+ for o in self.id_observers: |
+ o(value) |
+ id = property(_get_id, _set_id, doc="The current session ID.") |
+ |
+ timeout = 60 |
+ "Number of minutes after which to delete session data." |
+ |
+ locked = False |
+ """ |
+ If True, this session instance has exclusive read/write access |
+ to session data.""" |
+ |
+ loaded = False |
+ """ |
+ If True, data has been retrieved from storage. This should happen |
+ automatically on the first attempt to access session data.""" |
+ |
+ clean_thread = None |
+ "Class-level Monitor which calls self.clean_up." |
+ |
+ clean_freq = 5 |
+ "The poll rate for expired session cleanup in minutes." |
+ |
+ originalid = None |
+ "The session id passed by the client. May be missing or unsafe." |
+ |
+ missing = False |
+ "True if the session requested by the client did not exist." |
+ |
+ regenerated = False |
+ """ |
+ True if the application called session.regenerate(). This is not set by |
+ internal calls to regenerate the session id.""" |
+ |
+ debug=False |
+ |
+ def __init__(self, id=None, **kwargs): |
+ self.id_observers = [] |
+ self._data = {} |
+ |
+ for k, v in kwargs.items(): |
+ setattr(self, k, v) |
+ |
+ self.originalid = id |
+ self.missing = False |
+ if id is None: |
+ if self.debug: |
+ cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS') |
+ self._regenerate() |
+ else: |
+ self.id = id |
+ if not self._exists(): |
+ if self.debug: |
+ cherrypy.log('Expired or malicious session %r; ' |
+ 'making a new one' % id, 'TOOLS.SESSIONS') |
+ # Expired or malicious session. Make a new one. |
+ # See http://www.cherrypy.org/ticket/709. |
+ self.id = None |
+ self.missing = True |
+ self._regenerate() |
+ |
+ def now(self): |
+ """Generate the session specific concept of 'now'. |
+ |
+ Other session providers can override this to use alternative, |
+ possibly timezone aware, versions of 'now'. |
+ """ |
+ return datetime.datetime.now() |
+ |
+ def regenerate(self): |
+ """Replace the current session (with a new id).""" |
+ self.regenerated = True |
+ self._regenerate() |
+ |
+ def _regenerate(self): |
+ if self.id is not None: |
+ self.delete() |
+ |
+ old_session_was_locked = self.locked |
+ if old_session_was_locked: |
+ self.release_lock() |
+ |
+ self.id = None |
+ while self.id is None: |
+ self.id = self.generate_id() |
+ # Assert that the generated id is not already stored. |
+ if self._exists(): |
+ self.id = None |
+ |
+ if old_session_was_locked: |
+ self.acquire_lock() |
+ |
+ def clean_up(self): |
+ """Clean up expired sessions.""" |
+ pass |
+ |
+ def generate_id(self): |
+ """Return a new session id.""" |
+ return random20() |
+ |
+ def save(self): |
+ """Save session data.""" |
+ try: |
+ # If session data has never been loaded then it's never been |
+ # accessed: no need to save it |
+ if self.loaded: |
+ t = datetime.timedelta(seconds = self.timeout * 60) |
+ expiration_time = self.now() + t |
+ if self.debug: |
+ cherrypy.log('Saving with expiry %s' % expiration_time, |
+ 'TOOLS.SESSIONS') |
+ self._save(expiration_time) |
+ |
+ finally: |
+ if self.locked: |
+ # Always release the lock if the user didn't release it |
+ self.release_lock() |
+ |
+ def load(self): |
+ """Copy stored session data into this session instance.""" |
+ data = self._load() |
+ # data is either None or a tuple (session_data, expiration_time) |
+ if data is None or data[1] < self.now(): |
+ if self.debug: |
+ cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS') |
+ self._data = {} |
+ else: |
+ self._data = data[0] |
+ self.loaded = True |
+ |
+ # Stick the clean_thread in the class, not the instance. |
+ # The instances are created and destroyed per-request. |
+ cls = self.__class__ |
+ if self.clean_freq and not cls.clean_thread: |
+ # clean_up is in instancemethod and not a classmethod, |
+ # so that tool config can be accessed inside the method. |
+ t = cherrypy.process.plugins.Monitor( |
+ cherrypy.engine, self.clean_up, self.clean_freq * 60, |
+ name='Session cleanup') |
+ t.subscribe() |
+ cls.clean_thread = t |
+ t.start() |
+ |
+ def delete(self): |
+ """Delete stored session data.""" |
+ self._delete() |
+ |
+ def __getitem__(self, key): |
+ if not self.loaded: self.load() |
+ return self._data[key] |
+ |
+ def __setitem__(self, key, value): |
+ if not self.loaded: self.load() |
+ self._data[key] = value |
+ |
+ def __delitem__(self, key): |
+ if not self.loaded: self.load() |
+ del self._data[key] |
+ |
+ def pop(self, key, default=missing): |
+ """Remove the specified key and return the corresponding value. |
+ If key is not found, default is returned if given, |
+ otherwise KeyError is raised. |
+ """ |
+ if not self.loaded: self.load() |
+ if default is missing: |
+ return self._data.pop(key) |
+ else: |
+ return self._data.pop(key, default) |
+ |
+ def __contains__(self, key): |
+ if not self.loaded: self.load() |
+ return key in self._data |
+ |
+ if hasattr({}, 'has_key'): |
+ def has_key(self, key): |
+ """D.has_key(k) -> True if D has a key k, else False.""" |
+ if not self.loaded: self.load() |
+ return key in self._data |
+ |
+ def get(self, key, default=None): |
+ """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" |
+ if not self.loaded: self.load() |
+ return self._data.get(key, default) |
+ |
+ def update(self, d): |
+ """D.update(E) -> None. Update D from E: for k in E: D[k] = E[k].""" |
+ if not self.loaded: self.load() |
+ self._data.update(d) |
+ |
+ def setdefault(self, key, default=None): |
+ """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D.""" |
+ if not self.loaded: self.load() |
+ return self._data.setdefault(key, default) |
+ |
+ def clear(self): |
+ """D.clear() -> None. Remove all items from D.""" |
+ if not self.loaded: self.load() |
+ self._data.clear() |
+ |
+ def keys(self): |
+ """D.keys() -> list of D's keys.""" |
+ if not self.loaded: self.load() |
+ return self._data.keys() |
+ |
+ def items(self): |
+ """D.items() -> list of D's (key, value) pairs, as 2-tuples.""" |
+ if not self.loaded: self.load() |
+ return self._data.items() |
+ |
+ def values(self): |
+ """D.values() -> list of D's values.""" |
+ if not self.loaded: self.load() |
+ return self._data.values() |
+ |
+ |
+class RamSession(Session): |
+ |
+ # Class-level objects. Don't rebind these! |
+ cache = {} |
+ locks = {} |
+ |
+ def clean_up(self): |
+ """Clean up expired sessions.""" |
+ now = self.now() |
+ for id, (data, expiration_time) in copyitems(self.cache): |
+ if expiration_time <= now: |
+ try: |
+ del self.cache[id] |
+ except KeyError: |
+ pass |
+ try: |
+ del self.locks[id] |
+ except KeyError: |
+ pass |
+ |
+ # added to remove obsolete lock objects |
+ for id in list(self.locks): |
+ if id not in self.cache: |
+ self.locks.pop(id, None) |
+ |
+ def _exists(self): |
+ return self.id in self.cache |
+ |
+ def _load(self): |
+ return self.cache.get(self.id) |
+ |
+ def _save(self, expiration_time): |
+ self.cache[self.id] = (self._data, expiration_time) |
+ |
+ def _delete(self): |
+ self.cache.pop(self.id, None) |
+ |
+ def acquire_lock(self): |
+ """Acquire an exclusive lock on the currently-loaded session data.""" |
+ self.locked = True |
+ self.locks.setdefault(self.id, threading.RLock()).acquire() |
+ |
+ def release_lock(self): |
+ """Release the lock on the currently-loaded session data.""" |
+ self.locks[self.id].release() |
+ self.locked = False |
+ |
+ def __len__(self): |
+ """Return the number of active sessions.""" |
+ return len(self.cache) |
+ |
+ |
+class FileSession(Session): |
+ """Implementation of the File backend for sessions |
+ |
+ storage_path |
+ The folder where session data will be saved. Each session |
+ will be saved as pickle.dump(data, expiration_time) in its own file; |
+ the filename will be self.SESSION_PREFIX + self.id. |
+ |
+ """ |
+ |
+ SESSION_PREFIX = 'session-' |
+ LOCK_SUFFIX = '.lock' |
+ pickle_protocol = pickle.HIGHEST_PROTOCOL |
+ |
+ def __init__(self, id=None, **kwargs): |
+ # The 'storage_path' arg is required for file-based sessions. |
+ kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) |
+ Session.__init__(self, id=id, **kwargs) |
+ |
+ def setup(cls, **kwargs): |
+ """Set up the storage system for file-based sessions. |
+ |
+ This should only be called once per process; this will be done |
+ automatically when using sessions.init (as the built-in Tool does). |
+ """ |
+ # The 'storage_path' arg is required for file-based sessions. |
+ kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) |
+ |
+ for k, v in kwargs.items(): |
+ setattr(cls, k, v) |
+ |
+ # Warn if any lock files exist at startup. |
+ lockfiles = [fname for fname in os.listdir(cls.storage_path) |
+ if (fname.startswith(cls.SESSION_PREFIX) |
+ and fname.endswith(cls.LOCK_SUFFIX))] |
+ if lockfiles: |
+ plural = ('', 's')[len(lockfiles) > 1] |
+ warn("%s session lockfile%s found at startup. If you are " |
+ "only running one process, then you may need to " |
+ "manually delete the lockfiles found at %r." |
+ % (len(lockfiles), plural, cls.storage_path)) |
+ setup = classmethod(setup) |
+ |
+ def _get_file_path(self): |
+ f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id) |
+ if not os.path.abspath(f).startswith(self.storage_path): |
+ raise cherrypy.HTTPError(400, "Invalid session id in cookie.") |
+ return f |
+ |
+ def _exists(self): |
+ path = self._get_file_path() |
+ return os.path.exists(path) |
+ |
+ def _load(self, path=None): |
+ if path is None: |
+ path = self._get_file_path() |
+ try: |
+ f = open(path, "rb") |
+ try: |
+ return pickle.load(f) |
+ finally: |
+ f.close() |
+ except (IOError, EOFError): |
+ return None |
+ |
+ def _save(self, expiration_time): |
+ f = open(self._get_file_path(), "wb") |
+ try: |
+ pickle.dump((self._data, expiration_time), f, self.pickle_protocol) |
+ finally: |
+ f.close() |
+ |
+ def _delete(self): |
+ try: |
+ os.unlink(self._get_file_path()) |
+ except OSError: |
+ pass |
+ |
+ def acquire_lock(self, path=None): |
+ """Acquire an exclusive lock on the currently-loaded session data.""" |
+ if path is None: |
+ path = self._get_file_path() |
+ path += self.LOCK_SUFFIX |
+ while True: |
+ try: |
+ lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL) |
+ except OSError: |
+ time.sleep(0.1) |
+ else: |
+ os.close(lockfd) |
+ break |
+ self.locked = True |
+ |
+ def release_lock(self, path=None): |
+ """Release the lock on the currently-loaded session data.""" |
+ if path is None: |
+ path = self._get_file_path() |
+ os.unlink(path + self.LOCK_SUFFIX) |
+ self.locked = False |
+ |
+ def clean_up(self): |
+ """Clean up expired sessions.""" |
+ now = self.now() |
+ # Iterate over all session files in self.storage_path |
+ for fname in os.listdir(self.storage_path): |
+ if (fname.startswith(self.SESSION_PREFIX) |
+ and not fname.endswith(self.LOCK_SUFFIX)): |
+ # We have a session file: lock and load it and check |
+ # if it's expired. If it fails, nevermind. |
+ path = os.path.join(self.storage_path, fname) |
+ self.acquire_lock(path) |
+ try: |
+ contents = self._load(path) |
+ # _load returns None on IOError |
+ if contents is not None: |
+ data, expiration_time = contents |
+ if expiration_time < now: |
+ # Session expired: deleting it |
+ os.unlink(path) |
+ finally: |
+ self.release_lock(path) |
+ |
+ def __len__(self): |
+ """Return the number of active sessions.""" |
+ return len([fname for fname in os.listdir(self.storage_path) |
+ if (fname.startswith(self.SESSION_PREFIX) |
+ and not fname.endswith(self.LOCK_SUFFIX))]) |
+ |
+ |
+class PostgresqlSession(Session): |
+ """ Implementation of the PostgreSQL backend for sessions. It assumes |
+ a table like this:: |
+ |
+ create table session ( |
+ id varchar(40), |
+ data text, |
+ expiration_time timestamp |
+ ) |
+ |
+ You must provide your own get_db function. |
+ """ |
+ |
+ pickle_protocol = pickle.HIGHEST_PROTOCOL |
+ |
+ def __init__(self, id=None, **kwargs): |
+ Session.__init__(self, id, **kwargs) |
+ self.cursor = self.db.cursor() |
+ |
+ def setup(cls, **kwargs): |
+ """Set up the storage system for Postgres-based sessions. |
+ |
+ This should only be called once per process; this will be done |
+ automatically when using sessions.init (as the built-in Tool does). |
+ """ |
+ for k, v in kwargs.items(): |
+ setattr(cls, k, v) |
+ |
+ self.db = self.get_db() |
+ setup = classmethod(setup) |
+ |
+ def __del__(self): |
+ if self.cursor: |
+ self.cursor.close() |
+ self.db.commit() |
+ |
+ def _exists(self): |
+ # Select session data from table |
+ self.cursor.execute('select data, expiration_time from session ' |
+ 'where id=%s', (self.id,)) |
+ rows = self.cursor.fetchall() |
+ return bool(rows) |
+ |
+ def _load(self): |
+ # Select session data from table |
+ self.cursor.execute('select data, expiration_time from session ' |
+ 'where id=%s', (self.id,)) |
+ rows = self.cursor.fetchall() |
+ if not rows: |
+ return None |
+ |
+ pickled_data, expiration_time = rows[0] |
+ data = pickle.loads(pickled_data) |
+ return data, expiration_time |
+ |
+ def _save(self, expiration_time): |
+ pickled_data = pickle.dumps(self._data, self.pickle_protocol) |
+ self.cursor.execute('update session set data = %s, ' |
+ 'expiration_time = %s where id = %s', |
+ (pickled_data, expiration_time, self.id)) |
+ |
+ def _delete(self): |
+ self.cursor.execute('delete from session where id=%s', (self.id,)) |
+ |
+ def acquire_lock(self): |
+ """Acquire an exclusive lock on the currently-loaded session data.""" |
+ # We use the "for update" clause to lock the row |
+ self.locked = True |
+ self.cursor.execute('select id from session where id=%s for update', |
+ (self.id,)) |
+ |
+ def release_lock(self): |
+ """Release the lock on the currently-loaded session data.""" |
+ # We just close the cursor and that will remove the lock |
+ # introduced by the "for update" clause |
+ self.cursor.close() |
+ self.locked = False |
+ |
+ def clean_up(self): |
+ """Clean up expired sessions.""" |
+ self.cursor.execute('delete from session where expiration_time < %s', |
+ (self.now(),)) |
+ |
+ |
+class MemcachedSession(Session): |
+ |
+ # The most popular memcached client for Python isn't thread-safe. |
+ # Wrap all .get and .set operations in a single lock. |
+ mc_lock = threading.RLock() |
+ |
+ # This is a seperate set of locks per session id. |
+ locks = {} |
+ |
+ servers = ['127.0.0.1:11211'] |
+ |
+ def setup(cls, **kwargs): |
+ """Set up the storage system for memcached-based sessions. |
+ |
+ This should only be called once per process; this will be done |
+ automatically when using sessions.init (as the built-in Tool does). |
+ """ |
+ for k, v in kwargs.items(): |
+ setattr(cls, k, v) |
+ |
+ import memcache |
+ cls.cache = memcache.Client(cls.servers) |
+ setup = classmethod(setup) |
+ |
+ def _get_id(self): |
+ return self._id |
+ def _set_id(self, value): |
+ # This encode() call is where we differ from the superclass. |
+ # Memcache keys MUST be byte strings, not unicode. |
+ if isinstance(value, unicodestr): |
+ value = value.encode('utf-8') |
+ |
+ self._id = value |
+ for o in self.id_observers: |
+ o(value) |
+ id = property(_get_id, _set_id, doc="The current session ID.") |
+ |
+ def _exists(self): |
+ self.mc_lock.acquire() |
+ try: |
+ return bool(self.cache.get(self.id)) |
+ finally: |
+ self.mc_lock.release() |
+ |
+ def _load(self): |
+ self.mc_lock.acquire() |
+ try: |
+ return self.cache.get(self.id) |
+ finally: |
+ self.mc_lock.release() |
+ |
+ def _save(self, expiration_time): |
+ # Send the expiration time as "Unix time" (seconds since 1/1/1970) |
+ td = int(time.mktime(expiration_time.timetuple())) |
+ self.mc_lock.acquire() |
+ try: |
+ if not self.cache.set(self.id, (self._data, expiration_time), td): |
+ raise AssertionError("Session data for id %r not set." % self.id) |
+ finally: |
+ self.mc_lock.release() |
+ |
+ def _delete(self): |
+ self.cache.delete(self.id) |
+ |
+ def acquire_lock(self): |
+ """Acquire an exclusive lock on the currently-loaded session data.""" |
+ self.locked = True |
+ self.locks.setdefault(self.id, threading.RLock()).acquire() |
+ |
+ def release_lock(self): |
+ """Release the lock on the currently-loaded session data.""" |
+ self.locks[self.id].release() |
+ self.locked = False |
+ |
+ def __len__(self): |
+ """Return the number of active sessions.""" |
+ raise NotImplementedError |
+ |
+ |
+# Hook functions (for CherryPy tools) |
+ |
+def save(): |
+ """Save any changed session data.""" |
+ |
+ if not hasattr(cherrypy.serving, "session"): |
+ return |
+ request = cherrypy.serving.request |
+ response = cherrypy.serving.response |
+ |
+ # Guard against running twice |
+ if hasattr(request, "_sessionsaved"): |
+ return |
+ request._sessionsaved = True |
+ |
+ if response.stream: |
+ # If the body is being streamed, we have to save the data |
+ # *after* the response has been written out |
+ request.hooks.attach('on_end_request', cherrypy.session.save) |
+ else: |
+ # If the body is not being streamed, we save the data now |
+ # (so we can release the lock). |
+ if isinstance(response.body, types.GeneratorType): |
+ response.collapse_body() |
+ cherrypy.session.save() |
+save.failsafe = True |
+ |
+def close(): |
+ """Close the session object for this request.""" |
+ sess = getattr(cherrypy.serving, "session", None) |
+ if getattr(sess, "locked", False): |
+ # If the session is still locked we release the lock |
+ sess.release_lock() |
+close.failsafe = True |
+close.priority = 90 |
+ |
+ |
+def init(storage_type='ram', path=None, path_header=None, name='session_id', |
+ timeout=60, domain=None, secure=False, clean_freq=5, |
+ persistent=True, httponly=False, debug=False, **kwargs): |
+ """Initialize session object (using cookies). |
+ |
+ storage_type |
+ One of 'ram', 'file', 'postgresql', 'memcached'. This will be |
+ used to look up the corresponding class in cherrypy.lib.sessions |
+ globals. For example, 'file' will use the FileSession class. |
+ |
+ path |
+ The 'path' value to stick in the response cookie metadata. |
+ |
+ path_header |
+ If 'path' is None (the default), then the response |
+ cookie 'path' will be pulled from request.headers[path_header]. |
+ |
+ name |
+ The name of the cookie. |
+ |
+ timeout |
+ The expiration timeout (in minutes) for the stored session data. |
+ If 'persistent' is True (the default), this is also the timeout |
+ for the cookie. |
+ |
+ domain |
+ The cookie domain. |
+ |
+ secure |
+ If False (the default) the cookie 'secure' value will not |
+ be set. If True, the cookie 'secure' value will be set (to 1). |
+ |
+ clean_freq (minutes) |
+ The poll rate for expired session cleanup. |
+ |
+ persistent |
+ If True (the default), the 'timeout' argument will be used |
+ to expire the cookie. If False, the cookie will not have an expiry, |
+ and the cookie will be a "session cookie" which expires when the |
+ browser is closed. |
+ |
+ httponly |
+ If False (the default) the cookie 'httponly' value will not be set. |
+ If True, the cookie 'httponly' value will be set (to 1). |
+ |
+ Any additional kwargs will be bound to the new Session instance, |
+ and may be specific to the storage type. See the subclass of Session |
+ you're using for more information. |
+ """ |
+ |
+ request = cherrypy.serving.request |
+ |
+ # Guard against running twice |
+ if hasattr(request, "_session_init_flag"): |
+ return |
+ request._session_init_flag = True |
+ |
+ # Check if request came with a session ID |
+ id = None |
+ if name in request.cookie: |
+ id = request.cookie[name].value |
+ if debug: |
+ cherrypy.log('ID obtained from request.cookie: %r' % id, |
+ 'TOOLS.SESSIONS') |
+ |
+ # Find the storage class and call setup (first time only). |
+ storage_class = storage_type.title() + 'Session' |
+ storage_class = globals()[storage_class] |
+ if not hasattr(cherrypy, "session"): |
+ if hasattr(storage_class, "setup"): |
+ storage_class.setup(**kwargs) |
+ |
+ # Create and attach a new Session instance to cherrypy.serving. |
+ # It will possess a reference to (and lock, and lazily load) |
+ # the requested session data. |
+ kwargs['timeout'] = timeout |
+ kwargs['clean_freq'] = clean_freq |
+ cherrypy.serving.session = sess = storage_class(id, **kwargs) |
+ sess.debug = debug |
+ def update_cookie(id): |
+ """Update the cookie every time the session id changes.""" |
+ cherrypy.serving.response.cookie[name] = id |
+ sess.id_observers.append(update_cookie) |
+ |
+ # Create cherrypy.session which will proxy to cherrypy.serving.session |
+ if not hasattr(cherrypy, "session"): |
+ cherrypy.session = cherrypy._ThreadLocalProxy('session') |
+ |
+ if persistent: |
+ cookie_timeout = timeout |
+ else: |
+ # See http://support.microsoft.com/kb/223799/EN-US/ |
+ # and http://support.mozilla.com/en-US/kb/Cookies |
+ cookie_timeout = None |
+ set_response_cookie(path=path, path_header=path_header, name=name, |
+ timeout=cookie_timeout, domain=domain, secure=secure, |
+ httponly=httponly) |
+ |
+ |
+def set_response_cookie(path=None, path_header=None, name='session_id', |
+ timeout=60, domain=None, secure=False, httponly=False): |
+ """Set a response cookie for the client. |
+ |
+ path |
+ the 'path' value to stick in the response cookie metadata. |
+ |
+ path_header |
+ if 'path' is None (the default), then the response |
+ cookie 'path' will be pulled from request.headers[path_header]. |
+ |
+ name |
+ the name of the cookie. |
+ |
+ timeout |
+ the expiration timeout for the cookie. If 0 or other boolean |
+ False, no 'expires' param will be set, and the cookie will be a |
+ "session cookie" which expires when the browser is closed. |
+ |
+ domain |
+ the cookie domain. |
+ |
+ secure |
+ if False (the default) the cookie 'secure' value will not |
+ be set. If True, the cookie 'secure' value will be set (to 1). |
+ |
+ httponly |
+ If False (the default) the cookie 'httponly' value will not be set. |
+ If True, the cookie 'httponly' value will be set (to 1). |
+ |
+ """ |
+ # Set response cookie |
+ cookie = cherrypy.serving.response.cookie |
+ cookie[name] = cherrypy.serving.session.id |
+ cookie[name]['path'] = (path or cherrypy.serving.request.headers.get(path_header) |
+ or '/') |
+ |
+ # We'd like to use the "max-age" param as indicated in |
+ # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't |
+ # save it to disk and the session is lost if people close |
+ # the browser. So we have to use the old "expires" ... sigh ... |
+## cookie[name]['max-age'] = timeout * 60 |
+ if timeout: |
+ e = time.time() + (timeout * 60) |
+ cookie[name]['expires'] = httputil.HTTPDate(e) |
+ if domain is not None: |
+ cookie[name]['domain'] = domain |
+ if secure: |
+ cookie[name]['secure'] = 1 |
+ if httponly: |
+ if not cookie[name].isReservedKey('httponly'): |
+ raise ValueError("The httponly cookie token is not supported.") |
+ cookie[name]['httponly'] = 1 |
+ |
+def expire(): |
+ """Expire the current session cookie.""" |
+ name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id') |
+ one_year = 60 * 60 * 24 * 365 |
+ e = time.time() - one_year |
+ cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e) |
+ |
+ |
Property changes on: third_party/cherrypy/lib/sessions.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |