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.
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-zippy
3
+ Version: 1.0.0
4
+ Summary: Archive Utility Toolkit supporting multi-format workflows, password recovery, and repair helpers.
5
+ Author-email: John Hauger Mitander <john@on1.no>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 John H. Mitander
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/John0n1/ZIPPY
29
+ Project-URL: Documentation, https://github.com/John0n1/ZIPPY#readme
30
+ Project-URL: Issues, https://github.com/John0n1/ZIPPY/issues
31
+ Keywords: archives,zip,tar,cli,toolkit
32
+ Classifier: Development Status :: 5 - Production/Stable
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: System Administrators
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Topic :: System :: Archiving
44
+ Classifier: Topic :: System :: Archiving :: Compression
45
+ Classifier: Topic :: Utilities
46
+ Requires-Python: >=3.9
47
+ Description-Content-Type: text/markdown
48
+ License-File: LICENSE
49
+ Requires-Dist: colorama>=0.4.6
50
+ Requires-Dist: python-dotenv>=1.0
51
+ Requires-Dist: pyzipper>=0.3.6
52
+ Requires-Dist: patool>=1.12
53
+ Dynamic: license-file
54
+
55
+ <div align="center">
56
+
57
+ # Zippy
58
+
59
+ **Multi-purpose archive extraction, repair and brute-force toolkit.**
60
+
61
+ </div>
62
+
63
+ ![PyPI](https://img.shields.io/pypi/v/zippy-toolkit?color=blue&logo=pypi&style=flat-square) ![Python](https://img.shields.io/pypi/pyversions/zippy-toolkit?color=blue&logo=python&style=flat-square) ![License](https://img.shields.io/pypi/l/zippy-toolkit?color=blue&logo=apache&style=flat-square) ![Build](https://img.shields.io/github/actions/workflow/status/John0n1/ZIPPY/ci.yml?branch=main&label=build&logo=github&style=flat-square) ![Coverage](https://img.shields.io/codecov/c/github/John0n1/ZIPPY/main?logo=codecov&style=flat-square)
64
+
65
+ ```bash
66
+
67
+ - **Broad format coverage** - (`tar`, `tar.gz`, `tar.bz2`, `tar.xz`, `tar.lzma`), (`gzip`, `bz2`, `xz`, `lzma`), `7z`, `rar`, `zstd` and ZIP family (including APK/JAR/WAR/IPA/EAR).
68
+ - **Consistent UX** - consistent logging, progress indicators, and tab completion for every command.
69
+ - **Security tooling** - create or re-lock encrypted ZIPs, run smart dictionary attacks with the bundled password list, and capture verbose traces when needed.
70
+ - **Integrity & repair** - repair, attempt salvage extractions, and perform best-effort recovery for TAR and single-file formats. It grabs whatever it can, even if files look broken or corrupted. This is achieved by scanning the archive for valid headers and footers, and extracting the data in between, then rebuilding the file structure using advanced data pointers and heuristics.
71
+
72
+ ## Installation
73
+
74
+ ### From PyPI (recommended)
75
+
76
+ ```bash
77
+ python -m pip install zippy
78
+ ```
79
+
80
+ ### From source
81
+
82
+ ```bash
83
+ git clone https://github.com/John0n1/ZIPPY.git
84
+ cd ZIPPY
85
+ python -m pip install .
86
+
87
+ ### Debian package
88
+
89
+ ```bash
90
+ sudo apt install ./zippy_*.deb
91
+ ```
92
+
93
+ ## Quick start
94
+
95
+ ```bash
96
+ # Extract an archive
97
+ zippy --extract backups/site.tar.xz -o ./site
98
+
99
+ # Create a password-protected ZIP from multiple paths
100
+ zippy --lock secure.zip -f docs,images -p "Tru5ted!"
101
+
102
+ # List TAR.BZ2 contents
103
+ zippy --list datasets.tar.bz2
104
+
105
+ # Attempt unlocking with the bundled wordlist
106
+ zippy --unlock encrypted.zip -d password_list.txt --verbose
107
+
108
+ # Run salvage repair on a damaged tarball
109
+ zippy --repair broken.tar.gz --repair-mode remove_corrupted
110
+ ```
111
+
112
+ Run `zippy --help` for the full command reference or `zippy --version` to confirm the installed release.
113
+
114
+ ## Supported archive formats
115
+
116
+ | Family | Types |
117
+ | ------ | ----- |
118
+ | ZIP | `zip` (with optional password protection) |
119
+ | TAR | `tar`, `tar.gz`, `tar.bz2`, `tar.xz`, `tar.lzma`, `.tgz`, `.tbz`, `.txz`, `.tlz` |
120
+ | Single-file | `gzip` (`.gz`), `bz2` (`.bz2`), `xz` (`.xz`), `lzma` (`.lzma`) |
121
+
122
+ Unsupported formats (e.g. 7z, rar, zstd) will raise a friendly error instructing you to choose a supported type.
123
+
124
+ ## Configuration & automation
125
+
126
+ - Use `--save-config <file>` to capture the current flag set (including passwords or dictionary paths if provided).
127
+ - Rehydrate saved flags via `--load-config <file>` for repeatable batch jobs.
128
+ - Disable animations with `--no-animation` for CI environments.
129
+
130
+ ## Logging & colours
131
+
132
+ Logging defaults to concise `INFO` output. Add `--verbose` for `DEBUG` traces. Colour output automatically downgrades in non-interactive terminals, while animations fall back to plain log messages when disabled or redirected. Windows terminals are supported through `colorama`.
133
+
134
+ ## Password dictionary
135
+
136
+ The bundled `password_list.txt` contains hundreds of common credentials, curated for demonstration purposes. The unlock command trims duplicates, ignores comments (`# ...`), and safely handles mixed encodings. Supply your own list with `--dictionary <file>` for larger attacks.
137
+
138
+ ## Contributing
139
+ Pull requests are welcome—please open an issue describing the enhancement before submitting substantial changes.
140
+
141
+ ## License
142
+
143
+ Zippy is released under the MIT License. See `LICENSE` for the full text.
144
+
145
+ ## Disclaimer
146
+
147
+ Zippy is provided "as is" without warranty of any kind. Use at your own risk and keep backups of critical data.
148
+
149
+ **The author is not responsible for any data loss or damage resulting from the use of this software.**
@@ -0,0 +1,16 @@
1
+ py_zippy-1.0.0.dist-info/licenses/LICENSE,sha256=9VKiQ2gJZk1vRNRUsnO3nBWxbNyA65LmMo1QORMNyZ0,1073
2
+ zippy/__init__.py,sha256=smZtZ8pgEvlsAVLKZhQqxDtzrk8K9X3VPSfyNqhjkyU,181
3
+ zippy/cli.py,sha256=YM0dRTBVAaDjv0E_NcrMfdHOhcuYbQL4KFWx_OZN5vI,8385
4
+ zippy/create.py,sha256=H3Yb2UDtJoZ2DBB7sdiWdXoXOVVrSNSjD4e_uh1CPFw,9567
5
+ zippy/extract.py,sha256=fGU2_9ORsL50EydvhzLgNwVQk2XZ6XAevp-tJ38vkeA,4773
6
+ zippy/list.py,sha256=7oYCHFKAlyuHw4CpZAmnRz7jmdPC0_yzKNNrs1nvAs8,2336
7
+ zippy/lock.py,sha256=118GnvT3yfIv8wP-DF7M4NCK9Yus1RKtQwdnB0OTlh0,3396
8
+ zippy/repair.py,sha256=7jCgHuv-dVhYFK3iVmpSX4f6JVWF1jbO8TWzfLAH6zk,9700
9
+ zippy/test.py,sha256=9nHneW9q_JUq3Lg3XEYIz4TDYVqK7RfyXFkLrv37xhA,5542
10
+ zippy/unlock.py,sha256=9AQ-6C8jUD0PbE-bXD9LZnropqP5zBh2L6_jHemJtxE,4428
11
+ zippy/utils.py,sha256=YQECi0vyOmz-RqX6bnnODzs162nAD72flw_bsBrCwkQ,10721
12
+ py_zippy-1.0.0.dist-info/METADATA,sha256=b170Pu_W2fU-O9UIjiNK3RcGmzQ5yTHVCu6pabxZ9eg,6647
13
+ py_zippy-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ py_zippy-1.0.0.dist-info/entry_points.txt,sha256=MuEnhP2ZUmhg38SIktO91HhqwYmODGjKj8nfyfI72C0,41
15
+ py_zippy-1.0.0.dist-info/top_level.txt,sha256=Y5J0yny13o5cYQhIr0YVS4WX5bYyFP0iFbMQ1WBa12E,6
16
+ py_zippy-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ zippy = zippy.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 John H. Mitander
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ zippy
zippy/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ zippy - Archive Utility Toolkit
3
+
4
+ This package provides utilities for working with various archive formats
5
+ including zip, tar, tar.gz, and gzip files.
6
+ """
7
+
8
+ __version__ = "1.0.0"
zippy/cli.py ADDED
@@ -0,0 +1,286 @@
1
+ import argparse
2
+ import json
3
+ import os
4
+ import readline
5
+ import sys
6
+ from typing import Iterable, Optional
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ from . import __version__
11
+ from .create import create_archive
12
+ from .extract import extract_archive
13
+ from .list import list_archive_contents
14
+ from .lock import lock_archive
15
+ from .repair import repair_archive
16
+ from .test import test_archive_integrity
17
+ from .unlock import unlock_archive
18
+ from .utils import (
19
+ Fore,
20
+ SUPPORTED_ARCHIVE_TYPES,
21
+ ZippyError,
22
+ color_text,
23
+ configure_logging,
24
+ get_logger,
25
+ handle_errors,
26
+ validate_path,
27
+ )
28
+
29
+ SCRIPT_NAME = "zippy"
30
+ CONFIG_FILE = "zippy_config.json"
31
+ PASSWORD_DICT_DEFAULT = "password_list.txt"
32
+
33
+
34
+ def _format_supported_types() -> str:
35
+ unique_types = sorted({value for value in SUPPORTED_ARCHIVE_TYPES.values()})
36
+ return ", ".join(unique_types)
37
+
38
+
39
+ def display_banner() -> None:
40
+ banner = f"{SCRIPT_NAME.upper()} v{__version__}"
41
+ print(color_text(banner, Fore.CYAN if Fore else None))
42
+
43
+
44
+ def setup_auto_completion(flags: Iterable[str]) -> None:
45
+ def completer(text: str, state: int) -> Optional[str]:
46
+ options = [flag for flag in flags if flag.startswith(text)]
47
+ return options[state] if state < len(options) else None
48
+
49
+ readline.set_completer(completer)
50
+ readline.parse_and_bind("tab: complete")
51
+
52
+
53
+ def build_parser() -> argparse.ArgumentParser:
54
+ parser = argparse.ArgumentParser(
55
+ description=f"{SCRIPT_NAME.upper()} - Archive Utility Toolkit",
56
+ )
57
+ command_group = parser.add_mutually_exclusive_group(required=True)
58
+ command_group.add_argument(
59
+ "--extract",
60
+ "-x",
61
+ dest="command",
62
+ action="store_const",
63
+ const="extract",
64
+ help="Extract archive contents",
65
+ )
66
+ command_group.add_argument(
67
+ "--create",
68
+ "-c",
69
+ dest="command",
70
+ action="store_const",
71
+ const="create",
72
+ help="Create a new archive",
73
+ )
74
+ command_group.add_argument(
75
+ "--list",
76
+ "-l",
77
+ dest="command",
78
+ action="store_const",
79
+ const="list",
80
+ help="List archive contents",
81
+ )
82
+ command_group.add_argument(
83
+ "--test",
84
+ "-t",
85
+ dest="command",
86
+ action="store_const",
87
+ const="test",
88
+ help="Test archive integrity",
89
+ )
90
+ command_group.add_argument(
91
+ "--unlock",
92
+ "-u",
93
+ dest="command",
94
+ action="store_const",
95
+ const="unlock",
96
+ help="Attempt to unlock a password-protected ZIP",
97
+ )
98
+ command_group.add_argument(
99
+ "--lock",
100
+ dest="command",
101
+ action="store_const",
102
+ const="lock",
103
+ help="Create or re-lock a password-protected ZIP",
104
+ )
105
+ command_group.add_argument(
106
+ "--repair",
107
+ "-r",
108
+ dest="command",
109
+ action="store_const",
110
+ const="repair",
111
+ help="[Experimental] Attempt archive repair",
112
+ )
113
+ parser.add_argument("archive_file", nargs="?", help="Path to the archive file.")
114
+ parser.add_argument(
115
+ "-o",
116
+ "--output",
117
+ dest="output_path",
118
+ default=".",
119
+ help="Output directory for extraction (default: .)",
120
+ )
121
+ parser.add_argument(
122
+ "-p", "--password", dest="password", help="Password for archive operations"
123
+ )
124
+ parser.add_argument(
125
+ "-d",
126
+ "--dictionary",
127
+ dest="dictionary_file",
128
+ default=PASSWORD_DICT_DEFAULT,
129
+ help=f"Dictionary file for unlock attempts (default: {PASSWORD_DICT_DEFAULT})",
130
+ )
131
+ parser.add_argument(
132
+ "-f",
133
+ "--files",
134
+ dest="files_to_add",
135
+ help="Comma-separated files/directories to add",
136
+ )
137
+ parser.add_argument("--type", dest="archive_type", help="Force archive type")
138
+ parser.add_argument(
139
+ "--repair-mode",
140
+ dest="repair_mode",
141
+ default="remove_corrupted",
142
+ choices=["remove_corrupted", "scan_only"],
143
+ help="Repair mode for ZIP archives",
144
+ )
145
+ parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")
146
+ parser.add_argument(
147
+ "--no-animation", action="store_true", help="Disable loading animation"
148
+ )
149
+ parser.add_argument(
150
+ "--save-config", dest="save_config_file", help="Save current settings to JSON"
151
+ )
152
+ parser.add_argument(
153
+ "--load-config", dest="load_config_file", help="Load settings from JSON"
154
+ )
155
+ parser.add_argument("--version", action="version", version=f"{SCRIPT_NAME} {__version__}")
156
+ return parser
157
+
158
+
159
+ def _persist_config(args: argparse.Namespace, path: str) -> None:
160
+ data = vars(args).copy()
161
+ with open(path, "w", encoding="utf-8") as handle:
162
+ json.dump(data, handle, indent=4)
163
+ get_logger(__name__).info("Configuration saved to %s", path)
164
+
165
+
166
+ def _load_config(path: str) -> dict:
167
+ if not os.path.exists(path):
168
+ handle_errors(f"Configuration file not found: {path}")
169
+ with open(path, "r", encoding="utf-8") as handle:
170
+ data = json.load(handle)
171
+ get_logger(__name__).info("Configuration loaded from %s", path)
172
+ return data
173
+
174
+
175
+ def _apply_loaded_config(args: argparse.Namespace, config: dict) -> None:
176
+ for key, value in config.items():
177
+ if hasattr(args, key):
178
+ setattr(args, key, value)
179
+
180
+
181
+ def _validate_archive_path(command: str, archive_path: Optional[str]) -> None:
182
+ if not archive_path:
183
+ handle_errors("Archive file path is required for this command.")
184
+ if command in {"extract", "list", "test", "unlock", "repair"}:
185
+ validate_path(archive_path, "Archive file path", must_exist=True, is_dir=False)
186
+ elif command in {"create", "lock"}:
187
+ directory = os.path.dirname(os.path.abspath(archive_path)) or "."
188
+ validate_path(directory, "Output directory", must_exist=True, is_dir=True)
189
+
190
+
191
+ def _execute_command(args: argparse.Namespace) -> None:
192
+ command = args.command
193
+ archive_path = args.archive_file
194
+ _validate_archive_path(command, archive_path)
195
+
196
+ if command == "extract":
197
+ extract_archive(
198
+ archive_path,
199
+ args.output_path,
200
+ args.password,
201
+ args.verbose,
202
+ args.no_animation,
203
+ )
204
+ elif command == "create":
205
+ create_archive(
206
+ archive_path,
207
+ args.files_to_add,
208
+ args.archive_type,
209
+ args.password,
210
+ args.verbose,
211
+ args.no_animation,
212
+ )
213
+ elif command == "list":
214
+ list_archive_contents(archive_path, args.verbose, args.no_animation)
215
+ elif command == "test":
216
+ test_archive_integrity(
217
+ archive_path, args.verbose, args.no_animation, args.password
218
+ )
219
+ elif command == "unlock":
220
+ unlock_archive(
221
+ archive_path,
222
+ args.dictionary_file,
223
+ args.password,
224
+ args.verbose,
225
+ args.no_animation,
226
+ )
227
+ elif command == "lock":
228
+ lock_archive(
229
+ archive_path,
230
+ args.files_to_add,
231
+ "zip",
232
+ args.password,
233
+ args.verbose,
234
+ args.no_animation,
235
+ )
236
+ elif command == "repair":
237
+ repair_archive(
238
+ archive_path,
239
+ args.verbose,
240
+ args.no_animation,
241
+ args.repair_mode,
242
+ args.password,
243
+ )
244
+ else: # pragma: no cover - defensive
245
+ handle_errors("Invalid command. See 'help'.")
246
+
247
+
248
+ def main(argv: Optional[list[str]] = None) -> int:
249
+ load_dotenv()
250
+ parser = build_parser()
251
+ args = parser.parse_args(argv)
252
+ configure_logging(args.verbose)
253
+ command_flags = [
254
+ "--extract",
255
+ "--create",
256
+ "--list",
257
+ "--test",
258
+ "--unlock",
259
+ "--lock",
260
+ "--repair",
261
+ ]
262
+ setup_auto_completion(command_flags)
263
+
264
+ if args.save_config_file:
265
+ _persist_config(args, args.save_config_file)
266
+ return 0
267
+
268
+ if args.load_config_file:
269
+ config = _load_config(args.load_config_file)
270
+ _apply_loaded_config(args, config)
271
+
272
+ display_banner()
273
+ try:
274
+ _execute_command(args)
275
+ except ZippyError as error:
276
+ get_logger(__name__).error(str(error))
277
+ return getattr(error, "exit_code", 1)
278
+ return 0
279
+
280
+
281
+ if __name__ == "__main__": # pragma: no cover
282
+ try:
283
+ sys.exit(main())
284
+ except ZippyError as error:
285
+ get_logger(__name__).error(str(error))
286
+ sys.exit(getattr(error, "exit_code", 1))