diff --git a/httpie/cli/nested_json/parse.py b/httpie/cli/nested_json/parse.py index 323a22eef1..9792c9c0f9 100644 --- a/httpie/cli/nested_json/parse.py +++ b/httpie/cli/nested_json/parse.py @@ -137,8 +137,8 @@ def send_buffer() -> Iterator[Token]: value = ''.join(buffer) kind = TokenKind.TEXT if not backslashes: - for variation, kind in [ - (int, TokenKind.NUMBER), + for variation, variation_kind in [ + (canonical_int, TokenKind.NUMBER), (check_escaped_int, TokenKind.TEXT), ]: try: @@ -146,6 +146,7 @@ def send_buffer() -> Iterator[Token]: except ValueError: continue else: + kind = variation_kind break yield Token( kind=kind, @@ -189,5 +190,20 @@ def check_escaped_int(value: str) -> str: return value[1:] +def canonical_int(value: str) -> int: + """Convert to int only when the value has no leading zeros. + + A literal such as ``01`` is a valid JSON *key*, not a number - JSON + numbers can't have leading zeros. Coercing it to ``int`` silently + rewrites the key to ``1`` and can collide with a real ``1`` key. + """ + if not value: + raise ValueError('Not an int') + converted = int(value) + if str(converted) != value: + raise ValueError('Not a canonical int') + return converted + + def assert_cant_happen(): raise ValueError('Unexpected value') diff --git a/httpie/output/writer.py b/httpie/output/writer.py index 4a2949bce2..136f0abac7 100644 --- a/httpie/output/writer.py +++ b/httpie/output/writer.py @@ -167,7 +167,7 @@ def get_stream_type_and_kwargs( raw_content_type_header = headers.get('Content-Type', None) if raw_content_type_header: content_type_header, _ = parse_content_type_header(raw_content_type_header) - is_stream = (content_type_header == 'text/event-stream') + is_stream = (content_type_header.lower() == 'text/event-stream') if not env.stdout_isatty and not prettify_groups: stream_class = RawStream diff --git a/tests/test_json.py b/tests/test_json.py index e758ebe7f4..656e7e1c92 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -172,6 +172,16 @@ def test_complex_json_arguments_with_non_json(httpbin, request_type, value): ], {'bottle-on-wall': [1, 2, 3]}, ), + ( + # Leading-zero keys stay keys and don't collide with '1' + ['01=x', '1=y'], + {'01': 'x', '1': 'y'}, + ), + ( + # ...but canonical integers still become numeric indices in paths + ['kids[0]=Thelma', 'kids[10]=Ashley'], + {'kids': ['Thelma', None, None, None, None, None, None, None, None, None, 'Ashley']}, + ), ( [ 'pet[species]=Dahut', diff --git a/tests/test_stream.py b/tests/test_stream.py index b0b9b8bde8..d9647fca96 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -128,6 +128,10 @@ def test_redirected_stream(httpbin): ['Accept:text/event-stream; charset=utf-8'], 3 ), + ( + ['Accept:Text/Event-Stream'], + 3 + ), ( ['Accept:text/plain'], 1