2014-03-10 01:18:05 -04:00
|
|
|
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
|
|
|
|
#
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
# a copy of this software and associated documentation files (the
|
|
|
|
# "Software"), to deal in the Software without restriction, including
|
|
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
# the following conditions:
|
|
|
|
#
|
|
|
|
# The above copyright notice and this permission notice shall be
|
|
|
|
# included in all copies or substantial portions of the Software.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
# SOFTWARE.
|
|
|
|
|
|
|
|
# Unix version uses unrar command line executable
|
|
|
|
|
|
|
|
import subprocess
|
|
|
|
import gc
|
|
|
|
|
|
|
|
import os, os.path
|
|
|
|
import time, re
|
|
|
|
|
|
|
|
from rar_exceptions import *
|
|
|
|
|
|
|
|
class UnpackerNotInstalled(Exception): pass
|
|
|
|
|
|
|
|
rar_executable_cached = None
|
2014-05-12 16:56:22 -04:00
|
|
|
rar_executable_version = None
|
2014-03-10 01:18:05 -04:00
|
|
|
|
|
|
|
def call_unrar(params):
|
|
|
|
"Calls rar/unrar command line executable, returns stdout pipe"
|
|
|
|
global rar_executable_cached
|
|
|
|
if rar_executable_cached is None:
|
2014-05-12 16:56:22 -04:00
|
|
|
for command in ('unrar', 'rar'):
|
2014-03-10 01:18:05 -04:00
|
|
|
try:
|
2014-05-12 16:56:22 -04:00
|
|
|
subprocess.Popen([command], stdout=subprocess.PIPE)
|
2014-03-10 01:18:05 -04:00
|
|
|
rar_executable_cached = command
|
|
|
|
break
|
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
if rar_executable_cached is None:
|
|
|
|
raise UnpackerNotInstalled("No suitable RAR unpacker installed")
|
2014-05-12 16:56:22 -04:00
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
assert type(params) == list, "params must be list"
|
|
|
|
args = [rar_executable_cached] + params
|
|
|
|
try:
|
|
|
|
gc.disable() # See http://bugs.python.org/issue1336
|
2014-05-12 16:56:22 -04:00
|
|
|
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
2014-03-10 01:18:05 -04:00
|
|
|
finally:
|
|
|
|
gc.enable()
|
|
|
|
|
|
|
|
class RarFileImplementation(object):
|
|
|
|
|
2014-05-12 16:56:22 -04:00
|
|
|
def init(self, password=None):
|
|
|
|
global rar_executable_version
|
2014-03-10 01:18:05 -04:00
|
|
|
self.password = password
|
2014-05-12 16:56:22 -04:00
|
|
|
|
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
stdoutdata, stderrdata = self.call('v', []).communicate()
|
2014-05-12 16:56:22 -04:00
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
for line in stderrdata.splitlines():
|
|
|
|
if line.strip().startswith("Cannot open"):
|
|
|
|
raise FileOpenError
|
2014-05-12 16:56:22 -04:00
|
|
|
if line.find("CRC failed")>=0:
|
|
|
|
raise IncorrectRARPassword
|
2014-03-10 01:18:05 -04:00
|
|
|
accum = []
|
|
|
|
source = iter(stdoutdata.splitlines())
|
|
|
|
line = ''
|
2014-05-12 16:56:22 -04:00
|
|
|
while not (line.startswith('UNRAR')):
|
2014-03-10 01:18:05 -04:00
|
|
|
line = source.next()
|
2014-05-12 16:56:22 -04:00
|
|
|
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
|
2014-03-10 01:18:05 -04:00
|
|
|
line = source.next()
|
2014-05-12 16:56:22 -04:00
|
|
|
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
|
2014-03-10 01:18:05 -04:00
|
|
|
else:
|
2014-05-12 16:56:22 -04:00
|
|
|
raise UnpackerNotInstalled("Unsupported RAR version, expected 4.x or 5.x, found: "
|
|
|
|
+ signature.split(" ")[1])
|
|
|
|
|
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
def escaped_password(self):
|
|
|
|
return '-' if self.password == None else self.password
|
2014-05-12 16:56:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
def call(self, cmd, options=[], files=[]):
|
|
|
|
options2 = options + ['p'+self.escaped_password()]
|
|
|
|
soptions = ['-'+x for x in options2]
|
|
|
|
return call_unrar([cmd]+soptions+['--',self.archiveName]+files)
|
2014-03-10 01:18:05 -04:00
|
|
|
|
|
|
|
def infoiter(self):
|
2014-05-12 16:56:22 -04:00
|
|
|
|
|
|
|
command = "v" if rar_executable_version == 4 else "l"
|
|
|
|
stdoutdata, stderrdata = self.call(command, ['c-']).communicate()
|
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
for line in stderrdata.splitlines():
|
|
|
|
if line.strip().startswith("Cannot open"):
|
|
|
|
raise FileOpenError
|
2014-05-12 16:56:22 -04:00
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
accum = []
|
|
|
|
source = iter(stdoutdata.splitlines())
|
|
|
|
line = ''
|
2014-05-12 16:56:22 -04:00
|
|
|
while not line.startswith('-----------'):
|
2014-03-10 01:18:05 -04:00
|
|
|
if line.strip().endswith('is not RAR archive'):
|
|
|
|
raise InvalidRARArchive
|
2014-05-12 16:56:22 -04:00
|
|
|
if line.startswith("CRC failed") or line.startswith("Checksum error"):
|
|
|
|
raise IncorrectRARPassword
|
2014-03-10 01:18:05 -04:00
|
|
|
line = source.next()
|
|
|
|
line = source.next()
|
|
|
|
i = 0
|
|
|
|
re_spaces = re.compile(r"\s+")
|
2014-05-12 16:56:22 -04:00
|
|
|
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()
|
2014-03-10 01:18:05 -04:00
|
|
|
data = {}
|
|
|
|
data['index'] = i
|
2014-05-12 16:56:22 -04:00
|
|
|
data['filename'] = " ".join(fields[4:])
|
|
|
|
data['size'] = int(fields[1])
|
|
|
|
attr = fields[0]
|
2014-03-10 01:18:05 -04:00
|
|
|
data['isdir'] = 'd' in attr.lower()
|
2014-05-12 16:56:22 -04:00
|
|
|
data['datetime'] = time.strptime(fields[2]+" "+fields[3], '%d-%m-%y %H:%M')
|
2014-03-10 01:18:05 -04:00
|
|
|
data['comment'] = None
|
|
|
|
yield data
|
|
|
|
i += 1
|
2014-05-12 16:56:22 -04:00
|
|
|
line = source.next()
|
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
|
|
|
|
def read_files(self, checker):
|
|
|
|
res = []
|
|
|
|
for info in self.infoiter():
|
|
|
|
checkres = checker(info)
|
2014-05-12 16:56:22 -04:00
|
|
|
if checkres==True and not info.isdir:
|
2014-03-10 01:18:05 -04:00
|
|
|
pipe = self.call('p', ['inul'], [info.filename]).stdout
|
|
|
|
res.append((info, pipe.read()))
|
2014-05-12 16:56:22 -04:00
|
|
|
return res
|
2014-03-10 01:18:05 -04:00
|
|
|
|
2014-05-12 16:56:22 -04:00
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
def extract(self, checker, path, withSubpath, overwrite):
|
|
|
|
res = []
|
|
|
|
command = 'x'
|
|
|
|
if not withSubpath:
|
|
|
|
command = 'e'
|
|
|
|
options = []
|
|
|
|
if overwrite:
|
|
|
|
options.append('o+')
|
|
|
|
else:
|
2014-05-12 16:56:22 -04:00
|
|
|
options.append('o-')
|
2014-03-10 01:18:05 -04:00
|
|
|
if not path.endswith(os.sep):
|
|
|
|
path += os.sep
|
|
|
|
names = []
|
|
|
|
for info in self.infoiter():
|
|
|
|
checkres = checker(info)
|
|
|
|
if type(checkres) in [str, unicode]:
|
|
|
|
raise NotImplementedError("Condition callbacks returning strings are deprecated and only supported in Windows")
|
2014-05-12 16:56:22 -04:00
|
|
|
if checkres==True and not info.isdir:
|
2014-03-10 01:18:05 -04:00
|
|
|
names.append(info.filename)
|
|
|
|
res.append(info)
|
|
|
|
names.append(path)
|
|
|
|
proc = self.call(command, options, names)
|
|
|
|
stdoutdata, stderrdata = proc.communicate()
|
2014-05-12 16:56:22 -04:00
|
|
|
if stderrdata.find("CRC failed")>=0 or stderrdata.find("Checksum error")>=0:
|
|
|
|
raise IncorrectRARPassword
|
|
|
|
return res
|
|
|
|
|
2014-03-10 01:18:05 -04:00
|
|
|
def destruct(self):
|
|
|
|
pass
|
|
|
|
|
2014-05-12 16:56:22 -04:00
|
|
|
|