Skip to content

Commit 84ebaa0

Browse files
authored
Do not convert volume target path to Windows style, implement tilde expansion for Windows (#2488)
Since we deal with Linux containers, the volume target path should always be a Unix style path, even on Windows. Prior to this, the code would convert the target path to a Windows style path on Windows hosts due to the use of filepath.Clean. This PR changes the code to use path.Clean for the target instead, which always produces a Unix style path. Pre-existing validation ensures that the user cannot pass Windows style paths as input. Another issue which was discovered by @danbarr as part of this was inconsistent handling of tilde expansion (i.e. converting ~/foo to an absolute path with the user's home folder). This was exacerbated by ToolHive using hand-rolled code to do absolute path expansion when there is a function in the Go standard library to do this. The new code explicitly checks to see if the path starts with ~ on Windows hosts, replaces it with the contents of the USERPROFILE env var if true, and then uses the Go standard library to convert to an absolute path.
1 parent 9598136 commit 84ebaa0

File tree

2 files changed

+23
-18
lines changed

2 files changed

+23
-18
lines changed

pkg/container/docker/client.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"os"
1212
"path/filepath"
13+
rt "runtime"
1314
"slices"
1415
"strconv"
1516
"strings"
@@ -756,15 +757,18 @@ func convertRelativePathToAbsolute(source string, mountDecl permissions.MountDec
756757
return source, true
757758
}
758759

759-
// Get the current working directory
760-
cwd, err := os.Getwd()
760+
// Special case for Windows: expand ~ to user profile directory.
761+
if rt.GOOS == "windows" && strings.HasPrefix(source, "~") {
762+
homeDir := os.Getenv("USERPROFILE")
763+
source = strings.Replace(source, "~", homeDir, 1)
764+
}
765+
766+
absPath, err := filepath.Abs(source)
761767
if err != nil {
762-
logger.Warnf("Warning: Failed to get current working directory: %v", err)
768+
logger.Warnf("Warning: Failed to convert to absolute path: %s (%v)", mountDecl, err)
763769
return "", false
764770
}
765771

766-
// Convert relative path to absolute path
767-
absPath := filepath.Join(cwd, source)
768772
logger.Infof("Converting relative path to absolute: %s -> %s", mountDecl, absPath)
769773
return absPath, true
770774
}

pkg/permissions/profile.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"os"
9+
pkgpath "path"
910
"path/filepath"
1011
"regexp"
1112
"strings"
@@ -198,11 +199,6 @@ func isWindowsPath(path string) bool {
198199
return false
199200
}
200201

201-
// cleanPath cleans a path using filepath.Clean
202-
func cleanPath(path string) string {
203-
return filepath.Clean(path)
204-
}
205-
206202
// validateResourceScheme checks if a resource URI scheme is valid
207203
func validateResourceScheme(scheme string) bool {
208204
return regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`).MatchString(scheme)
@@ -299,8 +295,10 @@ func parseResourceURI(declaration string) (source, target string, err error) {
299295
}
300296

301297
// Clean paths
302-
cleanedResource := cleanPath(resourceName)
303-
cleanedTarget := cleanPath(containerPath)
298+
cleanedResource := filepath.Clean(resourceName)
299+
// For the target, we explicitly use path.Clean so that we do not convert
300+
// Unix style paths into Windows style paths on Windows hosts
301+
cleanedTarget := pkgpath.Clean(containerPath)
304302

305303
return scheme + "://" + cleanedResource, cleanedTarget, nil
306304
}
@@ -324,7 +322,7 @@ func parseWindowsPath(declaration string, colonPositions []int) (source, target
324322
if err := validatePath(declaration); err != nil {
325323
return "", "", err
326324
}
327-
cleanedPath := cleanPath(declaration)
325+
cleanedPath := filepath.Clean(declaration)
328326
return cleanedPath, cleanedPath, nil
329327
}
330328

@@ -346,8 +344,9 @@ func parseWindowsPath(declaration string, colonPositions []int) (source, target
346344
return "", "", err
347345
}
348346

349-
cleanedSource := cleanPath(hostPath)
350-
cleanedTarget := cleanPath(containerPath)
347+
cleanedSource := filepath.Clean(hostPath)
348+
// See comment above about using path.Clean instead of filepath.Clean.
349+
cleanedTarget := pkgpath.Clean(containerPath)
351350
return cleanedSource, cleanedTarget, nil
352351
}
353352

@@ -381,8 +380,9 @@ func parseHostContainerPath(declaration string, colonPositions []int) (source, t
381380
return "", "", err
382381
}
383382

384-
cleanedSource := cleanPath(hostPath)
385-
cleanedTarget := cleanPath(containerPath)
383+
cleanedSource := filepath.Clean(hostPath)
384+
// See comment above about using path.Clean instead of filepath.Clean.
385+
cleanedTarget := pkgpath.Clean(containerPath)
386386
return cleanedSource, cleanedTarget, nil
387387
}
388388

@@ -401,7 +401,8 @@ func parseSinglePath(declaration string) (source, target string, err error) {
401401
return "", "", err
402402
}
403403

404-
cleanedPath := cleanPath(declaration)
404+
// Single path should always be converted to OS-specific cleaned path.
405+
cleanedPath := filepath.Clean(declaration)
405406
return cleanedPath, cleanedPath, nil
406407
}
407408

0 commit comments

Comments
 (0)