From 6751edd724e6f0a3acf673a592bbe9226ba32afd Mon Sep 17 00:00:00 2001 From: "Stanislav Lyu." Date: Sun, 29 Dec 2024 20:32:41 +0300 Subject: [PATCH] Reworked to support PathLike protocol instead of pathlib.Path --- src/niquests/_typing.py | 4 ++-- src/niquests/adapters.py | 52 +++++++++++++++++++++++++--------------- src/niquests/api.py | 42 +++++++++++++++++++------------- src/niquests/sessions.py | 39 +++++++++++++++++------------- tests/test_requests.py | 12 ++++++++++ 5 files changed, 95 insertions(+), 54 deletions(-) diff --git a/src/niquests/_typing.py b/src/niquests/_typing.py index df40d2fa36..5207998761 100644 --- a/src/niquests/_typing.py +++ b/src/niquests/_typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from pathlib import Path +from os import PathLike import typing from http.cookiejar import CookieJar @@ -72,7 +72,7 @@ CookieJar, ] #: Either Yes/No, or CA bundle pem location. Or directly the raw bundle content itself. -TLSVerifyType: typing.TypeAlias = typing.Union[bool, str, bytes, Path] +TLSVerifyType: typing.TypeAlias = typing.Union[bool, str, bytes, PathLike] #: Accept a pem certificate (concat cert, key) or an explicit tuple of cert, key pair with an optional password. TLSClientCertType: typing.TypeAlias = typing.Union[ str, typing.Tuple[str, str], typing.Tuple[str, str, str] diff --git a/src/niquests/adapters.py b/src/niquests/adapters.py index c061bbe580..51e57e5b5b 100644 --- a/src/niquests/adapters.py +++ b/src/niquests/adapters.py @@ -9,7 +9,6 @@ from __future__ import annotations import os.path -from pathlib import Path import socket # noqa: F401 import sys import time @@ -211,8 +210,9 @@ def send( data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. It is also possible to put the certificates (directly) in a string or bytes. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + It is also possible to put the certificates (directly) in a string or bytes. :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. :param on_post_connection: (optional) A callable that should be invoked just after the pool mgr picked up a live @@ -268,8 +268,9 @@ async def send( data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. It is also possible to put the certificates (directly) in a string or bytes. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + It is also possible to put the certificates (directly) in a string or bytes. :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. :param on_post_connection: (optional) A callable that should be invoked just after the pool mgr picked up a live @@ -555,8 +556,9 @@ def cert_verify( :param conn: The urllib3 connection object associated with the cert. :param url: The requested URL. :param verify: Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. It is also possible to put the certificates (directly) in a string or bytes. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + It is also possible to put the certificates (directly) in a string or bytes. :param cert: The SSL certificate to verify. """ if not parse_scheme(url) == "https": @@ -589,10 +591,16 @@ def cert_verify( cert_data = verify.decode("utf-8") else: # Allow self-specified cert location. + # Plain str path if isinstance(verify, str): cert_loc = verify - elif isinstance(verify, Path): - cert_loc = verify.absolute().as_posix() + # or path-like obj, that should have __fspath__ + elif hasattr(verify, "__fspath__"): + verify_pathlike = typing.cast(os.PathLike, verify) + cert_loc = verify_pathlike.__fspath__() + + if isinstance(cert_loc, bytes): + cert_loc = cert_loc.decode() if isinstance(cert_loc, str) and not os.path.exists(cert_loc): raise OSError( @@ -852,9 +860,10 @@ def send( data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :param verify: (optional) Either a boolean, in which case it controls whether - we verify the server's TLS certificate, or a string/pathlib.Path, in which case it - must be a path to a CA bundle to use. It is also possible to put the certificates - (directly) in a string or bytes. + we verify the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. + It is also possible to put the certificates (directly) in a string or bytes. :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. :param on_post_connection: (optional) A callable that contain a single positional argument for newly acquired @@ -1648,8 +1657,9 @@ def cert_verify( :param conn: The urllib3 connection object associated with the cert. :param url: The requested URL. :param verify: Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. It is also possible to put the certificates (directly) in a string or bytes. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + It is also possible to put the certificates (directly) in a string or bytes. :param cert: The SSL certificate to verify. """ if not parse_scheme(url) == "https": @@ -1685,8 +1695,12 @@ def cert_verify( if isinstance(verify, str): cert_loc = verify - elif isinstance(verify, Path): - cert_loc = verify.absolute().as_posix() + elif hasattr(verify, "__fspath__"): + verify_pathlike = typing.cast(os.PathLike, verify) + cerl_loc = verify_pathlike.__fspath__() + + if isinstance(cerl_loc, bytes): + cert_loc = cerl_loc.decode() if isinstance(cert_loc, str) and not os.path.exists(cert_loc): raise OSError( @@ -1941,9 +1955,9 @@ async def send( data before giving up, as a float, or a :ref:`(connect timeout, read timeout) ` tuple. :param verify: (optional) Either a boolean, in which case it controls whether - we verify the server's TLS certificate, or a string/pathlib.Path, in which case it - must be a path to a CA bundle to use. It is also possible to put the certificates - (directly) in a string or bytes. + we verify the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + It is also possible to put the certificates (directly) in a string or bytes. :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. :param on_post_connection: (optional) A callable that contain a single positional argument for newly acquired diff --git a/src/niquests/api.py b/src/niquests/api.py index 90c5c9c213..d48e49d419 100644 --- a/src/niquests/api.py +++ b/src/niquests/api.py @@ -80,8 +80,9 @@ def request( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -157,8 +158,9 @@ def get( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -218,8 +220,9 @@ def options( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -280,8 +283,9 @@ def head( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -352,8 +356,9 @@ def post( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -426,8 +431,9 @@ def put( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -500,8 +506,9 @@ def patch( :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). @@ -563,9 +570,10 @@ def delete( timeout) ` tuple. :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. - :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + ::param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. It is also possible to put the certificates (directly) in a string or bytes. :param stream: (optional) if ``False``, the response content will be immediately downloaded. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair, or ('cert', 'key', 'key_password'). diff --git a/src/niquests/sessions.py b/src/niquests/sessions.py index a4b74f8a66..534e46c4cc 100644 --- a/src/niquests/sessions.py +++ b/src/niquests/sessions.py @@ -533,8 +533,8 @@ def request( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -626,8 +626,9 @@ def get( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -694,8 +695,9 @@ def options( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -762,8 +764,9 @@ def head( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -838,8 +841,9 @@ def post( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -916,8 +920,9 @@ def put( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -994,8 +999,9 @@ def patch( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to @@ -1064,8 +1070,9 @@ def delete( :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string/pathlib.Path, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to + the server's TLS certificate, or a path passed as a string or os.Pathlike object, + in which case it must be a path to a CA bundle to use. + Defaults to ``True``. When set to ``False``, requests will accept any TLS certificate presented by the server, and will ignore hostname mismatches and/or expired certificates, which will make your application vulnerable to diff --git a/tests/test_requests.py b/tests/test_requests.py index b4c1471b90..1baa34b64a 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -17,6 +17,7 @@ from unittest import mock from urllib.parse import urlparse from urllib.request import getproxies +from pathlib import Path import pytest from niquests._compat import HAS_LEGACY_URLLIB3 @@ -960,6 +961,17 @@ def test_invalid_ssl_certificate_files(self, httpbin_secure): f"Could not find the TLS key file, invalid path: {INVALID_PATH}" ) + def test_ssl_certificate_as_pathlike(self, san_server): + _, port, ca_bundle = san_server + + s = niquests.Session() + + print(ca_bundle) + + r = s.get(f"https://localhost:{port}/", verify=Path(ca_bundle)) + + assert r.status_code == 204 + @pytest.mark.parametrize( "env, expected", (