diff --git a/sshfs.c b/sshfs.c index c97f66d..39d9ced 100644 --- a/sshfs.c +++ b/sshfs.c @@ -3984,6 +3984,7 @@ static char *tokenize_on_space(char *str) { static char *pos = NULL; char *start = NULL; + char *end = NULL; if (str) pos = str; @@ -3996,22 +3997,27 @@ static char *tokenize_on_space(char *str) pos++; start = pos; + end = pos; while (*pos != '\0') { // break on space, but not on '\ ' - if (*pos == ' ' && *(pos - 1) != '\\') { - break; + if (*pos == ' ') { + if (*(pos - 1) == '\\') { + end--; + } else { + break; + } } - pos++; + *end++ = *pos++; } if (*pos == '\0') { pos = NULL; } else { - *pos = '\0'; pos++; } + *end = '\0'; return start; } diff --git a/test/test_sshfs.py b/test/test_sshfs.py index c4cc64f..1c1d4eb 100755 --- a/test/test_sshfs.py +++ b/test/test_sshfs.py @@ -1080,3 +1080,33 @@ def test_contain_symlinks_option_precedence(tmpdir, capfd) -> None: with pytest.raises(OSError) as exc_info: os.readlink(pjoin(mnt_dir, "abs")) assert exc_info.value.errno == errno.EPERM + + +def test_backslash_escape(tmpdir, capfd): + """Regression test for parsing backslash escape sequences in options""" + + cases = [ + (r"one two three", " "), + (r"one\\ two three", " "), + # fuse_opt_parse interprets "\ " as " ", so these are still separate + (r"one two\ three", " "), + ] + + mnt_dir = str(tmpdir.mkdir("mnt")) + # we can skip cleanup since the mount always fails + + for line, args in cases: + cmdline = base_cmdline + [ + pjoin(basename, "sshfs"), + "-f", + "localhost:foo", + mnt_dir, + "-o", + "sshfs_debug", + "-o", + "ssh_command=/dev/null " + line, + ] + assert subprocess.run(cmdline).returncode != 0 + + captured = capfd.readouterr() + assert "executing " + args in captured.err