8000 Support invalid location header if no redirections · mleczakm/symfony@5c27013 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c27013

Browse files
author
Michał Mleczko
committed
Support invalid location header if no redirections
1 parent c5922d2 commit 5c27013

File tree

1 file changed

+112
-112
lines changed

1 file changed

+112
-112
lines changed

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 112 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -141,72 +141,70 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
141141
}
142142

143143
/**
144-
* {@inheritdoc}
144+
* Parses header lines as curl yields them to us.
145145
*/
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
147147
{
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+
}
150151

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);
153155

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+
}
155163
}
156164

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));
165167
}
166168

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);
174170
}
175171

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;
178175

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 ?? ':');
185179

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";
190185
}
186+
}
191187

192-
$this->doDestruct();
193-
} finally {
194-
$this->close();
188+
$location = null;
195189

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()];
203193

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;
208196
}
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']));
209205
}
206+
207+
return \strlen($data);
210208
}
211209

212210
/**
@@ -227,24 +225,6 @@ private function close(): void
227225
]);
228226
}
229227

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-
248228
/**
249229
* {@inheritdoc}
250230
*/
@@ -267,6 +247,24 @@ private static function perform(CurlClientState $multi, array &$responses = null
267247
}
268248
}
269249

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+
270268
/**
271269
* {@inheritdoc}
272270
*/
@@ -276,69 +274,71 @@ private static function select(CurlClientState $multi, float $timeout): int
276274
}
277275

278276
/**
279-
* Parses header lines as curl yields them to us.
277+
* {@inheritdoc}
280278
*/
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)
282280
{
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);
286283

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);
290286

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);
298288
}
299289

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;
302298
}
303299

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+
}
305307
}
306308

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+
}
314311

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
320317
}
321-
}
322318

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+
}
324324

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();
328328

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+
}
332336

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 = [];
335341
}
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']));
340342
}
341-
342-
return \strlen($data);
343343
}
344344
}

0 commit comments

Comments
 (0)
0