Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
This document records all notable changes to [HTTPie](https://httpie.io).
This project adheres to [Semantic Versioning](https://semver.org/).

## Unreleased

- Reject nested object or array access through a JSON `null` field with a type error instead of silently replacing the request body. Plain `null` values, direct value overrides, and filling sparse array entries remain supported.

## [3.2.4](https://github.com/httpie/cli/compare/3.2.3...3.2.4) (2024-11-01)

- Fix default certs loading and unpin `requests`. ([#1596](https://github.com/httpie/cli/issues/1596))
Expand Down
7 changes: 4 additions & 3 deletions httpie/cli/nested_json/interpret.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ def object_for(kind: PathAction) -> Any:
else:
assert_cant_happen()

# Initialize the root only; nested null values must pass type checking.
if context is None:
context = cursor = object_for(paths[0].kind)

for index, (path, next_path) in enumerate(zip(paths, paths[1:])):
# If there is no context yet, set it.
if cursor is None:
context = cursor = object_for(path.kind)
if path.kind is PathAction.KEY:
type_check(index, path, dict)
if next_path.kind is PathAction.SET:
Expand Down
35 changes: 35 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,41 @@ def test_nested_json_errors(input_json, expected_error, httpbin):
assert expected_lines == exc_lines


@pytest.mark.parametrize('field', ['foo', 'foo[bar]', '[0][foo]'])
@pytest.mark.parametrize(
'access, kind, required_type',
[('[key]', 'key', 'object'), ('[0]', 'index', 'array'), ('[]', 'append', 'array')],
)
def test_nested_json_null_container(field, access, kind, required_type):
with pytest.raises(NestedJSONSyntaxError) as exc:
http('--offline', DUMMY_URL, f'{field}:=null', f'{field}{access}=value')

assert str(exc.value).splitlines()[0] == (
f"HTTPie Type Error: Cannot perform {kind!r} based access on {field!r} "
"which has a type of 'NoneType' but this operation "
f"requires a type of {required_type!r}."
)


@pytest.mark.parametrize(
'input_json, expected_json',
[
(['keep=value', 'foo:=null'], {'keep': 'value', 'foo': None}),
(
['keep=value', 'foo:=null', 'foo=value'],
{'keep': 'value', 'foo': 'value'},
),
(
['keep=value', 'foo[1]=last', 'foo[0][bar]=first'],
{'keep': 'value', 'foo': [{'bar': 'first'}, 'last']},
),
],
)
def test_nested_json_null_values(input_json, expected_json):
r = http('--offline', DUMMY_URL, *input_json)
assert r.json == expected_json


def test_nested_json_sparse_array(httpbin_both):
r = http(httpbin_both + '/post', 'test[0]:=1', 'test[100]:=1')
assert len(r.json['json']['test']) == 101
Loading