We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Subsequent removal and addition of a cookie may leave the cookie removed
## Long story short

I encountered a flaky test failure when testing login-logout-login and discovered that a login right after a logout may not work, in other words, the authentication cookie may not be set.

This boiled down to the following error in `CookieJar`: for event loops with poor time resolution (in my case `uvloop`), subsequent removal and addition of a cookie may not add the cookie.

Here is a test case:
```python
import time
from http.cookies import SimpleCookie

from aiohttp.cookiejar import CookieJar
from uvloop import new_event_loop


def test_uvloop_cookies():
    loop = new_event_loop()

    for _ in range(1000):
        jar = CookieJar(unsafe=True, loop=loop)

        # Remove`test` cookie.
        jar.update_cookies(SimpleCookie('test=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/'))
        # Set `test` cookie to `bar`.
        jar.update_cookies(SimpleCookie('test="bar"'))

        # Assert that there a cookie.
        assert list(jar)
        time.sleep(0.1)
        assert list(jar)
```

Although it took me quite a bit of time to discover what's going on, it seems there is an extremely simple fix: replace strict comparison (`<`) with non-strict one (`<=`) [here](https://github.com/aio-libs/aiohttp/blob/eee65a4eed846394082c5e931fc75f9c1e7f30cd/aiohttp/cookiejar.py#L77).
I'll provide a PR soon if maintainers agree.

## Expected behaviour

Test passes

## Actual behaviour

Test fails

## Steps to reproduce

Run the test

## Your environment

uvloop=0.8.0, aiohttp=2.1.0


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `aiohttp/cookiejar.py`
Content:
```
1 import datetime
2 import pathlib
3 import pickle
4 import re
5 from collections import defaultdict
6 from collections.abc import Mapping
7 from http.cookies import Morsel
8 from math import ceil
9 
10 from yarl import URL
11 
12 from .abc import AbstractCookieJar
13 from .helpers import SimpleCookie, is_ip_address
14 
15 
16 class CookieJar(AbstractCookieJar):
17     """Implements cookie storage adhering to RFC 6265."""
18 
19     DATE_TOKENS_RE = re.compile(
20         r"[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]*"
21         r"(?P<token>[\x00-\x08\x0A-\x1F\d:a-zA-Z\x7F-\xFF]+)")
22 
23     DATE_HMS_TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})")
24 
25     DATE_DAY_OF_MONTH_RE = re.compile(r"(\d{1,2})")
26 
27     DATE_MONTH_RE = re.compile("(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|"
28                                "(aug)|(sep)|(oct)|(nov)|(dec)", re.I)
29 
30     DATE_YEAR_RE = re.compile(r"(\d{2,4})")
31 
32     MAX_TIME = 2051215261.0  # so far in future (2035-01-01)
33 
34     def __init__(self, *, unsafe=False, loop=None):
35         super().__init__(loop=loop)
36         self._cookies = defaultdict(SimpleCookie)
37         self._host_only_cookies = set()
38         self._unsafe = unsafe
39         self._next_expiration = ceil(self._loop.time())
40         self._expirations = {}
41 
42     def save(self, file_path):
43         file_path = pathlib.Path(file_path)
44         with file_path.open(mode='wb') as f:
45             pickle.dump(self._cookies, f, pickle.HIGHEST_PROTOCOL)
46 
47     def load(self, file_path):
48         file_path = pathlib.Path(file_path)
49         with file_path.open(mode='rb') as f:
50             self._cookies = pickle.load(f)
51 
52     def clear(self):
53         self._cookies.clear()
54         self._host_only_cookies.clear()
55         self._next_expiration = ceil(self._loop.time())
56         self._expirations.clear()
57 
58     def __iter__(self):
59         self._do_expiration()
60         for val in self._cookies.values():
61             yield from val.values()
62 
63     def __len__(self):
64         return sum(1 for i in self)
65 
66     def _do_expiration(self):
67         now = self._loop.time()
68         if self._next_expiration > now:
69             return
70         if not self._expirations:
71             return
72         next_expiration = self.MAX_TIME
73         to_del = []
74         cookies = self._cookies
75         expirations = self._expirations
76         for (domain, name), when in expirations.items():
77             if when < now:
78                 cookies[domain].pop(name, None)
79                 to_del.append((domain, name))
80                 self._host_only_cookies.discard((domain, name))
81             else:
82                 next_expiration = min(next_expiration, when)
83         for key in to_del:
84             del expirations[key]
85 
86         self._next_expiration = ceil(next_expiration)
87 
88     def _expire_cookie(self, when, domain, name):
89         self._next_expiration = min(self._next_expiration, when)
90         self._expirations[(domain, name)] = when
91 
92     def update_cookies(self, cookies, response_url=URL()):
93         """Update cookies."""
94         hostname = response_url.raw_host
95 
96         if not self._unsafe and is_ip_address(hostname):
97             # Don't accept cookies from IPs
98             return
99 
100         if isinstance(cookies, Mapping):
101             cookies = cookies.items()
102 
103         for name, cookie in cookies:
104             if not isinstance(cookie, Morsel):
105                 tmp = SimpleCookie()
106                 tmp[name] = cookie
107                 cookie = tmp[name]
108 
109             domain = cookie["domain"]
110 
111             # ignore domains with trailing dots
112             if domain.endswith('.'):
113                 domain = ""
114                 del cookie["domain"]
115 
116             if not domain and hostname is not None:
117                 # Set the cookie's domain to the response hostname
118                 # and set its host-only-flag
119                 self._host_only_cookies.add((hostname, name))
120                 domain = cookie["domain"] = hostname
121 
122             if domain.startswith("."):
123                 # Remove leading dot
124                 domain = domain[1:]
125                 cookie["domain"] = domain
126 
127             if hostname and not self._is_domain_match(domain, hostname):
128                 # Setting cookies for different domains is not allowed
129                 continue
130 
131             path = cookie["path"]
132             if not path or not path.startswith("/"):
133                 # Set the cookie's path to the response path
134                 path = response_url.path
135                 if not path.startswith("/"):
136                     path = "/"
137                 else:
138                     # Cut everything from the last slash to the end
139                     path = "/" + path[1:path.rfind("/")]
140                 cookie["path"] = path
141 
142             max_age = cookie["max-age"]
143             if max_age:
144                 try:
145                     delta_seconds = int(max_age)
146                     self._expire_cookie(self._loop.time() + delta_seconds,
147                                         domain, name)
148                 except ValueError:
149                     cookie["max-age"] = ""
150 
151             else:
152                 expires = cookie["expires"]
153                 if expires:
154                     expire_time = self._parse_date(expires)
155                     if expire_time:
156                         self._expire_cookie(expire_time.timestamp(),
157                                             domain, name)
158                     else:
159                         cookie["expires"] = ""
160 
161             # use dict method because SimpleCookie class modifies value
162             # before Python 3.4.3
163             dict.__setitem__(self._cookies[domain], name, cookie)
164 
165         self._do_expiration()
166 
167     def filter_cookies(self, request_url=URL()):
168         """Returns this jar's cookies filtered by their attributes."""
169         self._do_expiration()
170         request_url = URL(request_url)
171         filtered = SimpleCookie()
172         hostname = request_url.raw_host or ""
173         is_not_secure = request_url.scheme not in ("https", "wss")
174 
175         for cookie in self:
176             name = cookie.key
177             domain = cookie["domain"]
178 
179             # Send shared cookies
180             if not domain:
181                 filtered[name] = cookie.value
182                 continue
183 
184             if not self._unsafe and is_ip_address(hostname):
185                 continue
186 
187             if (domain, name) in self._host_only_cookies:
188                 if domain != hostname:
189                     continue
190             elif not self._is_domain_match(domain, hostname):
191                 continue
192 
193             if not self._is_path_match(request_url.path, cookie["path"]):
194                 continue
195 
196             if is_not_secure and cookie["secure"]:
197                 continue
198 
199             # It's critical we use the Morsel so the coded_value
200             # (based on cookie version) is preserved
201             mrsl_val = cookie.get(cookie.key, Morsel())
202             mrsl_val.set(cookie.key, cookie.value, cookie.coded_value)
203             filtered[name] = mrsl_val
204 
205         return filtered
206 
207     @staticmethod
208     def _is_domain_match(domain, hostname):
209         """Implements domain matching adhering to RFC 6265."""
210         if hostname == domain:
211             return True
212 
213         if not hostname.endswith(domain):
214             return False
215 
216         non_matching = hostname[:-len(domain)]
217 
218         if not non_matching.endswith("."):
219             return False
220 
221         return not is_ip_address(hostname)
222 
223     @staticmethod
224     def _is_path_match(req_path, cookie_path):
225         """Implements path matching adhering to RFC 6265."""
226         if not req_path.startswith("/"):
227             req_path = "/"
228 
229         if req_path == cookie_path:
230             return True
231 
232         if not req_path.startswith(cookie_path):
233             return False
234 
235         if cookie_path.endswith("/"):
236             return True
237 
238         non_matching = req_path[len(cookie_path):]
239 
240         return non_matching.startswith("/")
241 
242     @classmethod
243     def _parse_date(cls, date_str):
244         """Implements date string parsing adhering to RFC 6265."""
245         if not date_str:
246             return
247 
248         found_time = False
249         found_day = False
250         found_month = False
251         found_year = False
252 
253         hour = minute = second = 0
254         day = 0
255         month = 0
256         year = 0
257 
258         for token_match in cls.DATE_TOKENS_RE.finditer(date_str):
259 
260             token = token_match.group("token")
261 
262             if not found_time:
263                 time_match = cls.DATE_HMS_TIME_RE.match(token)
264                 if time_match:
265                     found_time = True
266                     hour, minute, second = [
267                         int(s) for s in time_match.groups()]
268                     continue
269 
270             if not found_day:
271                 day_match = cls.DATE_DAY_OF_MONTH_RE.match(token)
272                 if day_match:
273                     found_day = True
274                     day = int(day_match.group())
275                     continue
276 
277             if not found_month:
278                 month_match = cls.DATE_MONTH_RE.match(token)
279                 if month_match:
280                     found_month = True
281                     month = month_match.lastindex
282                     continue
283 
284             if not found_year:
285                 year_match = cls.DATE_YEAR_RE.match(token)
286                 if year_match:
287                     found_year = True
288                     year = int(year_match.group())
289 
290         if 70 <= year <= 99:
291             year += 1900
292         elif 0 <= year <= 69:
293             year += 2000
294 
295         if False in (found_day, found_month, found_year, found_time):
296             return
297 
298         if not 1 <= day <= 31:
299             return
300 
301         if year < 1601 or hour > 23 or minute > 59 or second > 59:
302             return
303 
304         return datetime.datetime(year, month, day,
305                                  hour, minute, second,
306                                  tzinfo=datetime.timezone.utc)
307
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/aiohttp/cookiejar.py b/aiohttp/cookiejar.py
--- a/aiohttp/cookiejar.py
+++ b/aiohttp/cookiejar.py
@@ -74,7 +74,7 @@
         cookies = self._cookies
         expirations = self._expirations
         for (domain, name), when in expirations.items():
-            if when < now:
+            if when <= now:
                 cookies[domain].pop(name, None)
                 to_del.append((domain, name))
                 self._host_only_cookies.discard((domain, name))

{"golden_diff": "diff --git a/aiohttp/cookiejar.py b/aiohttp/cookiejar.py\n--- a/aiohttp/cookiejar.py\n+++ b/aiohttp/cookiejar.py\n@@ -74,7 +74,7 @@\n         cookies = self._cookies\n         expirations = self._expirations\n         for (domain, name), when in expirations.items():\n-            if when < now:\n+            if when <= now:\n                 cookies[domain].pop(name, None)\n                 to_del.append((domain, name))\n                 self._host_only_cookies.discard((domain, name))\n", "issue": "Subsequent removal and addition of a cookie may leave the cookie removed\n## Long story short\r\n\r\nI encountered a flaky test failure when testing login-logout-login and discovered that a login right after a logout may not work, in other words, the authentication cookie may not be set.\r\n\r\nThis boiled down to the following error in `CookieJar`: for event loops with poor time resolution (in my case `uvloop`), subsequent removal and addition of a cookie may not add the cookie.\r\n\r\nHere is a test case:\r\n```python\r\nimport time\r\nfrom http.cookies import SimpleCookie\r\n\r\nfrom aiohttp.cookiejar import CookieJar\r\nfrom uvloop import new_event_loop\r\n\r\n\r\ndef test_uvloop_cookies():\r\n    loop = new_event_loop()\r\n\r\n    for _ in range(1000):\r\n        jar = CookieJar(unsafe=True, loop=loop)\r\n\r\n        # Remove`test` cookie.\r\n        jar.update_cookies(SimpleCookie('test=\"\"; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/'))\r\n        # Set `test` cookie to `bar`.\r\n        jar.update_cookies(SimpleCookie('test=\"bar\"'))\r\n\r\n        # Assert that there a cookie.\r\n        assert list(jar)\r\n        time.sleep(0.1)\r\n        assert list(jar)\r\n```\r\n\r\nAlthough it took me quite a bit of time to discover what's going on, it seems there is an extremely simple fix: replace strict comparison (`<`) with non-strict one (`<=`) [here](https://github.com/aio-libs/aiohttp/blob/eee65a4eed846394082c5e931fc75f9c1e7f30cd/aiohttp/cookiejar.py#L77).\r\nI'll provide a PR soon if maintainers agree.\r\n\r\n## Expected behaviour\r\n\r\nTest passes\r\n\r\n## Actual behaviour\r\n\r\nTest fails\r\n\r\n## Steps to reproduce\r\n\r\nRun the test\r\n\r\n## Your environment\r\n\r\nuvloop=0.8.0, aiohttp=2.1.0\r\n\n", "before_files": [{"content": "import datetime\nimport pathlib\nimport pickle\nimport re\nfrom collections import defaultdict\nfrom collections.abc import Mapping\nfrom http.cookies import Morsel\nfrom math import ceil\n\nfrom yarl import URL\n\nfrom .abc import AbstractCookieJar\nfrom .helpers import SimpleCookie, is_ip_address\n\n\nclass CookieJar(AbstractCookieJar):\n    \"\"\"Implements cookie storage adhering to RFC 6265.\"\"\"\n\n    DATE_TOKENS_RE = re.compile(\n        r\"[\\x09\\x20-\\x2F\\x3B-\\x40\\x5B-\\x60\\x7B-\\x7E]*\"\n        r\"(?P<token>[\\x00-\\x08\\x0A-\\x1F\\d:a-zA-Z\\x7F-\\xFF]+)\")\n\n    DATE_HMS_TIME_RE = re.compile(r\"(\\d{1,2}):(\\d{1,2}):(\\d{1,2})\")\n\n    DATE_DAY_OF_MONTH_RE = re.compile(r\"(\\d{1,2})\")\n\n    DATE_MONTH_RE = re.compile(\"(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|\"\n                               \"(aug)|(sep)|(oct)|(nov)|(dec)\", re.I)\n\n    DATE_YEAR_RE = re.compile(r\"(\\d{2,4})\")\n\n    MAX_TIME = 2051215261.0  # so far in future (2035-01-01)\n\n    def __init__(self, *, unsafe=False, loop=None):\n        super().__init__(loop=loop)\n        self._cookies = defaultdict(SimpleCookie)\n        self._host_only_cookies = set()\n        self._unsafe = unsafe\n        self._next_expiration = ceil(self._loop.time())\n        self._expirations = {}\n\n    def save(self, file_path):\n        file_path = pathlib.Path(file_path)\n        with file_path.open(mode='wb') as f:\n            pickle.dump(self._cookies, f, pickle.HIGHEST_PROTOCOL)\n\n    def load(self, file_path):\n        file_path = pathlib.Path(file_path)\n        with file_path.open(mode='rb') as f:\n            self._cookies = pickle.load(f)\n\n    def clear(self):\n        self._cookies.clear()\n        self._host_only_cookies.clear()\n        self._next_expiration = ceil(self._loop.time())\n        self._expirations.clear()\n\n    def __iter__(self):\n        self._do_expiration()\n        for val in self._cookies.values():\n            yield from val.values()\n\n    def __len__(self):\n        return sum(1 for i in self)\n\n    def _do_expiration(self):\n        now = self._loop.time()\n        if self._next_expiration > now:\n            return\n        if not self._expirations:\n            return\n        next_expiration = self.MAX_TIME\n        to_del = []\n        cookies = self._cookies\n        expirations = self._expirations\n        for (domain, name), when in expirations.items():\n            if when < now:\n                cookies[domain].pop(name, None)\n                to_del.append((domain, name))\n                self._host_only_cookies.discard((domain, name))\n            else:\n                next_expiration = min(next_expiration, when)\n        for key in to_del:\n            del expirations[key]\n\n        self._next_expiration = ceil(next_expiration)\n\n    def _expire_cookie(self, when, domain, name):\n        self._next_expiration = min(self._next_expiration, when)\n        self._expirations[(domain, name)] = when\n\n    def update_cookies(self, cookies, response_url=URL()):\n        \"\"\"Update cookies.\"\"\"\n        hostname = response_url.raw_host\n\n        if not self._unsafe and is_ip_address(hostname):\n            # Don't accept cookies from IPs\n            return\n\n        if isinstance(cookies, Mapping):\n            cookies = cookies.items()\n\n        for name, cookie in cookies:\n            if not isinstance(cookie, Morsel):\n                tmp = SimpleCookie()\n                tmp[name] = cookie\n                cookie = tmp[name]\n\n            domain = cookie[\"domain\"]\n\n            # ignore domains with trailing dots\n            if domain.endswith('.'):\n                domain = \"\"\n                del cookie[\"domain\"]\n\n            if not domain and hostname is not None:\n                # Set the cookie's domain to the response hostname\n                # and set its host-only-flag\n                self._host_only_cookies.add((hostname, name))\n                domain = cookie[\"domain\"] = hostname\n\n            if domain.startswith(\".\"):\n                # Remove leading dot\n                domain = domain[1:]\n                cookie[\"domain\"] = domain\n\n            if hostname and not self._is_domain_match(domain, hostname):\n                # Setting cookies for different domains is not allowed\n                continue\n\n            path = cookie[\"path\"]\n            if not path or not path.startswith(\"/\"):\n                # Set the cookie's path to the response path\n                path = response_url.path\n                if not path.startswith(\"/\"):\n                    path = \"/\"\n                else:\n                    # Cut everything from the last slash to the end\n                    path = \"/\" + path[1:path.rfind(\"/\")]\n                cookie[\"path\"] = path\n\n            max_age = cookie[\"max-age\"]\n            if max_age:\n                try:\n                    delta_seconds = int(max_age)\n                    self._expire_cookie(self._loop.time() + delta_seconds,\n                                        domain, name)\n                except ValueError:\n                    cookie[\"max-age\"] = \"\"\n\n            else:\n                expires = cookie[\"expires\"]\n                if expires:\n                    expire_time = self._parse_date(expires)\n                    if expire_time:\n                        self._expire_cookie(expire_time.timestamp(),\n                                            domain, name)\n                    else:\n                        cookie[\"expires\"] = \"\"\n\n            # use dict method because SimpleCookie class modifies value\n            # before Python 3.4.3\n            dict.__setitem__(self._cookies[domain], name, cookie)\n\n        self._do_expiration()\n\n    def filter_cookies(self, request_url=URL()):\n        \"\"\"Returns this jar's cookies filtered by their attributes.\"\"\"\n        self._do_expiration()\n        request_url = URL(request_url)\n        filtered = SimpleCookie()\n        hostname = request_url.raw_host or \"\"\n        is_not_secure = request_url.scheme not in (\"https\", \"wss\")\n\n        for cookie in self:\n            name = cookie.key\n            domain = cookie[\"domain\"]\n\n            # Send shared cookies\n            if not domain:\n                filtered[name] = cookie.value\n                continue\n\n            if not self._unsafe and is_ip_address(hostname):\n                continue\n\n            if (domain, name) in self._host_only_cookies:\n                if domain != hostname:\n                    continue\n            elif not self._is_domain_match(domain, hostname):\n                continue\n\n            if not self._is_path_match(request_url.path, cookie[\"path\"]):\n                continue\n\n            if is_not_secure and cookie[\"secure\"]:\n                continue\n\n            # It's critical we use the Morsel so the coded_value\n            # (based on cookie version) is preserved\n            mrsl_val = cookie.get(cookie.key, Morsel())\n            mrsl_val.set(cookie.key, cookie.value, cookie.coded_value)\n            filtered[name] = mrsl_val\n\n        return filtered\n\n    @staticmethod\n    def _is_domain_match(domain, hostname):\n        \"\"\"Implements domain matching adhering to RFC 6265.\"\"\"\n        if hostname == domain:\n            return True\n\n        if not hostname.endswith(domain):\n            return False\n\n        non_matching = hostname[:-len(domain)]\n\n        if not non_matching.endswith(\".\"):\n            return False\n\n        return not is_ip_address(hostname)\n\n    @staticmethod\n    def _is_path_match(req_path, cookie_path):\n        \"\"\"Implements path matching adhering to RFC 6265.\"\"\"\n        if not req_path.startswith(\"/\"):\n            req_path = \"/\"\n\n        if req_path == cookie_path:\n            return True\n\n        if not req_path.startswith(cookie_path):\n            return False\n\n        if cookie_path.endswith(\"/\"):\n            return True\n\n        non_matching = req_path[len(cookie_path):]\n\n        return non_matching.startswith(\"/\")\n\n    @classmethod\n    def _parse_date(cls, date_str):\n        \"\"\"Implements date string parsing adhering to RFC 6265.\"\"\"\n        if not date_str:\n            return\n\n        found_time = False\n        found_day = False\n        found_month = False\n        found_year = False\n\n        hour = minute = second = 0\n        day = 0\n        month = 0\n        year = 0\n\n        for token_match in cls.DATE_TOKENS_RE.finditer(date_str):\n\n            token = token_match.group(\"token\")\n\n            if not found_time:\n                time_match = cls.DATE_HMS_TIME_RE.match(token)\n                if time_match:\n                    found_time = True\n                    hour, minute, second = [\n                        int(s) for s in time_match.groups()]\n                    continue\n\n            if not found_day:\n                day_match = cls.DATE_DAY_OF_MONTH_RE.match(token)\n                if day_match:\n                    found_day = True\n                    day = int(day_match.group())\n                    continue\n\n            if not found_month:\n                month_match = cls.DATE_MONTH_RE.match(token)\n                if month_match:\n                    found_month = True\n                    month = month_match.lastindex\n                    continue\n\n            if not found_year:\n                year_match = cls.DATE_YEAR_RE.match(token)\n                if year_match:\n                    found_year = True\n                    year = int(year_match.group())\n\n        if 70 <= year <= 99:\n            year += 1900\n        elif 0 <= year <= 69:\n            year += 2000\n\n        if False in (found_day, found_month, found_year, found_time):\n            return\n\n        if not 1 <= day <= 31:\n            return\n\n        if year < 1601 or hour > 23 or minute > 59 or second > 59:\n            return\n\n        return datetime.datetime(year, month, day,\n                                 hour, minute, second,\n                                 tzinfo=datetime.timezone.utc)\n", "path": "aiohttp/cookiejar.py"}], "after_files": [{"content": "import datetime\nimport pathlib\nimport pickle\nimport re\nfrom collections import defaultdict\nfrom collections.abc import Mapping\nfrom http.cookies import Morsel\nfrom math import ceil\n\nfrom yarl import URL\n\nfrom .abc import AbstractCookieJar\nfrom .helpers import SimpleCookie, is_ip_address\n\n\nclass CookieJar(AbstractCookieJar):\n    \"\"\"Implements cookie storage adhering to RFC 6265.\"\"\"\n\n    DATE_TOKENS_RE = re.compile(\n        r\"[\\x09\\x20-\\x2F\\x3B-\\x40\\x5B-\\x60\\x7B-\\x7E]*\"\n        r\"(?P<token>[\\x00-\\x08\\x0A-\\x1F\\d:a-zA-Z\\x7F-\\xFF]+)\")\n\n    DATE_HMS_TIME_RE = re.compile(r\"(\\d{1,2}):(\\d{1,2}):(\\d{1,2})\")\n\n    DATE_DAY_OF_MONTH_RE = re.compile(r\"(\\d{1,2})\")\n\n    DATE_MONTH_RE = re.compile(\"(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|\"\n                               \"(aug)|(sep)|(oct)|(nov)|(dec)\", re.I)\n\n    DATE_YEAR_RE = re.compile(r\"(\\d{2,4})\")\n\n    MAX_TIME = 2051215261.0  # so far in future (2035-01-01)\n\n    def __init__(self, *, unsafe=False, loop=None):\n        super().__init__(loop=loop)\n        self._cookies = defaultdict(SimpleCookie)\n        self._host_only_cookies = set()\n        self._unsafe = unsafe\n        self._next_expiration = ceil(self._loop.time())\n        self._expirations = {}\n\n    def save(self, file_path):\n        file_path = pathlib.Path(file_path)\n        with file_path.open(mode='wb') as f:\n            pickle.dump(self._cookies, f, pickle.HIGHEST_PROTOCOL)\n\n    def load(self, file_path):\n        file_path = pathlib.Path(file_path)\n        with file_path.open(mode='rb') as f:\n            self._cookies = pickle.load(f)\n\n    def clear(self):\n        self._cookies.clear()\n        self._host_only_cookies.clear()\n        self._next_expiration = ceil(self._loop.time())\n        self._expirations.clear()\n\n    def __iter__(self):\n        self._do_expiration()\n        for val in self._cookies.values():\n            yield from val.values()\n\n    def __len__(self):\n        return sum(1 for i in self)\n\n    def _do_expiration(self):\n        now = self._loop.time()\n        if self._next_expiration > now:\n            return\n        if not self._expirations:\n            return\n        next_expiration = self.MAX_TIME\n        to_del = []\n        cookies = self._cookies\n        expirations = self._expirations\n        for (domain, name), when in expirations.items():\n            if when <= now:\n                cookies[domain].pop(name, None)\n                to_del.append((domain, name))\n                self._host_only_cookies.discard((domain, name))\n            else:\n                next_expiration = min(next_expiration, when)\n        for key in to_del:\n            del expirations[key]\n\n        self._next_expiration = ceil(next_expiration)\n\n    def _expire_cookie(self, when, domain, name):\n        self._next_expiration = min(self._next_expiration, when)\n        self._expirations[(domain, name)] = when\n\n    def update_cookies(self, cookies, response_url=URL()):\n        \"\"\"Update cookies.\"\"\"\n        hostname = response_url.raw_host\n\n        if not self._unsafe and is_ip_address(hostname):\n            # Don't accept cookies from IPs\n            return\n\n        if isinstance(cookies, Mapping):\n            cookies = cookies.items()\n\n        for name, cookie in cookies:\n            if not isinstance(cookie, Morsel):\n                tmp = SimpleCookie()\n                tmp[name] = cookie\n                cookie = tmp[name]\n\n            domain = cookie[\"domain\"]\n\n            # ignore domains with trailing dots\n            if domain.endswith('.'):\n                domain = \"\"\n                del cookie[\"domain\"]\n\n            if not domain and hostname is not None:\n                # Set the cookie's domain to the response hostname\n                # and set its host-only-flag\n                self._host_only_cookies.add((hostname, name))\n                domain = cookie[\"domain\"] = hostname\n\n            if domain.startswith(\".\"):\n                # Remove leading dot\n                domain = domain[1:]\n                cookie[\"domain\"] = domain\n\n            if hostname and not self._is_domain_match(domain, hostname):\n                # Setting cookies for different domains is not allowed\n                continue\n\n            path = cookie[\"path\"]\n            if not path or not path.startswith(\"/\"):\n                # Set the cookie's path to the response path\n                path = response_url.path\n                if not path.startswith(\"/\"):\n                    path = \"/\"\n                else:\n                    # Cut everything from the last slash to the end\n                    path = \"/\" + path[1:path.rfind(\"/\")]\n                cookie[\"path\"] = path\n\n            max_age = cookie[\"max-age\"]\n            if max_age:\n                try:\n                    delta_seconds = int(max_age)\n                    self._expire_cookie(self._loop.time() + delta_seconds,\n                                        domain, name)\n                except ValueError:\n                    cookie[\"max-age\"] = \"\"\n\n            else:\n                expires = cookie[\"expires\"]\n                if expires:\n                    expire_time = self._parse_date(expires)\n                    if expire_time:\n                        self._expire_cookie(expire_time.timestamp(),\n                                            domain, name)\n                    else:\n                        cookie[\"expires\"] = \"\"\n\n            # use dict method because SimpleCookie class modifies value\n            # before Python 3.4.3\n            dict.__setitem__(self._cookies[domain], name, cookie)\n\n        self._do_expiration()\n\n    def filter_cookies(self, request_url=URL()):\n        \"\"\"Returns this jar's cookies filtered by their attributes.\"\"\"\n        self._do_expiration()\n        request_url = URL(request_url)\n        filtered = SimpleCookie()\n        hostname = request_url.raw_host or \"\"\n        is_not_secure = request_url.scheme not in (\"https\", \"wss\")\n\n        for cookie in self:\n            name = cookie.key\n            domain = cookie[\"domain\"]\n\n            # Send shared cookies\n            if not domain:\n                filtered[name] = cookie.value\n                continue\n\n            if not self._unsafe and is_ip_address(hostname):\n                continue\n\n            if (domain, name) in self._host_only_cookies:\n                if domain != hostname:\n                    continue\n            elif not self._is_domain_match(domain, hostname):\n                continue\n\n            if not self._is_path_match(request_url.path, cookie[\"path\"]):\n                continue\n\n            if is_not_secure and cookie[\"secure\"]:\n                continue\n\n            # It's critical we use the Morsel so the coded_value\n            # (based on cookie version) is preserved\n            mrsl_val = cookie.get(cookie.key, Morsel())\n            mrsl_val.set(cookie.key, cookie.value, cookie.coded_value)\n            filtered[name] = mrsl_val\n\n        return filtered\n\n    @staticmethod\n    def _is_domain_match(domain, hostname):\n        \"\"\"Implements domain matching adhering to RFC 6265.\"\"\"\n        if hostname == domain:\n            return True\n\n        if not hostname.endswith(domain):\n            return False\n\n        non_matching = hostname[:-len(domain)]\n\n        if not non_matching.endswith(\".\"):\n            return False\n\n        return not is_ip_address(hostname)\n\n    @staticmethod\n    def _is_path_match(req_path, cookie_path):\n        \"\"\"Implements path matching adhering to RFC 6265.\"\"\"\n        if not req_path.startswith(\"/\"):\n            req_path = \"/\"\n\n        if req_path == cookie_path:\n            return True\n\n        if not req_path.startswith(cookie_path):\n            return False\n\n        if cookie_path.endswith(\"/\"):\n            return True\n\n        non_matching = req_path[len(cookie_path):]\n\n        return non_matching.startswith(\"/\")\n\n    @classmethod\n    def _parse_date(cls, date_str):\n        \"\"\"Implements date string parsing adhering to RFC 6265.\"\"\"\n        if not date_str:\n            return\n\n        found_time = False\n        found_day = False\n        found_month = False\n        found_year = False\n\n        hour = minute = second = 0\n        day = 0\n        month = 0\n        year = 0\n\n        for token_match in cls.DATE_TOKENS_RE.finditer(date_str):\n\n            token = token_match.group(\"token\")\n\n            if not found_time:\n                time_match = cls.DATE_HMS_TIME_RE.match(token)\n                if time_match:\n                    found_time = True\n                    hour, minute, second = [\n                        int(s) for s in time_match.groups()]\n                    continue\n\n            if not found_day:\n                day_match = cls.DATE_DAY_OF_MONTH_RE.match(token)\n                if day_match:\n                    found_day = True\n                    day = int(day_match.group())\n                    continue\n\n            if not found_month:\n                month_match = cls.DATE_MONTH_RE.match(token)\n                if month_match:\n                    found_month = True\n                    month = month_match.lastindex\n                    continue\n\n            if not found_year:\n                year_match = cls.DATE_YEAR_RE.match(token)\n                if year_match:\n                    found_year = True\n                    year = int(year_match.group())\n\n        if 70 <= year <= 99:\n            year += 1900\n        elif 0 <= year <= 69:\n            year += 2000\n\n        if False in (found_day, found_month, found_year, found_time):\n            return\n\n        if not 1 <= day <= 31:\n            return\n\n        if year < 1601 or hour > 23 or minute > 59 or second > 59:\n            return\n\n        return datetime.datetime(year, month, day,\n                                 hour, minute, second,\n                                 tzinfo=datetime.timezone.utc)\n", "path": "aiohttp/cookiejar.py"}]}