|
3 | 3 | # Copyright (c) Jupyter Development Team.
|
4 | 4 | # Distributed under the terms of the Modified BSD License.
|
5 | 5 |
|
| 6 | +import datetime |
6 | 7 | import functools
|
7 | 8 | import json
|
8 | 9 | import mimetypes
|
|
15 | 16 | try:
|
16 | 17 | # py3
|
17 | 18 | from http.client import responses
|
| 19 | + from http.cookies import Morsel |
18 | 20 | except ImportError:
|
19 | 21 | from httplib import responses
|
| 22 | + from Cookie import Morsel |
20 | 23 | try:
|
21 | 24 | from urllib.parse import urlparse # Py 3
|
22 | 25 | except ImportError:
|
23 | 26 | from urlparse import urlparse # Py 2
|
24 | 27 |
|
25 | 28 | from jinja2 import TemplateNotFound
|
26 |
| -from tornado import web, gen, escape |
| 29 | +from tornado import web, gen, escape, httputil |
27 | 30 | from tornado.log import app_log
|
28 | 31 |
|
29 | 32 | from notebook._sysinfo import get_sys_info
|
@@ -91,14 +94,41 @@ def set_default_headers(self):
|
91 | 94 | # for example, so just ignore)
|
92 | 95 | self.log.debug(e)
|
93 | 96 |
|
| 97 | + def force_clear_cookie(self, name, path="/", domain=None): |
| 98 | + """Deletes the cookie with the given name. |
| 99 | +
|
| 100 | + Tornado's cookie handling currently (Jan 2018) stores cookies in a dict |
| 101 | + keyed by name, so it can only modify one cookie with a given name per |
| 102 | + response. The browser can store multiple cookies with the same name |
| 103 | + but different domains and/or paths. This method lets us clear multiple |
| 104 | + cookies with the same name. |
| 105 | +
|
| 106 | + Due to limitations of the cookie protocol, you must pass the same |
| 107 | + path and domain to clear a cookie as were used when that cookie |
| 108 | + was set (but there is no way to find out on the server side |
| 109 | + which values were used for a given cookie). |
| 110 | + """ |
| 111 | + name = escape.native_str(name) |
| 112 | + expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) |
| 113 | + |
| 114 | + morsel = Morsel() |
| 115 | + morsel.set(name, '', '""') |
| 116 | + morsel['expires'] = httputil.format_timestamp(expires) |
| 117 | + morsel['path'] = path |
| 118 | + if domain: |
| 119 | + morsel['domain'] = domain |
| 120 | + self.add_header("Set-Cookie", morsel.OutputString()) |
| 121 | + |
94 | 122 | def clear_login_cookie(self):
|
95 | 123 | cookie_options = self.settings.get('cookie_options', {})
|
96 | 124 | path = cookie_options.setdefault('path', self.base_url)
|
97 | 125 | self.clear_cookie(self.cookie_name, path=path)
|
98 | 126 | if path and path != '/':
|
99 |
| - # also clear cookie on / to ensure old cookies |
100 |
| - # are cleared after the change in path behavior. |
101 |
| - self.clear_cookie(self.cookie_name) |
| 127 | + # also clear cookie on / to ensure old cookies are cleared |
| 128 | + # after the change in path behavior (changed in notebook 5.2.2). |
| 129 | + # N.B. This bypasses the normal cookie handling, which can't update |
| 130 | + # two cookies with the same name. See the method above. |
| 131 | + self.force_clear_cookie(self.cookie_name) |
102 | 132 |
|
103 | 133 | def get_current_user(self):
|
104 | 134 | if self.login_handler is None:
|
|
0 commit comments