possible bug

Pretty sure this is a bug, but maybe it’s already been fixed. I looked and it’s possibly related to this issue. Just wondering if anyone can sanity check me before I put in an issue (since that’s what the bug report helper says to do).

Windows machine, python 3.11, it only happens when it’s preceded with exactly two forward slashes.

from pathlib import Path
print(Path("//thing/1/whatever.txt").parts) # ('\\\\thing\\1\\', 'whatever.txt')
print(Path("/thing/1/whatever.txt").parts) # ('\\', 'thing', '1', 'whatever.txt')
print(Path("/////thing/1/whatever.txt").parts) # ('\\', 'thing', '1', 'whatever.txt')

These two cases are correct.

The second example, which begins with a single slash, is a relative rooted path. It’s a relative path because the drive component depends on the current working directory.

The first example is a UNC path, which is always an absolute path. A path that begins with two or more slashes should always be parsed as a UNC path. In this example, “thing” is the server component; “1” is the share component; and “//thing/1” is handled as the ‘drive’ of the UNC path.

If the working directory is set to an existing UNC path, such as “\\server\share\dir”, then a relative rooted path, such as “\spam\eggs”, will be resolved on the UNC ‘drive’ of the working directory – such as the resolved path “\\server\share\spam\eggs”.

This is a bug in 3.11 and earlier versions. The Windows file API always handles a path that begins with two or more slashes as a UNC path. It is wrong to parse this path as a relative rooted path.

In Python 3.12, pathlib.Path parses the result correctly, thanks to Barney Gale’s ongoing efforts to improve pathlib. For example:

>>> Path('/////thing/1/whatever.txt').drive
>>> Path('/////thing/1/whatever.txt').root
>>> Path('/////thing/1/whatever.txt').anchor
>>> Path('/////thing/1/whatever.txt').parts
('\\\\\\\\', 'thing', '1', 'whatever.txt')

It’s parsed as a UNC path with empty server and share components, just as Windows itself does. For example, this can be demonstrated via WinAPI GetFullPathName() with appended ".." components.

>>> GetFullPathName('/////thing/1/..')
>>> GetFullPathName('/////thing/1/../..')
>>> GetFullPathName('/////thing/1/../../..')

The drive part of the UNC path always remains “\\\”, regardless of the number of ".." components appended. Note that the result has an implicit root, which UNC paths in general can have, such as “//server/share” (implicit root) versus “//server/share/” (explicit root).

Unfortunately, the way Windows normalizes a path can have an ambiguously valid result when passed an invalid path. For example:

>>> print(GetFullPathName('//server//thing'))
>>> print(GetFullPathName('//server//thing/..'))

In the first case, Windows collapses the second set of doubled slashes, resulting in the valid UNC path “\\server\thing”. But it doesn’t actually parse “thing” as the share component of the UNC path, as the second case demonstrates, else ".." wouldn’t consume “thing”. This is simply undefined behavior when an invalid UNC path is passed to GetFullPathName(). It’s a case of garbage in, garbage out.