Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(44)

Side by Side Diff: third_party/cherrypy/lib/sessions.py

Issue 9368042: Add CherryPy to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/cherrypy/lib/reprconf.py ('k') | third_party/cherrypy/lib/static.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
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_loc k()``.
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 import random
90 import time
91 import threading
92 import types
93 from warnings import warn
94
95 import cherrypy
96 from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
97 from cherrypy.lib import httputil
98
99
100 missing = object()
101
102 class Session(object):
103 """A CherryPy dict-like Session object (one per request)."""
104
105 _id = None
106
107 id_observers = None
108 "A list of callbacks to which to pass new id's."
109
110 def _get_id(self):
111 return self._id
112 def _set_id(self, value):
113 self._id = value
114 for o in self.id_observers:
115 o(value)
116 id = property(_get_id, _set_id, doc="The current session ID.")
117
118 timeout = 60
119 "Number of minutes after which to delete session data."
120
121 locked = False
122 """
123 If True, this session instance has exclusive read/write access
124 to session data."""
125
126 loaded = False
127 """
128 If True, data has been retrieved from storage. This should happen
129 automatically on the first attempt to access session data."""
130
131 clean_thread = None
132 "Class-level Monitor which calls self.clean_up."
133
134 clean_freq = 5
135 "The poll rate for expired session cleanup in minutes."
136
137 originalid = None
138 "The session id passed by the client. May be missing or unsafe."
139
140 missing = False
141 "True if the session requested by the client did not exist."
142
143 regenerated = False
144 """
145 True if the application called session.regenerate(). This is not set by
146 internal calls to regenerate the session id."""
147
148 debug=False
149
150 def __init__(self, id=None, **kwargs):
151 self.id_observers = []
152 self._data = {}
153
154 for k, v in kwargs.items():
155 setattr(self, k, v)
156
157 self.originalid = id
158 self.missing = False
159 if id is None:
160 if self.debug:
161 cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS')
162 self._regenerate()
163 else:
164 self.id = id
165 if not self._exists():
166 if self.debug:
167 cherrypy.log('Expired or malicious session %r; '
168 'making a new one' % id, 'TOOLS.SESSIONS')
169 # Expired or malicious session. Make a new one.
170 # See http://www.cherrypy.org/ticket/709.
171 self.id = None
172 self.missing = True
173 self._regenerate()
174
175 def now(self):
176 """Generate the session specific concept of 'now'.
177
178 Other session providers can override this to use alternative,
179 possibly timezone aware, versions of 'now'.
180 """
181 return datetime.datetime.now()
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 def generate_id(self):
211 """Return a new session id."""
212 return random20()
213
214 def save(self):
215 """Save session data."""
216 try:
217 # If session data has never been loaded then it's never been
218 # accessed: no need to save it
219 if self.loaded:
220 t = datetime.timedelta(seconds = self.timeout * 60)
221 expiration_time = self.now() + t
222 if self.debug:
223 cherrypy.log('Saving with expiry %s' % expiration_time,
224 'TOOLS.SESSIONS')
225 self._save(expiration_time)
226
227 finally:
228 if self.locked:
229 # Always release the lock if the user didn't release it
230 self.release_lock()
231
232 def load(self):
233 """Copy stored session data into this session instance."""
234 data = self._load()
235 # data is either None or a tuple (session_data, expiration_time)
236 if data is None or data[1] < self.now():
237 if self.debug:
238 cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS')
239 self._data = {}
240 else:
241 self._data = data[0]
242 self.loaded = True
243
244 # Stick the clean_thread in the class, not the instance.
245 # The instances are created and destroyed per-request.
246 cls = self.__class__
247 if self.clean_freq and not cls.clean_thread:
248 # clean_up is in instancemethod and not a classmethod,
249 # so that tool config can be accessed inside the method.
250 t = cherrypy.process.plugins.Monitor(
251 cherrypy.engine, self.clean_up, self.clean_freq * 60,
252 name='Session cleanup')
253 t.subscribe()
254 cls.clean_thread = t
255 t.start()
256
257 def delete(self):
258 """Delete stored session data."""
259 self._delete()
260
261 def __getitem__(self, key):
262 if not self.loaded: self.load()
263 return self._data[key]
264
265 def __setitem__(self, key, value):
266 if not self.loaded: self.load()
267 self._data[key] = value
268
269 def __delitem__(self, key):
270 if not self.loaded: self.load()
271 del self._data[key]
272
273 def pop(self, key, default=missing):
274 """Remove the specified key and return the corresponding value.
275 If key is not found, default is returned if given,
276 otherwise KeyError is raised.
277 """
278 if not self.loaded: self.load()
279 if default is missing:
280 return self._data.pop(key)
281 else:
282 return self._data.pop(key, default)
283
284 def __contains__(self, key):
285 if not self.loaded: self.load()
286 return key in self._data
287
288 if hasattr({}, 'has_key'):
289 def has_key(self, key):
290 """D.has_key(k) -> True if D has a key k, else False."""
291 if not self.loaded: self.load()
292 return key in self._data
293
294 def get(self, key, default=None):
295 """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
296 if not self.loaded: self.load()
297 return self._data.get(key, default)
298
299 def update(self, d):
300 """D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
301 if not self.loaded: self.load()
302 self._data.update(d)
303
304 def setdefault(self, key, default=None):
305 """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
306 if not self.loaded: self.load()
307 return self._data.setdefault(key, default)
308
309 def clear(self):
310 """D.clear() -> None. Remove all items from D."""
311 if not self.loaded: self.load()
312 self._data.clear()
313
314 def keys(self):
315 """D.keys() -> list of D's keys."""
316 if not self.loaded: self.load()
317 return self._data.keys()
318
319 def items(self):
320 """D.items() -> list of D's (key, value) pairs, as 2-tuples."""
321 if not self.loaded: self.load()
322 return self._data.items()
323
324 def values(self):
325 """D.values() -> list of D's values."""
326 if not self.loaded: self.load()
327 return self._data.values()
328
329
330 class RamSession(Session):
331
332 # Class-level objects. Don't rebind these!
333 cache = {}
334 locks = {}
335
336 def clean_up(self):
337 """Clean up expired sessions."""
338 now = self.now()
339 for id, (data, expiration_time) in copyitems(self.cache):
340 if expiration_time <= now:
341 try:
342 del self.cache[id]
343 except KeyError:
344 pass
345 try:
346 del self.locks[id]
347 except KeyError:
348 pass
349
350 # added to remove obsolete lock objects
351 for id in list(self.locks):
352 if id not in self.cache:
353 self.locks.pop(id, None)
354
355 def _exists(self):
356 return self.id in self.cache
357
358 def _load(self):
359 return self.cache.get(self.id)
360
361 def _save(self, expiration_time):
362 self.cache[self.id] = (self._data, expiration_time)
363
364 def _delete(self):
365 self.cache.pop(self.id, None)
366
367 def acquire_lock(self):
368 """Acquire an exclusive lock on the currently-loaded session data."""
369 self.locked = True
370 self.locks.setdefault(self.id, threading.RLock()).acquire()
371
372 def release_lock(self):
373 """Release the lock on the currently-loaded session data."""
374 self.locks[self.id].release()
375 self.locked = False
376
377 def __len__(self):
378 """Return the number of active sessions."""
379 return len(self.cache)
380
381
382 class FileSession(Session):
383 """Implementation of the File backend for sessions
384
385 storage_path
386 The folder where session data will be saved. Each session
387 will be saved as pickle.dump(data, expiration_time) in its own file;
388 the filename will be self.SESSION_PREFIX + self.id.
389
390 """
391
392 SESSION_PREFIX = 'session-'
393 LOCK_SUFFIX = '.lock'
394 pickle_protocol = pickle.HIGHEST_PROTOCOL
395
396 def __init__(self, id=None, **kwargs):
397 # The 'storage_path' arg is required for file-based sessions.
398 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
399 Session.__init__(self, id=id, **kwargs)
400
401 def setup(cls, **kwargs):
402 """Set up the storage system for file-based sessions.
403
404 This should only be called once per process; this will be done
405 automatically when using sessions.init (as the built-in Tool does).
406 """
407 # The 'storage_path' arg is required for file-based sessions.
408 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
409
410 for k, v in kwargs.items():
411 setattr(cls, k, v)
412
413 # Warn if any lock files exist at startup.
414 lockfiles = [fname for fname in os.listdir(cls.storage_path)
415 if (fname.startswith(cls.SESSION_PREFIX)
416 and fname.endswith(cls.LOCK_SUFFIX))]
417 if lockfiles:
418 plural = ('', 's')[len(lockfiles) > 1]
419 warn("%s session lockfile%s found at startup. If you are "
420 "only running one process, then you may need to "
421 "manually delete the lockfiles found at %r."
422 % (len(lockfiles), plural, cls.storage_path))
423 setup = classmethod(setup)
424
425 def _get_file_path(self):
426 f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
427 if not os.path.abspath(f).startswith(self.storage_path):
428 raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
429 return f
430
431 def _exists(self):
432 path = self._get_file_path()
433 return os.path.exists(path)
434
435 def _load(self, path=None):
436 if path is None:
437 path = self._get_file_path()
438 try:
439 f = open(path, "rb")
440 try:
441 return pickle.load(f)
442 finally:
443 f.close()
444 except (IOError, EOFError):
445 return None
446
447 def _save(self, expiration_time):
448 f = open(self._get_file_path(), "wb")
449 try:
450 pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
451 finally:
452 f.close()
453
454 def _delete(self):
455 try:
456 os.unlink(self._get_file_path())
457 except OSError:
458 pass
459
460 def acquire_lock(self, path=None):
461 """Acquire an exclusive lock on the currently-loaded session data."""
462 if path is None:
463 path = self._get_file_path()
464 path += self.LOCK_SUFFIX
465 while True:
466 try:
467 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
468 except OSError:
469 time.sleep(0.1)
470 else:
471 os.close(lockfd)
472 break
473 self.locked = True
474
475 def release_lock(self, path=None):
476 """Release the lock on the currently-loaded session data."""
477 if path is None:
478 path = self._get_file_path()
479 os.unlink(path + self.LOCK_SUFFIX)
480 self.locked = False
481
482 def clean_up(self):
483 """Clean up expired sessions."""
484 now = self.now()
485 # Iterate over all session files in self.storage_path
486 for fname in os.listdir(self.storage_path):
487 if (fname.startswith(self.SESSION_PREFIX)
488 and not fname.endswith(self.LOCK_SUFFIX)):
489 # We have a session file: lock and load it and check
490 # if it's expired. If it fails, nevermind.
491 path = os.path.join(self.storage_path, fname)
492 self.acquire_lock(path)
493 try:
494 contents = self._load(path)
495 # _load returns None on IOError
496 if contents is not None:
497 data, expiration_time = contents
498 if expiration_time < now:
499 # Session expired: deleting it
500 os.unlink(path)
501 finally:
502 self.release_lock(path)
503
504 def __len__(self):
505 """Return the number of active sessions."""
506 return len([fname for fname in os.listdir(self.storage_path)
507 if (fname.startswith(self.SESSION_PREFIX)
508 and not fname.endswith(self.LOCK_SUFFIX))])
509
510
511 class PostgresqlSession(Session):
512 """ Implementation of the PostgreSQL backend for sessions. It assumes
513 a table like this::
514
515 create table session (
516 id varchar(40),
517 data text,
518 expiration_time timestamp
519 )
520
521 You must provide your own get_db function.
522 """
523
524 pickle_protocol = pickle.HIGHEST_PROTOCOL
525
526 def __init__(self, id=None, **kwargs):
527 Session.__init__(self, id, **kwargs)
528 self.cursor = self.db.cursor()
529
530 def setup(cls, **kwargs):
531 """Set up the storage system for Postgres-based sessions.
532
533 This should only be called once per process; this will be done
534 automatically when using sessions.init (as the built-in Tool does).
535 """
536 for k, v in kwargs.items():
537 setattr(cls, k, v)
538
539 self.db = self.get_db()
540 setup = classmethod(setup)
541
542 def __del__(self):
543 if self.cursor:
544 self.cursor.close()
545 self.db.commit()
546
547 def _exists(self):
548 # Select session data from table
549 self.cursor.execute('select data, expiration_time from session '
550 'where id=%s', (self.id,))
551 rows = self.cursor.fetchall()
552 return bool(rows)
553
554 def _load(self):
555 # Select session data from table
556 self.cursor.execute('select data, expiration_time from session '
557 'where id=%s', (self.id,))
558 rows = self.cursor.fetchall()
559 if not rows:
560 return None
561
562 pickled_data, expiration_time = rows[0]
563 data = pickle.loads(pickled_data)
564 return data, expiration_time
565
566 def _save(self, expiration_time):
567 pickled_data = pickle.dumps(self._data, self.pickle_protocol)
568 self.cursor.execute('update session set data = %s, '
569 'expiration_time = %s where id = %s',
570 (pickled_data, expiration_time, self.id))
571
572 def _delete(self):
573 self.cursor.execute('delete from session where id=%s', (self.id,))
574
575 def acquire_lock(self):
576 """Acquire an exclusive lock on the currently-loaded session data."""
577 # We use the "for update" clause to lock the row
578 self.locked = True
579 self.cursor.execute('select id from session where id=%s for update',
580 (self.id,))
581
582 def release_lock(self):
583 """Release the lock on the currently-loaded session data."""
584 # We just close the cursor and that will remove the lock
585 # introduced by the "for update" clause
586 self.cursor.close()
587 self.locked = False
588
589 def clean_up(self):
590 """Clean up expired sessions."""
591 self.cursor.execute('delete from session where expiration_time < %s',
592 (self.now(),))
593
594
595 class MemcachedSession(Session):
596
597 # The most popular memcached client for Python isn't thread-safe.
598 # Wrap all .get and .set operations in a single lock.
599 mc_lock = threading.RLock()
600
601 # This is a seperate set of locks per session id.
602 locks = {}
603
604 servers = ['127.0.0.1:11211']
605
606 def setup(cls, **kwargs):
607 """Set up the storage system for memcached-based sessions.
608
609 This should only be called once per process; this will be done
610 automatically when using sessions.init (as the built-in Tool does).
611 """
612 for k, v in kwargs.items():
613 setattr(cls, k, v)
614
615 import memcache
616 cls.cache = memcache.Client(cls.servers)
617 setup = classmethod(setup)
618
619 def _get_id(self):
620 return self._id
621 def _set_id(self, value):
622 # This encode() call is where we differ from the superclass.
623 # Memcache keys MUST be byte strings, not unicode.
624 if isinstance(value, unicodestr):
625 value = value.encode('utf-8')
626
627 self._id = value
628 for o in self.id_observers:
629 o(value)
630 id = property(_get_id, _set_id, doc="The current session ID.")
631
632 def _exists(self):
633 self.mc_lock.acquire()
634 try:
635 return bool(self.cache.get(self.id))
636 finally:
637 self.mc_lock.release()
638
639 def _load(self):
640 self.mc_lock.acquire()
641 try:
642 return self.cache.get(self.id)
643 finally:
644 self.mc_lock.release()
645
646 def _save(self, expiration_time):
647 # Send the expiration time as "Unix time" (seconds since 1/1/1970)
648 td = int(time.mktime(expiration_time.timetuple()))
649 self.mc_lock.acquire()
650 try:
651 if not self.cache.set(self.id, (self._data, expiration_time), td):
652 raise AssertionError("Session data for id %r not set." % self.id )
653 finally:
654 self.mc_lock.release()
655
656 def _delete(self):
657 self.cache.delete(self.id)
658
659 def acquire_lock(self):
660 """Acquire an exclusive lock on the currently-loaded session data."""
661 self.locked = True
662 self.locks.setdefault(self.id, threading.RLock()).acquire()
663
664 def release_lock(self):
665 """Release the lock on the currently-loaded session data."""
666 self.locks[self.id].release()
667 self.locked = False
668
669 def __len__(self):
670 """Return the number of active sessions."""
671 raise NotImplementedError
672
673
674 # Hook functions (for CherryPy tools)
675
676 def save():
677 """Save any changed session data."""
678
679 if not hasattr(cherrypy.serving, "session"):
680 return
681 request = cherrypy.serving.request
682 response = cherrypy.serving.response
683
684 # Guard against running twice
685 if hasattr(request, "_sessionsaved"):
686 return
687 request._sessionsaved = True
688
689 if response.stream:
690 # If the body is being streamed, we have to save the data
691 # *after* the response has been written out
692 request.hooks.attach('on_end_request', cherrypy.session.save)
693 else:
694 # If the body is not being streamed, we save the data now
695 # (so we can release the lock).
696 if isinstance(response.body, types.GeneratorType):
697 response.collapse_body()
698 cherrypy.session.save()
699 save.failsafe = True
700
701 def close():
702 """Close the session object for this request."""
703 sess = getattr(cherrypy.serving, "session", None)
704 if getattr(sess, "locked", False):
705 # If the session is still locked we release the lock
706 sess.release_lock()
707 close.failsafe = True
708 close.priority = 90
709
710
711 def init(storage_type='ram', path=None, path_header=None, name='session_id',
712 timeout=60, domain=None, secure=False, clean_freq=5,
713 persistent=True, httponly=False, debug=False, **kwargs):
714 """Initialize session object (using cookies).
715
716 storage_type
717 One of 'ram', 'file', 'postgresql', 'memcached'. This will be
718 used to look up the corresponding class in cherrypy.lib.sessions
719 globals. For example, 'file' will use the FileSession class.
720
721 path
722 The 'path' value to stick in the response cookie metadata.
723
724 path_header
725 If 'path' is None (the default), then the response
726 cookie 'path' will be pulled from request.headers[path_header].
727
728 name
729 The name of the cookie.
730
731 timeout
732 The expiration timeout (in minutes) for the stored session data.
733 If 'persistent' is True (the default), this is also the timeout
734 for the cookie.
735
736 domain
737 The cookie domain.
738
739 secure
740 If False (the default) the cookie 'secure' value will not
741 be set. If True, the cookie 'secure' value will be set (to 1).
742
743 clean_freq (minutes)
744 The poll rate for expired session cleanup.
745
746 persistent
747 If True (the default), the 'timeout' argument will be used
748 to expire the cookie. If False, the cookie will not have an expiry,
749 and the cookie will be a "session cookie" which expires when the
750 browser is closed.
751
752 httponly
753 If False (the default) the cookie 'httponly' value will not be set.
754 If True, the cookie 'httponly' value will be set (to 1).
755
756 Any additional kwargs will be bound to the new Session instance,
757 and may be specific to the storage type. See the subclass of Session
758 you're using for more information.
759 """
760
761 request = cherrypy.serving.request
762
763 # Guard against running twice
764 if hasattr(request, "_session_init_flag"):
765 return
766 request._session_init_flag = True
767
768 # Check if request came with a session ID
769 id = None
770 if name in request.cookie:
771 id = request.cookie[name].value
772 if debug:
773 cherrypy.log('ID obtained from request.cookie: %r' % id,
774 'TOOLS.SESSIONS')
775
776 # Find the storage class and call setup (first time only).
777 storage_class = storage_type.title() + 'Session'
778 storage_class = globals()[storage_class]
779 if not hasattr(cherrypy, "session"):
780 if hasattr(storage_class, "setup"):
781 storage_class.setup(**kwargs)
782
783 # Create and attach a new Session instance to cherrypy.serving.
784 # It will possess a reference to (and lock, and lazily load)
785 # the requested session data.
786 kwargs['timeout'] = timeout
787 kwargs['clean_freq'] = clean_freq
788 cherrypy.serving.session = sess = storage_class(id, **kwargs)
789 sess.debug = debug
790 def update_cookie(id):
791 """Update the cookie every time the session id changes."""
792 cherrypy.serving.response.cookie[name] = id
793 sess.id_observers.append(update_cookie)
794
795 # Create cherrypy.session which will proxy to cherrypy.serving.session
796 if not hasattr(cherrypy, "session"):
797 cherrypy.session = cherrypy._ThreadLocalProxy('session')
798
799 if persistent:
800 cookie_timeout = timeout
801 else:
802 # See http://support.microsoft.com/kb/223799/EN-US/
803 # and http://support.mozilla.com/en-US/kb/Cookies
804 cookie_timeout = None
805 set_response_cookie(path=path, path_header=path_header, name=name,
806 timeout=cookie_timeout, domain=domain, secure=secure,
807 httponly=httponly)
808
809
810 def set_response_cookie(path=None, path_header=None, name='session_id',
811 timeout=60, domain=None, secure=False, httponly=False):
812 """Set a response cookie for the client.
813
814 path
815 the 'path' value to stick in the response cookie metadata.
816
817 path_header
818 if 'path' is None (the default), then the response
819 cookie 'path' will be pulled from request.headers[path_header].
820
821 name
822 the name of the cookie.
823
824 timeout
825 the expiration timeout for the cookie. If 0 or other boolean
826 False, no 'expires' param will be set, and the cookie will be a
827 "session cookie" which expires when the browser is closed.
828
829 domain
830 the cookie domain.
831
832 secure
833 if False (the default) the cookie 'secure' value will not
834 be set. If True, the cookie 'secure' value will be set (to 1).
835
836 httponly
837 If False (the default) the cookie 'httponly' value will not be set.
838 If True, the cookie 'httponly' value will be set (to 1).
839
840 """
841 # Set response cookie
842 cookie = cherrypy.serving.response.cookie
843 cookie[name] = cherrypy.serving.session.id
844 cookie[name]['path'] = (path or cherrypy.serving.request.headers.get(path_he ader)
845 or '/')
846
847 # We'd like to use the "max-age" param as indicated in
848 # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't
849 # save it to disk and the session is lost if people close
850 # the browser. So we have to use the old "expires" ... sigh ...
851 ## cookie[name]['max-age'] = timeout * 60
852 if timeout:
853 e = time.time() + (timeout * 60)
854 cookie[name]['expires'] = httputil.HTTPDate(e)
855 if domain is not None:
856 cookie[name]['domain'] = domain
857 if secure:
858 cookie[name]['secure'] = 1
859 if httponly:
860 if not cookie[name].isReservedKey('httponly'):
861 raise ValueError("The httponly cookie token is not supported.")
862 cookie[name]['httponly'] = 1
863
864 def expire():
865 """Expire the current session cookie."""
866 name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_i d')
867 one_year = 60 * 60 * 24 * 365
868 e = time.time() - one_year
869 cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)
870
871
OLDNEW
« no previous file with comments | « third_party/cherrypy/lib/reprconf.py ('k') | third_party/cherrypy/lib/static.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698