|
26 | 26 | __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext", |
27 | 27 | "basename","dirname","commonprefix","getsize","getmtime", |
28 | 28 | "getatime","getctime", "islink","exists","lexists","isdir","isfile", |
29 | | - "ismount", "expanduser","expandvars","normpath","abspath", |
30 | | - "curdir","pardir","sep","pathsep","defpath","altsep", |
| 29 | + "ismount","isreserved","expanduser","expandvars","normpath", |
| 30 | + "abspath","curdir","pardir","sep","pathsep","defpath","altsep", |
31 | 31 | "extsep","devnull","realpath","supports_unicode_filenames","relpath", |
32 | 32 | "samefile", "sameopenfile", "samestat", "commonpath", "isjunction"] |
33 | 33 |
|
@@ -330,6 +330,42 @@ def ismount(path): |
330 | 330 | return False |
331 | 331 |
|
332 | 332 |
|
| 333 | +_reserved_chars = frozenset( |
| 334 | + {chr(i) for i in range(32)} | |
| 335 | + {'"', '*', ':', '<', '>', '?', '|', '/', '\\'} |
| 336 | +) |
| 337 | + |
| 338 | +_reserved_names = frozenset( |
| 339 | + {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} | |
| 340 | + {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} | |
| 341 | + {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} |
| 342 | +) |
| 343 | + |
| 344 | +def isreserved(path): |
| 345 | + """Return true if the pathname is reserved by the system.""" |
| 346 | + # Refer to "Naming Files, Paths, and Namespaces": |
| 347 | + # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file |
| 348 | + path = os.fsdecode(splitroot(path)[2]).replace(altsep, sep) |
| 349 | + return any(_isreservedname(name) for name in reversed(path.split(sep))) |
| 350 | + |
| 351 | +def _isreservedname(name): |
| 352 | + """Return true if the filename is reserved by the system.""" |
| 353 | + # Trailing dots and spaces are reserved. |
| 354 | + if name.endswith(('.', ' ')) and name not in ('.', '..'): |
| 355 | + return True |
| 356 | + # Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved. |
| 357 | + # ASCII control characters (0-31) are reserved. |
| 358 | + # Colon is reserved for file streams (e.g. "name:stream[:type]"). |
| 359 | + if _reserved_chars.intersection(name): |
| 360 | + return True |
| 361 | + # DOS device names are reserved (e.g. "nul" or "nul .txt"). The rules |
| 362 | + # are complex and vary across Windows versions. On the side of |
| 363 | + # caution, return True for names that may not be reserved. |
| 364 | + if name.partition('.')[0].rstrip(' ').upper() in _reserved_names: |
| 365 | + return True |
| 366 | + return False |
| 367 | + |
| 368 | + |
333 | 369 | # Expand paths beginning with '~' or '~user'. |
334 | 370 | # '~' means $HOME; '~user' means that user's home directory. |
335 | 371 | # If the path doesn't begin with '~', or if the user or $HOME is unknown, |
|
0 commit comments