jpayne@7
|
1 """
|
jpayne@7
|
2 requests.cookies
|
jpayne@7
|
3 ~~~~~~~~~~~~~~~~
|
jpayne@7
|
4
|
jpayne@7
|
5 Compatibility code to be able to use `cookielib.CookieJar` with requests.
|
jpayne@7
|
6
|
jpayne@7
|
7 requests.utils imports from here, so be careful with imports.
|
jpayne@7
|
8 """
|
jpayne@7
|
9
|
jpayne@7
|
10 import calendar
|
jpayne@7
|
11 import copy
|
jpayne@7
|
12 import time
|
jpayne@7
|
13
|
jpayne@7
|
14 from ._internal_utils import to_native_string
|
jpayne@7
|
15 from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
|
jpayne@7
|
16
|
jpayne@7
|
17 try:
|
jpayne@7
|
18 import threading
|
jpayne@7
|
19 except ImportError:
|
jpayne@7
|
20 import dummy_threading as threading
|
jpayne@7
|
21
|
jpayne@7
|
22
|
jpayne@7
|
23 class MockRequest:
|
jpayne@7
|
24 """Wraps a `requests.Request` to mimic a `urllib2.Request`.
|
jpayne@7
|
25
|
jpayne@7
|
26 The code in `cookielib.CookieJar` expects this interface in order to correctly
|
jpayne@7
|
27 manage cookie policies, i.e., determine whether a cookie can be set, given the
|
jpayne@7
|
28 domains of the request and the cookie.
|
jpayne@7
|
29
|
jpayne@7
|
30 The original request object is read-only. The client is responsible for collecting
|
jpayne@7
|
31 the new headers via `get_new_headers()` and interpreting them appropriately. You
|
jpayne@7
|
32 probably want `get_cookie_header`, defined below.
|
jpayne@7
|
33 """
|
jpayne@7
|
34
|
jpayne@7
|
35 def __init__(self, request):
|
jpayne@7
|
36 self._r = request
|
jpayne@7
|
37 self._new_headers = {}
|
jpayne@7
|
38 self.type = urlparse(self._r.url).scheme
|
jpayne@7
|
39
|
jpayne@7
|
40 def get_type(self):
|
jpayne@7
|
41 return self.type
|
jpayne@7
|
42
|
jpayne@7
|
43 def get_host(self):
|
jpayne@7
|
44 return urlparse(self._r.url).netloc
|
jpayne@7
|
45
|
jpayne@7
|
46 def get_origin_req_host(self):
|
jpayne@7
|
47 return self.get_host()
|
jpayne@7
|
48
|
jpayne@7
|
49 def get_full_url(self):
|
jpayne@7
|
50 # Only return the response's URL if the user hadn't set the Host
|
jpayne@7
|
51 # header
|
jpayne@7
|
52 if not self._r.headers.get("Host"):
|
jpayne@7
|
53 return self._r.url
|
jpayne@7
|
54 # If they did set it, retrieve it and reconstruct the expected domain
|
jpayne@7
|
55 host = to_native_string(self._r.headers["Host"], encoding="utf-8")
|
jpayne@7
|
56 parsed = urlparse(self._r.url)
|
jpayne@7
|
57 # Reconstruct the URL as we expect it
|
jpayne@7
|
58 return urlunparse(
|
jpayne@7
|
59 [
|
jpayne@7
|
60 parsed.scheme,
|
jpayne@7
|
61 host,
|
jpayne@7
|
62 parsed.path,
|
jpayne@7
|
63 parsed.params,
|
jpayne@7
|
64 parsed.query,
|
jpayne@7
|
65 parsed.fragment,
|
jpayne@7
|
66 ]
|
jpayne@7
|
67 )
|
jpayne@7
|
68
|
jpayne@7
|
69 def is_unverifiable(self):
|
jpayne@7
|
70 return True
|
jpayne@7
|
71
|
jpayne@7
|
72 def has_header(self, name):
|
jpayne@7
|
73 return name in self._r.headers or name in self._new_headers
|
jpayne@7
|
74
|
jpayne@7
|
75 def get_header(self, name, default=None):
|
jpayne@7
|
76 return self._r.headers.get(name, self._new_headers.get(name, default))
|
jpayne@7
|
77
|
jpayne@7
|
78 def add_header(self, key, val):
|
jpayne@7
|
79 """cookielib has no legitimate use for this method; add it back if you find one."""
|
jpayne@7
|
80 raise NotImplementedError(
|
jpayne@7
|
81 "Cookie headers should be added with add_unredirected_header()"
|
jpayne@7
|
82 )
|
jpayne@7
|
83
|
jpayne@7
|
84 def add_unredirected_header(self, name, value):
|
jpayne@7
|
85 self._new_headers[name] = value
|
jpayne@7
|
86
|
jpayne@7
|
87 def get_new_headers(self):
|
jpayne@7
|
88 return self._new_headers
|
jpayne@7
|
89
|
jpayne@7
|
90 @property
|
jpayne@7
|
91 def unverifiable(self):
|
jpayne@7
|
92 return self.is_unverifiable()
|
jpayne@7
|
93
|
jpayne@7
|
94 @property
|
jpayne@7
|
95 def origin_req_host(self):
|
jpayne@7
|
96 return self.get_origin_req_host()
|
jpayne@7
|
97
|
jpayne@7
|
98 @property
|
jpayne@7
|
99 def host(self):
|
jpayne@7
|
100 return self.get_host()
|
jpayne@7
|
101
|
jpayne@7
|
102
|
jpayne@7
|
103 class MockResponse:
|
jpayne@7
|
104 """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
|
jpayne@7
|
105
|
jpayne@7
|
106 ...what? Basically, expose the parsed HTTP headers from the server response
|
jpayne@7
|
107 the way `cookielib` expects to see them.
|
jpayne@7
|
108 """
|
jpayne@7
|
109
|
jpayne@7
|
110 def __init__(self, headers):
|
jpayne@7
|
111 """Make a MockResponse for `cookielib` to read.
|
jpayne@7
|
112
|
jpayne@7
|
113 :param headers: a httplib.HTTPMessage or analogous carrying the headers
|
jpayne@7
|
114 """
|
jpayne@7
|
115 self._headers = headers
|
jpayne@7
|
116
|
jpayne@7
|
117 def info(self):
|
jpayne@7
|
118 return self._headers
|
jpayne@7
|
119
|
jpayne@7
|
120 def getheaders(self, name):
|
jpayne@7
|
121 self._headers.getheaders(name)
|
jpayne@7
|
122
|
jpayne@7
|
123
|
jpayne@7
|
124 def extract_cookies_to_jar(jar, request, response):
|
jpayne@7
|
125 """Extract the cookies from the response into a CookieJar.
|
jpayne@7
|
126
|
jpayne@7
|
127 :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
|
jpayne@7
|
128 :param request: our own requests.Request object
|
jpayne@7
|
129 :param response: urllib3.HTTPResponse object
|
jpayne@7
|
130 """
|
jpayne@7
|
131 if not (hasattr(response, "_original_response") and response._original_response):
|
jpayne@7
|
132 return
|
jpayne@7
|
133 # the _original_response field is the wrapped httplib.HTTPResponse object,
|
jpayne@7
|
134 req = MockRequest(request)
|
jpayne@7
|
135 # pull out the HTTPMessage with the headers and put it in the mock:
|
jpayne@7
|
136 res = MockResponse(response._original_response.msg)
|
jpayne@7
|
137 jar.extract_cookies(res, req)
|
jpayne@7
|
138
|
jpayne@7
|
139
|
jpayne@7
|
140 def get_cookie_header(jar, request):
|
jpayne@7
|
141 """
|
jpayne@7
|
142 Produce an appropriate Cookie header string to be sent with `request`, or None.
|
jpayne@7
|
143
|
jpayne@7
|
144 :rtype: str
|
jpayne@7
|
145 """
|
jpayne@7
|
146 r = MockRequest(request)
|
jpayne@7
|
147 jar.add_cookie_header(r)
|
jpayne@7
|
148 return r.get_new_headers().get("Cookie")
|
jpayne@7
|
149
|
jpayne@7
|
150
|
jpayne@7
|
151 def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
jpayne@7
|
152 """Unsets a cookie by name, by default over all domains and paths.
|
jpayne@7
|
153
|
jpayne@7
|
154 Wraps CookieJar.clear(), is O(n).
|
jpayne@7
|
155 """
|
jpayne@7
|
156 clearables = []
|
jpayne@7
|
157 for cookie in cookiejar:
|
jpayne@7
|
158 if cookie.name != name:
|
jpayne@7
|
159 continue
|
jpayne@7
|
160 if domain is not None and domain != cookie.domain:
|
jpayne@7
|
161 continue
|
jpayne@7
|
162 if path is not None and path != cookie.path:
|
jpayne@7
|
163 continue
|
jpayne@7
|
164 clearables.append((cookie.domain, cookie.path, cookie.name))
|
jpayne@7
|
165
|
jpayne@7
|
166 for domain, path, name in clearables:
|
jpayne@7
|
167 cookiejar.clear(domain, path, name)
|
jpayne@7
|
168
|
jpayne@7
|
169
|
jpayne@7
|
170 class CookieConflictError(RuntimeError):
|
jpayne@7
|
171 """There are two cookies that meet the criteria specified in the cookie jar.
|
jpayne@7
|
172 Use .get and .set and include domain and path args in order to be more specific.
|
jpayne@7
|
173 """
|
jpayne@7
|
174
|
jpayne@7
|
175
|
jpayne@7
|
176 class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
|
jpayne@7
|
177 """Compatibility class; is a cookielib.CookieJar, but exposes a dict
|
jpayne@7
|
178 interface.
|
jpayne@7
|
179
|
jpayne@7
|
180 This is the CookieJar we create by default for requests and sessions that
|
jpayne@7
|
181 don't specify one, since some clients may expect response.cookies and
|
jpayne@7
|
182 session.cookies to support dict operations.
|
jpayne@7
|
183
|
jpayne@7
|
184 Requests does not use the dict interface internally; it's just for
|
jpayne@7
|
185 compatibility with external client code. All requests code should work
|
jpayne@7
|
186 out of the box with externally provided instances of ``CookieJar``, e.g.
|
jpayne@7
|
187 ``LWPCookieJar`` and ``FileCookieJar``.
|
jpayne@7
|
188
|
jpayne@7
|
189 Unlike a regular CookieJar, this class is pickleable.
|
jpayne@7
|
190
|
jpayne@7
|
191 .. warning:: dictionary operations that are normally O(1) may be O(n).
|
jpayne@7
|
192 """
|
jpayne@7
|
193
|
jpayne@7
|
194 def get(self, name, default=None, domain=None, path=None):
|
jpayne@7
|
195 """Dict-like get() that also supports optional domain and path args in
|
jpayne@7
|
196 order to resolve naming collisions from using one cookie jar over
|
jpayne@7
|
197 multiple domains.
|
jpayne@7
|
198
|
jpayne@7
|
199 .. warning:: operation is O(n), not O(1).
|
jpayne@7
|
200 """
|
jpayne@7
|
201 try:
|
jpayne@7
|
202 return self._find_no_duplicates(name, domain, path)
|
jpayne@7
|
203 except KeyError:
|
jpayne@7
|
204 return default
|
jpayne@7
|
205
|
jpayne@7
|
206 def set(self, name, value, **kwargs):
|
jpayne@7
|
207 """Dict-like set() that also supports optional domain and path args in
|
jpayne@7
|
208 order to resolve naming collisions from using one cookie jar over
|
jpayne@7
|
209 multiple domains.
|
jpayne@7
|
210 """
|
jpayne@7
|
211 # support client code that unsets cookies by assignment of a None value:
|
jpayne@7
|
212 if value is None:
|
jpayne@7
|
213 remove_cookie_by_name(
|
jpayne@7
|
214 self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
|
jpayne@7
|
215 )
|
jpayne@7
|
216 return
|
jpayne@7
|
217
|
jpayne@7
|
218 if isinstance(value, Morsel):
|
jpayne@7
|
219 c = morsel_to_cookie(value)
|
jpayne@7
|
220 else:
|
jpayne@7
|
221 c = create_cookie(name, value, **kwargs)
|
jpayne@7
|
222 self.set_cookie(c)
|
jpayne@7
|
223 return c
|
jpayne@7
|
224
|
jpayne@7
|
225 def iterkeys(self):
|
jpayne@7
|
226 """Dict-like iterkeys() that returns an iterator of names of cookies
|
jpayne@7
|
227 from the jar.
|
jpayne@7
|
228
|
jpayne@7
|
229 .. seealso:: itervalues() and iteritems().
|
jpayne@7
|
230 """
|
jpayne@7
|
231 for cookie in iter(self):
|
jpayne@7
|
232 yield cookie.name
|
jpayne@7
|
233
|
jpayne@7
|
234 def keys(self):
|
jpayne@7
|
235 """Dict-like keys() that returns a list of names of cookies from the
|
jpayne@7
|
236 jar.
|
jpayne@7
|
237
|
jpayne@7
|
238 .. seealso:: values() and items().
|
jpayne@7
|
239 """
|
jpayne@7
|
240 return list(self.iterkeys())
|
jpayne@7
|
241
|
jpayne@7
|
242 def itervalues(self):
|
jpayne@7
|
243 """Dict-like itervalues() that returns an iterator of values of cookies
|
jpayne@7
|
244 from the jar.
|
jpayne@7
|
245
|
jpayne@7
|
246 .. seealso:: iterkeys() and iteritems().
|
jpayne@7
|
247 """
|
jpayne@7
|
248 for cookie in iter(self):
|
jpayne@7
|
249 yield cookie.value
|
jpayne@7
|
250
|
jpayne@7
|
251 def values(self):
|
jpayne@7
|
252 """Dict-like values() that returns a list of values of cookies from the
|
jpayne@7
|
253 jar.
|
jpayne@7
|
254
|
jpayne@7
|
255 .. seealso:: keys() and items().
|
jpayne@7
|
256 """
|
jpayne@7
|
257 return list(self.itervalues())
|
jpayne@7
|
258
|
jpayne@7
|
259 def iteritems(self):
|
jpayne@7
|
260 """Dict-like iteritems() that returns an iterator of name-value tuples
|
jpayne@7
|
261 from the jar.
|
jpayne@7
|
262
|
jpayne@7
|
263 .. seealso:: iterkeys() and itervalues().
|
jpayne@7
|
264 """
|
jpayne@7
|
265 for cookie in iter(self):
|
jpayne@7
|
266 yield cookie.name, cookie.value
|
jpayne@7
|
267
|
jpayne@7
|
268 def items(self):
|
jpayne@7
|
269 """Dict-like items() that returns a list of name-value tuples from the
|
jpayne@7
|
270 jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
|
jpayne@7
|
271 vanilla python dict of key value pairs.
|
jpayne@7
|
272
|
jpayne@7
|
273 .. seealso:: keys() and values().
|
jpayne@7
|
274 """
|
jpayne@7
|
275 return list(self.iteritems())
|
jpayne@7
|
276
|
jpayne@7
|
277 def list_domains(self):
|
jpayne@7
|
278 """Utility method to list all the domains in the jar."""
|
jpayne@7
|
279 domains = []
|
jpayne@7
|
280 for cookie in iter(self):
|
jpayne@7
|
281 if cookie.domain not in domains:
|
jpayne@7
|
282 domains.append(cookie.domain)
|
jpayne@7
|
283 return domains
|
jpayne@7
|
284
|
jpayne@7
|
285 def list_paths(self):
|
jpayne@7
|
286 """Utility method to list all the paths in the jar."""
|
jpayne@7
|
287 paths = []
|
jpayne@7
|
288 for cookie in iter(self):
|
jpayne@7
|
289 if cookie.path not in paths:
|
jpayne@7
|
290 paths.append(cookie.path)
|
jpayne@7
|
291 return paths
|
jpayne@7
|
292
|
jpayne@7
|
293 def multiple_domains(self):
|
jpayne@7
|
294 """Returns True if there are multiple domains in the jar.
|
jpayne@7
|
295 Returns False otherwise.
|
jpayne@7
|
296
|
jpayne@7
|
297 :rtype: bool
|
jpayne@7
|
298 """
|
jpayne@7
|
299 domains = []
|
jpayne@7
|
300 for cookie in iter(self):
|
jpayne@7
|
301 if cookie.domain is not None and cookie.domain in domains:
|
jpayne@7
|
302 return True
|
jpayne@7
|
303 domains.append(cookie.domain)
|
jpayne@7
|
304 return False # there is only one domain in jar
|
jpayne@7
|
305
|
jpayne@7
|
306 def get_dict(self, domain=None, path=None):
|
jpayne@7
|
307 """Takes as an argument an optional domain and path and returns a plain
|
jpayne@7
|
308 old Python dict of name-value pairs of cookies that meet the
|
jpayne@7
|
309 requirements.
|
jpayne@7
|
310
|
jpayne@7
|
311 :rtype: dict
|
jpayne@7
|
312 """
|
jpayne@7
|
313 dictionary = {}
|
jpayne@7
|
314 for cookie in iter(self):
|
jpayne@7
|
315 if (domain is None or cookie.domain == domain) and (
|
jpayne@7
|
316 path is None or cookie.path == path
|
jpayne@7
|
317 ):
|
jpayne@7
|
318 dictionary[cookie.name] = cookie.value
|
jpayne@7
|
319 return dictionary
|
jpayne@7
|
320
|
jpayne@7
|
321 def __contains__(self, name):
|
jpayne@7
|
322 try:
|
jpayne@7
|
323 return super().__contains__(name)
|
jpayne@7
|
324 except CookieConflictError:
|
jpayne@7
|
325 return True
|
jpayne@7
|
326
|
jpayne@7
|
327 def __getitem__(self, name):
|
jpayne@7
|
328 """Dict-like __getitem__() for compatibility with client code. Throws
|
jpayne@7
|
329 exception if there are more than one cookie with name. In that case,
|
jpayne@7
|
330 use the more explicit get() method instead.
|
jpayne@7
|
331
|
jpayne@7
|
332 .. warning:: operation is O(n), not O(1).
|
jpayne@7
|
333 """
|
jpayne@7
|
334 return self._find_no_duplicates(name)
|
jpayne@7
|
335
|
jpayne@7
|
336 def __setitem__(self, name, value):
|
jpayne@7
|
337 """Dict-like __setitem__ for compatibility with client code. Throws
|
jpayne@7
|
338 exception if there is already a cookie of that name in the jar. In that
|
jpayne@7
|
339 case, use the more explicit set() method instead.
|
jpayne@7
|
340 """
|
jpayne@7
|
341 self.set(name, value)
|
jpayne@7
|
342
|
jpayne@7
|
343 def __delitem__(self, name):
|
jpayne@7
|
344 """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
|
jpayne@7
|
345 ``remove_cookie_by_name()``.
|
jpayne@7
|
346 """
|
jpayne@7
|
347 remove_cookie_by_name(self, name)
|
jpayne@7
|
348
|
jpayne@7
|
349 def set_cookie(self, cookie, *args, **kwargs):
|
jpayne@7
|
350 if (
|
jpayne@7
|
351 hasattr(cookie.value, "startswith")
|
jpayne@7
|
352 and cookie.value.startswith('"')
|
jpayne@7
|
353 and cookie.value.endswith('"')
|
jpayne@7
|
354 ):
|
jpayne@7
|
355 cookie.value = cookie.value.replace('\\"', "")
|
jpayne@7
|
356 return super().set_cookie(cookie, *args, **kwargs)
|
jpayne@7
|
357
|
jpayne@7
|
358 def update(self, other):
|
jpayne@7
|
359 """Updates this jar with cookies from another CookieJar or dict-like"""
|
jpayne@7
|
360 if isinstance(other, cookielib.CookieJar):
|
jpayne@7
|
361 for cookie in other:
|
jpayne@7
|
362 self.set_cookie(copy.copy(cookie))
|
jpayne@7
|
363 else:
|
jpayne@7
|
364 super().update(other)
|
jpayne@7
|
365
|
jpayne@7
|
366 def _find(self, name, domain=None, path=None):
|
jpayne@7
|
367 """Requests uses this method internally to get cookie values.
|
jpayne@7
|
368
|
jpayne@7
|
369 If there are conflicting cookies, _find arbitrarily chooses one.
|
jpayne@7
|
370 See _find_no_duplicates if you want an exception thrown if there are
|
jpayne@7
|
371 conflicting cookies.
|
jpayne@7
|
372
|
jpayne@7
|
373 :param name: a string containing name of cookie
|
jpayne@7
|
374 :param domain: (optional) string containing domain of cookie
|
jpayne@7
|
375 :param path: (optional) string containing path of cookie
|
jpayne@7
|
376 :return: cookie.value
|
jpayne@7
|
377 """
|
jpayne@7
|
378 for cookie in iter(self):
|
jpayne@7
|
379 if cookie.name == name:
|
jpayne@7
|
380 if domain is None or cookie.domain == domain:
|
jpayne@7
|
381 if path is None or cookie.path == path:
|
jpayne@7
|
382 return cookie.value
|
jpayne@7
|
383
|
jpayne@7
|
384 raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
|
jpayne@7
|
385
|
jpayne@7
|
386 def _find_no_duplicates(self, name, domain=None, path=None):
|
jpayne@7
|
387 """Both ``__get_item__`` and ``get`` call this function: it's never
|
jpayne@7
|
388 used elsewhere in Requests.
|
jpayne@7
|
389
|
jpayne@7
|
390 :param name: a string containing name of cookie
|
jpayne@7
|
391 :param domain: (optional) string containing domain of cookie
|
jpayne@7
|
392 :param path: (optional) string containing path of cookie
|
jpayne@7
|
393 :raises KeyError: if cookie is not found
|
jpayne@7
|
394 :raises CookieConflictError: if there are multiple cookies
|
jpayne@7
|
395 that match name and optionally domain and path
|
jpayne@7
|
396 :return: cookie.value
|
jpayne@7
|
397 """
|
jpayne@7
|
398 toReturn = None
|
jpayne@7
|
399 for cookie in iter(self):
|
jpayne@7
|
400 if cookie.name == name:
|
jpayne@7
|
401 if domain is None or cookie.domain == domain:
|
jpayne@7
|
402 if path is None or cookie.path == path:
|
jpayne@7
|
403 if toReturn is not None:
|
jpayne@7
|
404 # if there are multiple cookies that meet passed in criteria
|
jpayne@7
|
405 raise CookieConflictError(
|
jpayne@7
|
406 f"There are multiple cookies with name, {name!r}"
|
jpayne@7
|
407 )
|
jpayne@7
|
408 # we will eventually return this as long as no cookie conflict
|
jpayne@7
|
409 toReturn = cookie.value
|
jpayne@7
|
410
|
jpayne@7
|
411 if toReturn:
|
jpayne@7
|
412 return toReturn
|
jpayne@7
|
413 raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
|
jpayne@7
|
414
|
jpayne@7
|
415 def __getstate__(self):
|
jpayne@7
|
416 """Unlike a normal CookieJar, this class is pickleable."""
|
jpayne@7
|
417 state = self.__dict__.copy()
|
jpayne@7
|
418 # remove the unpickleable RLock object
|
jpayne@7
|
419 state.pop("_cookies_lock")
|
jpayne@7
|
420 return state
|
jpayne@7
|
421
|
jpayne@7
|
422 def __setstate__(self, state):
|
jpayne@7
|
423 """Unlike a normal CookieJar, this class is pickleable."""
|
jpayne@7
|
424 self.__dict__.update(state)
|
jpayne@7
|
425 if "_cookies_lock" not in self.__dict__:
|
jpayne@7
|
426 self._cookies_lock = threading.RLock()
|
jpayne@7
|
427
|
jpayne@7
|
428 def copy(self):
|
jpayne@7
|
429 """Return a copy of this RequestsCookieJar."""
|
jpayne@7
|
430 new_cj = RequestsCookieJar()
|
jpayne@7
|
431 new_cj.set_policy(self.get_policy())
|
jpayne@7
|
432 new_cj.update(self)
|
jpayne@7
|
433 return new_cj
|
jpayne@7
|
434
|
jpayne@7
|
435 def get_policy(self):
|
jpayne@7
|
436 """Return the CookiePolicy instance used."""
|
jpayne@7
|
437 return self._policy
|
jpayne@7
|
438
|
jpayne@7
|
439
|
jpayne@7
|
440 def _copy_cookie_jar(jar):
|
jpayne@7
|
441 if jar is None:
|
jpayne@7
|
442 return None
|
jpayne@7
|
443
|
jpayne@7
|
444 if hasattr(jar, "copy"):
|
jpayne@7
|
445 # We're dealing with an instance of RequestsCookieJar
|
jpayne@7
|
446 return jar.copy()
|
jpayne@7
|
447 # We're dealing with a generic CookieJar instance
|
jpayne@7
|
448 new_jar = copy.copy(jar)
|
jpayne@7
|
449 new_jar.clear()
|
jpayne@7
|
450 for cookie in jar:
|
jpayne@7
|
451 new_jar.set_cookie(copy.copy(cookie))
|
jpayne@7
|
452 return new_jar
|
jpayne@7
|
453
|
jpayne@7
|
454
|
jpayne@7
|
455 def create_cookie(name, value, **kwargs):
|
jpayne@7
|
456 """Make a cookie from underspecified parameters.
|
jpayne@7
|
457
|
jpayne@7
|
458 By default, the pair of `name` and `value` will be set for the domain ''
|
jpayne@7
|
459 and sent on every request (this is sometimes called a "supercookie").
|
jpayne@7
|
460 """
|
jpayne@7
|
461 result = {
|
jpayne@7
|
462 "version": 0,
|
jpayne@7
|
463 "name": name,
|
jpayne@7
|
464 "value": value,
|
jpayne@7
|
465 "port": None,
|
jpayne@7
|
466 "domain": "",
|
jpayne@7
|
467 "path": "/",
|
jpayne@7
|
468 "secure": False,
|
jpayne@7
|
469 "expires": None,
|
jpayne@7
|
470 "discard": True,
|
jpayne@7
|
471 "comment": None,
|
jpayne@7
|
472 "comment_url": None,
|
jpayne@7
|
473 "rest": {"HttpOnly": None},
|
jpayne@7
|
474 "rfc2109": False,
|
jpayne@7
|
475 }
|
jpayne@7
|
476
|
jpayne@7
|
477 badargs = set(kwargs) - set(result)
|
jpayne@7
|
478 if badargs:
|
jpayne@7
|
479 raise TypeError(
|
jpayne@7
|
480 f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
|
jpayne@7
|
481 )
|
jpayne@7
|
482
|
jpayne@7
|
483 result.update(kwargs)
|
jpayne@7
|
484 result["port_specified"] = bool(result["port"])
|
jpayne@7
|
485 result["domain_specified"] = bool(result["domain"])
|
jpayne@7
|
486 result["domain_initial_dot"] = result["domain"].startswith(".")
|
jpayne@7
|
487 result["path_specified"] = bool(result["path"])
|
jpayne@7
|
488
|
jpayne@7
|
489 return cookielib.Cookie(**result)
|
jpayne@7
|
490
|
jpayne@7
|
491
|
jpayne@7
|
492 def morsel_to_cookie(morsel):
|
jpayne@7
|
493 """Convert a Morsel object into a Cookie containing the one k/v pair."""
|
jpayne@7
|
494
|
jpayne@7
|
495 expires = None
|
jpayne@7
|
496 if morsel["max-age"]:
|
jpayne@7
|
497 try:
|
jpayne@7
|
498 expires = int(time.time() + int(morsel["max-age"]))
|
jpayne@7
|
499 except ValueError:
|
jpayne@7
|
500 raise TypeError(f"max-age: {morsel['max-age']} must be integer")
|
jpayne@7
|
501 elif morsel["expires"]:
|
jpayne@7
|
502 time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
|
jpayne@7
|
503 expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
|
jpayne@7
|
504 return create_cookie(
|
jpayne@7
|
505 comment=morsel["comment"],
|
jpayne@7
|
506 comment_url=bool(morsel["comment"]),
|
jpayne@7
|
507 discard=False,
|
jpayne@7
|
508 domain=morsel["domain"],
|
jpayne@7
|
509 expires=expires,
|
jpayne@7
|
510 name=morsel.key,
|
jpayne@7
|
511 path=morsel["path"],
|
jpayne@7
|
512 port=None,
|
jpayne@7
|
513 rest={"HttpOnly": morsel["httponly"]},
|
jpayne@7
|
514 rfc2109=False,
|
jpayne@7
|
515 secure=bool(morsel["secure"]),
|
jpayne@7
|
516 value=morsel.value,
|
jpayne@7
|
517 version=morsel["version"] or 0,
|
jpayne@7
|
518 )
|
jpayne@7
|
519
|
jpayne@7
|
520
|
jpayne@7
|
521 def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
|
jpayne@7
|
522 """Returns a CookieJar from a key/value dictionary.
|
jpayne@7
|
523
|
jpayne@7
|
524 :param cookie_dict: Dict of key/values to insert into CookieJar.
|
jpayne@7
|
525 :param cookiejar: (optional) A cookiejar to add the cookies to.
|
jpayne@7
|
526 :param overwrite: (optional) If False, will not replace cookies
|
jpayne@7
|
527 already in the jar with new ones.
|
jpayne@7
|
528 :rtype: CookieJar
|
jpayne@7
|
529 """
|
jpayne@7
|
530 if cookiejar is None:
|
jpayne@7
|
531 cookiejar = RequestsCookieJar()
|
jpayne@7
|
532
|
jpayne@7
|
533 if cookie_dict is not None:
|
jpayne@7
|
534 names_from_jar = [cookie.name for cookie in cookiejar]
|
jpayne@7
|
535 for name in cookie_dict:
|
jpayne@7
|
536 if overwrite or (name not in names_from_jar):
|
jpayne@7
|
537 cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
|
jpayne@7
|
538
|
jpayne@7
|
539 return cookiejar
|
jpayne@7
|
540
|
jpayne@7
|
541
|
jpayne@7
|
542 def merge_cookies(cookiejar, cookies):
|
jpayne@7
|
543 """Add cookies to cookiejar and returns a merged CookieJar.
|
jpayne@7
|
544
|
jpayne@7
|
545 :param cookiejar: CookieJar object to add the cookies to.
|
jpayne@7
|
546 :param cookies: Dictionary or CookieJar object to be added.
|
jpayne@7
|
547 :rtype: CookieJar
|
jpayne@7
|
548 """
|
jpayne@7
|
549 if not isinstance(cookiejar, cookielib.CookieJar):
|
jpayne@7
|
550 raise ValueError("You can only merge into CookieJar")
|
jpayne@7
|
551
|
jpayne@7
|
552 if isinstance(cookies, dict):
|
jpayne@7
|
553 cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
|
jpayne@7
|
554 elif isinstance(cookies, cookielib.CookieJar):
|
jpayne@7
|
555 try:
|
jpayne@7
|
556 cookiejar.update(cookies)
|
jpayne@7
|
557 except AttributeError:
|
jpayne@7
|
558 for cookie_in_jar in cookies:
|
jpayne@7
|
559 cookiejar.set_cookie(cookie_in_jar)
|
jpayne@7
|
560
|
jpayne@7
|
561 return cookiejar
|