From e06ef77b76ec86b1613e20c1ea91807681080199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Tue, 9 Jul 2024 21:44:12 -0400 Subject: [PATCH 1/6] Refactor the loader to use Guzzle for asynchronicity --- composer.json | 3 +- src/Loader.php | 94 ++++++++++++++------------ src/TufValidatedComposerRepository.php | 5 +- tests/ComposerCommandsTest.php | 4 +- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/composer.json b/composer.json index 35eceff..c61ab1e 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "require": { "composer-plugin-api": "^2.6", "php-tuf/php-tuf": "0.1.6", - "guzzlehttp/psr7": "^2.4" + "guzzlehttp/psr7": "^2.4", + "guzzlehttp/guzzle": "^7.8" }, "autoload": { "psr-4": { diff --git a/src/Loader.php b/src/Loader.php index 914f644..583b200 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -2,14 +2,17 @@ namespace Tuf\ComposerIntegration; -use Composer\Downloader\MaxFileSizeExceededException; -use Composer\Downloader\TransportException; use Composer\InstalledVersions; use Composer\IO\IOInterface; -use Composer\Util\HttpDownloader; +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Promise\Create; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Utils; +use GuzzleHttp\RequestOptions; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; use Tuf\Exception\DownloadSizeException; use Tuf\Exception\NotFoundException; use Tuf\Loader\LoaderInterface; @@ -24,36 +27,48 @@ class Loader implements LoaderInterface */ private array $cache = []; + private readonly ClientInterface $client; + public function __construct( - private HttpDownloader $downloader, - private ComposerFileStorage $storage, - private IOInterface $io, - private string $baseUrl = '' - ) {} + private readonly ComposerFileStorage $storage, + private readonly IOInterface $io, + string $baseUrl = '' + ) { + $options = []; + if ($baseUrl) { + $options['base_uri'] = $baseUrl; + } + $this->client = new Client($options); + } /** * {@inheritDoc} */ public function load(string $locator, int $maxBytes): PromiseInterface { - $url = $this->baseUrl . $locator; - if (array_key_exists($url, $this->cache)) { - $this->io->debug("[TUF] Loading $url from static cache."); + if (array_key_exists($locator, $this->cache)) { + $this->io->debug("[TUF] Loading '$locator' from static cache."); - $cachedStream = $this->cache[$url]; + $cachedStream = $this->cache[$locator]; // The underlying stream should always be seekable. assert($cachedStream->isSeekable()); $cachedStream->rewind(); return Create::promiseFor($cachedStream); } - $options = [ + $options = []; + // Try to enforce the maximum download size during transport. This will only have an effect + // if cURL is in use. + $options[RequestOptions::PROGRESS] = function (int $expectedBytes, int $bytesSoFar) use ($locator, $maxBytes): void + { // Add 1 to $maxBytes to work around a bug in Composer. // @see \Tuf\ComposerIntegration\ComposerCompatibleUpdater::getLength() - 'max_file_size' => $maxBytes + 1, - ]; + if ($bytesSoFar > $maxBytes + 1) { + throw new DownloadSizeException("$locator exceeded $maxBytes bytes"); + } + }; // Always send a X-PHP-TUF header with version information. - $options['http']['header'][] = self::versionHeader(); + $options[RequestOptions::HEADERS]['X-PHP-TUF'] = self::versionHeader(); // The name of the file in persistent storage will differ from $locator. $name = basename($locator, '.json'); @@ -62,37 +77,32 @@ public function load(string $locator, int $maxBytes): PromiseInterface $modifiedTime = $this->storage->getModifiedTime($name); if ($modifiedTime) { // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since. - $options['http']['header'][] = 'If-Modified-Since: ' . $modifiedTime->format('D, d M Y H:i:s') . ' GMT'; + $options[RequestOptions::HEADERS]['If-Modified-Since'] = $modifiedTime->format('D, d M Y H:i:s') . ' GMT'; } - try { - $response = $this->downloader->get($url, $options); - } catch (TransportException $e) { - if ($e->getStatusCode() === 404) { - throw new NotFoundException($locator); - } elseif ($e instanceof MaxFileSizeExceededException) { - throw new DownloadSizeException("$locator exceeded $maxBytes bytes"); + $onSuccess = function (ResponseInterface $response) use ($name, $locator): StreamInterface { + $status = $response->getStatusCode(); + $this->io->debug("[TUF] $status: '$locator'"); + + // If we sent an If-Modified-Since header and received a 304 (Not Modified) + // response, we can just load the file from cache. + if ($status === 304) { + $content = Utils::tryFopen($this->storage->toPath($name), 'r'); + $stream = Utils::streamFor($content); } else { - throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + $stream = $response->getBody(); } - } - - // If we sent an If-Modified-Since header and received a 304 (Not Modified) - // response, we can just load the file from cache. - if ($response->getStatusCode() === 304) { - $content = Utils::tryFopen($this->storage->toPath($name), 'r'); - } else { - // To prevent the static cache from running out of memory, write the response - // contents to a temporary stream (which will turn into a temporary file once - // once we've written 1024 bytes to it), which will be automatically cleaned - // up when it is garbage collected. - $content = Utils::tryFopen('php://temp/maxmemory:1024', 'r+'); - fwrite($content, $response->getBody()); - } + $this->cache[$locator] = $stream; + return $stream; + }; + $onFailure = function (\Throwable $e) use ($locator): never { + if ($e instanceof ClientException && $e->getCode() === 404) { + throw new NotFoundException($locator); + } + throw $e; + }; - $stream = $this->cache[$url] = Utils::streamFor($content); - $stream->rewind(); - return Create::promiseFor($stream); + return $this->client->getAsync($locator, $options)->then($onSuccess, $onFailure); } private static function versionHeader(): string diff --git a/src/TufValidatedComposerRepository.php b/src/TufValidatedComposerRepository.php index 09e162d..6c00030 100644 --- a/src/TufValidatedComposerRepository.php +++ b/src/TufValidatedComposerRepository.php @@ -55,9 +55,6 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, if (!empty($repoConfig['tuf'])) { // TUF metadata can optionally be loaded from a different place than the Composer package metadata. $metadataUrl = $repoConfig['tuf']['metadata-url'] ?? "$url/metadata/"; - if (!str_ends_with($metadataUrl, '/')) { - $metadataUrl .= '/'; - } $maxBytes = $repoConfig['tuf']['max-bytes'] ?? NULL; if (is_int($maxBytes)) { @@ -66,7 +63,7 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, // @todo: Write a custom implementation of FileStorage that stores repo keys to user's global composer cache? $storage = $this->initializeStorage($url, $config); - $loader = new Loader($httpDownloader, $storage, $io, $metadataUrl); + $loader = new Loader($storage, $io, $metadataUrl); $loader = new SizeCheckingLoader($loader); $this->updater = new ComposerCompatibleUpdater($loader, $storage); diff --git a/tests/ComposerCommandsTest.php b/tests/ComposerCommandsTest.php index 2522a55..2907e91 100644 --- a/tests/ComposerCommandsTest.php +++ b/tests/ComposerCommandsTest.php @@ -75,8 +75,8 @@ public function testRequireAndRemove(): void // Even though we are searching delegated roles for multiple targets, we should see the TUF metadata // loaded from the static cache. - $this->assertStringContainsString('[TUF] Loading http://localhost:8080/metadata/1.package_metadata.json from static cache.', $debug); - $this->assertStringContainsString('[TUF] Loading http://localhost:8080/metadata/1.package.json from static cache.', $debug); + $this->assertStringContainsString("[TUF] Loading '1.package_metadata.json' from static cache.", $debug); + $this->assertStringContainsString("[TUF] Loading '1.package.json' from static cache.", $debug); // The metadata should actually be *downloaded* no more than twice -- once while the // dependency tree is being solved, and again when the solved dependencies are actually // downloaded (which is done by Composer effectively re-invoking itself, resulting in From 05f1ebff13d54de5f702c03baad76d1910385bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Tue, 9 Jul 2024 22:41:32 -0400 Subject: [PATCH 2/6] Make LoaderTest pass --- composer.json | 3 +- src/Loader.php | 7 +- tests/LoaderTest.php | 164 +++++++++++++++---------------------------- 3 files changed, 63 insertions(+), 111 deletions(-) diff --git a/composer.json b/composer.json index c61ab1e..df552a5 100644 --- a/composer.json +++ b/composer.json @@ -37,8 +37,7 @@ "require-dev": { "composer/composer": "^2.1", "phpunit/phpunit": "^9.5", - "symfony/process": "^6", - "dms/phpunit-arraysubset-asserts": "^0.5.0" + "symfony/process": "^6" }, "scripts": { "test": [ diff --git a/src/Loader.php b/src/Loader.php index 583b200..bf02f88 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -32,13 +32,14 @@ class Loader implements LoaderInterface public function __construct( private readonly ComposerFileStorage $storage, private readonly IOInterface $io, - string $baseUrl = '' + string $baseUrl = '', + ?ClientInterface $client = null, ) { $options = []; if ($baseUrl) { $options['base_uri'] = $baseUrl; } - $this->client = new Client($options); + $this->client = $client ?? new Client($options); } /** @@ -108,7 +109,7 @@ public function load(string $locator, int $maxBytes): PromiseInterface private static function versionHeader(): string { return sprintf( - 'X-PHP-TUF: client=%s; plugin=%s', + 'client=%s; plugin=%s', InstalledVersions::getVersion('php-tuf/php-tuf'), InstalledVersions::getVersion('php-tuf/composer-integration'), ); diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index d7f42f7..7b069f8 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -3,17 +3,16 @@ namespace Tuf\ComposerIntegration\Tests; use Composer\Config; -use Composer\Downloader\MaxFileSizeExceededException; -use Composer\Downloader\TransportException; -use Composer\IO\IOInterface; -use Composer\Util\Http\Response; -use Composer\Util\HttpDownloader; -use DMS\PHPUnitExtensions\ArraySubset\Constraint\ArraySubset; +use Composer\IO\NullIO; +use GuzzleHttp\Client; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\RequestOptions; use PHPUnit\Framework\TestCase; use Psr\Http\Message\StreamInterface; use Tuf\ComposerIntegration\ComposerFileStorage; use Tuf\ComposerIntegration\Loader; -use Tuf\Exception\DownloadSizeException; use Tuf\Exception\NotFoundException; /** @@ -21,121 +20,72 @@ */ class LoaderTest extends TestCase { - public function testLoader(): void + private readonly MockHandler $responses; + + /** + * {@inheritDoc} + */ + protected function setUp(): void { - $loader = function (HttpDownloader $downloader): Loader { - return new Loader( - $downloader, - $this->createMock(ComposerFileStorage::class), - $this->createMock(IOInterface::class), - '/metadata/' - ); - }; - - $url = '/metadata/root.json'; - $downloader = $this->createMock(HttpDownloader::class); - $downloader->expects($this->atLeastOnce()) - ->method('get') - ->with($url, $this->mockOptions(129)) - ->willReturn(new Response(['url' => $url], 200, [], '')); - $this->assertInstanceOf(StreamInterface::class, $loader($downloader)->load('root.json', 128)->wait()); - - // Any TransportException with a 404 error could should be converted - // into a NotFoundException. - $exception = new TransportException(); - $exception->setStatusCode(404); - $downloader = $this->createMock(HttpDownloader::class); - $downloader->expects($this->atLeastOnce()) - ->method('get') - ->with('/metadata/bogus.txt', $this->mockOptions(11)) - ->willThrowException($exception); + parent::setUp(); + $this->responses = new MockHandler(); + } + + private function getLoader(?ComposerFileStorage $storage = null): Loader + { + return new Loader( + $storage ?? $this->createMock(ComposerFileStorage::class), + new NullIO(), + '/metadata/', + new Client([ + 'handler' => HandlerStack::create($this->responses), + ]), + ); + } + + public function testBasicSuccessAndFailure(): void + { + $loader = $this->getLoader(); + + $this->responses->append(new Response()); + $this->assertInstanceOf(StreamInterface::class, $loader->load('root.json', 128)->wait()); + $this->assertRequestOptions(); + + // Any 404 response should raise a NotFoundException. + $this->responses->append(new Response(404)); try { - $loader($downloader)->load('bogus.txt', 10); + $loader->load('bogus.txt', 10)->wait(); $this->fail('Expected a NotFoundException, but none was thrown.'); } catch (NotFoundException $e) { $this->assertSame('Item not found: bogus.txt', $e->getMessage()); - } - - // A MaxFileSizeExceededException should be converted into a - // DownloadSizeException. - $downloader = $this->createMock(HttpDownloader::class); - $downloader->expects($this->atLeastOnce()) - ->method('get') - ->with('/metadata/too_big.txt', $this->mockOptions(11)) - ->willThrowException(new MaxFileSizeExceededException()); - try { - $loader($downloader)->load('too_big.txt', 10); - $this->fail('Expected a DownloadSizeException, but none was thrown.'); - } catch (DownloadSizeException $e) { - $this->assertSame('too_big.txt exceeded 10 bytes', $e->getMessage()); - } - - // Any other TransportException should be wrapped in a - // \RuntimeException. - $originalException = new TransportException('Whiskey Tango Foxtrot', -32); - $downloader = $this->createMock(HttpDownloader::class); - $downloader->expects($this->atLeastOnce()) - ->method('get') - ->with('/metadata/wtf.txt', $this->mockOptions(11)) - ->willThrowException($originalException); - try { - $loader($downloader)->load('wtf.txt', 10); - $this->fail('Expected a RuntimeException, but none was thrown.'); - } catch (\RuntimeException $e) { - $this->assertSame($originalException->getMessage(), $e->getMessage()); - $this->assertSame($originalException->getCode(), $e->getCode()); - $this->assertSame($originalException, $e->getPrevious()); + $this->assertRequestOptions(); } } public function testNotModifiedResponse(): void { - $config = new Config(); - $storage = ComposerFileStorage::create('https://example.net/packages', $config); + $storage = ComposerFileStorage::create('https://example.net/packages', new Config()); $method = new \ReflectionMethod($storage, 'write'); - $method->setAccessible(true); $method->invoke($storage, 'test', 'Some test data.'); - $modifiedTime = $storage->getModifiedTime('test'); - - $downloader = $this->createMock(HttpDownloader::class); - $url = '2.test.json'; - $response = $this->createMock(Response::class); - $response->expects($this->atLeastOnce()) - ->method('getStatusCode') - ->willReturn(304); - $response->expects($this->never()) - ->method('getBody'); - $downloader->expects($this->atLeastOnce()) - ->method('get') - ->with($url, $this->mockOptions(1025, $modifiedTime)) - ->willReturn($response); - - $loader = new Loader($downloader, $storage, $this->createMock(IOInterface::class)); + + $this->responses->append(new Response(304)); // Since the response has no actual body data, the fact that we get the contents // of the file we wrote here is proof that it was ultimately read from persistent // storage by the loader. - $this->assertSame('Some test data.', $loader->load('2.test.json', 1024)->wait()->getContents()); + $this->assertSame('Some test data.', $this->getLoader($storage)->load('2.test.json', 1024)->wait()->getContents()); + $this->assertRequestOptions($storage->getModifiedTime('test')); } public function testStaticCache(): void { - $response = $this->createMock(Response::class); - $response->expects($this->any()) - ->method('getStatusCode') - ->willReturn(200); - $response->expects($this->any()) - ->method('getBody') - ->willReturn('Truly, this is amazing stuff.'); - - $downloader = $this->createMock(HttpDownloader::class); - $downloader->expects($this->once()) - ->method('get') - ->with('foo.txt', $this->mockOptions(1025)) - ->willReturn($response); - - $loader = new Loader($downloader, $this->createMock(ComposerFileStorage::class), $this->createMock(IOInterface::class)); + $loader = $this->getLoader(); + + $this->responses->append( + new Response(body: 'Truly, this is amazing stuff.'), + ); $stream = $loader->load('foo.txt', 1024)->wait(); + $this->assertRequestOptions(); // We should be at the beginning of the stream. $this->assertSame(0, $stream->tell()); @@ -148,18 +98,20 @@ public function testStaticCache(): void $this->assertSame(0, $stream->tell()); } - private function mockOptions(int $expectedSize, ?\DateTimeInterface $modifiedTime = null): object + private function assertRequestOptions(?\DateTimeInterface $modifiedTime = null): void { - $options = ['max_file_size' => $expectedSize]; + $options = $this->responses->getLastOptions(); + $this->assertIsCallable($options[RequestOptions::PROGRESS]); + + $request = $this->responses->getLastRequest(); // There's no real reason to expose versionHeader() to the world, so // it's okay to use reflection here. $method = new \ReflectionMethod(Loader::class, 'versionHeader'); - $options['http']['header'][] = $method->invoke(null); + $this->assertSame($request?->getHeaderLine('X-PHP-TUF'), $method->invoke(null)); if ($modifiedTime) { - $options['http']['header'][] = "If-Modified-Since: " . $modifiedTime->format('D, d M Y H:i:s') . ' GMT'; + $this->assertSame($request?->getHeaderLine('If-Modified-Since'), $modifiedTime->format('D, d M Y H:i:s') . ' GMT'); } - return new ArraySubset($options); } } From 6f297015c7fbf2fadff20bf37d82daa8576539b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Tue, 9 Jul 2024 23:01:13 -0400 Subject: [PATCH 3/6] Always pass Client to loader --- src/Loader.php | 13 ++----------- src/TufValidatedComposerRepository.php | 10 ++++++---- tests/LoaderTest.php | 1 - 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Loader.php b/src/Loader.php index bf02f88..ace4ced 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -27,20 +27,11 @@ class Loader implements LoaderInterface */ private array $cache = []; - private readonly ClientInterface $client; - public function __construct( private readonly ComposerFileStorage $storage, private readonly IOInterface $io, - string $baseUrl = '', - ?ClientInterface $client = null, - ) { - $options = []; - if ($baseUrl) { - $options['base_uri'] = $baseUrl; - } - $this->client = $client ?? new Client($options); - } + private readonly ClientInterface $client, + ) {} /** * {@inheritDoc} diff --git a/src/TufValidatedComposerRepository.php b/src/TufValidatedComposerRepository.php index 6c00030..ae0f74f 100644 --- a/src/TufValidatedComposerRepository.php +++ b/src/TufValidatedComposerRepository.php @@ -10,6 +10,7 @@ use Composer\Repository\ComposerRepository; use Composer\Util\Http\Response; use Composer\Util\HttpDownloader; +use GuzzleHttp\Client; use GuzzleHttp\Psr7\Utils; use Tuf\Client\Repository; use Tuf\Exception\NotFoundException; @@ -53,9 +54,6 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, $url = rtrim($repoConfig['url'], '/'); if (!empty($repoConfig['tuf'])) { - // TUF metadata can optionally be loaded from a different place than the Composer package metadata. - $metadataUrl = $repoConfig['tuf']['metadata-url'] ?? "$url/metadata/"; - $maxBytes = $repoConfig['tuf']['max-bytes'] ?? NULL; if (is_int($maxBytes)) { Repository::$maxBytes = $maxBytes; @@ -63,7 +61,11 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, // @todo: Write a custom implementation of FileStorage that stores repo keys to user's global composer cache? $storage = $this->initializeStorage($url, $config); - $loader = new Loader($storage, $io, $metadataUrl); + + // TUF metadata can optionally be loaded from a different place than the Composer package metadata. + $metadataUrl = $repoConfig['tuf']['metadata-url'] ?? "$url/metadata/"; + $client = new Client(['base_uri' => $metadataUrl]); + $loader = new Loader($storage, $io, $client); $loader = new SizeCheckingLoader($loader); $this->updater = new ComposerCompatibleUpdater($loader, $storage); diff --git a/tests/LoaderTest.php b/tests/LoaderTest.php index 7b069f8..8ab3c70 100644 --- a/tests/LoaderTest.php +++ b/tests/LoaderTest.php @@ -36,7 +36,6 @@ private function getLoader(?ComposerFileStorage $storage = null): Loader return new Loader( $storage ?? $this->createMock(ComposerFileStorage::class), new NullIO(), - '/metadata/', new Client([ 'handler' => HandlerStack::create($this->responses), ]), From 259986219e32f72a1d209ed7ede0d9ad824a3714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 17 Jul 2024 16:30:15 -0400 Subject: [PATCH 4/6] Adjust test expectation --- tests/ComposerCommandsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ComposerCommandsTest.php b/tests/ComposerCommandsTest.php index a9d3923..703359a 100644 --- a/tests/ComposerCommandsTest.php +++ b/tests/ComposerCommandsTest.php @@ -81,7 +81,7 @@ public function testRequireAndRemove(): void $this->assertStringContainsString("[TUF] Loading '1.package_metadata.json' from static cache.", $debug); $this->assertStringContainsString("[TUF] Loading '1.package.json' from static cache.", $debug); // ...which should preclude any "not modified" responses. - $this->assertStringNotContainsString('[304] http://localhost:8080/', $debug); + $this->assertStringNotContainsString('[TUF] 304:', $debug); // The metadata should actually be *downloaded* no more than twice -- once while the // dependency tree is being solved, and again when the solved dependencies are actually // downloaded (which is done by Composer effectively re-invoking itself, resulting in From ca4554238fc716bbf0c7ad7ba452917ab7a8969f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Fri, 19 Jul 2024 12:16:41 -0400 Subject: [PATCH 5/6] Stream request --- src/Loader.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Loader.php b/src/Loader.php index 5875a47..65ed277 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -31,9 +31,13 @@ public function __construct( */ public function load(string $locator, int $maxBytes): PromiseInterface { - $options = []; + $options = [ + // @see https://docs.guzzlephp.org/en/stable/request-options.html#stream + RequestOptions::STREAM => true, + ]; // Try to enforce the maximum download size during transport. This will only have an effect // if cURL is in use. + // @see https://docs.guzzlephp.org/en/stable/request-options.html#progress $options[RequestOptions::PROGRESS] = function (int $expectedBytes, int $bytesSoFar) use ($locator, $maxBytes): void { // Add 1 to $maxBytes to work around a bug in Composer. @@ -43,6 +47,7 @@ public function load(string $locator, int $maxBytes): PromiseInterface } }; // Always send a X-PHP-TUF header with version information. + // @see https://docs.guzzlephp.org/en/stable/request-options.html#headers $options[RequestOptions::HEADERS]['X-PHP-TUF'] = self::versionHeader(); // The name of the file in persistent storage will differ from $locator. @@ -52,6 +57,7 @@ public function load(string $locator, int $maxBytes): PromiseInterface $modifiedTime = $this->storage->getModifiedTime($name); if ($modifiedTime) { // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since. + // @see https://docs.guzzlephp.org/en/stable/request-options.html#headers $options[RequestOptions::HEADERS]['If-Modified-Since'] = $modifiedTime->format('D, d M Y H:i:s') . ' GMT'; } From d9bf8e6bc9a6aba5ad0225fb4e23bb322efb1b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Tue, 23 Jul 2024 12:58:17 -0400 Subject: [PATCH 6/6] Revert "Stream request" This reverts commit ca4554238fc716bbf0c7ad7ba452917ab7a8969f. --- src/Loader.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Loader.php b/src/Loader.php index 65ed277..5875a47 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -31,13 +31,9 @@ public function __construct( */ public function load(string $locator, int $maxBytes): PromiseInterface { - $options = [ - // @see https://docs.guzzlephp.org/en/stable/request-options.html#stream - RequestOptions::STREAM => true, - ]; + $options = []; // Try to enforce the maximum download size during transport. This will only have an effect // if cURL is in use. - // @see https://docs.guzzlephp.org/en/stable/request-options.html#progress $options[RequestOptions::PROGRESS] = function (int $expectedBytes, int $bytesSoFar) use ($locator, $maxBytes): void { // Add 1 to $maxBytes to work around a bug in Composer. @@ -47,7 +43,6 @@ public function load(string $locator, int $maxBytes): PromiseInterface } }; // Always send a X-PHP-TUF header with version information. - // @see https://docs.guzzlephp.org/en/stable/request-options.html#headers $options[RequestOptions::HEADERS]['X-PHP-TUF'] = self::versionHeader(); // The name of the file in persistent storage will differ from $locator. @@ -57,7 +52,6 @@ public function load(string $locator, int $maxBytes): PromiseInterface $modifiedTime = $this->storage->getModifiedTime($name); if ($modifiedTime) { // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since. - // @see https://docs.guzzlephp.org/en/stable/request-options.html#headers $options[RequestOptions::HEADERS]['If-Modified-Since'] = $modifiedTime->format('D, d M Y H:i:s') . ' GMT'; }