package config import ( "fmt" "os" "path" "path/filepath" "runtime" "strings" ) func validateAbsolutePath(name, value string, platform Platform, allowEmpty bool) (string, error) { if value == "" && allowEmpty { return "", nil } if value == "" || strings.IndexByte(value, 0) >= 0 { return "", fmt.Errorf("%s must be a nonempty absolute path", name) } if platform == PlatformWindows { if !isWindowsAbsolute(value) { return "", fmt.Errorf("%s must be an absolute Windows path", name) } return cleanWindowsPath(value), nil } // Config parsing can validate an inactive Unix shell path while running on // Windows. filepath follows the host OS, whereas the config field's stated // platform is Unix, so use slash-path semantics here. if !path.IsAbs(value) { return "", fmt.Errorf("%s must be an absolute Unix path", name) } return path.Clean(value), nil } func isWindowsAbsolute(value string) bool { if strings.HasPrefix(value, `\\`) { parts := strings.Split(strings.TrimPrefix(value, `\\`), `\`) return len(parts) >= 2 && parts[0] != "" && parts[1] != "" } return len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' && (value[2] == '\\' || value[2] == '/') } func cleanWindowsPath(value string) string { value = strings.ReplaceAll(value, "/", `\`) prefix := value[:3] rest := strings.TrimPrefix(value[3:], `\`) if strings.HasPrefix(value, `\\`) { components := strings.Split(strings.TrimPrefix(value, `\\`), `\`) prefix = `\\` + components[0] + `\` + components[1] + `\` rest = strings.Join(components[2:], `\`) } parts := make([]string, 0) for _, part := range strings.Split(rest, `\`) { switch part { case "", ".": continue case "..": if len(parts) > 0 { parts = parts[:len(parts)-1] } default: parts = append(parts, part) } } return prefix + strings.Join(parts, `\`) } func checkNativeExecutable(name, value string, platform Platform) (string, error) { if (platform == PlatformWindows) != (runtime.GOOS == "windows") { return "", fmt.Errorf("%s filesystem validation requires a native %s process", name, map[bool]string{true: "Windows", false: "Unix"}[platform == PlatformWindows]) } canonical, err := filepath.Abs(value) if err != nil { return "", fmt.Errorf("%s: %w", name, err) } if evaluated, evalErr := filepath.EvalSymlinks(canonical); evalErr == nil { canonical = evaluated } info, err := os.Stat(canonical) if err != nil { return "", fmt.Errorf("%s: %w", name, err) } if !info.Mode().IsRegular() { return "", fmt.Errorf("%s must name a regular file", name) } if platform == PlatformUnix && info.Mode().Perm()&0o111 == 0 { return "", fmt.Errorf("%s is not executable", name) } if platform == PlatformWindows { extension := strings.ToLower(filepath.Ext(canonical)) if extension != ".exe" && extension != ".com" && extension != ".cmd" && extension != ".bat" { return "", fmt.Errorf("%s is not a recognized Windows executable", name) } } return canonical, nil } func checkNativeDirectory(name, value string, platform Platform) (string, error) { if (platform == PlatformWindows) != (runtime.GOOS == "windows") { return "", fmt.Errorf("%s filesystem validation requires a native target", name) } canonical, err := filepath.Abs(value) if err != nil { return "", fmt.Errorf("%s: %w", name, err) } if evaluated, evalErr := filepath.EvalSymlinks(canonical); evalErr == nil { canonical = evaluated } info, err := os.Stat(canonical) if err != nil { return "", fmt.Errorf("%s: %w", name, err) } if !info.IsDir() { return "", fmt.Errorf("%s must name a directory", name) } return canonical, nil } func checkNativeRegularFile(name, value string, platform Platform) (string, error) { if (platform == PlatformWindows) != (runtime.GOOS == "windows") { return "", fmt.Errorf("%s filesystem validation requires a native target", name) } canonical, err := filepath.Abs(value) if err != nil { return "", fmt.Errorf("%s: %w", name, err) } if evaluated, evalErr := filepath.EvalSymlinks(canonical); evalErr == nil { canonical = evaluated } info, err := os.Stat(canonical) if err != nil { return "", fmt.Errorf("%s: %w", name, err) } if !info.Mode().IsRegular() { return "", fmt.Errorf("%s must name a regular file", name) } return canonical, nil }