diff --git a/src/snowflake/connector/session_manager.py b/src/snowflake/connector/session_manager.py index 35b98c6f19..e1c7ceba7f 100644 --- a/src/snowflake/connector/session_manager.py +++ b/src/snowflake/connector/session_manager.py @@ -14,7 +14,7 @@ from .vendored import requests from .vendored.requests import Response, Session from .vendored.requests.adapters import BaseAdapter, HTTPAdapter -from .vendored.requests.exceptions import InvalidProxyURL +from .vendored.requests.exceptions import InvalidProxyURL, InvalidURL from .vendored.requests.utils import prepend_scheme_if_needed, select_proxy from .vendored.urllib3 import PoolManager, Retry from .vendored.urllib3.poolmanager import ProxyManager @@ -23,7 +23,6 @@ if TYPE_CHECKING: from .vendored.urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool - logger = logging.getLogger(__name__) REQUESTS_RETRY = 1 # requests library builtin retry @@ -60,19 +59,25 @@ def wrapper(self, *args, **kwargs): class ProxySupportAdapter(HTTPAdapter): """This Adapter creates proper headers for Proxy CONNECT messages.""" - def get_connection( - self, url: str, proxies: dict | None = None + def get_connection_with_tls_context( + self, request, verify, proxies=None, cert=None ) -> HTTPConnectionPool | HTTPSConnectionPool: - proxy = select_proxy(url, proxies) - parsed_url = urlparse(url) - + proxy = select_proxy(request.url, proxies) + try: + host_params, pool_kwargs = self.build_connection_pool_key_attributes( + request, + verify, + cert, + ) + except ValueError as e: + raise InvalidURL(e, request=request) if proxy: proxy = prepend_scheme_if_needed(proxy, "http") proxy_url = parse_url(proxy) if not proxy_url.host: raise InvalidProxyURL( - "Please check proxy URL. It is malformed" - " and could be missing the host." + "Please check proxy URL. It is malformed " + "and could be missing the host." ) proxy_manager = self.proxy_manager_for(proxy) @@ -85,17 +90,22 @@ def get_connection( # Note: netloc also keeps user-info (user:pass@host) if present in URL. The driver never sends # URLs with embedded credentials, so we leave them unhandled — for full support # we’d need to manually concatenate hostname with optional port and IPv6 brackets. + parsed_url = urlparse(request.url) proxy_manager.proxy_headers["Host"] = parsed_url.netloc else: logger.debug( f"Unable to set 'Host' to proxy manager of type {type(proxy_manager)} as" f" it does not have attribute 'proxy_headers'." ) - conn = proxy_manager.connection_from_url(url) + + conn = proxy_manager.connection_from_host( + **host_params, pool_kwargs=pool_kwargs + ) else: # Only scheme should be lower case - url = parsed_url.geturl() - conn = self.poolmanager.connection_from_url(url) + conn = self.poolmanager.connection_from_host( + **host_params, pool_kwargs=pool_kwargs + ) return conn diff --git a/src/snowflake/connector/vendored/requests/__init__.py b/src/snowflake/connector/vendored/requests/__init__.py index 051cda1340..ad96080969 100644 --- a/src/snowflake/connector/vendored/requests/__init__.py +++ b/src/snowflake/connector/vendored/requests/__init__.py @@ -40,7 +40,7 @@ import warnings -import urllib3 +from .. import urllib3 from .exceptions import RequestsDependencyWarning @@ -128,7 +128,7 @@ def _check_cryptography(cryptography_version): ssl = None if not getattr(ssl, "HAS_SNI", False): - from urllib3.contrib import pyopenssl + from ..urllib3.contrib import pyopenssl pyopenssl.inject_into_urllib3() @@ -140,7 +140,7 @@ def _check_cryptography(cryptography_version): pass # urllib3's DependencyWarnings should be silenced. -from urllib3.exceptions import DependencyWarning +from ..urllib3.exceptions import DependencyWarning warnings.simplefilter("ignore", DependencyWarning) diff --git a/src/snowflake/connector/vendored/requests/adapters.py b/src/snowflake/connector/vendored/requests/adapters.py index 670c92767c..91871b7640 100644 --- a/src/snowflake/connector/vendored/requests/adapters.py +++ b/src/snowflake/connector/vendored/requests/adapters.py @@ -11,22 +11,22 @@ import typing import warnings -from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError -from urllib3.exceptions import HTTPError as _HTTPError -from urllib3.exceptions import InvalidHeader as _InvalidHeader -from urllib3.exceptions import ( +from ..urllib3.exceptions import ClosedPoolError, ConnectTimeoutError +from ..urllib3.exceptions import HTTPError as _HTTPError +from ..urllib3.exceptions import InvalidHeader as _InvalidHeader +from ..urllib3.exceptions import ( LocationValueError, MaxRetryError, NewConnectionError, ProtocolError, ) -from urllib3.exceptions import ProxyError as _ProxyError -from urllib3.exceptions import ReadTimeoutError, ResponseError -from urllib3.exceptions import SSLError as _SSLError -from urllib3.poolmanager import PoolManager, proxy_from_url -from urllib3.util import Timeout as TimeoutSauce -from urllib3.util import parse_url -from urllib3.util.retry import Retry +from ..urllib3.exceptions import ProxyError as _ProxyError +from ..urllib3.exceptions import ReadTimeoutError, ResponseError +from ..urllib3.exceptions import SSLError as _SSLError +from ..urllib3.poolmanager import PoolManager, proxy_from_url +from ..urllib3.util import Timeout as TimeoutSauce +from ..urllib3.util import parse_url +from ..urllib3.util.retry import Retry from .auth import _basic_auth_str from .compat import basestring, urlparse @@ -56,7 +56,7 @@ ) try: - from urllib3.contrib.socks import SOCKSProxyManager + from ..urllib3.contrib.socks import SOCKSProxyManager except ImportError: def SOCKSProxyManager(*args, **kwargs): diff --git a/src/snowflake/connector/vendored/requests/compat.py b/src/snowflake/connector/vendored/requests/compat.py index 7f9d754350..da7726957a 100644 --- a/src/snowflake/connector/vendored/requests/compat.py +++ b/src/snowflake/connector/vendored/requests/compat.py @@ -13,7 +13,7 @@ # ------- # urllib3 # ------- -from urllib3 import __version__ as urllib3_version +from ..urllib3 import __version__ as urllib3_version # Detect which major version of urllib3 is being used. try: diff --git a/src/snowflake/connector/vendored/requests/exceptions.py b/src/snowflake/connector/vendored/requests/exceptions.py index 83986b4898..aa923ce230 100644 --- a/src/snowflake/connector/vendored/requests/exceptions.py +++ b/src/snowflake/connector/vendored/requests/exceptions.py @@ -4,7 +4,7 @@ This module contains the set of Requests' exceptions. """ -from urllib3.exceptions import HTTPError as BaseHTTPError +from ..urllib3.exceptions import HTTPError as BaseHTTPError from .compat import JSONDecodeError as CompatJSONDecodeError diff --git a/src/snowflake/connector/vendored/requests/help.py b/src/snowflake/connector/vendored/requests/help.py index 8fbcd6560a..fc3d1daef5 100644 --- a/src/snowflake/connector/vendored/requests/help.py +++ b/src/snowflake/connector/vendored/requests/help.py @@ -6,7 +6,7 @@ import sys import idna -import urllib3 +from .. import urllib3 from . import __version__ as requests_version @@ -21,7 +21,7 @@ chardet = None try: - from urllib3.contrib import pyopenssl + from ..urllib3.contrib import pyopenssl except ImportError: pyopenssl = None OpenSSL = None diff --git a/src/snowflake/connector/vendored/requests/models.py b/src/snowflake/connector/vendored/requests/models.py index c4b25fa079..11838872a3 100644 --- a/src/snowflake/connector/vendored/requests/models.py +++ b/src/snowflake/connector/vendored/requests/models.py @@ -13,16 +13,16 @@ import encodings.idna # noqa: F401 from io import UnsupportedOperation -from urllib3.exceptions import ( +from ..urllib3.exceptions import ( DecodeError, LocationParseError, ProtocolError, ReadTimeoutError, SSLError, ) -from urllib3.fields import RequestField -from urllib3.filepost import encode_multipart_formdata -from urllib3.util import parse_url +from ..urllib3.fields import RequestField +from ..urllib3.filepost import encode_multipart_formdata +from ..urllib3.util import parse_url from ._internal_utils import to_native_string, unicode_is_ascii from .auth import HTTPBasicAuth diff --git a/src/snowflake/connector/vendored/requests/utils.py b/src/snowflake/connector/vendored/requests/utils.py index 8ab55852cc..97b2b90997 100644 --- a/src/snowflake/connector/vendored/requests/utils.py +++ b/src/snowflake/connector/vendored/requests/utils.py @@ -19,7 +19,7 @@ import zipfile from collections import OrderedDict -from urllib3.util import make_headers, parse_url +from ..urllib3.util import make_headers, parse_url from . import certs from .__version__ import __version__ diff --git a/src/snowflake/connector/vendored/urllib3/connection.py b/src/snowflake/connector/vendored/urllib3/connection.py index 8082387d94..94a512cd15 100644 --- a/src/snowflake/connector/vendored/urllib3/connection.py +++ b/src/snowflake/connector/vendored/urllib3/connection.py @@ -338,13 +338,22 @@ def connect(self) -> None: if self._has_connected_to_proxy: self.proxy_is_verified = False + # See issue for more context: https://github.com/urllib3/urllib3/issues/1878 + # the maintainers know that this issue can be resolve using the change below but + # they have not merged this change because they need to root-cause it. See + # comment: https://github.com/urllib3/urllib3/issues/1878#issuecomment-641548977 + # adding the fix in our vendored code so our users get unblocked + def _is_closed_patch_for_invalid_socket_descriptor(self): + if getattr(self.sock, "fileno", lambda _: None)() == -1: + return True + @property def is_closed(self) -> bool: - return self.sock is None + return self.sock is None or self._is_closed_patch_for_invalid_socket_descriptor() @property def is_connected(self) -> bool: - if self.sock is None: + if self.sock is None or self._is_closed_patch_for_invalid_socket_descriptor(): return False return not wait_for_read(self.sock, timeout=0.0) diff --git a/src/snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py b/src/snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py index 8a3c5bebdc..3553d57eed 100644 --- a/src/snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py +++ b/src/snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py @@ -1,6 +1,6 @@ from __future__ import annotations -import urllib3.connection +import snowflake.connector.vendored.urllib3.connection from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection @@ -12,5 +12,5 @@ def inject_into_urllib3() -> None: # if it isn't ignored HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection - urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] - urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] + snowflake.connector.vendored.urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] + snowflake.connector.vendored.urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] diff --git a/test/unit/test_proxies.py b/test/unit/test_proxies.py index f7ec07d562..1038123935 100644 --- a/test/unit/test_proxies.py +++ b/test/unit/test_proxies.py @@ -39,6 +39,9 @@ def __init__(self): def connection_from_url(self, url): pass + def connection_from_host(self, host, *args, **kwargs): + pass + def mock_proxy_manager_for_url_no_header(*args, **kwargs): return MockSOCKSProxyManager()