package pathsafe import ( "errors" "path" "regexp" "strings" ) var ( ErrRelativePathRequired = errors.New("relative path is required") ErrRelativePathAbsolute = errors.New("relative path must not be absolute") ErrRelativePathEscape = errors.New("relative path escapes root") ) var windowsAbsPathRE = regexp.MustCompile(`^[a-zA-Z]:[\\/]`) // NormalizeRelativeDestination validates and normalizes a relative destination // path to slash-separated form. func NormalizeRelativeDestination(relative string) (string, error) { trimmed := strings.TrimSpace(relative) if trimmed == "" { return "", ErrRelativePathRequired } if strings.HasPrefix(trimmed, "/") || strings.HasPrefix(trimmed, `\`) || windowsAbsPathRE.MatchString(trimmed) { return "", ErrRelativePathAbsolute } normalized := strings.ReplaceAll(trimmed, `\`, "/") cleaned := path.Clean(normalized) if cleaned == "." || cleaned == "" { return "", ErrRelativePathRequired } if cleaned == ".." || strings.HasPrefix(cleaned, "../") { return "", ErrRelativePathEscape } return cleaned, nil }