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