Skip to content
Open
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
4 changes: 3 additions & 1 deletion lib/urllib3/_base_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT
from .util.url import Url

_TYPE_BODY = typing.Union[bytes, typing.IO[typing.Any], typing.Iterable[bytes], str]
_TYPE_BODY = typing.Union[
bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str
]


class ProxyConfig(typing.NamedTuple):
Expand Down
1 change: 0 additions & 1 deletion lib/urllib3/_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None:
for key, val in other.items():
self.add(key, val)
elif isinstance(other, typing.Iterable):
other = typing.cast(typing.Iterable[tuple[str, str]], other)
for key, value in other:
self.add(key, value)
elif hasattr(other, "keys") and hasattr(other, "__getitem__"):
Expand Down
26 changes: 8 additions & 18 deletions lib/urllib3/_version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# file generated by setuptools-scm
# file generated by vcs-versioning
# don't change, don't track in version control
from __future__ import annotations

__all__ = [
"__version__",
Expand All @@ -10,25 +11,14 @@
"commit_id",
]

TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Tuple
from typing import Union

VERSION_TUPLE = Tuple[Union[int, str], ...]
COMMIT_ID = Union[str, None]
else:
VERSION_TUPLE = object
COMMIT_ID = object

version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
commit_id: COMMIT_ID
__commit_id__: COMMIT_ID
__version_tuple__: tuple[int | str, ...]
version_tuple: tuple[int | str, ...]
commit_id: str | None
__commit_id__: str | None

__version__ = version = '2.6.3'
__version_tuple__ = version_tuple = (2, 6, 3)
__version__ = version = '2.7.0'
__version_tuple__ = version_tuple = (2, 7, 0)

__commit_id__ = commit_id = None
8 changes: 4 additions & 4 deletions lib/urllib3/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@ def request_chunked(
"""
warnings.warn(
"HTTPConnection.request_chunked() is deprecated and will be removed "
"in urllib3 v2.1.0. Instead use HTTPConnection.request(..., chunked=True).",
category=DeprecationWarning,
"in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).",
category=FutureWarning,
stacklevel=2,
)
self.request(method, url, body=body, headers=headers, chunked=True)
Expand Down Expand Up @@ -697,9 +697,9 @@ def set_cert(
"""
warnings.warn(
"HTTPSConnection.set_cert() is deprecated and will be removed "
"in urllib3 v2.1.0. Instead provide the parameters to the "
"in urllib3 v3.0. Instead provide the parameters to the "
"HTTPSConnection constructor.",
category=DeprecationWarning,
category=FutureWarning,
stacklevel=2,
)

Expand Down
33 changes: 23 additions & 10 deletions lib/urllib3/connectionpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ def __init__(

if self.proxy:
# Enable Nagle's algorithm for proxies, to avoid packet fragmentation.
# We cannot know if the user has added default socket options, so we cannot replace the
# list.
# Defaulting `socket_options` to an empty list avoids it defaulting to
# ``HTTPConnection.default_socket_options``.
self.conn_kw.setdefault("socket_options", [])

self.conn_kw["proxy"] = self.proxy
Expand Down Expand Up @@ -702,8 +702,15 @@ def urlopen( # type: ignore[override]
redirect. Typically this won't need to be set because urllib3 will
auto-populate the value when needed.
"""
parsed_url = parse_url(url)
destination_scheme = parsed_url.scheme
# Ensure that the URL we're connecting to is properly encoded
if url.startswith("/"):
# URLs starting with / are inherently schemeless.
url = to_str(_encode_target(url))
destination_scheme = None
else:
parsed_url = parse_url(url)
destination_scheme = parsed_url.scheme
url = to_str(parsed_url.url)

if headers is None:
headers = self.headers
Expand All @@ -718,12 +725,6 @@ def urlopen( # type: ignore[override]
if assert_same_host and not self.is_same_host(url):
raise HostChangedError(self, url, retries)

# Ensure that the URL we're connecting to is properly encoded
if url.startswith("/"):
url = to_str(_encode_target(url))
else:
url = to_str(parsed_url.url)

conn = None

# Track whether `conn` needs to be released before
Expand Down Expand Up @@ -896,6 +897,18 @@ def urlopen( # type: ignore[override]
body = None
headers = HTTPHeaderDict(headers)._prepare_for_method_change()

# Strip headers marked as unsafe to forward to the redirected location.
# Check remove_headers_on_redirect to avoid a potential network call within
# self.is_same_host() which may use socket.gethostbyname() in the future.
if retries.remove_headers_on_redirect and not self.is_same_host(
redirect_location
):
new_headers = headers.copy() # type: ignore[union-attr]
for header in headers:
if header.lower() in retries.remove_headers_on_redirect:
new_headers.pop(header, None)
headers = new_headers

try:
retries = retries.increment(method, url, response=response, _pool=self)
except MaxRetryError:
Expand Down
6 changes: 5 additions & 1 deletion lib/urllib3/contrib/emscripten/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(
):
self._pool = None # set by pool class
self._body = None
self._uncached_read_occurred = False
self._response = internal_response
self._url = url
self._connection = connection
Expand Down Expand Up @@ -160,10 +161,13 @@ def read(
# don't cache partial content
cache_content = False
data = self._response.body.read(amt)
self._uncached_read_occurred = True
else: # read all we can (and cache it)
data = self._response.body.read()
if cache_content:
if cache_content and not self._uncached_read_occurred:
self._body = data
else:
self._uncached_read_occurred = True
if self.length_remaining is not None:
self.length_remaining = max(self.length_remaining - len(data), 0)
if len(data) == 0 or (
Expand Down
15 changes: 7 additions & 8 deletions lib/urllib3/contrib/pyopenssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

This needs the following packages installed:

* `pyOpenSSL`_ (tested with 16.0.0)
* `cryptography`_ (minimum 1.3.4, from pyopenssl)
* `idna`_ (minimum 2.0)
* `pyOpenSSL`_ (tested with 19.0.0)
* `cryptography`_ (minimum 2.3, from pyopenssl)
* `idna`_ (minimum 2.1, from cryptography)

However, pyOpenSSL depends on cryptography, so while we use all three directly here we
end up having relatively few packages required.
Expand Down Expand Up @@ -56,7 +56,6 @@ class UnsupportedExtension(Exception): # type: ignore[no-redef]
import typing
from io import BytesIO
from socket import socket as socket_cls
from socket import timeout

from .. import util

Expand Down Expand Up @@ -311,7 +310,7 @@ def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes:
raise
except OpenSSL.SSL.WantReadError as e:
if not util.wait_for_read(self.socket, self.socket.gettimeout()):
raise timeout("The read operation timed out") from e
raise TimeoutError("The read operation timed out") from e
else:
return self.recv(*args, **kwargs)

Expand All @@ -336,7 +335,7 @@ def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int:
raise
except OpenSSL.SSL.WantReadError as e:
if not util.wait_for_read(self.socket, self.socket.gettimeout()):
raise timeout("The read operation timed out") from e
raise TimeoutError("The read operation timed out") from e
else:
return self.recv_into(*args, **kwargs)

Expand All @@ -353,7 +352,7 @@ def _send_until_done(self, data: bytes) -> int:
return self.connection.send(data) # type: ignore[no-any-return]
except OpenSSL.SSL.WantWriteError as e:
if not util.wait_for_write(self.socket, self.socket.gettimeout()):
raise timeout() from e
raise TimeoutError() from e
continue
except OpenSSL.SSL.SysCallError as e:
raise OSError(e.args[0], str(e)) from e
Expand Down Expand Up @@ -520,7 +519,7 @@ def wrap_socket(
cnx.do_handshake()
except OpenSSL.SSL.WantReadError as e:
if not util.wait_for_read(sock, sock.gettimeout()):
raise timeout("select timed out") from e
raise TimeoutError("select timed out") from e
continue
except OpenSSL.SSL.Error as e:
raise ssl.SSLError(f"bad handshake: {e!r}") from e
Expand Down
2 changes: 1 addition & 1 deletion lib/urllib3/contrib/socks.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def _new_conn(self) -> socks.socksocket:
raise NewConnectionError(
self, f"Failed to establish a new connection: {error}"
)
else:
else: # Defensive: see https://github.com/urllib3/urllib3/pull/3728#pullrequestreview-3816302703
raise NewConnectionError(
self, f"Failed to establish a new connection: {e}"
) from e
Expand Down
4 changes: 2 additions & 2 deletions lib/urllib3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ def __reduce__(self) -> _TYPE_REDUCE_RESULT:
def pool(self) -> HTTPConnection:
warnings.warn(
"The 'pool' property is deprecated and will be removed "
"in urllib3 v2.1.0. Use 'conn' instead.",
DeprecationWarning,
"in urllib3 v3.0. Use 'conn' instead.",
FutureWarning,
stacklevel=2,
)

Expand Down
28 changes: 14 additions & 14 deletions lib/urllib3/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str:
An RFC-2231-formatted unicode string.

.. deprecated:: 2.0.0
Will be removed in urllib3 v2.1.0. This is not valid for
Will be removed in urllib3 v3.0. This is not valid for
``multipart/form-data`` header parameters.
"""
import warnings

warnings.warn(
"'format_header_param_rfc2231' is deprecated and will be "
"removed in urllib3 v2.1.0. This is not valid for "
"'format_header_param_rfc2231' is insecure, deprecated and will be "
"removed in urllib3 v3.0. This is not valid for "
"multipart/form-data header parameters.",
DeprecationWarning,
FutureWarning,
stacklevel=2,
)

Expand Down Expand Up @@ -104,7 +104,7 @@ def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str:
.. versionchanged:: 2.0.0
Renamed from ``format_header_param_html5`` and
``format_header_param``. The old names will be removed in
urllib3 v2.1.0.
urllib3 v3.0.
"""
if isinstance(value, bytes):
value = value.decode("utf-8")
Expand All @@ -118,15 +118,15 @@ def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str:
"""
.. deprecated:: 2.0.0
Renamed to :func:`format_multipart_header_param`. Will be
removed in urllib3 v2.1.0.
removed in urllib3 v3.0.
"""
import warnings

warnings.warn(
"'format_header_param_html5' has been renamed to "
"'format_multipart_header_param'. The old name will be "
"removed in urllib3 v2.1.0.",
DeprecationWarning,
"removed in urllib3 v3.0.",
FutureWarning,
stacklevel=2,
)
return format_multipart_header_param(name, value)
Expand All @@ -136,15 +136,15 @@ def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str:
"""
.. deprecated:: 2.0.0
Renamed to :func:`format_multipart_header_param`. Will be
removed in urllib3 v2.1.0.
removed in urllib3 v3.0.
"""
import warnings

warnings.warn(
"'format_header_param' has been renamed to "
"'format_multipart_header_param'. The old name will be "
"removed in urllib3 v2.1.0.",
DeprecationWarning,
"removed in urllib3 v3.0.",
FutureWarning,
stacklevel=2,
)
return format_multipart_header_param(name, value)
Expand All @@ -165,7 +165,7 @@ class RequestField:

.. versionchanged:: 2.0.0
The ``header_formatter`` parameter is deprecated and will
be removed in urllib3 v2.1.0.
be removed in urllib3 v3.0.
"""

def __init__(
Expand All @@ -188,8 +188,8 @@ def __init__(

warnings.warn(
"The 'header_formatter' parameter is deprecated and "
"will be removed in urllib3 v2.1.0.",
DeprecationWarning,
"will be removed in urllib3 v3.0.",
FutureWarning,
stacklevel=2,
)
self.header_formatter = header_formatter
Expand Down
14 changes: 8 additions & 6 deletions lib/urllib3/poolmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ def connection_from_context(
if "strict" in request_context:
warnings.warn(
"The 'strict' parameter is no longer needed on Python 3+. "
"This will raise an error in urllib3 v2.1.0.",
DeprecationWarning,
"This will raise an error in urllib3 v3.0.",
FutureWarning,
)
request_context.pop("strict")

Expand Down Expand Up @@ -436,10 +436,10 @@ def urlopen( # type: ignore[override]
if u.scheme is None:
warnings.warn(
"URLs without a scheme (ie 'https://') are deprecated and will raise an error "
"in a future version of urllib3. To avoid this DeprecationWarning ensure all URLs "
"in urllib3 v3.0. To avoid this FutureWarning ensure all URLs "
"start with 'https://' or 'http://'. Read more in this issue: "
"https://github.com/urllib3/urllib3/issues/2920",
category=DeprecationWarning,
category=FutureWarning,
stacklevel=2,
)

Expand Down Expand Up @@ -544,15 +544,17 @@ class ProxyManager(PoolManager):

proxy = urllib3.ProxyManager("https://localhost:3128/")

resp1 = proxy.request("GET", "https://google.com/")
resp2 = proxy.request("GET", "https://httpbin.org/")
resp1 = proxy.request("GET", "http://google.com/")
resp2 = proxy.request("GET", "http://httpbin.org/")

# One pool was shared by both plain HTTP requests.
print(len(proxy.pools))
# 1

resp3 = proxy.request("GET", "https://httpbin.org/")
resp4 = proxy.request("GET", "https://twitter.com/")

# A separate pool was added for each HTTPS target.
print(len(proxy.pools))
# 3

Expand Down
Loading