aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/ntpath.py
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-01-26 18:14:24 +0000
committerGitHub <noreply@github.com>2024-01-26 18:14:24 +0000
commit7e31d6dea276ac91402aefb023c58d239dfd9246 (patch)
tree3a32adf17e7fa06baa399363f57a079d4f631f24 /Lib/ntpath.py
parent6c2b419fb91c8d7daa769d39f73768114b5eb45a (diff)
downloadcpython-7e31d6dea276ac91402aefb023c58d239dfd9246.tar.gz
cpython-7e31d6dea276ac91402aefb023c58d239dfd9246.zip
gh-88569: add `ntpath.isreserved()` (#95486)
Add `ntpath.isreserved()`, which identifies reserved pathnames such as "NUL", "AUX" and "CON". Deprecate `pathlib.PurePath.is_reserved()`. --------- Co-authored-by: Eryk Sun <eryksun@gmail.com> Co-authored-by: Brett Cannon <brett@python.org> Co-authored-by: Steve Dower <steve.dower@microsoft.com>
Diffstat (limited to 'Lib/ntpath.py')
-rw-r--r--Lib/ntpath.py40
1 files changed, 38 insertions, 2 deletions
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index aa0e018eb66..e7cbfe17ecb 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -26,8 +26,8 @@ from genericpath import *
__all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
"basename","dirname","commonprefix","getsize","getmtime",
"getatime","getctime", "islink","exists","lexists","isdir","isfile",
- "ismount", "expanduser","expandvars","normpath","abspath",
- "curdir","pardir","sep","pathsep","defpath","altsep",
+ "ismount","isreserved","expanduser","expandvars","normpath",
+ "abspath","curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction"]
@@ -330,6 +330,42 @@ def ismount(path):
return False
+_reserved_chars = frozenset(
+ {chr(i) for i in range(32)} |
+ {'"', '*', ':', '<', '>', '?', '|', '/', '\\'}
+)
+
+_reserved_names = frozenset(
+ {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} |
+ {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} |
+ {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'}
+)
+
+def isreserved(path):
+ """Return true if the pathname is reserved by the system."""
+ # Refer to "Naming Files, Paths, and Namespaces":
+ # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
+ path = os.fsdecode(splitroot(path)[2]).replace(altsep, sep)
+ return any(_isreservedname(name) for name in reversed(path.split(sep)))
+
+def _isreservedname(name):
+ """Return true if the filename is reserved by the system."""
+ # Trailing dots and spaces are reserved.
+ if name.endswith(('.', ' ')) and name not in ('.', '..'):
+ return True
+ # Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved.
+ # ASCII control characters (0-31) are reserved.
+ # Colon is reserved for file streams (e.g. "name:stream[:type]").
+ if _reserved_chars.intersection(name):
+ return True
+ # DOS device names are reserved (e.g. "nul" or "nul .txt"). The rules
+ # are complex and vary across Windows versions. On the side of
+ # caution, return True for names that may not be reserved.
+ if name.partition('.')[0].rstrip(' ').upper() in _reserved_names:
+ return True
+ return False
+
+
# Expand paths beginning with '~' or '~user'.
# '~' means $HOME; '~user' means that user's home directory.
# If the path doesn't begin with '~', or if the user or $HOME is unknown,