|
10 | 10 | DEFAULT_JSON_OUTPUT_FILE = "safety_results.json" |
11 | 11 |
|
12 | 12 |
|
13 | | -def _add_pickle_arguments(parser: ArgumentParser) -> None: |
14 | | - """Add the standard pickle-related arguments to a parser.""" |
| 13 | +def main(argv: list[str] | None = None) -> int: |
| 14 | + if argv is None: |
| 15 | + argv = sys.argv |
| 16 | + |
| 17 | + parser = ArgumentParser( |
| 18 | + description="fickling is a static analyzer and interpreter for Python pickle data" |
| 19 | + ) |
15 | 20 | parser.add_argument( |
16 | 21 | "PICKLE_FILE", |
17 | 22 | type=str, |
@@ -90,10 +95,17 @@ def _add_pickle_arguments(parser: ArgumentParser) -> None: |
90 | 95 | action="store_true", |
91 | 96 | help="print a runtime trace while interpreting the input pickle file", |
92 | 97 | ) |
| 98 | + parser.add_argument("--version", "-v", action="store_true", help="print the version and exit") |
| 99 | + |
| 100 | + args = parser.parse_args(argv[1:]) |
93 | 101 |
|
| 102 | + if args.version: |
| 103 | + if sys.stdout.isatty(): |
| 104 | + print(f"fickling version {__version__}") |
| 105 | + else: |
| 106 | + print(__version__) |
| 107 | + return 0 |
94 | 108 |
|
95 | | -def _handle_pickle_command(args) -> int: |
96 | | - """Handle the standard pickle decompilation/injection/safety commands.""" |
97 | 109 | if args.create is None: |
98 | 110 | if args.PICKLE_FILE == "-": |
99 | 111 | if hasattr(sys.stdin, "buffer") and sys.stdin.buffer is not None: |
@@ -199,166 +211,3 @@ def _handle_pickle_command(args) -> int: |
199 | 211 | file.close() |
200 | 212 |
|
201 | 213 | return 0 |
202 | | - |
203 | | - |
204 | | -SUBCOMMANDS = {"pytorch", "polyglot"} |
205 | | - |
206 | | - |
207 | | -def _create_pickle_parser() -> ArgumentParser: |
208 | | - """Create parser for the original pickle commands (backward compatibility).""" |
209 | | - parser = ArgumentParser( |
210 | | - description="fickling is a static analyzer and interpreter for Python pickle data" |
211 | | - ) |
212 | | - parser.add_argument("--version", "-v", action="store_true", help="print the version and exit") |
213 | | - _add_pickle_arguments(parser) |
214 | | - return parser |
215 | | - |
216 | | - |
217 | | -def _create_subcommand_parser() -> ArgumentParser: |
218 | | - """Create parser with subcommands for PyTorch and polyglot operations.""" |
219 | | - parser = ArgumentParser( |
220 | | - description="fickling is a static analyzer and interpreter for Python pickle data" |
221 | | - ) |
222 | | - parser.add_argument("--version", "-v", action="store_true", help="print the version and exit") |
223 | | - |
224 | | - subparsers = parser.add_subparsers(dest="command", help="available commands") |
225 | | - |
226 | | - # PyTorch subcommand |
227 | | - pytorch_parser = subparsers.add_parser("pytorch", help="PyTorch model operations") |
228 | | - _setup_pytorch_subcommand(pytorch_parser) |
229 | | - |
230 | | - # Polyglot subcommand |
231 | | - polyglot_parser = subparsers.add_parser("polyglot", help="polyglot detection and creation") |
232 | | - _setup_polyglot_subcommand(polyglot_parser) |
233 | | - |
234 | | - return parser |
235 | | - |
236 | | - |
237 | | -def _get_first_positional(argv: list[str]) -> str | None: |
238 | | - """Get the first non-flag argument (potential subcommand or file).""" |
239 | | - for arg in argv[1:]: |
240 | | - if not arg.startswith("-"): |
241 | | - return arg |
242 | | - return None |
243 | | - |
244 | | - |
245 | | -def main(argv: list[str] | None = None) -> int: |
246 | | - if argv is None: |
247 | | - argv = sys.argv |
248 | | - |
249 | | - # Check for version flag first |
250 | | - if "--version" in argv or "-v" in argv: |
251 | | - if sys.stdout.isatty(): |
252 | | - print(f"fickling version {__version__}") |
253 | | - else: |
254 | | - print(__version__) |
255 | | - return 0 |
256 | | - |
257 | | - # Determine if we're using a subcommand or the original CLI |
258 | | - first_positional = _get_first_positional(argv) |
259 | | - |
260 | | - if first_positional in SUBCOMMANDS: |
261 | | - # Use subcommand parser |
262 | | - parser = _create_subcommand_parser() |
263 | | - args = parser.parse_args(argv[1:]) |
264 | | - |
265 | | - if args.command == "pytorch": |
266 | | - from .cli_pytorch import handle_pytorch_command |
267 | | - |
268 | | - return handle_pytorch_command(args) |
269 | | - if args.command == "polyglot": |
270 | | - from .cli_polyglot import handle_polyglot_command |
271 | | - |
272 | | - return handle_polyglot_command(args) |
273 | | - # Should not reach here |
274 | | - return 1 |
275 | | - # Use original pickle parser for backward compatibility |
276 | | - parser = _create_pickle_parser() |
277 | | - args = parser.parse_args(argv[1:]) |
278 | | - return _handle_pickle_command(args) |
279 | | - |
280 | | - |
281 | | -def _setup_pytorch_subcommand(parser: ArgumentParser) -> None: |
282 | | - """Set up the pytorch subcommand with its sub-subcommands.""" |
283 | | - subparsers = parser.add_subparsers(dest="pytorch_command", help="pytorch operations") |
284 | | - |
285 | | - # identify |
286 | | - identify_parser = subparsers.add_parser("identify", help="detect PyTorch file format(s)") |
287 | | - identify_parser.add_argument("file", type=str, help="path to the PyTorch model file") |
288 | | - identify_parser.add_argument("--json", action="store_true", help="output results as JSON") |
289 | | - |
290 | | - # show |
291 | | - show_parser = subparsers.add_parser("show", help="decompile internal pickle from PyTorch model") |
292 | | - show_parser.add_argument("file", type=str, help="path to the PyTorch model file") |
293 | | - show_parser.add_argument( |
294 | | - "--force", "-f", action="store_true", help="force processing unsupported formats" |
295 | | - ) |
296 | | - show_parser.add_argument("--trace", "-t", action="store_true", help="print a runtime trace") |
297 | | - |
298 | | - # check-safety |
299 | | - safety_parser = subparsers.add_parser( |
300 | | - "check-safety", help="run safety analysis on internal pickle" |
301 | | - ) |
302 | | - safety_parser.add_argument("file", type=str, help="path to the PyTorch model file") |
303 | | - safety_parser.add_argument( |
304 | | - "--force", "-f", action="store_true", help="force processing unsupported formats" |
305 | | - ) |
306 | | - safety_parser.add_argument( |
307 | | - "--json-output", |
308 | | - type=str, |
309 | | - default=None, |
310 | | - help="path to output JSON file for analysis results", |
311 | | - ) |
312 | | - safety_parser.add_argument( |
313 | | - "--print-results", "-p", action="store_true", help="print results to console" |
314 | | - ) |
315 | | - |
316 | | - # inject |
317 | | - inject_parser = subparsers.add_parser("inject", help="inject payload into PyTorch model") |
318 | | - inject_parser.add_argument("file", type=str, help="path to the PyTorch model file") |
319 | | - inject_parser.add_argument("-o", "--output", type=str, required=True, help="output file path") |
320 | | - inject_parser.add_argument( |
321 | | - "-c", "--code", type=str, required=True, help="Python code to inject" |
322 | | - ) |
323 | | - inject_parser.add_argument( |
324 | | - "--method", |
325 | | - type=str, |
326 | | - choices=["insertion", "combination"], |
327 | | - default="insertion", |
328 | | - help="injection method (default: insertion)", |
329 | | - ) |
330 | | - inject_parser.add_argument( |
331 | | - "--force", "-f", action="store_true", help="force processing unsupported formats" |
332 | | - ) |
333 | | - inject_parser.add_argument( |
334 | | - "--overwrite", action="store_true", help="overwrite original file with output" |
335 | | - ) |
336 | | - |
337 | | - |
338 | | -def _setup_polyglot_subcommand(parser: ArgumentParser) -> None: |
339 | | - """Set up the polyglot subcommand with its sub-subcommands.""" |
340 | | - subparsers = parser.add_subparsers(dest="polyglot_command", help="polyglot operations") |
341 | | - |
342 | | - # identify |
343 | | - identify_parser = subparsers.add_parser( |
344 | | - "identify", help="identify all possible PyTorch file formats" |
345 | | - ) |
346 | | - identify_parser.add_argument("file", type=str, help="path to the file to identify") |
347 | | - identify_parser.add_argument("--json", action="store_true", help="output results as JSON") |
348 | | - |
349 | | - # properties |
350 | | - properties_parser = subparsers.add_parser("properties", help="analyze file properties") |
351 | | - properties_parser.add_argument("file", type=str, help="path to the file to analyze") |
352 | | - properties_parser.add_argument( |
353 | | - "-r", "--recursive", action="store_true", help="analyze recursively into archives" |
354 | | - ) |
355 | | - properties_parser.add_argument("--json", action="store_true", help="output results as JSON") |
356 | | - |
357 | | - # create |
358 | | - create_parser = subparsers.add_parser("create", help="create a polyglot file") |
359 | | - create_parser.add_argument("file1", type=str, help="first input file") |
360 | | - create_parser.add_argument("file2", type=str, help="second input file") |
361 | | - create_parser.add_argument("-o", "--output", type=str, default=None, help="output file path") |
362 | | - create_parser.add_argument( |
363 | | - "--quiet", "-q", action="store_true", help="suppress output messages" |
364 | | - ) |
0 commit comments