Skip to content

Commit

Permalink
Merge branch 'main' into fix-http-version-log
Browse files Browse the repository at this point in the history
  • Loading branch information
pquentin committed Jan 30, 2024
2 parents a1f18b0 + 2aec09f commit 48fafbe
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 13 deletions.
1 change: 1 addition & 0 deletions changelog/3325.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed TLS 1.3 Post Handshake Auth when the server certificate validation was disabled.
87 changes: 87 additions & 0 deletions docs/reference/contrib/emscripten.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Pyodide, Emscripten, and PyScript
=================================

From the Pyodide documentation, `Pyodide <https://pyodide.org>`_ is a Python distribution for the browser and Node.js based on WebAssembly and `Emscripten <https://emscripten.org/>`_.
This technology also underpins the `PyScript framework <https://pyscript.net/>`_ and `Jupyterlite <https://jupyterlite.readthedocs.io/>`_, so should work in those environments too.

Starting in version 2.2.0 urllib3 supports being used in a Pyodide runtime utilizing
the `JavaScript fetch API <https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API>`_
or falling back on `XMLHttpRequest <https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest>`_
if the fetch API isn't available (such as when cross-origin isolation
isn't active). This means you can use Python libraries to make HTTP requests from your browser!

Because urllib3's Emscripten support is API-compatible, this means that
libraries that depend on urllib3 may now be usable from Emscripten and Pyodide environments, too.

.. warning::

**Support for Emscripten and Pyodide is experimental**. Report all bugs to the `urllib3 issue tracker <https://github.com/urllib3/urllib3/issues>`_.
Currently only supports browsers, does not yet support running in Node.js.

It's recommended to `run Pyodide in a Web Worker <https://pyodide.org/en/stable/usage/webworker.html#using-from-webworker>`_
in order to take full advantage of features like the fetch API which enables streaming of HTTP response bodies.

Getting started
---------------

Using urllib3 with Pyodide means you need to `get started with Pyodide first <https://pyodide.org/en/stable/usage/quickstart.html>`_.
The Pyodide project provides a `useful online REPL <https://pyodide.org/en/stable/console.html>`_ to try in your browser without
any setup or installation to test out the code examples below.

urllib3's Emscripten support is automatically enabled if ``sys.platform`` is ``"emscripten"``, so no setup is required beyond installation and importing the module.

You can install urllib3 in a Pyodide environment using micropip.
Try using the following code in a Pyodide console or ``<script>`` tag:

.. code-block:: python
import micropip
await micropip.install("urllib3")
import urllib3
resp = urllib3.request("GET", "https://example.com")
print(resp.status) # 200
print(resp.headers) # HTTPHeaderDict(...)
print(resp.data) # ...
Because `Requests <https://requests.readthedocs.io/en/latest/>`_ is built on urllib3, Requests also works out of the box:

.. code-block:: python
import micropip
await micropip.install("requests")
import requests
resp = requests.request("GET", "https://example.com")
print(resp.status_code) # 200
print(resp.headers)
Features
--------

Because we use JavaScript APIs under the hood, it's not possible to use all of urllib3 features.
Features which are usable with Emscripten support are:

* Requests over HTTP and HTTPS
* Timeouts
* Retries
* Streaming (with Web Workers and Cross-Origin Isolation)
* Redirects
* Decompressing response bodies

Features which don't work with Emscripten:

* Proxies, both forwarding and tunneling
* Customizing TLS and certificates (uses browsers' configuration)
* Configuring low-level socket options or source address

Streaming with Web Workers
--------------------------

To access the fetch API and do HTTP response streaming with urllib3
you must be running the code within a Web Worker and set specific HTTP headers
for the serving website to enable `Cross-Origin Isolation <https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated>`_.

You can verify whether a given environment is cross-origin isolated by evaluating the global ``crossOriginIsolated`` JavaScript property.
1 change: 1 addition & 0 deletions docs/reference/contrib/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ prime time or that require optional third-party dependencies.

.. toctree::

emscripten
pyopenssl
socks
2 changes: 1 addition & 1 deletion src/urllib3/connectionpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ def _make_request(
url,
response.version,
response.status,
response.length_remaining, # type: ignore[attr-defined]
response.length_remaining,
)

return response
Expand Down
3 changes: 1 addition & 2 deletions src/urllib3/http2.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ class HTTP2Connection(HTTPSConnection):
def __init__(
self, host: str, port: int | None = None, **kwargs: typing.Any
) -> None:
config = h2.config.H2Configuration(client_side=True)
self._h2_conn = _LockedObject(h2.connection.H2Connection(config=config))
self._h2_conn = self._new_h2_conn()
self._h2_stream: int | None = None
self._h2_headers: list[tuple[bytes, bytes]] = []

Expand Down
1 change: 1 addition & 0 deletions src/urllib3/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ def __init__(
self.chunked = True

self._decoder: ContentDecoder | None = None
self.length_remaining: int | None

def get_redirect_location(self) -> str | None | Literal[False]:
"""
Expand Down
11 changes: 3 additions & 8 deletions src/urllib3/util/ssl_.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,9 @@ def create_urllib3_context(

# Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is
# necessary for conditional client cert authentication with TLS 1.3.
# The attribute is None for OpenSSL <= 1.1.0 or does not exist in older
# versions of Python. We only enable if certificate verification is enabled to work
# around Python issue #37428
# See: https://bugs.python.org/issue37428
if (
cert_reqs == ssl.CERT_REQUIRED
and getattr(context, "post_handshake_auth", None) is not None
):
# The attribute is None for OpenSSL <= 1.1.0 or does not exist when using
# an SSLContext created by pyOpenSSL.
if getattr(context, "post_handshake_auth", None) is not None:
context.post_handshake_auth = True

# The order of the below lines setting verify_mode and check_hostname
Expand Down
19 changes: 17 additions & 2 deletions test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,36 @@ def test_wrap_socket_no_ssltransport(self) -> None:
ssl_.ssl_wrap_socket(sock, tls_in_tls=True)

@pytest.mark.parametrize(
["pha", "expected_pha"], [(None, None), (False, True), (True, True)]
["pha", "expected_pha", "cert_reqs"],
[
(None, None, None),
(None, None, ssl.CERT_NONE),
(None, None, ssl.CERT_OPTIONAL),
(None, None, ssl.CERT_REQUIRED),
(False, True, None),
(False, True, ssl.CERT_NONE),
(False, True, ssl.CERT_OPTIONAL),
(False, True, ssl.CERT_REQUIRED),
(True, True, None),
(True, True, ssl.CERT_NONE),
(True, True, ssl.CERT_OPTIONAL),
(True, True, ssl.CERT_REQUIRED),
],
)
def test_create_urllib3_context_pha(
self,
monkeypatch: pytest.MonkeyPatch,
pha: bool | None,
expected_pha: bool | None,
cert_reqs: int | None,
) -> None:
context = mock.create_autospec(ssl_.SSLContext)
context.set_ciphers = mock.Mock()
context.options = 0
context.post_handshake_auth = pha
monkeypatch.setattr(ssl_, "SSLContext", lambda *_, **__: context)

assert ssl_.create_urllib3_context() is context
assert ssl_.create_urllib3_context(cert_reqs=cert_reqs) is context

assert context.post_handshake_auth == expected_pha

Expand Down

0 comments on commit 48fafbe

Please sign in to comment.