From b5edae5aaca5e2d96d782d7a7bbe4f1d66e19fe6 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 30 Oct 2022 22:39:05 +0900 Subject: [PATCH 01/11] Add symbolic link options - Add enable_symlink boolean option for read, extractall, and extract - Default to be True. It will be False in future. - Add dereference boolean option for write and writeall - SevenZipFile constructor option dereference is marked deprecated and will be removed in future version Signed-off-by: Hiroshi Miura --- py7zr/py7zr.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index e17b786e..449e727d 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -533,6 +533,7 @@ def _extract( targets: Optional[List[str]] = None, return_dict: bool = False, callback: Optional[ExtractCallback] = None, + enable_symlink: bool = False, ) -> Optional[Dict[str, IO[Any]]]: if callback is None: pass @@ -598,7 +599,12 @@ def _extract( elif f.is_socket: pass # TODO: implement me. elif f.is_symlink or f.is_junction: - self.worker.register_filelike(f.id, outfilename) + if enable_symlink: + self.worker.register_filelike(f.id, outfilename) + else: + # Archive has symlink or junction + # this has security consequences. + raise ValueError("Archive has symbolic link that is not explicitly enabled.") else: self.worker.register_filelike(f.id, outfilename) target_files.append((outfilename, f.file_properties())) @@ -701,9 +707,9 @@ def _write_header(self): self.sig_header.calccrc(header_len, header_crc) self.sig_header.write(self.fp) - def _writeall(self, path, arcname): + def _writeall(self, path, arcname, dereference: bool = False): try: - if path.is_symlink() and not self.dereference: + if path.is_symlink() and not dereference: self.write(path, arcname) elif path.is_file(): self.write(path, arcname) @@ -712,14 +718,14 @@ def _writeall(self, path, arcname): self.write(path, arcname) for nm in sorted(os.listdir(str(path))): arc = os.path.join(arcname, nm) if arcname is not None else None - self._writeall(path.joinpath(nm), arc) + self._writeall(path.joinpath(nm), arc, dereference=dereference) else: return # pathlib ignores ELOOP and return False for is_*(). except OSError as ose: - if self.dereference and ose.errno in [errno.ELOOP]: + if dereference and ose.errno in [errno.ELOOP]: return # ignore ELOOP here, this resulted to stop looped symlink reference. - elif self.dereference and sys.platform == "win32" and ose.errno in [errno.ENOENT]: - return # ignore ENOENT which is happened when a case of ELOOP on windows. + elif dereference and sys.platform == "win32" and ose.errno in [errno.ENOENT]: + return # ignore ENOENT which is happened when a case of ELOOP on Windows. else: raise @@ -954,20 +960,22 @@ def readall(self) -> Optional[Dict[str, IO[Any]]]: self._dict = {} return self._extract(path=None, return_dict=True) - def extractall(self, path: Optional[Any] = None, callback: Optional[ExtractCallback] = None) -> None: + def extractall( + self, path: Optional[Any] = None, callback: Optional[ExtractCallback] = None, enable_symlink: bool = True + ) -> None: """Extract all members from the archive to the current working directory and set owner, modification time and permissions on directories afterwards. ``path`` specifies a different directory to extract to. """ - self._extract(path=path, return_dict=False, callback=callback) + self._extract(path=path, return_dict=False, callback=callback, enable_symlink=enable_symlink) def read(self, targets: Optional[List[str]] = None) -> Optional[Dict[str, IO[Any]]]: self._dict = {} return self._extract(path=None, targets=targets, return_dict=True) - def extract(self, path: Optional[Any] = None, targets: Optional[List[str]] = None) -> None: - self._extract(path, targets, return_dict=False) + def extract(self, path: Optional[Any] = None, targets: Optional[List[str]] = None, enable_symlink: bool = True) -> None: + self._extract(path, targets, return_dict=False, enable_symlink=enable_symlink) def reporter(self, callback: ExtractCallback): while True: @@ -992,18 +1000,18 @@ def reporter(self, callback: ExtractCallback): pass self.q.task_done() - def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None): + def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = False): """Write files in target path into archive.""" if isinstance(path, str): path = pathlib.Path(path) if not path.exists(): raise ValueError("specified path does not exist.") if path.is_dir() or path.is_file(): - self._writeall(path, arcname) + self._writeall(path, arcname, dereference or self.dereference) else: raise ValueError("specified path is not a directory or a file") - def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None): + def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = False): """Write single target file into archive.""" if isinstance(file, str): path = pathlib.Path(file) @@ -1012,11 +1020,11 @@ def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None): else: raise ValueError("Unsupported file type.") folder = self.header.initialize() - file_info = self._make_file_info(path, arcname, self.dereference) + file_info = self._make_file_info(path, arcname, dereference or self.dereference) self.header.files_info.files.append(file_info) self.header.files_info.emptyfiles.append(file_info["emptystream"]) self.files.append(file_info) - self.worker.archive(self.fp, self.files, folder, deref=self.dereference) + self.worker.archive(self.fp, self.files, folder, deref=dereference or self.dereference) def writed(self, targets: Dict[str, IO[Any]]) -> None: for target, input in targets.items(): From 16a307860d41626a446d8cafa52094c4c6ea916c Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 13 Oct 2024 16:29:49 +0900 Subject: [PATCH 02/11] Apply suggestions from code review Use SevenZipFile argument dereference as default for extract methods --- py7zr/py7zr.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 05193f92..02147c6c 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -719,7 +719,7 @@ def _write_header(self): self.sig_header.calccrc(header_len, header_crc) self.sig_header.write(self.fp) - def _writeall(self, path, arcname, dereference: bool = False): + def _writeall(self, path, arcname, dereference: bool = self.dereference): try: if path.is_symlink() and not dereference: self.write(path, arcname) @@ -1082,18 +1082,18 @@ def reporter(self, callback: ExtractCallback): pass self.q.task_done() - def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = False): + def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = self.dereference): """Write files in target path into archive.""" if isinstance(path, str): path = pathlib.Path(path) if not path.exists(): raise ValueError("specified path does not exist.") if path.is_dir() or path.is_file(): - self._writeall(path, arcname, dereference or self.dereference) + self._writeall(path, arcname, dereference) else: raise ValueError("specified path is not a directory or a file") - def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = False): + def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = self.dereference): """Write single target file into archive.""" if not isinstance(file, str) and not isinstance(file, pathlib.Path): raise ValueError("Unsupported file type.") @@ -1110,7 +1110,7 @@ def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, d self.header.files_info.files.append(file_info) self.header.files_info.emptyfiles.append(file_info["emptystream"]) self.files.append(file_info) - self.worker.archive(self.fp, self.files, folder, deref=dereference or self.dereference) + self.worker.archive(self.fp, self.files, folder, deref=dereference) def writed(self, targets: dict[str, IO[Any]]) -> None: for target, input in targets.items(): From 7619ddaf7c8052bd50badbba2bd3e24f1918ce22 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 13 Oct 2024 16:30:10 +0900 Subject: [PATCH 03/11] Update py7zr/py7zr.py --- py7zr/py7zr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 02147c6c..b42c7a00 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -1106,7 +1106,7 @@ def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, d else: path = file folder = self.header.initialize() - file_info = self._make_file_info(path, arcname, dereference or self.dereference) + file_info = self._make_file_info(path, arcname, dereference) self.header.files_info.files.append(file_info) self.header.files_info.emptyfiles.append(file_info["emptystream"]) self.files.append(file_info) From 043a51daf9e4376aec675e02a92ee65e4a863780 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Wed, 26 Feb 2025 18:27:08 +0900 Subject: [PATCH 04/11] style: handling of dereference option in write and writeall methods - use SevenZipFile constructor dereference option as default --- py7zr/py7zr.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index e185c5d6..ec4c4bf5 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -74,6 +74,7 @@ FILE_ATTRIBUTE_UNIX_EXTENSION = 0x8000 FILE_ATTRIBUTE_WINDOWS_MASK = 0x07FFF + class ArchiveFile: """Represent each files metadata inside archive file. It holds file properties; filename, permissions, and type whether @@ -331,7 +332,7 @@ def __init__( mode: str = "r", *, filters: Optional[list[dict[str, int]]] = None, - dereference=False, + dereference: bool = False, password: Optional[str] = None, header_encryption: bool = False, blocksize: Optional[int] = None, @@ -711,7 +712,7 @@ def _write_header(self): self.sig_header.calccrc(header_len, header_crc) self.sig_header.write(self.fp) - def _writeall(self, path, arcname, dereference: bool = self.dereference): + def _writeall(self, path, arcname, dereference): try: if path.is_symlink() and not dereference: self.write(path, arcname) @@ -820,7 +821,7 @@ def _var_release(self): del self.sig_header @staticmethod - def _make_file_info(target: pathlib.Path, arcname: Optional[str] = None, dereference=False) -> dict[str, Any]: + def _make_file_info(target: pathlib.Path, arcname: Optional[str] = None, dereference: bool = False) -> dict[str, Any]: f: dict[str, Any] = {} f["origin"] = target if arcname is not None: @@ -1020,7 +1021,7 @@ def extractall( *, callback: Optional[ExtractCallback] = None, factory: Optional[WriterFactory] = None, - enable_symlink: bool = True + enable_symlink: bool = True, ) -> None: """Extract all members from the archive to the current working directory and set owner, modification time and permissions on @@ -1045,7 +1046,9 @@ def extract( # This also matches the behavior of TarFile if targets is not None: targets = [remove_trailing_slash(target) for target in targets] - self._extract(path, targets, recursive=recursive, callback=callback, writer_factory=factory, enable_symlink=enable_symlink) + self._extract( + path, targets, recursive=recursive, callback=callback, writer_factory=factory, enable_symlink=enable_symlink + ) def reporter(self, callback: ExtractCallback): while True: @@ -1072,18 +1075,20 @@ def reporter(self, callback: ExtractCallback): pass self.q.task_done() - def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = self.dereference): + def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None): """Write files in target path into archive.""" if isinstance(path, str): path = pathlib.Path(path) if not path.exists(): raise ValueError("specified path does not exist.") + if dereference is None: + dereference = self.dereference if path.is_dir() or path.is_file(): self._writeall(path, arcname, dereference) else: raise ValueError("specified path is not a directory or a file") - def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: bool = self.dereference): + def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None): """Write single target file into archive.""" if not isinstance(file, str) and not isinstance(file, pathlib.Path): raise ValueError("Unsupported file type.") @@ -1096,11 +1101,13 @@ def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, d else: path = file folder = self.header.initialize() + if dereference is None: + dereference = self.dereference file_info = self._make_file_info(path, arcname, dereference) self.header.files_info.files.append(file_info) self.header.files_info.emptyfiles.append(file_info["emptystream"]) self.files.append(file_info) - self.worker.archive(self.fp, self.files, folder, deref=dereference) + self.worker.archive(self.fp, self.files, folder, dereference) def writef(self, bio: IO[Any], arcname: str): if not check_archive_path(arcname): @@ -1131,7 +1138,7 @@ def _writef(self, bio: IO[Any], arcname: str): self.header.files_info.files.append(file_info) self.header.files_info.emptyfiles.append(file_info["emptystream"]) self.files.append(file_info) - self.worker.archive(self.fp, self.files, folder, deref=False) + self.worker.archive(self.fp, self.files, folder, False) else: file_info = self._make_file_info_from_name(bio, size, arcname) self.header.files_info.files.append(file_info) @@ -1585,7 +1592,7 @@ def flush_archive(self, fp, folder): self.header.main_streams.packinfo.packsizes.append(compressor.packsize) folder.unpacksizes = compressor.unpacksizes - def archive(self, fp: BinaryIO, files, folder, deref=False): + def archive(self, fp: BinaryIO, files, folder, dereference): """Run archive task for specified 7zip folder.""" f = files[self.current_file_index] if f.has_strdata(): @@ -1593,8 +1600,8 @@ def archive(self, fp: BinaryIO, files, folder, deref=False): self.header.files_info.files[self.current_file_index]["maxsize"] = foutsize self.header.files_info.files[self.current_file_index]["digest"] = crc self.last_file_index = self.current_file_index - elif (f.is_symlink and not deref) or not f.emptystream: - foutsize, crc = self.write(fp, f, (f.is_symlink and not deref), folder) + elif (f.is_symlink and not dereference) or not f.emptystream: + foutsize, crc = self.write(fp, f, (f.is_symlink and not dereference), folder) self.header.files_info.files[self.current_file_index]["maxsize"] = foutsize self.header.files_info.files[self.current_file_index]["digest"] = crc self.last_file_index = self.current_file_index From 47f446a6f8e60e5f93d8d24e3cdbf5c2d9b86ac0 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 01:22:09 +0900 Subject: [PATCH 05/11] type: fix error --- py7zr/py7zr.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index ec4c4bf5..94809237 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -386,7 +386,7 @@ def __init__( elif isinstance(file, io.IOBase): self._filePassed = True self.fp = file - self.filename = getattr(file, "name", None) + self.filename = getattr(file, "name", None) # type: ignore self.mode = mode # noqa else: raise TypeError(f"invalid file: {type(file)}") @@ -712,7 +712,7 @@ def _write_header(self): self.sig_header.calccrc(header_len, header_crc) self.sig_header.write(self.fp) - def _writeall(self, path, arcname, dereference): + def _writeall(self, path: pathlib.Path, arcname: Optional[str], dereference: bool): try: if path.is_symlink() and not dereference: self.write(path, arcname) @@ -723,7 +723,7 @@ def _writeall(self, path, arcname, dereference): self.write(path, arcname) for nm in sorted(os.listdir(str(path))): arc = os.path.join(arcname, nm) if arcname is not None else None - self._writeall(path.joinpath(nm), arc, dereference=dereference) + self._writeall(path.joinpath(nm), arc, dereference) else: return # pathlib ignores ELOOP and return False for is_*(). except OSError as ose: @@ -1075,7 +1075,7 @@ def reporter(self, callback: ExtractCallback): pass self.q.task_done() - def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None): + def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None) -> None: """Write files in target path into archive.""" if isinstance(path, str): path = pathlib.Path(path) @@ -1088,7 +1088,7 @@ def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None else: raise ValueError("specified path is not a directory or a file") - def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None): + def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None) -> None: """Write single target file into archive.""" if not isinstance(file, str) and not isinstance(file, pathlib.Path): raise ValueError("Unsupported file type.") From 26f012b1c6dd522737e9cb140fb5607ed5f17131 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 01:26:49 +0900 Subject: [PATCH 06/11] type: fix error --- py7zr/cli.py | 5 ----- py7zr/py7zr.py | 10 +++++++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/py7zr/cli.py b/py7zr/cli.py index 71ed9eba..29db092b 100644 --- a/py7zr/cli.py +++ b/py7zr/cli.py @@ -31,7 +31,6 @@ from lzma import CHECK_CRC64, CHECK_SHA256, is_check_supported from typing import Any, Optional -import _lzma # type: ignore import multivolumefile import texttable # type: ignore @@ -347,8 +346,6 @@ def run_extract(self, args: argparse.Namespace) -> int: else: print("The archive is corrupted, or password is wrong. ABORT.") return 1 - except _lzma.LZMAError: - return 1 cb = None # Optional[ExtractCallback] if verbose: @@ -374,8 +371,6 @@ def run_extract(self, args: argparse.Namespace) -> int: else: print("The archive is corrupted, or password is wrong. ABORT.") return 1 - except _lzma.LZMAError: - return 1 else: return 0 diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 94809237..cb253b3c 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -712,7 +712,7 @@ def _write_header(self): self.sig_header.calccrc(header_len, header_crc) self.sig_header.write(self.fp) - def _writeall(self, path: pathlib.Path, arcname: Optional[str], dereference: bool): + def _writeall(self, path: pathlib.Path, arcname: Optional[str], dereference: bool) -> None: try: if path.is_symlink() and not dereference: self.write(path, arcname) @@ -1075,7 +1075,9 @@ def reporter(self, callback: ExtractCallback): pass self.q.task_done() - def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None) -> None: + def writeall( + self, path: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None + ) -> None: """Write files in target path into archive.""" if isinstance(path, str): path = pathlib.Path(path) @@ -1088,7 +1090,9 @@ def writeall(self, path: Union[pathlib.Path, str], arcname: Optional[str] = None else: raise ValueError("specified path is not a directory or a file") - def write(self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None) -> None: + def write( + self, file: Union[pathlib.Path, str], arcname: Optional[str] = None, dereference: Optional[bool] = None + ) -> None: """Write single target file into archive.""" if not isinstance(file, str) and not isinstance(file, pathlib.Path): raise ValueError("Unsupported file type.") From a13cdb90db54f81d5e4024adbf7ad524690cded9 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 08:25:47 +0900 Subject: [PATCH 07/11] type: add more hints --- py7zr/py7zr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index cb253b3c..1078319f 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -1575,12 +1575,12 @@ def write(self, fp: BinaryIO, f, assym, folder): insize, foutsize, crc = compressor.compress(fd, fp) return self._after_write(insize, foutsize, crc) - def writestr(self, fp: BinaryIO, f, folder): + def writestr(self, fp: BinaryIO, f: ArchiveFile, folder: Folder): compressor = folder.get_compressor() insize, foutsize, crc = compressor.compress(f.data(), fp) return self._after_write(insize, foutsize, crc) - def flush_archive(self, fp, folder): + def flush_archive(self, fp: BinaryIO, folder: Folder): compressor = folder.get_compressor() foutsize = compressor.flush(fp) if len(self.files) > 0: @@ -1596,7 +1596,7 @@ def flush_archive(self, fp, folder): self.header.main_streams.packinfo.packsizes.append(compressor.packsize) folder.unpacksizes = compressor.unpacksizes - def archive(self, fp: BinaryIO, files, folder, dereference): + def archive(self, fp: BinaryIO, files: ArchiveFileList, folder: Folder, dereference: bool) -> None: """Run archive task for specified 7zip folder.""" f = files[self.current_file_index] if f.has_strdata(): From 039ad6a70c00d18069a7681d41dcab2fc644f704 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 09:06:27 +0900 Subject: [PATCH 08/11] type: add more hints --- py7zr/compressor.py | 6 +++--- py7zr/py7zr.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/py7zr/compressor.py b/py7zr/compressor.py index 852bb608..4832e733 100644 --- a/py7zr/compressor.py +++ b/py7zr/compressor.py @@ -890,10 +890,10 @@ def _set_alternate_compressors_coders(self, alt_filter, password=None): }, ) - def compress(self, fd, fp, crc=0): + def compress(self, fd, fp, crc: int = 0): data = fd.read(self._block_size) - insize = len(data) - foutsize = 0 + insize: int = len(data) + foutsize: int = 0 while data: crc = calculate_crc32(data, crc) for i, compressor in enumerate(self.chain): diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 1078319f..6a85d377 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -40,7 +40,7 @@ from multiprocessing import Process from shutil import ReadError from threading import Thread -from typing import IO, Any, BinaryIO, Optional, Union +from typing import IO, Any, BinaryIO, Optional, Tuple, Union import multivolumefile @@ -1549,7 +1549,7 @@ def _find_link_target(self, target): member = linkname return member - def _after_write(self, insize, foutsize, crc): + def _after_write(self, insize: int, foutsize: int, crc: int) -> Tuple[int, int]: self.header.main_streams.substreamsinfo.digestsdefined.append(True) self.header.main_streams.substreamsinfo.digests.append(crc) if self.header.main_streams.substreamsinfo.unpacksizes is None: @@ -1575,12 +1575,12 @@ def write(self, fp: BinaryIO, f, assym, folder): insize, foutsize, crc = compressor.compress(fd, fp) return self._after_write(insize, foutsize, crc) - def writestr(self, fp: BinaryIO, f: ArchiveFile, folder: Folder): + def writestr(self, fp: BinaryIO, f: ArchiveFile, folder: Folder) -> Tuple[int, int]: compressor = folder.get_compressor() insize, foutsize, crc = compressor.compress(f.data(), fp) return self._after_write(insize, foutsize, crc) - def flush_archive(self, fp: BinaryIO, folder: Folder): + def flush_archive(self, fp: BinaryIO, folder: Folder) -> None: compressor = folder.get_compressor() foutsize = compressor.flush(fp) if len(self.files) > 0: From 9074829a7fd7e0e6539afa580bb23c24e62c2a98 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 09:50:46 +0900 Subject: [PATCH 09/11] type: add more hints and update copyright - Fix a case when given fd is None for Worker.writestr --- py7zr/compressor.py | 6 +++--- py7zr/py7zr.py | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/py7zr/compressor.py b/py7zr/compressor.py index 4832e733..020c91bd 100644 --- a/py7zr/compressor.py +++ b/py7zr/compressor.py @@ -2,7 +2,7 @@ # # p7zr library # -# Copyright (c) 2019-2023 Hiroshi Miura +# Copyright (c) 2019-2025 Hiroshi Miura # Copyright (c) 2004-2015 by Joachim Bauch, mail@joachim-bauch.de # 7-Zip Copyright (C) 1999-2010 Igor Pavlov # LZMA SDK Copyright (C) 1999-2010 Igor Pavlov @@ -28,7 +28,7 @@ import zlib from abc import ABC, abstractmethod from enum import Enum -from typing import Any, Optional, Union +from typing import Any, Optional, Tuple, Union, IO, BinaryIO import bcj import inflate64 @@ -890,7 +890,7 @@ def _set_alternate_compressors_coders(self, alt_filter, password=None): }, ) - def compress(self, fd, fp, crc: int = 0): + def compress(self, fd: BinaryIO, fp: IO, crc: int = 0) -> Tuple[int, int, int]: data = fd.read(self._block_size) insize: int = len(data) foutsize: int = 0 diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index 6a85d377..b4ee5bd1 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -2,7 +2,7 @@ # # p7zr library # -# Copyright (c) 2019-2024 Hiroshi Miura +# Copyright (c) 2019-2025 Hiroshi Miura # Copyright (c) 2004-2015 by Joachim Bauch, mail@joachim-bauch.de # 7-Zip Copyright (C) 1999-2010 Igor Pavlov # LZMA SDK Copyright (C) 1999-2010 Igor Pavlov @@ -1562,7 +1562,7 @@ def _after_write(self, insize: int, foutsize: int, crc: int) -> Tuple[int, int]: self.header.main_streams.substreamsinfo.num_unpackstreams_folders[-1] += 1 return foutsize, crc - def write(self, fp: BinaryIO, f, assym, folder): + def write(self, fp: BinaryIO, f, assym, folder: Folder) -> Tuple[int, int]: compressor = folder.get_compressor() if assym: link_target: str = self._find_link_target(f.origin) @@ -1577,7 +1577,10 @@ def write(self, fp: BinaryIO, f, assym, folder): def writestr(self, fp: BinaryIO, f: ArchiveFile, folder: Folder) -> Tuple[int, int]: compressor = folder.get_compressor() - insize, foutsize, crc = compressor.compress(f.data(), fp) + fd: Optional[BinaryIO] = f.data() + if fd is None: + return 0, 0 + insize, foutsize, crc = compressor.compress(fd, fp) return self._after_write(insize, foutsize, crc) def flush_archive(self, fp: BinaryIO, folder: Folder) -> None: From 1d764c4200324bf7d7979033f6c3aa25357efc00 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 10:08:54 +0900 Subject: [PATCH 10/11] type: add more hints --- py7zr/compressor.py | 2 +- py7zr/py7zr.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/py7zr/compressor.py b/py7zr/compressor.py index 020c91bd..94b78c13 100644 --- a/py7zr/compressor.py +++ b/py7zr/compressor.py @@ -890,7 +890,7 @@ def _set_alternate_compressors_coders(self, alt_filter, password=None): }, ) - def compress(self, fd: BinaryIO, fp: IO, crc: int = 0) -> Tuple[int, int, int]: + def compress(self, fd: BinaryIO, fp: BinaryIO, crc: int = 0) -> Tuple[int, int, int]: data = fd.read(self._block_size) insize: int = len(data) foutsize: int = 0 diff --git a/py7zr/py7zr.py b/py7zr/py7zr.py index b4ee5bd1..d7fefa76 100644 --- a/py7zr/py7zr.py +++ b/py7zr/py7zr.py @@ -1562,12 +1562,12 @@ def _after_write(self, insize: int, foutsize: int, crc: int) -> Tuple[int, int]: self.header.main_streams.substreamsinfo.num_unpackstreams_folders[-1] += 1 return foutsize, crc - def write(self, fp: BinaryIO, f, assym, folder: Folder) -> Tuple[int, int]: + def write(self, fp: BinaryIO, f: ArchiveFile, assym: bool, folder: Folder) -> Tuple[int, int]: compressor = folder.get_compressor() if assym: link_target: str = self._find_link_target(f.origin) tgt: bytes = link_target.encode("utf-8") - fd = io.BytesIO(tgt) + fd: BinaryIO = io.BytesIO(tgt) insize, foutsize, crc = compressor.compress(fd, fp) fd.close() else: From 302366090d518cc54b4adec254d7dea8b26785f8 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 27 Feb 2025 10:18:06 +0900 Subject: [PATCH 11/11] style: tweak import order --- py7zr/compressor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py7zr/compressor.py b/py7zr/compressor.py index 94b78c13..6ba4e956 100644 --- a/py7zr/compressor.py +++ b/py7zr/compressor.py @@ -28,7 +28,7 @@ import zlib from abc import ABC, abstractmethod from enum import Enum -from typing import Any, Optional, Tuple, Union, IO, BinaryIO +from typing import Any, BinaryIO, Optional, Tuple, Union import bcj import inflate64