Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for digest API #136

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions libarchive/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):

Expand Down
63 changes: 52 additions & 11 deletions libarchive/ffi.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
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
import sysconfig

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')

Expand Down Expand Up @@ -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"
)