import base64
import collections
import contextlib
+import datetime as dt
import glob
import http.cookiejar
import http.cookies
import tempfile
import time
import urllib.request
-from datetime import datetime, timedelta, timezone
from enum import Enum, auto
from hashlib import pbkdf2_hmac
from .utils._utils import _YDLLogger
from .utils.networking import normalize_url
-CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi'}
+CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi', 'whale'}
SUPPORTED_BROWSERS = CHROMIUM_BASED_BROWSERS | {'firefox', 'safari'}
logger.info('Extracting cookies from firefox')
if not sqlite3:
logger.warning('Cannot extract cookies from firefox without sqlite3 support. '
- 'Please use a python interpreter compiled with sqlite3 support')
+ 'Please use a Python interpreter compiled with sqlite3 support')
return YoutubeDLCookieJar()
if profile is None:
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())
+ 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')
yield os.path.expanduser('~/Library/Application Support/Firefox/Profiles')
else:
- yield from map(os.path.expanduser, ('~/.mozilla/firefox', '~/snap/firefox/common/.mozilla/firefox'))
+ yield from map(os.path.expanduser, (
+ '~/.mozilla/firefox',
+ '~/snap/firefox/common/.mozilla/firefox',
+ '~/.var/app/org.mozilla.firefox/.mozilla/firefox',
+ ))
def _firefox_cookie_dbs(roots):
'edge': os.path.join(appdata_local, R'Microsoft\Edge\User Data'),
'opera': os.path.join(appdata_roaming, R'Opera Software\Opera Stable'),
'vivaldi': os.path.join(appdata_local, R'Vivaldi\User Data'),
+ 'whale': os.path.join(appdata_local, R'Naver\Naver Whale\User Data'),
}[browser_name]
elif sys.platform == 'darwin':
'edge': os.path.join(appdata, 'Microsoft Edge'),
'opera': os.path.join(appdata, 'com.operasoftware.Opera'),
'vivaldi': os.path.join(appdata, 'Vivaldi'),
+ 'whale': os.path.join(appdata, 'Naver/Whale'),
}[browser_name]
else:
'edge': os.path.join(config, 'microsoft-edge'),
'opera': os.path.join(config, 'opera'),
'vivaldi': os.path.join(config, 'vivaldi'),
+ 'whale': os.path.join(config, 'naver-whale'),
}[browser_name]
# Linux keyring names can be determined by snooping on dbus while opening the browser in KDE:
'edge': 'Microsoft Edge' if sys.platform == 'darwin' else 'Chromium',
'opera': 'Opera' if sys.platform == 'darwin' else 'Chromium',
'vivaldi': 'Vivaldi' if sys.platform == 'darwin' else 'Chrome',
+ 'whale': 'Whale',
}[browser_name]
browsers_without_profiles = {'opera'}
return {
'browser_dir': browser_dir,
'keyring_name': keyring_name,
- 'supports_profiles': browser_name not in browsers_without_profiles
+ 'supports_profiles': browser_name not in browsers_without_profiles,
}
if not sqlite3:
logger.warning(f'Cannot extract cookies from {browser_name} without sqlite3 support. '
- 'Please use a python interpreter compiled with sqlite3 support')
+ 'Please use a Python interpreter compiled with sqlite3 support')
return YoutubeDLCookieJar()
config = _get_chromium_based_browser_settings(browser_name)
if value is None:
return is_encrypted, None
+ # In chrome, session cookies have expires_utc set to 0
+ # In our cookie-store, cookies that do not expire should have expires set to None
+ if not expires_utc:
+ expires_utc = None
+
return is_encrypted, http.cookiejar.Cookie(
version=0, name=name, value=value, port=None, port_specified=False,
domain=host_key, domain_specified=bool(host_key), domain_initial_dot=host_key.startswith('.'),
def _mac_absolute_time_to_posix(timestamp):
- return int((datetime(2001, 1, 1, 0, 0, tzinfo=timezone.utc) + timedelta(seconds=timestamp)).timestamp())
+ return int((dt.datetime(2001, 1, 1, 0, 0, tzinfo=dt.timezone.utc) + dt.timedelta(seconds=timestamp)).timestamp())
def _parse_safari_cookies_header(data, logger):
xdg_current_desktop = env.get('XDG_CURRENT_DESKTOP', None)
desktop_session = env.get('DESKTOP_SESSION', None)
if xdg_current_desktop is not None:
- xdg_current_desktop = xdg_current_desktop.split(':')[0].strip()
-
- if xdg_current_desktop == 'Unity':
- if desktop_session is not None and 'gnome-fallback' in desktop_session:
+ for part in map(str.strip, xdg_current_desktop.split(':')):
+ if part == 'Unity':
+ if desktop_session is not None and 'gnome-fallback' in desktop_session:
+ return _LinuxDesktopEnvironment.GNOME
+ else:
+ return _LinuxDesktopEnvironment.UNITY
+ elif part == 'Deepin':
+ return _LinuxDesktopEnvironment.DEEPIN
+ elif part == 'GNOME':
return _LinuxDesktopEnvironment.GNOME
- else:
- return _LinuxDesktopEnvironment.UNITY
- elif xdg_current_desktop == 'Deepin':
- return _LinuxDesktopEnvironment.DEEPIN
- elif xdg_current_desktop == 'GNOME':
- return _LinuxDesktopEnvironment.GNOME
- elif xdg_current_desktop == 'X-Cinnamon':
- return _LinuxDesktopEnvironment.CINNAMON
- elif xdg_current_desktop == 'KDE':
- kde_version = env.get('KDE_SESSION_VERSION', None)
- if kde_version == '5':
- return _LinuxDesktopEnvironment.KDE5
- elif kde_version == '6':
- return _LinuxDesktopEnvironment.KDE6
- elif kde_version == '4':
- return _LinuxDesktopEnvironment.KDE4
- else:
- logger.info(f'unknown KDE version: "{kde_version}". Assuming KDE4')
- return _LinuxDesktopEnvironment.KDE4
- elif xdg_current_desktop == 'Pantheon':
- return _LinuxDesktopEnvironment.PANTHEON
- elif xdg_current_desktop == 'XFCE':
- return _LinuxDesktopEnvironment.XFCE
- elif xdg_current_desktop == 'UKUI':
- return _LinuxDesktopEnvironment.UKUI
- elif xdg_current_desktop == 'LXQt':
- return _LinuxDesktopEnvironment.LXQT
- else:
- logger.info(f'XDG_CURRENT_DESKTOP is set to an unknown value: "{xdg_current_desktop}"')
+ elif part == 'X-Cinnamon':
+ return _LinuxDesktopEnvironment.CINNAMON
+ elif part == 'KDE':
+ kde_version = env.get('KDE_SESSION_VERSION', None)
+ if kde_version == '5':
+ return _LinuxDesktopEnvironment.KDE5
+ elif kde_version == '6':
+ return _LinuxDesktopEnvironment.KDE6
+ elif kde_version == '4':
+ return _LinuxDesktopEnvironment.KDE4
+ else:
+ logger.info(f'unknown KDE version: "{kde_version}". Assuming KDE4')
+ return _LinuxDesktopEnvironment.KDE4
+ elif part == 'Pantheon':
+ return _LinuxDesktopEnvironment.PANTHEON
+ elif part == 'XFCE':
+ return _LinuxDesktopEnvironment.XFCE
+ elif part == 'UKUI':
+ return _LinuxDesktopEnvironment.UKUI
+ elif part == 'LXQt':
+ return _LinuxDesktopEnvironment.LXQT
+ logger.info(f'XDG_CURRENT_DESKTOP is set to an unknown value: "{xdg_current_desktop}"')
elif desktop_session is not None:
if desktop_session == 'deepin':
elif desktop_environment == _LinuxDesktopEnvironment.KDE6:
linux_keyring = _LinuxKeyring.KWALLET6
elif desktop_environment in (
- _LinuxDesktopEnvironment.KDE3, _LinuxDesktopEnvironment.LXQT, _LinuxDesktopEnvironment.OTHER
+ _LinuxDesktopEnvironment.KDE3, _LinuxDesktopEnvironment.LXQT, _LinuxDesktopEnvironment.OTHER,
):
linux_keyring = _LinuxKeyring.BASICTEXT
else:
'dbus-send', '--session', '--print-reply=literal',
f'--dest={service_name}',
wallet_path,
- 'org.kde.KWallet.networkWallet'
+ 'org.kde.KWallet.networkWallet',
], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
if returncode:
'kwallet-query',
'--read-password', f'{browser_keyring_name} Safe Storage',
'--folder', f'{browser_keyring_name} Keys',
- network_wallet
+ network_wallet,
], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
if returncode:
for item in col.get_all_items():
if item.get_label() == f'{browser_keyring_name} Safe Storage':
return item.get_secret()
- else:
- logger.error('failed to read from keyring')
- return b''
+ logger.error('failed to read from keyring')
+ return b''
def _get_linux_keyring_password(browser_keyring_name, keyring, logger):
None, # pvReserved: must be NULL
None, # pPromptStruct: information about prompts to display
0, # dwFlags
- ctypes.byref(blob_out) # pDataOut
+ ctypes.byref(blob_out), # pDataOut
)
if not ret:
logger.warning('failed to decrypt with DPAPI', only_once=True)
_LEGAL_VALUE_CHARS = _LEGAL_KEY_CHARS + re.escape('(),/<=>?@[]{}')
_RESERVED = {
- "expires",
- "path",
- "comment",
- "domain",
- "max-age",
- "secure",
- "httponly",
- "version",
- "samesite",
+ 'expires',
+ 'path',
+ 'comment',
+ 'domain',
+ 'max-age',
+ 'secure',
+ 'httponly',
+ 'version',
+ 'samesite',
}
- _FLAGS = {"secure", "httponly"}
+ _FLAGS = {'secure', 'httponly'}
# Added 'bad' group to catch the remaining value
- _COOKIE_PATTERN = re.compile(r"""
+ _COOKIE_PATTERN = re.compile(r'''
\s* # Optional whitespace at start of cookie
(?P<key> # Start of group 'key'
- [""" + _LEGAL_KEY_CHARS + r"""]+?# Any word of at least one letter
+ [''' + _LEGAL_KEY_CHARS + r''']+?# Any word of at least one letter
) # End of group 'key'
( # Optional group: there may not be a value.
\s*=\s* # Equal Sign
| # or
\w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr
| # or
- [""" + _LEGAL_VALUE_CHARS + r"""]* # Any word or empty string
+ [''' + _LEGAL_VALUE_CHARS + r''']* # Any word or empty string
) # End of group 'val'
| # or
(?P<bad>(?:\\;|[^;])*?) # 'bad' group fallback for invalid values
)? # End of optional value group
\s* # Any number of spaces.
(\s+|;|$) # Ending either at space, semicolon, or EOS.
- """, re.ASCII | re.VERBOSE)
+ ''', re.ASCII | re.VERBOSE)
def load(self, data):
# Workaround for https://github.com/yt-dlp/yt-dlp/issues/4776
# with no name, whereas http.cookiejar regards it as a
# cookie with no value.
name, value = '', name
- f.write('%s\n' % '\t'.join((
+ f.write('{}\n'.format('\t'.join((
cookie.domain,
self._true_or_false(cookie.domain.startswith('.')),
cookie.path,
self._true_or_false(cookie.secure),
str_or_none(cookie.expires, default=''),
- name, value
- )))
+ name, value,
+ ))))
def save(self, filename=None, ignore_discard=True, ignore_expires=True):
"""
return line
cookie_list = line.split('\t')
if len(cookie_list) != self._ENTRY_LEN:
- raise http.cookiejar.LoadError('invalid length %d' % len(cookie_list))
+ raise http.cookiejar.LoadError(f'invalid length {len(cookie_list)}')
cookie = self._CookieFileEntry(*cookie_list)
if cookie.expires_at and not cookie.expires_at.isdigit():
- raise http.cookiejar.LoadError('invalid expires at %s' % cookie.expires_at)
+ raise http.cookiejar.LoadError(f'invalid expires at {cookie.expires_at}')
return line
cf = io.StringIO()