mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-21 20:46:36 -05:00
[cookies] Support firefox container in --cookies-from-browser
(#4753)
Authored by: bashonly
This commit is contained in:
parent
459262ac97
commit
9bd13fe5bb
5 changed files with 54 additions and 21 deletions
11
README.md
11
README.md
|
@ -706,13 +706,14 @@ ## Filesystem Options:
|
||||||
and dump cookie jar in
|
and dump cookie jar in
|
||||||
--no-cookies Do not read/dump cookies from/to file
|
--no-cookies Do not read/dump cookies from/to file
|
||||||
(default)
|
(default)
|
||||||
--cookies-from-browser BROWSER[+KEYRING][:PROFILE]
|
--cookies-from-browser BROWSER[+KEYRING][:PROFILE[:CONTAINER]]
|
||||||
The name of the browser and (optionally) the
|
The name of the browser and (optionally) the
|
||||||
name/path of the profile to load cookies
|
name/path of the profile to load cookies
|
||||||
from, separated by a ":". Currently
|
from (and container name if Firefox)
|
||||||
supported browsers are: brave, chrome,
|
separated by a ":". Currently supported
|
||||||
chromium, edge, firefox, opera, safari,
|
browsers are: brave, chrome, chromium, edge,
|
||||||
vivaldi. By default, the most recently
|
firefox, opera, safari, vivaldi. By default,
|
||||||
|
the default container of the most recently
|
||||||
accessed profile is used. The keyring used
|
accessed profile is used. The keyring used
|
||||||
for decrypting Chromium cookies on Linux can
|
for decrypting Chromium cookies on Linux can
|
||||||
be (optionally) specified after the browser
|
be (optionally) specified after the browser
|
||||||
|
|
|
@ -304,8 +304,9 @@ class YoutubeDL:
|
||||||
should act on each input URL as opposed to for the entire queue
|
should act on each input URL as opposed to for the entire queue
|
||||||
cookiefile: File name or text stream from where cookies should be read and dumped to
|
cookiefile: File name or text stream from where cookies should be read and dumped to
|
||||||
cookiesfrombrowser: A tuple containing the name of the browser, the profile
|
cookiesfrombrowser: A tuple containing the name of the browser, the profile
|
||||||
name/path from where cookies are loaded, and the name of the
|
name/path from where cookies are loaded, the name of the keyring,
|
||||||
keyring, e.g. ('chrome', ) or ('vivaldi', 'default', 'BASICTEXT')
|
and the container name, e.g. ('chrome', ) or
|
||||||
|
('vivaldi', 'default', 'BASICTEXT') or ('firefox', 'default', None, 'Meta')
|
||||||
legacyserverconnect: Explicitly allow HTTPS connection to servers that do not
|
legacyserverconnect: Explicitly allow HTTPS connection to servers that do not
|
||||||
support RFC 5746 secure renegotiation
|
support RFC 5746 secure renegotiation
|
||||||
nocheckcertificate: Do not verify SSL certificates
|
nocheckcertificate: Do not verify SSL certificates
|
||||||
|
|
|
@ -346,6 +346,7 @@ def parse_chapters(name, value):
|
||||||
|
|
||||||
# Cookies from browser
|
# Cookies from browser
|
||||||
if opts.cookiesfrombrowser:
|
if opts.cookiesfrombrowser:
|
||||||
|
container = None
|
||||||
mobj = re.match(r'(?P<name>[^+:]+)(\s*\+\s*(?P<keyring>[^:]+))?(\s*:(?P<profile>.+))?', opts.cookiesfrombrowser)
|
mobj = re.match(r'(?P<name>[^+:]+)(\s*\+\s*(?P<keyring>[^:]+))?(\s*:(?P<profile>.+))?', opts.cookiesfrombrowser)
|
||||||
if mobj is None:
|
if mobj is None:
|
||||||
raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
|
raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
|
||||||
|
@ -354,12 +355,15 @@ def parse_chapters(name, value):
|
||||||
if browser_name not in SUPPORTED_BROWSERS:
|
if browser_name not in SUPPORTED_BROWSERS:
|
||||||
raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
|
raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
|
||||||
f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}')
|
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:
|
if keyring is not None:
|
||||||
keyring = keyring.upper()
|
keyring = keyring.upper()
|
||||||
if keyring not in SUPPORTED_KEYRINGS:
|
if keyring not in SUPPORTED_KEYRINGS:
|
||||||
raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
|
raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
|
||||||
f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
|
f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
|
||||||
opts.cookiesfrombrowser = (browser_name, profile, keyring)
|
opts.cookiesfrombrowser = (browser_name, profile, keyring, container)
|
||||||
|
|
||||||
# MetadataParser
|
# MetadataParser
|
||||||
def metadataparser_actions(f):
|
def metadataparser_actions(f):
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import http.cookiejar
|
import http.cookiejar
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
sqlite3,
|
sqlite3,
|
||||||
)
|
)
|
||||||
from .minicurses import MultilinePrinter, QuietMultilinePrinter
|
from .minicurses import MultilinePrinter, QuietMultilinePrinter
|
||||||
from .utils import Popen, YoutubeDLCookieJar, error_to_str, expand_path
|
from .utils import Popen, YoutubeDLCookieJar, error_to_str, expand_path, try_call
|
||||||
|
|
||||||
CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi'}
|
CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi'}
|
||||||
SUPPORTED_BROWSERS = CHROMIUM_BASED_BROWSERS | {'firefox', 'safari'}
|
SUPPORTED_BROWSERS = CHROMIUM_BASED_BROWSERS | {'firefox', 'safari'}
|
||||||
|
@ -85,8 +86,9 @@ def _create_progress_bar(logger):
|
||||||
def load_cookies(cookie_file, browser_specification, ydl):
|
def load_cookies(cookie_file, browser_specification, ydl):
|
||||||
cookie_jars = []
|
cookie_jars = []
|
||||||
if browser_specification is not None:
|
if browser_specification is not None:
|
||||||
browser_name, profile, keyring = _parse_browser_specification(*browser_specification)
|
browser_name, profile, keyring, container = _parse_browser_specification(*browser_specification)
|
||||||
cookie_jars.append(extract_cookies_from_browser(browser_name, profile, YDLLogger(ydl), keyring=keyring))
|
cookie_jars.append(
|
||||||
|
extract_cookies_from_browser(browser_name, profile, YDLLogger(ydl), keyring=keyring, container=container))
|
||||||
|
|
||||||
if cookie_file is not None:
|
if cookie_file is not None:
|
||||||
is_filename = YoutubeDLCookieJar.is_path(cookie_file)
|
is_filename = YoutubeDLCookieJar.is_path(cookie_file)
|
||||||
|
@ -101,9 +103,9 @@ def load_cookies(cookie_file, browser_specification, ydl):
|
||||||
return _merge_cookie_jars(cookie_jars)
|
return _merge_cookie_jars(cookie_jars)
|
||||||
|
|
||||||
|
|
||||||
def extract_cookies_from_browser(browser_name, profile=None, logger=YDLLogger(), *, keyring=None):
|
def extract_cookies_from_browser(browser_name, profile=None, logger=YDLLogger(), *, keyring=None, container=None):
|
||||||
if browser_name == 'firefox':
|
if browser_name == 'firefox':
|
||||||
return _extract_firefox_cookies(profile, logger)
|
return _extract_firefox_cookies(profile, container, logger)
|
||||||
elif browser_name == 'safari':
|
elif browser_name == 'safari':
|
||||||
return _extract_safari_cookies(profile, logger)
|
return _extract_safari_cookies(profile, logger)
|
||||||
elif browser_name in CHROMIUM_BASED_BROWSERS:
|
elif browser_name in CHROMIUM_BASED_BROWSERS:
|
||||||
|
@ -112,7 +114,7 @@ def extract_cookies_from_browser(browser_name, profile=None, logger=YDLLogger(),
|
||||||
raise ValueError(f'unknown browser: {browser_name}')
|
raise ValueError(f'unknown browser: {browser_name}')
|
||||||
|
|
||||||
|
|
||||||
def _extract_firefox_cookies(profile, logger):
|
def _extract_firefox_cookies(profile, container, logger):
|
||||||
logger.info('Extracting cookies from firefox')
|
logger.info('Extracting cookies from firefox')
|
||||||
if not sqlite3:
|
if not sqlite3:
|
||||||
logger.warning('Cannot extract cookies from firefox without sqlite3 support. '
|
logger.warning('Cannot extract cookies from firefox without sqlite3 support. '
|
||||||
|
@ -126,6 +128,20 @@ def _extract_firefox_cookies(profile, logger):
|
||||||
else:
|
else:
|
||||||
search_root = os.path.join(_firefox_browser_dir(), profile)
|
search_root = os.path.join(_firefox_browser_dir(), profile)
|
||||||
|
|
||||||
|
container_id = None
|
||||||
|
if container is not None:
|
||||||
|
containers_path = os.path.join(search_root, '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:
|
||||||
|
identities = json.load(containers).get('identities', [])
|
||||||
|
container_id = next((context.get('userContextId') for context in identities if container in (
|
||||||
|
context.get('name'),
|
||||||
|
try_call(lambda: re.fullmatch(r'userContext([^\.]+)\.label', context['l10nID']).group())
|
||||||
|
)), None)
|
||||||
|
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)
|
cookie_database_path = _find_most_recently_used_file(search_root, 'cookies.sqlite', logger)
|
||||||
if cookie_database_path is None:
|
if cookie_database_path is None:
|
||||||
raise FileNotFoundError(f'could not find firefox cookies database in {search_root}')
|
raise FileNotFoundError(f'could not find firefox cookies database in {search_root}')
|
||||||
|
@ -135,7 +151,18 @@ def _extract_firefox_cookies(profile, logger):
|
||||||
cursor = None
|
cursor = None
|
||||||
try:
|
try:
|
||||||
cursor = _open_database_copy(cookie_database_path, tmpdir)
|
cursor = _open_database_copy(cookie_database_path, tmpdir)
|
||||||
cursor.execute('SELECT host, name, value, path, expiry, isSecure FROM moz_cookies')
|
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')
|
||||||
|
cursor.execute('SELECT host, name, value, path, expiry, isSecure FROM moz_cookies')
|
||||||
jar = YoutubeDLCookieJar()
|
jar = YoutubeDLCookieJar()
|
||||||
with _create_progress_bar(logger) as progress_bar:
|
with _create_progress_bar(logger) as progress_bar:
|
||||||
table = cursor.fetchall()
|
table = cursor.fetchall()
|
||||||
|
@ -948,11 +975,11 @@ def _is_path(value):
|
||||||
return os.path.sep in value
|
return os.path.sep in value
|
||||||
|
|
||||||
|
|
||||||
def _parse_browser_specification(browser_name, profile=None, keyring=None):
|
def _parse_browser_specification(browser_name, profile=None, keyring=None, container=None):
|
||||||
if browser_name not in SUPPORTED_BROWSERS:
|
if browser_name not in SUPPORTED_BROWSERS:
|
||||||
raise ValueError(f'unsupported browser: "{browser_name}"')
|
raise ValueError(f'unsupported browser: "{browser_name}"')
|
||||||
if keyring not in (None, *SUPPORTED_KEYRINGS):
|
if keyring not in (None, *SUPPORTED_KEYRINGS):
|
||||||
raise ValueError(f'unsupported keyring: "{keyring}"')
|
raise ValueError(f'unsupported keyring: "{keyring}"')
|
||||||
if profile is not None and _is_path(profile):
|
if profile is not None and _is_path(profile):
|
||||||
profile = os.path.expanduser(profile)
|
profile = os.path.expanduser(profile)
|
||||||
return browser_name, profile, keyring
|
return browser_name, profile, keyring, container
|
||||||
|
|
|
@ -1400,12 +1400,12 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
|
||||||
help='Do not read/dump cookies from/to file (default)')
|
help='Do not read/dump cookies from/to file (default)')
|
||||||
filesystem.add_option(
|
filesystem.add_option(
|
||||||
'--cookies-from-browser',
|
'--cookies-from-browser',
|
||||||
dest='cookiesfrombrowser', metavar='BROWSER[+KEYRING][:PROFILE]',
|
dest='cookiesfrombrowser', metavar='BROWSER[+KEYRING][:PROFILE[:CONTAINER]]',
|
||||||
help=(
|
help=(
|
||||||
'The name of the browser and (optionally) the name/path of '
|
'The name of the browser and (optionally) the name/path of the profile to load cookies from '
|
||||||
'the profile to load cookies from, separated by a ":". '
|
'(and container name if Firefox) separated by a ":". '
|
||||||
f'Currently supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}. '
|
f'Currently supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}. '
|
||||||
'By default, the most recently accessed profile is used. '
|
'By default, the default container of the most recently accessed profile is used. '
|
||||||
'The keyring used for decrypting Chromium cookies on Linux can be '
|
'The keyring used for decrypting Chromium cookies on Linux can be '
|
||||||
'(optionally) specified after the browser name separated by a "+". '
|
'(optionally) specified after the browser name separated by a "+". '
|
||||||
f'Currently supported keyrings are: {", ".join(map(str.lower, sorted(SUPPORTED_KEYRINGS)))}'))
|
f'Currently supported keyrings are: {", ".join(map(str.lower, sorted(SUPPORTED_KEYRINGS)))}'))
|
||||||
|
|
Loading…
Reference in a new issue