diff --git a/src/prompt_toolkit/formatted_text/ansi.py b/src/prompt_toolkit/formatted_text/ansi.py index 2f355c2b7..9904f872c 100644 --- a/src/prompt_toolkit/formatted_text/ansi.py +++ b/src/prompt_toolkit/formatted_text/ansi.py @@ -291,7 +291,16 @@ def ansi_escape(text: object) -> str: """ Replace characters with a special meaning. """ - return str(text).replace("\x1b", "?").replace("\b", "?") + # Besides ESC, the parser also treats \x9b as the start of a control + # sequence, and everything between \001 and \002 as raw output. + return ( + str(text) + .replace("\x1b", "?") + .replace("\x9b", "?") + .replace("\x01", "?") + .replace("\x02", "?") + .replace("\b", "?") + ) class ANSIFormatter(Formatter): diff --git a/src/prompt_toolkit/formatted_text/html.py b/src/prompt_toolkit/formatted_text/html.py index a940ac8f8..94ea013a6 100644 --- a/src/prompt_toolkit/formatted_text/html.py +++ b/src/prompt_toolkit/formatted_text/html.py @@ -139,6 +139,7 @@ def html_escape(text: object) -> str: .replace("<", "<") .replace(">", ">") .replace('"', """) + .replace("'", "'") ) diff --git a/tests/test_formatted_text.py b/tests/test_formatted_text.py index a111a7f20..484389713 100644 --- a/tests/test_formatted_text.py +++ b/tests/test_formatted_text.py @@ -209,6 +209,26 @@ def test_ansi_interpolation(): ("ansigreen bg:ansimagenta", "2"), ] + # 8-bit CSI (\x9b) and the \001..\002 raw escape markers can't be + # injected through interpolated values either. + value = ANSI("{}").format("\x9b31mhi") + assert to_formatted_text(value) == [ + ("", "?"), + ("", "3"), + ("", "1"), + ("", "m"), + ("", "h"), + ("", "i"), + ] + + value = ANSI("%s") % "\x01x\x02y" + assert to_formatted_text(value) == [ + ("", "?"), + ("", "x"), + ("", "?"), + ("", "y"), + ] + def test_interpolation(): value = Template(" {} ").format(HTML("hello")) @@ -251,6 +271,13 @@ def test_html_interpolation(): value = HTML("{:02d}{:.3f}").format(3, 3.14159) assert to_formatted_text(value) == [("class:b", "03"), ("class:u", "3.142")] + # Quotes inside attribute values. + value = HTML("").format("a'bg='b") + assert to_formatted_text(value) == [("fg:a'bg='b", "x")] + + value = HTML('') % 'a"bg="b' + assert to_formatted_text(value) == [('fg:a"bg="b', "x")] + def test_merge_formatted_text(): html1 = HTML("hello")