~ubuntu-branches/debian/jessie/cherrypy3/jessie

« back to all changes in this revision

Viewing changes to cherrypy/lib/sessions.py

  • Committer: Package Import Robot
  • Author(s): Gustavo Noronha Silva, JCF Ploemen, Stéphane Graber, Gustavo Noronha
  • Date: 2012-01-06 10:13:27 UTC
  • mfrom: (1.1.4) (7.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20120106101327-smxnhguqs14ubl7e
Tags: 3.2.2-1
[ JCF Ploemen ]
* New upstream release (Closes: #571196).
* Bumped Standards-Version to 3.8.4 (no changes needed).
* Removing patch 02: no longer needed, incorporated upstream.
* Updating patch 00 to match release.
* Install cherryd man page via debian/manpages.
* debian/copyright:
  + Added notice for cherrypy/lib/httpauth.py.
  + Fixed years.
* debian/watch:
  + Don't hit on the -py3 release by blocking '-' from the version.
  + Mangle upstream version, inserting a tilde for beta/rc.

[ Stéphane Graber <stgraber@ubuntu.com> ]
 * Convert from python-support to dh_python2 (#654375)
  - debian/pyversions: Removed (no longer needed)
  - debian/rules
   + Replace call to dh_pysupport by dh_python2
   + Add --with=python2 to all dh calls
  - debian/control
   + Drop build-depends on python-support
   + Bump build-depends on python-all to >= 2.6.6-3~
   + Replace XS-Python-Version by X-Python-Version
   + Remove XB-Python-Version from binary package

[ Gustavo Noronha ]
* debian/control, debian/rules, debian/manpages:
 - use help2man to generate a manpage for cherryd at build time, since
  one is no longer shipped along with the source code
* debian/control:
- add python-nose to Build-Depends, since it's used during the
  documentation build for cross-reference generation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
"""Session implementation for CherryPy.
2
2
 
3
 
We use cherrypy.request to store some convenient variables as
4
 
well as data about the session for the current request. Instead of
5
 
polluting cherrypy.request we use a Session object bound to
6
 
cherrypy.session to store these variables.
 
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.
7
85
"""
8
86
 
9
87
import datetime
10
88
import os
11
 
try:
12
 
    import cPickle as pickle
13
 
except ImportError:
14
 
    import pickle
15
89
import random
16
 
 
17
 
try:
18
 
    # Python 2.5+
19
 
    from hashlib import sha1 as sha
20
 
except ImportError:
21
 
    from sha import new as sha
22
 
 
23
90
import time
24
91
import threading
25
92
import types
26
93
from warnings import warn
27
94
 
28
95
import cherrypy
29
 
from cherrypy.lib import http
 
96
from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
 
97
from cherrypy.lib import httputil
30
98
 
31
99
 
32
100
missing = object()
34
102
class Session(object):
35
103
    """A CherryPy dict-like Session object (one per request)."""
36
104
    
37
 
    __metaclass__ = cherrypy._AttributeDocstrings
38
 
    
39
105
    _id = None
 
106
    
40
107
    id_observers = None
41
 
    id_observers__doc = "A list of callbacks to which to pass new id's."
 
108
    "A list of callbacks to which to pass new id's."
42
109
    
43
 
    id__doc = "The current session ID."
44
110
    def _get_id(self):
45
111
        return self._id
46
112
    def _set_id(self, value):
47
113
        self._id = value
48
114
        for o in self.id_observers:
49
115
            o(value)
50
 
    id = property(_get_id, _set_id, doc=id__doc)
 
116
    id = property(_get_id, _set_id, doc="The current session ID.")
51
117
    
52
118
    timeout = 60
53
 
    timeout__doc = "Number of minutes after which to delete session data."
 
119
    "Number of minutes after which to delete session data."
54
120
    
55
121
    locked = False
56
 
    locked__doc = """
 
122
    """
57
123
    If True, this session instance has exclusive read/write access
58
124
    to session data."""
59
125
    
60
126
    loaded = False
61
 
    loaded__doc = """
 
127
    """
62
128
    If True, data has been retrieved from storage. This should happen
63
129
    automatically on the first attempt to access session data."""
64
130
    
65
131
    clean_thread = None
66
 
    clean_thread__doc = "Class-level Monitor which calls self.clean_up."
 
132
    "Class-level Monitor which calls self.clean_up."
67
133
    
68
134
    clean_freq = 5
69
 
    clean_freq__doc = "The poll rate for expired session cleanup in minutes."
 
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
70
149
    
71
150
    def __init__(self, id=None, **kwargs):
72
151
        self.id_observers = []
73
152
        self._data = {}
74
153
        
75
 
        for k, v in kwargs.iteritems():
 
154
        for k, v in kwargs.items():
76
155
            setattr(self, k, v)
77
156
        
 
157
        self.originalid = id
 
158
        self.missing = False
78
159
        if id is None:
79
 
            self.regenerate()
 
160
            if self.debug:
 
161
                cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS')
 
162
            self._regenerate()
80
163
        else:
81
164
            self.id = id
82
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')
83
169
                # Expired or malicious session. Make a new one.
84
170
                # See http://www.cherrypy.org/ticket/709.
85
171
                self.id = None
86
 
                self.regenerate()
87
 
    
 
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
 
88
183
    def regenerate(self):
89
184
        """Replace the current session (with a new id)."""
 
185
        self.regenerated = True
 
186
        self._regenerate()
 
187
    
 
188
    def _regenerate(self):
90
189
        if self.id is not None:
91
190
            self.delete()
92
191
        
108
207
        """Clean up expired sessions."""
109
208
        pass
110
209
    
111
 
    try:
112
 
        os.urandom(20)
113
 
    except (AttributeError, NotImplementedError):
114
 
        # os.urandom not available until Python 2.4. Fall back to random.random.
115
 
        def generate_id(self):
116
 
            """Return a new session id."""
117
 
            return sha('%s' % random.random()).hexdigest()
118
 
    else:
119
 
        def generate_id(self):
120
 
            """Return a new session id."""
121
 
            return os.urandom(20).encode('hex')
 
210
    def generate_id(self):
 
211
        """Return a new session id."""
 
212
        return random20()
122
213
    
123
214
    def save(self):
124
215
        """Save session data."""
125
216
        try:
126
217
            # If session data has never been loaded then it's never been
127
 
            #   accessed: no need to delete it
 
218
            #   accessed: no need to save it
128
219
            if self.loaded:
129
220
                t = datetime.timedelta(seconds = self.timeout * 60)
130
 
                expiration_time = datetime.datetime.now() + t
 
221
                expiration_time = self.now() + t
 
222
                if self.debug:
 
223
                    cherrypy.log('Saving with expiry %s' % expiration_time,
 
224
                                 'TOOLS.SESSIONS')
131
225
                self._save(expiration_time)
132
226
            
133
227
        finally:
139
233
        """Copy stored session data into this session instance."""
140
234
        data = self._load()
141
235
        # data is either None or a tuple (session_data, expiration_time)
142
 
        if data is None or data[1] < datetime.datetime.now():
143
 
            # Expired session: flush session data
 
236
        if data is None or data[1] < self.now():
 
237
            if self.debug:
 
238
                cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS')
144
239
            self._data = {}
145
240
        else:
146
241
            self._data = data[0]
153
248
            # clean_up is in instancemethod and not a classmethod,
154
249
            # so that tool config can be accessed inside the method.
155
250
            t = cherrypy.process.plugins.Monitor(
156
 
                cherrypy.engine, self.clean_up, self.clean_freq * 60)
 
251
                cherrypy.engine, self.clean_up, self.clean_freq * 60,
 
252
                name='Session cleanup')
157
253
            t.subscribe()
158
254
            cls.clean_thread = t
159
255
            t.start()
189
285
        if not self.loaded: self.load()
190
286
        return key in self._data
191
287
    
192
 
    def has_key(self, key):
193
 
        """D.has_key(k) -> True if D has a key k, else False."""
194
 
        if not self.loaded: self.load()
195
 
        return self._data.has_key(key)
 
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
196
293
    
197
294
    def get(self, key, default=None):
198
295
        """D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""
238
335
    
239
336
    def clean_up(self):
240
337
        """Clean up expired sessions."""
241
 
        now = datetime.datetime.now()
242
 
        for id, (data, expiration_time) in self.cache.items():
243
 
            if expiration_time < now:
 
338
        now = self.now()
 
339
        for id, (data, expiration_time) in copyitems(self.cache):
 
340
            if expiration_time <= now:
244
341
                try:
245
342
                    del self.cache[id]
246
343
                except KeyError:
249
346
                    del self.locks[id]
250
347
                except KeyError:
251
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)
252
354
    
253
355
    def _exists(self):
254
356
        return self.id in self.cache
260
362
        self.cache[self.id] = (self._data, expiration_time)
261
363
    
262
364
    def _delete(self):
263
 
        del self.cache[self.id]
 
365
        self.cache.pop(self.id, None)
264
366
    
265
367
    def acquire_lock(self):
266
368
        """Acquire an exclusive lock on the currently-loaded session data."""
280
382
class FileSession(Session):
281
383
    """Implementation of the File backend for sessions
282
384
    
283
 
    storage_path: the folder where session data will be saved. Each session
 
385
    storage_path
 
386
        The folder where session data will be saved. Each session
284
387
        will be saved as pickle.dump(data, expiration_time) in its own file;
285
388
        the filename will be self.SESSION_PREFIX + self.id.
 
389
    
286
390
    """
287
391
    
288
392
    SESSION_PREFIX = 'session-'
289
393
    LOCK_SUFFIX = '.lock'
 
394
    pickle_protocol = pickle.HIGHEST_PROTOCOL
290
395
    
291
396
    def __init__(self, id=None, **kwargs):
292
397
        # The 'storage_path' arg is required for file-based sessions.
302
407
        # The 'storage_path' arg is required for file-based sessions.
303
408
        kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
304
409
        
305
 
        for k, v in kwargs.iteritems():
 
410
        for k, v in kwargs.items():
306
411
            setattr(cls, k, v)
307
412
        
308
413
        # Warn if any lock files exist at startup.
342
447
    def _save(self, expiration_time):
343
448
        f = open(self._get_file_path(), "wb")
344
449
        try:
345
 
            pickle.dump((self._data, expiration_time), f)
 
450
            pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
346
451
        finally:
347
452
            f.close()
348
453
    
376
481
    
377
482
    def clean_up(self):
378
483
        """Clean up expired sessions."""
379
 
        now = datetime.datetime.now()
 
484
        now = self.now()
380
485
        # Iterate over all session files in self.storage_path
381
486
        for fname in os.listdir(self.storage_path):
382
487
            if (fname.startswith(self.SESSION_PREFIX)
405
510
 
406
511
class PostgresqlSession(Session):
407
512
    """ Implementation of the PostgreSQL backend for sessions. It assumes
408
 
        a table like this:
 
513
        a table like this::
409
514
 
410
515
            create table session (
411
516
                id varchar(40),
416
521
    You must provide your own get_db function.
417
522
    """
418
523
    
 
524
    pickle_protocol = pickle.HIGHEST_PROTOCOL
 
525
    
419
526
    def __init__(self, id=None, **kwargs):
420
527
        Session.__init__(self, id, **kwargs)
421
528
        self.cursor = self.db.cursor()
426
533
        This should only be called once per process; this will be done
427
534
        automatically when using sessions.init (as the built-in Tool does).
428
535
        """
429
 
        for k, v in kwargs.iteritems():
 
536
        for k, v in kwargs.items():
430
537
            setattr(cls, k, v)
431
538
        
432
539
        self.db = self.get_db()
457
564
        return data, expiration_time
458
565
    
459
566
    def _save(self, expiration_time):
460
 
        pickled_data = pickle.dumps(self._data)
 
567
        pickled_data = pickle.dumps(self._data, self.pickle_protocol)
461
568
        self.cursor.execute('update session set data = %s, '
462
569
                            'expiration_time = %s where id = %s',
463
570
                            (pickled_data, expiration_time, self.id))
482
589
    def clean_up(self):
483
590
        """Clean up expired sessions."""
484
591
        self.cursor.execute('delete from session where expiration_time < %s',
485
 
                            (datetime.datetime.now(),))
 
592
                            (self.now(),))
486
593
 
487
594
 
488
595
class MemcachedSession(Session):
502
609
        This should only be called once per process; this will be done
503
610
        automatically when using sessions.init (as the built-in Tool does).
504
611
        """
505
 
        for k, v in kwargs.iteritems():
 
612
        for k, v in kwargs.items():
506
613
            setattr(cls, k, v)
507
614
        
508
615
        import memcache
509
616
        cls.cache = memcache.Client(cls.servers)
510
617
    setup = classmethod(setup)
511
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
    
512
632
    def _exists(self):
513
633
        self.mc_lock.acquire()
514
634
        try:
558
678
    
559
679
    if not hasattr(cherrypy.serving, "session"):
560
680
        return
 
681
    request = cherrypy.serving.request
 
682
    response = cherrypy.serving.response
561
683
    
562
684
    # Guard against running twice
563
 
    if hasattr(cherrypy.request, "_sessionsaved"):
 
685
    if hasattr(request, "_sessionsaved"):
564
686
        return
565
 
    cherrypy.request._sessionsaved = True
 
687
    request._sessionsaved = True
566
688
    
567
 
    if cherrypy.response.stream:
 
689
    if response.stream:
568
690
        # If the body is being streamed, we have to save the data
569
691
        #   *after* the response has been written out
570
 
        cherrypy.request.hooks.attach('on_end_request', cherrypy.session.save)
 
692
        request.hooks.attach('on_end_request', cherrypy.session.save)
571
693
    else:
572
694
        # If the body is not being streamed, we save the data now
573
695
        # (so we can release the lock).
574
 
        if isinstance(cherrypy.response.body, types.GeneratorType):
575
 
            cherrypy.response.collapse_body()
 
696
        if isinstance(response.body, types.GeneratorType):
 
697
            response.collapse_body()
576
698
        cherrypy.session.save()
577
699
save.failsafe = True
578
700
 
587
709
 
588
710
 
589
711
def init(storage_type='ram', path=None, path_header=None, name='session_id',
590
 
         timeout=60, domain=None, secure=False, clean_freq=5, **kwargs):
 
712
         timeout=60, domain=None, secure=False, clean_freq=5,
 
713
         persistent=True, httponly=False, debug=False, **kwargs):
591
714
    """Initialize session object (using cookies).
592
715
    
593
 
    storage_type: one of 'ram', 'file', 'postgresql'. This will be used
594
 
        to look up the corresponding class in cherrypy.lib.sessions
 
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
595
719
        globals. For example, 'file' will use the FileSession class.
596
 
    path: the 'path' value to stick in the response cookie metadata.
597
 
    path_header: if 'path' is None (the default), then the response
 
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
598
726
        cookie 'path' will be pulled from request.headers[path_header].
599
 
    name: the name of the cookie.
600
 
    timeout: the expiration timeout (in minutes) for both the cookie and
601
 
        stored session data.
602
 
    domain: the cookie domain.
603
 
    secure: if False (the default) the cookie 'secure' value will not
 
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
604
741
        be set. If True, the cookie 'secure' value will be set (to 1).
605
 
    clean_freq (minutes): the poll rate for expired session cleanup.
 
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).
606
755
    
607
756
    Any additional kwargs will be bound to the new Session instance,
608
757
    and may be specific to the storage type. See the subclass of Session
609
758
    you're using for more information.
610
759
    """
611
760
    
612
 
    request = cherrypy.request
 
761
    request = cherrypy.serving.request
613
762
    
614
763
    # Guard against running twice
615
764
    if hasattr(request, "_session_init_flag"):
620
769
    id = None
621
770
    if name in request.cookie:
622
771
        id = request.cookie[name].value
 
772
        if debug:
 
773
            cherrypy.log('ID obtained from request.cookie: %r' % id,
 
774
                         'TOOLS.SESSIONS')
623
775
    
624
776
    # Find the storage class and call setup (first time only).
625
777
    storage_class = storage_type.title() + 'Session'
634
786
    kwargs['timeout'] = timeout
635
787
    kwargs['clean_freq'] = clean_freq
636
788
    cherrypy.serving.session = sess = storage_class(id, **kwargs)
 
789
    sess.debug = debug
637
790
    def update_cookie(id):
638
791
        """Update the cookie every time the session id changes."""
639
 
        cherrypy.response.cookie[name] = id
 
792
        cherrypy.serving.response.cookie[name] = id
640
793
    sess.id_observers.append(update_cookie)
641
794
    
642
795
    # Create cherrypy.session which will proxy to cherrypy.serving.session
643
796
    if not hasattr(cherrypy, "session"):
644
797
        cherrypy.session = cherrypy._ThreadLocalProxy('session')
645
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
646
805
    set_response_cookie(path=path, path_header=path_header, name=name,
647
 
                        timeout=timeout, domain=domain, secure=secure)
 
806
                        timeout=cookie_timeout, domain=domain, secure=secure,
 
807
                        httponly=httponly)
648
808
 
649
809
 
650
810
def set_response_cookie(path=None, path_header=None, name='session_id',
651
 
                        timeout=60, domain=None, secure=False):
 
811
                        timeout=60, domain=None, secure=False, httponly=False):
652
812
    """Set a response cookie for the client.
653
813
    
654
 
    path: the 'path' value to stick in the response cookie metadata.
655
 
    path_header: if 'path' is None (the default), then the response
 
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
656
819
        cookie 'path' will be pulled from request.headers[path_header].
657
 
    name: the name of the cookie.
658
 
    timeout: the expiration timeout for the cookie.
659
 
    domain: the cookie domain.
660
 
    secure: if False (the default) the cookie 'secure' value will not
 
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
661
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
 
662
840
    """
663
841
    # Set response cookie
664
 
    cookie = cherrypy.response.cookie
 
842
    cookie = cherrypy.serving.response.cookie
665
843
    cookie[name] = cherrypy.serving.session.id
666
 
    cookie[name]['path'] = (path or cherrypy.request.headers.get(path_header)
 
844
    cookie[name]['path'] = (path or cherrypy.serving.request.headers.get(path_header)
667
845
                            or '/')
668
846
    
669
847
    # We'd like to use the "max-age" param as indicated in
672
850
    # the browser. So we have to use the old "expires" ... sigh ...
673
851
##    cookie[name]['max-age'] = timeout * 60
674
852
    if timeout:
675
 
        cookie[name]['expires'] = http.HTTPDate(time.time() + (timeout * 60))
 
853
        e = time.time() + (timeout * 60)
 
854
        cookie[name]['expires'] = httputil.HTTPDate(e)
676
855
    if domain is not None:
677
856
        cookie[name]['domain'] = domain
678
857
    if secure:
679
858
        cookie[name]['secure'] = 1
680
 
 
 
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
681
863
 
682
864
def expire():
683
865
    """Expire the current session cookie."""
684
 
    name = cherrypy.request.config.get('tools.sessions.name', 'session_id')
 
866
    name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id')
685
867
    one_year = 60 * 60 * 24 * 365
686
 
    exp = time.gmtime(time.time() - one_year)
687
 
    t = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", exp)
688
 
    cherrypy.response.cookie[name]['expires'] = t
 
868
    e = time.time() - one_year
 
869
    cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)
689
870
 
690
871