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

Add initial support for paste filtering and bracketed paste mode #7508

Closed
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/patterns/patterns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
https://www\.itscj\.ipsj\.or\.jp/iso-ir/[-0-9]+\.pdf
https://www\.vt100\.net/docs/[-a-zA-Z0-9#_\/.]*
https://www.w3.org/[-a-zA-Z0-9?&=\/_#]*
http://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]*
https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
https://[a-z-]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
[Pp]ublicKeyToken="?[0-9a-fA-F]{16}"?
Expand Down
26 changes: 7 additions & 19 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <Utils.h>
#include <PasteConverter.h>
#include <WinUser.h>
#include <LibraryResources.h>
#include "..\..\types\inc\GlyphWidth.hpp"
Expand All @@ -16,6 +17,7 @@
#include "TermControlAutomationPeer.h"

using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Console::Utils;
using namespace ::Microsoft::Console::VirtualTerminal;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::Graphics::Display;
Expand Down Expand Up @@ -1797,31 +1799,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

// Method Description:
// - Pre-process text pasted (presumably from the clipboard)
// before sending it over the terminal's connection, converting
// Windows-space \r\n line-endings to \r line-endings
// and send it over the terminal's connection.
void TermControl::_SendPastedTextToConnection(const std::wstring& wstr)
{
// Some notes on this implementation:
//
// - std::regex can do this in a single line, but is somewhat
// overkill for a simple search/replace operation (and its
// performance guarantees aren't exactly stellar)
// - The STL doesn't have a simple string search/replace method.
// This fact is lamentable.
// - This line-ending conversion is intentionally fairly
// conservative, to avoid stripping out lone \n characters
// where they could conceivably be intentional.

std::wstring stripped{ wstr };

std::wstring::size_type pos = 0;
PasteFlags flags = PasteFlags::CarriageReturnNewline;

while ((pos = stripped.find(L"\r\n", pos)) != std::wstring::npos)
if (_terminal->IsBracketedPasteModeEnabled())
{
stripped.replace(pos, 2, L"\r");
flags |= PasteFlags::Bracketed;
}

_connection.WriteInput(stripped);
_connection.WriteInput(PasteConverter::Convert(wstr, flags));
_terminal->TrySnapOnInput();
}

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ namespace Microsoft::Terminal::Core
virtual bool EnableButtonEventMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableAnyEventMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableAlternateScrollMode(const bool enabled) noexcept = 0;
virtual bool EnableBracketedPasteMode(const bool enabled) noexcept = 0;
virtual bool IsBracketedPasteModeEnabled() const = 0;

virtual bool IsVtInputEnabled() const = 0;

Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class Microsoft::Terminal::Core::Terminal final :
bool EnableButtonEventMouseMode(const bool enabled) noexcept override;
bool EnableAnyEventMouseMode(const bool enabled) noexcept override;
bool EnableAlternateScrollMode(const bool enabled) noexcept override;
bool EnableBracketedPasteMode(const bool enabled) noexcept override;
bool IsBracketedPasteModeEnabled() const noexcept override;

bool IsVtInputEnabled() const noexcept override;

Expand Down Expand Up @@ -219,6 +221,7 @@ class Microsoft::Terminal::Core::Terminal final :
bool _snapOnInput;
bool _altGrAliasing;
bool _suppressApplicationTitle;
bool _bracketedPasteMode;

#pragma region Text Selection
// a selection is represented as a range between two COORDs (start and end)
Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,17 @@ bool Terminal::EnableAlternateScrollMode(const bool enabled) noexcept
return true;
}

bool Terminal::EnableBracketedPasteMode(const bool enabled) noexcept
{
_bracketedPasteMode = enabled;
return true;
}

bool Terminal::IsBracketedPasteModeEnabled() const noexcept
{
return _bracketedPasteMode;
}

bool Terminal::IsVtInputEnabled() const noexcept
{
// We should never be getting this call in Terminal.
Expand Down
9 changes: 9 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,12 @@ bool TerminalDispatch::EnableAlternateScroll(const bool enabled) noexcept
return true;
}

bool TerminalDispatch::EnableBracketedPasteMode(const bool enabled) noexcept
{
_terminalApi.EnableBracketedPasteMode(enabled);
return true;
}

bool TerminalDispatch::SetPrivateModes(const gsl::span<const DispatchTypes::PrivateModeParams> params) noexcept
{
return _SetResetPrivateModes(params, true);
Expand Down Expand Up @@ -436,6 +442,9 @@ bool TerminalDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateMode
case DispatchTypes::PrivateModeParams::ATT610_StartCursorBlink:
success = EnableCursorBlinking(enable);
break;
case DispatchTypes::PrivateModeParams::XTERM_BracketedPasteMode:
success = EnableBracketedPasteMode(enable);
break;
case DispatchTypes::PrivateModeParams::W32IM_Win32InputMode:
success = EnableWin32InputMode(enable);
break;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc
bool EnableButtonEventMouseMode(const bool enabled) noexcept override; // ?1002
bool EnableAnyEventMouseMode(const bool enabled) noexcept override; // ?1003
bool EnableAlternateScroll(const bool enabled) noexcept override; // ?1007
bool EnableBracketedPasteMode(const bool enabled) noexcept override; // ?2004

bool SetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECSET
bool ResetPrivateModes(const gsl::span<const ::Microsoft::Console::VirtualTerminal::DispatchTypes::PrivateModeParams> /*params*/) noexcept override; // DECRST
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
SGR_EXTENDED_MODE = 1006,
ALTERNATE_SCROLL = 1007,
ASB_AlternateScreenBuffer = 1049,
XTERM_BracketedPasteMode = 2004,
W32IM_Win32InputMode = 9001
};

Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
virtual bool EnableButtonEventMouseMode(const bool enabled) = 0; // ?1002
virtual bool EnableAnyEventMouseMode(const bool enabled) = 0; // ?1003
virtual bool EnableAlternateScroll(const bool enabled) = 0; // ?1007
virtual bool EnableBracketedPasteMode(const bool enabled) = 0; // ?2004

virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCColorTable
virtual bool SetDefaultForeground(const DWORD color) = 0; // OSCDefaultForeground
virtual bool SetDefaultBackground(const DWORD color) = 0; // OSCDefaultBackground
Expand Down
18 changes: 17 additions & 1 deletion src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2108,7 +2108,6 @@ bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)

//Routine Description:
// Enable Any Event mode - send all mouse events to the input.

//Arguments:
// - enabled - true to enable, false to disable.
// Return value:
Expand Down Expand Up @@ -2146,6 +2145,23 @@ bool AdaptDispatch::EnableAlternateScroll(const bool enabled)
return success;
}


//Routine Description:
// Enable "brackted paste mode".
//Arguments:
// - enabled - true to enable, false to disable.
// Return value:
// True if handled successfully. False otherwise.
bool AdaptDispatch::EnableBracketedPasteMode(const bool /*enabled*/)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to do with conhost. Those legacy code always scare me . Call we just leave it like this?

{
if (_pConApi->IsConsolePty())
{
return false;
}

return false;
}

//Routine Description:
// Set Cursor Style - Changes the cursor's style to match the given Dispatch
// cursor style. Unix styles are a combination of the shape and the blinking state.
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ namespace Microsoft::Console::VirtualTerminal
bool EnableButtonEventMouseMode(const bool enabled) override; // ?1002
bool EnableAnyEventMouseMode(const bool enabled) override; // ?1003
bool EnableAlternateScroll(const bool enabled) override; // ?1007
bool EnableBracketedPasteMode(const bool enabled) override; // ?2004

bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR
bool SetCursorColor(const COLORREF cursorColor) override;

Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
bool EnableButtonEventMouseMode(const bool /*enabled*/) noexcept override { return false; } // ?1002
bool EnableAnyEventMouseMode(const bool /*enabled*/) noexcept override { return false; } // ?1003
bool EnableAlternateScroll(const bool /*enabled*/) noexcept override { return false; } // ?1007
bool EnableBracketedPasteMode(const bool /*enabled*/) noexcept override { return false; } // ?2004
bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) noexcept override { return false; } // OSCColorTable
bool SetDefaultForeground(const DWORD /*color*/) noexcept override { return false; } // OSCDefaultForeground
bool SetDefaultBackground(const DWORD /*color*/) noexcept override { return false; } // OSCDefaultBackground
Expand Down
63 changes: 63 additions & 0 deletions src/types/PasteConverter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "precomp.h"
#include "inc/PasteConverter.h"

using namespace Microsoft::Console::Utils;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't honestly know where to put this class. Right now it is heavily VT-related. But in the future maybe other features will be added, making it less VT-related. So perhaps Types is also OK?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep this VT-specific for now. It can be refactored later if need be.


std::wstring PasteConverter::Convert(const std::wstring& wstr, PasteFlags flags)
{
std::wstring converted{ wstr };

// Convert Windows-space \r\n line-endings to \r (carriage return) line-endings
if (flags & PasteFlags::CarriageReturnNewline)
{
// Some notes on this implementation:
//
// - std::regex can do this in a single line, but is somewhat
// overkill for a simple search/replace operation (and its
// performance guarantees aren't exactly stellar)
// - The STL doesn't have a simple string search/replace method.
// This fact is lamentable.
// - This line-ending conversion is intentionally fairly
// conservative, to avoid stripping out lone \n characters
// where they could conceivably be intentional.
std::wstring::size_type pos = 0;

while ((pos = converted.find(L"\r\n", pos)) != std::wstring::npos)
{
converted.replace(pos, 2, L"\r");
}
}

// Bracketed Paste Mode, invented by xterm and implemented in many popular terminal emulators.
// See: http://www.xfree86.org/current/ctlseqs.html#Bracketed%20Paste%20Mode
if (flags & PasteFlags::Bracketed)
{
// For security reasons, control characters should be filtered.
// Here ASCII control characters will be removed, except HT(0x09), LF(0x0a), CR(0x0d) and DEL(0x7F).
converted.erase(std::remove_if(converted.begin(), converted.end(), [](wchar_t c) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to minimize the copy opertion so I choose erase instead of regex_replace. Performance-wise, I think this should be enough for most cases.

if (c >= L'\x20' && c <= L'\x7f')
{
// Printable ASCII + DEL.
return false;
}

if (c > L'\x7f')
{
// Not a control character for sure.
return false;
}

return c >= L'\x00' && c <= L'\x08' ||
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best resource I can find is here https://security.stackexchange.com/a/52655. It suggest removing every control characters except:

  • CR(0x0d) \r
  • LF(0x0a) \n
  • HT(0x09) \t,
  • DEL(0x7F)

Then there's some other opinions in https://bugzilla.gnome.org/show_bug.cgi?id=753197, regarding removing:

  • 0x80-0x9F

And here https://bugzilla.gnome.org/show_bug.cgi?id=794653, regarding removing:

  • BS(0x08)
  • DEL(0x7F)

Copy link
Contributor

@WSLUser WSLUser Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since security is my thing even more than dev stuff, I would agree all of these should be disabled by default. I would also agree we should probably put it behind a setting for certain control characters to be allowed such as BS and DEL with nice warning basically saying "you're on your own if you get hacked".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @WSLUser. That’s a great idea, to give users more options controlling paste behavior. In fact many terminals offer these kind of options so I figure a dedicated class for pasting is a must.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's some confusion here about what bracketed paste is actually meant to do. On most terminals I've tested, it doesn't do any additional filtering - it just adds the escape sequences at the beginning and end of the paste (that's the "bracketing").

While some terminals do filter out certain control characters, or convert them in some way, they do that all the time - not just when bracketed paste mode is enabled. So I think you're kind of mixing two different features here - paste filtering (which should have nothing to do with bracketed paste mode), and then the bracketed paste mode itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks now I realize that. It’s just we don’t have a setting for people to enable/disable filtering pasted content. So I think that can be added later with another flag SanitizeContent or something. Or should we just filter them all without letting people choose.

In my imagination there will be more flags coming in the future and do various kinds of things

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can understand wanting to add support for paste filtering (although I would have expected that in a PR named something like "Add paste filtering"). I also understand leaving the option to enable or disable it for a follow-up PR. What I don't understand is why we want to tie this to bracketed paste - it's a completely separate concept.

If we want to make the paste filtering controllable via an escape sequence for some reason, then we should be inventing our own escape sequence for that - not repurposing an existing sequence. I don't know if I've just misunderstood the intention of this PR, but it make no sense to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I kind misunderstand the relation between filtering escape sequence and adding support for bracketed paste mode. I’ll try to make this better.

c >= L'\x0b' && c <= L'\x0c' ||
c >= L'\x0e' && c <= L'\x1f';
}));

converted.insert(0, L"\x1b[200~");
converted.append(L"\x1b[201~");
}

return converted;
}
32 changes: 32 additions & 0 deletions src/types/inc/PasteConverter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.

Module Name:
- PasteConverter.h

Abstract:
- Pre-process the text pasted (presumably from the clipboard).

--*/

#pragma once

#include <string>

namespace Microsoft::Console::Utils
{
enum PasteFlags
{
CarriageReturnNewline = 1,
Bracketed = 2
};

DEFINE_ENUM_FLAG_OPERATORS(PasteFlags)

class PasteConverter
{
public:
static std::wstring Convert(const std::wstring& wstr, PasteFlags flags);
};
}
4 changes: 3 additions & 1 deletion src/types/lib/types.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ClCompile Include="..\KeyEvent.cpp" />
<ClCompile Include="..\MenuEvent.cpp" />
<ClCompile Include="..\ModifierKeyState.cpp" />
<ClCompile Include="..\PasteConverter.cpp" />
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp" />
<ClCompile Include="..\ThemeUtils.cpp" />
<ClCompile Include="..\UiaTextRangeBase.cpp" />
Expand All @@ -42,6 +43,7 @@
<ClInclude Include="..\inc\Environment.hpp" />
<ClInclude Include="..\inc\GlyphWidth.hpp" />
<ClInclude Include="..\inc\IInputEvent.hpp" />
<ClInclude Include="..\inc\PasteConverter.h" />
<ClInclude Include="..\inc\ThemeUtils.h" />
<ClInclude Include="..\inc\utils.hpp" />
<ClInclude Include="..\inc\Viewport.hpp" />
Expand All @@ -64,4 +66,4 @@
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
</Project>
</Project>
8 changes: 7 additions & 1 deletion src/types/lib/types.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
<ClCompile Include="..\TermControlUiaTextRange.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\PasteConverter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\inc\IInputEvent.hpp">
Expand Down Expand Up @@ -161,8 +164,11 @@
<ClInclude Include="..\IUiaTraceable.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\PasteConverter.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
</Project>
</Project>