mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 18:02:38 +02:00

The `exit()` [1] function is not necessarily present in every Python environment, as it's added by the `site` module. Also, this function is "[...] useful for the interactive interpreter shell and should not be used in programs" For this reason, we replace all such occurrences with `sys.exit()` [2], which is the canonical function to exit Python programs. [1]: https://docs.python.org/3/library/constants.html#exit [2]: https://docs.python.org/3/library/sys.html#sys.exit
109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
import functools
|
|
import os
|
|
import sys
|
|
from typing import List, Optional, Tuple
|
|
|
|
import click
|
|
|
|
from . import errors
|
|
from .document import Document
|
|
|
|
|
|
@errors.handle_document_errors
|
|
def _validate_input_filename(
|
|
ctx: click.Context, param: str, value: Optional[str]
|
|
) -> Optional[str]:
|
|
if value is None:
|
|
return None
|
|
filename = Document.normalize_filename(value)
|
|
Document.validate_input_filename(filename)
|
|
return filename
|
|
|
|
|
|
@errors.handle_document_errors
|
|
def _validate_input_filenames(
|
|
ctx: click.Context, param: List[str], value: Tuple[str]
|
|
) -> List[str]:
|
|
normalized_filenames = []
|
|
for filename in value:
|
|
filename = Document.normalize_filename(filename)
|
|
Document.validate_input_filename(filename)
|
|
normalized_filenames.append(filename)
|
|
return normalized_filenames
|
|
|
|
|
|
@errors.handle_document_errors
|
|
def _validate_output_filename(
|
|
ctx: click.Context, param: str, value: Optional[str]
|
|
) -> Optional[str]:
|
|
if value is None:
|
|
return None
|
|
filename = Document.normalize_filename(value)
|
|
Document.validate_output_filename(filename)
|
|
return filename
|
|
|
|
|
|
# XXX: Click versions 7.x and below inspect the number of arguments that the
|
|
# callback handler supports. Unfortunately, common Python decorators (such as
|
|
# `handle_document_errors()`) mask this number, so we need to reinstate it
|
|
# somehow [1]. The simplest way to do so is using a wrapper function.
|
|
#
|
|
# Once we stop supporting Click 7.x, we can remove the wrappers below.
|
|
#
|
|
# [1]: https://github.com/freedomofpress/dangerzone/issues/206#issuecomment-1297336863
|
|
def validate_input_filename(
|
|
ctx: click.Context, param: str, value: Optional[str]
|
|
) -> Optional[str]:
|
|
return _validate_input_filename(ctx, param, value)
|
|
|
|
|
|
def validate_input_filenames(
|
|
ctx: click.Context, param: List[str], value: Tuple[str]
|
|
) -> List[str]:
|
|
return _validate_input_filenames(ctx, param, value)
|
|
|
|
|
|
def validate_output_filename(
|
|
ctx: click.Context, param: str, value: Optional[str]
|
|
) -> Optional[str]:
|
|
return _validate_output_filename(ctx, param, value)
|
|
|
|
|
|
def check_suspicious_options(args: List[str]) -> None:
|
|
options = set([arg for arg in args if arg.startswith("-")])
|
|
try:
|
|
files = set(os.listdir())
|
|
except Exception:
|
|
# If we can list files in the current working directory, this means that
|
|
# we're probably in an unlinked directory. Dangerzone should still work in
|
|
# this case, so we should return here.
|
|
return
|
|
|
|
intersection = options & files
|
|
if intersection:
|
|
filenames_str = ", ".join(intersection)
|
|
msg = (
|
|
f"Security: Detected CLI options that are also present as files in the"
|
|
f" current working directory: {filenames_str}"
|
|
)
|
|
click.echo(msg)
|
|
sys.exit(1)
|
|
|
|
|
|
def override_parser_and_check_suspicious_options(click_main: click.Command) -> None:
|
|
"""Override the argument parsing logic of Click.
|
|
|
|
Click does not allow us to have access to the raw arguments that it receives (either
|
|
from sys.argv or from its testing module). To circumvent this, we can override its
|
|
`Command.parse_args()` method, which is public and should be safe to do so.
|
|
|
|
We can use it to check for any suspicious options prior to arg parsing.
|
|
"""
|
|
orig_parse_fn = click_main.parse_args
|
|
|
|
@functools.wraps(orig_parse_fn)
|
|
def custom_parse_fn(ctx: click.Context, args: List[str]) -> List[str]:
|
|
check_suspicious_options(args)
|
|
return orig_parse_fn(ctx, args)
|
|
|
|
click_main.parse_args = custom_parse_fn # type: ignore [method-assign]
|