Incorrectly strips trailing dot from absolute hostnames, causing TooManyRedirects
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 38/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Stale
- Tech stack
- python
- Domain
- api, networking
Research direction
Start with requests/api.py and requests/sessions.py, especially the request and redirect paths shown in the traceback, then follow how urllib3 constructs the TLS SNI and Host header. Reproduce the issue with requests.get("https://pyropus.ca./") and verify that the Host header preserves the trailing dot while the request completes without a redirect loop.
Written by the indexing model from the issue text.
Description
requests appears to be incorrectly stripping the trailing dot on absolute hostnames (i.e. explicitly marked as in the root DNS namespace) in URLs when constructing the Host: request header, like pyropus.ca.. This causes redirect loops when the site chooses the absolute hostname as canonical and redirects requests without the trailing dot.
The example site redirects requests with a host header of Host: pyropus.ca to the same URL with the host name changed to the canonical absolute version:
HTTP/1.1 301 Moved Permanently
[...]
Location: https://pyropus.ca./
requests then strips the dot and resends the request, resulting in a redirect loop.
The trailing dot needs to be stripped from the SNI header for https requests (required by TLS/etc spec) but it should not be stripped from the Host: header value.
When fixing the SNI trailing dot issue, curl had this bug because they changed it to also affect the Host: header value, but they've fixed it by reverting that part of the change:
https://github.com/curl/curl/issues/8290
Many other common user agents that I've tested handle this properly:
- all GUI browers, to my knowledge, preserve the absolute domain name in the Host: header - I've tested Firefox, Chromium, Vivaldi, Konqueror, and Safari
- wget also preserves the absolute domain name on redirect URLs
- curl is now fixed and handles it
A few less common ones have the same buggy behaviour, or did -- I haven't re-checked:
- lynx, links, and elinks handle it like curl, erroring out on a redirect loop
Expected Result
Successful 200 response with content.
Actual Result
Traceback (most recent call last):
File "<string>", line 1, in <module>
File ".../test/.venv/lib/python3.9/site-packages/requests/api.py", line 75, in get
return request('get', url, params=params, **kwargs)
File ".../test/.venv/lib/python3.9/site-packages/requests/api.py", line 61, in request
return session.request(method=method, url=url, **kwargs)
File ".../test/.venv/lib/python3.9/site-packages/requests/sessions.py", line 529, in request
resp = self.send(prep, **send_kwargs)
File ".../test/.venv/lib/python3.9/site-packages/requests/sessions.py", line 667, in send
history = [resp for resp in gen]
File ".../test/.venv/lib/python3.9/site-packages/requests/sessions.py", line 667, in <listcomp>
history = [resp for resp in gen]
File ".../test/.venv/lib/python3.9/site-packages/requests/sessions.py", line 166, in resolve_redirects
raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp)
requests.exceptions.TooManyRedirects: Exceeded 30 redirects.
Reproduction Steps
import requests
requests.get("https://pyropus.ca./")
System Information
$ python -m requests.help
{
"chardet": {
"version": null
},
"charset_normalizer": {
"version": "2.0.12"
},
"cryptography": {
"version": ""
},
"idna": {
"version": "3.3"
},
"implementation": {
"name": "CPython",
"version": "3.9.2"
},
"platform": {
"release": "5.17.5",
"system": "Linux"
},
"pyOpenSSL": {
"openssl_version": "",
"version": null
},
"requests": {
"version": "2.27.1"
},
"system_ssl": {
"version": "101010bf"
},
"urllib3": {
"version": "1.26.9"
},
"using_charset_normalizer": true,
"using_pyopenssl": false
}
Edit: added a couple words to the summary to clarify.
- Dominant language
- Python
- Stars
- 54.7k
- Forks
- 10.8k
- Avg merge
- 18m
- Merged PRs (30d)
- 1
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from psf/requests
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
`iter_lines(decode_unicode=True, delimiter=<str>)` raises `TypeError` when `Response.encoding` is `None`May be free again A pull request for this issue was closed without being merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
-
get_encoding_from_headers treats any content type containing 'text' as text/* (ISO-8859-1)Possibly taken @suyashgitt claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Invalid `params` type (e.g. int) raises misleading "TypeError: Cannot mix str and non-str arguments"Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Similar issues
-
plot_graph crashes when a mesh node has no m2m edgePossibly taken @tygvi claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
mllam/neural-lam#883 · 1 comment ·
Maintainers usually reply within 2 days
-
enhancement possible-duplicate
Difficulty 1/5 1-3 hours Newbie friendliness 78/100
AOSSIE-Org/PictoPy#1715 · 2 comments ·
Maintainers usually reply within 6 days
-
SQLite connector omits generated columns from the catalog, and drops relationships sourced on themOpen
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
NVIDIA/auto-ontology#511 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
ethereum/execution-specs#3996 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
cpp-best-practices/project_validator#267 ·
Maintainers usually reply within 1 day