@@ -141,72 +141,70 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
141
141
}
142
142
143
143
/**
144
- * {@inheritdoc}
144
+ * Parses header lines as curl yields them to us.
145
145
*/
146
- public function getInfo ( string $ type = null )
146
+ private static function parseHeaderLine ( $ ch , string $ data , array & $ info , array & $ headers , ? array $ options , CurlClientState $ multi , int $ id , ? string & $ location , ? callable $ resolveRedirect , ? LoggerInterface $ logger ): int
147
147
{
148
- if (!$ info = $ this ->finalInfo ) {
149
- self ::perform ($ this ->multi );
148
+ if (!\in_array ($ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ), ['headers ' , 'destruct ' ], true )) {
149
+ return \strlen ($ data ); // Ignore HTTP trailers
150
+ }
150
151
151
- if ('debug ' === $ type ) {
152
- rewind ($ this ->debugBuffer );
152
+ if ("\r\n" !== $ data ) {
153
+ // Regular header line: add it to the list
154
+ self ::addResponseHeaders ([substr ($ data , 0 , -2 )], $ info , $ headers );
153
155
154
- return stream_get_contents ($ this ->debugBuffer );
156
+ if (0 === strpos ($ data , 'HTTP ' ) && 300 <= $ info ['http_code ' ] && $ info ['http_code ' ] < 400 ) {
157
+ if (curl_getinfo ($ ch , CURLINFO_REDIRECT_COUNT ) === $ options ['max_redirects ' ]) {
158
+ curl_setopt ($ ch , CURLOPT_FOLLOWLOCATION , false );
159
+ } elseif (303 === $ info ['http_code ' ] || ('POST ' === $ info ['http_method ' ] && \in_array ($ info ['http_code ' ], [301 , 302 ], true ))) {
160
+ $ info ['http_method ' ] = 'HEAD ' === $ info ['http_method ' ] ? 'HEAD ' : 'GET ' ;
161
+ curl_setopt ($ ch , CURLOPT_POSTFIELDS , '' );
162
+ }
155
163
}
156
164
157
- $ info = array_merge ($ this ->info , curl_getinfo ($ this ->handle ));
158
- $ info ['url ' ] = $ this ->info ['url ' ] ?? $ info ['url ' ];
159
- $ info ['redirect_url ' ] = $ this ->info ['redirect_url ' ] ?? null ;
160
-
161
- // workaround curl not subtracting the time offset for pushed responses
162
- if (isset ($ this ->info ['url ' ]) && $ info ['start_time ' ] / 1000 < $ info ['total_time ' ]) {
163
- $ info ['total_time ' ] -= $ info ['starttransfer_time ' ] ?: $ info ['total_time ' ];
164
- $ info ['starttransfer_time ' ] = 0.0 ;
165
+ if (0 === stripos ($ data , 'Location: ' )) {
166
+ $ location = trim (substr ($ data , 9 , -2 ));
165
167
}
166
168
167
- if (!\in_array (curl_getinfo ($ this ->handle , CURLINFO_PRIVATE ), ['headers ' , 'content ' ], true )) {
168
- rewind ($ this ->debugBuffer );
169
- $ info ['debug ' ] = stream_get_contents ($ this ->debugBuffer );
170
- fclose ($ this ->debugBuffer );
171
- $ this ->debugBuffer = null ;
172
- $ this ->finalInfo = $ info ;
173
- }
169
+ return \strlen ($ data );
174
170
}
175
171
176
- return null !== $ type ? $ info [$ type ] ?? null : $ info ;
177
- }
172
+ // End of headers: handle redirects and add to the activity list
173
+ $ statusCode = curl_getinfo ($ ch , CURLINFO_RESPONSE_CODE );
174
+ $ info ['redirect_url ' ] = null ;
178
175
179
- public function __destruct ()
180
- {
181
- try {
182
- if (null === $ this ->timeout ) {
183
- return ; // Unused pushed response
184
- }
176
+ if (300 <= $ statusCode && $ statusCode < 400 && null !== $ location && curl_getinfo ($ ch , CURLINFO_REDIRECT_COUNT ) < $ options ['max_redirects ' ]) {
177
+ $ info ['redirect_url ' ] = $ resolveRedirect ($ ch , $ location );
178
+ $ url = parse_url ($ location ?? ': ' );
185
179
186
- if ('content ' === $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE )) {
187
- $ this ->close ();
188
- } elseif ('headers ' === $ waitFor ) {
189
- curl_setopt ($ this ->handle , CURLOPT_PRIVATE , 'destruct ' );
180
+ if (isset ($ url ['host ' ]) && null !== $ ip = $ multi ->dnsCache ->hostnames [$ url ['host ' ] = strtolower ($ url ['host ' ])] ?? null ) {
181
+ // Populate DNS cache for redirects if needed
182
+ $ port = $ url ['port ' ] ?? ('http ' === ($ url ['scheme ' ] ?? parse_url (curl_getinfo ($ ch , CURLINFO_EFFECTIVE_URL ), PHP_URL_SCHEME )) ? 80 : 443 );
183
+ curl_setopt ($ ch , CURLOPT_RESOLVE , ["{$ url ['host ' ]}: $ port: $ ip " ]);
184
+ $ multi ->dnsCache ->removals ["- {$ url ['host ' ]}: $ port " ] = "- {$ url ['host ' ]}: $ port " ;
190
185
}
186
+ }
191
187
192
- $ this ->doDestruct ();
193
- } finally {
194
- $ this ->close ();
188
+ $ location = null ;
195
189
196
- // Clear local caches when the only remaining handles are about pushed responses
197
- if (!$ this ->multi ->openHandles ) {
198
- if ($ this ->logger ) {
199
- foreach ($ this ->multi ->pushedResponses as $ url => $ response ) {
200
- $ this ->logger ->debug (sprintf ('Unused pushed response: "%s" ' , $ url ));
201
- }
202
- }
190
+ if ($ statusCode < 300 || 400 <= $ statusCode || curl_getinfo ($ ch , CURLINFO_REDIRECT_COUNT ) === $ options ['max_redirects ' ]) {
191
+ // Headers and redirects completed, time to get the response's body
192
+ $ multi ->handlesActivity [$ id ] = [new FirstChunk ()];
203
193
204
- $ this ->multi ->pushedResponses = [];
205
- // Schedule DNS cache eviction for the next request
206
- $ this ->multi ->dnsCache ->evictions = $ this ->multi ->dnsCache ->evictions ?: $ this ->multi ->dnsCache ->removals ;
207
- $ this ->multi ->dnsCache ->removals = $ this ->multi ->dnsCache ->hostnames = [];
194
+ if ('destruct ' === $ waitFor ) {
195
+ return 0 ;
208
196
}
197
+
198
+ if ($ certinfo = curl_getinfo ($ ch , CURLINFO_CERTINFO )) {
199
+ $ info ['peer_certificate_chain ' ] = array_map ('openssl_x509_read ' , array_column ($ certinfo , 'Cert ' ));
200
+ }
201
+
202
+ curl_setopt ($ ch , CURLOPT_PRIVATE , 'content ' );
203
+ } elseif (null !== $ info ['redirect_url ' ] && $ logger ) {
204
+ $ logger ->info (sprintf ('Redirecting: "%s %s" ' , $ info ['http_code ' ], $ info ['redirect_url ' ]));
209
205
}
206
+
207
+ return \strlen ($ data );
210
208
}
211
209
212
210
/**
@@ -227,24 +225,6 @@ private function close(): void
227
225
]);
228
226
}
229
227
230
- /**
231
- * {@inheritdoc}
232
- */
233
- private static function schedule (self $ response , array &$ runningResponses ): void
234
- {
235
- if (isset ($ runningResponses [$ i = (int ) $ response ->multi ->handle ])) {
236
- $ runningResponses [$ i ][1 ][$ response ->id ] = $ response ;
237
- } else {
238
- $ runningResponses [$ i ] = [$ response ->multi , [$ response ->id => $ response ]];
239
- }
240
-
241
- if ('' === curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE )) {
242
- // Response already completed
243
- $ response ->multi ->handlesActivity [$ response ->id ][] = null ;
244
- $ response ->multi ->handlesActivity [$ response ->id ][] = null !== $ response ->info ['error ' ] ? new TransportException ($ response ->info ['error ' ]) : null ;
245
- }
246
- }
247
-
248
228
/**
249
229
* {@inheritdoc}
250
230
*/
@@ -267,6 +247,24 @@ private static function perform(CurlClientState $multi, array &$responses = null
267
247
}
268
248
}
269
249
250
+ /**
251
+ * {@inheritdoc}
252
+ */
253
+ private static function schedule (self $ response , array &$ runningResponses ): void
254
+ {
255
+ if (isset ($ runningResponses [$ i = (int ) $ response ->multi ->handle ])) {
256
+ $ runningResponses [$ i ][1 ][$ response ->id ] = $ response ;
257
+ } else {
258
+ $ runningResponses [$ i ] = [$ response ->multi , [$ response ->id => $ response ]];
259
+ }
260
+
261
+ if ('' === curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE )) {
262
+ // Response already completed
263
+ $ response ->multi ->handlesActivity [$ response ->id ][] = null ;
264
+ $ response ->multi ->handlesActivity [$ response ->id ][] = null !== $ response ->info ['error ' ] ? new TransportException ($ response ->info ['error ' ]) : null ;
265
+ }
266
+ }
267
+
270
268
/**
271
269
* {@inheritdoc}
272
270
*/
@@ -276,69 +274,71 @@ private static function select(CurlClientState $multi, float $timeout): int
276
274
}
277
275
278
276
/**
279
- * Parses header lines as curl yields them to us.
277
+ * {@inheritdoc}
280
278
*/
281
- private static function parseHeaderLine ( $ ch , string $ data , array & $ info , array & $ headers , ? array $ options , CurlClientState $ multi , int $ id , ? string & $ location , ? callable $ resolveRedirect , ? LoggerInterface $ logger ): int
279
+ public function getInfo ( string $ type = null )
282
280
{
283
- if (!\in_array ($ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ), ['headers ' , 'destruct ' ], true )) {
284
- return \strlen ($ data ); // Ignore HTTP trailers
285
- }
281
+ if (!$ info = $ this ->finalInfo ) {
282
+ self ::perform ($ this ->multi );
286
283
287
- if ("\r\n" !== $ data ) {
288
- // Regular header line: add it to the list
289
- self ::addResponseHeaders ([substr ($ data , 0 , -2 )], $ info , $ headers );
284
+ if ('debug ' === $ type ) {
285
+ rewind ($ this ->debugBuffer );
290
286
291
- if (0 === strpos ($ data , 'HTTP ' ) && 300 <= $ info ['http_code ' ] && $ info ['http_code ' ] < 400 ) {
292
- if (curl_getinfo ($ ch , CURLINFO_REDIRECT_COUNT ) === $ options ['max_redirects ' ]) {
293
- curl_setopt ($ ch , CURLOPT_FOLLOWLOCATION , false );
294
- } elseif (303 === $ info ['http_code ' ] || ('POST ' === $ info ['http_method ' ] && \in_array ($ info ['http_code ' ], [301 , 302 ], true ))) {
295
- $ info ['http_method ' ] = 'HEAD ' === $ info ['http_method ' ] ? 'HEAD ' : 'GET ' ;
296
- curl_setopt ($ ch , CURLOPT_POSTFIELDS , '' );
297
- }
287
+ return stream_get_contents ($ this ->debugBuffer );
298
288
}
299
289
300
- if (0 === stripos ($ data , 'Location: ' )) {
301
- $ location = trim (substr ($ data , 9 , -2 ));
290
+ $ info = array_merge ($ this ->info , curl_getinfo ($ this ->handle ));
291
+ $ info ['url ' ] = $ this ->info ['url ' ] ?? $ info ['url ' ];
292
+ $ info ['redirect_url ' ] = $ this ->info ['redirect_url ' ] ?? null ;
293
+
294
+ // workaround curl not subtracting the time offset for pushed responses
295
+ if (isset ($ this ->info ['url ' ]) && $ info ['start_time ' ] / 1000 < $ info ['total_time ' ]) {
296
+ $ info ['total_time ' ] -= $ info ['starttransfer_time ' ] ?: $ info ['total_time ' ];
297
+ $ info ['starttransfer_time ' ] = 0.0 ;
302
298
}
303
299
304
- return \strlen ($ data );
300
+ if (!\in_array (curl_getinfo ($ this ->handle , CURLINFO_PRIVATE ), ['headers ' , 'content ' ], true )) {
301
+ rewind ($ this ->debugBuffer );
302
+ $ info ['debug ' ] = stream_get_contents ($ this ->debugBuffer );
303
+ fclose ($ this ->debugBuffer );
304
+ $ this ->debugBuffer = null ;
305
+ $ this ->finalInfo = $ info ;
306
+ }
305
307
}
306
308
307
- // End of headers: handle redirects and add to the activity list
308
- $ statusCode = curl_getinfo ($ ch , CURLINFO_RESPONSE_CODE );
309
- $ info ['redirect_url ' ] = null ;
310
-
311
- if (300 <= $ statusCode
10000
&& $ statusCode < 400 && null !== $ location ) {
312
- $ info ['redirect_url ' ] = $ resolveRedirect ($ ch , $ location );
313
- $ url = parse_url ($ location ?? ': ' );
309
+ return null !== $ type ? $ info [$ type ] ?? null : $ info ;
310
+ }
314
311
315
- if ( isset ( $ url [ ' host ' ]) && null !== $ ip = $ multi -> dnsCache -> hostnames [ $ url [ ' host ' ] = strtolower ( $ url [ ' host ' ])] ?? null ) {
316
- // Populate DNS cache for redirects if needed
317
- $ port = $ url [ ' port ' ] ?? ( ' http ' === ( $ url [ ' scheme ' ] ?? parse_url ( curl_getinfo ( $ ch , CURLINFO_EFFECTIVE_URL ), PHP_URL_SCHEME )) ? 80 : 443 );
318
- curl_setopt ( $ ch , CURLOPT_RESOLVE , [ "{ $ url [ ' host ' ]} : $ port : $ ip " ]);
319
- $ multi -> dnsCache -> removals [ " - { $ url [ ' host ' ]} : $ port " ] = " - { $ url [ ' host ' ]} : $ port " ;
312
+ public function __destruct ()
313
+ {
314
+ try {
315
+ if ( null === $ this -> timeout ) {
316
+ return ; // Unused pushed response
320
317
}
321
- }
322
318
323
- $ location = null ;
319
+ if ('content ' === $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE )) {
320
+ $ this ->close ();
321
+ } elseif ('headers ' === $ waitFor ) {
322
+ curl_setopt ($ this ->handle , CURLOPT_PRIVATE , 'destruct ' );
323
+ }
324
324
325
- if ( $ statusCode < 300 || 400 <= $ statusCode || curl_getinfo ( $ ch , CURLINFO_REDIRECT_COUNT ) === $ options [ ' max_redirects ' ]) {
326
- // Headers and redirects completed, time to get the response's body
327
- $ multi -> handlesActivity [ $ id ] = [ new FirstChunk ()] ;
325
+ $ this -> doDestruct ();
326
+ } finally {
327
+ $ this -> close () ;
328
328
329
- if ('destruct ' === $ waitFor ) {
330
- return 0 ;
331
- }
329
+ // Clear local caches when the only remaining handles are about pushed responses
330
+ if (!$ this ->multi ->openHandles ) {
331
+ if ($ this ->logger ) {
332
+ foreach ($ this ->multi ->pushedResponses as $ url => $ response ) {
333
+ $ this ->logger ->debug (sprintf ('Unused pushed response: "%s" ' , $ url ));
334
+ }
335
+ }
332
336
333
- if ($ certinfo = curl_getinfo ($ ch , CURLINFO_CERTINFO )) {
334
- $ info ['peer_certificate_chain ' ] = array_map ('openssl_x509_read ' , array_column ($ certinfo , 'Cert ' ));
337
+ $ this ->multi ->pushedResponses = [];
338
+ // Schedule DNS cache eviction for the next request
339
+ $ this ->multi ->dnsCache ->evictions = $ this ->multi ->dnsCache ->evictions ?: $ this ->multi ->dnsCache ->removals ;
340
+ $ this ->multi ->dnsCache ->removals = $ this ->multi ->dnsCache ->hostnames = [];
335
341
}
336
-
337
- curl_setopt ($ ch , CURLOPT_PRIVATE , 'content ' );
338
- } elseif (null !== $ info ['redirect_url ' ] && $ logger ) {
339
- $ logger ->info (sprintf ('Redirecting: "%s %s" ' , $ info ['http_code ' ], $ info ['redirect_url ' ]));
340
342
}
341
-
342
- return \strlen ($ data );
343
343
}
344
344
}
0 commit comments