Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make secure_filename() escape reserved Windows filenames #2422

Open
vytas7 opened this issue Dec 20, 2024 · 3 comments
Open

Make secure_filename() escape reserved Windows filenames #2422

vytas7 opened this issue Dec 20, 2024 · 3 comments

Comments

@vytas7
Copy link
Member

vytas7 commented Dec 20, 2024

@CaselIT noticed that we didn't hold Windows hand regarding device file names, like
https://github.com/pallets/werkzeug/blob/7868bef5d978093a8baa0784464ebe5d775ae92a/src/werkzeug/utils.py#L229-L237

(Originally posted by @CaselIT in #2421 (comment).)

@vytas7 vytas7 changed the title Make secure_filename() escape reserved filenames on Windows Make secure_filename() escape reserved filenames on Windows Dec 20, 2024
@vytas7 vytas7 changed the title Make secure_filename() escape reserved filenames on Windows Make secure_filename() escape reserved Windows filenames Dec 20, 2024
@vytas7
Copy link
Member Author

vytas7 commented Dec 20, 2024

I was reading a bit about this, and it is actually possible to both create and delete such files on Windows by using proper escaping. Maybe just a documentation note would be enough? I don't expect many to deploy Falcon on a Windows server in production, but of course you never know.

If we do decide to implement this check, we ought to restrict this behaviour to the Windows platform to make sure we avoid a breaking change elsewhere.

See also: os.path.isreserved(...) (Python 3.13+ needed).

@CaselIT
Copy link
Member

CaselIT commented Dec 21, 2024

just documenting that special file names in windows are not handled is likely fine

@vytas7
Copy link
Member Author

vytas7 commented Dec 21, 2024

This is what is defined in the stdlib's (3.13) ntpath.py:

_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[-1:] in ('.', ' '):
        return name not in ('.', '..')
    # 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.
    return name.partition('.')[0].rstrip(' ').upper() in _reserved_names

Most of the things char-related are irrelevant as we already replace them (although probably check that trailing dot case), so we could just check for these reserved names if we detect the Windows platform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants