8000 bpo-42681: Fix range checks for color and pair numbers in curses by serhiy-storchaka · Pull Request #23874 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-42681: Fix range checks for color and pair numbers in curses #23874

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

Merged
merged 10 commits into from
Jan 3, 2021
14 changes: 8 additions & 6 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,15 @@ The module :mod:`curses` defines the following functions:
.. function:: color_content(color_number)

Return the intensity of the red, green, and blue (RGB) components in the color
*color_number*, which must be between ``0`` and :const:`COLORS`. Return a 3-tuple,
*color_number*, which must be between ``0`` and ``COLORS - 1``. Return a 3-tuple,
containing the R,G,B values for the given color, which will be between
``0`` (no component) and ``1000`` (maximum amount of component).


.. function:: color_pair(color_number)
.. function:: color_pair(pair_number)

Return the attribute value for displaying text in the specified color. This
Return the attribute value for displaying text in the specified color pair.
Only the first 256 color pairs are supported. This
attribute value can be combined with :const:`A_STANDOUT`, :const:`A_REVERSE`,
and the other :const:`A_\*` attributes. :func:`pair_number` is the counterpart
to this function.
Expand Down Expand Up @@ -287,7 +288,7 @@ The module :mod:`curses` defines the following functions:
Change the definition of a color, taking the number of the color to be changed
followed by three RGB values (for the amounts of red, green, and blue
components). The value of *color_number* must be between ``0`` and
:const:`COLORS`. Each of *r*, *g*, *b*, must be a value between ``0`` and
`COLORS - 1`. Each of *r*, *g*, *b*, must be a value between ``0`` and
``1000``. When :func:`init_color` is used, all occurrences of that color on the
screen immediately change to the new definition. This function is a no-op on
most terminals; it is active only if :func:`can_change_color` returns ``True``.
Expand All @@ -300,7 +301,8 @@ The module :mod:`curses` defines the following functions:
color number. The value of *pair_number* must be between ``1`` and
``COLOR_PAIRS - 1`` (the ``0`` color pair is wired to white on black and cannot
be changed). The value of *fg* and *bg* arguments must be between ``0`` and
:const:`COLORS`. If the color-pair was previously initialized, the screen is
``COLORS - 1``, or, after calling :func:`use_default_colors`, ``-1``.
If the color-pair was previously initialized, the screen is
refreshed and all occurrences of that color-pair are changed to the new
definition.

Expand Down Expand Up @@ -450,7 +452,7 @@ The module :mod:`curses` defines the following functions:
.. function:: pair_content(pair_number)

Return a tuple ``(fg, bg)`` containing the colors for the requested color pair.
The value of *pair_number* must be between ``1`` and ``COLOR_PAIRS - 1``.
The value of *pair_number* must be between ``0`` and ``COLOR_PAIRS - 1``.


.. function:: pair_number(attr)
Expand Down
113 changes: 87 additions & 26 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
# This script doesn't actually display anything very coherent. but it
# does call (nearly) every method and function.
#
# Functions not tested: {def,reset}_{shell,prog}_mode, getch(), getstr(),
# init_color()
# Functions not tested: {def,reset}_{shell,prog}_mode, getch(), getstr()
# Only called, not tested: getmouse(), ungetmouse()
#

import os
import string
import sys
import tempfile
import functools
import unittest

from test.support import requires, verbose, SaveSignals
Expand All @@ -37,6 +37,15 @@ def requires_curses_func(name):
return unittest.skipUnless(hasattr(curses, name),
'requires curses.%s' % name)

def requires_colors(test):
@functools.wraps(test)
def wrapped(self, *args, **kwargs):
if not curses.has_colors():
self.skipTest('requires colors support')
curses.start_color()
test(self, *args, **kwargs)
return wrapped

term = os.environ.get('TERM')

# If newterm was supported we could use it instead of initscr and not exit
Expand Down Expand Up @@ -281,31 +290,83 @@ def test_module_funcs(self):
curses.use_env(1)

# Functions only available on a few platforms
def test_colors_funcs(self):
if not curses.has_colors():
self.skipTest('requires colors support')
curses.start_color()
curses.init_pair(2, 1,1)
curses.color_content(1)
curses.color_pair(2)

def bad_colors(self):
return (-1, curses.COLORS, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)

def bad_colors2(self):
return (curses.COLORS, 2**31, 2**63, 2**64)

def bad_pairs(self):
return (-1, curses.COLOR_PAIRS, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)

@requires_colors
def test_color_content(self):
self.assertEqual(curses.color_content(curses.COLOR_BLACK), (0, 0, 0))
curses.color_content(0)
curses.color_content(curses.COLORS - 1)

for color in self.bad_colors():
self.assertRaises(ValueError, curses.color_content, color)

@requires_colors
def test_init_color(self):
if not curses.can_change_color:
self.skipTest('cannot change color')

curses.init_color(0, 0, 0, 0)
self.assertEqual(curses.color_content(0), (0, 0, 0))
curses.init_color(0, 1000, 1000, 1000)
self.assertEqual(curses.color_content(0), (1000, 1000, 1000))
curses.init_color(curses.COLORS - 1, 0, 500, 1000)
self.assertEqual(curses.color_content(curses.COLORS - 1), (0, 500, 1000))

for color in self.bad_colors():
self.assertRaises(ValueError, curses.init_color, color, 0, 0, 0)
for comp in (-1, 1001):
self.assertRaises(ValueError, curses.init_color, 0, comp, 0, 0)
self.assertRaises(ValueError, curses.init_color, 0, 0, comp, 0)
self.assertRaises(ValueError, curses.init_color, 0, 0, 0, comp)

@requires_colors
def test_pair_content(self):
self.assertEqual(curses.pair_content(0),
(curses.COLOR_WHITE, curses.COLOR_BLACK))
curses.pair_content(curses.COLOR_PAIRS - 1)
curses.pair_number(0)

if hasattr(curses, 'use_default_colors'):
curses.use_default_colors()

self.assertRaises(ValueError, curses.color_content, -1)
self.assertRaises(ValueError, curses.color_content, curses.COLORS + 1)
self.assertRaises(ValueError, curses.color_content, -2**31 - 1)
self.assertRaises(ValueError, curses.color_content, 2**31)
self.assertRaises(ValueError, curses.color_content, -2**63 - 1)
self.assertRaises(ValueError, curses.color_content, 2**63 - 1)
self.assertRaises(ValueError, curses.pair_content, -1)
self.assertRaises(ValueError, curses.pair_content, curses.COLOR_PAIRS)
self.assertRaises(ValueError, curses.pair_content, -2**31 - 1)
self.assertRaises(ValueError, curses.pair_content, 2**31)
self.assertRaises(ValueError, curses.pair_content, -2**63 - 1)
self.assertRaises(ValueError, curses.pair_content, 2**63 - 1)

for pair in self.bad_pairs():
self.assertRaises(ValueError, curses.pair_content, pair)

@requires_colors
def test_init_pair(self):
curses.init_pair(1, 0, 0)
self.assertEqual(curses.pair_content(1), (0, 0))
curses.init_pair(1, curses.COLORS - 1, curses.COLORS - 1)
self.assertEqual(curses.pair_content(1),
(curses.COLORS - 1, curses.COLORS - 1))
curses.init_pair(curses.COLOR_PAIRS - 1, 2, 3)
self.assertEqual(curses.pair_content(curses.COLOR_PAIRS - 1), (2, 3))

for pair in self.bad_pairs():
self.assertRaises(ValueError, curses.init_pair, pair, 0, 0)
for color in self.bad_colors2():
self.assertRaises(ValueError, curses.init_pair, 1, color, 0)
self.assertRaises(ValueError, curses.init_pair, 1, 0, color)

@requires_colors
def test_color_attrs(self):
for pair in 0, 1, 255:
attr = curses.color_pair(pair)
self.assertEqual(curses.pair_number(attr), pair, attr)
self.assertEqual(curses.pair_number(attr | curses.A_BOLD), pair)
self.assertEqual(curses.color_pair(0), 0)
self.assertEqual(curses.pair_number(0), 0)

@requires_curses_func('use_default_colors')
@requires_colors
def test_use_default_colors(self):
curses.use_default_colors()
self.assertEqual(curses.pair_content(0), (-1, -1))

@requires_curses_func('keyname')
def test_keyname(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed range checks for color and pair numbers in :mod:`curses`.
Loading
0