Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

normalize command line arguments #4724

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
122 changes: 98 additions & 24 deletions src/cascadia/WindowsTerminal/AppHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ bool AppHost::OnF7Pressed()
}

// Method Description:
// - Retrieve any commandline args passed on the commandline, and pass them to
// the app logic for processing.
// - Retrieve the command line arguments, and pass them to the app logic for processing.
// - If the logic determined there's an error while processing that commandline,
// display a message box to the user with the text of the error, and exit.
// * We display a message box because we're a Win32 application (not a
Expand All @@ -87,37 +86,112 @@ bool AppHost::OnF7Pressed()
// - <none>
void AppHost::_HandleCommandlineArgs()
{
if (auto commandline{ GetCommandLineW() })
const auto args{ _GetArgs() };
if (!args.empty())
{
int argc = 0;
const auto result = _logic.SetStartupCommandline({ args });
const auto message = _logic.EarlyExitMessage();
if (!message.empty())
{
const auto displayHelp = result == 0;
const auto messageTitle = displayHelp ? IDS_HELP_DIALOG_TITLE : IDS_ERROR_DIALOG_TITLE;
const auto messageIcon = displayHelp ? MB_ICONWARNING : MB_ICONERROR;
// TODO:GH#4134: polish this dialog more, to make the text more
// like msiexec /?
MessageBoxW(nullptr,
message.data(),
GetStringResource(messageTitle).data(),
MB_OK | messageIcon);

ExitProcess(result);
}
}
}

// Method Description:
// - Retrieve the command line.
// - Use our own algorithm to tokenize it because `CommandLineToArgvW` treats \"
// as an escape sequence to preserve the quotation mark (GH#4571)
// Instead treat a pair of consecutive quotation marks as a single quotation mark.
// - Populate the arguments as vector of hstrings.
// Arguments:
// - <none>
// Return Value:
// - Program arguments as vector of hstrings. If the function fails it returns an empty vector.
std::vector<winrt::hstring> AppHost::_GetArgs() const noexcept
{
try
{
std::vector<winrt::hstring> args{};
// get the command line
const wchar_t* const cmdLnPtr{ GetCommandLineW() };
if (!cmdLnPtr)
{
return {};
}

// Get the argv, and turn them into a hstring array to pass to the app.
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(commandline, &argc) };
if (argv)
std::wstring cmdLn{ cmdLnPtr };
auto cmdLnEnd{ cmdLn.end() }; // end of still valid content in the command line
auto iter{ cmdLn.begin() }; // iterator pointing to the current position in the command line
auto argBegin{ iter }; // iterator pointing to the begin of an argument
auto firstQuote{ cmdLnEnd }; // iterator pointing to the first quotation mark of a pair of quotation marks
auto quoted{ false }; // indicates whether a substring is quoted
auto within{ false }; // indicates whether the current character is inside of an argument

args.reserve(gsl::narrow_cast<size_t>(64U)); // pre-allocate some space for the string handles
while (iter < cmdLnEnd)
{
std::vector<winrt::hstring> args;
for (auto& elem : wil::make_range(argv.get(), argc))
auto increment{ true }; // used to avoid iterator incrementation if a quotation mark has been removed
switch (*iter)
{
args.emplace_back(elem);
case L' ': // space and tab are the usual separators for arguments
case L'\t':
if (!quoted && within)
{
within = false;
args.emplace_back(std::wstring{ argBegin, iter });
}
break;

case L'"': // quotation marks need special handling
german-one marked this conversation as resolved.
Show resolved Hide resolved
quoted = !quoted;
if (firstQuote == iter) // treat "" as a single "
{
firstQuote = cmdLnEnd;
}
else
{
firstQuote = iter;
std::move(iter + 1, cmdLnEnd, iter); // move the rest of the command line to the left to overwrite the quote
--cmdLnEnd; // the string isn't shorter but its valid content is, we can't just resize without potentially invalidate iterators
increment = false; // indicate that iter shall not be incremented because it already points to a new character
}
[[fallthrough]];
default: // any other character (including quotation mark)
if (!within)
{
within = true;
argBegin = iter;
}
break;
}

const auto result = _logic.SetStartupCommandline({ args });
const auto message = _logic.EarlyExitMessage();
if (!message.empty())
if (increment)
{
const auto displayHelp = result == 0;
const auto messageTitle = displayHelp ? IDS_HELP_DIALOG_TITLE : IDS_ERROR_DIALOG_TITLE;
const auto messageIcon = displayHelp ? MB_ICONWARNING : MB_ICONERROR;
// TODO:GH#4134: polish this dialog more, to make the text more
// like msiexec /?
MessageBoxW(nullptr,
message.data(),
GetStringResource(messageTitle).data(),
MB_OK | messageIcon);

ExitProcess(result);
++iter;
}
}

if (within)
{
args.emplace_back(std::wstring{ argBegin, iter });
}

return args;
}
catch (...)
{
return {};
}
}

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/WindowsTerminal/AppHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class AppHost
winrt::TerminalApp::AppLogic _logic;

void _HandleCommandlineArgs();
std::vector<winrt::hstring> _GetArgs() const noexcept;

void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::TerminalApp::LaunchMode& launchMode);
void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender,
Expand Down