diff --git a/libarchive/entry.py b/libarchive/entry.py index 70701ef..cdbf203 100644 --- a/libarchive/entry.py +++ b/libarchive/entry.py @@ -86,6 +86,12 @@ def modify(self, header_codec=None, **attributes): rdev (int | Tuple[int, int]): device number, if the file is a device rdevmajor (int): major part of the device number rdevminor (int): minor part of the device number + md5Digest (bytes): MD5 digest + rmd160Digest (bytes): RMD160 digest + sha1Digest (bytes): SHA1 digest + sha256Digest (bytes): SHA256 digest + sha384Digest (bytes): SHA384 digest + sha512Digest (bytes): SHA512 digest """ if header_codec: self.header_codec = header_codec @@ -433,6 +439,81 @@ def rdevminor(self): def rdevminor(self, value): ffi.entry_set_rdevminor(self._entry_p, value) + @property + def md5Digest(self): + return self._digest(ffi.ARCHIVE_ENTRY_DIGEST_MD5) + + @md5Digest.setter + def md5Digest(self, value): + self._set_digest(ffi.ARCHIVE_ENTRY_DIGEST_MD5, value) + + @property + def rmd160Digest(self): + return self._digest(ffi.ARCHIVE_ENTRY_DIGEST_RMD160) + + @rmd160Digest.setter + def rmd160Digest(self, value): + self._set_digest(ffi.ARCHIVE_ENTRY_DIGEST_RMD160, value) + + @property + def sha1Digest(self): + return self._digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA1) + + @sha1Digest.setter + def sha1Digest(self, value): + self._set_digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA1, value) + + @property + def sha256Digest(self): + return self._digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA256) + + @sha256Digest.setter + def sha256Digest(self, value): + self._set_digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA256, value) + + @property + def sha384Digest(self): + return self._digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA384) + + @sha384Digest.setter + def sha384Digest(self, value): + self._set_digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA384, value) + + @property + def sha512Digest(self): + return self._digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA512) + + @sha512Digest.setter + def sha512Digest(self, value): + self._set_digest(ffi.ARCHIVE_ENTRY_DIGEST_SHA512, value) + + def _digest(self, digestType): + try: + ptr = ffi.entry_digest(self._entry_p, digestType) + if ptr: + return bytes(ptr[:ffi._DIGEST_LENGTHS[digestType - 1]]) + except AttributeError: + raise NotImplementedError(f"the libarchive being used (version " + f"{ffi.version_number()} path " + f"{ffi.libarchive_path}) doesn't " + f"support read-only digest APIs") + return None + + def _set_digest(self, digestType, value): + try: + digestLen = ffi._DIGEST_LENGTHS[digestType - 1] + if len(value) != digestLen: + raise ValueError(f"Invalid input digest Expected {digestLen} " + f"bytes. Got {len(value)}.") + buffer = (digestLen * ffi.c_ubyte)(*value) + ffi.entry_set_digest(self._entry_p, digestType, buffer) + except AttributeError: + raise NotImplementedError(f"the libarchive being used (version " + f"{ffi.version_number()} path " + f"{ffi.libarchive_path}) doesn't support " + f"writable digest APIs") + return None + class ConsumedArchiveEntry(ArchiveEntry): diff --git a/libarchive/ffi.py b/libarchive/ffi.py index 1fc321a..1044b7c 100644 --- a/libarchive/ffi.py +++ b/libarchive/ffi.py @@ -1,15 +1,5 @@ -from ctypes import ( - c_char_p, c_int, c_uint, c_long, c_longlong, c_size_t, c_int64, - c_void_p, c_wchar_p, CFUNCTYPE, POINTER, -) - -try: - from ctypes import c_ssize_t -except ImportError: - from ctypes import c_longlong as c_ssize_t - -import ctypes from ctypes.util import find_library +import ctypes import logging import mmap import os @@ -17,6 +7,21 @@ from .exception import ArchiveError +c_char_p = ctypes.c_char_p +c_int = ctypes.c_int +c_uint = ctypes.c_uint +c_long = ctypes.c_long +c_longlong = ctypes.c_longlong +c_size_t = ctypes.c_size_t +c_int64, = ctypes.c_int64, +c_ubyte = ctypes.c_ubyte +c_void_p = ctypes.c_void_p +c_wchar_p = ctypes.c_wchar_p +CFUNCTYPE = ctypes.CFUNCTYPE +POINTER = ctypes.POINTER + +c_ubyte_p = POINTER(c_ubyte) +c_ssize_t = getattr(ctypes, 'c_ssize_t', c_longlong) logger = logging.getLogger('libarchive') @@ -362,3 +367,39 @@ def get_write_filter_function(filter_name): f"the libarchive being used (version {version_number()}, " f"path {libarchive_path}) doesn't support encryption" ) + +# archive digest API +try: + ffi('entry_digest', [c_archive_entry_p, c_int], c_ubyte_p) + + ARCHIVE_ENTRY_DIGEST_MD5 = 1 + ARCHIVE_ENTRY_DIGEST_RMD160 = 2 + ARCHIVE_ENTRY_DIGEST_SHA1 = 3 + ARCHIVE_ENTRY_DIGEST_SHA256 = 4 + ARCHIVE_ENTRY_DIGEST_SHA384 = 5 + ARCHIVE_ENTRY_DIGEST_SHA512 = 6 + + _DIGEST_LENGTHS = [ + 16, # MD5 + 20, # RMD160 + 20, # SHA1 + 32, # SHA256 + 48, # SHA384 + 64, # SHA512 + ] + +except AttributeError: + logger.info( + f"the libarchive being used (version {version_number()}, " + f"path {libarchive_path}) doesn't support read-only message digest API" + ) + +try: + ffi('entry_set_digest', + [ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_ubyte)], + ctypes.c_int) +except AttributeError: + logger.info( + f"the libarchive being used (version {version_number()}, " + f"path {libarchive_path}) doesn't support mutable message digest API" + )