Strip problematic private escape sequences:

["Private escape sequences"](https://en.wikipedia.org/wiki/ANSI_escape_code#Fp_Escape_sequences)
are escape sequences that are reserved for private use, though `\x1b7` and
`\x1b8` are commonly used for storing and restoring the current cursor
position. When those escape codes are not stripped the cursor jumps around and
causes Rich to write garbage output. An example of a program that uses this
cursor store/restore functionality is the APK package manager in Alpine Linux:

48d91f482e/src/print.c (L232-240)

This commit updates the ANSI parser to ignore the `\x1b0`-`\x1b?` escape
sequences, thus preventing them from being printed and causing havoc.
This commit is contained in:
dosisod 2024-02-12 16:35:27 -08:00
parent 26152e9cc9
commit f021979c79
4 changed files with 22 additions and 0 deletions

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Fixed
- Strip problematic private escape sequences (like `\x1b7`)
## [13.7.0] - 2023-11-15
### Added

View File

@ -25,6 +25,7 @@ The following people have contributed to the development of Rich:
- [Kenneth Hoste](https://github.com/boegel)
- [Lanqing Huang](https://github.com/lqhuang)
- [Finn Hughes](https://github.com/finnhughes)
- [Logan Hunt](https://github.com/dosisod)
- [Ionite](https://github.com/ionite34)
- [Josh Karpel](https://github.com/JoshKarpel)
- [Jan Katins](https://github.com/jankatins)

View File

@ -9,6 +9,7 @@ from .text import Text
re_ansi = re.compile(
r"""
(?:\x1b[0-?])|
(?:\x1b\](.*?)\x1b\\)|
(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
""",

View File

@ -68,3 +68,17 @@ def test_decode_issue_2688(ansi_bytes, expected_text):
text = Text.from_ansi(ansi_bytes.decode())
assert str(text) == expected_text
@pytest.mark.parametrize("code", [*"0123456789:;<=>?"])
def test_strip_private_escape_sequences(code):
text = Text.from_ansi(f"\x1b{code}x")
console = Console(force_terminal=True)
with console.capture() as capture:
console.print(text)
expected = "x\n"
assert capture.get() == expected