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

254 statements  

1""" 

2requests.cookies 

3~~~~~~~~~~~~~~~~ 

4 

5Compatibility code to be able to use `http.cookiejar.CookieJar` with requests. 

6 

7requests.utils imports from here, so be careful with imports. 

8""" 

9 

10from __future__ import annotations 

11 

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 

18 

19from ._internal_utils import to_native_string 

20from ._types import is_prepared as _is_prepared 

21from .compat import Morsel, cookielib, urlparse, urlunparse 

22 

23if TYPE_CHECKING: 

24 from _typeshed import SupportsKeysAndGetItem 

25 

26 from .models import PreparedRequest 

27 

28import threading 

29 

30 

31class MockRequest: 

32 """Wraps a `requests.PreparedRequest` to mimic a `urllib2.Request`. 

33 

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. 

37 

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 """ 

42 

43 type: str 

44 

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 

50 

51 def get_type(self) -> str: 

52 return self.type 

53 

54 def get_host(self) -> str: 

55 return urlparse(self._r.url).netloc 

56 

57 def get_origin_req_host(self) -> str: 

58 return self.get_host() 

59 

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 ) 

79 

80 def is_unverifiable(self) -> bool: 

81 return True 

82 

83 def has_header(self, name: str) -> bool: 

84 return name in self._r.headers or name in self._new_headers 

85 

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] 

88 

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 ) 

94 

95 def add_unredirected_header(self, name: str, value: str) -> None: 

96 self._new_headers[name] = value 

97 

98 def get_new_headers(self) -> dict[str, str]: 

99 return self._new_headers 

100 

101 @property 

102 def unverifiable(self) -> bool: 

103 return self.is_unverifiable() 

104 

105 @property 

106 def origin_req_host(self) -> str: 

107 return self.get_origin_req_host() 

108 

109 @property 

110 def host(self) -> str: 

111 return self.get_host() 

112 

113 

114class MockResponse: 

115 """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. 

116 

117 ...what? Basically, expose the parsed HTTP headers from the server response 

118 the way `http.cookiejar` expects to see them. 

119 """ 

120 

121 def __init__(self, headers: Any) -> None: 

122 """Make a MockResponse for `cookiejar` to read. 

123 

124 :param headers: a httplib.HTTPMessage or analogous carrying the headers 

125 """ 

126 self._headers = headers 

127 

128 def info(self) -> Any: 

129 return self._headers 

130 

131 def getheaders(self, name: str) -> Any: 

132 self._headers.getheaders(name) 

133 

134 

135def extract_cookies_to_jar( 

136 jar: CookieJar, request: PreparedRequest, response: Any 

137) -> None: 

138 """Extract the cookies from the response into a CookieJar. 

139 

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] 

151 

152 

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. 

156 

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") 

162 

163 

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. 

168 

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)) 

180 

181 for domain, path, name in clearables: 

182 cookiejar.clear(domain, path, name) 

183 

184 

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 """ 

189 

190 

191class RequestsCookieJar(CookieJar, MutableMapping[str, str | None]): # type: ignore[misc] 

192 """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict 

193 interface. 

194 

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. 

198 

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``. 

203 

204 Unlike a regular CookieJar, this class is pickleable. 

205 

206 .. warning:: dictionary operations that are normally O(1) may be O(n). 

207 """ 

208 

209 _policy: CookiePolicy 

210 

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. 

221 

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 

228 

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 

242 

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 

249 

250 def iterkeys(self) -> Iterator[str]: 

251 """Dict-like iterkeys() that returns an iterator of names of cookies 

252 from the jar. 

253 

254 .. seealso:: itervalues() and iteritems(). 

255 """ 

256 for cookie in iter(self): 

257 yield cookie.name 

258 

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. 

262 

263 .. seealso:: values() and items(). 

264 """ 

265 return list(self.iterkeys()) 

266 

267 def itervalues(self) -> Iterator[str | None]: 

268 """Dict-like itervalues() that returns an iterator of values of cookies 

269 from the jar. 

270 

271 .. seealso:: iterkeys() and iteritems(). 

272 """ 

273 for cookie in iter(self): 

274 yield cookie.value 

275 

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. 

279 

280 .. seealso:: keys() and items(). 

281 """ 

282 return list(self.itervalues()) 

283 

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. 

287 

288 .. seealso:: iterkeys() and itervalues(). 

289 """ 

290 for cookie in iter(self): 

291 yield cookie.name, cookie.value 

292 

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. 

297 

298 .. seealso:: keys() and values(). 

299 """ 

300 return list(self.iteritems()) 

301 

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 

309 

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 

317 

318 def multiple_domains(self) -> bool: 

319 """Returns True if there are multiple domains in the jar. 

320 Returns False otherwise. 

321 

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 

330 

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. 

337 

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 

347 

348 def __iter__(self) -> Iterator[Cookie]: # type: ignore[override] 

349 """RequestCookieJar's __iter__ comes from CookieJar not MutableMapping.""" 

350 return super().__iter__() 

351 

352 def __contains__(self, name: object) -> bool: 

353 try: 

354 return super().__contains__(name) 

355 except CookieConflictError: 

356 return True 

357 

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. 

362 

363 .. warning:: operation is O(n), not O(1). 

364 """ 

365 return self._find_no_duplicates(name) 

366 

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) 

375 

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) 

381 

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) 

390 

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) 

400 

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. 

405 

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. 

409 

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 

420 

421 raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") 

422 

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. 

428 

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 

449 

450 if toReturn is not None: 

451 return toReturn 

452 raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") 

453 

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 

460 

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() 

466 

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 

473 

474 def get_policy(self) -> CookiePolicy: 

475 """Return the CookiePolicy instance used.""" 

476 return self._policy 

477 

478 

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 

482 

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 

492 

493 

494def create_cookie(name: str, value: str, **kwargs: Any) -> Cookie: 

495 """Make a cookie from underspecified parameters. 

496 

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 } 

515 

516 badargs = set(kwargs) - set(result) 

517 if badargs: 

518 raise TypeError( 

519 f"create_cookie() got unexpected keyword arguments: {list(badargs)}" 

520 ) 

521 

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"]) 

527 

528 return cookielib.Cookie(**result) 

529 

530 

531def morsel_to_cookie(morsel: Morsel[Any]) -> Cookie: 

532 """Convert a Morsel object into a Cookie containing the one k/v pair.""" 

533 

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 ) 

558 

559 

560_CookieJarT = TypeVar("_CookieJarT", bound=CookieJar) 

561 

562 

563@overload 

564def cookiejar_from_dict( 

565 cookie_dict: dict[str, str] | None, 

566 cookiejar: None = None, 

567 overwrite: bool = True, 

568) -> RequestsCookieJar: ... 

569 

570 

571@overload 

572def cookiejar_from_dict( 

573 cookie_dict: dict[str, str] | None, 

574 cookiejar: _CookieJarT, 

575 overwrite: bool = True, 

576) -> _CookieJarT: ... 

577 

578 

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. 

585 

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() 

594 

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])) 

600 

601 return cookiejar 

602 

603 

604def merge_cookies( 

605 cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None 

606) -> CookieJar: 

607 """Add cookies to cookiejar and returns a merged CookieJar. 

608 

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") 

615 

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) 

624 

625 return cookiejar