From fd7dd430e6974a4c3fda54c6011697b592feb88f Mon Sep 17 00:00:00 2001 From: XiaoLei Wu Date: Wed, 21 Oct 2026 17:05:30 +0800 Subject: [PATCH] Validate min_unit in bs_size_human_readable Only BS_BUNIT_UNDEF is normalized to bytes. Other values outside the binary unit range can index past b_units, including decimal units passed through the Python human_readable() wrapper. Treat unsupported values as B before scaling and indexing, preserving the existing UNDEF fallback. B remains the minimum unit, so larger sizes can still be promoted to other binary units. Add regression coverage for the range boundaries, invalid integer values and decimal unit names, with zero and signed sizes and both translation modes. --- src/bs_size.c | 5 +++-- tests/lbs_py_override_unittest.py | 9 +++++++++ tests/libbytesize_unittest.py | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/bs_size.c b/src/bs_size.c index cda12f3..b1fb5a4 100644 --- a/src/bs_size.c +++ b/src/bs_size.c @@ -829,7 +829,8 @@ char* bs_size_convert_to (const BSSize size, BSUnit unit, BSError **error) { /** * bs_size_human_readable: - * @min_unit: the smallest unit the returned representation should use + * @min_unit: the smallest unit the returned representation should use. + * Values outside the binary unit range are treated as #BS_BUNIT_B. * @max_places: maximum number of decimal places the representation should use * @xlate: whether to try to translate the representation or not * @@ -853,7 +854,7 @@ char* bs_size_human_readable (const BSSize size, BSBunit min_unit, int max_place mpf_init2 (cur_val, BS_FLOAT_PREC_BITS); mpf_set_z (cur_val, size->bytes); - if (min_unit == BS_BUNIT_UNDEF) + if (min_unit < BS_BUNIT_B || min_unit > BS_BUNIT_YiB) min_unit = BS_BUNIT_B; sign = mpf_sgn (cur_val); diff --git a/tests/lbs_py_override_unittest.py b/tests/lbs_py_override_unittest.py index d4d38ea..b803f80 100755 --- a/tests/lbs_py_override_unittest.py +++ b/tests/lbs_py_override_unittest.py @@ -305,6 +305,15 @@ def testConvertTo(self): locale.setlocale(locale.LC_ALL,'en_US.UTF-8') + def testHumanReadableDecimalMinUnit(self): + for spec in ("0 B", "1.5 KiB", "-1.5 KiB"): + size = Size(spec) + for xlate in (False, True): + expected = size.human_readable(xlate=xlate) + for min_unit in ("KB", "MB"): + with self.subTest(spec=spec, min_unit=min_unit, xlate=xlate): + self.assertEqual(size.human_readable(min_unit, xlate=xlate), expected) + def testRoundToNearest(self): size = Size("1.5 KiB") conv = size.round_to_nearest(Size("1 KiB"), rounding=ROUND_UP) diff --git a/tests/libbytesize_unittest.py b/tests/libbytesize_unittest.py index e958866..5b4412d 100755 --- a/tests/libbytesize_unittest.py +++ b/tests/libbytesize_unittest.py @@ -9,7 +9,7 @@ from locale_utils import get_avail_locales, missing_locales, requires_locales -from bytesize import KiB, MB, GiB, ROUND_UP, ROUND_DOWN, ROUND_HALF_UP, OverflowError, InvalidSpecError +from bytesize import B, KiB, MB, GiB, YiB, ROUND_UP, ROUND_DOWN, ROUND_HALF_UP, OverflowError, InvalidSpecError # SizeStruct is part of the 'private' API and needs to be imported differently # when running from locally build tree and when using installed library @@ -423,6 +423,12 @@ def testGetBytesStr(self): #enddef def testHumanReadable(self): + strSizeStruct = SizeStruct.new_from_str("1.5 KiB").human_readable(B, 2, False) + self.assertEqual(strSizeStruct, "1.5 KiB") + + strSizeStruct = SizeStruct.new_from_str("1.5 YiB").human_readable(YiB, 2, False) + self.assertEqual(strSizeStruct, "1.5 YiB") + strSizeStruct = SizeStruct.new_from_str("12 KiB").human_readable(KiB, 2, False) self.assertEqual(strSizeStruct, "12 KiB") @@ -447,6 +453,15 @@ def testHumanReadable(self): self.assertEqual(SizeStruct.new_from_str(strSizeStruct).get_bytes(), (100 * 1024**3, 1)) #enddef + def testHumanReadableInvalidMinUnit(self): + for spec in ("0 B", "1.5 KiB", "-1.5 KiB"): + size = SizeStruct.new_from_str(spec) + for xlate in (False, True): + expected = size.human_readable(B, 2, xlate) + for min_unit in (9, -1, 10, 21, 100): + with self.subTest(spec=spec, min_unit=min_unit, xlate=xlate): + self.assertEqual(size.human_readable(min_unit, 2, xlate), expected) + @requires_locales({'cs_CZ.UTF-8'}) def testHumanReadableLocale(self): locale.setlocale(locale.LC_ALL, 'cs_CZ.UTF-8')