fix: corrects handling of UNC root paths in windows.

---------
Signed-off-by: Luis M. Santos <luis.santos2@nih.gov>
Co-authored-by: Luis M. Santos <luis.santos2@nih.gov>
This commit is contained in:
Luis Miguel Santos 2026-01-05 11:32:07 -05:00 committed by GitHub
parent 2c96279df9
commit ea43506196
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 78 additions and 1 deletions

View File

@ -8,6 +8,10 @@ import (
) )
const windowsGoOS = "windows" const windowsGoOS = "windows"
const windowsUNCPathPrefix = "\\\\"
const windowsDriveColon = ":"
const windowsDrivePathTerminator = ":\\"
const windowsUNCPathTerminator = "\\"
func HostRunningOnWindows() bool { func HostRunningOnWindows() bool {
return runtime.GOOS == windowsGoOS return runtime.GOOS == windowsGoOS
@ -30,8 +34,10 @@ func ToPosix(windowsPath string) (posixPath string) {
func FromPosix(posixPath string) (windowsPath string) { func FromPosix(posixPath string) (windowsPath string) {
// decode the volume (e.g. /c/<path> --> C:\\) - There should always be a volume name. // decode the volume (e.g. /c/<path> --> C:\\) - There should always be a volume name.
// The volume may be a UNC path (e.g. /\\localhost\C$\ --> \\localhost\C$\)
pathFields := strings.Split(posixPath, "/") pathFields := strings.Split(posixPath, "/")
volumeName := strings.ToUpper(pathFields[1]) + `:\\` rootPath := strings.ToUpper(pathFields[1])
volumeName := AppendRootTerminator(rootPath)
// translate non-escaped forward slashes into backslashes // translate non-escaped forward slashes into backslashes
remainingTranslatedPath := strings.Join(pathFields[2:], "\\") remainingTranslatedPath := strings.Join(pathFields[2:], "\\")
@ -39,3 +45,17 @@ func FromPosix(posixPath string) (windowsPath string) {
// combine volume name and backslash components // combine volume name and backslash components
return filepath.Clean(volumeName + remainingTranslatedPath) return filepath.Clean(volumeName + remainingTranslatedPath)
} }
func AppendRootTerminator(rootPath string) string {
// UNC paths start with \\ => \\localhost\
// Windows drive paths start with a letter and a colon => C:\
// See https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths
// This function should not be used on file paths since it is meant for root directory paths!
if strings.HasSuffix(rootPath, windowsDrivePathTerminator) {
return rootPath
}
if strings.HasPrefix(rootPath, windowsUNCPathPrefix) || strings.HasSuffix(rootPath, windowsDriveColon) {
return rootPath + windowsUNCPathTerminator
}
return rootPath + windowsDrivePathTerminator
}

View File

@ -0,0 +1,57 @@
package windows
import (
"testing"
)
func TestAppendRootTerminator(t *testing.T) {
tests := []struct {
name string
path string
expect string
}{
{
name: "NormalUNC",
path: "\\\\localhost\\myserver\\",
expect: "\\\\localhost\\myserver\\\\",
},
{
name: "DriveUNC",
path: "\\\\localhost\\C$\\",
expect: "\\\\localhost\\C$\\\\",
},
{
name: "DriveUNCDirectoryPath",
path: "\\\\localhost\\C$\\Program Files\\Microsoft Visual Studio 10.0\\Common7\\IDE\\PrivateAssemblies\\",
expect: "\\\\localhost\\C$\\Program Files\\Microsoft Visual Studio 10.0\\Common7\\IDE\\PrivateAssemblies\\\\",
},
{
name: "DriveC",
path: "C",
expect: "C:\\",
},
{
name: "DriveC2",
path: "C:",
expect: "C:\\",
},
{
name: "DriveC\\",
path: "C:\\",
expect: "C:\\",
},
{
name: "DriveCDefaultLegacyBehavior",
path: "C:\\Program Files\\Microsoft Visual Studio 10.0\\Common7\\IDE\\PrivateAssemblies\\",
expect: "C:\\Program Files\\Microsoft Visual Studio 10.0\\Common7\\IDE\\PrivateAssemblies\\:\\",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := AppendRootTerminator(tt.path)
if got != tt.expect {
t.Errorf("FromPosix() got = %v, expects %v", got, tt.expect)
}
})
}
}