135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"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
|
|
}
|
|
if !filepath.IsAbs(value) {
|
|
return "", fmt.Errorf("%s must be an absolute Unix path", name)
|
|
}
|
|
return filepath.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
|
|
}
|