1
0
mirror of https://github.com/ciromattia/kcc synced 2026-05-17 04:51:44 +00:00

Updated rarfile

This commit is contained in:
Paweł Jastrzębski
2014-01-15 10:00:05 +01:00
parent 3af30faee9
commit 116dce09fd

View File

@@ -108,6 +108,8 @@ if sys.hexversion < 0x3000000:
# py2.6 has broken bytes() # py2.6 has broken bytes()
def bytes(s, enc): def bytes(s, enc):
return str(s) return str(s)
else:
unicode = str
# see if compat bytearray() is needed # see if compat bytearray() is needed
try: try:
@@ -188,10 +190,6 @@ NEED_COMMENTS = 1
#: whether to convert comments to unicode strings #: whether to convert comments to unicode strings
UNICODE_COMMENTS = 0 UNICODE_COMMENTS = 0
#: When RAR is corrupt, stopping on bad header is better
#: On unknown/misparsed RAR headers reporting is better
REPORT_BAD_HEADER = 0
#: Convert RAR time tuple into datetime() object #: Convert RAR time tuple into datetime() object
USE_DATETIME = 0 USE_DATETIME = 0
@@ -340,9 +338,11 @@ class RarSignalExit(RarExecError):
"""Unrar exited with signal""" """Unrar exited with signal"""
def is_rarfile(fn): def is_rarfile(xfile):
'''Check quickly whether file is rar archive.''' '''Check quickly whether file is rar archive.'''
buf = open(fn, "rb").read(len(RAR_ID)) fd = XFile(xfile)
buf = fd.read(len(RAR_ID))
fd.close()
return buf == RAR_ID return buf == RAR_ID
@@ -453,11 +453,12 @@ class RarFile(object):
'''Parse RAR structure, provide access to files in archive. '''Parse RAR structure, provide access to files in archive.
''' '''
#: Archive comment. Byte string or None. Use UNICODE_COMMENTS #: Archive comment. Byte string or None. Use :data:`UNICODE_COMMENTS`
#: to get automatic decoding to unicode. #: to get automatic decoding to unicode.
comment = None comment = None
def __init__(self, rarfile, mode="r", charset=None, info_callback=None, crc_check = True): def __init__(self, rarfile, mode="r", charset=None, info_callback=None,
crc_check = True, errors = "stop"):
"""Open and parse a RAR archive. """Open and parse a RAR archive.
Parameters: Parameters:
@@ -472,6 +473,9 @@ class RarFile(object):
debug callback, gets to see all archive entries. debug callback, gets to see all archive entries.
crc_check crc_check
set to False to disable CRC checks set to False to disable CRC checks
errors
Either "stop" to quietly stop parsing on errors,
or "strict" to raise errors. Default is "stop".
""" """
self.rarfile = rarfile self.rarfile = rarfile
self.comment = None self.comment = None
@@ -485,6 +489,13 @@ class RarFile(object):
self._crc_check = crc_check self._crc_check = crc_check
self._vol_list = [] self._vol_list = []
if errors == "stop":
self._strict = False
elif errors == "strict":
self._strict = True
else:
raise ValueError("Invalid value for 'errors' parameter.")
self._main = None self._main = None
if mode != "r": if mode != "r":
@@ -548,8 +559,9 @@ class RarFile(object):
'''Returns file-like object (:class:`RarExtFile`), '''Returns file-like object (:class:`RarExtFile`),
from where the data can be read. from where the data can be read.
The object implements io.RawIOBase interface, so it can The object implements :class:`io.RawIOBase` interface, so it can
be further wrapped with io.BufferedReader and io.TextIOWrapper. be further wrapped with :class:`io.BufferedReader`
and :class:`io.TextIOWrapper`.
On older Python where io module is not available, it implements On older Python where io module is not available, it implements
only .read(), .seek(), .tell() and .close() methods. only .read(), .seek(), .tell() and .close() methods.
@@ -588,16 +600,19 @@ class RarFile(object):
psw = None psw = None
# is temp write usable? # is temp write usable?
if not USE_EXTRACT_HACK or not self._main: use_hack = 1
if not self._main:
use_hack = 0 use_hack = 0
elif self._main.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD): elif self._main.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD):
use_hack = 0 use_hack = 0
elif inf.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): elif inf.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER):
use_hack = 0 use_hack = 0
elif is_filelike(self.rarfile):
pass
elif inf.file_size > HACK_SIZE_LIMIT: elif inf.file_size > HACK_SIZE_LIMIT:
use_hack = 0 use_hack = 0
else: elif not USE_EXTRACT_HACK:
use_hack = 1 use_hack = 0
# now extract # now extract
if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0: if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0:
@@ -610,7 +625,7 @@ class RarFile(object):
def read(self, fname, psw = None): def read(self, fname, psw = None):
"""Return uncompressed data for archive entry. """Return uncompressed data for archive entry.
For longer files using .open() may be better idea. For longer files using :meth:`RarFile.open` may be better idea.
Parameters: Parameters:
@@ -633,7 +648,7 @@ class RarFile(object):
def printdir(self): def printdir(self):
"""Print archive file list to stdout.""" """Print archive file list to stdout."""
for f in self._info_list: for f in self._info_list:
print((f.filename)) print(f.filename)
def extract(self, member, path=None, pwd=None): def extract(self, member, path=None, pwd=None):
"""Extract single file into current directory. """Extract single file into current directory.
@@ -641,7 +656,7 @@ class RarFile(object):
Parameters: Parameters:
member member
filename or RarInfo instance filename or :class:`RarInfo` instance
path path
optional destination path optional destination path
pwd pwd
@@ -661,7 +676,7 @@ class RarFile(object):
path path
optional destination path optional destination path
members members
optional filename or RarInfo instance list to extract optional filename or :class:`RarInfo` instance list to extract
pwd pwd
optional password to use optional password to use
""" """
@@ -687,10 +702,23 @@ class RarFile(object):
output = p.communicate()[0] output = p.communicate()[0]
check_returncode(p, output) check_returncode(p, output)
def strerror(self):
"""Return error string if parsing failed,
or None if no problems.
"""
return self._parse_error
## ##
## private methods ## private methods
## ##
def _set_error(self, msg, *args):
if args:
msg = msg % args
self._parse_error = msg
if self._strict:
raise BadRarFile(msg)
# store entry # store entry
def _process_entry(self, item): def _process_entry(self, item):
if item.type == RAR_BLOCK_FILE: if item.type == RAR_BLOCK_FILE:
@@ -738,7 +766,7 @@ class RarFile(object):
self._fd = None self._fd = None
def _parse_real(self): def _parse_real(self):
fd = open(self.rarfile, "rb") fd = XFile(self.rarfile)
self._fd = fd self._fd = fd
id = fd.read(len(RAR_ID)) id = fd.read(len(RAR_ID))
if id != RAR_ID: if id != RAR_ID:
@@ -757,9 +785,13 @@ class RarFile(object):
if not h: if not h:
if more_vols: if more_vols:
volume += 1 volume += 1
volfile = self._next_volname(volfile)
fd.close() fd.close()
fd = open(volfile, "rb") try:
volfile = self._next_volname(volfile)
fd = XFile(volfile)
except IOError:
self._set_error("Cannot open next volume: %s", volfile)
break
self._fd = fd self._fd = fd
more_vols = 0 more_vols = 0
endarc = 0 endarc = 0
@@ -824,8 +856,7 @@ class RarFile(object):
# now read actual header # now read actual header
return self._parse_block_header(fd) return self._parse_block_header(fd)
except struct.error: except struct.error:
if REPORT_BAD_HEADER: self._set_error('Broken header in RAR file')
raise BadRarFile('Broken header in RAR file')
return None return None
# common header # common header
@@ -852,8 +883,7 @@ class RarFile(object):
# unexpected EOF? # unexpected EOF?
if len(h.header_data) != h.header_size: if len(h.header_data) != h.header_size:
if REPORT_BAD_HEADER: self._set_error('Unexpected EOF when reading header')
raise BadRarFile('Unexpected EOF when reading header')
return None return None
# block has data assiciated with it? # block has data assiciated with it?
@@ -896,18 +926,9 @@ class RarFile(object):
if h.header_crc == calc_crc: if h.header_crc == calc_crc:
return h return h
# need to panic? # header parsing failed.
if REPORT_BAD_HEADER: self._set_error('Header CRC error (%02x): exp=%x got=%x (xlen = %d)',
xlen = len(crcdat) h.type, h.header_crc, calc_crc, len(crcdat))
crcdat = h.header_data[2:]
msg = 'Header CRC error (%02x): exp=%x got=%x (xlen = %d)' % ( h.type, h.header_crc, calc_crc, xlen )
xlen = len(crcdat)
while xlen >= S_BLK_HDR.size - 2:
crc = crc32(crcdat[:xlen]) & 0xFFFF
if crc == h.header_crc:
msg += ' / crc match, xlen = %d' % xlen
xlen -= 1
raise BadRarFile(msg)
# instead panicing, send eof # instead panicing, send eof
return None return None
@@ -1053,6 +1074,8 @@ class RarFile(object):
# given current vol name, construct next one # given current vol name, construct next one
def _next_volname(self, volfile): def _next_volname(self, volfile):
if is_filelike(volfile):
raise IOError("Working on single FD")
if self._main.flags & RAR_MAIN_NEWNUMBERING: if self._main.flags & RAR_MAIN_NEWNUMBERING:
return self._next_newvol(volfile) return self._next_newvol(volfile)
return self._next_oldvol(volfile) return self._next_oldvol(volfile)
@@ -1093,7 +1116,7 @@ class RarFile(object):
BSIZE = 32*1024 BSIZE = 32*1024
size = inf.compress_size + inf.header_size size = inf.compress_size + inf.header_size
rf = open(inf.volume_file, "rb", 0) rf = XFile(inf.volume_file, 0)
rf.seek(inf.header_offset) rf.seek(inf.header_offset)
tmpfd, tmpname = mkstemp(suffix='.rar') tmpfd, tmpname = mkstemp(suffix='.rar')
@@ -1125,7 +1148,7 @@ class RarFile(object):
def _read_comment_v3(self, inf, psw=None): def _read_comment_v3(self, inf, psw=None):
# read data # read data
rf = open(inf.volume_file, "rb") rf = XFile(inf.volume_file)
rf.seek(inf.file_offset) rf.seek(inf.file_offset)
data = rf.read(inf.compress_size) data = rf.read(inf.compress_size)
rf.close() rf.close()
@@ -1138,7 +1161,7 @@ class RarFile(object):
if self._crc_check: if self._crc_check:
crc = crc32(cmt) crc = crc32(cmt)
if crc < 0: if crc < 0:
crc += (int(1) << 32) crc += (long(1) << 32)
if crc != inf.CRC: if crc != inf.CRC:
return None return None
@@ -1146,6 +1169,8 @@ class RarFile(object):
# extract using unrar # extract using unrar
def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None): def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None):
if is_filelike(rarfile):
raise ValueError("Cannot use unrar directly on memory buffer")
cmd = [UNRAR_TOOL] + list(OPEN_ARGS) cmd = [UNRAR_TOOL] + list(OPEN_ARGS)
if psw is not None: if psw is not None:
cmd.append("-p" + psw) cmd.append("-p" + psw)
@@ -1342,7 +1367,7 @@ class RarExtFile(RawIOBase):
raise BadRarFile("Failed the read enough data") raise BadRarFile("Failed the read enough data")
crc = self.CRC crc = self.CRC
if crc < 0: if crc < 0:
crc += (int(1) << 32) crc += (long(1) << 32)
if crc != self.inf.CRC: if crc != self.inf.CRC:
raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename) raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename)
@@ -1553,7 +1578,7 @@ class DirectReader(RarExtFile):
RarExtFile._open(self) RarExtFile._open(self)
self.volfile = self.inf.volume_file self.volfile = self.inf.volume_file
self.fd = open(self.volfile, "rb", 0) self.fd = XFile(self.volfile, 0)
self.fd.seek(self.inf.header_offset, 0) self.fd.seek(self.inf.header_offset, 0)
self.cur = self.rf._parse_header(self.fd) self.cur = self.rf._parse_header(self.fd)
self.cur_avail = self.cur.add_size self.cur_avail = self.cur.add_size
@@ -1705,10 +1730,47 @@ class HeaderDecrypt:
return res return res
# handle (filename|filelike) object
class XFile(object):
__slots__ = ('_fd', '_need_close')
def __init__(self, xfile, bufsize = 1024):
if is_filelike(xfile):
self._need_close = False
self._fd = xfile
self._fd.seek(0)
else:
self._need_close = True
self._fd = open(xfile, 'rb', bufsize)
def read(self, n=None):
return self._fd.read(n)
def tell(self):
return self._fd.tell()
def seek(self, ofs, whence=0):
return self._fd.seek(ofs, whence)
def readinto(self, dst):
return self._fd.readinto(dst)
def close(self):
if self._need_close:
self._fd.close()
def __enter__(self):
return self
def __exit__(self, typ, val, tb):
self.close()
## ##
## Utility functions ## Utility functions
## ##
def is_filelike(obj):
if isinstance(obj, str) or isinstance(obj, unicode):
return False
res = True
for a in ('read', 'tell', 'seek'):
res = res and hasattr(obj, a)
if not res:
raise ValueError("Invalid object passed as file")
return True
def rar3_s2k(psw, salt): def rar3_s2k(psw, salt):
"""String-to-key hash for RAR3.""" """String-to-key hash for RAR3."""
@@ -1874,4 +1936,3 @@ def check_returncode(p, out):
msg = "%s [%d]" % (exc.__doc__, p.returncode) msg = "%s [%d]" % (exc.__doc__, p.returncode)
raise exc(msg) raise exc(msg)