mirror of
https://github.com/soimort/you-get.git
synced 2025-02-11 12:42:29 +03:00
Add Chrome/Chromium cookie file support.
This commit is contained in:
parent
c118ce7f22
commit
6e98325525
4
setup.py
4
setup.py
@ -41,5 +41,7 @@ setup(
|
|||||||
|
|
||||||
classifiers = proj_info['classifiers'],
|
classifiers = proj_info['classifiers'],
|
||||||
|
|
||||||
entry_points = {'console_scripts': proj_info['console_scripts']}
|
entry_points = {'console_scripts': proj_info['console_scripts']},
|
||||||
|
install_requires = proj_info['install_requires'],
|
||||||
|
setup_requires = proj_info['setup_requires']
|
||||||
)
|
)
|
||||||
|
@ -115,6 +115,7 @@ from .util import log, term
|
|||||||
from .util.git import get_version
|
from .util.git import get_version
|
||||||
from .util.strings import get_filename, unescape_html
|
from .util.strings import get_filename, unescape_html
|
||||||
from . import json_output as json_output_
|
from . import json_output as json_output_
|
||||||
|
from .cookies import load_cookies
|
||||||
|
|
||||||
dry_run = False
|
dry_run = False
|
||||||
json_output = False
|
json_output = False
|
||||||
@ -1250,32 +1251,7 @@ def script_main(script_name, download, download_playlist, **kwargs):
|
|||||||
dry_run = True
|
dry_run = True
|
||||||
info_only = False
|
info_only = False
|
||||||
elif o in ('-c', '--cookies'):
|
elif o in ('-c', '--cookies'):
|
||||||
try:
|
cookies = load_cookies(a)
|
||||||
cookies = cookiejar.MozillaCookieJar(a)
|
|
||||||
cookies.load()
|
|
||||||
except:
|
|
||||||
import sqlite3
|
|
||||||
cookies = cookiejar.MozillaCookieJar()
|
|
||||||
con = sqlite3.connect(a)
|
|
||||||
cur = con.cursor()
|
|
||||||
try:
|
|
||||||
cur.execute("SELECT host, path, isSecure, expiry, name, value FROM moz_cookies")
|
|
||||||
for item in cur.fetchall():
|
|
||||||
c = cookiejar.Cookie(0, item[4], item[5],
|
|
||||||
None, False,
|
|
||||||
item[0],
|
|
||||||
item[0].startswith('.'),
|
|
||||||
item[0].startswith('.'),
|
|
||||||
item[1], False,
|
|
||||||
item[2],
|
|
||||||
item[3], item[3]=="",
|
|
||||||
None, None, {})
|
|
||||||
cookies.set_cookie(c)
|
|
||||||
except: pass
|
|
||||||
# TODO: Chromium Cookies
|
|
||||||
# SELECT host_key, path, secure, expires_utc, name, encrypted_value FROM cookies
|
|
||||||
# http://n8henrie.com/2013/11/use-chromes-cookies-for-easier-downloading-with-python-requests/
|
|
||||||
|
|
||||||
elif o in ('-l', '--playlist'):
|
elif o in ('-l', '--playlist'):
|
||||||
playlist = True
|
playlist = True
|
||||||
elif o in ('--no-caption',):
|
elif o in ('--no-caption',):
|
||||||
|
194
src/you_get/cookies.py
Normal file
194
src/you_get/cookies.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from ctypes import byref, c_void_p, c_buffer, c_char, c_uint16, c_uint32, c_int32, c_char_p, POINTER, Structure
|
||||||
|
|
||||||
|
from http.cookiejar import Cookie, CookieJar, MozillaCookieJar
|
||||||
|
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
# from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
# from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
|
|
||||||
|
# https://chromium.googlesource.com/chromium/src/+/master/components/os_crypt/os_crypt_linux.cc#80
|
||||||
|
# https://chromium.googlesource.com/chromium/src/+/master/components/os_crypt/keychain_password_mac.mm#50
|
||||||
|
def key_mac(browser="Chromium"):
|
||||||
|
service_name = ("Chromium Safe Storage" if browser == "Chromium" else "Chrome Safe Storage")
|
||||||
|
|
||||||
|
# from github: keyring/backends/_OS_X_API.py
|
||||||
|
noErr = 0
|
||||||
|
_sec = ctypes.CDLL('/System/Library/Frameworks/Security.framework/Versions/A/Security')
|
||||||
|
SecKeychainFindGenericPassword = _sec.SecKeychainFindGenericPassword
|
||||||
|
SecKeychainFindGenericPassword.argtypes = (
|
||||||
|
c_void_p,
|
||||||
|
c_uint32,
|
||||||
|
c_char_p,
|
||||||
|
c_uint32,
|
||||||
|
c_char_p,
|
||||||
|
POINTER(c_uint32), # passwordLength
|
||||||
|
POINTER(c_void_p), # passwordData
|
||||||
|
POINTER(sec_keychain_item_ref), # itemRef
|
||||||
|
)
|
||||||
|
SecKeychainFindGenericPassword.restype = c_uint32
|
||||||
|
SecKeychainItemFreeContent = _sec.SecKeychainItemFreeContent
|
||||||
|
SecKeychainItemFreeContent.argtypes = (
|
||||||
|
c_void_p, c_void_p,
|
||||||
|
)
|
||||||
|
SecKeychainItemFreeContent.restype = c_uint32
|
||||||
|
|
||||||
|
passwdlen = c_uint32()
|
||||||
|
passwd = c_void_p()
|
||||||
|
status = SecKeychainFindGenericPassword(None,
|
||||||
|
len(service_name),
|
||||||
|
service_name,
|
||||||
|
len(browser),
|
||||||
|
browser,
|
||||||
|
passwdlen,
|
||||||
|
passwd,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if status != noErr: return ""
|
||||||
|
password = ctypes.create_string_buffer(passwdlen.value)
|
||||||
|
ctypes.memmove(password, passwd.value, passwdlen.value)
|
||||||
|
SecKeychainItemFreeContent(None, passwd)
|
||||||
|
return password.raw.decode('utf-8')
|
||||||
|
def key(browser="Chromium"):
|
||||||
|
if sys.platform == "linux":
|
||||||
|
return "peanuts" # v10
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
return key_mac(browser)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
# https://chromium.googlesource.com/chromium/src/+/master/crypto/symmetric_key.cc#53
|
||||||
|
def encrypt_key(key, salt, iterations, key_size_in_bits):
|
||||||
|
return hashlib.pbkdf2_hmac("sha1", key, salt, iterations, key_size_in_bits / 8)
|
||||||
|
|
||||||
|
|
||||||
|
def decode(ciphertext):
|
||||||
|
if not ciphertext: return ciphertext
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return decode_windows(ciphertext)
|
||||||
|
|
||||||
|
passwd = key(browser="Chromium") or key(browser="Chrome")
|
||||||
|
|
||||||
|
if not passwd: return ""
|
||||||
|
if sys.platform == "linux":
|
||||||
|
text = decode_linux(ciphertext, passwd)
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
text = decode_macos(ciphertext, passwd)
|
||||||
|
else: return ciphertext
|
||||||
|
|
||||||
|
right = text[-1]
|
||||||
|
if type(right) != int: right = ord(right)
|
||||||
|
return text[:-right].decode('utf-8', 'replace')
|
||||||
|
|
||||||
|
|
||||||
|
# https://chromium.googlesource.com/chromium/src/+/master/components/os_crypt/os_crypt_linux.cc#182
|
||||||
|
# avaliable versions: v10
|
||||||
|
def decode_linux(ciphertext, key="peanuts"):
|
||||||
|
# Salt for Symmetric key derivation.
|
||||||
|
kSalt = b"saltysalt"
|
||||||
|
# Key size required for 128 bit AES.
|
||||||
|
kDerivedKeySizeInBits = 128
|
||||||
|
# Constant for Symmetic key derivation.
|
||||||
|
kEncryptionIterations = 1
|
||||||
|
# Size of initialization vector for AES 128-bit.
|
||||||
|
kIVBlockSizeAES128 = 16
|
||||||
|
|
||||||
|
encrypted = encrypt_key(key.encode("utf-8"), kSalt, kEncryptionIterations, kDerivedKeySizeInBits) # v10
|
||||||
|
iv = kIVBlockSizeAES128 * b" "
|
||||||
|
return AES.new(encrypted, AES.MODE_CBC, iv).decrypt(ciphertext[3:])
|
||||||
|
# cipher = Cipher(algorithms.AES(encrypted), modes.CBC(iv), backend=default_backend())
|
||||||
|
# d = cipher.decryptor()
|
||||||
|
# return d.update(ciphertext[3:]) + d.finalize()
|
||||||
|
|
||||||
|
|
||||||
|
# https://chromium.googlesource.com/chromium/src/+/master/components/os_crypt/os_crypt_win.cc#48
|
||||||
|
def decode_windows(ciphertext):
|
||||||
|
from ctypes import cdll, windll
|
||||||
|
from ctypes.wintypes import DWORD
|
||||||
|
|
||||||
|
class DATA_BLOB(Structure):
|
||||||
|
_fields_ = [("cbData", DWORD), ("pbData", POINTER(c_char))]
|
||||||
|
|
||||||
|
def decrypt(ciphertext):
|
||||||
|
memcpy = cdll.msvcrt.memcpy
|
||||||
|
CryptUnprotectData = windll.crypt32.CryptUnprotectData
|
||||||
|
LocalFree = windll.kernel32.LocalFree
|
||||||
|
|
||||||
|
bufferIn = c_buffer(ciphertext, len(ciphertext))
|
||||||
|
blobIn = DATA_BLOB(len(ciphertext), bufferIn)
|
||||||
|
blobOut = DATA_BLOB()
|
||||||
|
if CryptUnprotectData(byref(blobIn), None, None, None, None, None, byref(blobOut)):
|
||||||
|
cbData = int(blobOut.cbData)
|
||||||
|
pbData = blobOut.pbData
|
||||||
|
buffer = c_buffer(cbData)
|
||||||
|
memcpy(buffer, pbData, cbData)
|
||||||
|
LocalFree(pbData)
|
||||||
|
return buffer.raw
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
return decrypt(ciphertext)
|
||||||
|
|
||||||
|
|
||||||
|
# https://chromium.googlesource.com/chromium/src/+/master/components/os_crypt/os_crypt_mac.mm#133
|
||||||
|
# avaliable versions: v10
|
||||||
|
# TODO: need test
|
||||||
|
def decode_macos(ciphertext, key=""):
|
||||||
|
# Salt for Symmetric key derivation.
|
||||||
|
kSalt = b"saltysalt"
|
||||||
|
# Key size required for 128 bit AES.
|
||||||
|
kDerivedKeySizeInBits = 128
|
||||||
|
# Constant for Symmetic key derivation.
|
||||||
|
kEncryptionIterations = 1003
|
||||||
|
# TODO(dhollowa): Refactor to allow dependency injection of Keychain.
|
||||||
|
use_mock_keychain = False
|
||||||
|
|
||||||
|
encrypted = encrypt_key(key.encode("utf-8"), kSalt, kEncryptionIterations, kDerivedKeySizeInBits) # v10
|
||||||
|
iv = kIVBlockSizeAES128 * b" "
|
||||||
|
return AES.new(encrypted, AES.MODE_CBC, iv).decrypt(ciphertext[3:])
|
||||||
|
# cipher = Cipher(algorithms.AES(encrypted), modes.CBC(iv), backend=default_backend())
|
||||||
|
# d = cipher.decryptor()
|
||||||
|
# return d.update(ciphertext[3:]) + d.finalize()
|
||||||
|
|
||||||
|
|
||||||
|
def load_cookies(a):
|
||||||
|
try:
|
||||||
|
cookies = MozillaCookieJar(a)
|
||||||
|
cookies.load()
|
||||||
|
except:
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
cookies = MozillaCookieJar()
|
||||||
|
con = sqlite3.connect(a)
|
||||||
|
cur = con.cursor()
|
||||||
|
|
||||||
|
tables = cur.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
||||||
|
|
||||||
|
stmt = ""
|
||||||
|
for t in tables:
|
||||||
|
if t[0] == "cookies":
|
||||||
|
stmt = "SELECT host_key, path, secure, expires_utc, name, value, encrypted_value FROM cookies"; break
|
||||||
|
elif t[0] == "moz_cookies":
|
||||||
|
stmt = "SELECT host, path, isSecure, expiry, name, value FROM moz_cookies"; break
|
||||||
|
if not stmt: return
|
||||||
|
|
||||||
|
items = cur.execute(stmt).fetchall()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
value = item[5]
|
||||||
|
if len(item) == 7 and not value: value = decode(item[-1])
|
||||||
|
c = Cookie(0, item[4], value,
|
||||||
|
None, False,
|
||||||
|
item[0],
|
||||||
|
item[0].startswith('.'),
|
||||||
|
item[0].startswith('.'),
|
||||||
|
item[1], False,
|
||||||
|
item[2],
|
||||||
|
item[3], item[3]=="",
|
||||||
|
None, None, {})
|
||||||
|
cookies.set_cookie(c)
|
||||||
|
return cookies
|
@ -36,5 +36,13 @@
|
|||||||
|
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"you-get = you_get.__main__:main"
|
"you-get = you_get.__main__:main"
|
||||||
|
],
|
||||||
|
|
||||||
|
"setup_requires": [
|
||||||
|
"setuptools_scm"
|
||||||
|
],
|
||||||
|
|
||||||
|
"install_requires": [
|
||||||
|
"pycrypto"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user