Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/trunk/cherrypy/lib/sessions.py

Revision 2683 (checked in by fumanchu, 3 months ago)

More and more and more doc work.

  • Property svn:eol-style set to native
Line 
1 """Session implementation for CherryPy.
2
3 You need to edit your config file to use sessions. Here's an example::
4
5     [/]
6     tools.sessions.on = True
7     tools.sessions.storage_type = "file"
8     tools.sessions.storage_path = "/home/site/sessions"
9     tools.sessions.timeout = 60
10
11 This sets the session to be stored in files in the directory /home/site/sessions,
12 and the session timeout to 60 minutes. If you omit ``storage_type`` the sessions
13 will be saved in RAM.  ``tools.sessions.on`` is the only required line for
14 working sessions, the rest are optional.
15
16 By default, the session ID is passed in a cookie, so the client's browser must
17 have cookies enabled for your site.
18
19 To set data for the current session, use
20 ``cherrypy.session['fieldname'] = 'fieldvalue'``;
21 to get data use ``cherrypy.session.get('fieldname')``.
22
23 ================
24 Locking sessions
25 ================
26
27 By default, the ``'locking'`` mode of sessions is ``'implicit'``, which means
28 the session is locked early and unlocked late. If you want to control when the
29 session data is locked and unlocked, set ``tools.sessions.locking = 'explicit'``.
30 Then call ``cherrypy.session.acquire_lock()`` and ``cherrypy.session.release_lock()``.
31 Regardless of which mode you use, the session is guaranteed to be unlocked when
32 the request is complete.
33
34 =================
35 Expiring Sessions
36 =================
37
38 You can force a session to expire with :func:`cherrypy.lib.sessions.expire`.
39 Simply call that function at the point you want the session to expire, and it
40 will cause the session cookie to expire client-side.
41
42 ===========================
43 Session Fixation Protection
44 ===========================
45
46 If CherryPy receives, via a request cookie, a session id that it does not
47 recognize, it will reject that id and create a new one to return in the
48 response cookie. This `helps prevent session fixation attacks
49 <http://en.wikipedia.org/wiki/Session_fixation#Regenerate_SID_on_each_request>`_.
50 However, CherryPy "recognizes" a session id by looking up the saved session
51 data for that id. Therefore, if you never save any session data,
52 **you will get a new session id for every request**.
53
54 ================
55 Sharing Sessions
56 ================
57
58 If you run multiple instances of CherryPy (for example via mod_python behind
59 Apache prefork), you most likely cannot use the RAM session backend, since each
60 instance of CherryPy will have its own memory space. Use a different backend
61 instead, and verify that all instances are pointing at the same file or db
62 location. Alternately, you might try a load balancer which makes sessions
63 "sticky". Google is your friend, there.
64
65 ================
66 Expiration Dates
67 ================
68
69 The response cookie will possess an expiration date to inform the client at
70 which point to stop sending the cookie back in requests. If the server time
71 and client time differ, expect sessions to be unreliable. **Make sure the
72 system time of your server is accurate**.
73
74 CherryPy defaults to a 60-minute session timeout, which also applies to the
75 cookie which is sent to the client. Unfortunately, some versions of Safari
76 ("4 public beta" on Windows XP at least) appear to have a bug in their parsing
77 of the GMT expiration date--they appear to interpret the date as one hour in
78 the past. Sixty minutes minus one hour is pretty close to zero, so you may
79 experience this bug as a new session id for every request, unless the requests
80 are less than one second apart. To fix, try increasing the session.timeout.
81
82 On the other extreme, some users report Firefox sending cookies after their
83 expiration date, although this was on a system with an inaccurate system time.
84 Maybe FF doesn't trust system time.
85 """
86
87 import datetime
88 import os
89 try:
90     import cPickle as pickle
91 except ImportError:
92     import pickle
93 import random
94 try:
95     # Python 2.5+
96     from hashlib import sha1 as sha
97 except ImportError:
98     from sha import new as sha
99 import time
100 import threading
101 import types
102 from warnings import warn
103
104 import cherrypy
105 from cherrypy.lib import httputil
106
107
108 missing = object()
109
110 class Session(object):
111     """A CherryPy dict-like Session object (one per request)."""
112    
113     _id = None
114    
115     id_observers = None
116     "A list of callbacks to which to pass new id's."
117    
118     def _get_id(self):
119         return self._id
120     def _set_id(self, value):
121         self._id = value
122         for o in self.id_observers:
123             o(value)
124     id = property(_get_id, _set_id, doc="The current session ID.")
125    
126     timeout = 60
127     "Number of minutes after which to delete session data."
128    
129     locked = False
130     """
131     If True, this session instance has exclusive read/write access
132     to session data."""
133    
134     loaded = False
135     """
136     If True, data has been retrieved from storage. This should happen
137     automatically on the first attempt to access session data."""
138    
139     clean_thread = None
140     "Class-level Monitor which calls self.clean_up."
141    
142     clean_freq = 5
143     "The poll rate for expired session cleanup in minutes."
144    
145     originalid = None
146     "The session id passed by the client. May be missing or unsafe."
147    
148     missing = False
149     "True if the session requested by the client did not exist."
150    
151     regenerated = False
152     """
153     True if the application called session.regenerate(). This is not set by
154     internal calls to regenerate the session id."""
155    
156     debug=False
157    
158     def __init__(self, id=None, **kwargs):
159         self.id_observers = []
160         self._data = {}
161        
162         for k, v in kwargs.items():
163             setattr(self, k, v)
164        
165         self.originalid = id
166         self.missing = False
167         if id is None:
168             if self.debug:
169                 cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS')
170             self._regenerate()
171         else:
172             self.id = id
173             if not self._exists():
174                 if self.debug:
175                     cherrypy.log('Expired or malicious session %r; '
176                                  'making a new one' % id, 'TOOLS.SESSIONS')
177                 # Expired or malicious session. Make a new one.
178                 # See http://www.cherrypy.org/ticket/709.
179                 self.id = None
180                 self.missing = True
181                 self._regenerate()
182    
183     def regenerate(self):
184         """Replace the current session (with a new id)."""
185         self.regenerated = True
186         self._regenerate()
187    
188     def _regenerate(self):
189         if self.id is not None:
190             self.delete()
191        
192         old_session_was_locked = self.locked
193         if old_session_was_locked:
194             self.release_lock()
195        
196         self.id = None
197         while self.id is None:
198             self.id = self.generate_id()
199             # Assert that the generated id is not already stored.
200             if self._exists():
201                 self.id = None
202        
203         if old_session_was_locked:
204             self.acquire_lock()
205    
206     def clean_up(self):
207         """Clean up expired sessions."""
208         pass
209    
210     try:
211         os.urandom(20)
212     except (AttributeError, NotImplementedError):
213         # os.urandom not available until Python 2.4. Fall back to random.random.
214         def generate_id(self):
215             """Return a new session id."""
216             return sha('%s' % random.random()).hexdigest()
217     else:
218         def generate_id(self):
219             """Return a new session id."""
220             return os.urandom(20).encode('hex')
221    
222     def save(self):
223         """Save session data."""
224         try:
225             # If session data has never been loaded then it's never been
226             #   accessed: no need to save it
227             if self.loaded:
228                 t = datetime.timedelta(seconds = self.timeout * 60)
229                 expiration_time = datetime.datetime.now() + t
230                 if self.debug:
231                     cherrypy.log('Saving with expiry %s' % expiration_time,
232                                  'TOOLS.SESSIONS')
233                 self._save(expiration_time)
234            
235         finally:
236             if self.locked:
237                 # Always release the lock if the user didn't release it
238                 self.release_lock()
239    
240     def load(self):
241         """Copy stored session data into this session instance."""
242         data = self._load()
243         # data is either None or a tuple (session_data, expiration_time)
244         if data is None or data[1] < datetime.datetime.now():
245             if self.debug:
246                 cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS')
247             self._data = {}
248         else:
249             self._data = data[0]
250         self.loaded = True
251        
252         # Stick the clean_thread in the class, not the instance.
253         # The instances are created and destroyed per-request.
254         cls = self.__class__
255         if self.clean_freq and not cls.clean_thread:
256             # clean_up is in instancemethod and not a classmethod,
257             # so that tool config can be accessed inside the method.
258             t = cherrypy.process.plugins.Monitor(
259                 cherrypy.engine, self.clean_up, self.clean_freq * 60,
260                 name='Session cleanup')
261             t.subscribe()
262             cls.clean_thread = t
263             t.start()
264    
265     def delete(self):
266         """Delete stored session data."""
267         self._delete()
268    
269     def __getitem__(self, key):
270         if not self.loaded: self.load()
271         return self._data[key]
272    
273     def __setitem__(self, key, value):
274         if not self.loaded: self.load()
275         self._data[key] = value
276    
277     def __delitem__(self, key):
278         if not self.loaded: self.load()
279         del self._data[key]
280    
281     def pop(self, key, default=missing):
282         """Remove the specified key and return the corresponding value.
283         If key is not found, default is returned if given,
284         otherwise KeyError is raised.
285         """
286         if not self.loaded: self.load()
287         if default is missing:
288             return self._data.pop(key)
289         else:
290             return self._data.pop(key, default)
291    
292     def __contains__(self, key):
293         if not self.loaded: self.load()
294         return key in self._data
295    
296     def has_key(self, key):
297         """D.has_key(k) -> True if D has a key k, else False."""
298         if not self.loaded: self.load()
299         return key in self._data
300    
301     def get(self, key, default=None):
302         """D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""
303         if not self.loaded: self.load()
304         return self._data.get(key, default)
305    
306     def update(self, d):
307         """D.update(E) -> None.  Update D from E: for k in E: D[k] = E[k]."""
308         if not self.loaded: self.load()
309         self._data.update(d)
310    
311     def setdefault(self, key, default=None):
312         """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
313         if not self.loaded: self.load()
314         return self._data.setdefault(key, default)
315    
316     def clear(self):
317         """D.clear() -> None.  Remove all items from D."""
318         if not self.loaded: self.load()
319         self._data.clear()
320    
321     def keys(self):
322         """D.keys() -> list of D's keys."""
323         if not self.loaded: self.load()
324         return self._data.keys()
325    
326     def items(self):
327         """D.items() -> list of D's (key, value) pairs, as 2-tuples."""
328         if not self.loaded: self.load()
329         return self._data.items()
330    
331     def values(self):
332         """D.values() -> list of D's values."""
333         if not self.loaded: self.load()
334         return self._data.values()
335
336
337 class RamSession(Session):
338    
339     # Class-level objects. Don't rebind these!
340     cache = {}
341     locks = {}
342    
343     def clean_up(self):
344         """Clean up expired sessions."""
345         now = datetime.datetime.now()
346         for id, (data, expiration_time) in self.cache.items():
347             if expiration_time <= now:
348                 try:
349                     del self.cache[id]
350                 except KeyError:
351                     pass
352                 try:
353                     del self.locks[id]
354                 except KeyError:
355                     pass
356    
357     def _exists(self):
358         return self.id in self.cache
359    
360     def _load(self):
361         return self.cache.get(self.id)
362    
363     def _save(self, expiration_time):
364         self.cache[self.id] = (self._data, expiration_time)
365    
366     def _delete(self):
367         self.cache.pop(self.id, None)
368    
369     def acquire_lock(self):
370         """Acquire an exclusive lock on the currently-loaded session data."""
371         self.locked = True
372         self.locks.setdefault(self.id, threading.RLock()).acquire()
373    
374     def release_lock(self):
375         """Release the lock on the currently-loaded session data."""
376         self.locks[self.id].release()
377         self.locked = False
378    
379     def __len__(self):
380         """Return the number of active sessions."""
381         return len(self.cache)
382
383
384 class FileSession(Session):
385     """Implementation of the File backend for sessions
386     
387     storage_path
388         The folder where session data will be saved. Each session
389         will be saved as pickle.dump(data, expiration_time) in its own file;
390         the filename will be self.SESSION_PREFIX + self.id.
391     
392     """
393    
394     SESSION_PREFIX = 'session-'
395     LOCK_SUFFIX = '.lock'
396     pickle_protocol = pickle.HIGHEST_PROTOCOL
397    
398     def __init__(self, id=None, **kwargs):
399         # The 'storage_path' arg is required for file-based sessions.
400         kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
401         Session.__init__(self, id=id, **kwargs)
402    
403     def setup(cls, **kwargs):
404         """Set up the storage system for file-based sessions.
405         
406         This should only be called once per process; this will be done
407         automatically when using sessions.init (as the built-in Tool does).
408         """
409         # The 'storage_path' arg is required for file-based sessions.
410         kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
411        
412         for k, v in kwargs.items():
413             setattr(cls, k, v)
414        
415         # Warn if any lock files exist at startup.
416         lockfiles = [fname for fname in os.listdir(cls.storage_path)
417                      if (fname.startswith(cls.SESSION_PREFIX)
418                          and fname.endswith(cls.LOCK_SUFFIX))]
419         if lockfiles:
420             plural = ('', 's')[len(lockfiles) > 1]
421             warn("%s session lockfile%s found at startup. If you are "
422                  "only running one process, then you may need to "
423                  "manually delete the lockfiles found at %r."
424                  % (len(lockfiles), plural, cls.storage_path))
425     setup = classmethod(setup)
426    
427     def _get_file_path(self):
428         f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
429         if not os.path.abspath(f).startswith(self.storage_path):
430             raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
431         return f
432    
433     def _exists(self):
434         path = self._get_file_path()
435         return os.path.exists(path)
436    
437     def _load(self, path=None):
438         if path is None:
439             path = self._get_file_path()
440         try:
441             f = open(path, "rb")
442             try:
443                 return pickle.load(f)
444             finally:
445                 f.close()
446         except (IOError, EOFError):
447             return None
448    
449     def _save(self, expiration_time):
450         f = open(self._get_file_path(), "wb")
451         try:
452             pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
453         finally:
454             f.close()
455    
456     def _delete(self):
457         try:
458             os.unlink(self._get_file_path())
459         except OSError:
460             pass
461    
462     def acquire_lock(self, path=None):
463         """Acquire an exclusive lock on the currently-loaded session data."""
464         if path is None:
465             path = self._get_file_path()
466         path += self.LOCK_SUFFIX
467         while True:
468             try:
469                 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
470             except OSError:
471                 time.sleep(0.1)
472             else:
473                 os.close(lockfd)
474                 break
475         self.locked = True
476    
477     def release_lock(self, path=None):
478         """Release the lock on the currently-loaded session data."""
479         if path is None:
480             path = self._get_file_path()
481         os.unlink(path + self.LOCK_SUFFIX)
482         self.locked = False
483    
484     def clean_up(self):
485         """Clean up expired sessions."""
486         now = datetime.datetime.now()
487         # Iterate over all session files in self.storage_path
488         for fname in os.listdir(self.storage_path):
489             if (fname.startswith(self.SESSION_PREFIX)
490                 and not fname.endswith(self.LOCK_SUFFIX)):
491                 # We have a session file: lock and load it and check
492                 #   if it's expired. If it fails, nevermind.
493                 path = os.path.join(self.storage_path, fname)
494                 self.acquire_lock(path)
495                 try:
496                     contents = self._load(path)
497                     # _load returns None on IOError
498                     if contents is not None:
499                         data, expiration_time = contents
500                         if expiration_time < now:
501                             # Session expired: deleting it
502                             os.unlink(path)
503                 finally:
504                     self.release_lock(path)
505    
506     def __len__(self):
507         """Return the number of active sessions."""
508         return len([fname for fname in os.listdir(self.storage_path)
509                     if (fname.startswith(self.SESSION_PREFIX)
510                         and not fname.endswith(self.LOCK_SUFFIX))])
511
512
513 class PostgresqlSession(Session):
514     """ Implementation of the PostgreSQL backend for sessions. It assumes
515         a table like this::
516
517             create table session (
518                 id varchar(40),
519                 data text,
520                 expiration_time timestamp
521             )
522     
523     You must provide your own get_db function.
524     """
525    
526     pickle_protocol = pickle.HIGHEST_PROTOCOL
527    
528     def __init__(self, id=None, **kwargs):
529         Session.__init__(self, id, **kwargs)
530         self.cursor = self.db.cursor()
531    
532     def setup(cls, **kwargs):
533         """Set up the storage system for Postgres-based sessions.
534         
535         This should only be called once per process; this will be done
536         automatically when using sessions.init (as the built-in Tool does).
537         """
538         for k, v in kwargs.items():
539             setattr(cls, k, v)
540        
541         self.db = self.get_db()
542     setup = classmethod(setup)
543    
544     def __del__(self):
545         if self.cursor:
546             self.cursor.close()
547         self.db.commit()
548    
549     def _exists(self):
550         # Select session data from table
551         self.cursor.execute('select data, expiration_time from session '
552                             'where id=%s', (self.id,))
553         rows = self.cursor.fetchall()
554         return bool(rows)
555    
556     def _load(self):
557         # Select session data from table
558         self.cursor.execute('select data, expiration_time from session '
559                             'where id=%s', (self.id,))
560         rows = self.cursor.fetchall()
561         if not rows:
562             return None
563        
564         pickled_data, expiration_time = rows[0]
565         data = pickle.loads(pickled_data)
566         return data, expiration_time
567    
568     def _save(self, expiration_time):
569         pickled_data = pickle.dumps(self._data, self.pickle_protocol)
570         self.cursor.execute('update session set data = %s, '
571                             'expiration_time = %s where id = %s',
572                             (pickled_data, expiration_time, self.id))
573    
574     def _delete(self):
575         self.cursor.execute('delete from session where id=%s', (self.id,))
576    
577     def acquire_lock(self):
578         """Acquire an exclusive lock on the currently-loaded session data."""
579         # We use the "for update" clause to lock the row
580         self.locked = True
581         self.cursor.execute('select id from session where id=%s for update',
582                             (self.id,))
583    
584     def release_lock(self):
585         """Release the lock on the currently-loaded session data."""
586         # We just close the cursor and that will remove the lock
587         #   introduced by the "for update" clause
588         self.cursor.close()
589         self.locked = False
590    
591     def clean_up(self):
592         """Clean up expired sessions."""
593         self.cursor.execute('delete from session where expiration_time < %s',
594                             (datetime.datetime.now(),))
595
596
597 class MemcachedSession(Session):
598    
599     # The most popular memcached client for Python isn't thread-safe.
600     # Wrap all .get and .set operations in a single lock.
601     mc_lock = threading.RLock()
602    
603     # This is a seperate set of locks per session id.
604     locks = {}
605    
606     servers = ['127.0.0.1:11211']
607    
608     def setup(cls, **kwargs):
609         """Set up the storage system for memcached-based sessions.
610         
611         This should only be called once per process; this will be done
612         automatically when using sessions.init (as the built-in Tool does).
613         """
614         for k, v in kwargs.items():
615             setattr(cls, k, v)
616        
617         import memcache
618         cls.cache = memcache.Client(cls.servers)
619     setup = classmethod(setup)
620    
621     def _exists(self):
622         self.mc_lock.acquire()
623         try:
624             return bool(self.cache.get(self.id))
625         finally:
626             self.mc_lock.release()
627    
628     def _load(self):
629         self.mc_lock.acquire()
630         try:
631             return self.cache.get(self.id)
632         finally:
633             self.mc_lock.release()
634    
635     def _save(self, expiration_time):
636         # Send the expiration time as "Unix time" (seconds since 1/1/1970)
637         td = int(time.mktime(expiration_time.timetuple()))
638         self.mc_lock.acquire()
639         try:
640             if not self.cache.set(self.id, (self._data, expiration_time), td):
641                 raise AssertionError("Session data for id %r not set." % self.id)
642         finally:
643             self.mc_lock.release()
644    
645     def _delete(self):
646         self.cache.delete(self.id)
647    
648     def acquire_lock(self):
649         """Acquire an exclusive lock on the currently-loaded session data."""
650         self.locked = True
651         self.locks.setdefault(self.id, threading.RLock()).acquire()
652    
653     def release_lock(self):
654         """Release the lock on the currently-loaded session data."""
655         self.locks[self.id].release()
656         self.locked = False
657    
658     def __len__(self):
659         """Return the number of active sessions."""
660         raise NotImplementedError
661
662
663 # Hook functions (for CherryPy tools)
664
665 def save():
666     """Save any changed session data."""
667    
668     if not hasattr(cherrypy.serving, "session"):
669         return
670     request = cherrypy.serving.request
671     response = cherrypy.serving.response
672    
673     # Guard against running twice
674     if hasattr(request, "_sessionsaved"):
675         return
676     request._sessionsaved = True
677    
678     if response.stream:
679         # If the body is being streamed, we have to save the data
680         #   *after* the response has been written out
681         request.hooks.attach('on_end_request', cherrypy.session.save)
682     else:
683         # If the body is not being streamed, we save the data now
684         # (so we can release the lock).
685         if isinstance(response.body, types.GeneratorType):
686             response.collapse_body()
687         cherrypy.session.save()
688 save.failsafe = True
689
690 def close():
691     """Close the session object for this request."""
692     sess = getattr(cherrypy.serving, "session", None)
693     if getattr(sess, "locked", False):
694         # If the session is still locked we release the lock
695         sess.release_lock()
696 close.failsafe = True
697 close.priority = 90
698
699
700 def init(storage_type='ram', path=None, path_header=None, name='session_id',
701          timeout=60, domain=None, secure=False, clean_freq=5,
702          persistent=True, debug=False, **kwargs):
703     """Initialize session object (using cookies).
704     
705     storage_type
706         One of 'ram', 'file', 'postgresql'. This will be used
707         to look up the corresponding class in cherrypy.lib.sessions
708         globals. For example, 'file' will use the FileSession class.
709     
710     path
711         The 'path' value to stick in the response cookie metadata.
712     
713     path_header
714         If 'path' is None (the default), then the response
715         cookie 'path' will be pulled from request.headers[path_header].
716     
717     name
718         The name of the cookie.
719     
720     timeout
721         The expiration timeout (in minutes) for the stored session data.
722         If 'persistent' is True (the default), this is also the timeout
723         for the cookie.
724     
725     domain
726         The cookie domain.
727     
728     secure
729         If False (the default) the cookie 'secure' value will not
730         be set. If True, the cookie 'secure' value will be set (to 1).
731     
732     clean_freq (minutes)
733         The poll rate for expired session cleanup.
734     
735     persistent
736         If True (the default), the 'timeout' argument will be used
737         to expire the cookie. If False, the cookie will not have an expiry,
738         and the cookie will be a "session cookie" which expires when the
739         browser is closed.
740     
741     Any additional kwargs will be bound to the new Session instance,
742     and may be specific to the storage type. See the subclass of Session
743     you're using for more information.
744     """
745    
746     request = cherrypy.serving.request
747    
748     # Guard against running twice
749     if hasattr(request, "_session_init_flag"):
750         return
751     request._session_init_flag = True
752    
753     # Check if request came with a session ID
754     id = None
755     if name in request.cookie:
756         id = request.cookie[name].value
757         if debug:
758             cherrypy.log('ID obtained from request.cookie: %r' % id,
759                          'TOOLS.SESSIONS')
760    
761     # Find the storage class and call setup (first time only).
762     storage_class = storage_type.title() + 'Session'
763     storage_class = globals()[storage_class]
764     if not hasattr(cherrypy, "session"):
765         if hasattr(storage_class, "setup"):
766             storage_class.setup(**kwargs)
767    
768     # Create and attach a new Session instance to cherrypy.serving.
769     # It will possess a reference to (and lock, and lazily load)
770     # the requested session data.
771     kwargs['timeout'] = timeout
772     kwargs['clean_freq'] = clean_freq
773     cherrypy.serving.session = sess = storage_class(id, **kwargs)
774     sess.debug = debug
775     def update_cookie(id):
776         """Update the cookie every time the session id changes."""
777         cherrypy.serving.response.cookie[name] = id
778     sess.id_observers.append(update_cookie)
779    
780     # Create cherrypy.session which will proxy to cherrypy.serving.session
781     if not hasattr(cherrypy, "session"):
782         cherrypy.session = cherrypy._ThreadLocalProxy('session')
783    
784     if persistent:
785         cookie_timeout = timeout
786     else:
787         # See http://support.microsoft.com/kb/223799/EN-US/
788         # and http://support.mozilla.com/en-US/kb/Cookies
789         cookie_timeout = None
790     set_response_cookie(path=path, path_header=path_header, name=name,
791                         timeout=cookie_timeout, domain=domain, secure=secure)
792
793
794 def set_response_cookie(path=None, path_header=None, name='session_id',
795                         timeout=60, domain=None, secure=False):
796     """Set a response cookie for the client.
797     
798     path
799         the 'path' value to stick in the response cookie metadata.
800
801     path_header
802         if 'path' is None (the default), then the response
803         cookie 'path' will be pulled from request.headers[path_header].
804
805     name
806         the name of the cookie.
807
808     timeout
809         the expiration timeout for the cookie. If 0 or other boolean
810         False, no 'expires' param will be set, and the cookie will be a
811         "session cookie" which expires when the browser is closed.
812
813     domain
814         the cookie domain.
815
816     secure
817         if False (the default) the cookie 'secure' value will not
818         be set. If True, the cookie 'secure' value will be set (to 1).
819
820     """
821     # Set response cookie
822     cookie = cherrypy.serving.response.cookie
823     cookie[name] = cherrypy.serving.session.id
824     cookie[name]['path'] = (path or cherrypy.serving.request.headers.get(path_header)
825                             or '/')
826    
827     # We'd like to use the "max-age" param as indicated in
828     # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't
829     # save it to disk and the session is lost if people close
830     # the browser. So we have to use the old "expires" ... sigh ...
831 ##    cookie[name]['max-age'] = timeout * 60
832     if timeout:
833         e = time.time() + (timeout * 60)
834         cookie[name]['expires'] = httputil.HTTPDate(e)
835     if domain is not None:
836         cookie[name]['domain'] = domain
837     if secure:
838         cookie[name]['secure'] = 1
839
840
841 def expire():
842     """Expire the current session cookie."""
843     name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id')
844     one_year = 60 * 60 * 24 * 365
845     e = time.time() - one_year
846     cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)
847
848
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets