Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add aiohttp.MozillaCookieJar to load/save cookies.txt files #7998

Open
1 task done
milahu opened this issue Dec 28, 2023 · 0 comments
Open
1 task done

add aiohttp.MozillaCookieJar to load/save cookies.txt files #7998

milahu opened this issue Dec 28, 2023 · 0 comments

Comments

@milahu
Copy link

milahu commented Dec 28, 2023

Is your feature request related to a problem?

aiohttp.CookieJar can only load/save cookies from/to cookies.pickle files
but not from/to cookies.txt files

Describe the solution you'd like

there should be aiohttp.MozillaCookieJar

Describe alternatives you've considered

write my own class AiohttpMozillaCookieJar(aiohttp.CookieJar)

AiohttpMozillaCookieJar.py
# NOTE this is not-really tested... its just a draft

from http.cookiejar import MozillaCookieJar
from http.cookies import BaseCookie, Morsel, SimpleCookie
from collections import defaultdict
from pathlib import Path
from datetime import datetime
from http.cookiejar import Cookie
from typing import Union


from aiohttp import CookieJar


PathLike = Union[str, "os.PathLike[str]"]


class AiohttpMozillaCookieJar(CookieJar):
    """
    load/save cookies from/to a cookies.txt file with aiohttp

    convert between http.cookiejar.MozillaCookieJar and aiohttp.CookieJar

    import aiohttp
    from AiohttpMozillaCookieJar import AiohttpMozillaCookieJar
    jar = AiohttpMozillaCookieJar()
    cookies_txt_path = "cookies.txt"
    jar.load(cookies_txt_path)
    async with aiohttp.ClientSession(cookie_jar=jar) as session:
        url = "..."
        response = await aiohttp_session.get(url)
    jar.save(cookies_txt_path)

    author: Milan Hauth <milahu@gmail.com>
    license: MIT License
    """

    def load(self, file_path):

        file_path = Path(file_path)

        jar_1 = MozillaCookieJar()
        jar_1.load(file_path)

        # Cookie in jar_1 -> Morsel in jar_2

        # jar_1._cookies # dict
        # jar_1._cookies[domain] # dict
        # jar_1._cookies[domain][path] # dict
        # jar_1._cookies[domain][path][name] # Cookie

        # jar_2._cookies # collections.defaultdict(SimpleCookie)
        # jar_2._cookies[(domain, path)] # SimpleCookie
        # jar_2._cookies[(domain, path)][name] # Morsel

        for cookie_1 in jar_1:

            morsel_2 = Morsel()

            domain = cookie_1.domain
            path = cookie_1.path
            name = cookie_1.name

            if name.lower() in Morsel._reserved:
                #print(f"illegal morsel name: {name}")
                continue

            for key in (
                'path',
                'comment',
                'domain',
                'secure',
                'version',
            ):
                if (value := getattr(cookie_1, key)) is not None:
                    morsel_2[key] = value

            morsel_2._key = cookie_1.name
            morsel_2._value = cookie_1.value
            morsel_2._coded_value = cookie_1.value

            try:
                morsel_2['expires'] = datetime.fromtimestamp(cookie_1.expires).strftime(
                    "%a, %d %b %Y %H:%M:%S GMT"
                )
            except (
                OSError,  # Invalid argument (value of cookie.expires is invalid)
                TypeError,  # cookie.expires is None
            ):
                pass

            for key in ('HttpOnly', 'SameSite'):
                if (value := cookie_1.get_nonstandard_attr(key, None)) is not None:
                    morsel_2[key] = value

            self._cookies[(domain, path)][name] = morsel_2


    def save(self, file_path: PathLike) -> None:

        file_path = Path(file_path)

        jar_1 = MozillaCookieJar()

        # Morsel in jar_2 -> Cookie in jar_1

        # jar_2._cookies # collections.defaultdict(SimpleCookie)
        # jar_2._cookies[(domain, path)] # SimpleCookie
        # jar_2._cookies[(domain, path)][name] # Morsel

        # jar_1._cookies # dict
        # jar_1._cookies[domain] # dict
        # jar_1._cookies[domain][path] # dict
        # jar_1._cookies[domain][path][name] # Cookie

        for (domain, path), cookie_2 in self._cookies.items():

            for name, morsel_2 in cookie_2.items():

                try:
                    expires = self._expirations[(domain, path, name)].timestamp()
                except KeyError:
                    try:
                        expires = datetime.strptime(morsel_2["expires"], "%a, %d %b %Y %H:%M:%S %Z").timestamp()
                    except ValueError:
                        # morsel_2["expires"] can be empty string
                        expires = None

                cookie_1 = Cookie(
                    0, # version
                    name, # name
                    morsel_2.value, # value
                    None, # port
                    False, # port_specified
                    domain, # domain
                    False, # domain_specified
                    False, # domain_initial_dot
                    path, # path
                    False, # path_specified
                    morsel_2["secure"], # secure
                    expires, # expires
                    False, # discard
                    None, # comment
                    None, # comment_url
                    {}, # rest
                )
                if not domain in jar_1._cookies:
                    jar_1._cookies[domain] = dict()
                if not path in jar_1._cookies[domain]:
                    jar_1._cookies[domain][path] = dict()
                jar_1._cookies[domain][path][name] = cookie_1

        jar_1.save(file_path)
test-aiohttp-cookies.py
#!/usr/bin/env python

import sys
import asyncio
import aiohttp
from AiohttpMozillaCookieJar import AiohttpMozillaCookieJar
import json
import os

cookie_jar_backend = "pickle"
cookie_jar_backend = "txt"

cookie_jar_path = f"test-aiohttp-cookies.cookies.{cookie_jar_backend}"

cookie_jar_kwargs = dict()
#cookie_jar_kwargs["unsafe"] = True

cookie_jar = None
if cookie_jar_backend == "txt":
    cookie_jar = AiohttpMozillaCookieJar(**cookie_jar_kwargs)
elif cookie_jar_backend == "pickle":
    cookie_jar = aiohttp.CookieJar(**cookie_jar_kwargs)
else:
    raise ValueError(f"cookie_jar_backend {cookie_jar_backend}")

def dump_cookie_jar(cookie_jar):

    for k1 in cookie_jar._cookies:
        domain, path = k1

        #print(f"cookie_jar dir", dir(cookie_jar))

        #print(f"cookie_jar _expirations", cookie_jar._expirations)
        #print(f"cookie_jar _host_only_cookies", cookie_jar._host_only_cookies)
        #print(f"cookie_jar _max_time", cookie_jar._max_time)
        #print(f"cookie_jar _unsafe", cookie_jar._unsafe)

        #print(f"cookie_jar._cookies: domain={domain} path={path}", cookie_jar._cookies[k1])
        #print(f"cookie_jar._cookies: domain={domain} path={path} dir", dir(cookie_jar._cookies[k1]))
        #print(f"cookie_jar._cookies: domain={domain} path={path} items", cookie_jar._cookies[k1].items())
        for name, morsel in cookie_jar._cookies[k1].items():
            print(f"cookie_jar._cookies: domain={domain} path={path} {name}={morsel.value}")

if False:
    # only dump cookiejar
    cookie_jar.load(cookie_jar_path)
    dump_cookie_jar(cookie_jar)
    sys.exit()

async def main():

    session_kwargs = dict()
    # not used?
    session_kwargs["cookie_jar"] = cookie_jar
    session_kwargs["headers"] = dict(session_header="1")

    get_kwargs = dict()
    get_kwargs["headers"] = dict(request_header="1")
    # TypeError: ClientSession._request() got an unexpected keyword argument 'cookie_jar'
    #get_kwargs["cookie_jar"] = cookie_jar

    if os.path.exists(cookie_jar_path):
        print(f"loading cookies from {cookie_jar_path}")
        cookie_jar.load(cookie_jar_path)

    async with aiohttp.ClientSession(**session_kwargs) as aiohttp_session:

        # debug cookies
        # FIXME aiohttp does not send cookies?!

        print(f"aiohttp_session.cookie_jar._cookies:")
        dump_cookie_jar(aiohttp_session.cookie_jar)

        #print(f"aiohttp_session.cookie_jar._cookies httpbin.dev /cookies:\n" + str(aiohttp_session.cookie_jar._cookies[("httpbin.dev", "/cookies")]))

        url_base = "http://httpbin.dev"
        #url_base = "https://httpbin.dev"

        # debug: set cookies
        #url = f"{url_base}/cookies/set?foo=bar"
        url = f"{url_base}/cookies/set?foo=bar&a=1&b=2"
        response = await aiohttp_session.get(url, **get_kwargs)

        # Cookie headers are not here
        #print(f"response.request_info.headers: {response.request_info.headers}")

        response_text = (await response.content.read()).decode("utf8")
        #print(f"response_text: {response_text}")

        # Set-Cookie headers are not here
        #print(f"response.headers: {response.headers}")

        print(f"aiohttp_session.cookie_jar._cookies:")
        dump_cookie_jar(aiohttp_session.cookie_jar)

        #print(f"aiohttp_session.cookie_jar._cookies httpbin.dev /cookies:\n" + str(aiohttp_session.cookie_jar._cookies[("httpbin.dev", "/cookies")]))


        # NOTE cookies are not returned by /headers
        url = f"{url_base}/headers"
        response = await aiohttp_session.get(url, **get_kwargs)

        print(f"response.request_info.headers.Cookie: {response.request_info.headers.get('Cookie')}")
        #print(f"response.request_info dir: {dir(response.request_info)}")

        response_status = response.status
        # TODO debug request headers
        #print(f"response dir: {dir(response)}")
        #print(f"response_status: {response_status}")

        # Set-Cookie headers are not here
        #print(f"headers: {response.headers}")

        response_content = response.content
        response_text = (await response_content.read()).decode("utf8")
        print(f"sent headers: {response_text}")



        # debug: get cookies
        url = f"{url_base}/cookies"
        response = await aiohttp_session.get(url, **get_kwargs)

        print(f"response.request_info.headers.Cookie: {response.request_info.headers.get('Cookie')}")
        #print(f"response.request_info dir: {dir(response.request_info)}")

        response_status = response.status
        # TODO debug request headers
        #print(f"response dir: {dir(response)}")
        #print(f"response_status: {response_status}")

        # Set-Cookie headers are not here
        #print(f"headers: {response.headers}")

        response_content = response.content
        response_text = (await response_content.read()).decode("utf8")
        print(f"sent cookies: {response_text}")

        print(f"aiohttp_session.cookie_jar._cookies:")
        dump_cookie_jar(aiohttp_session.cookie_jar)

        #print(f"aiohttp_session.cookie_jar._cookies httpbin.dev /cookies:\n" + str(aiohttp_session.cookie_jar._cookies[("httpbin.dev", "/cookies")]))

        print(f"saving cookies to {cookie_jar_path}")
        aiohttp_session.cookie_jar.save(cookie_jar_path)

asyncio.get_event_loop().run_until_complete(main())

Related component

Client

Additional context

stackoverflow:

How to use cookies exported from chrome in python aiohttp?

I'm trying to visit some website using aiohttp/python. Currently I can export the site's cookies with extensions in Google Chrome, formatted like

.domain.name TRUE / FALSE 1547016401 cookies values

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant