-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Display Unicode URIs side-by-side with their Punycode encoding #15488
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,7 @@ IConnection | |
ICustom | ||
IDialog | ||
IDirect | ||
Idn | ||
IExplorer | ||
IFACEMETHOD | ||
IFile | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3042,31 +3042,64 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
_core.ClearHoveredCell(); | ||
} | ||
|
||
winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable /*sender*/, | ||
IInspectable /*args*/) | ||
// Attackers abuse Unicode characters that happen to look similar to ASCII characters. Cyrillic for instance has | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// its own glyphs for а, с, е, о, р, х, and у that look practically identical to their ASCII counterparts. | ||
// This is called an "IDN homoglyph attack". | ||
// | ||
// But outright showing Punycode URIs only is similarly flawed as they can end up looking similar to valid ASCII URIs. | ||
// xn--cnn.com for instance looks confusingly similar to cnn.com, but actually represents U+407E. | ||
// | ||
// An optimal solution would detect any URI that contains homoglyphs and show them in their Punycode form. | ||
// Such a detector however is not quite trivial and requires constant maintenance, which this project's | ||
// maintainers aren't currently well equipped to handle. As such we do the next best thing and show the | ||
// Punycode encoding side-by-side with the Unicode string for any IDN. | ||
static winrt::hstring sanitizeURI(winrt::hstring uri) | ||
{ | ||
auto weakThis{ get_weak() }; | ||
co_await wil::resume_foreground(Dispatcher()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another instance where we were switching to the foreground to make touching UI objects safe. Are we convinced that this is safe? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only place So yeah this can only be called from the UI thread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's technically an implementation detail, but I'll allow it ;P |
||
if (auto self{ weakThis.get() }) | ||
if (uri.empty()) | ||
{ | ||
auto lastHoveredCell = _core.HoveredCell(); | ||
if (lastHoveredCell) | ||
return uri; | ||
} | ||
|
||
wchar_t punycodeBuffer[256]; | ||
wchar_t unicodeBuffer[256]; | ||
|
||
// These functions return int, but are documented to only return positive numbers. | ||
// Better make sure though. It allows us to pass punycodeLength right into IdnToUnicode. | ||
const auto punycodeLength = std::max(0, IdnToAscii(0, uri.data(), gsl::narrow<int>(uri.size()), &punycodeBuffer[0], 256)); | ||
const auto unicodeLength = std::max(0, IdnToUnicode(0, &punycodeBuffer[0], punycodeLength, &unicodeBuffer[0], 256)); | ||
|
||
if (punycodeLength <= 0 || unicodeLength <= 0) | ||
{ | ||
winrt::hstring uriText = _core.HoveredUriText(); | ||
if (uriText.empty()) | ||
return RS_(L"InvalidUri"); | ||
} | ||
|
||
const std::wstring_view punycode{ &punycodeBuffer[0], gsl::narrow_cast<size_t>(punycodeLength) }; | ||
const std::wstring_view unicode{ &unicodeBuffer[0], gsl::narrow_cast<size_t>(unicodeLength) }; | ||
|
||
// IdnToAscii/IdnToUnicode return the input string as is if it's all | ||
// plain ASCII. But we don't know if the input URI is Punycode or not. | ||
// --> It's non-Punycode and ASCII if it round-trips. | ||
if (uri == punycode && uri == unicode) | ||
{ | ||
co_return; | ||
return uri; | ||
} | ||
|
||
try | ||
return winrt::hstring{ fmt::format(FMT_COMPILE(L"{}\n({})"), punycode, unicode) }; | ||
} | ||
|
||
void TermControl::_hoveredHyperlinkChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/) | ||
{ | ||
// DisplayUri will filter out non-printable characters and confusables. | ||
Windows::Foundation::Uri parsedUri{ uriText }; | ||
if (!parsedUri) | ||
const auto lastHoveredCell = _core.HoveredCell(); | ||
if (!lastHoveredCell) | ||
{ | ||
co_return; | ||
return; | ||
} | ||
|
||
const auto uriText = sanitizeURI(_core.HoveredUriText()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we should encode URIs to Punycode a little earlier than this... If the encoding to Punycode fails, we shouldn't even allow the option to click it. On the other hand, the old code didn't handle that either and this fixes the immediate issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly, I am OKAY with this. Thanks so much. |
||
if (uriText.empty()) | ||
{ | ||
return; | ||
} | ||
uriText = parsedUri.DisplayUri(); | ||
|
||
const auto panel = SwapChainPanel(); | ||
const auto scale = panel.CompositionScaleX(); | ||
|
@@ -3089,10 +3122,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation | |
OverlayCanvas().SetLeft(HyperlinkTooltipBorder(), locationInDIPs.x - offset.x); | ||
OverlayCanvas().SetTop(HyperlinkTooltipBorder(), locationInDIPs.y - offset.y); | ||
} | ||
CATCH_LOG(); | ||
} | ||
} | ||
} | ||
|
||
winrt::fire_and_forget TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I feel like "as a warning" is better in this case. 🤔
After all, this doesn't prevent you from clicking the URL.