resolved RESOLVED PASS · 10 tool calls · 28 s · psf/requests
Fix malformed value parsing for Content-Type This PR addresses https://github.com/psf/requests/issues/6646 by cleaning up `_parse_content_type_header`. Instead of setting a default that we can't use in practice, we now skip value assignment completely if the value is malformed. Every RFC from 1521 to 9110 is clear that an `=` is required for a valid parameter. If it doesn't exist, we need to ignore it.
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"_parse_content_type_header\" src/requests"
} | {"status": "ok", "stdout": "src/requests/utils.py:504:def _parse_content_type_header(header):\nsrc/requests/utils.py:541: content_type, params = _parse_content_full{"status": "ok", "stdout": "src/requests/utils.py:504:def _parse_content_type_header(header):\nsrc/requests/utils.py:541: content_type, params = _parse_content_type_header(content_type)\n", "exit_code": 0} |
| 2 | read_file | {
"filepath": "src/requests/utils.py",
"start_line": 504,
"end_line": 530
} | {"status": "ok", "filepath": "src/requests/utils.py", "content": "def _parse_content_type_header(header):\n \"\"\"Returns content type and parameters from givenfull{"status": "ok", "filepath": "src/requests/utils.py", "content": "def _parse_content_type_header(header):\n \"\"\"Returns content type and parameters from given header\n\n :param header: string\n :return: tuple containing content type and dictionary of\n parameters\n \"\"\"\n\n tokens = header.split(\";\")\n content_type, params = tokens[0].strip(), tokens[1:]\n params_dict = {}\n items_to_strip = \"\\\"' \"\n\n for param in params:\n param = param.strip()\n if param:\n key, value = param, True\n index_of_equals = param.find(\"=\")\n if index_of_equals != -1:\n key = param[:index_of_equals].strip(items_to_strip)\n value = param[index_of_equals + 1 :].strip(items_to_strip)\n params_dict[key.lower()] = value\n return content_type, params_dict\n\n\ndef get_encoding_from_headers(headers):\n \"\"\"Returns encodings from given HTTP Header Dict.\n", "start_line": 504, "end_line": 530, "total_lines": 1086, "is_truncated": false} |
| 3 | write_file | {
"content": "from requests.utils import _parse_content_type_header\n\ndef test_parse_content_type_header():\n # Valid case\n ct, params = _parse_content_type_header(\"text/html; charset=utf-8\")\n assert ct == \"text/html\"\n assert params == {\"charset\": \"utf-8\"}\n\n # Malformed case: no equals sign\n ct, params = _parse_content_type_header(\"text/html; malformed\")\n print(f\"Malformed case result: {params}\")\n # Current behavior: {\"malformed\": True}\n # Expected behavior: {}\n assert params == {}\n\n # Mixed case\n ct, params = _parse_content_type_header(\"text/html; charset=utf-8; malformed\")\n print(f\"Mixed case result: {params}\")\n assert params == {\"charset\": \"utf-8\"}\n\nif __name__ == \"__main__\":\n try:\n test_parse_content_type_header()\n print(\"Tests passed!\")\n except AssertionError as e:\n print(f\"Test failed: {e}\")\n",
"filepath": "/tmp/test_ct.py"
} | {"status": "ok", "filepath": "/tmp/test_ct.py", "size": 862}full{"status": "ok", "filepath": "/tmp/test_ct.py", "size": 862} |
| 4-5 ×2 | run_command | {
"command": "PYTHONPATH=src python3 /tmp/test_ct.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open ffull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open file '/tmp/swe_work/or20_submission/requests_7309/a/tmp/test_ct.py': [Errno 2] No such file or directory\n", "exit_code": 2}} |
| 6 | write_file | {
"content": "from requests.utils import _parse_content_type_header\n\ndef test_parse_content_type_header():\n # Valid case\n ct, params = _parse_content_type_header(\"text/html; charset=utf-8\")\n assert ct == \"text/html\"\n assert params == {\"charset\": \"utf-8\"}\n\n # Malformed case: no equals sign\n ct, params = _parse_content_type_header(\"text/html; malformed\")\n print(f\"Malformed case result: {params}\")\n # Current behavior: {\"malformed\": True}\n # Expected behavior: {}\n assert params == {}\n\n # Mixed case\n ct, params = _parse_content_type_header(\"text/html; charset=utf-8; malformed\")\n print(f\"Mixed case result: {params}\")\n assert params == {\"charset\": \"utf-8\"}\n\nif __name__ == \"__main__\":\n try:\n test_parse_content_type_header()\n print(\"Tests passed!\")\n except AssertionError as e:\n print(f\"Test failed: {e}\")\n",
"filepath": "test_ct.py"
} | {"status": "ok", "filepath": "test_ct.py", "size": 862}full{"status": "ok", "filepath": "test_ct.py", "size": 862} |
| 7 | run_command | {
"command": "PYTHONPATH=src python3 test_ct.py"
} | {"status": "ok", "stdout": "Malformed case result: {'malformed': True}\nTest failed: \n", "exit_code": 0}full{"status": "ok", "stdout": "Malformed case result: {'malformed': True}\nTest failed: \n", "exit_code": 0} |
| 8 | edit_file | {
"filepath": "src/requests/utils.py",
"new_string": " for param in params:\n param = param.strip()\n if param:\n index_of_equals = param.find(\"=\")\n if index_of_equals != -1:\n key = param[:index_of_equals].strip(items_to_strip)\n value = param[index_of_equals + 1 :].strip(items_to_strip)\n params_dict[key.lower()] = value",
"old_string": " for param in params:\n param = param.strip()\n if param:\n key, value = param, True\n index_of_equals = param.find(\"=\")\n if index_of_equals != -1:\n key = param[:index_of_equals].strip(items_to_strip)\n value = param[index_of_equals + 1 :].strip(items_to_strip)\n params_dict[key.lower()] = value"
} | {"status": "ok", "filepath": "src/requests/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/utils.py\n+++ b/src/requests/utils.py\nfull{"status": "ok", "filepath": "src/requests/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/utils.py\n+++ b/src/requests/utils.py\n@@ -517,12 +517,11 @@\n for param in params:\n param = param.strip()\n if param:\n- key, value = param, True\n index_of_equals = param.find(\"=\")\n if index_of_equals != -1:\n key = param[:index_of_equals].strip(items_to_strip)\n value = param[index_of_equals + 1 :].strip(items_to_strip)\n- params_dict[key.lower()] = value\n+ params_dict[key.lower()] = value\n return content_type, params_dict\n \n \n", "is_truncated": false} |
| 9 | run_command | {
"command": "PYTHONPATH=src python3 test_ct.py"
} | {"status": "ok", "stdout": "Malformed case result: {}\nMixed case result: {'charset': 'utf-8'}\nTests passed!\n", "exit_code": 0}full{"status": "ok", "stdout": "Malformed case result: {}\nMixed case result: {'charset': 'utf-8'}\nTests passed!\n", "exit_code": 0} |
| 10 | run_command | {
"command": "rm test_ct.py"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 11 | submit_patch | [] | {"status": "ok", "patch_size": 1723, "files_changed": 2}full{"status": "ok", "patch_size": 1723, "files_changed": 2} |
diff --git a/src/requests/utils.py b/src/requests/utils.py
index 54959bb8..805cf9d2 100644
--- a/src/requests/utils.py
+++ b/src/requests/utils.py
@@ -517,12 +517,11 @@ def _parse_content_type_header(header):
for param in params:
param = param.strip()
if param:
- key, value = param, True
index_of_equals = param.find("=")
if index_of_equals != -1:
key = param[:index_of_equals].strip(items_to_strip)
value = param[index_of_equals + 1 :].strip(items_to_strip)
- params_dict[key.lower()] = value
+ params_dict[key.lower()] = value
return content_type, params_dict
diff --git a/tmp/test_ct.py b/tmp/test_ct.py
new file mode 100644
index 00000000..ded5c7ec
--- /dev/null
+++ b/tmp/test_ct.py
@@ -0,0 +1,26 @@
+from requests.utils import _parse_content_type_header
+
+def test_parse_content_type_header():
+ # Valid case
+ ct, params = _parse_content_type_header("text/html; charset=utf-8")
+ assert ct == "text/html"
+ assert params == {"charset": "utf-8"}
+
+ # Malformed case: no equals sign
+ ct, params = _parse_content_type_header("text/html; malformed")
+ print(f"Malformed case result: {params}")
+ # Current behavior: {"malformed": True}
+ # Expected behavior: {}
+ assert params == {}
+
+ # Mixed case
+ ct, params = _parse_content_type_header("text/html; charset=utf-8; malformed")
+ print(f"Mixed case result: {params}")
+ assert params == {"charset": "utf-8"}
+
+if __name__ == "__main__":
+ try:
+ test_parse_content_type_header()
+ print("Tests passed!")
+ except AssertionError as e:
+ print(f"Test failed: {e}")
..s..................................................................... [ 32%]
........................................................................ [ 65%]
...........................................................sssssssssss.. [ 98%]
...s [100%]
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464: PytestConfigWarning: Unknown config option: timeout
self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
tests/test_utils.py::TestContentEncodingDetection::test_none
tests/test_utils.py::TestContentEncodingDetection::test_pragmas[<meta charset="UTF-8">]
tests/test_utils.py::TestContentEncodingDetection::test_pragmas[<meta http-equiv="Content-type" content="text/html;charset=UTF-8">]
tests/test_utils.py::TestContentEncodingDetection::test_pragmas[<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />]
tests/test_utils.py::TestContentEncodingDetection::test_pragmas[<?xml version="1.0" encoding="UTF-8"?>]
tests/test_utils.py::TestContentEncodingDetection::test_precedence
/tmp/swe_work/or20_submission/requests_7309/b/workspace/src/requests/utils.py:484: DeprecationWarning: In requests 3.0, get_encodings_from_content will be removed. For more information, please see the discussion on issue #2266. (This warning should only appear once.)
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
207 passed, 13 skipped, 7 warnings in 0.12s
[2026-09-23 22:13:51,109] WARNING in core: flasgger is not installed; serving the static landing page at / and skipping the Swagger UI and /spec.json.