diff --git a/syft/internal/windows/path.go b/syft/internal/windows/path.go index 7aa59d1ca..409917103 100644 --- a/syft/internal/windows/path.go +++ b/syft/internal/windows/path.go @@ -8,6 +8,10 @@ import ( ) const windowsGoOS = "windows" +const windowsUNCPathPrefix = "\\\\" +const windowsDriveColon = ":" +const windowsDrivePathTerminator = ":\\" +const windowsUNCPathTerminator = "\\" func HostRunningOnWindows() bool { return runtime.GOOS == windowsGoOS @@ -30,8 +34,10 @@ func ToPosix(windowsPath string) (posixPath string) { func FromPosix(posixPath string) (windowsPath string) { // decode the volume (e.g. /c/ --> 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, "/") - volumeName := strings.ToUpper(pathFields[1]) + `:\\` + rootPath := strings.ToUpper(pathFields[1]) + volumeName := AppendRootTerminator(rootPath) // translate non-escaped forward slashes into backslashes remainingTranslatedPath := strings.Join(pathFields[2:], "\\") @@ -39,3 +45,17 @@ func FromPosix(posixPath string) (windowsPath string) { // combine volume name and backslash components 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 +} diff --git a/syft/internal/windows/path_test.go b/syft/internal/windows/path_test.go new file mode 100644 index 000000000..229964c6d --- /dev/null +++ b/syft/internal/windows/path_test.go @@ -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) + } + }) + } +}