Update website
This commit is contained in:
parent
0a686aeb9a
commit
c4ffa0f6ee
4360 changed files with 1727 additions and 718385 deletions
|
@ -1,3 +0,0 @@
|
|||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
|
@ -1,3 +0,0 @@
|
|||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
|
@ -48,9 +48,9 @@ interface HttpClientInterface
|
|||
'buffer' => true, // bool|resource|\Closure - whether the content of the response should be buffered or not,
|
||||
// or a stream resource where the response body should be written,
|
||||
// or a closure telling if/where the response should be buffered based on its headers
|
||||
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort
|
||||
// the request; it MUST be called on DNS resolution, on arrival of headers and on
|
||||
// completion; it SHOULD be called on upload/download of data and at least 1/s
|
||||
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort the
|
||||
// request; it MUST be called on connection, on headers and on completion; it SHOULD be
|
||||
// called on upload/download of data and at least 1/s
|
||||
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution
|
||||
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
|
||||
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
|
||||
|
|
|
@ -12,26 +12,32 @@ if (!$_POST) {
|
|||
$_POST['content-type'] = $_SERVER['HTTP_CONTENT_TYPE'] ?? '?';
|
||||
}
|
||||
|
||||
$headers = [
|
||||
'SERVER_PROTOCOL',
|
||||
'SERVER_NAME',
|
||||
'REQUEST_URI',
|
||||
'REQUEST_METHOD',
|
||||
'PHP_AUTH_USER',
|
||||
'PHP_AUTH_PW',
|
||||
'REMOTE_ADDR',
|
||||
'REMOTE_PORT',
|
||||
];
|
||||
|
||||
foreach ($headers as $k) {
|
||||
if (isset($_SERVER[$k])) {
|
||||
$vars[$k] = $_SERVER[$k];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($_SERVER as $k => $v) {
|
||||
switch ($k) {
|
||||
default:
|
||||
if (0 !== strpos($k, 'HTTP_')) {
|
||||
continue 2;
|
||||
}
|
||||
// no break
|
||||
case 'SERVER_NAME':
|
||||
case 'SERVER_PROTOCOL':
|
||||
case 'REQUEST_URI':
|
||||
case 'REQUEST_METHOD':
|
||||
case 'PHP_AUTH_USER':
|
||||
case 'PHP_AUTH_PW':
|
||||
$vars[$k] = $v;
|
||||
if (0 === strpos($k, 'HTTP_')) {
|
||||
$vars[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$json = json_encode($vars, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
|
||||
|
||||
switch ($vars['REQUEST_URI']) {
|
||||
switch (parse_url($vars['REQUEST_URI'], \PHP_URL_PATH)) {
|
||||
default:
|
||||
exit;
|
||||
|
||||
|
@ -94,7 +100,8 @@ switch ($vars['REQUEST_URI']) {
|
|||
|
||||
case '/302':
|
||||
if (!isset($vars['HTTP_AUTHORIZATION'])) {
|
||||
header('Location: http://localhost:8057/', true, 302);
|
||||
$location = $_GET['location'] ?? 'http://localhost:8057/';
|
||||
header('Location: '.$location, true, 302);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ abstract class HttpClientTestCase extends TestCase
|
|||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
if (!function_exists('ob_gzhandler')) {
|
||||
static::markTestSkipped('The "ob_gzhandler" function is not available.');
|
||||
}
|
||||
|
||||
TestHttpServer::start();
|
||||
}
|
||||
|
||||
|
@ -730,6 +734,18 @@ abstract class HttpClientTestCase extends TestCase
|
|||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testIPv6Resolve()
|
||||
{
|
||||
TestHttpServer::start(-8087);
|
||||
|
||||
$client = $this->getHttpClient(__FUNCTION__);
|
||||
$response = $client->request('GET', 'http://symfony.com:8087/', [
|
||||
'resolve' => ['symfony.com' => '::1'],
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testNotATimeout()
|
||||
{
|
||||
$client = $this->getHttpClient(__FUNCTION__);
|
||||
|
@ -1148,4 +1164,33 @@ abstract class HttpClientTestCase extends TestCase
|
|||
$response = $client2->request('GET', '/');
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testBindToPort()
|
||||
{
|
||||
$client = $this->getHttpClient(__FUNCTION__);
|
||||
$response = $client->request('GET', 'http://localhost:8057', ['bindto' => '127.0.0.1:9876']);
|
||||
$response->getStatusCode();
|
||||
|
||||
$vars = $response->toArray();
|
||||
|
||||
self::assertSame('127.0.0.1', $vars['REMOTE_ADDR']);
|
||||
self::assertSame('9876', $vars['REMOTE_PORT']);
|
||||
}
|
||||
|
||||
public function testBindToPortV6()
|
||||
{
|
||||
TestHttpServer::start(-8087);
|
||||
|
||||
$client = $this->getHttpClient(__FUNCTION__);
|
||||
$response = $client->request('GET', 'http://[::1]:8087', ['bindto' => '[::1]:9876']);
|
||||
$response->getStatusCode();
|
||||
|
||||
$vars = $response->toArray();
|
||||
|
||||
self::assertSame('::1', $vars['REMOTE_ADDR']);
|
||||
|
||||
if ('\\' !== \DIRECTORY_SEPARATOR) {
|
||||
self::assertSame('9876', $vars['REMOTE_PORT']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,13 @@ class TestHttpServer
|
|||
*/
|
||||
public static function start(int $port = 8057)
|
||||
{
|
||||
if (0 > $port) {
|
||||
$port = -$port;
|
||||
$ip = '[::1]';
|
||||
} else {
|
||||
$ip = '127.0.0.1';
|
||||
}
|
||||
|
||||
if (isset(self::$process[$port])) {
|
||||
self::$process[$port]->stop();
|
||||
} else {
|
||||
|
@ -32,14 +39,14 @@ class TestHttpServer
|
|||
}
|
||||
|
||||
$finder = new PhpExecutableFinder();
|
||||
$process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:'.$port]));
|
||||
$process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', $ip.':'.$port]));
|
||||
$process->setWorkingDirectory(__DIR__.'/Fixtures/web');
|
||||
$process->start();
|
||||
self::$process[$port] = $process;
|
||||
|
||||
do {
|
||||
usleep(50000);
|
||||
} while (!@fopen('http://127.0.0.1:'.$port, 'r'));
|
||||
} while (!@fopen('http://'.$ip.':'.$port, 'r'));
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
|
1
vendor/symfony/http-client/AmpHttpClient.php
vendored
1
vendor/symfony/http-client/AmpHttpClient.php
vendored
|
@ -118,6 +118,7 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
|
|||
}
|
||||
|
||||
$request = new Request(implode('', $url), $method);
|
||||
$request->setBodySizeLimit(0);
|
||||
|
||||
if ($options['http_version']) {
|
||||
switch ((float) $options['http_version']) {
|
||||
|
|
11
vendor/symfony/http-client/CurlHttpClient.php
vendored
11
vendor/symfony/http-client/CurlHttpClient.php
vendored
|
@ -274,7 +274,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
|
|||
if (file_exists($options['bindto'])) {
|
||||
$curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto'];
|
||||
} elseif (!str_starts_with($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) {
|
||||
$curlopts[\CURLOPT_INTERFACE] = $matches[1];
|
||||
$curlopts[\CURLOPT_INTERFACE] = trim($matches[1], '[]');
|
||||
$curlopts[\CURLOPT_LOCALPORT] = $matches[2];
|
||||
} else {
|
||||
$curlopts[\CURLOPT_INTERFACE] = $options['bindto'];
|
||||
|
@ -424,6 +424,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
|
|||
return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) {
|
||||
try {
|
||||
$location = self::parseUrl($location);
|
||||
$url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));
|
||||
$url = self::resolveUrl($location, $url);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -436,16 +438,13 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
|
|||
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
|
||||
}
|
||||
|
||||
if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
|
||||
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
if ($redirectHeaders && isset($location['authority'])) {
|
||||
$requestHeaders = parse_url($location['authority'], \PHP_URL_HOST) === $redirectHeaders['host'] ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
|
||||
} elseif ($noContent && $redirectHeaders) {
|
||||
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);
|
||||
}
|
||||
|
||||
$url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));
|
||||
$url = self::resolveUrl($location, $url);
|
||||
|
||||
curl_setopt($ch, \CURLOPT_PROXY, self::getProxyUrl($options['proxy'], $url));
|
||||
|
||||
return implode('', $url);
|
||||
|
|
42
vendor/symfony/http-client/HttpClientTrait.php
vendored
42
vendor/symfony/http-client/HttpClientTrait.php
vendored
|
@ -197,7 +197,13 @@ trait HttpClientTrait
|
|||
if ($resolve = $options['resolve'] ?? false) {
|
||||
$options['resolve'] = [];
|
||||
foreach ($resolve as $k => $v) {
|
||||
$options['resolve'][substr(self::parseUrl('http://'.$k)['authority'], 2)] = (string) $v;
|
||||
if ('' === $v = (string) $v) {
|
||||
$v = null;
|
||||
} elseif ('[' === $v[0] && ']' === substr($v, -1) && str_contains($v, ':')) {
|
||||
$v = substr($v, 1, -1);
|
||||
}
|
||||
|
||||
$options['resolve'][substr(self::parseUrl('http://'.$k)['authority'], 2)] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +226,13 @@ trait HttpClientTrait
|
|||
|
||||
if ($resolve = $defaultOptions['resolve'] ?? false) {
|
||||
foreach ($resolve as $k => $v) {
|
||||
$options['resolve'] += [substr(self::parseUrl('http://'.$k)['authority'], 2) => (string) $v];
|
||||
if ('' === $v = (string) $v) {
|
||||
$v = null;
|
||||
} elseif ('[' === $v[0] && ']' === substr($v, -1) && str_contains($v, ':')) {
|
||||
$v = substr($v, 1, -1);
|
||||
}
|
||||
|
||||
$options['resolve'] += [substr(self::parseUrl('http://'.$k)['authority'], 2) => $v];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -514,29 +526,37 @@ trait HttpClientTrait
|
|||
*/
|
||||
private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array
|
||||
{
|
||||
if (false === $parts = parse_url($url)) {
|
||||
if ('/' !== ($url[0] ?? '') || false === $parts = parse_url($url.'#')) {
|
||||
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
|
||||
}
|
||||
unset($parts['fragment']);
|
||||
$tail = '';
|
||||
|
||||
if (false === $parts = parse_url(\strlen($url) !== strcspn($url, '?#') ? $url : $url.$tail = '#')) {
|
||||
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
|
||||
}
|
||||
|
||||
if ($query) {
|
||||
$parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true);
|
||||
}
|
||||
|
||||
$scheme = $parts['scheme'] ?? null;
|
||||
$host = $parts['host'] ?? null;
|
||||
|
||||
if (!$scheme && $host && !str_starts_with($url, '//')) {
|
||||
$parts = parse_url(':/'.$url.$tail);
|
||||
$parts['path'] = substr($parts['path'], 2);
|
||||
$scheme = $host = null;
|
||||
}
|
||||
|
||||
$port = $parts['port'] ?? 0;
|
||||
|
||||
if (null !== $scheme = $parts['scheme'] ?? null) {
|
||||
if (null !== $scheme) {
|
||||
if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s".', $url));
|
||||
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s": "%s" expected.', $url, implode('" or "', array_keys($allowedSchemes))));
|
||||
}
|
||||
|
||||
$port = $allowedSchemes[$scheme] === $port ? 0 : $port;
|
||||
$scheme .= ':';
|
||||
}
|
||||
|
||||
if (null !== $host = $parts['host'] ?? null) {
|
||||
if (null !== $host) {
|
||||
if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host));
|
||||
}
|
||||
|
@ -564,7 +584,7 @@ trait HttpClientTrait
|
|||
'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null,
|
||||
'path' => isset($parts['path'][0]) ? $parts['path'] : null,
|
||||
'query' => isset($parts['query']) ? '?'.$parts['query'] : null,
|
||||
'fragment' => isset($parts['fragment']) ? '#'.$parts['fragment'] : null,
|
||||
'fragment' => isset($parts['fragment']) && !$tail ? '#'.$parts['fragment'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -80,12 +80,12 @@ class AmpListener implements EventListener
|
|||
public function startSendingRequest(Request $request, Stream $stream): Promise
|
||||
{
|
||||
$host = $stream->getRemoteAddress()->getHost();
|
||||
$this->info['primary_ip'] = $host;
|
||||
|
||||
if (false !== strpos($host, ':')) {
|
||||
$host = '['.$host.']';
|
||||
}
|
||||
|
||||
$this->info['primary_ip'] = $host;
|
||||
$this->info['primary_port'] = $stream->getRemoteAddress()->getPort();
|
||||
$this->info['pretransfer_time'] = microtime(true) - $this->info['start_time'];
|
||||
$this->info['debug'] .= sprintf("* Connected to %s (%s) port %d\n", $request->getUri()->getHost(), $host, $this->info['primary_port']);
|
||||
|
|
|
@ -34,19 +34,31 @@ class AmpResolver implements Dns\Resolver
|
|||
|
||||
public function resolve(string $name, ?int $typeRestriction = null): Promise
|
||||
{
|
||||
if (!isset($this->dnsMap[$name]) || !\in_array($typeRestriction, [Record::A, null], true)) {
|
||||
$recordType = Record::A;
|
||||
$ip = $this->dnsMap[$name] ?? null;
|
||||
|
||||
if (null !== $ip && str_contains($ip, ':')) {
|
||||
$recordType = Record::AAAA;
|
||||
}
|
||||
if (null === $ip || $recordType !== ($typeRestriction ?? $recordType)) {
|
||||
return Dns\resolver()->resolve($name, $typeRestriction);
|
||||
}
|
||||
|
||||
return new Success([new Record($this->dnsMap[$name], Record::A, null)]);
|
||||
return new Success([new Record($ip, $recordType, null)]);
|
||||
}
|
||||
|
||||
public function query(string $name, int $type): Promise
|
||||
{
|
||||
if (!isset($this->dnsMap[$name]) || Record::A !== $type) {
|
||||
$recordType = Record::A;
|
||||
$ip = $this->dnsMap[$name] ?? null;
|
||||
|
||||
if (null !== $ip && str_contains($ip, ':')) {
|
||||
$recordType = Record::AAAA;
|
||||
}
|
||||
if (null === $ip || $recordType !== $type) {
|
||||
return Dns\resolver()->query($name, $type);
|
||||
}
|
||||
|
||||
return new Success([new Record($this->dnsMap[$name], Record::A, null)]);
|
||||
return new Success([new Record($ip, $recordType, null)]);
|
||||
}
|
||||
}
|
||||
|
|
33
vendor/symfony/http-client/NativeHttpClient.php
vendored
33
vendor/symfony/http-client/NativeHttpClient.php
vendored
|
@ -79,6 +79,9 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
if (str_starts_with($options['bindto'], 'host!')) {
|
||||
$options['bindto'] = substr($options['bindto'], 5);
|
||||
}
|
||||
if ((\PHP_VERSION_ID < 80223 || 80300 <= \PHP_VERSION_ID && 80311 < \PHP_VERSION_ID) && '\\' === \DIRECTORY_SEPARATOR && '[' === $options['bindto'][0]) {
|
||||
$options['bindto'] = preg_replace('{^\[[^\]]++\]}', '[$0]', $options['bindto']);
|
||||
}
|
||||
}
|
||||
|
||||
$hasContentLength = isset($options['normalized_headers']['content-length']);
|
||||
|
@ -138,15 +141,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
// Memoize the last progress to ease calling the callback periodically when no network transfer happens
|
||||
$lastProgress = [0, 0];
|
||||
$maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF;
|
||||
$multi = $this->multi;
|
||||
$resolve = static function (string $host, ?string $ip = null) use ($multi): ?string {
|
||||
if (null !== $ip) {
|
||||
$multi->dnsCache[$host] = $ip;
|
||||
}
|
||||
|
||||
return $multi->dnsCache[$host] ?? null;
|
||||
};
|
||||
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration, $resolve) {
|
||||
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) {
|
||||
if ($info['total_time'] >= $maxDuration) {
|
||||
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
|
||||
}
|
||||
|
@ -162,7 +157,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
$lastProgress = $progress ?: $lastProgress;
|
||||
}
|
||||
|
||||
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo, $resolve);
|
||||
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo);
|
||||
};
|
||||
} elseif (0 < $options['max_duration']) {
|
||||
$maxDuration = $options['max_duration'];
|
||||
|
@ -330,7 +325,12 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
*/
|
||||
private static function dnsResolve($host, NativeClientState $multi, array &$info, ?\Closure $onProgress): string
|
||||
{
|
||||
if (null === $ip = $multi->dnsCache[$host] ?? null) {
|
||||
$flag = '' !== $host && '[' === $host[0] && ']' === $host[-1] && str_contains($host, ':') ? \FILTER_FLAG_IPV6 : \FILTER_FLAG_IPV4;
|
||||
$ip = \FILTER_FLAG_IPV6 === $flag ? substr($host, 1, -1) : $host;
|
||||
|
||||
if (filter_var($ip, \FILTER_VALIDATE_IP, $flag)) {
|
||||
// The host is already an IP address
|
||||
} elseif (null === $ip = $multi->dnsCache[$host] ?? null) {
|
||||
$info['debug'] .= "* Hostname was NOT found in DNS cache\n";
|
||||
$now = microtime(true);
|
||||
|
||||
|
@ -338,13 +338,15 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
throw new TransportException(sprintf('Could not resolve host "%s".', $host));
|
||||
}
|
||||
|
||||
$info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now);
|
||||
$multi->dnsCache[$host] = $ip = $ip[0];
|
||||
$info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n";
|
||||
$host = $ip;
|
||||
} else {
|
||||
$info['debug'] .= "* Hostname was found in DNS cache\n";
|
||||
$host = str_contains($ip, ':') ? "[$ip]" : $ip;
|
||||
}
|
||||
|
||||
$info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now);
|
||||
$info['primary_ip'] = $ip;
|
||||
|
||||
if ($onProgress) {
|
||||
|
@ -352,7 +354,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
$onProgress();
|
||||
}
|
||||
|
||||
return $ip;
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -383,13 +385,14 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
|
||||
try {
|
||||
$url = self::parseUrl($location);
|
||||
$locationHasHost = isset($url['authority']);
|
||||
$url = self::resolveUrl($url, $info['url']);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$info['redirect_url'] = null;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = self::resolveUrl($url, $info['url']);
|
||||
$info['redirect_url'] = implode('', $url);
|
||||
|
||||
if ($info['redirect_count'] >= $maxRedirects) {
|
||||
|
@ -424,7 +427,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||
|
||||
[$host, $port] = self::parseHostPort($url, $info);
|
||||
|
||||
if (false !== (parse_url($location.'#', \PHP_URL_HOST) ?? false)) {
|
||||
if ($locationHasHost) {
|
||||
// Authorization and Cookie headers MUST NOT follow except for the initial host name
|
||||
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
$requestHeaders[] = 'Host: '.$host.$port;
|
||||
|
|
|
@ -13,22 +13,25 @@ namespace Symfony\Component\HttpClient;
|
|||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use Symfony\Component\HttpClient\Response\AsyncResponse;
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Decorator that blocks requests to private networks by default.
|
||||
*
|
||||
* @author Hallison Boaventura <hallisonboaventura@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
use AsyncDecoratorTrait;
|
||||
|
||||
private const PRIVATE_SUBNETS = [
|
||||
'127.0.0.0/8',
|
||||
|
@ -45,11 +48,14 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
|
|||
'::/128',
|
||||
];
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
private $client;
|
||||
private $subnets;
|
||||
private $ipFlags;
|
||||
private $dnsCache;
|
||||
|
||||
/**
|
||||
* @param string|array|null $subnets String or array of subnets using CIDR notation that will be used by IpUtils.
|
||||
* @param string|array|null $subnets String or array of subnets using CIDR notation that should be considered private.
|
||||
* If null is passed, the standard private subnets will be used.
|
||||
*/
|
||||
public function __construct(HttpClientInterface $client, $subnets = null)
|
||||
|
@ -62,8 +68,23 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
|
|||
throw new \LogicException(sprintf('You cannot use "%s" if the HttpFoundation component is not installed. Try running "composer require symfony/http-foundation".', __CLASS__));
|
||||
}
|
||||
|
||||
if (null === $subnets) {
|
||||
$ipFlags = \FILTER_FLAG_IPV4 | \FILTER_FLAG_IPV6;
|
||||
} else {
|
||||
$ipFlags = 0;
|
||||
foreach ((array) $subnets as $subnet) {
|
||||
$ipFlags |= str_contains($subnet, ':') ? \FILTER_FLAG_IPV6 : \FILTER_FLAG_IPV4;
|
||||
}
|
||||
}
|
||||
|
||||
if (!\defined('STREAM_PF_INET6')) {
|
||||
$ipFlags &= ~\FILTER_FLAG_IPV6;
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
$this->subnets = $subnets;
|
||||
$this->subnets = null !== $subnets ? (array) $subnets : null;
|
||||
$this->ipFlags = $ipFlags;
|
||||
$this->dnsCache = new \ArrayObject();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,59 +92,91 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
|
|||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$onProgress = $options['on_progress'] ?? null;
|
||||
if (null !== $onProgress && !\is_callable($onProgress)) {
|
||||
throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress)));
|
||||
}
|
||||
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions, true);
|
||||
|
||||
$redirectHeaders = parse_url($url['authority']);
|
||||
$host = $redirectHeaders['host'];
|
||||
$url = implode('', $url);
|
||||
$dnsCache = $this->dnsCache;
|
||||
|
||||
$ip = self::dnsResolve($dnsCache, $host, $this->ipFlags, $options);
|
||||
self::ipCheck($ip, $this->subnets, $this->ipFlags, $host, $url);
|
||||
|
||||
$onProgress = $options['on_progress'] ?? null;
|
||||
$subnets = $this->subnets;
|
||||
$lastUrl = '';
|
||||
$ipFlags = $this->ipFlags;
|
||||
$lastPrimaryIp = '';
|
||||
|
||||
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use ($onProgress, $subnets, &$lastUrl, &$lastPrimaryIp): void {
|
||||
if ($info['url'] !== $lastUrl) {
|
||||
$host = trim(parse_url($info['url'], PHP_URL_HOST) ?: '', '[]');
|
||||
$resolve ??= static fn () => null;
|
||||
|
||||
if (($ip = $host)
|
||||
&& !filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)
|
||||
&& !filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)
|
||||
&& !$ip = $resolve($host)
|
||||
) {
|
||||
if ($ip = @(dns_get_record($host, \DNS_A)[0]['ip'] ?? null)) {
|
||||
$resolve($host, $ip);
|
||||
} elseif ($ip = @(dns_get_record($host, \DNS_AAAA)[0]['ipv6'] ?? null)) {
|
||||
$resolve($host, '['.$ip.']');
|
||||
}
|
||||
}
|
||||
|
||||
if ($ip && IpUtils::checkIp($ip, $subnets ?? self::PRIVATE_SUBNETS)) {
|
||||
throw new TransportException(sprintf('Host "%s" is blocked for "%s".', $host, $info['url']));
|
||||
}
|
||||
|
||||
$lastUrl = $info['url'];
|
||||
}
|
||||
|
||||
if ($info['primary_ip'] !== $lastPrimaryIp) {
|
||||
if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) {
|
||||
throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url']));
|
||||
}
|
||||
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, $ipFlags, &$lastPrimaryIp): void {
|
||||
if (!\in_array($info['primary_ip'] ?? '', ['', $lastPrimaryIp], true)) {
|
||||
self::ipCheck($info['primary_ip'], $subnets, $ipFlags, null, $info['url']);
|
||||
$lastPrimaryIp = $info['primary_ip'];
|
||||
}
|
||||
|
||||
null !== $onProgress && $onProgress($dlNow, $dlSize, $info);
|
||||
};
|
||||
|
||||
return $this->client->request($method, $url, $options);
|
||||
}
|
||||
if (0 >= $maxRedirects = $options['max_redirects']) {
|
||||
return new AsyncResponse($this->client, $method, $url, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
return $this->client->stream($responses, $timeout);
|
||||
$options['max_redirects'] = 0;
|
||||
$redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = $options['headers'];
|
||||
|
||||
if (isset($options['normalized_headers']['host']) || isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Host:') && 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
|
||||
});
|
||||
}
|
||||
|
||||
return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use (&$method, &$options, $maxRedirects, &$redirectHeaders, $subnets, $ipFlags, $dnsCache): \Generator {
|
||||
if (null !== $chunk->getError() || $chunk->isTimeout() || !$chunk->isFirst()) {
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$statusCode = $context->getStatusCode();
|
||||
|
||||
if ($statusCode < 300 || 400 <= $statusCode || null === $url = $context->getInfo('redirect_url')) {
|
||||
$context->passthru();
|
||||
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($url, \PHP_URL_HOST);
|
||||
$ip = self::dnsResolve($dnsCache, $host, $ipFlags, $options);
|
||||
self::ipCheck($ip, $subnets, $ipFlags, $host, $url);
|
||||
|
||||
// Do like curl and browsers: turn POST to GET on 301, 302 and 303
|
||||
if (303 === $statusCode || 'POST' === $method && \in_array($statusCode, [301, 302], true)) {
|
||||
$method = 'HEAD' === $method ? 'HEAD' : 'GET';
|
||||
unset($options['body'], $options['json']);
|
||||
|
||||
if (isset($options['normalized_headers']['content-length']) || isset($options['normalized_headers']['content-type']) || isset($options['normalized_headers']['transfer-encoding'])) {
|
||||
$filterContentHeaders = static function ($h) {
|
||||
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
|
||||
};
|
||||
$options['header'] = array_filter($options['header'], $filterContentHeaders);
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
|
||||
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
// Authorization and Cookie headers MUST NOT follow except for the initial host name
|
||||
$options['headers'] = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
|
||||
static $redirectCount = 0;
|
||||
$context->setInfo('redirect_count', ++$redirectCount);
|
||||
|
||||
$context->replaceRequest($method, $url, $options);
|
||||
|
||||
if ($redirectCount >= $maxRedirects) {
|
||||
$context->passthru();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,14 +196,73 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
|
|||
{
|
||||
$clone = clone $this;
|
||||
$clone->client = $this->client->withOptions($options);
|
||||
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->dnsCache->exchangeArray([]);
|
||||
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
private static function dnsResolve(\ArrayObject $dnsCache, string $host, int $ipFlags, array &$options): string
|
||||
{
|
||||
if ($ip = filter_var(trim($host, '[]'), \FILTER_VALIDATE_IP) ?: $options['resolve'][$host] ?? false) {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
if ($dnsCache->offsetExists($host)) {
|
||||
return $dnsCache[$host];
|
||||
}
|
||||
|
||||
if ((\FILTER_FLAG_IPV4 & $ipFlags) && $ip = gethostbynamel($host)) {
|
||||
return $options['resolve'][$host] = $dnsCache[$host] = $ip[0];
|
||||
}
|
||||
|
||||
if (!(\FILTER_FLAG_IPV6 & $ipFlags)) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
if ($ip = dns_get_record($host, \DNS_AAAA)) {
|
||||
$ip = $ip[0]['ipv6'];
|
||||
} elseif (extension_loaded('sockets')) {
|
||||
if (!$info = socket_addrinfo_lookup($host, 0, ['ai_socktype' => \SOCK_STREAM, 'ai_family' => \AF_INET6])) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
$ip = socket_addrinfo_explain($info[0])['ai_addr']['sin6_addr'];
|
||||
} elseif ('localhost' === $host || 'localhost.' === $host) {
|
||||
$ip = '::1';
|
||||
} else {
|
||||
return $host;
|
||||
}
|
||||
|
||||
return $options['resolve'][$host] = $dnsCache[$host] = $ip;
|
||||
}
|
||||
|
||||
private static function ipCheck(string $ip, ?array $subnets, int $ipFlags, ?string $host, string $url): void
|
||||
{
|
||||
if (null === $subnets) {
|
||||
// Quick check, but not reliable enough, see https://github.com/php/php-src/issues/16944
|
||||
$ipFlags |= \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
|
||||
}
|
||||
|
||||
if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $ipFlags) && !IpUtils::checkIp($ip, $subnets ?? self::PRIVATE_SUBNETS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $host) {
|
||||
$type = 'Host';
|
||||
} else {
|
||||
$host = $ip;
|
||||
$type = 'IP';
|
||||
}
|
||||
|
||||
throw new TransportException($type.\sprintf(' "%s" is blocked for "%s".', $host, $url));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,17 +89,10 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
|
|||
$info['max_duration'] = $options['max_duration'];
|
||||
$info['debug'] = '';
|
||||
|
||||
$resolve = static function (string $host, ?string $ip = null) use ($multi): ?string {
|
||||
if (null !== $ip) {
|
||||
$multi->dnsCache[$host] = $ip;
|
||||
}
|
||||
|
||||
return $multi->dnsCache[$host] ?? null;
|
||||
};
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
$onProgress = $this->onProgress = static function () use (&$info, $onProgress, $resolve) {
|
||||
$onProgress = $this->onProgress = static function () use (&$info, $onProgress) {
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info, $resolve);
|
||||
$onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info);
|
||||
};
|
||||
|
||||
$pauseDeferred = new Deferred();
|
||||
|
|
|
@ -156,8 +156,8 @@ final class AsyncContext
|
|||
$this->info['previous_info'][] = $info = $this->response->getInfo();
|
||||
if (null !== $onProgress = $options['on_progress'] ?? null) {
|
||||
$thisInfo = &$this->info;
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info, $resolve);
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info);
|
||||
};
|
||||
}
|
||||
if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) {
|
||||
|
|
|
@ -51,8 +51,8 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
|
|||
|
||||
if (null !== $onProgress = $options['on_progress'] ?? null) {
|
||||
$thisInfo = &$this->info;
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info, $resolve);
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info);
|
||||
};
|
||||
}
|
||||
$this->response = $client->request($method, $url, ['buffer' => false] + $options);
|
||||
|
@ -117,11 +117,20 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
|
|||
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if ('debug' === ($type ?? 'debug')) {
|
||||
$debug = implode('', array_column($this->info['previous_info'] ?? [], 'debug'));
|
||||
$debug .= $this->response->getInfo('debug');
|
||||
|
||||
if ('debug' === $type) {
|
||||
return $debug;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $type) {
|
||||
return $this->info[$type] ?? $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
return $this->info + $this->response->getInfo();
|
||||
return array_merge($this->info + $this->response->getInfo(), ['debug' => $debug]);
|
||||
}
|
||||
|
||||
public function toStream(bool $throw = true)
|
||||
|
@ -249,6 +258,7 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
|
|||
return;
|
||||
}
|
||||
|
||||
$chunk = null;
|
||||
foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
|
||||
$r = $asyncMap[$response];
|
||||
|
||||
|
@ -291,6 +301,9 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
|
|||
}
|
||||
}
|
||||
|
||||
if (null === $chunk) {
|
||||
throw new \LogicException(\sprintf('"%s" is not compliant with HttpClientInterface: its "stream()" method didn\'t yield any chunks when it should have.', get_debug_type($client)));
|
||||
}
|
||||
if (null === $chunk->getError() && $chunk->isLast()) {
|
||||
$r->yieldedState = self::LAST_CHUNK_YIELDED;
|
||||
}
|
||||
|
|
|
@ -115,20 +115,13 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
|
|||
curl_pause($ch, \CURLPAUSE_CONT);
|
||||
|
||||
if ($onProgress = $options['on_progress']) {
|
||||
$resolve = static function (string $host, ?string $ip = null) use ($multi): ?string {
|
||||
if (null !== $ip) {
|
||||
$multi->dnsCache->hostnames[$host] = $ip;
|
||||
}
|
||||
|
||||
return $multi->dnsCache->hostnames[$host] ?? null;
|
||||
};
|
||||
$url = isset($info['url']) ? ['url' => $info['url']] : [];
|
||||
curl_setopt($ch, \CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer, $resolve) {
|
||||
curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) {
|
||||
try {
|
||||
rewind($debugBuffer);
|
||||
$debug = ['debug' => stream_get_contents($debugBuffer)];
|
||||
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug, $resolve);
|
||||
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug);
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[(int) $ch][] = null;
|
||||
$multi->handlesActivity[(int) $ch][] = $e;
|
||||
|
@ -327,7 +320,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
|
|||
}
|
||||
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
|
||||
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) || (curl_error($ch) === 'OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 0' && -1.0 === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) && \in_array('close', array_map('strtolower', $responses[$id]->headers['connection']), true)) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
|
||||
}
|
||||
} finally {
|
||||
$multi->performing = false;
|
||||
|
@ -441,15 +434,6 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
|
|||
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
|
||||
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
|
||||
} else {
|
||||
$url = parse_url($location ?? ':');
|
||||
|
||||
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
|
||||
// Populate DNS cache for redirects if needed
|
||||
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443);
|
||||
curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
|
||||
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,11 +58,11 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
|
|||
$content = false;
|
||||
}
|
||||
|
||||
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info, ?\Closure $resolve = null) use (&$traceInfo, $onProgress) {
|
||||
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) {
|
||||
$traceInfo = $info;
|
||||
|
||||
if (null !== $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $info, $resolve);
|
||||
$onProgress($dlNow, $dlSize, $info);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
2
vendor/symfony/http-client/composer.json
vendored
2
vendor/symfony/http-client/composer.json
vendored
|
@ -25,7 +25,7 @@
|
|||
"php": ">=7.2.5",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/deprecation-contracts": "^2.1|^3",
|
||||
"symfony/http-client-contracts": "^2.5.3",
|
||||
"symfony/http-client-contracts": "^2.5.4",
|
||||
"symfony/polyfill-php73": "^1.11",
|
||||
"symfony/polyfill-php80": "^1.16",
|
||||
"symfony/service-contracts": "^1.0|^2|^3"
|
||||
|
|
3
vendor/symfony/service-contracts/.gitignore
vendored
3
vendor/symfony/service-contracts/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
|
@ -1,3 +0,0 @@
|
|||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
Loading…
Add table
Add a link
Reference in a new issue