diff --git a/comictaggerlib/UnRAR2/__init__.py b/comictaggerlib/UnRAR2/__init__.py index 4095d1c..a913fcb 100644 --- a/comictaggerlib/UnRAR2/__init__.py +++ b/comictaggerlib/UnRAR2/__init__.py @@ -33,7 +33,7 @@ similar to the C interface provided by UnRAR. There is also a higher level interface which makes some common operations easier. """ -__version__ = '0.99.2' +__version__ = '0.99.3' try: WindowsError diff --git a/comictaggerlib/UnRAR2/test_UnRAR2.py b/comictaggerlib/UnRAR2/test_UnRAR2.py index a1a20e4..e86ba2c 100644 --- a/comictaggerlib/UnRAR2/test_UnRAR2.py +++ b/comictaggerlib/UnRAR2/test_UnRAR2.py @@ -12,13 +12,11 @@ def cleanup(dir='test'): os.removedirs(os.path.join(path, dir)) - - - -# reuse RarArchive object, en +# basic test cleanup() rarc = UnRAR2.RarFile('test.rar') rarc.infolist() +assert rarc.comment == "This is a test." for info in rarc.infoiter(): saveinfo = info assert (str(info)=="""""") @@ -98,7 +96,8 @@ list(UnRAR2.RarFile('test_nulls.rar').infoiter()) # extract files from an archive with protected files cleanup() -UnRAR2.RarFile('test_protected_files.rar', password="protected").extract() +rarc = UnRAR2.RarFile('test_protected_files.rar', password="protected") +rarc.extract() assert os.path.exists('test'+os.sep+'top_secret_xxx_file.txt') cleanup() errored = False diff --git a/comictaggerlib/UnRAR2/unix.py b/comictaggerlib/UnRAR2/unix.py index fd56ca9..bd9ee85 100644 --- a/comictaggerlib/UnRAR2/unix.py +++ b/comictaggerlib/UnRAR2/unix.py @@ -33,6 +33,7 @@ from rar_exceptions import * class UnpackerNotInstalled(Exception): pass rar_executable_cached = None +rar_executable_version = None def call_unrar(params): "Calls rar/unrar command line executable, returns stdout pipe" @@ -59,10 +60,10 @@ def call_unrar(params): class RarFileImplementation(object): def init(self, password=None): + global rar_executable_version self.password = password - stdoutdata, stderrdata = self.call('v', []).communicate() for line in stderrdata.splitlines(): @@ -73,18 +74,42 @@ class RarFileImplementation(object): accum = [] source = iter(stdoutdata.splitlines()) line = '' - while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')): - if line.strip().endswith('is not RAR archive'): - raise InvalidRARArchive + while not (line.startswith('UNRAR')): line = source.next() - while not line.startswith('Pathname/Comment'): - accum.append(line.rstrip('\n')) + signature = line + # The code below is mighty flaky + # and will probably crash on localized versions of RAR + # but I see no safe way to rewrite it using a CLI tool + if signature.startswith("UNRAR 4"): + rar_executable_version = 4 + while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')): + if line.strip().endswith('is not RAR archive'): + raise InvalidRARArchive + line = source.next() + while not line.startswith('Pathname/Comment'): + accum.append(line.rstrip('\n')) + line = source.next() + if len(accum): + accum[0] = accum[0][9:] # strip out "Comment:" part + self.comment = '\n'.join(accum[:-1]) + else: + self.comment = None + elif signature.startswith("UNRAR 5"): + rar_executable_version = 5 line = source.next() - if len(accum): - accum[0] = accum[0][9:] - self.comment = '\n'.join(accum[:-1]) + while not line.startswith('Archive:'): + if line.strip().endswith('is not RAR archive'): + raise InvalidRARArchive + accum.append(line.rstrip('\n')) + line = source.next() + if len(accum): + self.comment = '\n'.join(accum[:-1]).strip() + else: + self.comment = None else: - self.comment = None + raise UnpackerNotInstalled("Unsupported RAR version, expected 4.x or 5.x, found: " + + signature.split(" ")[1]) + def escaped_password(self): return '-' if self.password == None else self.password @@ -97,7 +122,8 @@ class RarFileImplementation(object): def infoiter(self): - stdoutdata, stderrdata = self.call('v', ['c-']).communicate() + command = "v" if rar_executable_version == 4 else "l" + stdoutdata, stderrdata = self.call(command, ['c-']).communicate() for line in stderrdata.splitlines(): if line.strip().startswith("Cannot open"): @@ -106,33 +132,48 @@ class RarFileImplementation(object): accum = [] source = iter(stdoutdata.splitlines()) line = '' - while not line.startswith('--------------'): + while not line.startswith('-----------'): if line.strip().endswith('is not RAR archive'): raise InvalidRARArchive - if line.find("CRC failed")>=0: + if line.startswith("CRC failed") or line.startswith("Checksum error"): raise IncorrectRARPassword line = source.next() line = source.next() i = 0 re_spaces = re.compile(r"\s+") - while not line.startswith('--------------'): - accum.append(line) - if len(accum)==2: + if rar_executable_version == 4: + while not line.startswith('-----------'): + accum.append(line) + if len(accum)==2: + data = {} + data['index'] = i + # asterisks mark password-encrypted files + data['filename'] = accum[0].strip().lstrip("*") # asterisks marks password-encrypted files + fields = re_spaces.split(accum[1].strip()) + data['size'] = int(fields[0]) + attr = fields[5] + data['isdir'] = 'd' in attr.lower() + data['datetime'] = time.strptime(fields[3]+" "+fields[4], '%d-%m-%y %H:%M') + data['comment'] = None + yield data + accum = [] + i += 1 + line = source.next() + elif rar_executable_version == 5: + while not line.startswith('-----------'): + fields = line.strip().lstrip("*").split() data = {} data['index'] = i - #!!!ATB - changed this because it was choking when a folder or file started with a space. - #!!! now, just strip off the first char in the string - data['filename'] = accum[0].rstrip()[1:] - info = re_spaces.split(accum[1].strip()) - data['size'] = int(info[0]) - attr = info[5] + data['filename'] = " ".join(fields[4:]) + data['size'] = int(fields[1]) + attr = fields[0] data['isdir'] = 'd' in attr.lower() - data['datetime'] = time.strptime(info[3]+" "+info[4], '%d-%m-%y %H:%M') + data['datetime'] = time.strptime(fields[2]+" "+fields[3], '%d-%m-%y %H:%M') data['comment'] = None yield data - accum = [] i += 1 - line = source.next() + line = source.next() + def read_files(self, checker): res = [] @@ -153,7 +194,7 @@ class RarFileImplementation(object): if overwrite: options.append('o+') else: - options.append('o-') + options.append('o-') if not path.endswith(os.sep): path += os.sep names = [] @@ -167,7 +208,7 @@ class RarFileImplementation(object): names.append(path) proc = self.call(command, options, names) stdoutdata, stderrdata = proc.communicate() - if stderrdata.find("CRC failed")>=0: + if stderrdata.find("CRC failed")>=0 or stderrdata.find("Checksum error")>=0: raise IncorrectRARPassword return res