New submission from Eryk Sun:

pathlib._WindowsFlavour.is_reserved assumes Windows uses an exact match up to 
the file extension for reserved DOS device names. However, this misses cases 
involving trailing spaces and colons, such as the following examples:

Trailing colon:

    >>> pathlib.Path('C:/foo/NUL:').is_reserved()
    False
    >>> print(os.path._getfullpathname('C:/foo/NUL:'))
    \\.\NUL

Trailing spaces:

    >>> pathlib.Path('C:/foo/NUL  ').is_reserved()
    False
    >>> print(os.path._getfullpathname('C:/foo/NUL  '))
    \\.\NUL

Trailing spaces followed by a file extension:

    >>> pathlib.Path('C:/foo/NUL  .txt').is_reserved()
    False
    >>> print(os.path._getfullpathname('C:/foo/NUL  .txt'))
    \\.\NUL

Windows calls RtlIsDosDeviceName_Ustr to check whether a path represents a DOS 
device name. Here's a link to the reverse-engineered implementation of this 
function in ReactOS 4.1:

http://code.reactos.org/browse/reactos/branches/ros-branch-0_4_1/reactos/sdk/lib/rtl/path.c?r=71210#to85

The ReactOS implementation performs the following steps:

    * Return false for a UNC or unknown path type or an empty path.
    * Strip a final ":" if present. Return false if it was the
      only character.
    * Strip trailing dot and space characters.
    * Iterate over the path in reverse. If the current character is
      a "\\" or "/" or a ":" drive letter separator (at index 1),
      then if the next character matches the first letter of a DOS
      device name, splice out the base name as a potential match.
      Else return false.
    * Return false if the first character at this point does not 
      match the first letter of a DOS device name.
    * Remove the file extension, starting at the first dot or colon.
    * Remove trailing spaces.
    * Return the name offset and length if it equals
        * "COM" or "LPT" plus a digit
        * "PRN", "AUX", "NUL, or "CON"
    * Else return false.

It seems that ":" and "." are effectively equivalent for the purposes of 
is_reserved. Given this is the case, it could return whether 
parts[-1].partition('.')[0].partition(':')[0].rstrip(' ').upper() is in 
self.reserved_names. Or maybe use a regex for the entire check.

If a script is running on Windows, I think the best approach is to call 
os.path.abspath, which calls _getfullpathname. This lets Windows itself 
determine if the path maps to the \\.\ device namespace. However, I realize 
that is_reserved is intended to be cross-platform.

By the way, the comment for this method says that r"foo\NUL" isn't reserved, 
but it is. Maybe the author checked by trying to open NUL in a non-existing foo 
directory. DOS device names are only reserved in practice when opening and 
creating files in existing directories (as opposed to reserved in principle 
with GetFullPathName, which doesn't check for a valid path). NT can thus return 
an error that's consistent with how DOS behaved in the 1980s -- because that's 
really important, you know.

----------
components: Library (Lib), Windows
messages: 273344
nosy: eryksun, paul.moore, steve.dower, tim.golden, zach.ware
priority: normal
severity: normal
stage: needs patch
status: open
title: pathlib is_reserved fails for some reserved paths on Windows
type: behavior
versions: Python 3.5, Python 3.6

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue27827>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to