From f021979c79f57163a68e68f708278ca6c10b7805 Mon Sep 17 00:00:00 2001 From: dosisod <39638017+dosisod@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:35:27 -0800 Subject: [PATCH] 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: https://gitlab.alpinelinux.org/alpine/apk-tools/-/blob/48d91f482eb48a0a107b714ee183bb7e07782e14/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. --- CHANGELOG.md | 6 ++++++ CONTRIBUTORS.md | 1 + rich/ansi.py | 1 + tests/test_ansi.py | 14 ++++++++++++++ 4 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0eecd7..a359a1ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 22b1be0d..51375d0d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -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) diff --git a/rich/ansi.py b/rich/ansi.py index 66365e65..7de86ce5 100644 --- a/rich/ansi.py +++ b/rich/ansi.py @@ -9,6 +9,7 @@ from .text import Text re_ansi = re.compile( r""" +(?:\x1b[0-?])| (?:\x1b\](.*?)\x1b\\)| (?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~])) """, diff --git a/tests/test_ansi.py b/tests/test_ansi.py index ae87cde1..d81f6459 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -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