mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-25 07:23:28 +00:00
use archivename_validator everywhere
also: simplify, reuse code from text_validator.
This commit is contained in:
parent
d0d21e7928
commit
a475227e18
12 changed files with 54 additions and 67 deletions
|
@ -16,7 +16,7 @@
|
|||
from ..constants import * # NOQA
|
||||
from ..compress import CompressionSpec
|
||||
from ..helpers import comment_validator, ChunkerParams
|
||||
from ..helpers import NameSpec, FilesCacheMode
|
||||
from ..helpers import archivename_validator, FilesCacheMode
|
||||
from ..helpers import eval_escapes
|
||||
from ..helpers import timestamp, archive_ts_now
|
||||
from ..helpers import get_cache_dir, os_stat
|
||||
|
@ -861,5 +861,5 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
|
|||
help="select compression algorithm, see the output of the " '"borg help compression" command for details.',
|
||||
)
|
||||
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument("paths", metavar="PATH", nargs="*", type=str, action="extend", help="paths to archive")
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
from ..helpers import bin_to_hex, prepare_dump_dict
|
||||
from ..helpers import dash_open
|
||||
from ..helpers import StableDict
|
||||
from ..helpers import positive_int_validator, NameSpec
|
||||
from ..helpers import positive_int_validator, archivename_validator
|
||||
from ..manifest import Manifest
|
||||
from ..platform import get_process_id
|
||||
from ..repository import Repository, LIST_SCAN_LIMIT, TAG_PUT, TAG_DELETE, TAG_COMMIT
|
||||
|
@ -387,7 +387,7 @@ def build_parser_debug(self, subparsers, common_parser, mid_common_parser):
|
|||
help="dump archive items (metadata) (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_dump_archive_items)
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
|
||||
debug_dump_archive_epilog = process_epilog(
|
||||
"""
|
||||
|
@ -404,7 +404,7 @@ def build_parser_debug(self, subparsers, common_parser, mid_common_parser):
|
|||
help="dump decoded archive metadata (debug)",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_debug_dump_archive)
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument("path", metavar="PATH", type=str, help="file to dump data into")
|
||||
|
||||
debug_dump_manifest_epilog = process_epilog(
|
||||
|
|
|
@ -106,8 +106,8 @@ def build_parser_diff(self, subparsers, common_parser, mid_common_parser):
|
|||
)
|
||||
subparser.add_argument("--sort", dest="sort", action="store_true", help="Sort the output lines by file path.")
|
||||
subparser.add_argument("--json-lines", action="store_true", help="Format output as JSON Lines. ")
|
||||
subparser.add_argument("name", metavar="ARCHIVE1", type=archivename_validator(), help="ARCHIVE1 name")
|
||||
subparser.add_argument("other_name", metavar="ARCHIVE2", type=archivename_validator(), help="ARCHIVE2 name")
|
||||
subparser.add_argument("name", metavar="ARCHIVE1", type=archivename_validator, help="ARCHIVE1 name")
|
||||
subparser.add_argument("other_name", metavar="ARCHIVE2", type=archivename_validator, help="ARCHIVE2 name")
|
||||
subparser.add_argument(
|
||||
"paths",
|
||||
metavar="PATH",
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
from ._common import build_filter, build_matcher
|
||||
from ..archive import BackupError, BackupOSError
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import NameSpec
|
||||
from ..helpers import archivename_validator
|
||||
from ..helpers import remove_surrogates
|
||||
from ..helpers import HardLinkManager
|
||||
from ..helpers import ProgressIndicatorPercent
|
||||
|
@ -175,7 +175,7 @@ def build_parser_extract(self, subparsers, common_parser, mid_common_parser):
|
|||
action="store_true",
|
||||
help="create holes in output sparse file from all-zero chunks",
|
||||
)
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument(
|
||||
"paths", metavar="PATH", nargs="*", type=str, help="paths to extract; patterns are supported"
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
from ..archive import Archive
|
||||
from ..cache import Cache
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import ItemFormatter, BaseFormatter, NameSpec
|
||||
from ..helpers import ItemFormatter, BaseFormatter, archivename_validator
|
||||
from ..manifest import Manifest
|
||||
|
||||
from ..logger import create_logger
|
||||
|
@ -116,7 +116,7 @@ def build_parser_list(self, subparsers, common_parser, mid_common_parser):
|
|||
"but keys used in it are added to the JSON output. "
|
||||
"Some keys are always present. Note: JSON can only represent text.",
|
||||
)
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument(
|
||||
"paths", metavar="PATH", nargs="*", type=str, help="paths to list; patterns are supported"
|
||||
)
|
||||
|
|
|
@ -147,7 +147,7 @@ def build_parser_recreate(self, subparsers, common_parser, mid_common_parser):
|
|||
dest="target",
|
||||
metavar="TARGET",
|
||||
default=None,
|
||||
type=archivename_validator(),
|
||||
type=archivename_validator,
|
||||
help="create a new archive with the name ARCHIVE, do not replace existing archive "
|
||||
"(only applies for a single archive)",
|
||||
)
|
||||
|
|
|
@ -41,7 +41,7 @@ def build_parser_rename(self, subparsers, common_parser, mid_common_parser):
|
|||
help="rename archive",
|
||||
)
|
||||
subparser.set_defaults(func=self.do_rename)
|
||||
subparser.add_argument("name", metavar="OLDNAME", type=archivename_validator(), help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="OLDNAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument(
|
||||
"newname", metavar="NEWNAME", type=archivename_validator(), help="specify the new archive name"
|
||||
"newname", metavar="NEWNAME", type=archivename_validator, help="specify the new archive name"
|
||||
)
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
from ..helpers import msgpack
|
||||
from ..helpers import create_filter_process
|
||||
from ..helpers import ChunkIteratorFileWrapper
|
||||
from ..helpers import comment_validator, ChunkerParams
|
||||
from ..helpers import NameSpec
|
||||
from ..helpers import archivename_validator, comment_validator, ChunkerParams
|
||||
from ..helpers import remove_surrogates
|
||||
from ..helpers import timestamp, archive_ts_now
|
||||
from ..helpers import basic_json_data, json_print
|
||||
|
@ -404,7 +403,7 @@ def build_parser_tar(self, subparsers, common_parser, mid_common_parser):
|
|||
choices=("BORG", "PAX", "GNU"),
|
||||
help="select tar format: BORG, PAX or GNU",
|
||||
)
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument("tarfile", metavar="FILE", help='output tar file. "-" to write to stdout instead.')
|
||||
subparser.add_argument(
|
||||
"paths", metavar="PATH", nargs="*", type=str, help="paths to extract; patterns are supported"
|
||||
|
@ -536,5 +535,5 @@ def build_parser_tar(self, subparsers, common_parser, mid_common_parser):
|
|||
help="select compression algorithm, see the output of the " '"borg help compression" command for details.',
|
||||
)
|
||||
|
||||
subparser.add_argument("name", metavar="NAME", type=NameSpec, help="specify the archive name")
|
||||
subparser.add_argument("name", metavar="NAME", type=archivename_validator, help="specify the archive name")
|
||||
subparser.add_argument("tarfile", metavar="TARFILE", help='input tar file. "-" to read from stdin instead.')
|
||||
|
|
|
@ -40,10 +40,9 @@ def do_transfer(self, args, *, repository, manifest, cache, other_repository=Non
|
|||
return EXIT_SUCCESS
|
||||
|
||||
an_errors = []
|
||||
av = archivename_validator()
|
||||
for archive_name in archive_names:
|
||||
try:
|
||||
av(archive_name)
|
||||
archivename_validator(archive_name)
|
||||
except argparse.ArgumentTypeError as err:
|
||||
an_errors.append(str(err))
|
||||
if an_errors:
|
||||
|
|
|
@ -20,11 +20,10 @@
|
|||
from .misc import ChunkIteratorFileWrapper, open_item, chunkit, iter_separated, ErrorIgnoringTextIOWrapper
|
||||
from .parseformat import bin_to_hex, safe_encode, safe_decode
|
||||
from .parseformat import remove_surrogates, eval_escapes, decode_dict, positive_int_validator, interval
|
||||
from .parseformat import ChunkerParams, FilesCacheMode, partial_format, DatetimeWrapper
|
||||
from .parseformat import SortBySpec, ChunkerParams, FilesCacheMode, partial_format, DatetimeWrapper
|
||||
from .parseformat import format_file_size, parse_file_size, FileSize, parse_storage_quota
|
||||
from .parseformat import sizeof_fmt, sizeof_fmt_iec, sizeof_fmt_decimal
|
||||
from .parseformat import format_line, replace_placeholders, PlaceholderError
|
||||
from .parseformat import SortBySpec, NameSpec
|
||||
from .parseformat import format_archive, parse_stringified_list, clean_lines
|
||||
from .parseformat import Location, location_validator, archivename_validator, comment_validator, text_validator
|
||||
from .parseformat import BaseFormatter, ArchiveFormatter, ItemFormatter, file_status
|
||||
|
|
|
@ -227,8 +227,6 @@ def __call__(self, text, overrides=None):
|
|||
|
||||
replace_placeholders = PlaceholderReplacer()
|
||||
|
||||
NameSpec = str
|
||||
|
||||
|
||||
def SortBySpec(text):
|
||||
from ..manifest import AI_HUMAN_SORT_KEYS
|
||||
|
@ -538,48 +536,21 @@ def validator(text):
|
|||
return validator
|
||||
|
||||
|
||||
def archivename_validator():
|
||||
def text_validator(*, name, max_length, min_length=0, invalid_ctrl_chars="\0", invalid_chars="", no_blanks=False):
|
||||
def validator(text):
|
||||
assert isinstance(text, str)
|
||||
# we make sure that the archive name can be used as directory name (for borg mount)
|
||||
text = replace_placeholders(text)
|
||||
MAX_PATH = 260 # Windows default. Since Win10, there is a registry setting LongPathsEnabled to get more.
|
||||
MAX_DIRNAME = MAX_PATH - len("12345678.123")
|
||||
SAFETY_MARGIN = 48 # borgfs path: mountpoint / archivename / dir / dir / ... / file
|
||||
MAX_ARCHIVENAME = MAX_DIRNAME - SAFETY_MARGIN
|
||||
if not (0 < len(text) <= MAX_ARCHIVENAME):
|
||||
raise argparse.ArgumentTypeError(f'Invalid archive name: "{text}" [0 < length <= {MAX_ARCHIVENAME}]')
|
||||
# note: ":" is also a invalid path char on windows, but we can not blacklist it,
|
||||
# because e.g. our {now} placeholder creates ISO-8601 like output like 2022-12-10T20:47:42 .
|
||||
invalid_chars = r"/" + r"\"<|>?*" # posix + windows
|
||||
if re.search(f"[{re.escape(invalid_chars)}]", text):
|
||||
raise argparse.ArgumentTypeError(
|
||||
f'Invalid archive name: "{text}" [invalid chars detected matching "{invalid_chars}"]'
|
||||
)
|
||||
invalid_ctrl_chars = "".join(chr(i) for i in range(32))
|
||||
if re.search(f"[{re.escape(invalid_ctrl_chars)}]", text):
|
||||
raise argparse.ArgumentTypeError(
|
||||
f'Invalid archive name: "{text}" [invalid control chars detected, ASCII < 32]'
|
||||
)
|
||||
if text.startswith(" ") or text.endswith(" "):
|
||||
raise argparse.ArgumentTypeError(f'Invalid archive name: "{text}" [leading or trailing blanks]')
|
||||
try:
|
||||
text.encode("utf-8", errors="strict")
|
||||
except UnicodeEncodeError:
|
||||
# looks like text contains surrogate-escapes
|
||||
raise argparse.ArgumentTypeError(f'Invalid archive name: "{text}" [contains non-unicode characters]')
|
||||
return text
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def text_validator(*, name, max_length, invalid_ctrl_chars="\0"):
|
||||
def validator(text):
|
||||
assert isinstance(text, str)
|
||||
if not (len(text) <= max_length):
|
||||
raise argparse.ArgumentTypeError(f'Invalid {name}: "{text}" [length <= {max_length}]')
|
||||
if re.search(f"[{re.escape(invalid_ctrl_chars)}]", text):
|
||||
if len(text) < min_length:
|
||||
raise argparse.ArgumentTypeError(f'Invalid {name}: "{text}" [length < {min_length}]')
|
||||
if len(text) > max_length:
|
||||
raise argparse.ArgumentTypeError(f'Invalid {name}: "{text}" [length > {max_length}]')
|
||||
if invalid_ctrl_chars and re.search(f"[{re.escape(invalid_ctrl_chars)}]", text):
|
||||
raise argparse.ArgumentTypeError(f'Invalid {name}: "{text}" [invalid control chars detected]')
|
||||
if invalid_chars and re.search(f"[{re.escape(invalid_chars)}]", text):
|
||||
raise argparse.ArgumentTypeError(
|
||||
f'Invalid {name}: "{text}" [invalid chars detected matching "{invalid_chars}"]'
|
||||
)
|
||||
if no_blanks and (text.startswith(" ") or text.endswith(" ")):
|
||||
raise argparse.ArgumentTypeError(f'Invalid {name}: "{text}" [leading or trailing blanks detected]')
|
||||
try:
|
||||
text.encode("utf-8", errors="strict")
|
||||
except UnicodeEncodeError:
|
||||
|
@ -593,6 +564,27 @@ def validator(text):
|
|||
comment_validator = text_validator(name="comment", max_length=10000)
|
||||
|
||||
|
||||
def archivename_validator(text):
|
||||
# we make sure that the archive name can be used as directory name (for borg mount)
|
||||
MAX_PATH = 260 # Windows default. Since Win10, there is a registry setting LongPathsEnabled to get more.
|
||||
MAX_DIRNAME = MAX_PATH - len("12345678.123")
|
||||
SAFETY_MARGIN = 48 # borgfs path: mountpoint / archivename / dir / dir / ... / file
|
||||
MAX_ARCHIVENAME = MAX_DIRNAME - SAFETY_MARGIN
|
||||
invalid_ctrl_chars = "".join(chr(i) for i in range(32))
|
||||
# note: ":" is also an invalid path char on windows, but we can not blacklist it,
|
||||
# because e.g. our {now} placeholder creates ISO-8601 like output like 2022-12-10T20:47:42 .
|
||||
invalid_chars = r"/" + r"\"<|>?*" # posix + windows
|
||||
validate_text = text_validator(
|
||||
name="archive name",
|
||||
min_length=1,
|
||||
max_length=MAX_ARCHIVENAME,
|
||||
invalid_ctrl_chars=invalid_ctrl_chars,
|
||||
invalid_chars=invalid_chars,
|
||||
no_blanks=True,
|
||||
)
|
||||
return validate_text(text)
|
||||
|
||||
|
||||
class BaseFormatter:
|
||||
FIXED_KEYS = {
|
||||
# Formatting aids
|
||||
|
|
|
@ -256,8 +256,7 @@ def test_bad_syntax(self):
|
|||
],
|
||||
)
|
||||
def test_archivename_ok(name):
|
||||
av = archivename_validator()
|
||||
av(name) # must not raise an exception
|
||||
archivename_validator(name) # must not raise an exception
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -285,9 +284,8 @@ def test_archivename_ok(name):
|
|||
],
|
||||
)
|
||||
def test_archivename_invalid(name):
|
||||
av = archivename_validator()
|
||||
with pytest.raises(ArgumentTypeError):
|
||||
av(name)
|
||||
archivename_validator(name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("text", ["", "single line", "multi\nline\ncomment"])
|
||||
|
|
Loading…
Reference in a new issue