From 1bb87babe62a485e4d57d025e4b29e84f69e47d6 Mon Sep 17 00:00:00 2001 From: Chauncey McAskill Date: Thu, 15 Oct 2020 16:11:16 -0400 Subject: [PATCH 1/2] Fix availability of $urls in FileDownloader Fixed: - Ensure manipulations to the first element of $urls in the $download callback are available in $accept and $reject --- src/Composer/Downloader/FileDownloader.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 45c977bf4..ece83a104 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -142,6 +142,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $reject = null; $download = function () use ($io, $output, $httpDownloader, $cache, $cacheKeyGenerator, $eventDispatcher, $package, $fileName, &$urls, &$accept, &$reject) { $url = reset($urls); + $index = key($urls); if ($eventDispatcher) { $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed'], 'package', $package); @@ -154,6 +155,8 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $url['processed'] = $preFileDownloadEvent->getProcessedUrl(); } + $urls[$index] = $url; + $checksum = $package->getDistSha1Checksum(); $cacheKey = $url['cacheKey']; From fcc072fdb6fa5d0332ab20c0750a9deb0f436bc0 Mon Sep 17 00:00:00 2001 From: Chauncey McAskill Date: Thu, 15 Oct 2020 23:42:11 -0400 Subject: [PATCH 2/2] Add test to check processed URL and cache key --- .../Test/Downloader/FileDownloaderTest.php | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index d419cb1b1..0063d1f14 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -12,7 +12,11 @@ namespace Composer\Test\Downloader; +use Composer\Config; use Composer\Downloader\FileDownloader; +use Composer\EventDispatcher\EventDispatcher; +use Composer\Plugin\PluginEvents; +use Composer\Plugin\PreFileDownloadEvent; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Http\Response; @@ -156,6 +160,193 @@ class FileDownloaderTest extends TestCase } } + public function testDownloadWithCustomProcessedUrl() + { + $self = $this; + + $path = $this->getUniqueTmpDirectory(); + $config = new Config(false, $path); + + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $packageMock->expects($this->any()) + ->method('getDistUrl') + ->will($this->returnValue('url')); + $packageMock->expects($this->any()) + ->method('getDistUrls') + ->will($this->returnValue(array('url'))); + + $rootPackageMock = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $rootPackageMock->expects($this->any()) + ->method('getScripts') + ->will($this->returnValue(array())); + + $composerMock = $this->getMockBuilder('Composer\Composer')->getMock(); + $composerMock->expects($this->any()) + ->method('getPackage') + ->will($this->returnValue($rootPackageMock)); + $composerMock->expects($this->any()) + ->method('getConfig') + ->will($this->returnValue($config)); + + $expectedUrl = 'foobar'; + $expectedCacheKey = '/'.sha1($expectedUrl).'.'; + + $dispatcher = new EventDispatcher( + $composerMock, + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock() + ); + $dispatcher->addListener(PluginEvents::PRE_FILE_DOWNLOAD, function ( PreFileDownloadEvent $event ) use ($expectedUrl) { + $event->setProcessedUrl($expectedUrl); + }); + + $cacheMock = $this->getMockBuilder('Composer\Cache') + ->disableOriginalConstructor() + ->getMock(); + $cacheMock + ->expects($this->any()) + ->method('copyTo') + ->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { + $self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); + + return false; + })); + $cacheMock + ->expects($this->any()) + ->method('copyFrom') + ->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { + $self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); + + return false; + })); + + $httpDownloaderMock = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); + $httpDownloaderMock + ->expects($this->any()) + ->method('addCopy') + ->will($this->returnCallback(function ($url) use ($self, $expectedUrl) { + $self->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); + + return \React\Promise\resolve( + new Response(array('url' => 'http://example.org/'), 200, array(), 'file~') + ); + })); + + $downloader = $this->getDownloader(null, $config, $dispatcher, $cacheMock, $httpDownloaderMock); + + try { + $loop = new Loop($this->httpDownloader); + $promise = $downloader->download($packageMock, $path); + $loop->wait(array($promise)); + + $this->fail('Download was expected to throw'); + } catch (\Exception $e) { + if (is_dir($path)) { + $fs = new Filesystem(); + $fs->removeDirectory($path); + } elseif (is_file($path)) { + unlink($path); + } + + $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); + $this->assertStringContainsString('could not be saved to', $e->getMessage()); + } + } + + public function testDownloadWithCustomCacheKey() + { + $self = $this; + + $path = $this->getUniqueTmpDirectory(); + $config = new Config(false, $path); + + $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); + $packageMock->expects($this->any()) + ->method('getDistUrl') + ->will($this->returnValue('url')); + $packageMock->expects($this->any()) + ->method('getDistUrls') + ->will($this->returnValue(array('url'))); + + $rootPackageMock = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $rootPackageMock->expects($this->any()) + ->method('getScripts') + ->will($this->returnValue(array())); + + $composerMock = $this->getMockBuilder('Composer\Composer')->getMock(); + $composerMock->expects($this->any()) + ->method('getPackage') + ->will($this->returnValue($rootPackageMock)); + $composerMock->expects($this->any()) + ->method('getConfig') + ->will($this->returnValue($config)); + + $expectedUrl = 'url'; + $customCacheKey = 'xyzzy'; + $expectedCacheKey = '/'.sha1($customCacheKey).'.'; + + $dispatcher = new EventDispatcher( + $composerMock, + $this->getMockBuilder('Composer\IO\IOInterface')->getMock(), + $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock() + ); + $dispatcher->addListener(PluginEvents::PRE_FILE_DOWNLOAD, function ( PreFileDownloadEvent $event ) use ($customCacheKey) { + $event->setCustomCacheKey($customCacheKey); + }); + + $cacheMock = $this->getMockBuilder('Composer\Cache') + ->disableOriginalConstructor() + ->getMock(); + $cacheMock + ->expects($this->any()) + ->method('copyTo') + ->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { + $self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyTo method:'); + + return false; + })); + $cacheMock + ->expects($this->any()) + ->method('copyFrom') + ->will($this->returnCallback(function ($cacheKey) use ($self, $expectedCacheKey) { + $self->assertEquals($expectedCacheKey, $cacheKey, 'Failed assertion on $cacheKey argument of Cache::copyFrom method:'); + + return false; + })); + + $httpDownloaderMock = $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); + $httpDownloaderMock + ->expects($this->any()) + ->method('addCopy') + ->will($this->returnCallback(function ($url) use ($self, $expectedUrl) { + $self->assertEquals($expectedUrl, $url, 'Failed assertion on $url argument of HttpDownloader::addCopy method:'); + + return \React\Promise\resolve( + new Response(array('url' => 'http://example.org/'), 200, array(), 'file~') + ); + })); + + $downloader = $this->getDownloader(null, $config, $dispatcher, $cacheMock, $httpDownloaderMock); + + try { + $loop = new Loop($this->httpDownloader); + $promise = $downloader->download($packageMock, $path); + $loop->wait(array($promise)); + + $this->fail('Download was expected to throw'); + } catch (\Exception $e) { + if (is_dir($path)) { + $fs = new Filesystem(); + $fs->removeDirectory($path); + } elseif (is_file($path)) { + unlink($path); + } + + $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage()); + $this->assertStringContainsString('could not be saved to', $e->getMessage()); + } + } + public function testCacheGarbageCollectionIsCalled() { $expectedTtl = '99999999';