Skip to content

Commit

Permalink
Sanitize C1 control chars in SetConsoleTitle API (#10847)
Browse files Browse the repository at this point in the history
When the `SetContoleTitle` API is called with a title containing control
characters, we need to filter out those characters before we can forward
the title change over conpty as an escape sequence. If we don't do that,
the receiving terminal will end up executing the control characters
instead of updating the title. We were already filtering out the C0
control characters, but with this PR we're now filtering out C1 controls
characters as well.

I've simply updated the sanitizing routine in `DoSrvSetConsoleTitleW` to
filter our characters in the range `0x80` to `0x9F`. This is in addition
to the C0 range (`0x00` to `0x1F`) that was already excluded. 

## Validation Steps Performed

I've added a conpty unit test that calls `DoSrvSetConsoleTitleW` with
titles containing a variety of C0 and C1 controls characters, and which
verifies that those characters are stripped from the title forwarded to
conpty.

I've also confirmed that the test case in issue #10312 is now working
correctly in Windows Terminal.

Closes #10312
  • Loading branch information
j4james authored Aug 2, 2021
1 parent 9416694 commit 9ba2080
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 9 deletions.
14 changes: 5 additions & 9 deletions src/host/getset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1931,15 +1931,11 @@ void DoSrvPrivateRefreshWindow(_In_ const SCREEN_INFORMATION& screenInfo)
// to embed control characters in that string.
if (gci.IsInVtIoMode())
{
std::wstring sanitized;
sanitized.reserve(title.size());
for (size_t i = 0; i < title.size(); i++)
{
if (title.at(i) >= UNICODE_SPACE)
{
sanitized.push_back(title.at(i));
}
}
std::wstring sanitized{ title };
sanitized.erase(std::remove_if(sanitized.begin(), sanitized.end(), [](auto ch) {
return ch < UNICODE_SPACE || (ch > UNICODE_DEL && ch < UNICODE_NBSP);
}),
sanitized.end());

gci.SetTitle({ sanitized });
}
Expand Down
35 changes: 35 additions & 0 deletions src/host/ut_host/ConptyOutputTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class ConptyOutputTests
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
TEST_METHOD(InvalidateUntilOneBeforeEnd);
TEST_METHOD(SetConsoleTitleWithControlChars);

private:
bool _writeCallback(const char* const pch, size_t const cch);
Expand Down Expand Up @@ -364,3 +365,37 @@ void ConptyOutputTests::InvalidateUntilOneBeforeEnd()

VERIFY_SUCCEEDED(renderer.PaintFrame());
}

void ConptyOutputTests::SetConsoleTitleWithControlChars()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:control", L"{0x00, 0x0A, 0x1B, 0x80, 0x9B, 0x9C}")
END_TEST_METHOD_PROPERTIES()

int control;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"control", control));

auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;

Log::Comment(NoThrowString().Format(
L"SetConsoleTitle with a control character (0x%02X) embedded in the text", control));

std::wstringstream titleText;
titleText << L"Hello " << wchar_t(control) << L"World!";
VERIFY_SUCCEEDED(DoSrvSetConsoleTitleW(titleText.str()));

// This is the standard init sequences for the first frame.
expectedOutput.push_back("\x1b[2J");
expectedOutput.push_back("\x1b[m");
expectedOutput.push_back("\x1b[H");

// The title change is propagated as an OSC 0 sequence.
// Control characters are stripped, so it's always "Hello World".
expectedOutput.push_back("\x1b]0;Hello World!\a");

// This is also part of the standard init sequence.
expectedOutput.push_back("\x1b[?25h");

VERIFY_SUCCEEDED(renderer.PaintFrame());
}

0 comments on commit 9ba2080

Please sign in to comment.