From 825d3ce386e66ac0c73e41e352d84053f9f0e624 Mon Sep 17 00:00:00 2001 From: bashonly <88596187+bashonly@users.noreply.github.com> Date: Thu, 1 Sep 2022 09:52:59 +0000 Subject: [PATCH] [cookies] Improve container support (#4806) Closes #4800 Authored by: bashonly, pukkandan, coletdjnz --- README.md | 27 ++++++++++++++------------- yt_dlp/__init__.py | 14 ++++++++------ yt_dlp/cookies.py | 28 ++++++++++++++-------------- yt_dlp/options.py | 13 +++++++------ 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c101048d5..896508965 100644 --- a/README.md +++ b/README.md @@ -706,19 +706,20 @@ ## Filesystem Options: and dump cookie jar in --no-cookies Do not read/dump cookies from/to file (default) - --cookies-from-browser BROWSER[+KEYRING][:PROFILE[:CONTAINER]] - The name of the browser and (optionally) the - name/path of the profile to load cookies - from (and container name if Firefox) - separated by a ":". Currently supported - browsers are: brave, chrome, chromium, edge, - firefox, opera, safari, vivaldi. By default, - the default container of the most recently - accessed profile is used. The keyring used - for decrypting Chromium cookies on Linux can - be (optionally) specified after the browser - name separated by a "+". Currently supported - keyrings are: basictext, gnomekeyring, kwallet + --cookies-from-browser BROWSER[+KEYRING][:PROFILE][::CONTAINER] + The name of the browser to load cookies + from. Currently supported browsers are: + brave, chrome, chromium, edge, firefox, + opera, safari, vivaldi. Optionally, the + KEYRING used for decrypting Chromium cookies + on Linux, the name/path of the PROFILE to + load cookies from, and the CONTAINER name + (if Firefox) ("none" for no container) can + be given with their respective seperators. + By default, all containers of the most + recently accessed profile are used. + Currently supported keyrings are: basictext, + gnomekeyring, kwallet --no-cookies-from-browser Do not load cookies from browser (default) --cache-dir DIR Location in the filesystem where youtube-dl can store some downloaded information (such diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index f4a2086ce..552f29bd9 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -347,23 +347,25 @@ def parse_chapters(name, value): # Cookies from browser if opts.cookiesfrombrowser: container = None - mobj = re.match(r'(?P[^+:]+)(\s*\+\s*(?P[^:]+))?(\s*:(?P.+))?', opts.cookiesfrombrowser) + mobj = re.fullmatch(r'''(?x) + (?P[^+:]+) + (?:\s*\+\s*(?P[^:]+))? + (?:\s*:\s*(?P.+?))? + (?:\s*::\s*(?P.+))? + ''', opts.cookiesfrombrowser) if mobj is None: raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}') - browser_name, keyring, profile = mobj.group('name', 'keyring', 'profile') + browser_name, keyring, profile, container = mobj.group('name', 'keyring', 'profile', 'container') browser_name = browser_name.lower() if browser_name not in SUPPORTED_BROWSERS: raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". ' f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}') - elif profile and browser_name == 'firefox': - if ':' in profile and not os.path.exists(profile): - profile, container = profile.split(':', 1) if keyring is not None: keyring = keyring.upper() if keyring not in SUPPORTED_KEYRINGS: raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". ' f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}') - opts.cookiesfrombrowser = (browser_name, profile, keyring, container) + opts.cookiesfrombrowser = (browser_name, profile or None, keyring, container or None) # MetadataParser def metadataparser_actions(f): diff --git a/yt_dlp/cookies.py b/yt_dlp/cookies.py index c5fb5ab68..9100f46ac 100644 --- a/yt_dlp/cookies.py +++ b/yt_dlp/cookies.py @@ -128,9 +128,14 @@ def _extract_firefox_cookies(profile, container, logger): else: search_root = os.path.join(_firefox_browser_dir(), profile) + cookie_database_path = _find_most_recently_used_file(search_root, 'cookies.sqlite', logger) + if cookie_database_path is None: + raise FileNotFoundError(f'could not find firefox cookies database in {search_root}') + logger.debug(f'Extracting cookies from: "{cookie_database_path}"') + container_id = None - if container is not None: - containers_path = os.path.join(search_root, 'containers.json') + if container not in (None, 'none'): + containers_path = os.path.join(os.path.dirname(cookie_database_path), 'containers.json') if not os.path.isfile(containers_path) or not os.access(containers_path, os.R_OK): raise FileNotFoundError(f'could not read containers.json in {search_root}') with open(containers_path, 'r') as containers: @@ -142,26 +147,21 @@ def _extract_firefox_cookies(profile, container, logger): if not isinstance(container_id, int): raise ValueError(f'could not find firefox container "{container}" in containers.json') - cookie_database_path = _find_most_recently_used_file(search_root, 'cookies.sqlite', logger) - if cookie_database_path is None: - raise FileNotFoundError(f'could not find firefox cookies database in {search_root}') - logger.debug(f'Extracting cookies from: "{cookie_database_path}"') - with tempfile.TemporaryDirectory(prefix='yt_dlp') as tmpdir: cursor = None try: cursor = _open_database_copy(cookie_database_path, tmpdir) - origin_attributes = '' if isinstance(container_id, int): - origin_attributes = f'^userContextId={container_id}' logger.debug( f'Only loading cookies from firefox container "{container}", ID {container_id}') - try: cursor.execute( - 'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE originAttributes=?', - (origin_attributes, )) - except sqlite3.OperationalError: - logger.debug('Database exception, loading all cookies') + 'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE originAttributes LIKE ? OR originAttributes LIKE ?', + (f'%userContextId={container_id}', f'%userContextId={container_id}&%')) + elif container == 'none': + logger.debug('Only loading cookies not belonging to any container') + cursor.execute( + 'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE NOT INSTR(originAttributes,"userContextId=")') + else: cursor.execute('SELECT host, name, value, path, expiry, isSecure FROM moz_cookies') jar = YoutubeDLCookieJar() with _create_progress_bar(logger) as progress_bar: diff --git a/yt_dlp/options.py b/yt_dlp/options.py index e50ecc579..da6b1d25b 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -1400,14 +1400,15 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs): help='Do not read/dump cookies from/to file (default)') filesystem.add_option( '--cookies-from-browser', - dest='cookiesfrombrowser', metavar='BROWSER[+KEYRING][:PROFILE[:CONTAINER]]', + dest='cookiesfrombrowser', metavar='BROWSER[+KEYRING][:PROFILE][::CONTAINER]', help=( - 'The name of the browser and (optionally) the name/path of the profile to load cookies from ' - '(and container name if Firefox) separated by a ":". ' + 'The name of the browser to load cookies from. ' f'Currently supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}. ' - 'By default, the default container of the most recently accessed profile is used. ' - 'The keyring used for decrypting Chromium cookies on Linux can be ' - '(optionally) specified after the browser name separated by a "+". ' + 'Optionally, the KEYRING used for decrypting Chromium cookies on Linux, ' + 'the name/path of the PROFILE to load cookies from, ' + 'and the CONTAINER name (if Firefox) ("none" for no container) ' + 'can be given with their respective seperators. ' + 'By default, all containers of the most recently accessed profile are used. ' f'Currently supported keyrings are: {", ".join(map(str.lower, sorted(SUPPORTED_KEYRINGS)))}')) filesystem.add_option( '--no-cookies-from-browser',