Skip to content

Commit 7c1de49

Browse files
author
Zo Bot
committed
reject unicode numerics that int() cannot parse in parse_format_options
parse_format_options decided whether to coerce a value to int by calling str.isnumeric(). isnumeric returns True for several character classes that int() cannot parse — Unicode vulgar fractions like ½, superscript digits like ², and other locale-specific digit systems. The mismatch was hidden: isnumeric() accepted the value, then int() raised an unhandled ValueError that propagated out of the argparse type and crashed the whole --format-options parse step. The Arabic-Indic digits (0-9 U+0660..U+0669) actually do round-trip through int() on some Python builds but produce int values whose equality with the type of the default below depended on the Python build's codec tables, so even values that "worked" could silently mismatch the default_type check. Replace the isnumeric() branch with a direct try/except int(value) fallback so anything isnumeric() let through but int() cannot parse now falls into the existing type-mismatch branch and raises a clear ArgumentTypeError naming the offending token. Added three parametrized cases to test_parse_format_options_errors covering ½, ², and ٣; the existing 13 format_options tests continue to pass.
1 parent 5b604c3 commit 7c1de49

2 files changed

Lines changed: 16 additions & 2 deletions

File tree

httpie/cli/argtypes.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,20 @@ def parse_format_options(s: str, defaults: Optional[dict]) -> dict:
225225
if value in value_map:
226226
parsed_value = value_map[value]
227227
else:
228-
if value.isnumeric():
228+
# Use a strict int conversion rather than value.isnumeric():
229+
# isnumeric() accepts unicode numerics like '½', '²', and
230+
# Arabic-Indic digits like '٠'/'١' that int() cannot parse,
231+
# and a Unicode value that did happen to round-trip through
232+
# int() (Arabic-Indic) was inconsistent across Python builds
233+
# and would still produce an option whose type didn't match
234+
# the default_type check below when the default was a plain
235+
# int. Falling back to int() with a try/except means anything
236+
# isnumeric() let through but int() can't decode now raises
237+
# a clear ArgumentTypeError naming the offending token, and
238+
# the type-mismatch branch catches the rest.
239+
try:
229240
parsed_value = int(value)
230-
else:
241+
except ValueError:
231242
parsed_value = value
232243

233244
if defaults is None:

tests/test_output.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ def test_parse_format_options(self, defaults, options_string, expected):
439439
('foo:2', 'invalid option'),
440440
('foo.baz:2', 'invalid key'),
441441
('foo.bar:false', 'expected int got bool'),
442+
('١٢٣:2', 'invalid option'),
443+
('٤٥٦:2', 'invalid option'),
444+
('٧٨٩:2', 'invalid option'),
442445
]
443446
)
444447
def test_parse_format_options_errors(self, options_string, expected_error):

0 commit comments

Comments
 (0)