browser2: Handle cookie expiration, session cookies

Every related method accepts a "now" parameter. If provided, it will be
used instead of the system time.
This commit is contained in:
Laurent Bachelier 2012-04-11 08:50:07 +02:00 committed by Romain Bignon
commit db304b955c
2 changed files with 54 additions and 6 deletions

View file

@ -161,7 +161,7 @@ class CookieJar(object):
return False return False
def _normalize_cookie(self, cookie, url): def _normalize_cookie(self, cookie, url, now=None):
""" """
Update a cookie we got from the response. Update a cookie we got from the response.
The goal is to have data relevant for use in future requests. The goal is to have data relevant for use in future requests.
@ -169,6 +169,10 @@ class CookieJar(object):
* Sets path if there is not one. * Sets path if there is not one.
* Set Expires from Max-Age. We need the expires to have an absolute expiration date. * Set Expires from Max-Age. We need the expires to have an absolute expiration date.
* Force the Secure flag if required. (see SECURE_DOMAINS) * Force the Secure flag if required. (see SECURE_DOMAINS)
:type cookie: :class:`cookies.Cookie`
:type url: str
:type now: datetime
""" """
url = urlparse.urlparse(url) url = urlparse.urlparse(url)
if cookie.domain is None: if cookie.domain is None:
@ -176,7 +180,9 @@ class CookieJar(object):
if cookie.path is None: if cookie.path is None:
cookie.path = '/' cookie.path = '/'
if cookie.max_age is not None: if cookie.max_age is not None:
cookie.expires = datetime.now() + timedelta(seconds=cookie.max_age) if now is None:
now = datetime.now()
cookie.expires = now + timedelta(seconds=cookie.max_age)
if url.scheme == 'https' \ if url.scheme == 'https' \
and self._match_domain_list(self.SECURE_DOMAINS, cookie.domain): and self._match_domain_list(self.SECURE_DOMAINS, cookie.domain):
cookie.secure = True cookie.secure = True
@ -194,24 +200,46 @@ class CookieJar(object):
if self._can_set(c, response.url): if self._can_set(c, response.url):
self.set(c) self.set(c)
def for_request(self, url): def for_request(self, url, now=None):
""" """
Get a key/value dictionnary of cookies for a given request URL. Get a key/value dictionnary of cookies for a given request URL.
:type url: str :type url: str
:type now: datetime
:rtype: dict :rtype: dict
""" """
url = urlparse.urlparse(url) url = urlparse.urlparse(url)
if now is None:
now = datetime.now()
# we want insecure cookies in https too! # we want insecure cookies in https too!
secure = None if url.scheme == 'https' else False secure = None if url.scheme == 'https' else False
cdict = dict() cdict = dict()
# get sorted cookies # get sorted cookies
cookies = self.all(domain=url.hostname, path=url.path, secure=secure) cookies = self.all(domain=url.hostname, path=url.path, secure=secure)
for cookie in cookies: for cookie in cookies:
# only use session cookies and cookies with future expirations
if cookie.expires is None or cookie.expires > now:
# update only if not set, since first cookies are "better" # update only if not set, since first cookies are "better"
cdict.setdefault(cookie.name, cookie.value) cdict.setdefault(cookie.name, cookie.value)
return cdict return cdict
def flush(self, now=None, session=False):
"""
Remove expired cookies. If session is True, also remove all session cookies.
:type now: datetime
:type session: bool
"""
# we need a list copy since we remove from the iterable
for cookie in list(self.iter()):
# remove session cookies if requested
if cookie.expires is None and session:
self.remove(cookie)
# remove non-session cookies if expired before now
if cookie.expires is not None and cookie.expires < now:
self.remove(cookie)
def set(self, cookie): def set(self, cookie):
""" """
Add or replace a Cookie in the jar. Add or replace a Cookie in the jar.

View file

@ -19,7 +19,9 @@
from __future__ import absolute_import from __future__ import absolute_import
import requests from datetime import datetime
from requests import HTTPError
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
from .browser import BaseBrowser, DomainBrowser, Weboob from .browser import BaseBrowser, DomainBrowser, Weboob
@ -74,7 +76,7 @@ def test_brokenpost():
r = b.location(r.url + '/feed') r = b.location(r.url + '/feed')
assert 'hello' in r.text assert 'hello' in r.text
assert 'world' in r.text assert 'world' in r.text
except requests.HTTPError, e: except HTTPError, e:
if str(e).startswith('503 '): if str(e).startswith('503 '):
raise SkipTest('Quota exceeded') raise SkipTest('Quota exceeded')
else: else:
@ -296,3 +298,21 @@ def test_cookiejar():
assert len(cj.all(secure=True)) == 1 assert len(cj.all(secure=True)) == 1
# not the same cookie, but the same identifiers # not the same cookie, but the same identifiers
assert cj.remove(cookie1) is True assert cj.remove(cookie1) is True
cj.clear()
cookie6 = bc('e1=1; domain=www.example.com; path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;')
cookie7 = bc('e2=1; domain=www.example.com; path=/; Expires=Thu, 01 Jan 2010 00:00:01 GMT;')
now = datetime(2000, 01, 01)
cj.set(cookie0)
cj.set(cookie6)
cj.set(cookie7)
assert cj.for_request('http://www.example.com/', now) == {'e2': '1', 'j': 'v'}
assert cj.for_request('http://www.example.com/', datetime(2020, 01, 01)) == {'j': 'v'}
assert len(cj.all()) == 3
cj.flush(now)
assert len(cj.all()) == 2
assert cj.remove(cookie6) is False # already removed
cj.flush(now, session=True)
assert len(cj.all()) == 1