Compare commits

..

8 Commits

Author SHA1 Message Date
Leonard Hecker
d22203fa0a Missed one 2026-04-17 21:38:08 +02:00
Leonard Hecker
e3deb12e34 AuditMode fixes 2026-04-17 20:35:03 +02:00
Leonard Hecker
d7250ac44a Avoid IsValidDirectory for WSL mangling 2026-04-17 18:54:16 +02:00
Leonard Hecker
71db27815d Merge remote-tracking branch 'origin/main' into dev/lhecker/osc-7-wsl 2026-04-17 14:40:57 +02:00
Dustin L. Howett
a240d3ec04 turn the big mangling test into a table 2026-04-14 17:50:18 -05:00
Dustin L. Howett
9748965027 Add a test for the OSC 7 style mangling, fix wsl 2026-04-14 17:24:12 -05:00
Leonard Hecker
05282a311a Begone 2026-04-08 23:20:46 +02:00
Leonard Hecker
99c748c5ef Implement UNC path mangling for OSC 7 via WSL 2026-04-08 23:03:57 +02:00
6 changed files with 287 additions and 231 deletions

View File

@@ -1600,7 +1600,7 @@ namespace winrt::TerminalApp::implementation
// Replace the Starting directory with the CWD, if given
const auto workingDirectory = control.WorkingDirectory();
if (Utils::IsValidDirectory(workingDirectory.c_str()))
if (!workingDirectory.empty())
{
controlSettings.DefaultSettings()->StartingDirectory(workingDirectory);
}
@@ -3607,7 +3607,7 @@ namespace winrt::TerminalApp::implementation
profile = GetClosestProfileForDuplicationOfProfile(profile);
controlSettings = Settings::TerminalSettings::CreateWithProfile(_settings, profile);
const auto workingDirectory = tabImpl->GetActiveTerminalControl().WorkingDirectory();
if (Utils::IsValidDirectory(workingDirectory.c_str()))
if (!workingDirectory.empty())
{
controlSettings.DefaultSettings()->StartingDirectory(workingDirectory);
}

View File

@@ -102,7 +102,7 @@ namespace winrt::TerminalApp::implementation
args.Profile(::Microsoft::Console::Utils::GuidToString(_profile.Guid()));
// If we know the user's working directory use it instead of the profile.
if (const auto dir = _control.WorkingDirectory(); ::Microsoft::Console::Utils::IsValidDirectory(dir.c_str()))
if (const auto dir = _control.WorkingDirectory(); !dir.empty())
{
args.StartingDirectory(dir);
}

View File

@@ -1693,8 +1693,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::OpenCWD()
{
const auto workingDirectory = WorkingDirectory();
ShellExecute(nullptr, nullptr, L"explorer", workingDirectory.c_str(), nullptr, SW_SHOW);
if (const auto cwd = WorkingDirectory(); Utils::IsValidDirectory(cwd.c_str()))
{
ShellExecute(nullptr, nullptr, L"explorer", cwd.c_str(), nullptr, SW_SHOW);
}
}
void ControlCore::ClearQuickFix()

View File

@@ -5,6 +5,7 @@
#include "AllShortcutActions.h"
#include "ActionMap.h"
#include "Command.h"
#include <til/io.h>
#include "ActionMap.g.cpp"
@@ -1289,7 +1290,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::hstring currentWorkingDirectory)
{
// enumerate all the parent directories we want to import snippets from
std::filesystem::path directory{ std::wstring_view{ currentWorkingDirectory } };
std::filesystem::path directory;
if (::Microsoft::Console::Utils::IsValidDirectory(currentWorkingDirectory.c_str()))
{
directory.assign(std::wstring_view{ currentWorkingDirectory });
}
std::vector<std::filesystem::path> directories;
while (!directory.empty())
{

View File

@@ -356,165 +356,77 @@ void UtilsTests::TestMangleWSLPaths()
{
// Continue on failures
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
const SetVerifyOutput verifySettings{ VerifyOutputSettings::LogOnlyFailures };
wchar_t computerName[64];
DWORD computerNameLen = ARRAYSIZE(computerName);
THROW_IF_WIN32_BOOL_FALSE(GetComputerNameExW(ComputerNamePhysicalDnsHostname, computerName, &computerNameLen));
const auto startingDirectory{ L"SENTINEL" };
// MUST MANGLE
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl" --cd "SENTINEL" )", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
const auto expectedUserProfilePath = wil::GetEnvironmentVariableW<std::wstring>(L"USERPROFILE");
const auto osc7 = fmt::format(FMT_COMPILE(LR"(\\{}\home\user)"), &computerName[0]);
struct Test
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl" --cd "SENTINEL" -d X)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
std::wstring_view commandline;
std::wstring_view startingDirectory;
std::wstring_view expectedCommandline;
std::wstring_view expectedPath;
} testCases[] = {
// clang-format off
// command line plus starting directory should become new command line and new starting directory
// MUST MANGLE
{ LR"(wsl)", startingDirectory, LR"("wsl" --cd "SENTINEL" )", L"" },
{ LR"(wsl -d X)", startingDirectory, LR"("wsl" --cd "SENTINEL" -d X)", L"" },
{ LR"(wsl -d X ~/bin/sh)", startingDirectory, LR"("wsl" --cd "SENTINEL" -d X ~/bin/sh)", L"" },
{ LR"(wsl.exe)", startingDirectory, LR"("wsl.exe" --cd "SENTINEL" )", L"" },
{ LR"(wsl.exe -d X)", startingDirectory, LR"("wsl.exe" --cd "SENTINEL" -d X)", L"" },
{ LR"(wsl.exe -d X ~/bin/sh)", startingDirectory, LR"("wsl.exe" --cd "SENTINEL" -d X ~/bin/sh)", L"" },
{ LR"("wsl")", startingDirectory, LR"("wsl" --cd "SENTINEL" )", L"" },
{ LR"("wsl.exe")", startingDirectory, LR"("wsl.exe" --cd "SENTINEL" )", L"" },
{ LR"("wsl" -d X)", startingDirectory, LR"("wsl" --cd "SENTINEL" -d X)", L"" },
{ LR"("wsl.exe" -d X)", startingDirectory, LR"("wsl.exe" --cd "SENTINEL" -d X)", L"" },
{ LR"("C:\Windows\system32\wsl.exe" -d X)", startingDirectory, LR"("C:\Windows\system32\wsl.exe" --cd "SENTINEL" -d X)", L"" },
{ LR"("C:\windows\system32\wsl" -d X)", startingDirectory, LR"("C:\windows\system32\wsl" --cd "SENTINEL" -d X)", L"" },
{ LR"(wsl ~/bin)", startingDirectory, LR"("wsl" --cd "SENTINEL" ~/bin)", L"" },
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d X ~/bin/sh)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl" --cd "SENTINEL" -d X ~/bin/sh)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
// Any wsl.exe is treated as a WSL profile, regardless of directory.
{ LR"("C:\wsl.exe" -d X)", startingDirectory, LR"("C:\wsl.exe" --cd "SENTINEL" -d X)", L"" },
{ LR"(C:\wsl.exe)", startingDirectory, LR"("C:\wsl.exe" --cd "SENTINEL" )", L"" },
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl.exe)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl.exe" --cd "SENTINEL" )", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
// MUST NOT MANGLE
{ LR"(wsl --cd C:\)", startingDirectory, LR"(wsl --cd C:\)", startingDirectory },
{ LR"(wsl ~)", startingDirectory, LR"(wsl ~)", startingDirectory },
{ LR"(wsl ~ -d Ubuntu)", startingDirectory, LR"(wsl ~ -d Ubuntu)", startingDirectory },
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl.exe -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl.exe" --cd "SENTINEL" -d X)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl.exe -d X ~/bin/sh)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl.exe" --cd "SENTINEL" -d X ~/bin/sh)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("wsl")", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl" --cd "SENTINEL" )", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("wsl.exe")", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl.exe" --cd "SENTINEL" )", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("wsl" -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl" --cd "SENTINEL" -d X)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("wsl.exe" -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl.exe" --cd "SENTINEL" -d X)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("C:\Windows\system32\wsl.exe" -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("C:\Windows\system32\wsl.exe" --cd "SENTINEL" -d X)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("C:\windows\system32\wsl" -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("C:\windows\system32\wsl" --cd "SENTINEL" -d X)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl ~/bin)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("wsl" --cd "SENTINEL" ~/bin)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
// MUST NOT MANGLE
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"("C:\wsl.exe" -d X)", startingDirectory);
VERIFY_ARE_EQUAL(LR"("C:\wsl.exe" -d X)", commandline);
VERIFY_ARE_EQUAL(startingDirectory, path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(C:\wsl.exe)", startingDirectory);
VERIFY_ARE_EQUAL(LR"(C:\wsl.exe)", commandline);
VERIFY_ARE_EQUAL(startingDirectory, path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl --cd C:\)", startingDirectory);
VERIFY_ARE_EQUAL(LR"(wsl --cd C:\)", commandline);
VERIFY_ARE_EQUAL(startingDirectory, path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl ~)", startingDirectory);
VERIFY_ARE_EQUAL(LR"(wsl ~)", commandline);
VERIFY_ARE_EQUAL(startingDirectory, path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl ~ -d Ubuntu)", startingDirectory);
VERIFY_ARE_EQUAL(LR"(wsl ~ -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(startingDirectory, path);
}
{
// Test for GH#11994 - make sure `//wsl$/` paths get mangled back to
// `\\wsl$\`, to workaround a potential bug in `wsl --cd`
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d Ubuntu)", LR"(//wsl$/Ubuntu/home/user)");
VERIFY_ARE_EQUAL(LR"("wsl" --cd "\\wsl$\Ubuntu\home\user" -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d Ubuntu)", LR"(\\wsl$\Ubuntu\home\user)");
VERIFY_ARE_EQUAL(LR"("wsl" --cd "\\wsl$\Ubuntu\home\user" -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{ LR"(wsl -d Ubuntu)", LR"(//wsl$/Ubuntu/home/user)", LR"("wsl" --cd "\\wsl$\Ubuntu\home\user" -d Ubuntu)", L"" },
{ LR"(wsl -d Ubuntu)", LR"(\\wsl$\Ubuntu\home\user)", LR"("wsl" --cd "\\wsl$\Ubuntu\home\user" -d Ubuntu)", L"" },
{
// Same, but with `wsl.localhost`
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d Ubuntu)", LR"(//wsl.localhost/Ubuntu/home/user)");
VERIFY_ARE_EQUAL(LR"("wsl" --cd "\\wsl.localhost\Ubuntu\home\user" -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d Ubuntu)", LR"(\\wsl.localhost\Ubuntu\home\user)");
VERIFY_ARE_EQUAL(LR"("wsl" --cd "\\wsl.localhost\Ubuntu\home\user" -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{ LR"(wsl -d Ubuntu)", LR"(//wsl.localhost/Ubuntu/home/user)", LR"("wsl" --cd "\\wsl.localhost\Ubuntu\home\user" -d Ubuntu)", L"" },
{ LR"(wsl -d Ubuntu)", LR"(\\wsl.localhost\Ubuntu\home\user)", LR"("wsl" --cd "\\wsl.localhost\Ubuntu\home\user" -d Ubuntu)", L"" },
/// Tests for GH #12353
/// Tests for OSC 7-style file URI to UNC path mangling
{ LR"(wsl -d Ubuntu)", osc7, LR"("wsl" --cd "/home/user" -d Ubuntu)", L"" },
const auto expectedUserProfilePath = wil::ExpandEnvironmentStringsW<std::wstring>(L"%USERPROFILE%");
/// Tests for GH #12353
{ LR"(wsl -d Ubuntu)", L"~", LR"("wsl" --cd "~" -d Ubuntu)", L"" },
{ LR"(wsl ~ -d Ubuntu)", L"~", LR"(wsl ~ -d Ubuntu)", expectedUserProfilePath },
{ LR"(ubuntu ~ -d Ubuntu)", L"~", LR"(ubuntu ~ -d Ubuntu)", expectedUserProfilePath },
{ LR"(powershell.exe)", L"~", LR"(powershell.exe)", expectedUserProfilePath },
// clang-format on
};
size_t i = 0;
for (auto&& testCase : testCases)
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl -d Ubuntu)", L"~");
VERIFY_ARE_EQUAL(LR"("wsl" --cd "~" -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(L"", path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(wsl ~ -d Ubuntu)", L"~");
VERIFY_ARE_EQUAL(LR"(wsl ~ -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(expectedUserProfilePath, path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(ubuntu ~ -d Ubuntu)", L"~");
VERIFY_ARE_EQUAL(LR"(ubuntu ~ -d Ubuntu)", commandline);
VERIFY_ARE_EQUAL(expectedUserProfilePath, path);
}
{
auto [commandline, path] = MangleStartingDirectoryForWSL(LR"(powershell.exe)", L"~");
VERIFY_ARE_EQUAL(LR"(powershell.exe)", commandline);
VERIFY_ARE_EQUAL(expectedUserProfilePath, path);
auto testMessage{ WEX::Common::NoThrowString{}.Format(L"test case %zu", i) };
auto [actualCommandline, actualPath] = MangleStartingDirectoryForWSL(testCase.commandline, testCase.startingDirectory);
VERIFY_ARE_EQUAL(testCase.expectedCommandline, actualCommandline, testMessage);
VERIFY_ARE_EQUAL(testCase.expectedPath, actualPath, testMessage);
++i;
}
}
#endif

View File

@@ -1016,6 +1016,193 @@ bool Utils::IsRunningElevated()
return isElevated;
}
// Checks whether the command line starts with wsl(.exe) as its executable.
// This is intentionally permissive: ANY wsl.exe is treated as a WSL profile.
// The only goal is to avoid false positives like "cmd /c wsl ..." where wsl
// appears as an argument rather than the executable.
static bool _isWslExe(
std::wstring_view commandLine,
std::wstring_view& outExePath,
std::wstring_view& outArguments)
{
if (commandLine.size() < 3)
{
return false;
}
// Shared by both branches: split arguments after the exe, skipping one optional space.
const auto splitArgs = [&](size_t pos) noexcept {
if (pos < commandLine.size() && til::at(commandLine, pos) == L' ')
{
pos++;
}
outArguments = til::safe_slice_abs(commandLine, pos, std::wstring_view::npos);
return true;
};
// Quoted: the executable path extends from the first to the second double-quote.
if (commandLine.front() == L'"')
{
const auto close = commandLine.find(L'"', 1);
if (close == std::wstring_view::npos)
{
return false;
}
const auto path = commandLine.substr(1, close - 1);
const auto sep = path.find_last_of(L"\\/");
const auto name = sep == std::wstring_view::npos ? path : path.substr(sep + 1);
if (!til::equals_insensitive_ascii(name, L"wsl") &&
!til::equals_insensitive_ascii(name, L"wsl.exe"))
{
return false;
}
outExePath = path;
return splitArgs(close + 1);
}
const auto beg = commandLine.begin();
const auto end = commandLine.end();
auto it = beg;
// Unquoted: find "wsl" as a filename component preceded by '\', '/' or start-of-string,
// optionally followed by ".exe", then ' ' or end-of-string.
for (;;)
{
static constexpr auto needle = L"wsl";
it = std::search(
it,
end,
needle,
needle + 3,
[](wchar_t c1, wchar_t c2) noexcept {
return til::tolower_ascii(c1) == c2;
});
if (it == end)
{
return false;
}
// Ensure that no matter what, we advance at least 1 char per iteration.
++it;
// Verify if it's "/wsl" and not some freestanding word.
if ((it - beg) >= 2 && til::at(it, -2) != L'\\' && til::at(it, -2) != L'/')
{
continue;
}
// Skip past "wsl" (we have already advanced by 1 above).
it += 2;
// Consume an optional ".exe" suffix.
const auto hasExeSuffix = (end - it) >= 4 && til::equals_insensitive_ascii(std::wstring_view{ &*it, 4 }, L".exe");
if (hasExeSuffix)
{
it += 4;
}
// Ensure that wsl.exe is followed by either whitespace or the end of the string.
// (Aka: It's its own word.)
if (it != end && *it != L' ')
{
continue;
}
const std::wstring_view candidate{ beg, it };
// For paths with spaces (e.g. "C:\Program Files\WSL\wsl.exe ..."), the
// boundary between exe and arguments is ambiguous. Verify the file exists.
if (candidate.find(L' ') != std::wstring_view::npos)
{
std::wstring path;
path.reserve(candidate.size() + 4);
path.append(candidate);
if (!hasExeSuffix)
{
path.append(L".exe");
}
const auto attrs = GetFileAttributesW(path.c_str());
if (attrs == INVALID_FILE_ATTRIBUTES || (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
continue;
}
}
outExePath = candidate;
return splitArgs(it - beg);
}
}
// Checks whether the given hostname refers to the local machine.
// WSL uses GetComputerNameExA(ComputerNamePhysicalDnsHostname) to produce
// the hostname in OSC 7 URIs (see WSL's GetLinuxHostName/CleanHostname).
static bool _isLocalHost(std::wstring_view hostname)
{
if (til::equals_insensitive_ascii(hostname, L"localhost"))
{
return true;
}
static const auto cachedHostname = []() {
wchar_t buf[64];
DWORD len = ARRAYSIZE(buf);
if (!GetComputerNameExW(ComputerNamePhysicalDnsHostname, &buf[0], &len))
{
len = 0;
}
return std::wstring{ &buf[0], static_cast<size_t>(len) };
}();
return til::equals_insensitive_ascii(hostname, cachedHostname);
}
static std::wstring _mangleStartingDirectoryForWSL(std::wstring_view startingDirectory)
{
std::wstring dir{ startingDirectory };
if (til::starts_with(dir, L"//wsl$") || til::starts_with(dir, L"//wsl.localhost"))
{
// GH#11994: `wsl --cd` treats forward-slash paths as linux-relative.
// We routinely see these two paths being used, but they're actually
// meant as Windows paths, so we convert them to \\ here.
std::ranges::replace(dir, L'/', L'\\');
return dir;
}
if (!til::starts_with(dir, LR"(\\)"))
{
// Any other paths, primarily C:\..., etc., are not our concern.
return dir;
}
if (til::starts_with(dir, LR"(\\wsl$)") || til::starts_with(dir, LR"(\\wsl.localhost)"))
{
// Some users have configured shells in WSL to emit OSC 9;9 paths with wslpath.
// Do nothing with them (pass them through with --cd.)
return dir;
}
// WSL shells use OSC 7 which uses file URIs. We turn those into UNC paths for
// storage and here we turn them back (\\hostname\bar\baz --> /bar/baz).
// Only do this for UNC paths whose hostname refers to the local machine.
// Our OSC 7 parser already rejects lexically invalid paths (via til::is_legal_path).
const auto slash = dir.find_first_of(L"\\/", 2);
const auto hostname = til::safe_slice_abs(dir, 2, slash);
if (!_isLocalHost(hostname))
{
// Leave (most likely) genuine UNC SMB paths alone.
return dir;
}
// Extract the path component and convert it to forward slashes (= UNIX path).
dir.erase(0, std::min(slash, dir.size()));
std::ranges::replace(dir, L'\\', L'/');
return dir;
}
// Function Description:
// - Promotes a starting directory provided to a WSL invocation to a commandline argument.
// This is necessary because WSL has some modicum of support for linux-side directories (!) which
@@ -1023,87 +1210,36 @@ bool Utils::IsRunningElevated()
std::tuple<std::wstring, std::wstring> Utils::MangleStartingDirectoryForWSL(std::wstring_view commandLine,
std::wstring_view startingDirectory)
{
do
{
if (startingDirectory.size() > 0 && commandLine.size() >= 3)
{ // "wsl" is three characters; this is a safe bet. no point in doing it if there's no starting directory though!
// Find the first space, quote or the end of the string -- we'll look for wsl before that.
const auto terminator{ commandLine.find_first_of(LR"(" )", 1) }; // look past the first character in case it starts with "
const auto start{ til::at(commandLine, 0) == L'"' ? 1 : 0 };
const std::filesystem::path executablePath{ commandLine.substr(start, terminator - start) };
const auto executableFilename{ executablePath.filename() };
if (executableFilename == L"wsl" || executableFilename == L"wsl.exe")
{
// We've got a WSL -- let's just make sure it's the right one.
if (executablePath.has_parent_path())
{
std::wstring systemDirectory{};
if (FAILED(wil::GetSystemDirectoryW(systemDirectory)))
{
break; // just bail out.
}
if (!til::equals_insensitive_ascii(executablePath.parent_path().native(), systemDirectory))
{
break; // it wasn't in system32!
}
}
else
{
// assume that unqualified WSL is the one in system32 (minor danger)
}
const auto arguments{ terminator == std::wstring_view::npos ? std::wstring_view{} : commandLine.substr(terminator + 1) };
if (arguments.find(L"--cd") != std::wstring_view::npos)
{
break; // they've already got a --cd!
}
const auto tilde{ arguments.find_first_of(L'~') };
if (tilde != std::wstring_view::npos)
{
if (tilde + 1 == arguments.size() || til::at(arguments, tilde + 1) == L' ')
{
// We want to suppress --cd if they have added a bare ~ to their commandline (they conflict).
break;
}
// Tilde followed by non-space should be okay (like, wsl -d Debian ~/blah.sh)
}
// GH#11994 - If the path starts with //wsl$, then the user is
// likely passing a Windows-style path to the WSL filesystem,
// but with forward slashes instead of backslashes.
// Unfortunately, `wsl --cd` will try to treat this as a
// linux-relative path, which will fail to do the expected
// thing.
//
// In that case, manually mangle the startingDirectory to use
// backslashes as the path separator instead.
std::wstring mangledDirectory{ startingDirectory };
if (til::starts_with(mangledDirectory, L"//wsl$") || til::starts_with(mangledDirectory, L"//wsl.localhost"))
{
mangledDirectory = std::filesystem::path{ startingDirectory }.make_preferred().wstring();
}
return {
fmt::format(FMT_COMPILE(LR"("{}" --cd "{}" {})"), executablePath.native(), mangledDirectory, arguments),
std::wstring{}
};
}
}
} while (false);
// GH #12353: `~` is never a valid windows path. We can only accept that as
// a startingDirectory when the exe is specifically wsl.exe, because that
// can override the real startingDirectory. If the user set the
// startingDirectory to ~, but the commandline to something like pwsh.exe,
// that won't actually work. In that case, mangle the startingDirectory to
// %userprofile%, so it's at least something reasonable.
return {
std::wstring{ commandLine },
startingDirectory == L"~" ? wil::ExpandEnvironmentStringsW<std::wstring>(L"%USERPROFILE%") :
std::wstring{ startingDirectory }
// Returns true if arguments contain a bare ~ (end-of-string or followed by space).
// A bare ~ conflicts with --cd; ~/path is fine (e.g. wsl -d Debian ~/blah.sh).
const auto hasBareTilde = [](std::wstring_view args) {
const auto t = args.find(L'~');
return t != std::wstring_view::npos &&
(t + 1 == args.size() || til::at(args, t + 1) == L' ');
};
std::wstring_view exePath;
std::wstring_view arguments;
std::wstring newCmd;
std::wstring newDir;
if (!startingDirectory.empty() &&
_isWslExe(commandLine, exePath, arguments) &&
arguments.find(L"--cd") == std::wstring_view::npos &&
!hasBareTilde(arguments))
{
const auto dir = _mangleStartingDirectoryForWSL(startingDirectory);
newCmd = fmt::format(FMT_COMPILE(LR"("{}" --cd "{}" {})"), exePath, dir, arguments);
}
else
{
// GH#12353: ~ is never a valid Windows path. We can only accept it
// when the exe is wsl.exe. So, we mangle it to %USERPROFILE% here.
newCmd = commandLine;
newDir = startingDirectory == L"~" ? wil::GetEnvironmentVariableW<std::wstring>(L"USERPROFILE") : std::wstring{ startingDirectory };
}
return { std::move(newCmd), std::move(newDir) };
}
std::wstring_view Utils::TrimPaste(std::wstring_view textView) noexcept