mirror of https://github.com/scrapy/scrapy.git
Revert sharing of the SSL context in _ScrapyClientContextFactory. (#7450)
This commit is contained in:
parent
da6dfae750
commit
b7cd42da39
|
|
@ -78,13 +78,6 @@ class _ScrapyClientContextFactory(BrowserLikePolicyForHTTPS):
|
||||||
self.tls_ciphers = AcceptableCiphers.fromOpenSSLCipherString(tls_ciphers)
|
self.tls_ciphers = AcceptableCiphers.fromOpenSSLCipherString(tls_ciphers)
|
||||||
else:
|
else:
|
||||||
self.tls_ciphers = DEFAULT_CIPHERS
|
self.tls_ciphers = DEFAULT_CIPHERS
|
||||||
with _filter_method_warning():
|
|
||||||
self._certificate_options = CertificateOptions(
|
|
||||||
method=self._ssl_method,
|
|
||||||
fixBrokenPeers=True,
|
|
||||||
acceptableCiphers=self.tls_ciphers,
|
|
||||||
)
|
|
||||||
self._ctx = self._get_context()
|
|
||||||
self._verify_certificates = verify_certificates
|
self._verify_certificates = verify_certificates
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -109,23 +102,38 @@ class _ScrapyClientContextFactory(BrowserLikePolicyForHTTPS):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# should be removed together with ScrapyClientContextFactory
|
||||||
def getCertificateOptions(self) -> CertificateOptions: # pragma: no cover
|
def getCertificateOptions(self) -> CertificateOptions: # pragma: no cover
|
||||||
return self._certificate_options
|
return self._get_cert_options()
|
||||||
|
|
||||||
|
def _get_cert_options(self) -> CertificateOptions:
|
||||||
|
with _filter_method_warning():
|
||||||
|
return CertificateOptions(
|
||||||
|
method=self._ssl_method,
|
||||||
|
fixBrokenPeers=True,
|
||||||
|
acceptableCiphers=self.tls_ciphers,
|
||||||
|
)
|
||||||
|
|
||||||
# kept for old-style HTTP/1.0 downloader context twisted calls,
|
# kept for old-style HTTP/1.0 downloader context twisted calls,
|
||||||
# e.g. connectSSL()
|
# e.g. connectSSL()
|
||||||
|
# should be removed together with ScrapyClientContextFactory
|
||||||
def getContext(self, hostname: Any = None, port: Any = None) -> SSL.Context:
|
def getContext(self, hostname: Any = None, port: Any = None) -> SSL.Context:
|
||||||
return self._ctx
|
return self._get_context()
|
||||||
|
|
||||||
def _get_context(self) -> SSL.Context:
|
def _get_context(self) -> SSL.Context:
|
||||||
ctx = self._certificate_options.getContext()
|
cert_options = self._get_cert_options()
|
||||||
|
ctx = cert_options.getContext()
|
||||||
ctx.set_options(0x4) # OP_LEGACY_SERVER_CONNECT
|
ctx.set_options(0x4) # OP_LEGACY_SERVER_CONNECT
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def creatorForNetloc(self, hostname: bytes, port: int) -> ClientTLSOptions:
|
def creatorForNetloc(self, hostname: bytes, port: int) -> ClientTLSOptions:
|
||||||
if not self._verify_certificates:
|
if not self._verify_certificates:
|
||||||
return _ScrapyClientTLSOptions(hostname.decode("ascii"), self._ctx) # type: ignore[no-untyped-call]
|
# _ScrapyClientTLSOptions is needed to skip verification errors
|
||||||
# Note that this doesn't use self._ctx
|
return _ScrapyClientTLSOptions(
|
||||||
|
hostname.decode("ascii"), self._get_context()
|
||||||
|
) # type: ignore[no-untyped-call]
|
||||||
|
# Otherwise use the normal Twisted function.
|
||||||
|
# Note that this doesn't use self._get_context().
|
||||||
with _filter_method_warning():
|
with _filter_method_warning():
|
||||||
return optionsForClientTLS(
|
return optionsForClientTLS(
|
||||||
hostname=hostname.decode("ascii"),
|
hostname=hostname.decode("ascii"),
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ from twisted.python.versions import Version as TxVersion
|
||||||
TWISTED_FAILURE_HAS_STACK = TWISTED_VERSION < TxVersion("twisted", 24, 10, 0)
|
TWISTED_FAILURE_HAS_STACK = TWISTED_VERSION < TxVersion("twisted", 24, 10, 0)
|
||||||
|
|
||||||
PYOPENSSL_VERSION = Version(PYOPENSSL_VERSION_STRING)
|
PYOPENSSL_VERSION = Version(PYOPENSSL_VERSION_STRING)
|
||||||
# SSL.Context.use_certificate wants an X509 object, SSL.Context.use_privatekey wants a PKey object
|
# SSL.Context.use_certificate() wants an X509 object, SSL.Context.use_privatekey() wants a PKey object
|
||||||
PYOPENSSL_WANTS_X509_PKEY = PYOPENSSL_VERSION < Version("24.3.0")
|
PYOPENSSL_WANTS_X509_PKEY = PYOPENSSL_VERSION < Version("24.3.0")
|
||||||
|
# SSL.Context.set_cipher_list() creates a temporary connection, making the context immutable
|
||||||
|
PYOPENSSL_SET_CIPHER_LIST_TMP_CONN = PYOPENSSL_VERSION < Version("25.2.0")
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from scrapy.core.downloader.contextfactory import (
|
||||||
)
|
)
|
||||||
from scrapy.core.downloader.handlers.http11 import _RequestBodyProducer
|
from scrapy.core.downloader.handlers.http11 import _RequestBodyProducer
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
|
from scrapy.utils._deps_compat import PYOPENSSL_SET_CIPHER_LIST_TMP_CONN
|
||||||
from scrapy.utils.defer import maybe_deferred_to_future
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
from scrapy.utils.misc import build_from_crawler
|
from scrapy.utils.misc import build_from_crawler
|
||||||
from scrapy.utils.python import to_bytes
|
from scrapy.utils.python import to_bytes
|
||||||
|
|
@ -98,7 +99,7 @@ class TestContextFactoryBase:
|
||||||
|
|
||||||
class TestContextFactory(TestContextFactoryBase):
|
class TestContextFactory(TestContextFactoryBase):
|
||||||
@coroutine_test
|
@coroutine_test
|
||||||
async def testPayload(self, server_url: str) -> None:
|
async def test_payload(self, server_url: str) -> None:
|
||||||
s = "0123456789" * 10
|
s = "0123456789" * 10
|
||||||
crawler = get_crawler()
|
crawler = get_crawler()
|
||||||
client_context_factory = _load_context_factory_from_settings(crawler)
|
client_context_factory = _load_context_factory_from_settings(crawler)
|
||||||
|
|
@ -107,6 +108,41 @@ class TestContextFactory(TestContextFactoryBase):
|
||||||
)
|
)
|
||||||
assert body == to_bytes(s)
|
assert body == to_bytes(s)
|
||||||
|
|
||||||
|
def test_no_context_sharing(self) -> None:
|
||||||
|
"""Every call to creatorForNetloc() should give a fresh context."""
|
||||||
|
crawler = get_crawler()
|
||||||
|
client_context_factory: _ScrapyClientContextFactory = (
|
||||||
|
_load_context_factory_from_settings(crawler)
|
||||||
|
)
|
||||||
|
creator1 = client_context_factory.creatorForNetloc(b"website1.tld", 443)
|
||||||
|
assert creator1._hostnameBytes == b"website1.tld"
|
||||||
|
creator2 = client_context_factory.creatorForNetloc(b"website2.tld", 443)
|
||||||
|
assert creator2._hostnameBytes == b"website2.tld"
|
||||||
|
assert creator1._ctx is not creator2._ctx
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
PYOPENSSL_SET_CIPHER_LIST_TMP_CONN,
|
||||||
|
reason="Fails or doesn't make sense on this pyOpenSSL version",
|
||||||
|
)
|
||||||
|
def test_no_immutable_ctx_warning(self) -> None:
|
||||||
|
"""There should be no pyOpenSSL context modification warning.
|
||||||
|
|
||||||
|
pyOpenSSL < 25.1.0 doesn't produce this warning, and on 25.1.0 it's
|
||||||
|
always produced due to
|
||||||
|
https://github.com/scrapy/scrapy/issues/6859#issuecomment-4294917851.
|
||||||
|
"""
|
||||||
|
crawler = get_crawler()
|
||||||
|
client_context_factory: _ScrapyClientContextFactory = (
|
||||||
|
_load_context_factory_from_settings(crawler)
|
||||||
|
)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings(
|
||||||
|
"error",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
message="Attempting to mutate a Context after a Connection was created",
|
||||||
|
)
|
||||||
|
client_context_factory.creatorForNetloc(b"website.tld", 443)
|
||||||
|
|
||||||
|
|
||||||
class TestContextFactoryTLSMethod(TestContextFactoryBase):
|
class TestContextFactoryTLSMethod(TestContextFactoryBase):
|
||||||
async def _assert_factory_works(
|
async def _assert_factory_works(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue