Issue 1:
Content-Length header is duplicated if specified on Python3, leading to 400 responses:

Python 3.3.0 (default, Apr 30 2013, 06:45:58)
[GCC 4.2.1 Compatible Apple Clang 4.0 ((tags/Apple/clang-421.0.60))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> requests.post("http://httpbin.org/post", headers={"Content-Length": 0})
<Response [400]>

We need to go deeper:

>>> import six
>>> six.moves.http_client.HTTPConnection.debuglevel = 1
>>> requests.post("http://httpbin.org/post", headers={"Content-Length": 0})
send: b'POST /post HTTP/1.1\r\nHost: httpbin.org\r\nAccept-Encoding: identity\r\nContent-Length: 0\r\nUser-Agent: python-requests/1.2.0 CPython/3.3.0 Darwin/12.3.0\r\nContent-Length: 0\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, compress\r\n\r\n'
reply: 'HTTP/1.1 400 BAD_REQUEST\r\n'
header: Content-Length header: Connection <Response [400]>

That's what happens:

POST /post HTTP/1.1
Host: httpbin.org
Accept-Encoding: identity
Content-Length: 0
User-Agent: python-requests/1.2.0 CPython/3.3.0 Darwin/12.3.0
Content-Length: 0
Accept: */*
Accept-Encoding: gzip, deflate, compress

Two content-lengths, one 400 response :-(

On Python2 everything's OK.

Issue 2:
Spaces are not allowed in header names: https://www.rfc-editor.org/rfc/rfc9112.html#name-field-line-parsing

When requests encounters such invalid header names, it will not only throw away the invalid header, but also stops parsing all following headers.

When the invalid header comes before headers such as Content-Length, this can lead to requests getting stuck indefinitely.

Expected Result

Requests ignores the invalid header (or shows/fixes it; the RFC is only defining behavior for servers and proxies, it is unclear what the correct behavior for clients is and different clients behave differently) and continues parsing of the response.

Actual Result

Requests ignores the invalid header and all headers after the invalid one. This can lead to requests getting stuck.

Reproduction Steps

pip install wptserve: server that allows us to send invalid responses

python test.py

test.py:

import wptserve
from wptserve.handlers import handler, as_is_handler
import requests
from requests.exceptions import ConnectionError
import subprocess

http1 = wptserve.WebTestHttpd(port=8081,
routes=[("*", "*.asis", as_is_handler)],
use_ssl=False,
key_file=None,
certificate=None,
)
http1.start()

@handler
def space_after_length(request, response):
return 200, [("Content-Length", 14), ("SPACE ",'space'), ("hidden", "hidden")], '<div>ABC</div>'
http1.router.register("*", "/space_after_length", space_after_length)

@handler
def space_first(request, response):
return 200, [("SPACE ",'space'), ("Content-Length", 14), ("hidden", "hidden")], '<div>ABC</div>'
http1.router.register("*", "/space_first", space_first)

for path in ["/space_first", "/space_after_length"]:
url = f"http://localhost:8081{path}"
curl = subprocess.check_output(["curl", "-s", "-I", url]).decode("utf-8")
try:
r = requests.get(url, timeout=1)
# r = requests.get(url) # Stuck forever
headers = r.headers
except ConnectionError:
headers = "Hangs forever or timeout"
finally:
print(f"{path}\ncurl output:\n{curl}requests output:\n{headers}\n")

Output:

/space_first
curl output:
HTTP/1.1 200 OK
SPACE : space
Content-Length: 14
hidden: hidden
Server: BaseHTTP/0.6 Python/3.9.6
Date: Thu, 29 Sep 2022 13:46:58 GMT

requests output:
Hangs forever or timeout

/space_after_length
curl output:
HTTP/1.1 200 OK
Content-Length: 14
SPACE : space
hidden: hidden
Server: BaseHTTP/0.6 Python/3.9.6
Date: Thu, 29 Sep 2022 13:46:59 GMT

requests output:
{'Content-Length': '14'}

System Information

$ python -m requests.help

{
"chardet": {
"version": null
},
"charset_normalizer": {
"version": "2.1.1"
},
"cryptography": {
"version": ""
},
"idna": {
"version": "3.4"
},
"implementation": {
"name": "CPython",
"version": "3.9.6"
},
"platform": {
"release": "21.6.0",
"system": "Darwin"
},
"pyOpenSSL": {
"openssl_version": "",
"version": null
},
"requests": {
"version": "2.28.1"
},
"system_ssl": {
"version": "1010111f"
},
"urllib3": {
"version": "1.26.12"
},
"using_charset_normalizer": true,
"using_pyopenssl": false
}