1
1
import asyncio
2
+ import calendar
2
3
import contextlib
3
4
import datetime
4
5
import os # noqa
5
6
import pathlib
6
7
import pickle
7
8
import re
9
+ import time
8
10
from collections import defaultdict
9
11
from http .cookies import BaseCookie , Morsel , SimpleCookie
12
+ from math import ceil
10
13
from typing import ( # noqa
11
14
DefaultDict ,
12
15
Dict ,
24
27
from yarl import URL
25
28
26
29
from .abc import AbstractCookieJar , ClearCookiePredicate
27
- from .helpers import is_ip_address , next_whole_second
30
+ from .helpers import is_ip_address
28
31
from .typedefs import LooseCookies , PathLike , StrOrURL
29
32
30
33
__all__ = ("CookieJar" , "DummyCookieJar" )
@@ -52,9 +55,22 @@ class CookieJar(AbstractCookieJar):
52
55
53
56
DATE_YEAR_RE = re .compile (r"(\d{2,4})" )
54
57
55
- MAX_TIME = datetime .datetime .max .replace (tzinfo = datetime .timezone .utc )
56
-
57
- MAX_32BIT_TIME = datetime .datetime .fromtimestamp (2 ** 31 - 1 , datetime .timezone .utc )
58
+ # calendar.timegm() fails for timestamps after datetime.datetime.max
59
+ # Minus one as a loss of precision occurs when timestamp() is called.
60
+ MAX_TIME = (
61
+ int (datetime .datetime .max .replace (tzinfo = datetime .timezone .utc ).timestamp ()) - 1
62
+ )
63
+ try :
64
+ calendar .timegm (time .gmtime (MAX_TIME ))
65
+ except OSError :
66
+ # Hit the maximum representable time on Windows
67
+ # https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64
68
+ MAX_TIME = calendar .timegm ((3000 , 12 , 31 , 23 , 59 , 59 , - 1 , - 1 , - 1 ))
69
+ except OverflowError :
70
+ # #4515: datetime.max may not be representable on 32-bit platforms
71
+ MAX_TIME = 2 ** 31 - 1
72
+ # Avoid minuses in the future, 3x faster
73
+ SUB_MAX_TIME = MAX_TIME - 1
58
74
59
75
def __init__ (
60
76
self ,
@@ -83,14 +99,8 @@ def __init__(
83
99
for url in treat_as_secure_origin
84
100
]
85
101
self ._treat_as_secure_origin = treat_as_secure_origin
86
- self ._next_expiration = next_whole_second ()
87
- self ._expirations : Dict [Tuple [str , str , str ], datetime .datetime ] = {}
88
- # #4515: datetime.max may not be representable on 32-bit platforms
89
- self ._max_time = self .MAX_TIME
90
- try :
91
- self ._max_time .timestamp ()
92
- except OverflowError :
93
- self ._max_time = self .MAX_32BIT_TIME
102
+ self ._next_expiration : float = ceil (time .time ())
103
+ self ._expirations : Dict [Tuple [str , str , str ], float ] = {}
94
104
95
105
def save (self , file_path : PathLike ) -> None :
96
106
file_path = pathlib .Path (file_path )
@@ -104,14 +114,14 @@ def load(self, file_path: PathLike) -> None:
104
114
105
115
def clear (self , predicate : Optional [ClearCookiePredicate ] = None ) -> None :
106
116
if predicate is None :
107
- self ._next_expiration = next_whole_second ( )
117
+ self ._next_expiration = ceil ( time . time () )
108
118
self ._cookies .clear ()
109
119
self ._host_only_cookies .clear ()
110
120
self ._expirations .clear ()
111
121
return
112
122
113
123
to_del = []
114
- now = datetime . datetime . now ( datetime . timezone . utc )
124
+ now = time . time ( )
115
125
for (domain , path ), cookie in self ._cookies .items ():
116
126
for name , morsel in cookie .items ():
117
127
key = (domain , path , name )
@@ -127,13 +137,11 @@ def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None:
127
137
del self ._expirations [(domain , path , name )]
128
138
self ._cookies [(domain , path )].pop (name , None )
129
139
130
- next_expiration = min (self ._expirations .values (), default = self ._max_time )
131
- try :
132
- self ._next_expiration = next_expiration .replace (
133
- microsecond = 0
134
- ) + datetime .timedelta (seconds = 1 )
135
- except OverflowError :
136
- self ._next_expiration = self ._max_time
140
+ self ._next_expiration = (
141
+ min (* self ._expirations .values (), self .SUB_MAX_TIME ) + 1
142
+ if self ._expirations
143
+ else self .MAX_TIME
144
+ )
137
145
138
146
def clear_domain (self , domain : str ) -> None :
139
147
self .clear (lambda x : self ._is_domain_match (domain , x ["domain" ]))
@@ -149,9 +157,7 @@ def __len__(self) -> int:
149
157
def _do_expiration (self ) -> None :
150
158
self .clear (lambda x : False )
151
159
152
- def _expire_cookie (
153
- self , when : datetime .datetime , domain : str , path : str , name : str
154
- ) -> None :
160
+ def _expire_cookie (self , when : float , domain : str , path : str , name : str ) -> None :
155
161
self ._next_expiration = min (self ._next_expiration , when )
156
162
self ._expirations [(domain , path , name )] = when
157
163
@@ -209,12 +215,7 @@ def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> No
209
215
if max_age :
210
216
try :
211
217
delta_seconds = int (max_age )
212
- try :
213
- max_age_expiration = datetime .datetime .now (
214
- datetime .timezone .utc
215
- ) + datetime .timedelta (seconds = delta_seconds )
216
- except OverflowError :
217
- max_age_expiration = self ._max_time
218
+ max_age_expiration = min (time .time () + delta_seconds , self .MAX_TIME )
218
219
self ._expire_cookie (max_age_expiration , domain , path , name )
219
220
except ValueError :
220
221
cookie ["max-age" ] = ""
@@ -323,7 +324,7 @@ def _is_path_match(req_path: str, cookie_path: str) -> bool:
323
324
return non_matching .startswith ("/" )
324
325
325
326
@classmethod
326
- def _parse_date (cls , date_str : str ) -> Optional [datetime . datetime ]:
327
+ def _parse_date (cls , date_str : str ) -> Optional [int ]:
327
328
"""Implements date string parsing adhering to RFC 6265."""
328
329
if not date_str :
329
330
return None
@@ -384,9 +385,7 @@ def _parse_date(cls, date_str: str) -> Optional[datetime.datetime]:
384
385
if year < 1601 or hour > 23 or minute > 59 or second > 59 :
385
386
return None
386
387
387
- return datetime .datetime (
388
- year , month , day , hour , minute , second , tzinfo = datetime .timezone .utc
389
- )
388
+ return calendar .timegm ((year , month , day , hour , minute , second , - 1 , - 1 , - 1 ))
390
389
391
390
392
391
class DummyCookieJar (AbstractCookieJar ):
0 commit comments