Skip to content

Commit 98bd878

Browse files
Don't crash when handling arbitrary random data.
1 parent 0718fa8 commit 98bd878

2 files changed

Lines changed: 61 additions & 20 deletions

File tree

pyte/screens.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ def resize(self, lines: int | None = None, columns: int | None = None) -> None:
351351
self.lines, self.columns = lines, columns
352352
self.set_margins()
353353

354-
def set_margins(self, top: int | None = None, bottom: int | None = None) -> None:
354+
def set_margins(self, top: int | None = None, bottom: int | None = None, *args: object, private: bool = False) -> None:
355355
"""Select top and bottom margins for the scrolling region.
356356
357357
:param int top: the smallest line number that is scrolled.
@@ -665,7 +665,7 @@ def restore_cursor(self) -> None:
665665
self.reset_mode(mo.DECOM)
666666
self.cursor_position()
667667

668-
def insert_lines(self, count: int | None = None) -> None:
668+
def insert_lines(self, count: int | None = None, *args: object, private: bool = False) -> None:
669669
"""Insert the indicated # of lines at line with cursor. Lines
670670
displayed **at** and below the cursor move down. Lines moved
671671
past the bottom margin are lost.
@@ -685,7 +685,7 @@ def insert_lines(self, count: int | None = None) -> None:
685685

686686
self.carriage_return()
687687

688-
def delete_lines(self, count: int | None = None) -> None:
688+
def delete_lines(self, count: int | None = None, *args: object, private: bool = False) -> None:
689689
"""Delete the indicated # of lines, starting at line with
690690
cursor. As lines are deleted, lines displayed below cursor
691691
move up. Lines added to bottom of screen have spaces with same
@@ -708,7 +708,7 @@ def delete_lines(self, count: int | None = None) -> None:
708708

709709
self.carriage_return()
710710

711-
def insert_characters(self, count: int | None = None) -> None:
711+
def insert_characters(self, count: int | None = None, *args: object, private: bool = False) -> None:
712712
"""Insert the indicated # of blank characters at the cursor
713713
position. The cursor does not move and remains at the beginning
714714
of the inserted blank characters. Data on the line is shifted
@@ -725,7 +725,7 @@ def insert_characters(self, count: int | None = None) -> None:
725725
line[x + count] = line[x]
726726
line.pop(x, None)
727727

728-
def delete_characters(self, count: int | None = None) -> None:
728+
def delete_characters(self, count: int | None = None, *args: object, private: bool = False) -> None:
729729
"""Delete the indicated # of characters, starting with the
730730
character at cursor position. When a character is deleted, all
731731
characters to the right of cursor move left. Character attributes
@@ -743,7 +743,7 @@ def delete_characters(self, count: int | None = None) -> None:
743743
else:
744744
line.pop(x, None)
745745

746-
def erase_characters(self, count: int | None = None) -> None:
746+
def erase_characters(self, count: int | None = None, *args: object, private: bool = False) -> None:
747747
"""Erase the indicated # of characters, starting with the
748748
character at cursor position. Character attributes are set
749749
cursor attributes. The cursor remains in the same position.
@@ -765,7 +765,7 @@ def erase_characters(self, count: int | None = None) -> None:
765765
min(self.cursor.x + count, self.columns)):
766766
line[x] = self.cursor.attrs
767767

768-
def erase_in_line(self, how: int = 0, private: bool = False) -> None:
768+
def erase_in_line(self, how: int = 0, *args: object, private: bool = False) -> None:
769769
"""Erase a line in a specific way.
770770
771771
Character attributes are set to cursor attributes.
@@ -787,6 +787,8 @@ def erase_in_line(self, how: int = 0, private: bool = False) -> None:
787787
interval = range(self.cursor.x + 1)
788788
elif how == 2:
789789
interval = range(self.columns)
790+
else:
791+
return # Invalid value for `how`.
790792

791793
line = self.buffer[self.cursor.y]
792794
for x in interval:
@@ -821,6 +823,8 @@ def erase_in_display(self, how: int= 0, *args: Any, **kwargs: Any) -> None:
821823
interval = range(self.cursor.y)
822824
elif how == 2 or how == 3:
823825
interval = range(self.lines)
826+
else:
827+
return # Invalid value for `how`.
824828

825829
self.dirty.update(interval)
826830
for y in interval:
@@ -835,7 +839,7 @@ def set_tab_stop(self) -> None:
835839
"""Set a horizontal tab stop at cursor position."""
836840
self.tabstops.add(self.cursor.x)
837841

838-
def clear_tab_stop(self, how: int = 0) -> None:
842+
def clear_tab_stop(self, how: int = 0, *args: object, private: bool = False) -> None:
839843
"""Clear a horizontal tab stop.
840844
841845
:param int how: defines a way the tab stop should be cleared:
@@ -870,7 +874,7 @@ def ensure_vbounds(self, use_margins: bool | None = None) -> None:
870874

871875
self.cursor.y = min(max(top, self.cursor.y), bottom)
872876

873-
def cursor_up(self, count: int | None = None) -> None:
877+
def cursor_up(self, count: int | None = None, *args: object, private: bool = False) -> None:
874878
"""Move cursor up the indicated # of lines in same column.
875879
Cursor stops at top margin.
876880
@@ -879,7 +883,7 @@ def cursor_up(self, count: int | None = None) -> None:
879883
top, _bottom = self.margins or Margins(0, self.lines - 1)
880884
self.cursor.y = max(self.cursor.y - (count or 1), top)
881885

882-
def cursor_up1(self, count: int | None = None) -> None:
886+
def cursor_up1(self, count: int | None = None, *args: object, private: bool = False) -> None:
883887
"""Move cursor up the indicated # of lines to column 1. Cursor
884888
stops at bottom margin.
885889
@@ -888,7 +892,7 @@ def cursor_up1(self, count: int | None = None) -> None:
888892
self.cursor_up(count)
889893
self.carriage_return()
890894

891-
def cursor_down(self, count: int | None = None) -> None:
895+
def cursor_down(self, count: int | None = None, *args: object, private: bool = False) -> None:
892896
"""Move cursor down the indicated # of lines in same column.
893897
Cursor stops at bottom margin.
894898
@@ -897,7 +901,7 @@ def cursor_down(self, count: int | None = None) -> None:
897901
_top, bottom = self.margins or Margins(0, self.lines - 1)
898902
self.cursor.y = min(self.cursor.y + (count or 1), bottom)
899903

900-
def cursor_down1(self, count: int | None = None) -> None:
904+
def cursor_down1(self, count: int | None = None, *args: object, private: bool = False) -> None:
901905
"""Move cursor down the indicated # of lines to column 1.
902906
Cursor stops at bottom margin.
903907
@@ -906,7 +910,7 @@ def cursor_down1(self, count: int | None = None) -> None:
906910
self.cursor_down(count)
907911
self.carriage_return()
908912

909-
def cursor_back(self, count: int | None = None) -> None:
913+
def cursor_back(self, count: int | None = None, *args: object, private: bool = False) -> None:
910914
"""Move cursor left the indicated # of columns. Cursor stops
911915
at left margin.
912916
@@ -920,7 +924,7 @@ def cursor_back(self, count: int | None = None) -> None:
920924
self.cursor.x -= count or 1
921925
self.ensure_hbounds()
922926

923-
def cursor_forward(self, count: int | None = None) -> None:
927+
def cursor_forward(self, count: int | None = None, *args: object, private: bool = False) -> None:
924928
"""Move cursor right the indicated # of columns. Cursor stops
925929
at right margin.
926930
@@ -929,7 +933,7 @@ def cursor_forward(self, count: int | None = None) -> None:
929933
self.cursor.x += count or 1
930934
self.ensure_hbounds()
931935

932-
def cursor_position(self, line: int | None = None, column: int | None = None) -> None:
936+
def cursor_position(self, line: int | None = None, column: int | None = None, *args: object, private: bool = False) -> None:
933937
"""Set the cursor to a specific `line` and `column`.
934938
935939
Cursor is allowed to move out of the scrolling region only when
@@ -956,15 +960,15 @@ def cursor_position(self, line: int | None = None, column: int | None = None) ->
956960
self.ensure_hbounds()
957961
self.ensure_vbounds()
958962

959-
def cursor_to_column(self, column: int | None = None) -> None:
963+
def cursor_to_column(self, column: int | None = None, *args: object, private: bool = False) -> None:
960964
"""Move cursor to a specific column in the current line.
961965
962966
:param int column: column number to move the cursor to.
963967
"""
964968
self.cursor.x = (column or 1) - 1
965969
self.ensure_hbounds()
966970

967-
def cursor_to_line(self, line: int | None = None) -> None:
971+
def cursor_to_line(self, line: int | None = None, *args: object, private: bool = False) -> None:
968972
"""Move cursor to a specific line in the current column.
969973
970974
:param int line: line number to move the cursor to.
@@ -1046,7 +1050,7 @@ def select_graphic_rendition(self, *attrs: int, private: bool = False) -> None:
10461050

10471051
self.cursor.attrs = self.cursor.attrs._replace(**replace)
10481052

1049-
def report_device_attributes(self, mode: int = 0, **kwargs: bool) -> None:
1053+
def report_device_attributes(self, mode: int = 0, *args: object, **kwargs: bool) -> None:
10501054
"""Report terminal identity.
10511055
10521056
.. versionadded:: 0.5.0
@@ -1061,7 +1065,7 @@ def report_device_attributes(self, mode: int = 0, **kwargs: bool) -> None:
10611065
if mode == 0 and not kwargs.get("private"):
10621066
self.write_process_input(ctrl.CSI + "?6c")
10631067

1064-
def report_device_status(self, mode: int) -> None:
1068+
def report_device_status(self, mode: int, *args: object, private: bool = False) -> None:
10651069
"""Report terminal status or cursor position.
10661070
10671071
:param int mode: if 5 -- terminal status, 6 -- cursor position,

pyte/streams.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"""
2121
from __future__ import annotations
2222

23+
import inspect
2324
import codecs
2425
import itertools
2526
import re
@@ -169,6 +170,39 @@ def attach(self, screen: Screen) -> None:
169170
self.listener = screen
170171
self._parser: ParserGenerator | None = None
171172
self._initialize_parser()
173+
self._validate_screen_callbacks(screen)
174+
175+
def _validate_screen_callbacks(self, screen: Screen) -> None:
176+
"""
177+
Ensure that all `csi` methods of the screen:
178+
- take a `private: bool=...` keyword argument.
179+
- accept any number of positional arguments.
180+
"""
181+
for method_name in self.csi.values():
182+
method = getattr(screen, method_name, None)
183+
if method is not None:
184+
sig = inspect.signature(method)
185+
186+
takes_args = any(
187+
param.kind == inspect.Parameter.VAR_POSITIONAL
188+
for param in sig.parameters.values()
189+
)
190+
takes_kwargs = any(
191+
param.kind == inspect.Parameter.VAR_KEYWORD
192+
for param in sig.parameters.values()
193+
)
194+
195+
if "private" not in sig.parameters and not takes_kwargs:
196+
raise RuntimeError(
197+
f"Screen method {method_name!r} doesn't "
198+
"accept `private:bool=...` keyword argument"
199+
)
200+
201+
if not takes_args:
202+
raise RuntimeError(
203+
f"Screen method {method_name!r} doesn't "
204+
"accept arbitrary number of positional arguments."
205+
)
172206

173207
def detach(self, screen: Screen) -> None:
174208
"""Remove a given screen from the listener queue and fails
@@ -345,7 +379,10 @@ def create_dispatcher(mapping: Mapping[str, str]) -> dict[str, Callable[..., Non
345379
yield None
346380
break
347381
else:
348-
params.append(min(int(current or 0), 9999))
382+
try:
383+
params.append(min(int(current or 0), 9999))
384+
except ValueError:
385+
continue # Not a valid integer literal.
349386

350387
if char == ";":
351388
current = ""

0 commit comments

Comments
 (0)