1212namespace Symfony \Component \HttpClient ;
1313
1414use Psr \Log \LoggerAwareInterface ;
15- use Psr \Log \LoggerAwareTrait ;
15+ use Psr \Log \LoggerInterface ;
1616use Symfony \Component \HttpClient \Exception \InvalidArgumentException ;
1717use Symfony \Component \HttpClient \Exception \TransportException ;
1818use Symfony \Component \HttpClient \Internal \CurlClientState ;
3535final class CurlHttpClientimplements HttpClientInterface, LoggerAwareInterface, ResetInterface
3636{
3737use HttpClientTrait;
38- use LoggerAwareTrait;
3938
4039private $ defaultOptions =self ::OPTIONS_DEFAULTS + [
4140'auth_ntlm ' =>null ,// array|string - an array containing the username as first value, and optionally the
4241// password as the second one; or string like username:password - enabling NTLM auth
4342 ];
4443
44+ /**
45+ * @var LoggerInterface|null
46+ */
47+ private $ logger ;
48+
4549/**
4650 * An internal object to share state between the client and its responses.
4751 *
4852 * @var CurlClientState
4953 */
5054private $ multi ;
5155
52- private static $ curlVersion ;
53-
5456/**
5557 * @param array $defaultOptions Default request's options
5658 * @param int $maxHostConnections The maximum number of connections to a single host
@@ -70,33 +72,12 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections
7072 [,$ this ->defaultOptions ] =self ::prepareRequest (null ,null ,$ defaultOptions ,$ this ->defaultOptions );
7173 }
7274
73- $ this ->multi =new CurlClientState ();
74- self ::$ curlVersion =self ::$ curlVersion ??curl_version ();
75-
76- // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
77- if (\defined ('CURLPIPE_MULTIPLEX ' )) {
78- curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_PIPELINING , \CURLPIPE_MULTIPLEX );
79- }
80- if (\defined ('CURLMOPT_MAX_HOST_CONNECTIONS ' )) {
81- $ maxHostConnections =curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_MAX_HOST_CONNECTIONS ,0 <$ maxHostConnections ?$ maxHostConnections : \PHP_INT_MAX ) ?0 :$ maxHostConnections ;
82- }
83- if (\defined ('CURLMOPT_MAXCONNECTS ' ) &&0 <$ maxHostConnections ) {
84- curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_MAXCONNECTS ,$ maxHostConnections );
85- }
86-
87- // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
88- if (0 >=$ maxPendingPushes || \PHP_VERSION_ID <70217 || (\PHP_VERSION_ID >=70300 && \PHP_VERSION_ID <70304 )) {
89- return ;
90- }
91-
92- // HTTP/2 push crashes before curl 7.61
93- if (!\defined ('CURLMOPT_PUSHFUNCTION ' ) ||0x073D00 >self ::$ curlVersion ['version_number ' ] || !(\CURL_VERSION_HTTP2 &self ::$ curlVersion ['features ' ])) {
94- return ;
95- }
75+ $ this ->multi =new CurlClientState ($ maxHostConnections ,$ maxPendingPushes );
76+ }
9677
97- curl_multi_setopt ( $ this -> multi -> handle , \ CURLMOPT_PUSHFUNCTION , function ( $ parent , $ pushed , array $ requestHeaders ) use ( $ maxPendingPushes ) {
98- return $ this -> handlePush ( $ parent , $ pushed , $ requestHeaders , $ maxPendingPushes );
99- }) ;
78+ public function setLogger ( LoggerInterface $ logger ): void
79+ {
80+ $ this -> logger = $ this -> multi -> logger = $ logger ;
10081 }
10182
10283/**
@@ -142,7 +123,7 @@ public function request(string $method, string $url, array $options = []): Respo
142123$ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_1_0 ;
143124 }elseif (1.1 === (float )$ options ['http_version ' ]) {
144125$ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_1_1 ;
145- }elseif (\defined ('CURL_VERSION_HTTP2 ' ) && (\CURL_VERSION_HTTP2 &self ::$ curlVersion ['features ' ]) && ('https: ' ===$ scheme ||2.0 === (float )$ options ['http_version ' ])) {
126+ }elseif (\defined ('CURL_VERSION_HTTP2 ' ) && (\CURL_VERSION_HTTP2 &CurlClientState ::$ curlVersion ['features ' ]) && ('https: ' ===$ scheme ||2.0 === (float )$ options ['http_version ' ])) {
146127$ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_2_0 ;
147128 }
148129
@@ -185,7 +166,7 @@ public function request(string $method, string $url, array $options = []): Respo
185166$ this ->multi ->dnsCache ->evictions = [];
186167$ port =parse_url ($ authority , \PHP_URL_PORT ) ?: ('http: ' ===$ scheme ?80 :443 );
187168
188- if ($ resolve &&0x072A00 >self ::$ curlVersion ['version_number ' ]) {
169+ if ($ resolve &&0x072A00 >CurlClientState ::$ curlVersion ['version_number ' ]) {
189170// DNS cache removals require curl 7.42 or higher
190171// On lower versions, we have to create a new multi handle
191172curl_multi_close ($ this ->multi ->handle );
@@ -312,7 +293,7 @@ public function request(string $method, string $url, array $options = []): Respo
312293 }
313294 }
314295
315- return $ pushedResponse ??new CurlResponse ($ this ->multi ,$ ch ,$ options ,$ this ->logger ,$ method ,self ::createRedirectResolver ($ options ,$ host ),self ::$ curlVersion ['version_number ' ]);
296+ return $ pushedResponse ??new CurlResponse ($ this ->multi ,$ ch ,$ options ,$ this ->logger ,$ method ,self ::createRedirectResolver ($ options ,$ host ),CurlClientState ::$ curlVersion ['version_number ' ]);
316297 }
317298
318299/**
@@ -336,70 +317,9 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa
336317
337318public function reset ()
338319 {
339- $ this ->multi ->logger =$ this ->logger ;
340320$ this ->multi ->reset ();
341321 }
342322
343- /**
344- * @return array
345- */
346- public function __sleep ()
347- {
348- throw new \BadMethodCallException ('Cannot serialize ' .__CLASS__ );
349- }
350-
351- public function __wakeup ()
352- {
353- throw new \BadMethodCallException ('Cannot unserialize ' .__CLASS__ );
354- }
355-
356- public function __destruct ()
357- {
358- $ this ->multi ->logger =$ this ->logger ;
359- }
360-
361- private function handlePush ($ parent ,$ pushed ,array $ requestHeaders ,int $ maxPendingPushes ):int
362- {
363- $ headers = [];
364- $ origin =curl_getinfo ($ parent , \CURLINFO_EFFECTIVE_URL );
365-
366- foreach ($ requestHeadersas $ h ) {
367- if (false !==$ i =strpos ($ h ,': ' ,1 )) {
368- $ headers [substr ($ h ,0 ,$ i )][] =substr ($ h ,1 +$ i );
369- }
370- }
371-
372- if (!isset ($ headers [':method ' ]) || !isset ($ headers [':scheme ' ]) || !isset ($ headers [':authority ' ]) || !isset ($ headers [':path ' ])) {
373- $ this ->logger &&$ this ->logger ->debug (sprintf ('Rejecting pushed response from "%s": pushed headers are invalid ' ,$ origin ));
374-
375- return \CURL_PUSH_DENY ;
376- }
377-
378- $ url =$ headers [':scheme ' ][0 ].':// ' .$ headers [':authority ' ][0 ];
379-
380- // curl before 7.65 doesn't validate the pushed ":authority" header,
381- // but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
382- // ignoring domains mentioned as alt-name in the certificate for now (same as curl).
383- if (!str_starts_with ($ origin ,$ url .'/ ' )) {
384- $ this ->logger &&$ this ->logger ->debug (sprintf ('Rejecting pushed response from "%s": server is not authoritative for "%s" ' ,$ origin ,$ url ));
385-
386- return \CURL_PUSH_DENY ;
387- }
388-
389- if ($ maxPendingPushes <=\count ($ this ->multi ->pushedResponses )) {
390- $ fifoUrl =key ($ this ->multi ->pushedResponses );
391- unset($ this ->multi ->pushedResponses [$ fifoUrl ]);
392- $ this ->logger &&$ this ->logger ->debug (sprintf ('Evicting oldest pushed response: "%s" ' ,$ fifoUrl ));
393- }
394-
395- $ url .=$ headers [':path ' ][0 ];
396- $ this ->logger &&$ this ->logger ->debug (sprintf ('Queueing pushed response: "%s" ' ,$ url ));
397-
398- $ this ->multi ->pushedResponses [$ url ] =new PushedResponse (new CurlResponse ($ this ->multi ,$ pushed ),$ headers ,$ this ->multi ->openHandles [(int )$ parent ][1 ] ?? [],$ pushed );
399-
400- return \CURL_PUSH_OK ;
401- }
402-
403323/**
404324 * Accepts pushed responses only if their headers related to authentication match the request.
405325 */