support selecting a domain for '--cookies-from-browser'

for example 'gallery-dl --cookies-from-browser firefox/twitter.com'
This commit is contained in:
Mike Fährmann
2023-05-05 21:32:18 +02:00
parent 3ca5dac8b6
commit a14b63d941
5 changed files with 61 additions and 27 deletions

View File

@@ -446,12 +446,13 @@ Description
* The optional second entry is a profile name or an absolute path to a profile directory * The optional second entry is a profile name or an absolute path to a profile directory
* The optional third entry is the keyring to retrieve passwords for decrypting cookies from * The optional third entry is the keyring to retrieve passwords for decrypting cookies from
* The optional fourth entry is a (Firefox) container name (``"none"`` for only cookies with no container) * The optional fourth entry is a (Firefox) container name (``"none"`` for only cookies with no container)
* The optional fifth entry is the domain to extract cookies for. Prefix it with a dot ``.`` to include cookies for subdomains. Has no effect when also specifying a container.
.. code:: json .. code:: json
["firefox"] ["firefox"]
["firefox", null, null, "Personal"] ["firefox", null, null, "Personal"]
["chromium", "Private", "kwallet"] ["chromium", "Private", "kwallet", null, ".twitter.com"]
extractor.*.cookies-update extractor.*.cookies-update

View File

@@ -19,11 +19,12 @@
--clear-cache MODULE Delete cached login sessions, cookies, etc. for --clear-cache MODULE Delete cached login sessions, cookies, etc. for
MODULE (ALL to delete everything) MODULE (ALL to delete everything)
--cookies FILE File to load additional cookies from --cookies FILE File to load additional cookies from
--cookies-from-browser BROWSER[+KEYRING][:PROFILE][::CONTAINER] --cookies-from-browser BROWSER[/DOMAIN][+KEYRING][:PROFILE][::CONTAINER]
Name of the browser to load cookies from, with Name of the browser to load cookies from, with
optional keyring name prefixed with '+', profile optional domain prefixed with '/', keyring name
prefixed with ':', and container prefixed with prefixed with '+', profile prefixed with ':',
'::' ('none' for no container) and container prefixed with '::' ('none' for no
container)
## Output Options: ## Output Options:
-q, --quiet Activate quiet mode -q, --quiet Activate quiet mode

View File

@@ -70,12 +70,14 @@ def main():
if args.cookies_from_browser: if args.cookies_from_browser:
browser, _, profile = args.cookies_from_browser.partition(":") browser, _, profile = args.cookies_from_browser.partition(":")
browser, _, keyring = browser.partition("+") browser, _, keyring = browser.partition("+")
browser, _, domain = browser.partition("/")
if profile.startswith(":"): if profile.startswith(":"):
container = profile[1:] container = profile[1:]
profile = None profile = None
else: else:
profile, _, container = profile.partition("::") profile, _, container = profile.partition("::")
config.set((), "cookies", (browser, profile, keyring, container)) config.set((), "cookies", (
browser, profile, keyring, container, domain))
if args.options_pp: if args.options_pp:
config.set((), "postprocessor-options", args.options_pp) config.set((), "postprocessor-options", args.options_pp)
for opts in args.options: for opts in args.options:

View File

@@ -34,19 +34,19 @@ logger = logging.getLogger("cookies")
def load_cookies(cookiejar, browser_specification): def load_cookies(cookiejar, browser_specification):
browser_name, profile, keyring, container = \ browser_name, profile, keyring, container, domain = \
_parse_browser_specification(*browser_specification) _parse_browser_specification(*browser_specification)
if browser_name == "firefox": if browser_name == "firefox":
load_cookies_firefox(cookiejar, profile, container) load_cookies_firefox(cookiejar, profile, container, domain)
elif browser_name == "safari": elif browser_name == "safari":
load_cookies_safari(cookiejar, profile) load_cookies_safari(cookiejar, profile, domain)
elif browser_name in SUPPORTED_BROWSERS_CHROMIUM: elif browser_name in SUPPORTED_BROWSERS_CHROMIUM:
load_cookies_chrome(cookiejar, browser_name, profile, keyring) load_cookies_chrome(cookiejar, browser_name, profile, keyring, domain)
else: else:
raise ValueError("unknown browser '{}'".format(browser_name)) raise ValueError("unknown browser '{}'".format(browser_name))
def load_cookies_firefox(cookiejar, profile=None, container=None): def load_cookies_firefox(cookiejar, profile=None, container=None, domain=None):
path, container_id = _firefox_cookies_database(profile, container) path, container_id = _firefox_cookies_database(profile, container)
with DatabaseCopy(path) as db: with DatabaseCopy(path) as db:
@@ -60,6 +60,13 @@ def load_cookies_firefox(cookiejar, profile=None, container=None):
sql += " WHERE originAttributes LIKE ? OR originAttributes LIKE ?" sql += " WHERE originAttributes LIKE ? OR originAttributes LIKE ?"
uid = "%userContextId={}".format(container_id) uid = "%userContextId={}".format(container_id)
parameters = (uid, uid + "&%") parameters = (uid, uid + "&%")
elif domain:
if domain[0] == ".":
sql += " WHERE host == ? OR host LIKE ?"
parameters = (domain[1:], "%" + domain)
else:
sql += " WHERE host == ? OR host == ?"
parameters = (domain, "." + domain)
set_cookie = cookiejar.set_cookie set_cookie = cookiejar.set_cookie
for name, value, domain, path, secure, expires in db.execute( for name, value, domain, path, secure, expires in db.execute(
@@ -69,9 +76,10 @@ def load_cookies_firefox(cookiejar, profile=None, container=None):
domain, bool(domain), domain.startswith("."), domain, bool(domain), domain.startswith("."),
path, bool(path), secure, expires, False, None, None, {}, path, bool(path), secure, expires, False, None, None, {},
)) ))
logger.info("Extracted %s cookies from Firefox", len(cookiejar))
def load_cookies_safari(cookiejar, profile=None): def load_cookies_safari(cookiejar, profile=None, domain=None):
"""Ref.: https://github.com/libyal/dtformats/blob """Ref.: https://github.com/libyal/dtformats/blob
/main/documentation/Safari%20Cookies.asciidoc /main/documentation/Safari%20Cookies.asciidoc
- This data appears to be out of date - This data appears to be out of date
@@ -87,7 +95,8 @@ def load_cookies_safari(cookiejar, profile=None):
_safari_parse_cookies_page(p.read_bytes(page_size), cookiejar) _safari_parse_cookies_page(p.read_bytes(page_size), cookiejar)
def load_cookies_chrome(cookiejar, browser_name, profile, keyring): def load_cookies_chrome(cookiejar, browser_name, profile=None,
keyring=None, domain=None):
config = _get_chromium_based_browser_settings(browser_name) config = _get_chromium_based_browser_settings(browser_name)
path = _chrome_cookies_database(profile, config) path = _chrome_cookies_database(profile, config)
logger.debug("Extracting cookies from %s", path) logger.debug("Extracting cookies from %s", path)
@@ -95,19 +104,31 @@ def load_cookies_chrome(cookiejar, browser_name, profile, keyring):
with DatabaseCopy(path) as db: with DatabaseCopy(path) as db:
db.text_factory = bytes db.text_factory = bytes
decryptor = get_cookie_decryptor( decryptor = get_cookie_decryptor(
config["directory"], config["keyring"], keyring=keyring) config["directory"], config["keyring"], keyring)
if domain:
if domain[0] == ".":
condition = " WHERE host_key == ? OR host_key LIKE ?"
parameters = (domain[1:], "%" + domain)
else:
condition = " WHERE host_key == ? OR host_key == ?"
parameters = (domain, "." + domain)
else:
condition = ""
parameters = ()
try: try:
rows = db.execute( rows = db.execute(
"SELECT host_key, name, value, encrypted_value, path, " "SELECT host_key, name, value, encrypted_value, path, "
"expires_utc, is_secure FROM cookies") "expires_utc, is_secure FROM cookies" + condition, parameters)
except sqlite3.OperationalError: except sqlite3.OperationalError:
rows = db.execute( rows = db.execute(
"SELECT host_key, name, value, encrypted_value, path, " "SELECT host_key, name, value, encrypted_value, path, "
"expires_utc, secure FROM cookies") "expires_utc, secure FROM cookies" + condition, parameters)
set_cookie = cookiejar.set_cookie set_cookie = cookiejar.set_cookie
failed_cookies = unencrypted_cookies = 0 failed_cookies = 0
unencrypted_cookies = 0
for domain, name, value, enc_value, path, expires, secure in rows: for domain, name, value, enc_value, path, expires, secure in rows:
@@ -136,8 +157,8 @@ def load_cookies_chrome(cookiejar, browser_name, profile, keyring):
failed_message = "" failed_message = ""
logger.info("Extracted %s cookies from %s%s", logger.info("Extracted %s cookies from %s%s",
len(cookiejar), browser_name, failed_message) len(cookiejar), browser_name.capitalize(), failed_message)
counts = decryptor.cookie_counts.copy() counts = decryptor.cookie_counts
counts["unencrypted"] = unencrypted_cookies counts["unencrypted"] = unencrypted_cookies
logger.debug("cookie version breakdown: %s", counts) logger.debug("cookie version breakdown: %s", counts)
@@ -224,7 +245,7 @@ def _safari_parse_cookies_header(data):
return page_sizes, p.cursor return page_sizes, p.cursor
def _safari_parse_cookies_page(data, jar): def _safari_parse_cookies_page(data, cookiejar, domain=None):
p = DataParser(data) p = DataParser(data)
p.expect_bytes(b"\x00\x00\x01\x00", "page signature") p.expect_bytes(b"\x00\x00\x01\x00", "page signature")
number_of_cookies = p.read_uint() number_of_cookies = p.read_uint()
@@ -238,12 +259,12 @@ def _safari_parse_cookies_page(data, jar):
for i, record_offset in enumerate(record_offsets): for i, record_offset in enumerate(record_offsets):
p.skip_to(record_offset, "space between records") p.skip_to(record_offset, "space between records")
record_length = _safari_parse_cookies_record( record_length = _safari_parse_cookies_record(
data[record_offset:], jar) data[record_offset:], cookiejar, domain)
p.read_bytes(record_length) p.read_bytes(record_length)
p.skip_to_end("space in between pages") p.skip_to_end("space in between pages")
def _safari_parse_cookies_record(data, cookiejar): def _safari_parse_cookies_record(data, cookiejar, host=None):
p = DataParser(data) p = DataParser(data)
record_size = p.read_uint() record_size = p.read_uint()
p.skip(4, "unknown record field 1") p.skip(4, "unknown record field 1")
@@ -262,6 +283,14 @@ def _safari_parse_cookies_record(data, cookiejar):
p.skip_to(domain_offset) p.skip_to(domain_offset)
domain = p.read_cstring() domain = p.read_cstring()
if host:
if host[0] == ".":
if host[1:] != domain and not domain.endswith(host):
return record_size
else:
if host != domain and ("." + host) != domain:
return record_size
p.skip_to(name_offset) p.skip_to(name_offset)
name = p.read_cstring() name = p.read_cstring()
@@ -978,7 +1007,7 @@ def _is_path(value):
def _parse_browser_specification( def _parse_browser_specification(
browser, profile=None, keyring=None, container=None): browser, profile=None, keyring=None, container=None, domain=None):
browser = browser.lower() browser = browser.lower()
if browser not in SUPPORTED_BROWSERS: if browser not in SUPPORTED_BROWSERS:
raise ValueError("unsupported browser '{}'".format(browser)) raise ValueError("unsupported browser '{}'".format(browser))
@@ -986,4 +1015,4 @@ def _parse_browser_specification(
raise ValueError("unsupported keyring '{}'".format(keyring)) raise ValueError("unsupported keyring '{}'".format(keyring))
if profile and _is_path(profile): if profile and _is_path(profile):
profile = os.path.expanduser(profile) profile = os.path.expanduser(profile)
return browser, profile, keyring, container return browser, profile, keyring, container, domain

View File

@@ -156,9 +156,10 @@ def build_parser():
general.add_argument( general.add_argument(
"--cookies-from-browser", "--cookies-from-browser",
dest="cookies_from_browser", dest="cookies_from_browser",
metavar="BROWSER[+KEYRING][:PROFILE][::CONTAINER]", metavar="BROWSER[/DOMAIN][+KEYRING][:PROFILE][::CONTAINER]",
help=("Name of the browser to load cookies from, " help=("Name of the browser to load cookies from, with optional "
"with optional keyring name prefixed with '+', " "domain prefixed with '/', "
"keyring name prefixed with '+', "
"profile prefixed with ':', and " "profile prefixed with ':', and "
"container prefixed with '::' ('none' for no container)"), "container prefixed with '::' ('none' for no container)"),
) )