Handle weird permissions in tar archives
This commit is contained in:
parent
14a4cddb8b
commit
a7ebb587e1
@ -1,4 +1,5 @@
|
|||||||
import abc
|
import abc
|
||||||
|
import stat
|
||||||
import zipfile
|
import zipfile
|
||||||
import datetime
|
import datetime
|
||||||
import tarfile
|
import tarfile
|
||||||
@ -104,6 +105,12 @@ class ArchiveBasedAbstractParser(abstract.AbstractParser):
|
|||||||
full_path: str):
|
full_path: str):
|
||||||
"""Add the file at full_path to the archive, via the given member."""
|
"""Add the file at full_path to the archive, via the given member."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _set_member_permissions(member: ArchiveMember, permissions: int) -> ArchiveMember:
|
||||||
|
"""Set the permission of the archive member."""
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
return member
|
||||||
|
|
||||||
def get_meta(self) -> Dict[str, Union[str, dict]]:
|
def get_meta(self) -> Dict[str, Union[str, dict]]:
|
||||||
meta = dict() # type: Dict[str, Union[str, dict]]
|
meta = dict() # type: Dict[str, Union[str, dict]]
|
||||||
|
|
||||||
@ -120,6 +127,7 @@ class ArchiveBasedAbstractParser(abstract.AbstractParser):
|
|||||||
|
|
||||||
zin.extract(member=item, path=temp_folder)
|
zin.extract(member=item, path=temp_folder)
|
||||||
full_path = os.path.join(temp_folder, member_name)
|
full_path = os.path.join(temp_folder, member_name)
|
||||||
|
os.chmod(full_path, stat.S_IRUSR)
|
||||||
|
|
||||||
specific_meta = self._specific_get_meta(full_path, member_name)
|
specific_meta = self._specific_get_meta(full_path, member_name)
|
||||||
local_meta = {**local_meta, **specific_meta}
|
local_meta = {**local_meta, **specific_meta}
|
||||||
@ -164,6 +172,9 @@ class ArchiveBasedAbstractParser(abstract.AbstractParser):
|
|||||||
zin.extract(member=item, path=temp_folder)
|
zin.extract(member=item, path=temp_folder)
|
||||||
full_path = os.path.join(temp_folder, member_name)
|
full_path = os.path.join(temp_folder, member_name)
|
||||||
|
|
||||||
|
original_permissions = os.stat(full_path).st_mode
|
||||||
|
os.chmod(full_path, original_permissions | stat.S_IWUSR | stat.S_IRUSR)
|
||||||
|
|
||||||
if self._specific_cleanup(full_path) is False:
|
if self._specific_cleanup(full_path) is False:
|
||||||
logging.warning("Something went wrong during deep cleaning of %s",
|
logging.warning("Something went wrong during deep cleaning of %s",
|
||||||
member_name)
|
member_name)
|
||||||
@ -202,6 +213,7 @@ class ArchiveBasedAbstractParser(abstract.AbstractParser):
|
|||||||
os.rename(member_parser.output_filename, full_path)
|
os.rename(member_parser.output_filename, full_path)
|
||||||
|
|
||||||
zinfo = self.member_class(member_name) # type: ignore
|
zinfo = self.member_class(member_name) # type: ignore
|
||||||
|
zinfo = self._set_member_permissions(zinfo, original_permissions)
|
||||||
clean_zinfo = self._clean_member(zinfo)
|
clean_zinfo = self._clean_member(zinfo)
|
||||||
self._add_file_to_archive(zout, clean_zinfo, full_path)
|
self._add_file_to_archive(zout, clean_zinfo, full_path)
|
||||||
|
|
||||||
@ -216,11 +228,11 @@ class TarParser(ArchiveBasedAbstractParser):
|
|||||||
mimetypes = {'application/x-tar'}
|
mimetypes = {'application/x-tar'}
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
super().__init__(filename)
|
super().__init__(filename)
|
||||||
# yes, it's tarfile.TarFile.open and not tarfile.TarFile,
|
# yes, it's tarfile.open and not tarfile.TarFile,
|
||||||
# as stated in the documentation:
|
# as stated in the documentation:
|
||||||
# https://docs.python.org/3/library/tarfile.html#tarfile.TarFile
|
# https://docs.python.org/3/library/tarfile.html#tarfile.TarFile
|
||||||
# This is required to support compressed archives.
|
# This is required to support compressed archives.
|
||||||
self.archive_class = tarfile.TarFile.open
|
self.archive_class = tarfile.open
|
||||||
self.member_class = tarfile.TarInfo
|
self.member_class = tarfile.TarInfo
|
||||||
|
|
||||||
def is_archive_valid(self):
|
def is_archive_valid(self):
|
||||||
@ -239,7 +251,7 @@ class TarParser(ArchiveBasedAbstractParser):
|
|||||||
assert isinstance(member, tarfile.TarInfo) # please mypy
|
assert isinstance(member, tarfile.TarInfo) # please mypy
|
||||||
metadata = {}
|
metadata = {}
|
||||||
if member.mtime != 0:
|
if member.mtime != 0:
|
||||||
metadata['mtime'] = datetime.datetime.fromtimestamp(member.mtime)
|
metadata['mtime'] = str(datetime.datetime.fromtimestamp(member.mtime))
|
||||||
if member.uid != 0:
|
if member.uid != 0:
|
||||||
metadata['uid'] = str(member.uid)
|
metadata['uid'] = str(member.uid)
|
||||||
if member.gid != 0:
|
if member.gid != 0:
|
||||||
@ -267,6 +279,12 @@ class TarParser(ArchiveBasedAbstractParser):
|
|||||||
assert isinstance(member, tarfile.TarInfo) # please mypy
|
assert isinstance(member, tarfile.TarInfo) # please mypy
|
||||||
return member.name
|
return member.name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _set_member_permissions(member: ArchiveMember, permissions: int) -> ArchiveMember:
|
||||||
|
assert isinstance(member, tarfile.TarInfo) # please mypy
|
||||||
|
member.mode = permissions
|
||||||
|
return member
|
||||||
|
|
||||||
|
|
||||||
class TarGzParser(TarParser):
|
class TarGzParser(TarParser):
|
||||||
compression = ':gz'
|
compression = ':gz'
|
||||||
|
@ -293,7 +293,7 @@ class TestCorruptedFiles(unittest.TestCase):
|
|||||||
os.remove('./tests/data/clean.epub')
|
os.remove('./tests/data/clean.epub')
|
||||||
|
|
||||||
def test_tar(self):
|
def test_tar(self):
|
||||||
with tarfile.TarFile('./tests/data/clean.tar', 'w') as zout:
|
with tarfile.TarFile.open('./tests/data/clean.tar', 'w') as zout:
|
||||||
zout.add('./tests/data/dirty.flac')
|
zout.add('./tests/data/dirty.flac')
|
||||||
zout.add('./tests/data/dirty.docx')
|
zout.add('./tests/data/dirty.docx')
|
||||||
zout.add('./tests/data/dirty.jpg')
|
zout.add('./tests/data/dirty.jpg')
|
||||||
@ -302,6 +302,7 @@ class TestCorruptedFiles(unittest.TestCase):
|
|||||||
tarinfo.mtime = time.time()
|
tarinfo.mtime = time.time()
|
||||||
tarinfo.uid = 1337
|
tarinfo.uid = 1337
|
||||||
tarinfo.gid = 1338
|
tarinfo.gid = 1338
|
||||||
|
tarinfo.size = os.stat('./tests/data/dirty.png').st_size
|
||||||
with open('./tests/data/dirty.png', 'rb') as f:
|
with open('./tests/data/dirty.png', 'rb') as f:
|
||||||
zout.addfile(tarinfo, f)
|
zout.addfile(tarinfo, f)
|
||||||
p, mimetype = parser_factory.get_parser('./tests/data/clean.tar')
|
p, mimetype = parser_factory.get_parser('./tests/data/clean.tar')
|
||||||
@ -316,3 +317,26 @@ class TestCorruptedFiles(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
archive.TarParser('./tests/data/clean.tar')
|
archive.TarParser('./tests/data/clean.tar')
|
||||||
os.remove('./tests/data/clean.tar')
|
os.remove('./tests/data/clean.tar')
|
||||||
|
|
||||||
|
class TestReadOnlyArchiveMembers(unittest.TestCase):
|
||||||
|
def test_onlymember_tar(self):
|
||||||
|
with tarfile.open('./tests/data/clean.tar', 'w') as zout:
|
||||||
|
zout.add('./tests/data/dirty.png')
|
||||||
|
tarinfo = tarfile.TarInfo('./tests/data/dirty.jpg')
|
||||||
|
tarinfo.mtime = time.time()
|
||||||
|
tarinfo.uid = 1337
|
||||||
|
tarinfo.mode = 0o000
|
||||||
|
tarinfo.size = os.stat('./tests/data/dirty.jpg').st_size
|
||||||
|
with open('./tests/data/dirty.jpg', 'rb') as f:
|
||||||
|
zout.addfile(tarinfo=tarinfo, fileobj=f)
|
||||||
|
p, mimetype = parser_factory.get_parser('./tests/data/clean.tar')
|
||||||
|
self.assertEqual(mimetype, 'application/x-tar')
|
||||||
|
meta = p.get_meta()
|
||||||
|
self.assertEqual(meta['./tests/data/dirty.jpg']['uid'], '1337')
|
||||||
|
self.assertTrue(p.remove_all())
|
||||||
|
|
||||||
|
p = archive.TarParser('./tests/data/clean.cleaned.tar')
|
||||||
|
self.assertEqual(p.get_meta(), {})
|
||||||
|
os.remove('./tests/data/clean.tar')
|
||||||
|
os.remove('./tests/data/clean.cleaned.tar')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user