@@ -55,7 +55,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
55
55
*
56
56
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
57
57
*/
58
- public function __construct (array $ defaultOptions = [], int $ maxHostConnections = 6 , int $ maxPendingPushes = 50 )
58
+ public function __construct (array $ defaultOptions = [], int $ maxHostConnections = 6 , int $ maxPendingPushes = 0 )
59
59
{
60
60
if (!\extension_loaded ('curl ' )) {
61
61
throw new \LogicException ('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed. ' );
@@ -105,35 +105,30 @@ public function request(string $method, string $url, array $options = []): Respo
105
105
$ host = parse_url ($ authority , PHP_URL_HOST );
106
106
$ url = implode ('' , $ url );
107
107
108
+ if (!isset ($ options ['normalized_headers ' ]['user-agent ' ])) {
109
+ $ options ['normalized_headers ' ]['user-agent ' ][] = 'Symfony HttpClient/Curl ' ;
110
+ $ options ['headers ' ][] = 'User-Agent: Symfony HttpClient/Curl ' ;
111
+ }
112
+
108
113
if ($ pushedResponse = $ this ->multi ->pushedResponses [$ url ] ?? null ) {
109
114
unset($ this ->multi ->pushedResponses [$ url ]);
110
- // Accept pushed responses only if their headers related to authentication match the request
111
- $ expectedHeaders = ['authorization ' , 'cookie ' , 'x-requested-with ' , 'range ' ];
112
- foreach ($ expectedHeaders as $ k => $ v ) {
113
- $ expectedHeaders [$ k ] = null ;
114
-
115
- foreach ($ options ['normalized_headers ' ][$ v ] ?? [] as $ h ) {
116
- $ expectedHeaders [$ k ][] = substr ($ h , 2 + \strlen ($ v ));
117
- }
118
- }
119
115
120
- if (' GET ' === $ method && $ expectedHeaders === $ pushedResponse-> headers && ! $ options [ ' body ' ] ) {
121
- $ this ->logger && $ this ->logger ->debug (sprintf ('Connecting request to pushed response: "%s %s" ' , $ method , $ url ));
116
+ if (self :: acceptPushForRequest ( $ method, $ options , $ pushedResponse) ) {
117
+ $ this ->logger && $ this ->logger ->debug (sprintf ('Accepting pushed response: "%s %s" ' , $ method , $ url ));
122
118
123
119
// Reinitialize the pushed response with request's options
124
120
$ pushedResponse ->response ->__construct ($ this ->multi , $ url , $ options , $ this ->logger );
125
121
126
122
return $ pushedResponse ->response ;
127
123
}
128
124
129
- $ this ->logger && $ this ->logger ->debug (sprintf ('Rejecting pushed response for "%s": authorization headers don \' t match the request ' , $ url ));
125
+ $ this ->logger && $ this ->logger ->debug (sprintf ('Rejecting pushed response: "%s". ' , $ url ));
130
126
}
131
127
132
128
$ this ->logger && $ this ->logger ->info (sprintf ('Request: "%s %s" ' , $ method , $ url ));
133
129
134
130
$ curlopts = [
135
131
CURLOPT_URL => $ url ,
136
- CURLOPT_USERAGENT => 'Symfony HttpClient/Curl ' ,
137
132
CURLOPT_TCP_NODELAY => true ,
138
133
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS ,
139
134
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS ,
@@ -306,7 +301,7 @@ public function __destruct()
306
301
$ active = 0 ;
307
302
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ this ->multi ->handle , $ active ));
308
303
309
- foreach ($ this ->multi ->openHandles as $ ch ) {
304
+ foreach ($ this ->multi ->openHandles as [ $ ch] ) {
310
305
curl_setopt ($ ch , CURLOPT_VERBOSE , false );
311
306
}
312
307
}
@@ -318,17 +313,17 @@ private static function handlePush($parent, $pushed, array $requestHeaders, Curl
318
313
319
314
foreach ($ requestHeaders as $ h ) {
320
315
if (false !== $ i = strpos ($ h , ': ' , 1 )) {
321
- $ headers [substr ($ h , 0 , $ i )] = substr ($ h , 1 + $ i );
316
+ $ headers [substr ($ h , 0 , $ i )][] = substr ($ h , 1 + $ i );
322
317
}
323
318
}
324
319
325
- if (!isset ($ headers [':method ' ]) || !isset ($ headers [':scheme ' ]) || !isset ($ headers [':authority ' ]) || !isset ($ headers [':path ' ]) || ' GET ' !== $ headers [ ' :method ' ] || isset ( $ headers [ ' range ' ]) ) {
320
+ if (!isset ($ headers [':method ' ]) || !isset ($ headers [':scheme ' ]) || !isset ($ headers [':authority ' ]) || !isset ($ headers [':path ' ])) {
326
321
$ logger && $ logger ->debug (sprintf ('Rejecting pushed response from "%s": pushed headers are invalid ' , $ origin ));
327
322
328
323
return CURL_PUSH_DENY ;
329
324
}
330
325
331
- $ url = $ headers [':scheme ' ].':// ' .$ headers [':authority ' ];
326
+ $ url = $ headers [':scheme ' ][ 0 ] .':// ' .$ headers [':authority ' ][ 0 ];
332
327
333
328
if ($ maxPendingPushes <= \count ($ multi ->pushedResponses )) {
334
329
$ logger && $ logger ->debug (sprintf ('Rejecting pushed response from "%s" for "%s": the queue is full ' , $ origin , $ url ));
@@ -345,22 +340,38 @@ private static function handlePush($parent, $pushed, array $requestHeaders, Curl
345
340
return CURL_PUSH_DENY ;
346
341
}
347
342
348
- $ url .= $ headers [':path ' ];
343
+ $ url .= $ headers [':path ' ][ 0 ] ;
349
344
$ logger && $ logger ->debug (sprintf ('Queueing pushed response: "%s" ' , $ url ));
350
345
351
- $ multi ->pushedResponses [$ url ] = new PushedResponse (
352
- new CurlResponse ($ multi , $ pushed ),
353
- [
354
- $ headers ['authorization ' ] ?? null ,
355
- $ headers ['cookie ' ] ?? null ,
356
- $ headers ['x-requested-with ' ] ?? null ,
357
- null ,
358
- ]
359
- );
346
+ $ multi ->pushedResponses [$ url ] = new PushedResponse (new CurlResponse ($ multi , $ pushed ), $ headers , $ multi ->openHandles [(int ) $ parent ][1 ] ?? []);
360
347
361
348
return CURL_PUSH_OK ;
362
349
}
363
350
351
+ /**
352
+ * Accepts pushed responses only if their headers related to authentication match the request.
353
+ */
354
+ private static function acceptPushForRequest (string $ method , array $ options , PushedResponse $ pushedResponse ): bool
355
+ {
356
+ if ($ options ['body ' ] || $ method !== $ pushedResponse ->requestHeaders [':method ' ][0 ]) {
357
+ return false ;
358
+ }
359
+
360
+ foreach (['proxy ' , 'no_proxy ' , 'bindto ' ] as $ k ) {
361
+ if ($ options [$ k ] !== $ pushedResponse ->parentOptions [$ k ]) {
362
+ return false ;
363
+ }
364
+ }
365
+
366
+ foreach (['authorization ' , 'cookie ' , 'range ' , 'proxy-authorization ' ] as $ k ) {
367
+ if (($ pushedResponse ->requestHeaders [$ k ] ?? null ) !== ($ options ['normalized_headers ' ][$ k ] ?? null )) {
368
+ return false ;
369
+ }
370
+ }
371
+
372
+ return true ;
373
+ }
374
+
364
375
/**
365
376
* Wraps the request's body callback to allow it to return strings longer than curl requested.
366
377
*/
0 commit comments