mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-22 04:45:05 +00:00
Merge branch 'feature/UpdateRarfile' into dev
This commit is contained in:
commit
c42974e035
2 changed files with 132 additions and 33 deletions
|
@ -3,9 +3,10 @@
|
|||
* Update attr 22.2.0 (683d056) to 23.1.0 (67e4ff2)
|
||||
* Update Beautiful Soup 4.12.2 to 4.12.2 (30c58a1)
|
||||
* Update diskcache 5.6.1 (4d30686) to 5.6.3 (323787f)
|
||||
* Update Pytvmaze library 2.0.8 (81888a5) to 2.0.8 (b451391)
|
||||
* Update soupsieve 2.4.1 (2e66beb) to 2.5.0 (dc71495)
|
||||
* Update hachoir 3.1.2 (f739b43) to 3.2.0 (38d759f)
|
||||
* Update Pytvmaze library 2.0.8 (81888a5) to 2.0.8 (b451391)
|
||||
* Update Rarfile 4.1a1 (8a72967) to 4.1 (c9140d8)
|
||||
* Update soupsieve 2.4.1 (2e66beb) to 2.5.0 (dc71495)
|
||||
|
||||
|
||||
### 3.30.1 (2023-10-02 22:50:00 UTC)
|
||||
|
|
|
@ -59,7 +59,7 @@ import sys
|
|||
import warnings
|
||||
from binascii import crc32, hexlify
|
||||
from datetime import datetime, timezone
|
||||
from hashlib import blake2s, pbkdf2_hmac, sha1
|
||||
from hashlib import blake2s, pbkdf2_hmac, sha1, sha256
|
||||
from pathlib import Path
|
||||
from struct import Struct, pack, unpack
|
||||
from subprocess import DEVNULL, PIPE, STDOUT, Popen
|
||||
|
@ -92,7 +92,7 @@ class AES_CBC_Decrypt:
|
|||
self.decrypt = ciph.decryptor().update
|
||||
|
||||
|
||||
__version__ = "4.1a1"
|
||||
__version__ = "4.1"
|
||||
|
||||
# export only interesting items
|
||||
__all__ = ["get_rar_version", "is_rarfile", "is_rarfile_sfx", "RarInfo", "RarFile", "RarExtFile"]
|
||||
|
@ -110,6 +110,12 @@ UNAR_TOOL = "unar"
|
|||
#: executable for bsdtar tool
|
||||
BSDTAR_TOOL = "bsdtar"
|
||||
|
||||
#: executable for p7zip/7z tool
|
||||
SEVENZIP_TOOL = "7z"
|
||||
|
||||
#: executable for alternative 7z tool
|
||||
SEVENZIP2_TOOL = "7zz"
|
||||
|
||||
#: default fallback charset
|
||||
DEFAULT_CHARSET = "windows-1252"
|
||||
|
||||
|
@ -282,6 +288,9 @@ DOS_MODE_SYSTEM = 0x04
|
|||
DOS_MODE_HIDDEN = 0x02
|
||||
DOS_MODE_READONLY = 0x01
|
||||
|
||||
RAR5_PW_CHECK_SIZE = 8
|
||||
RAR5_PW_SUM_SIZE = 4
|
||||
|
||||
##
|
||||
## internal constants
|
||||
##
|
||||
|
@ -300,6 +309,8 @@ _BAD_CHARS = r"""\x00-\x1F<>|"?*"""
|
|||
RC_BAD_CHARS_UNIX = re.compile(r"[%s]" % _BAD_CHARS)
|
||||
RC_BAD_CHARS_WIN32 = re.compile(r"[%s:^\\]" % _BAD_CHARS)
|
||||
|
||||
FORCE_TOOL = False
|
||||
|
||||
|
||||
def _find_sfx_header(xfile):
|
||||
sig = RAR_ID[:-1]
|
||||
|
@ -641,6 +652,27 @@ class RarInfo:
|
|||
|
||||
class RarFile:
|
||||
"""Parse RAR structure, provide access to files in archive.
|
||||
|
||||
Parameters:
|
||||
|
||||
file
|
||||
archive file name or file-like object.
|
||||
mode
|
||||
only "r" is supported.
|
||||
charset
|
||||
fallback charset to use, if filenames are not already Unicode-enabled.
|
||||
info_callback
|
||||
debug callback, gets to see all archive entries.
|
||||
crc_check
|
||||
set to False to disable CRC checks
|
||||
errors
|
||||
Either "stop" to quietly stop parsing on errors,
|
||||
or "strict" to raise errors. Default is "stop".
|
||||
part_only
|
||||
If True, read only single file and allow it to be middle-part
|
||||
of multi-volume archive.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
"""
|
||||
|
||||
#: File name, if available. Unicode string or None.
|
||||
|
@ -651,27 +683,6 @@ class RarFile:
|
|||
|
||||
def __init__(self, file, mode="r", charset=None, info_callback=None,
|
||||
crc_check=True, errors="stop", part_only=False):
|
||||
"""Open and parse a RAR archive.
|
||||
|
||||
Parameters:
|
||||
|
||||
file
|
||||
archive file name or file-like object.
|
||||
mode
|
||||
only "r" is supported.
|
||||
charset
|
||||
fallback charset to use, if filenames are not already Unicode-enabled.
|
||||
info_callback
|
||||
debug callback, gets to see all archive entries.
|
||||
crc_check
|
||||
set to False to disable CRC checks
|
||||
errors
|
||||
Either "stop" to quietly stop parsing on errors,
|
||||
or "strict" to raise errors. Default is "stop".
|
||||
part_only
|
||||
If True, read only single file and allow it to be middle-part
|
||||
of multi-volume archive.
|
||||
"""
|
||||
if is_filelike(file):
|
||||
self.filename = getattr(file, "name", None)
|
||||
else:
|
||||
|
@ -751,6 +762,16 @@ class RarFile:
|
|||
"""
|
||||
return self._file_parser.getinfo(name)
|
||||
|
||||
def getinfo_orig(self, name):
|
||||
"""Return RarInfo for file source.
|
||||
|
||||
RAR5: if name is hard-linked or copied file,
|
||||
returns original entry with original filename.
|
||||
|
||||
.. versionadded:: 4.1
|
||||
"""
|
||||
return self._file_parser.getinfo_orig(name)
|
||||
|
||||
def open(self, name, mode="r", pwd=None):
|
||||
"""Returns file-like object (:class:`RarExtFile`) from where the data can be read.
|
||||
|
||||
|
@ -1058,6 +1079,15 @@ class CommonParser:
|
|||
except KeyError:
|
||||
raise NoRarEntry("No such file: %s" % fname) from None
|
||||
|
||||
def getinfo_orig(self, member):
|
||||
inf = self.getinfo(member)
|
||||
if inf.file_redir:
|
||||
redir_type, redir_flags, redir_name = inf.file_redir
|
||||
# cannot leave to unrar as it expects copied file to exist
|
||||
if redir_type in (RAR5_XREDIR_FILE_COPY, RAR5_XREDIR_HARD_LINK):
|
||||
inf = self.getinfo(redir_name)
|
||||
return inf
|
||||
|
||||
def parse(self):
|
||||
"""Process file."""
|
||||
self._fd = None
|
||||
|
@ -1111,6 +1141,7 @@ class CommonParser:
|
|||
endarc = False
|
||||
self._vol_list.append(volfile)
|
||||
self._main = None
|
||||
self._hdrenc_main = None
|
||||
continue
|
||||
break
|
||||
h.volume = volume
|
||||
|
@ -1255,6 +1286,8 @@ class CommonParser:
|
|||
return self._open_unrar(self._rarfile, inf, pwd)
|
||||
|
||||
def _open_clear(self, inf):
|
||||
if FORCE_TOOL:
|
||||
return self._open_unrar(self._rarfile, inf)
|
||||
return DirectReader(self, inf)
|
||||
|
||||
def _open_hack_core(self, inf, pwd, prefix, suffix):
|
||||
|
@ -1302,7 +1335,7 @@ class CommonParser:
|
|||
# not giving filename avoids encoding related problems
|
||||
fn = None
|
||||
if not tmpfile or force_file:
|
||||
fn = inf.filename
|
||||
fn = inf.filename.replace("/", os.path.sep)
|
||||
|
||||
# read from unrar pipe
|
||||
cmd = setup.open_cmdline(pwd, rarfile, fn)
|
||||
|
@ -1768,14 +1801,18 @@ class RAR5Parser(CommonParser):
|
|||
# AES encrypted headers
|
||||
_last_aes256_key = (-1, None, None) # (kdf_count, salt, key)
|
||||
|
||||
def _get_utf8_password(self):
|
||||
pwd = self._password
|
||||
if isinstance(pwd, str):
|
||||
return pwd.encode("utf8")
|
||||
return pwd
|
||||
|
||||
def _gen_key(self, kdf_count, salt):
|
||||
if self._last_aes256_key[:2] == (kdf_count, salt):
|
||||
return self._last_aes256_key[2]
|
||||
if kdf_count > 24:
|
||||
raise BadRarFile("Too large kdf_count")
|
||||
pwd = self._password
|
||||
if isinstance(pwd, str):
|
||||
pwd = pwd.encode("utf8")
|
||||
pwd = self._get_utf8_password()
|
||||
key = pbkdf2_hmac("sha256", pwd, salt, 1 << kdf_count)
|
||||
self._last_aes256_key = (kdf_count, salt, key)
|
||||
return key
|
||||
|
@ -1938,15 +1975,39 @@ class RAR5Parser(CommonParser):
|
|||
h.flags |= RAR_ENDARC_NEXT_VOLUME
|
||||
return h
|
||||
|
||||
def _check_password(self, check_value, kdf_count_shift, salt):
|
||||
if len(check_value) != RAR5_PW_CHECK_SIZE + RAR5_PW_SUM_SIZE:
|
||||
return
|
||||
|
||||
hdr_check = check_value[:RAR5_PW_CHECK_SIZE]
|
||||
hdr_sum = check_value[RAR5_PW_CHECK_SIZE:]
|
||||
sum_hash = sha256(hdr_check).digest()
|
||||
if sum_hash[:RAR5_PW_SUM_SIZE] != hdr_sum:
|
||||
return
|
||||
|
||||
kdf_count = (1 << kdf_count_shift) + 32
|
||||
pwd = self._get_utf8_password()
|
||||
pwd_hash = pbkdf2_hmac("sha256", pwd, salt, kdf_count)
|
||||
|
||||
pwd_check = bytearray(RAR5_PW_CHECK_SIZE)
|
||||
len_mask = RAR5_PW_CHECK_SIZE - 1
|
||||
for i, v in enumerate(pwd_hash):
|
||||
pwd_check[i & len_mask] ^= v
|
||||
|
||||
if pwd_check != hdr_check:
|
||||
raise RarWrongPassword()
|
||||
|
||||
def _parse_encryption_block(self, h, hdata, pos):
|
||||
h.encryption_algo, pos = load_vint(hdata, pos)
|
||||
h.encryption_flags, pos = load_vint(hdata, pos)
|
||||
h.encryption_kdf_count, pos = load_byte(hdata, pos)
|
||||
h.encryption_salt, pos = load_bytes(hdata, 16, pos)
|
||||
if h.encryption_flags & RAR5_ENC_FLAG_HAS_CHECKVAL:
|
||||
h.encryption_check_value = load_bytes(hdata, 12, pos)
|
||||
h.encryption_check_value, pos = load_bytes(hdata, 12, pos)
|
||||
if h.encryption_algo != RAR5_XENC_CIPHER_AES256:
|
||||
raise BadRarFile("Unsupported header encryption cipher")
|
||||
if h.encryption_check_value and self._password:
|
||||
self._check_password(h.encryption_check_value, h.encryption_kdf_count, h.encryption_salt)
|
||||
self._hdrenc_main = h
|
||||
return h
|
||||
|
||||
|
@ -2183,6 +2244,7 @@ class RarExtFile(io.RawIOBase):
|
|||
_remain = 0
|
||||
_returncode = 0
|
||||
_md_context = None
|
||||
_seeking = False
|
||||
|
||||
def _open_extfile(self, parser, inf):
|
||||
self.name = inf.filename
|
||||
|
@ -2191,7 +2253,10 @@ class RarExtFile(io.RawIOBase):
|
|||
|
||||
if self._fd:
|
||||
self._fd.close()
|
||||
md_class = self._inf._md_class or NoHashContext
|
||||
if self._seeking:
|
||||
md_class = NoHashContext
|
||||
else:
|
||||
md_class = self._inf._md_class or NoHashContext
|
||||
self._md_context = md_class()
|
||||
self._fd = None
|
||||
self._remain = self._inf.file_size
|
||||
|
@ -2282,7 +2347,9 @@ class RarExtFile(io.RawIOBase):
|
|||
"""
|
||||
|
||||
# disable crc check when seeking
|
||||
self._md_context = NoHashContext()
|
||||
if not self._seeking:
|
||||
self._md_context = NoHashContext()
|
||||
self._seeking = True
|
||||
|
||||
fsize = self._inf.file_size
|
||||
cur_ofs = self.tell()
|
||||
|
@ -2463,6 +2530,11 @@ class DirectReader(RarExtFile):
|
|||
def _read(self, cnt):
|
||||
"""Read from potentially multi-volume archive."""
|
||||
|
||||
pos = self._fd.tell()
|
||||
need = self._cur.data_offset + self._cur.add_size - self._cur_avail
|
||||
if pos != need:
|
||||
self._fd.seek(need, 0)
|
||||
|
||||
buf = []
|
||||
while cnt > 0:
|
||||
# next vol needed?
|
||||
|
@ -3290,6 +3362,8 @@ class ToolSetup:
|
|||
def get_cmdline(self, key, pwd, nodash=False):
|
||||
cmdline = list(self.setup[key])
|
||||
cmdline[0] = globals()[cmdline[0]]
|
||||
if key == "check_cmd":
|
||||
return cmdline
|
||||
self.add_password_arg(cmdline, pwd)
|
||||
if not nodash:
|
||||
cmdline.append("--")
|
||||
|
@ -3352,10 +3426,30 @@ BSDTAR_CONFIG = {
|
|||
"errmap": [None],
|
||||
}
|
||||
|
||||
SEVENZIP_CONFIG = {
|
||||
"open_cmd": ("SEVENZIP_TOOL", "e", "-so", "-bb0"),
|
||||
"check_cmd": ("SEVENZIP_TOOL", "i"),
|
||||
"password": "-p",
|
||||
"no_password": ("-p",),
|
||||
"errmap": [None,
|
||||
RarWarning, RarFatalError, None, None, # 1..4
|
||||
None, None, RarUserError, RarMemoryError] # 5..8
|
||||
}
|
||||
|
||||
SEVENZIP2_CONFIG = {
|
||||
"open_cmd": ("SEVENZIP2_TOOL", "e", "-so", "-bb0"),
|
||||
"check_cmd": ("SEVENZIP2_TOOL", "i"),
|
||||
"password": "-p",
|
||||
"no_password": ("-p",),
|
||||
"errmap": [None,
|
||||
RarWarning, RarFatalError, None, None, # 1..4
|
||||
None, None, RarUserError, RarMemoryError] # 5..8
|
||||
}
|
||||
|
||||
CURRENT_SETUP = None
|
||||
|
||||
|
||||
def tool_setup(unrar=True, unar=True, bsdtar=True, force=False):
|
||||
def tool_setup(unrar=True, unar=True, bsdtar=True, sevenzip=True, sevenzip2=True, force=False):
|
||||
"""Pick a tool, return cached ToolSetup.
|
||||
"""
|
||||
global CURRENT_SETUP
|
||||
|
@ -3368,6 +3462,10 @@ def tool_setup(unrar=True, unar=True, bsdtar=True, force=False):
|
|||
lst.append(UNRAR_CONFIG)
|
||||
if unar:
|
||||
lst.append(UNAR_CONFIG)
|
||||
if sevenzip:
|
||||
lst.append(SEVENZIP_CONFIG)
|
||||
if sevenzip2:
|
||||
lst.append(SEVENZIP2_CONFIG)
|
||||
if bsdtar:
|
||||
lst.append(BSDTAR_CONFIG)
|
||||
|
||||
|
|
Loading…
Reference in a new issue