py-zippy 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_zippy-1.0.0.dist-info/METADATA +149 -0
- py_zippy-1.0.0.dist-info/RECORD +16 -0
- py_zippy-1.0.0.dist-info/WHEEL +5 -0
- py_zippy-1.0.0.dist-info/entry_points.txt +2 -0
- py_zippy-1.0.0.dist-info/licenses/LICENSE +21 -0
- py_zippy-1.0.0.dist-info/top_level.txt +1 -0
- zippy/__init__.py +8 -0
- zippy/cli.py +286 -0
- zippy/create.py +237 -0
- zippy/extract.py +128 -0
- zippy/list.py +68 -0
- zippy/lock.py +104 -0
- zippy/repair.py +243 -0
- zippy/test.py +153 -0
- zippy/unlock.py +120 -0
- zippy/utils.py +375 -0
zippy/repair.py
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
import bz2
|
2
|
+
import gzip
|
3
|
+
import lzma
|
4
|
+
import os
|
5
|
+
import shutil
|
6
|
+
import tarfile
|
7
|
+
import zipfile
|
8
|
+
|
9
|
+
try:
|
10
|
+
import pyzipper
|
11
|
+
except ImportError: # pragma: no cover - optional dependency
|
12
|
+
pyzipper = None
|
13
|
+
|
14
|
+
from .utils import (
|
15
|
+
Fore,
|
16
|
+
_salvage_extract_on_repair_fail,
|
17
|
+
_tar_salvage_extraction,
|
18
|
+
color_text,
|
19
|
+
get_archive_type,
|
20
|
+
get_logger,
|
21
|
+
handle_errors,
|
22
|
+
loading_animation,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
logger = get_logger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
def _open_zip_reader(path, password, verbose):
|
30
|
+
if password:
|
31
|
+
pwd_bytes = password.encode("utf-8")
|
32
|
+
if pyzipper:
|
33
|
+
zf = pyzipper.AESZipFile(path, "r")
|
34
|
+
zf.pwd = pwd_bytes
|
35
|
+
return zf
|
36
|
+
reader = zipfile.ZipFile(path, "r")
|
37
|
+
reader.setpassword(pwd_bytes)
|
38
|
+
return reader
|
39
|
+
return zipfile.ZipFile(path, "r")
|
40
|
+
|
41
|
+
|
42
|
+
def _open_zip_writer(path, password):
|
43
|
+
if password and pyzipper:
|
44
|
+
writer = pyzipper.AESZipFile(
|
45
|
+
path,
|
46
|
+
"w",
|
47
|
+
compression=zipfile.ZIP_DEFLATED,
|
48
|
+
encryption=pyzipper.WZ_AES,
|
49
|
+
)
|
50
|
+
writer.pwd = password.encode("utf-8")
|
51
|
+
writer.setencryption(pyzipper.WZ_AES, nbits=256)
|
52
|
+
return writer
|
53
|
+
return zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED)
|
54
|
+
|
55
|
+
|
56
|
+
def repair_archive(
|
57
|
+
archive_path,
|
58
|
+
verbose=False,
|
59
|
+
disable_animation=False,
|
60
|
+
repair_mode="remove_corrupted",
|
61
|
+
password=None,
|
62
|
+
):
|
63
|
+
"""
|
64
|
+
Repairs a corrupted archive.
|
65
|
+
|
66
|
+
Parameters:
|
67
|
+
- archive_path (str): Path to the archive file.
|
68
|
+
- verbose (bool): Enable verbose output for debugging.
|
69
|
+
- disable_animation (bool): Disable loading animation.
|
70
|
+
- repair_mode (str): Repair mode for ZIP archives (default: remove_corrupted).
|
71
|
+
|
72
|
+
Raises:
|
73
|
+
- ValueError: If the archive type is unsupported.
|
74
|
+
- RuntimeError: If repair fails due to various issues.
|
75
|
+
"""
|
76
|
+
archive_type = get_archive_type(archive_path)
|
77
|
+
supported = {
|
78
|
+
"zip",
|
79
|
+
"tar",
|
80
|
+
"tar.gz",
|
81
|
+
"tar.bz2",
|
82
|
+
"tar.xz",
|
83
|
+
"tar.lzma",
|
84
|
+
"gzip",
|
85
|
+
"bz2",
|
86
|
+
"xz",
|
87
|
+
"lzma",
|
88
|
+
}
|
89
|
+
if archive_type not in supported:
|
90
|
+
handle_errors(
|
91
|
+
"Repair operation is only supported for ZIP, TAR (gz/bz2/xz/lzma) and single-file gzip/bz2/xz/lzma archives at this time."
|
92
|
+
)
|
93
|
+
logger.warning(
|
94
|
+
color_text(
|
95
|
+
"[Experimental Feature] Archive repair is a complex process and may not always be successful.",
|
96
|
+
Fore.YELLOW if Fore else None,
|
97
|
+
)
|
98
|
+
)
|
99
|
+
logger.info("Repair mode: %s", repair_mode)
|
100
|
+
repair_attempted = False
|
101
|
+
try:
|
102
|
+
loading_animation(
|
103
|
+
f"Attempting to repair {os.path.basename(archive_path)}",
|
104
|
+
duration=3,
|
105
|
+
disable_animation=disable_animation,
|
106
|
+
)
|
107
|
+
if archive_type == "zip":
|
108
|
+
try:
|
109
|
+
with _open_zip_reader(archive_path, password, verbose) as zf:
|
110
|
+
bad_file = zf.testzip()
|
111
|
+
if bad_file:
|
112
|
+
logger.warning("Possible corruption detected in: %s", bad_file)
|
113
|
+
if repair_mode == "remove_corrupted":
|
114
|
+
repair_attempted = True
|
115
|
+
logger.info(
|
116
|
+
"Attempting to repair by removing corrupted file: %s",
|
117
|
+
bad_file,
|
118
|
+
)
|
119
|
+
temp_zip_path = archive_path + ".temp_repair.zip"
|
120
|
+
with _open_zip_writer(temp_zip_path, password) as temp_zf:
|
121
|
+
with _open_zip_reader(
|
122
|
+
archive_path, password, verbose
|
123
|
+
) as original_zf:
|
124
|
+
for item in original_zf.infolist():
|
125
|
+
if item.filename != bad_file:
|
126
|
+
try:
|
127
|
+
data = original_zf.read(item.filename)
|
128
|
+
temp_zf.writestr(item, data)
|
129
|
+
except Exception as e_read:
|
130
|
+
logger.warning(
|
131
|
+
"Could not copy %s. Error: %s",
|
132
|
+
item.filename,
|
133
|
+
e_read,
|
134
|
+
)
|
135
|
+
os.remove(archive_path)
|
136
|
+
os.rename(temp_zip_path, archive_path)
|
137
|
+
logger.info(
|
138
|
+
"Repair finished. Corrupted file '%s' removed. Repaired archive: %s",
|
139
|
+
bad_file,
|
140
|
+
archive_path,
|
141
|
+
)
|
142
|
+
elif repair_mode == "scan_only":
|
143
|
+
logger.info(
|
144
|
+
"Scan-only mode: corruption reported; no changes made."
|
145
|
+
)
|
146
|
+
else:
|
147
|
+
logger.warning(
|
148
|
+
"Unknown repair mode '%s'. No repair action taken.",
|
149
|
+
repair_mode,
|
150
|
+
)
|
151
|
+
else:
|
152
|
+
logger.info(
|
153
|
+
"Integrity check passed for %s. No major errors detected.",
|
154
|
+
archive_path,
|
155
|
+
)
|
156
|
+
except zipfile.BadZipFile as e:
|
157
|
+
logger.error("ZIP archive appears to be badly corrupted: %s", e)
|
158
|
+
logger.error("Specialised ZIP repair tools may be required.")
|
159
|
+
except RuntimeError as e:
|
160
|
+
if "password" in str(e).lower() or "encrypted" in str(e).lower():
|
161
|
+
handle_errors(
|
162
|
+
"Password is required to repair encrypted ZIP archives.",
|
163
|
+
verbose,
|
164
|
+
)
|
165
|
+
else:
|
166
|
+
handle_errors(f"Error during ZIP repair attempt: {e}", verbose)
|
167
|
+
except Exception as e:
|
168
|
+
handle_errors(f"Error during ZIP repair attempt: {e}", verbose)
|
169
|
+
elif archive_type.startswith("tar"):
|
170
|
+
repair_attempted = True
|
171
|
+
logger.info("Enhanced TAR repair: Attempting to extract readable files...")
|
172
|
+
extracted_files_dir = (
|
173
|
+
f"{os.path.basename(archive_path)}_extracted_during_repair"
|
174
|
+
)
|
175
|
+
os.makedirs(extracted_files_dir, exist_ok=True)
|
176
|
+
extracted_count = _tar_salvage_extraction(
|
177
|
+
archive_path, extracted_files_dir, verbose
|
178
|
+
)
|
179
|
+
if extracted_count > 0:
|
180
|
+
logger.info(
|
181
|
+
"Extracted %s files from TAR archive to: %s",
|
182
|
+
extracted_count,
|
183
|
+
extracted_files_dir,
|
184
|
+
)
|
185
|
+
logger.warning(
|
186
|
+
"Salvage operation only. Original archive may remain corrupted."
|
187
|
+
)
|
188
|
+
else:
|
189
|
+
logger.warning(
|
190
|
+
"No files could be extracted from the TAR archive. It may be severely corrupted."
|
191
|
+
)
|
192
|
+
elif archive_type in {"gzip", "bz2", "xz", "lzma"}:
|
193
|
+
repair_attempted = True
|
194
|
+
logger.info(
|
195
|
+
"%s repair: Attempting basic decompression to salvage content...",
|
196
|
+
archive_type.upper(),
|
197
|
+
)
|
198
|
+
output_file_name = (
|
199
|
+
f"{os.path.splitext(os.path.basename(archive_path))[0]}_recovered"
|
200
|
+
)
|
201
|
+
try:
|
202
|
+
if archive_type == "gzip":
|
203
|
+
opener = gzip.open
|
204
|
+
kwargs = {}
|
205
|
+
elif archive_type == "bz2":
|
206
|
+
opener = bz2.open
|
207
|
+
kwargs = {}
|
208
|
+
elif archive_type == "xz":
|
209
|
+
opener = lzma.open
|
210
|
+
kwargs = {"format": lzma.FORMAT_XZ}
|
211
|
+
else: # lzma
|
212
|
+
opener = lzma.open
|
213
|
+
kwargs = {"format": lzma.FORMAT_ALONE}
|
214
|
+
with opener(archive_path, "rb", **kwargs) as comp:
|
215
|
+
with open(output_file_name, "wb") as outfile:
|
216
|
+
shutil.copyfileobj(comp, outfile)
|
217
|
+
logger.info(
|
218
|
+
"Successfully decompressed content to: %s", output_file_name
|
219
|
+
)
|
220
|
+
except (gzip.BadGzipFile, OSError, lzma.LZMAError) as e:
|
221
|
+
logger.error(
|
222
|
+
"%s archive appears to be corrupted: %s", archive_type.upper(), e
|
223
|
+
)
|
224
|
+
logger.error("Specialised tools may be required for deeper recovery.")
|
225
|
+
except Exception as e:
|
226
|
+
handle_errors(
|
227
|
+
f"Error during {archive_type.upper()} repair attempt: {e}", verbose
|
228
|
+
)
|
229
|
+
logger.info("[Repair attempt finished. Results may vary.]")
|
230
|
+
if not repair_attempted and archive_type not in {"gzip", "bz2", "xz", "lzma"}:
|
231
|
+
logger.info(
|
232
|
+
"No repair action taken based on the integrity check (or in scan_only mode)."
|
233
|
+
)
|
234
|
+
if repair_attempted or archive_type in {"gzip", "bz2", "xz", "lzma"}:
|
235
|
+
salvage_output_dir_name = (
|
236
|
+
f"{os.path.basename(archive_path)}_salvaged_content"
|
237
|
+
)
|
238
|
+
_salvage_extract_on_repair_fail(
|
239
|
+
archive_path, salvage_output_dir_name, archive_type, verbose
|
240
|
+
)
|
241
|
+
logger.warning("It's recommended to have backups of important archives.")
|
242
|
+
except Exception as e:
|
243
|
+
handle_errors(f"Repair operation failed: {e}", verbose)
|
zippy/test.py
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
import bz2
|
2
|
+
import gzip
|
3
|
+
import lzma
|
4
|
+
import os
|
5
|
+
import tarfile
|
6
|
+
import zipfile
|
7
|
+
|
8
|
+
try:
|
9
|
+
import pyzipper
|
10
|
+
except ImportError: # pragma: no cover - optional dependency
|
11
|
+
pyzipper = None
|
12
|
+
|
13
|
+
from .utils import (
|
14
|
+
get_logger,
|
15
|
+
get_archive_type,
|
16
|
+
handle_errors,
|
17
|
+
is_single_file_type,
|
18
|
+
loading_animation,
|
19
|
+
requires_external_tool,
|
20
|
+
external_test,
|
21
|
+
tar_read_mode,
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
logger = get_logger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
def _test_zip_with_pyzipper(archive_path, password, verbose):
|
29
|
+
if not pyzipper:
|
30
|
+
handle_errors(
|
31
|
+
"Encrypted ZIP integrity checking requires the 'pyzipper' package.",
|
32
|
+
verbose,
|
33
|
+
)
|
34
|
+
if not password:
|
35
|
+
handle_errors("Password is required to test encrypted ZIP archives.", verbose)
|
36
|
+
with pyzipper.AESZipFile(archive_path, "r") as zf:
|
37
|
+
zf.pwd = password.encode("utf-8")
|
38
|
+
for info in zf.infolist():
|
39
|
+
if info.is_dir():
|
40
|
+
continue
|
41
|
+
zf.read(info.filename)
|
42
|
+
logger.info("Integrity test for %s: [OK] (AES ZIP)", archive_path)
|
43
|
+
|
44
|
+
|
45
|
+
def test_archive_integrity(
|
46
|
+
archive_path, verbose=False, disable_animation=False, password=None
|
47
|
+
):
|
48
|
+
"""
|
49
|
+
Tests the integrity of an archive.
|
50
|
+
|
51
|
+
Parameters:
|
52
|
+
- archive_path (str): Path to the archive file.
|
53
|
+
- verbose (bool): Enable verbose output for debugging.
|
54
|
+
- disable_animation (bool): Disable loading animation.
|
55
|
+
|
56
|
+
Raises:
|
57
|
+
- ValueError: If the archive type is unsupported.
|
58
|
+
- RuntimeError: If integrity test fails due to various issues.
|
59
|
+
"""
|
60
|
+
archive_type = get_archive_type(archive_path)
|
61
|
+
if not archive_type:
|
62
|
+
handle_errors(f"Unsupported archive type for: {archive_path}")
|
63
|
+
try:
|
64
|
+
loading_animation(
|
65
|
+
f"Testing integrity of {os.path.basename(archive_path)}",
|
66
|
+
duration=1,
|
67
|
+
disable_animation=disable_animation,
|
68
|
+
)
|
69
|
+
if archive_type == "zip":
|
70
|
+
try:
|
71
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
72
|
+
if password:
|
73
|
+
zf.setpassword(password.encode("utf-8"))
|
74
|
+
result = zf.testzip()
|
75
|
+
if result is None:
|
76
|
+
logger.info("Integrity test for %s: [OK]", archive_path)
|
77
|
+
else:
|
78
|
+
handle_errors(
|
79
|
+
f"Integrity test failed for {archive_path}. Corrupted file: {result}",
|
80
|
+
exit_code=2,
|
81
|
+
)
|
82
|
+
except RuntimeError as e:
|
83
|
+
lower = str(e).lower()
|
84
|
+
if (
|
85
|
+
"password" in lower
|
86
|
+
or "encrypted" in lower
|
87
|
+
or "compression method" in lower
|
88
|
+
):
|
89
|
+
_test_zip_with_pyzipper(archive_path, password, verbose)
|
90
|
+
else:
|
91
|
+
handle_errors(
|
92
|
+
f"Integrity test failed for {archive_path}. {e}", exit_code=2
|
93
|
+
)
|
94
|
+
except NotImplementedError:
|
95
|
+
_test_zip_with_pyzipper(archive_path, password, verbose)
|
96
|
+
elif archive_type.startswith("tar"):
|
97
|
+
try:
|
98
|
+
with tarfile.open(archive_path, tar_read_mode(archive_type)) as tf:
|
99
|
+
tf.getnames()
|
100
|
+
logger.info(
|
101
|
+
"Integrity test for %s: [OK] (Basic TAR check)", archive_path
|
102
|
+
)
|
103
|
+
except tarfile.ReadError as e:
|
104
|
+
handle_errors(
|
105
|
+
f"Integrity test failed for {archive_path}. Possible corruption: {e}",
|
106
|
+
exit_code=2,
|
107
|
+
)
|
108
|
+
elif is_single_file_type(archive_type):
|
109
|
+
try:
|
110
|
+
if archive_type == "gzip":
|
111
|
+
opener = gzip.open
|
112
|
+
kwargs = {}
|
113
|
+
error_type = gzip.BadGzipFile
|
114
|
+
elif archive_type == "bz2":
|
115
|
+
opener = bz2.open
|
116
|
+
kwargs = {}
|
117
|
+
error_type = OSError
|
118
|
+
elif archive_type == "xz":
|
119
|
+
opener = lzma.open
|
120
|
+
kwargs = {"format": lzma.FORMAT_XZ}
|
121
|
+
error_type = lzma.LZMAError
|
122
|
+
elif archive_type == "lzma":
|
123
|
+
opener = lzma.open
|
124
|
+
kwargs = {"format": lzma.FORMAT_ALONE}
|
125
|
+
error_type = lzma.LZMAError
|
126
|
+
else:
|
127
|
+
handle_errors(
|
128
|
+
f"Integrity test for {archive_type} not implemented.", verbose
|
129
|
+
)
|
130
|
+
return
|
131
|
+
with opener(archive_path, "rb", **kwargs) as stream:
|
132
|
+
stream.read(1024)
|
133
|
+
logger.info(
|
134
|
+
"Integrity test for %s: [OK] (Basic %s check)",
|
135
|
+
archive_path,
|
136
|
+
archive_type.upper(),
|
137
|
+
)
|
138
|
+
except error_type as e: # type: ignore[name-defined]
|
139
|
+
handle_errors(
|
140
|
+
f"Integrity test failed for {archive_path}. Possible corruption: {e}",
|
141
|
+
exit_code=2,
|
142
|
+
)
|
143
|
+
elif requires_external_tool(archive_type):
|
144
|
+
external_test(archive_path, verbose=verbose)
|
145
|
+
logger.info(
|
146
|
+
"Integrity test for %s: [OK] (External backend)", archive_path
|
147
|
+
)
|
148
|
+
else:
|
149
|
+
handle_errors(
|
150
|
+
f"Integrity test for {archive_type} not implemented.", verbose
|
151
|
+
)
|
152
|
+
except Exception as e:
|
153
|
+
handle_errors(f"Integrity test could not be performed: {e}", verbose)
|
zippy/unlock.py
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
import os
|
2
|
+
import zipfile
|
3
|
+
|
4
|
+
try:
|
5
|
+
import pyzipper
|
6
|
+
except ImportError: # pragma: no cover - optional but recommended dependency
|
7
|
+
pyzipper = None
|
8
|
+
|
9
|
+
from .utils import (
|
10
|
+
Fore,
|
11
|
+
color_text,
|
12
|
+
get_logger,
|
13
|
+
get_archive_type,
|
14
|
+
handle_errors,
|
15
|
+
loading_animation,
|
16
|
+
validate_path,
|
17
|
+
)
|
18
|
+
|
19
|
+
PASSWORD_DICT_DEFAULT = "password_list.txt"
|
20
|
+
|
21
|
+
|
22
|
+
logger = get_logger(__name__)
|
23
|
+
|
24
|
+
|
25
|
+
def unlock_archive(
|
26
|
+
archive_path,
|
27
|
+
dictionary_file=PASSWORD_DICT_DEFAULT,
|
28
|
+
password=None,
|
29
|
+
verbose=False,
|
30
|
+
disable_animation=False,
|
31
|
+
):
|
32
|
+
"""
|
33
|
+
Attempts to unlock a password-protected ZIP archive using a provided password or a dictionary attack.
|
34
|
+
|
35
|
+
Parameters:
|
36
|
+
- archive_path (str): Path to the archive file.
|
37
|
+
- dictionary_file (str): Path to the dictionary file containing possible passwords.
|
38
|
+
- password (str): Password for the archive (if known).
|
39
|
+
- verbose (bool): Enable verbose output for debugging.
|
40
|
+
- disable_animation (bool): Disable loading animation.
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
- ValueError: If the archive type is unsupported or no passwords are provided.
|
44
|
+
- RuntimeError: If unlocking fails due to incorrect password or other issues.
|
45
|
+
"""
|
46
|
+
archive_type = get_archive_type(archive_path)
|
47
|
+
if archive_type != "zip":
|
48
|
+
handle_errors(
|
49
|
+
"Unlock operation is only supported for ZIP archives at this time."
|
50
|
+
)
|
51
|
+
if not password and not dictionary_file:
|
52
|
+
handle_errors("Please provide a password or a dictionary file for unlocking.")
|
53
|
+
if dictionary_file:
|
54
|
+
dictionary_path = validate_path(
|
55
|
+
dictionary_file, "Dictionary file", must_exist=True, is_dir=False
|
56
|
+
)
|
57
|
+
if password:
|
58
|
+
passwords_to_try = [password]
|
59
|
+
elif dictionary_file:
|
60
|
+
try:
|
61
|
+
with open(dictionary_path, "r", encoding="utf-8", errors="ignore") as df:
|
62
|
+
passwords_to_try = [
|
63
|
+
line.strip()
|
64
|
+
for line in df
|
65
|
+
if line.strip() and not line.startswith("#")
|
66
|
+
]
|
67
|
+
except FileNotFoundError:
|
68
|
+
handle_errors(f"Dictionary file not found: {dictionary_path}")
|
69
|
+
except Exception as e:
|
70
|
+
handle_errors(f"Error reading dictionary file: {e}", verbose)
|
71
|
+
else:
|
72
|
+
handle_errors("No passwords to try for unlocking.")
|
73
|
+
try:
|
74
|
+
loading_animation(
|
75
|
+
f"Attempting to unlock {os.path.basename(archive_path)}",
|
76
|
+
duration=2,
|
77
|
+
disable_animation=disable_animation,
|
78
|
+
)
|
79
|
+
found_password = False
|
80
|
+
for pwd in passwords_to_try:
|
81
|
+
try_password = pwd.encode("utf-8", errors="ignore")
|
82
|
+
try:
|
83
|
+
if pyzipper:
|
84
|
+
with pyzipper.AESZipFile(archive_path, "r") as zf:
|
85
|
+
zf.pwd = try_password
|
86
|
+
zf.extractall()
|
87
|
+
else:
|
88
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
89
|
+
zf.extractall(pwd=try_password)
|
90
|
+
logger.info(
|
91
|
+
color_text(f"Password found: {pwd}", Fore.GREEN if Fore else None)
|
92
|
+
)
|
93
|
+
found_password = True
|
94
|
+
break
|
95
|
+
except RuntimeError as e:
|
96
|
+
if (
|
97
|
+
"bad password" in str(e).lower()
|
98
|
+
or "incorrect password" in str(e).lower()
|
99
|
+
):
|
100
|
+
if verbose:
|
101
|
+
logger.debug("Trying password '%s' - failed", pwd)
|
102
|
+
continue
|
103
|
+
if "requires AES" in str(e).lower() and not pyzipper:
|
104
|
+
handle_errors(
|
105
|
+
"Archive appears to use AES encryption. Install the 'pyzipper' package to unlock it.",
|
106
|
+
verbose,
|
107
|
+
)
|
108
|
+
handle_errors(f"Unlock attempt failed due to: {e}", verbose)
|
109
|
+
break
|
110
|
+
except (zipfile.BadZipFile, OSError) as e:
|
111
|
+
handle_errors(f"Unlock attempt failed unexpectedly: {e}", verbose)
|
112
|
+
break
|
113
|
+
if not found_password:
|
114
|
+
logger.warning("Password not found in the provided list.")
|
115
|
+
if dictionary_file:
|
116
|
+
logger.info("Tried passwords from dictionary: %s", dictionary_file)
|
117
|
+
if password:
|
118
|
+
logger.info("Explicit password tested: %s", password)
|
119
|
+
except Exception as e:
|
120
|
+
handle_errors(f"Password unlocking process failed: {e}", verbose)
|