From 1ffd11f2de8964ecdc5e0aa466f7ea78253aae66 Mon Sep 17 00:00:00 2001 From: VicJay Date: Wed, 25 Mar 2026 11:39:01 -0700 Subject: [PATCH] fix: skip MSYS2 path conversion for rsync-over-SSH remote paths rsync passes paths like "user@host:/c/path" as arguments to SSH. The ":/c/" substring triggers MSYS2 drive-letter path conversion, causing "source and destination cannot both be remote" errors. Detect SCP/rsync remote-path syntax inside find_path_start_and_type() by strictly validating the user@host:/path pattern: - '@' is recorded only when the prefix consists entirely of valid username characters (alphanumeric, dots, dashes). - In the ':' arm, skip conversion only when the hostname between '@' and ':' also consists of valid hostname characters, AND the colon is followed by '/' then an alphanumeric character. This avoids false positives from filenames containing '@' or path lists with colons, while still catching the standard SCP syntax. SSH config aliases (my-machine:/path) without '@' are not covered. Suggested-by: Johannes Schindelin --- winsup/cygwin/msys2_path_conv.cc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/winsup/cygwin/msys2_path_conv.cc b/winsup/cygwin/msys2_path_conv.cc index 4c0cc82cf2..471a2185bf 100644 --- a/winsup/cygwin/msys2_path_conv.cc +++ b/winsup/cygwin/msys2_path_conv.cc @@ -364,6 +364,12 @@ path_type find_path_start_and_type(const char** src, int recurse, const char* en if (*it == ':') goto skip_p2w; + // Track '@' position for SCP/rsync remote path detection. + // Only set when the prefix looks like a valid username + // (alphanumeric, dots, dashes). +#define ALNUMDD ".-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const char *at = NULL; + while (it != end && *it) { switch (*it) { case '`': @@ -389,11 +395,24 @@ path_type find_path_start_and_type(const char** src, int recurse, const char* en if (it + 3 < end && it[2] == '.' && it[3] == '/') goto skip_p2w; } + + // Skip SCP/rsync remote paths: user@host:/path + // Verify: at was set, hostname between at+1 and colon + // is all valid hostname chars, and colon is followed + // by / then an alphanumeric character. + if (at && strspn(at + 1, ALNUMDD) == (size_t)(it - at - 1) + && it + 2 < end && it[1] == '/' && isalnum(it[2])) + goto skip_p2w; break; case '@': // Paths do not contain '@@' if (it + 1 < end && it[1] == '@') goto skip_p2w; + + // Record '@' only if the prefix is a valid username + if (!at && strspn(*src, ALNUMDD) == (size_t)(it - *src)) + at = it; + break; } ++it; }