From fa263f2bc72d8b0891139a426a6efab83606b6c4 Mon Sep 17 00:00:00 2001 From: lingxiu58 Date: Mon, 25 May 2026 06:08:03 +0000 Subject: [PATCH] fix: preserve Content-Type when sending JSON data with a single custom header When sending JSON data with exactly one custom header, the Content-Type header was silently dropped. Root cause: apply_missing_repeated_headers() used CIMultiDict.popone()+update() which can lose entries due to fragile CIMultiDict/CaseInsensitiveDict interaction. Fix: rebuild headers from scratch using add() instead of pop+replace, materializing items to lists before iteration. Closes #1834 --- httpie/client.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/httpie/client.py b/httpie/client.py index a1da284a7c..810dfde10e 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -238,24 +238,31 @@ def apply_missing_repeated_headers( ones. This allows the requests to be prepared as usual, and then later merged with headers that are specified multiple times.""" - new_headers = HTTPHeadersDict(prepared_request.headers) - for prepared_name, prepared_value in prepared_request.headers.items(): + # Materialize items to lists to avoid CIMultiDict/CaseInsensitiveDict + # interaction issues that can silently drop headers (e.g. Content-Type). + prepared_items = list(prepared_request.headers.items()) + original_items = list(original_headers.items()) + new_headers = HTTPHeadersDict() + for prepared_name, prepared_value in prepared_items: if prepared_name not in original_headers: + new_headers.add(prepared_name, prepared_value) continue - original_keys, original_values = zip(*filter( - lambda item: item[0].casefold() == prepared_name.casefold(), - original_headers.items() - )) + matched = [ + (k, v) for k, v in original_items + if k.casefold() == prepared_name.casefold() + ] + original_keys, original_values = zip(*matched) if prepared_value not in original_values: # If the current value is not among the initial values # set for this field, then it means that this field got # overridden on the way, and we should preserve it. + new_headers.add(prepared_name, prepared_value) continue - new_headers.popone(prepared_name) - new_headers.update(zip(original_keys, original_values)) + for key, value in zip(original_keys, original_values): + new_headers.add(key, value) prepared_request.headers = new_headers