Eryk Sun <eryk...@gmail.com> added the comment:

resolve() has additional problems, which possibly could be addressed all at 
once because it's a small method and the problems are closely related.

For an empty path it returns os.getcwd(). I don't think this case is possible 
in the normal way a Path gets constructed. Anyway, returning the unresolved 
working directory is incorrect. Windows is not a POSIX OS. WinAPI 
[Set,Get]CurrentDirectory does not ensure the working directory is a resolved 
path. An empty path should be replaced with "." and processed normally.

It fails when access is denied. _getfinalpathname needs to open a handle, and 
CreateFile requires at least the right to read file attributes and synchronize. 
For example, resolve() fails for a path in another user's profile, which grants 
access only to the user, system, and administrators.

It doesn't keep the "\\\\?\\" extended-path prefix when the source path already 
has it. It should only strip the prefix if the source path doesn't have it. 
That said, the resolved path may actually require it (i.e. long paths, DOS 
device names, trailing spaces), so maybe it should  never be removed.

Its behavior is inconsistent for invalid paths. For example, if "C:/Temp" 
exists and "C:/Spam" does not, then resolving "C:/Temp/bad?" raises whereas 
"C:/Spam/bad?" does not. IMO, it's simpler if neither raises in non-strict 
mode, at least not in _WindowsFlavour.resolve. Malformed paths will slip 
through, but raising an exception in those cases should be the job of the 
constructor. 

It fails to handle device paths. For example, C:/Temp/nul" resolves to the 
"NUL" device if "C:/Temp" exists. In this case _getfinalpathname will typically 
fail, either due to an invalid function or an invalid parameter. These errors, 
along with the error for an invalid filename, get lumped into the CRT's default 
EINVAL error code.

Also, GetFinalPathNameByHandle was added in Vista, so _getfinalpathname is 
always available in 3.5+. There's no need to use it conditionally.

Here's a prototype that addresses these issues:

    import nt
    import os
    import errno

    def _is_extended(path):
        return path.startswith('\\\\?\\')

    def _extended_to_normal(path):
        if _is_extended(path):
            path = path[4:]
            if path.startswith('UNC\\'):
                path = '\\' + path[3:]
        return path

    def _getfinalpathname(path):
        if not path:
            path = '.'
        elif _is_extended(path):
            return nt._getfinalpathname(path)
        return _extended_to_normal(nt._getfinalpathname(path))

    def resolve(path, strict=False):
        s = str(path)
        if strict:
            return _getfinalpathname(s)
        # Non-strict mode resolves as much as possible while retaining
        # tail components that cannot be resolved if they're missing,
        # inaccessible, or invalid.
        tail_parts = []
        while True:
            try:
                s = _getfinalpathname(s)
                break
            except OSError as e:
                if not (isinstance(e, (FileNotFoundError, PermissionError)) or
                        e.errno == errno.EINVAL):
                    raise
            head, tail = os.path.split(s)
            if head == s:
                return path.absolute()
            s = head
            tail_parts.append(tail)
        if tail_parts:
            s = os.path.join(s, *reversed(tail_parts))
        return s

----------

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

Reply via email to