Skip to content
Merged
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
21 changes: 21 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ dev

- \[Short description of non-trivial change.\]

2.27.1.post4 (2026-11-05)
-------------------------

**Security hardening**

- `no_proxy` entries now match whole domain labels only:
`no_proxy=example.com` still bypasses the proxy for `example.com` and
`sub.example.com`, but no longer for `badexample.com`. A leading dot on an
entry is ignored. Backport of requests #7427 (2.34.0), the port of CPython
bpo-39057. No CVE or GHSA. Python 2 compatible.

**Bugfix**

- `check_compatibility()` accepts urllib3 post-release versions such as the
ActiveState `1.26.20.post4` build. It used to raise ValueError on them,
which `import requests` turned into a `RequestsDependencyWarning`.

This release uses the `X.Y.Z.postN` version scheme, replacing the
`+security.N` local versions of earlier ActiveState releases (inventory
2.27.1.4).

2.27.1+security.3 (2026-06-12)
-------------------------------

Expand Down
5 changes: 5 additions & 0 deletions requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
urllib3_version = urllib3_version.split('+', 1)[0].split('.')
assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.

# Only major.minor.patch matter. Drop a PEP 440 post-release segment such
# as the ActiveState '1.26.20.post4' builds, which would otherwise break
# the three-way unpack below.
urllib3_version = urllib3_version[:3]

# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
urllib3_version.append('0')
Expand Down
2 changes: 1 addition & 1 deletion requests/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'https://requests.readthedocs.io'
__version__ = '2.27.1+security.3'
__version__ = '2.27.1.post4'
__build__ = 0x022701
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
Expand Down
7 changes: 7 additions & 0 deletions requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,13 @@ def should_bypass_proxies(url, no_proxy):
host_with_port += ':{}'.format(parsed.port)

for host in no_proxy:
# Match whole domain labels only, so that "example.com" does
# not also bypass the proxy for "badexample.com". A leading
# dot on the entry is ignored. (CPython bpo-39057)
host = host.lstrip('.')
if parsed.hostname == host or host_with_port == host:
return True
host = '.' + host
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
# The URL does match something in no_proxy, so we don't want
# to apply the proxies on this URL.
Expand Down
26 changes: 26 additions & 0 deletions tests/test_check_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""check_compatibility must accept the version strings that ActiveState's
urllib3 builds report: plain, PEP 440 local (+security.N) and post-release
(.postN)."""

import pytest

from requests import check_compatibility


@pytest.mark.parametrize('urllib3_version', (
'1.26.20',
'1.26',
'1.26.20+security.2',
'1.26.20.post3',
'1.26.20.post4',
'1.21.1',
))
def test_supported_urllib3_versions(urllib3_version):
check_compatibility(urllib3_version, '3.0.4', None)


@pytest.mark.parametrize('urllib3_version', ('2.0.7', '1.20.0', '1.27.0.post1'))
def test_unsupported_urllib3_versions(urllib3_version):
with pytest.raises(AssertionError):
check_compatibility(urllib3_version, '3.0.4', None)
22 changes: 22 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,28 @@ def test_should_bypass_proxies_no_proxy(
assert should_bypass_proxies(url, no_proxy=no_proxy) == expected


@pytest.mark.parametrize(
'url, expected', (
('http://localhost/', True),
('http://anotherdomain.com:8888/', True),
('http://newdomain.com:1234/', True),
('http://www.newdomain.com:1234/', True),
('http://foo.d.o.t/', True),
('http://d.o.t/', True),
('http://prelocalhost/', False),
('http://badanotherdomain.com/', False),
('http://newdomain.com/', False),
('http://newdomain.com:1235/', False),
))
def test_should_bypass_proxies_no_proxy_domain_boundary(url, expected):
"""Ensure no_proxy matching respects domain boundaries and does not
greedily match domains that merely end with the no_proxy entry.
See CPython bpo-39057 and requests #7427.
"""
no_proxy = 'localhost, anotherdomain.com, newdomain.com:1234, .d.o.t'
assert should_bypass_proxies(url, no_proxy=no_proxy) == expected


@pytest.mark.skipif(os.name != 'nt', reason='Test only on Windows')
@pytest.mark.parametrize(
'url, expected, override', (
Expand Down
Loading