Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
cookies.py626 linesDownload Raw Back to requests
1"""2requests.cookies3~~~~~~~~~~~~~~~~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 annotations11 12import calendar13import copy14import time15from collections.abc import Iterator, MutableMapping16from http.cookiejar import Cookie, CookieJar, CookiePolicy17from typing import TYPE_CHECKING, Any, TypeVar, overload18 19from ._internal_utils import to_native_string20from ._types import is_prepared as _is_prepared21from .compat import Morsel, cookielib, urlparse, urlunparse22 23if TYPE_CHECKING:24    from _typeshed import SupportsKeysAndGetItem25 26    from .models import PreparedRequest27 28import threading29 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 correctly35    manage cookie policies, i.e., determine whether a cookie can be set, given the36    domains of the request and the cookie.37 38    The original request object is read-only. The client is responsible for collecting39    the new headers via `get_new_headers()` and interpreting them appropriately. You40    probably want `get_cookie_header`, defined below.41    """42 43    type: str44 45    def __init__(self, request: PreparedRequest) -> None:46        assert _is_prepared(request)47        self._r = request48        self._new_headers: dict[str, str] = {}49        self.type = urlparse(self._r.url).scheme50 51    def get_type(self) -> str:52        return self.type53 54    def get_host(self) -> str:55        return urlparse(self._r.url).netloc56 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 Host62        # header63        if not self._r.headers.get("Host"):64            return self._r.url65        # If they did set it, retrieve it and reconstruct the expected domain66        host = to_native_string(self._r.headers["Host"], encoding="utf-8")67        parsed = urlparse(self._r.url)68        # Reconstruct the URL as we expect it69        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 True82 83    def has_header(self, name: str) -> bool:84        return name in self._r.headers or name in self._new_headers85 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] = value97 98    def get_new_headers(self) -> dict[str, str]:99        return self._new_headers100 101    @property102    def unverifiable(self) -> bool:103        return self.is_unverifiable()104 105    @property106    def origin_req_host(self) -> str:107        return self.get_origin_req_host()108 109    @property110    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 response118    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 headers125        """126        self._headers = headers127 128    def info(self) -> Any:129        return self._headers130 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: Any137) -> 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 object142    :param response: urllib3.HTTPResponse object143    """144    if not (hasattr(response, "_original_response") and response._original_response):145        return146    # 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: str158    """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 = None166) -> 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            continue175        if domain is not None and domain != cookie.domain:176            continue177        if path is not None and path != cookie.path:178            continue179        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 dict193    interface.194 195    This is the CookieJar we create by default for requests and sessions that196    don't specify one, since some clients may expect response.cookies and197    session.cookies to support dict operations.198 199    Requests does not use the dict interface internally; it's just for200    compatibility with external client code. All requests code should work201    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: CookiePolicy210 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 in219        order to resolve naming collisions from using one cookie jar over220        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 default228 229    def set(230        self, name: str, value: str | Morsel[dict[str, str]] | None, **kwargs: Any231    ) -> Cookie | None:232        """Dict-like set() that also supports optional domain and path args in233        order to resolve naming collisions from using one cookie jar over234        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            return242 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 c249 250    def iterkeys(self) -> Iterator[str]:251        """Dict-like iterkeys() that returns an iterator of names of cookies252        from the jar.253 254        .. seealso:: itervalues() and iteritems().255        """256        for cookie in iter(self):257            yield cookie.name258 259    def keys(self) -> list[str]:  # type: ignore[override]260        """Dict-like keys() that returns a list of names of cookies from the261        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 cookies269        from the jar.270 271        .. seealso:: iterkeys() and iteritems().272        """273        for cookie in iter(self):274            yield cookie.value275 276    def values(self) -> list[str | None]:  # type: ignore[override]277        """Dict-like values() that returns a list of values of cookies from the278        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 tuples286        from the jar.287 288        .. seealso:: iterkeys() and itervalues().289        """290        for cookie in iter(self):291            yield cookie.name, cookie.value292 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 the295        jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a296        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 domains309 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 paths317 318    def multiple_domains(self) -> bool:319        """Returns True if there are multiple domains in the jar.320        Returns False otherwise.321 322        :rtype: bool323        """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 check327                return True328            domains.append(cookie.domain)329        return False  # there is only one domain in jar330 331    def get_dict(332        self, domain: str | None = None, path: str | None = None333    ) -> dict[str, str | None]:334        """Takes as an argument an optional domain and path and returns a plain335        old Python dict of name-value pairs of cookies that meet the336        requirements.337 338        :rtype: dict339        """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 == path344            ):345                dictionary[cookie.name] = cookie.value346        return dictionary347 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 True357 358    def __getitem__(self, name: str) -> str | None:359        """Dict-like __getitem__() for compatibility with client code. Throws360        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]] | None369    ) -> None:370        """Dict-like __setitem__ for compatibility with client code. Throws371        exception if there is already a cookie of that name in the jar. In that372        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``'s378        ``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 None385            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 = None403    ) -> 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 are408        conflicting cookies.409 410        :param name: a string containing name of cookie411        :param domain: (optional) string containing domain of cookie412        :param path: (optional) string containing path of cookie413        :return: cookie.value414        """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.value420 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 = None425    ) -> str:426        """Both ``__get_item__`` and ``get`` call this function: it's never427        used elsewhere in Requests.428 429        :param name: a string containing name of cookie430        :param domain: (optional) string containing domain of cookie431        :param path: (optional) string containing path of cookie432        :raises KeyError: if cookie is not found433        :raises CookieConflictError: if there are multiple cookies434            that match name and optionally domain and path435        :return: cookie.value436        """437        toReturn = None438        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 criteria444                            raise CookieConflictError(445                                f"There are multiple cookies with name, {name!r}"446                            )447                        # we will eventually return this as long as no cookie conflict448                        toReturn = cookie.value449 450        if toReturn is not None:451            return toReturn452        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 object458        state.pop("_cookies_lock")459        return state460 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_cj473 474    def get_policy(self) -> CookiePolicy:475        """Return the CookiePolicy instance used."""476        return self._policy477 478 479def _copy_cookie_jar(jar: CookieJar | None) -> CookieJar | None:  # type: ignore[reportUnusedFunction]  # cross-module usage in models.py480    if jar is None:481        return None482 483    if copy_method := getattr(jar, "copy", None):484        # We're dealing with an instance of RequestsCookieJar485        return copy_method()486    # We're dealing with a generic CookieJar instance487    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_jar492 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 = None535    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@overload564def cookiejar_from_dict(565    cookie_dict: dict[str, str] | None,566    cookiejar: None = None,567    overwrite: bool = True,568) -> RequestsCookieJar: ...569 570 571@overload572def 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 cookies589        already in the jar with new ones.590    :rtype: CookieJar591    """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 cookiejar602 603 604def merge_cookies(605    cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None606) -> 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: CookieJar612    """613    if not isinstance(cookiejar, cookielib.CookieJar):  # type: ignore[reportUnnecessaryIsInstance]  # runtime guard614        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 cookiejar626 
codekingpro/portable-devtools · Team Ai