Skip to content

Commit cb4073c

Browse files
Merge branch '5.4' into 6.4
* 5.4: [HttpClient] Resolve hostnames in NoPrivateNetworkHttpClient [security-http] Check owner of persisted remember-me cookie
2 parents 05d88cb + 3b643b8 commit cb4073c

10 files changed

+78
-15
lines changed

HttpOptions.php

+2
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ public function buffer(bool $buffer): static
156156
}
157157

158158
/**
159+
* @param callable(int, int, array, \Closure|null=):void $callback
160+
*
159161
* @return $this
160162
*/
161163
public function setOnProgress(callable $callback): static

NativeHttpClient.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,15 @@ public function request(string $method, string $url, array $options = []): Respo
137137

138138
if ($onProgress = $options['on_progress']) {
139139
$maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF;
140-
$onProgress = static function (...$progress) use ($onProgress, &$info, $maxDuration) {
140+
$multi = $this->multi;
141+
$resolve = static function (string $host, ?string $ip = null) use ($multi): ?string {
142+
if (null !== $ip) {
143+
$multi->dnsCache[$host] = $ip;
144+
}
145+
146+
return $multi->dnsCache[$host] ?? null;
147+
};
148+
$onProgress = static function (...$progress) use ($onProgress, &$info, $maxDuration, $resolve) {
141149
if ($info['total_time'] >= $maxDuration) {
142150
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
143151
}
@@ -156,7 +164,7 @@ public function request(string $method, string $url, array $options = []): Respo
156164
$lastProgress = $progress ?: $lastProgress;
157165
}
158166

159-
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo);
167+
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo, $resolve);
160168
};
161169
} elseif (0 < $options['max_duration']) {
162170
$maxDuration = $options['max_duration'];

NoPrivateNetworkHttpClient.php

+15-2
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,27 @@ public function request(string $method, string $url, array $options = []): Respo
5656

5757
$subnets = $this->subnets;
5858

59-
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets): void {
59+
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use ($onProgress, $subnets): void {
6060
static $lastUrl = '';
6161
static $lastPrimaryIp = '';
6262

6363
if ($info['url'] !== $lastUrl) {
6464
$host = trim(parse_url($info['url'], PHP_URL_HOST) ?: '', '[]');
65+
$resolve ??= static fn () => null;
66+
67+
if (($ip = $host)
68+
&& !filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)
69+
&& !filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)
70+
&& !$ip = $resolve($host)
71+
) {
72+
if ($ip = @(dns_get_record($host, \DNS_A)[0]['ip'] ?? null)) {
73+
$resolve($host, $ip);
74+
} elseif ($ip = @(dns_get_record($host, \DNS_AAAA)[0]['ipv6'] ?? null)) {
75+
$resolve($host, '['.$ip.']');
76+
}
77+
}
6578

66-
if ($host && IpUtils::checkIp($host, $subnets ?? IpUtils::PRIVATE_SUBNETS)) {
79+
if ($ip && IpUtils::checkIp($ip, $subnets ?? IpUtils::PRIVATE_SUBNETS)) {
6780
throw new TransportException(sprintf('Host "%s" is blocked for "%s".', $host, $info['url']));
6881
}
6982

Response/AmpResponse.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,17 @@ public function __construct(AmpClientState $multi, Request $request, array $opti
8888
$info['max_duration'] = $options['max_duration'];
8989
$info['debug'] = '';
9090

91+
$resolve = static function (string $host, ?string $ip = null) use ($multi): ?string {
92+
if (null !== $ip) {
93+
$multi->dnsCache[$host] = $ip;
94+
}
95+
96+
return $multi->dnsCache[$host] ?? null;
97+
};
9198
$onProgress = $options['on_progress'] ?? static function () {};
92-
$onProgress = $this->onProgress = static function () use (&$info, $onProgress) {
99+
$onProgress = $this->onProgress = static function () use (&$info, $onProgress, $resolve) {
93100
$info['total_time'] = microtime(true) - $info['start_time'];
94-
$onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info);
101+
$onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info, $resolve);
95102
};
96103

97104
$pauseDeferred = new Deferred();

Response/AsyncContext.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ public function replaceRequest(string $method, string $url, array $options = [])
161161
$this->info['previous_info'][] = $info = $this->response->getInfo();
162162
if (null !== $onProgress = $options['on_progress'] ?? null) {
163163
$thisInfo = &$this->info;
164-
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
165-
$onProgress($dlNow, $dlSize, $thisInfo + $info);
164+
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use (&$thisInfo, $onProgress) {
165+
$onProgress($dlNow, $dlSize, $thisInfo + $info, $resolve);
166166
};
167167
}
168168
if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) {

Response/AsyncResponse.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public function __construct(HttpClientInterface $client, string $method, string
5252

5353
if (null !== $onProgress = $options['on_progress'] ?? null) {
5454
$thisInfo = &$this->info;
55-
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
56-
$onProgress($dlNow, $dlSize, $thisInfo + $info);
55+
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use (&$thisInfo, $onProgress) {
56+
$onProgress($dlNow, $dlSize, $thisInfo + $info, $resolve);
5757
};
5858
}
5959
$this->response = $client->request($method, $url, ['buffer' => false] + $options);

Response/CurlResponse.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,20 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, ?arr
118118
curl_pause($ch, \CURLPAUSE_CONT);
119119

120120
if ($onProgress = $options['on_progress']) {
121+
$resolve = static function (string $host, ?string $ip = null) use ($multi): ?string {
122+
if (null !== $ip) {
123+
$multi->dnsCache->hostnames[$host] = $ip;
124+
}
125+
126+
return $multi->dnsCache->hostnames[$host] ?? null;
127+
};
121128
$url = isset($info['url']) ? ['url' => $info['url']] : [];
122129
curl_setopt($ch, \CURLOPT_NOPROGRESS, false);
123-
curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) {
130+
curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer, $resolve) {
124131
try {
125132
rewind($debugBuffer);
126133
$debug = ['debug' => stream_get_contents($debugBuffer)];
127-
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug);
134+
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug, $resolve);
128135
} catch (\Throwable $e) {
129136
$multi->handlesActivity[(int) $ch][] = null;
130137
$multi->handlesActivity[(int) $ch][] = $e;

Tests/HttpClientTestCase.php

+23
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
1616
use Symfony\Component\HttpClient\Exception\TransportException;
1717
use Symfony\Component\HttpClient\Internal\ClientState;
18+
use Symfony\Component\HttpClient\NoPrivateNetworkHttpClient;
1819
use Symfony\Component\HttpClient\Response\StreamWrapper;
1920
use Symfony\Component\Process\Exception\ProcessFailedException;
2021
use Symfony\Component\Process\Process;
@@ -462,6 +463,28 @@ public function testMisspelledScheme()
462463
$httpClient->request('GET', 'http:/localhost:8057/');
463464
}
464465

466+
public function testNoPrivateNetwork()
467+
{
468+
$client = $this->getHttpClient(__FUNCTION__);
469+
$client = new NoPrivateNetworkHttpClient($client);
470+
471+
$this->expectException(TransportException::class);
472+
$this->expectExceptionMessage('Host "localhost" is blocked');
473+
474+
$client->request('GET', 'http://localhost:8888');
475+
}
476+
477+
public function testNoPrivateNetworkWithResolve()
478+
{
479+
$client = $this->getHttpClient(__FUNCTION__);
480+
$client = new NoPrivateNetworkHttpClient($client);
481+
482+
$this->expectException(TransportException::class);
483+
$this->expectExceptionMessage('Host "symfony.com" is blocked');
484+
485+
$client->request('GET', 'http://symfony.com', ['resolve' => ['symfony.com' => '127.0.0.1']]);
486+
}
487+
465488
/**
466489
* @dataProvider getRedirectWithAuthTests
467490
*/

Tests/MockHttpClientTest.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,17 @@ protected function getHttpClient(string $testCase): HttpClientInterface
331331

332332
switch ($testCase) {
333333
default:
334-
return new MockHttpClient(function (string $method, string $url, array $options) use ($client) {
334+
return new MockHttpClient(function (string $method, string $url, array $options) use ($client, $testCase) {
335335
try {
336336
// force the request to be completed so that we don't test side effects of the transport
337337
$response = $client->request($method, $url, ['buffer' => false] + $options);
338338
$content = $response->getContent(false);
339339

340340
return new MockResponse($content, $response->getInfo());
341341
} catch (\Throwable $e) {
342+
if (str_starts_with($testCase, 'testNoPrivateNetwork')) {
343+
throw $e;
344+
}
342345
$this->fail($e->getMessage());
343346
}
344347
});

TraceableHttpClient.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ public function request(string $method, string $url, array $options = []): Respo
5555
$content = false;
5656
}
5757

58-
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) {
58+
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use (&$traceInfo, $onProgress) {
5959
$traceInfo = $info;
6060

6161
if (null !== $onProgress) {
62-
$onProgress($dlNow, $dlSize, $info);
62+
$onProgress($dlNow, $dlSize, $info, $resolve);
6363
}
6464
};
6565

0 commit comments

Comments
 (0)