@@ -626,12 +626,23 @@ def _getfinalpathname_nonstrict(path, ignored_error=OSError):
626626 allowed_winerror = 1 , 2 , 3 , 5 , 21 , 32 , 50 , 53 , 65 , 67 , 87 , 123 , 161 , 1005 , 1920 , 1921
627627
628628 # Non-strict algorithm is to find as much of the target directory
629- # as we can and join the rest.
629+ # as we can and join the rest. join() is not used, because the tail
630+ # can contain a colon and be mistaken for a drive (gh-102475).
631+ if isinstance (path , bytes ):
632+ sep = b'\\ '
633+ else :
634+ sep = '\\ '
635+
636+ def join (path , tail ):
637+ if path [- 1 :] == sep or not tail :
638+ return path + tail
639+ return path + sep + tail
640+
630641 tail = path [:0 ]
631642 while path :
632643 try :
633644 path = _getfinalpathname (path )
634- return join (path , tail ) if tail else path
645+ return join (path , tail )
635646 except ignored_error as ex :
636647 if ex .winerror not in allowed_winerror :
637648 raise
@@ -642,7 +653,7 @@ def _getfinalpathname_nonstrict(path, ignored_error=OSError):
642653 new_path = _readlink_deep (path ,
643654 ignored_error = ignored_error )
644655 if new_path != path :
645- return join (new_path , tail ) if tail else new_path
656+ return join (new_path , tail )
646657 except ignored_error :
647658 # If we fail to readlink(), let's keep traversing
648659 pass
@@ -657,7 +668,7 @@ def _getfinalpathname_nonstrict(path, ignored_error=OSError):
657668 path , name = split (path )
658669 if path and not name :
659670 return path + tail
660- tail = join (name , tail ) if tail else name
671+ tail = join (name , tail )
661672 return tail
662673
663674 def realpath (path , * , strict = False ):
@@ -666,7 +677,7 @@ def realpath(path, *, strict=False):
666677 prefix = b'\\ \\ ?\\ '
667678 unc_prefix = b'\\ \\ ?\\ UNC\\ '
668679 new_unc_prefix = b'\\ \\ '
669- cwd = os . getcwdb ()
680+ colon_sep = b': \\ '
670681 # bpo-38081: Special case for realpath(b'nul')
671682 devnull = b'nul'
672683 if normcase (path ) == devnull :
@@ -675,7 +686,7 @@ def realpath(path, *, strict=False):
675686 prefix = '\\ \\ ?\\ '
676687 unc_prefix = '\\ \\ ?\\ UNC\\ '
677688 new_unc_prefix = '\\ \\ '
678- cwd = os . getcwd ()
689+ colon_sep = ': \\ '
679690 # bpo-38081: Special case for realpath('nul')
680691 devnull = 'nul'
681692 if normcase (path ) == devnull :
@@ -691,7 +702,9 @@ def realpath(path, *, strict=False):
691702 ignored_error = OSError
692703
693704 if not had_prefix and not isabs (path ):
694- path = join (cwd , path )
705+ # abspath() is used instead of join(cwd, path), because the path
706+ # can be relative to another drive (gh-102475).
707+ path = abspath (path )
695708 try :
696709 path = _getfinalpathname (path )
697710 initial_winerror = 0
@@ -711,25 +724,29 @@ def realpath(path, *, strict=False):
711724 # strip off that prefix unless it was already provided on the original
712725 # path.
713726 if not had_prefix and path .startswith (prefix ):
714- # For UNC paths, the prefix will actually be \\?\UNC\
715- # Handle that case as well.
727+ # For UNC drives, the path starts with \\?\UNC\.
716728 if path .startswith (unc_prefix ):
717729 spath = new_unc_prefix + path [len (unc_prefix ):]
718- else :
730+ # For drive-letter drives, the path starts with \\?\<letter>:\.
731+ elif path .startswith (colon_sep , len (prefix ) + 1 ):
719732 spath = path [len (prefix ):]
720- # Ensure that the non-prefixed path resolves to the same path
721- try :
722- if _getfinalpathname (spath ) == path :
723- path = spath
724- except ValueError as ex :
725- # Unexpected, as an invalid path should not have gained a prefix
726- # at any point, but we ignore this error just in case.
727- pass
728- except OSError as ex :
729- # If the path does not exist and originally did not exist, then
730- # strip the prefix anyway.
731- if ex .winerror == initial_winerror :
732- path = spath
733+ # For all others, e.g. volume GUID paths, it cannot be stripped.
734+ else :
735+ spath = None
736+ if spath is not None :
737+ # Ensure that the non-prefixed path resolves to the same path
738+ try :
739+ if _getfinalpathname (spath ) == path :
740+ path = spath
741+ except ValueError :
742+ # Unexpected, as an invalid path should not have gained a
743+ # prefix at any point, but we ignore this error just in case.
744+ pass
745+ except OSError as ex :
746+ # If the path does not exist and originally did not exist,
747+ # then strip the prefix anyway.
748+ if ex .winerror == initial_winerror :
749+ path = spath
733750 return path
734751
735752
0 commit comments