From 942562c3822b87ca73bd07b022b501104f67ad0e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Jul 2020 17:00:29 +0200 Subject: [PATCH 01/52] Clean up Zip Util to be more strict about what is a valid package archive, fixes #8931 --- src/Composer/Util/Zip.php | 50 ++++++++++-------- .../artifacts/composer-1.0.0-alpha6.zip | Bin 5227 -> 4046 bytes .../Fixtures/artifacts/jsonInFirstLevel.zip | Bin 542 -> 519 bytes .../jsonInFirstLevelWithExtraFirstLevel.zip | Bin 0 -> 595 bytes .../Fixtures/artifacts/not-an-artifact.zip | Bin 4140 -> 3264 bytes .../Test/Util/Fixtures/Zip/multiple.zip | Bin 2569 -> 936 bytes .../Test/Util/Fixtures/Zip/single-sub.zip | Bin 0 -> 306 bytes .../Test/Util/Fixtures/Zip/subfolder.zip | Bin 1328 -> 0 bytes .../Test/Util/Fixtures/Zip/subfolders.zip | Bin 0 -> 454 bytes tests/Composer/Test/Util/ZipTest.php | 18 +++++-- 10 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevelWithExtraFirstLevel.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/single-sub.zip delete mode 100644 tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/subfolders.zip diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index ab10d5bbf..3ebdcdd85 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -71,37 +71,41 @@ class Zip */ private static function locateFile(\ZipArchive $zip, $filename) { - $indexOfShortestMatch = false; - $lengthOfShortestMatch = -1; + // return root composer.json if it is there and is a file + if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + $topLevelPaths = array(); for ($i = 0; $i < $zip->numFiles; $i++) { - $stat = $zip->statIndex($i); - if (strcmp(basename($stat['name']), $filename) === 0) { - $directoryName = dirname($stat['name']); - if ($directoryName === '.') { - //if composer.json is in root directory - //it has to be the one to use. - return $i; - } + $name = $zip->getNameIndex($i); + $dirname = dirname($name); - if (strpos($directoryName, '\\') !== false || - strpos($directoryName, '/') !== false) { - //composer.json files below first directory are rejected - continue; + // handle archives with proper TOC + if ($dirname === '.') { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + // archive can only contain one top level directory + return false; } + continue; + } - $length = strlen($stat['name']); - if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { - //Check it's not a directory. - $contents = $zip->getFromIndex($i); - if ($contents !== false) { - $indexOfShortestMatch = $i; - $lengthOfShortestMatch = $length; - } + // handle archives which do not have a TOC record for the directory itself + if (false === strpos('\\', $dirname) && false === strpos('/', $dirname)) { + $topLevelPaths[$dirname.'/'] = true; + if (\count($topLevelPaths) > 1) { + // archive can only contain one top level directory + return false; } } } - return $indexOfShortestMatch; + if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + + // no composer.json found either at the top level or within the topmost directory + return false; } } diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip index e94843eb614f84703773840a0b898a10ee8e1874..585b4f7ea0075fa9935849ea299e15eb4c11af2e 100644 GIT binary patch delta 98 zcmaE@aZY~2LdMP8nY>vh?`0C$e4Bk0`{Z(gwUbqZRG8*6PW~t$IXPL#p2?31%-k#F sz+}ukd7*&B+&jYJBCKo-K)?osB1{YnOL##%0Eqk>jsO4v delta 979 zcmX>n|5{_iLdGaA1`wFIq}~b4fD)Vx0u1r-zK+iR!4dkQ5j+frO%Z`0{FPCJA;6oN z1-mwRWNniJnZ-EadMDZ{@@k<=2kXV_xdg`tm*f|vf>i!!j0pS<#2{Oe6GW5K6Os}> z`1*u>;1BBvXkeNk!K^MI&A5?Cp!tU*j{=`hD+6;F1JFI}9Q&NR6tsYj4`P`7fK_Jm zc1BZ{$+pY_yn5(XGU>%nPG^?Q#%+9|D$w}Vk{wKaii(Z_Y97aB6rQ;UAL2Qq@`v{NY09mngMZ9fmH8-SfGnmGi-jq zdW?PY6IKb#0I1iC4{~*M@paY9O#uafX28F(yS zse4-IoX;r}PhO@h1}%dtrY1&Pj7$vHt~0w3Xt3gH;0iO74O@UrQ{y#jS-~N^_|d`G zE}(y2F-%^>yRja70^nrefur68_1d{9@WYz`TKoOa=x$6tjP@VKLi{-)Ba=M?F2AY(wS#~Hn8faLE~qRcg9L-=x*K`T(bpJZbmf$W z91tJb7EFI4+mZ#e2^zj|UnAS_?1pwG$V3=D?dpnjm<^EF!5#=u4*;q4Xf}X@B*2@M R4Wxt_2-z7K7+!%m3;=?4hAsdA literal 542 zcmWIWW@Zs#U|`^2n7%B?CH~jPFdiUJ9EdrAIJKgrC{eGZqJ-O1SMTH*?{E#rrHlb6 zTKbq?p0)rg1!0gu#idCpnML}^`MCx8#i>PlS;hHztHS~UPI;g44c!zJpanGP;(4ue z=Rcn*KCPpr_t8_=`)uH)zyK|8U9EFx&NHtxykdO8I3Q>RD+8)c$c__0*hGXIk#!29 z=`_Oh9wQUPE7-#Xs2>TyZ4dB9)rKA}2tCF?Cbm#QHv!qzApau3Tp$x_0#5spb%Xqj c0M~&`WZmEp2=HcQ11Vtv!f!xYh!MmC05v{@hyVZp diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevelWithExtraFirstLevel.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevelWithExtraFirstLevel.zip new file mode 100644 index 0000000000000000000000000000000000000000..b400918b9c7ce2bde31d94f1d931c2a0e8123b8e GIT binary patch literal 595 zcmWIWW@Zs#U|`^2n7%B?CH~jPFdiUJ9EdrAIJKgrC{eGZqJ-O1SMTH*?{E#rrHlb^ zEkT_h0=irmO!EXP^#)>)LB*v>DVat3$@#ej`NgS4dRfK!d9J4oxf%?3STC$@vG-oP zdUw-;99=Vd!$!s=@+v+RrrC^>FYrY$`!F#~Oq7`H%gxkh2be8wm)1`wFKxZVlOfD)Vx0u1r-zK+iR!4dkQ5j+f04H1DL{FPCJA;6oN z1-mwRWNnibxh2@4Iw#sH@@k<-7IKRR#_PER#|M|>7o~!%@BkVgRtK>nIYBfzJs~OK zgRf862mY{*fCi=s63prX(u^CK1e$+1@+k26v@$SE05|gcY9GKl%fK2hpQ+dQqK~9F)afUD45fL2dc5niN0G?1m dcIY6-F$X-31H4(;K!L>xgwo6m48Ora2LOIBzxn_G diff --git a/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip index db8c50302d78e7512c043c4598fd62f518af151c..96c0959bbd5a61358708d7c4eb32e5e99e37caa6 100644 GIT binary patch literal 936 zcmWIWW@Zs#0D&V3DSluEl;8l;Y56%RsYQnR0Z>&O410m9vKTsYCjezxBp4V3kyPoq z1jh%LA#iljshH9~?7e zFH3`*&jt1t$dlLRi!;jsc_1u`PP5P;IK z$m3#A0E;j(0OL#J{-eT6R~Ub8ZqBWbzQ!ndE-42@2YACw#umjelN*5Mr~xs|WO#(5 zSmFNi4#*0I`qrNy8r2G1(SU5lQzR=;LI=efs843z=F0>5K<6vEPq0M}%oL0-|B%Dzdp)wrxFQs0as$xh7$lQ%dH`n4i2#1E2Y8mltYKvXn!&&dginD6 Jtp#ER1^_Zv^%?*G literal 2569 zcmWIWW@h1H0D9J_iOYJ9Y%ed5Eum*72cZaN; zaO1hyoCT}rHJO$_(7EzMR7prBVnNskyOY0^Ds%kJwjF24`F0_sf&IaR+?UBr$ITWk zIBoJHw=et7;;j7DKSRxJuOFOPWPgm;YD-4jPyO)b75;*!*R2!J-G1ZSj^|&WJ^lFQ zWsTOVpKe!P@t|$qy1u1*_P$Z zvM=ps&z`pNhrzl%+Hq%qa`wMc760+NN7~K(FCKsWzf*^O&(yDb<~`i2ATjaWtqr-; zcQZc7?E9#?;6#6G`i~d*(ws90894P&B}z0J}C&6u`A1#~!j)?6D_-u2nA{XGA6^h$g2eBqefhbjzQbzgzyynQ}!*S#@2M9@x2TPq(|s0i6oM2s&*) zb0=Up7d^*_qBz%3A0@y+84i(itoaTZ2)Ly8a(kQTY2Gbh$V+fN$+?4pHGIMMi;A}d zH;DuVs5Vtj`Wn_%pDE+daO1;{_y!(@gT2OflWvH$tTQ{O*dpN6QJcK3h17he(UnQoQ2wV@9g5>*eL1`PU;LD zxtoANhd-%<0s$q>LZTfJj)c>$0xl!a(lBz6QY{TbQX+byKuw8;`WPt@3CQR8j6inDU&pP;Mj(3#VGP1U$c32%iibX+8-tP#vHK0ZDC0pg@vUPu z6C{8UMIyv|pt1}(ph2b}ms*lYrYvdPie?I0I^lp9kH{g0p0ZILrp1ixFi@t2I1H4u zksSsq`m&J915p%*rJ$LDk`iFK7THngDFNBIzmAzKNGSmnjff%zJyl?jP6ZU#t-)m+ zTC}1%6k;+c^pG>h~n5SP$Mynf|UUQ-mGk({K^Z2 L*MQ+J3+4d;JZ$R> diff --git a/tests/Composer/Test/Util/Fixtures/Zip/single-sub.zip b/tests/Composer/Test/Util/Fixtures/Zip/single-sub.zip new file mode 100644 index 0000000000000000000000000000000000000000..b8ccd4c76300549eb937b85666e320a8f5aeffde GIT binary patch literal 306 zcmWIWW@h1H0D17q?=T&nl0D)3oVs5IEm4Z@Qe!hNEVv!P8EmwdyBaj+{(oWcQj3P`s-14K8bLsX!;6@au6R5yyVxEMfE3=E765)2Hhl^wD# kU14mwZs<}UeU0%X$2BJq9pKH%22#TWgbRUmHHgCi0GA^^2LJ#7 literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip b/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip deleted file mode 100644 index 93060bea23bf3efe8fec023cb6c2dd42ee44a80e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1328 zcmWIWW@h1H0D9<*fOmYO^o~FaN4*AxTAik* z2{)dL%~_!BcW{<YzU)LgyQ-2i#pKZKtBI5c|F|oE_%ktS7iS*FE=|M50!NPqi(^6jtqz$w;lm-d`6t`T9;Jrwua zFSm-5p}cqtKcnZLNQ*=1`#}c0c612b^@tNyd=1r@oS$2eUz}Q`msOmf2TR3y zZy|xW`dJVI)6zXh?u4jnvo788r{?dHBTB04qV&KHtPXAa zNl7h&Yji-;D8j%2guy|mR!N|0OwP|O$S+PU(#tB&&x2W?_ZDLP>SsX=OiTA1xf7zQ z&AN2UpPIi*jwq?Fi_!x+jGd!CuXwu@&;uYG;LXS+%8bi%JTSjHymbUIAztNxdlgwX zrbm%=8$or$y$vx0markTestSkipped('The PHP zip extension is loaded.'); @@ -74,7 +74,7 @@ class ZipTest extends TestCase return; } - $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip'); + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolders.zip'); $this->assertNull($result); } @@ -103,7 +103,7 @@ class ZipTest extends TestCase $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } - public function testReturnsRootComposerJsonAndSkipsSubfolders() + public function testMultipleTopLevelDirsIsInvalid() { if (!extension_loaded('zip')) { $this->markTestSkipped('The PHP zip extension is not loaded.'); @@ -112,6 +112,18 @@ class ZipTest extends TestCase $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); + $this->assertNull($result); + } + + public function testReturnsComposerJsonFromFirstSubfolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/single-sub.zip'); + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } } From c3d40ae79a3c0bc00e2aceba1571019bb9ce3257 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Jul 2020 17:16:19 +0200 Subject: [PATCH 02/52] Fix passing of repo http options in async requests --- src/Composer/Repository/ComposerRepository.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 941cf08dd..78c6cf495 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -1202,7 +1202,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $filename = $preFileDownloadEvent->getProcessedUrl(); } - $options = $lastModifiedTime ? array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))) : array(); + $options = $this->options; + if ($lastModifiedTime) { + if (isset($options['http']['header'])) { + $options['http']['header'] = (array) $options['http']['header']; + } + $options['http']['header'][] = array('If-Modified-Since: '.$lastModifiedTime); + } $io = $this->io; $url = $this->url; From 8cec8bd5460b897cca772cb312d281bc64f0c7ad Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Jul 2020 17:22:41 +0200 Subject: [PATCH 03/52] Allow verify_peer/verify_peer_name http options to be handled by curl downloader --- src/Composer/Util/Http/CurlDownloader.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 257a29115..b2219fbc5 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -56,6 +56,8 @@ class CurlDownloader 'ssl' => array( 'cafile' => CURLOPT_CAINFO, 'capath' => CURLOPT_CAPATH, + 'verify_peer' => CURLOPT_SSL_VERIFYPEER, + 'verify_peer_name' => CURLOPT_SSL_VERIFYHOST, ), ); @@ -178,7 +180,11 @@ class CurlDownloader foreach (self::$options as $type => $curlOptions) { foreach ($curlOptions as $name => $curlOption) { if (isset($options[$type][$name])) { - curl_setopt($curlHandle, $curlOption, $options[$type][$name]); + if ($type === 'ssl' && $name === 'verify_peer_name') { + curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]); + } else { + curl_setopt($curlHandle, $curlOption, $options[$type][$name]); + } } } } From 750a92b4b7aecda0e5b2f9b963f1cb1421900675 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Jul 2020 17:29:00 +0200 Subject: [PATCH 04/52] Fix headers array format --- src/Composer/Repository/ComposerRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 78c6cf495..d3db3069c 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -1139,7 +1139,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } - $options['http']['header'][] = array('If-Modified-Since: '.$lastModifiedTime); + $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; $response = $this->httpDownloader->get($filename, $options); $json = $response->getBody(); if ($json === '' && $response->getStatusCode() === 304) { @@ -1207,7 +1207,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if (isset($options['http']['header'])) { $options['http']['header'] = (array) $options['http']['header']; } - $options['http']['header'][] = array('If-Modified-Since: '.$lastModifiedTime); + $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime; } $io = $this->io; From 4e1dd4bfdf77997f637fe9fe35b48ac9a94576dd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 20 Jul 2020 20:49:00 +0200 Subject: [PATCH 05/52] added phpdocs in StreamContextFactory --- src/Composer/Util/StreamContextFactory.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 09291a908..e91707ab3 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -29,6 +29,7 @@ final class StreamContextFactory * Creates a context supporting HTTP proxies * * @param string $url URL the context is to be used for + * @psalm-param array{http: array{follow_location?: int, max_redirects?: int, header?: string|array}} $defaultOptions * @param array $defaultOptions Options to merge with the default * @param array $defaultParams Parameters to specify on the context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled @@ -56,7 +57,8 @@ final class StreamContextFactory /** * @param string $url * @param array $options - * @return array ['http' => ['header' => [...], 'proxy' => '..', 'request_fulluri' => bool]] formatted as a stream context array + * @psalm-return array{http:{header: string[], proxy?: string, request_fulluri: bool}, ssl: array} + * @return array formatted as a stream context array */ public static function initOptions($url, array $options) { From c353ac835c8e09600b91b9c20778390ee0ff1099 Mon Sep 17 00:00:00 2001 From: Wissem Riahi Date: Tue, 21 Jul 2020 17:10:26 +0200 Subject: [PATCH 06/52] Add exception for multiple composer.json files (#3) --- .../Repository/ArtifactRepository.php | 7 +++- src/Composer/Util/Zip.php | 12 +++--- .../Util/Fixtures/Zip/multiple_subfolders.zip | Bin 0 -> 3724 bytes tests/Composer/Test/Util/ZipTest.php | 39 ++++++++++++------ 4 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/multiple_subfolders.zip diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index a0acb7a61..f8b068218 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -88,7 +88,12 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { - $json = Zip::getComposerJson($file->getPathname()); + $json = null; + try { + $json = Zip::getComposerJson($file->getPathname()); + } catch (\Exception $exception) { + $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE); + } if (null === $json) { return false; diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 3ebdcdd85..ad6617edf 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -66,8 +66,9 @@ class Zip * * @param \ZipArchive $zip * @param string $filename + * @throws \RuntimeException * - * @return bool|int + * @return int */ private static function locateFile(\ZipArchive $zip, $filename) { @@ -85,8 +86,7 @@ class Zip if ($dirname === '.') { $topLevelPaths[$name] = true; if (\count($topLevelPaths) > 1) { - // archive can only contain one top level directory - return false; + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); } continue; } @@ -95,8 +95,7 @@ class Zip if (false === strpos('\\', $dirname) && false === strpos('/', $dirname)) { $topLevelPaths[$dirname.'/'] = true; if (\count($topLevelPaths) > 1) { - // archive can only contain one top level directory - return false; + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); } } } @@ -105,7 +104,6 @@ class Zip return $index; } - // no composer.json found either at the top level or within the topmost directory - return false; + throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); } } diff --git a/tests/Composer/Test/Util/Fixtures/Zip/multiple_subfolders.zip b/tests/Composer/Test/Util/Fixtures/Zip/multiple_subfolders.zip new file mode 100644 index 0000000000000000000000000000000000000000..5c5bc5adf86e747df57a7e1d2b7218612e3134ec GIT binary patch literal 3724 zcmWIWW@h1H0D<-iHv+&6D8b4gz>t=oZ>%30!Nc&j=u2E}<>$Dsj3NvHa4nZ&Z=z`7 zglS1kN``Bz0cwS56Jg*0!bvfx+Jum`>A3{Q2bbg*rGl(gDEtzqQUKBacE(1oLk0qE z?{D$3PVecfWWBH8RVp6u5E9_Hvb);s{^{_7kA)`B+_|Gk-Wu=`ir#<5)C13G)@W#$$jo}R4(|;d+)#7cr6LM|S#J72|r`=QH#f~P0>&dmo z+<9VU%fI^K)!8o+Kf8vu2sn9^UtF>0dY)v<1bLM?{9~bq^qF(Rgl#(*1H9Qe4lj+2dku^V76}HV5a*9W4LVSW z$H)6RI{ODlz(X7qS+Ed?#4WluK4fhukxW=GD7N5Mg7qTDHnM)~u`P>Lzg|4fh)+%s zO-@fpO8DUG6ZU~WtRtX-X@Uf^x_~s}MrncOAEFLt7=j-v9B5#+Xlr3{oGEG$`vMeX z>>S)32P{m1L0AFAphOFCPjFC*A2f-BDKVgX;ND2i&n?I=PA$^QD$dUXrNA;^SmwR0 zi2;S@>SsX=OiTA1xf7zQ&AN2UpPIi*jwq?Fi_!zzR-aeAT?%L-2;;U*5r^;bT8ujc z3T1&IAT80s)TgNE7@+2HoJm2-z;v&3=dXI}Xq@o8dfHP*PgB>=*V9wSGn9{m zO_N9Cmk|4+)>~^U9ZkSa7s=T7I|k_N4Jb~ZgjOJc5)C+@Fk)&7v}AZY!`APxfkfMV z{j&m{N0k?^zaTwBLx$li(}cG93qyC6sHHy^nbX|M8?(RFqj18EsDr;ft_i;^{bSi( z&vf8b?RFNK2CkE0@XyV&cqk5m5nY^%MzW%j<_ zzw__ke|3HK^wov8{!Vx|&uDSkj^obT?kUb(d%R@XJ;k$Egq=7Pk3|HP7T(I_P&_wb zhh2r$1?NqM`Y(#U^ewiPeW7^s#`*=`yB5j5ed)k|GGNX!F}^56=tf;%NO4PK1YBNZbM zFHFHzaD%jAqzd)Xf*Vm)qL;v!r6_XHW0s=mL62FAg5vFCQ)Ww4J#gCISU33ZyiB2v; zFvJK@a6^oM7Cpdxi`b0V4b*KsMZ*n>9?LL(r~ z0i|YqM)08+aR=E5l#n504zjoYI!Z7hIR#$9W4Z|uM9A$0StK_tX^g;X7D{=I9$JWO zg4{3=LoxOR&{(+3(9$jkyy<})lc2^6a&4`MVqO?CHZSYN6J<20x<{_LkYi;@qaX{^ zsTc_aR7D{>5o8W>NJ^qOF@Y7`iC97rVI;2V3Xy)_I=%rK2@XX_2?|Mbpvnu`RiN?{ zcNS1)Lox!xhoB-9U;0H&cX90KK16mGA`L?l2*_Q?p@o|6=3zApONb$yNKCrZ<3RE* rM&f`K*2uw!Ki$m%ng`GI@TkC&?hr<^0z;gEK@bS#fzcPv3E}|&bq`N_ literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/ZipTest.php b/tests/Composer/Test/Util/ZipTest.php index 78c7e9657..ba61e8bf6 100644 --- a/tests/Composer/Test/Util/ZipTest.php +++ b/tests/Composer/Test/Util/ZipTest.php @@ -55,28 +55,30 @@ class ZipTest extends TestCase $this->assertNull($result); } - public function testReturnsNullIfTheZipHasNoComposerJson() + /** + * @expectedException \RuntimeException + */ + public function testThrowsExceptionIfTheZipHasNoComposerJson() { if (!extension_loaded('zip')) { $this->markTestSkipped('The PHP zip extension is not loaded.'); return; } - $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip'); - - $this->assertNull($result); + Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip'); } - public function testReturnsNullIfTheComposerJsonIsInASubSubfolder() + /** + * @expectedException \RuntimeException + */ + public function testThrowsExceptionIfTheComposerJsonIsInASubSubfolder() { if (!extension_loaded('zip')) { $this->markTestSkipped('The PHP zip extension is not loaded.'); return; } - $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolders.zip'); - - $this->assertNull($result); + Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolders.zip'); } public function testReturnsComposerJsonInZipRoot() @@ -99,10 +101,12 @@ class ZipTest extends TestCase } $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip'); - $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } + /** + * @expectedException \RuntimeException + */ public function testMultipleTopLevelDirsIsInvalid() { if (!extension_loaded('zip')) { @@ -110,9 +114,7 @@ class ZipTest extends TestCase return; } - $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); - - $this->assertNull($result); + Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); } public function testReturnsComposerJsonFromFirstSubfolder() @@ -126,4 +128,17 @@ class ZipTest extends TestCase $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } + + /** + * @expectedException \RuntimeException + */ + public function testThrowsExceptionIfMultipleComposerInSubFoldersWereFound() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple_subfolders.zip'); + } } From ac055e5718508ac95ce1c9bc172efcab6f266e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Tue, 21 Jul 2020 23:25:05 +0200 Subject: [PATCH 07/52] Fix: Reference --- src/Composer/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index c7af69427..21e211285 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1679,7 +1679,7 @@ class Installer * restrict the update operation to a few packages, all other packages * that are already installed will be kept at their current version * - * @deprecated use setAllowList instead + * @deprecated use setUpdateAllowList instead * * @param array $packages * @return Installer From e5c7835d575e35349fc25dc2e201cee10f05ad12 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Sun, 26 Jul 2020 15:22:59 -0400 Subject: [PATCH 08/52] Properly support PHP 8.0 Named Arguments See https://wiki.php.net/rfc/named_params#internal_functions (implemented but not yet merged) An ArgumentCountError will be thrown when passing variadic arguments to a function with call_user_func_array() if extra unknown named arguments are encountered. Fatal error: Uncaught ArgumentCountError: array_merge() does not accept unknown named parameters in phar:///path/to/composer.phar/src/Composer/DependencyResolver/DefaultPolicy.php:84 (e.g. for `['phpunit/phpunit' => [72]]`) --- src/Composer/DependencyResolver/DefaultPolicy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 542c6e625..d7d386801 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -81,7 +81,7 @@ class DefaultPolicy implements PolicyInterface $literals = $this->pruneRemoteAliases($pool, $literals); } - $selected = call_user_func_array('array_merge', $packages); + $selected = call_user_func_array('array_merge', array_values($packages)); // now sort the result across all packages to respect replaces across packages usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) { From b25296ef744a4c9ea8db50bfcd762d18362fa51d Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 27 Jul 2020 13:26:57 +0100 Subject: [PATCH 09/52] Driver: only cache composer.json file without API data to disk --- src/Composer/Repository/Vcs/BitbucketDriver.php | 14 +++++++------- src/Composer/Repository/Vcs/GitHubDriver.php | 14 +++++++------- src/Composer/Repository/Vcs/GitLabDriver.php | 16 ++++++++-------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index c360bad10..08f119527 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -119,10 +119,14 @@ abstract class BitbucketDriver extends VcsDriver if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { - return $this->infoCache[$identifier] = JsonFile::parseJson($res); - } + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); - $composer = $this->getBaseComposerInformation($identifier); + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } if ($composer) { // specials for bitbucket @@ -173,10 +177,6 @@ abstract class BitbucketDriver extends VcsDriver } $this->infoCache[$identifier] = $composer; - - if ($this->shouldCache($identifier)) { - $this->cache->write($identifier, json_encode($composer)); - } } return $this->infoCache[$identifier]; diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 7abda8164..26904b12f 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -150,10 +150,14 @@ class GitHubDriver extends VcsDriver if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { - return $this->infoCache[$identifier] = JsonFile::parseJson($res); - } + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); - $composer = $this->getBaseComposerInformation($identifier); + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } if ($composer) { // specials for github @@ -172,10 +176,6 @@ class GitHubDriver extends VcsDriver } } - if ($this->shouldCache($identifier)) { - $this->cache->write($identifier, json_encode($composer)); - } - $this->infoCache[$identifier] = $composer; } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 2878f3f74..bb5c2121e 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -130,10 +130,14 @@ class GitLabDriver extends VcsDriver if (!isset($this->infoCache[$identifier])) { if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { - return $this->infoCache[$identifier] = JsonFile::parseJson($res); - } + $composer = JsonFile::parseJson($res); + } else { + $composer = $this->getBaseComposerInformation($identifier); - $composer = $this->getBaseComposerInformation($identifier); + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + } if ($composer) { // specials for gitlab (this data is only available if authentication is provided) @@ -145,10 +149,6 @@ class GitLabDriver extends VcsDriver } } - if ($this->shouldCache($identifier)) { - $this->cache->write($identifier, json_encode($composer)); - } - $this->infoCache[$identifier] = $composer; } @@ -446,7 +446,7 @@ class GitLabDriver extends VcsDriver if (!$moreThanGuestAccess) { $this->io->writeError('GitLab token with Guest only access detected'); - return $this->attemptCloneFallback(); + return $this->attemptCloneFallback(); } } From 404dea61c2a40f27a91c203c00ed33fb286ebedc Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Wed, 29 Jul 2020 19:30:57 +0200 Subject: [PATCH 10/52] Allow specifying a version requirement for the relevant CLDR --- .../Repository/PlatformRepository.php | 35 +++++++++++++------ .../Repository/PlatformRepositoryTest.php | 32 +++++++++++++++++ 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index ecd4ca256..848576aa3 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -154,7 +154,12 @@ class PlatformRepository extends ArrayRepository break; case 'intl': - $name = 'ICU'; + # Add a seperate version for the CLDR library version + $cldrVersion = \ResourceBundle::create('root', 'ICUDATA-curr', false)->get('Version'); + $this->addLibrary('cldr', 'The unicode CLDR project', $cldrVersion); + + $name = 'icu'; + $description = 'The ICU unicode and globalization support library'; if (defined('INTL_ICU_VERSION')) { $prettyVersion = INTL_ICU_VERSION; } else { @@ -228,15 +233,7 @@ class PlatformRepository extends ArrayRepository continue 2; } - try { - $version = $this->versionParser->normalize($prettyVersion); - } catch (\UnexpectedValueException $e) { - continue; - } - - $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); - $lib->setDescription($description); - $this->addPackage($lib); + $this->addLibrary($name, $description, $prettyVersion); } $hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; @@ -346,4 +343,22 @@ class PlatformRepository extends ArrayRepository { return 'ext-' . str_replace(' ', '-', $name); } + + /** + * @param string $name + * @param string $description + * @param string $prettyVersion + */ + private function addLibrary($name, $description, $prettyVersion) + { + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + return; + } + + $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); + $lib->setDescription($description); + $this->addPackage($lib); + } } diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index aa51a2fc6..20dd295fc 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -12,6 +12,7 @@ namespace Composer\Test\Repository; +use Composer\Package\Package; use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; use Composer\Util\Platform; @@ -67,4 +68,35 @@ class PlatformRepositoryTest extends TestCase { $this->assertNotNull($package, 'failed to find HHVM package'); $this->assertSame('4.0.1.0-dev', $package->getVersion()); } + + public function testICULibraryVersion() + { + if (!defined('INTL_ICU_VERSION')) { + $this->markTestSkipped('Test only work with ext-intl present'); + } + + $platformRepository = new PlatformRepository(); + $packages = $platformRepository->getPackages(); + + /** @var Package $icuPackage */ + $icuPackage = null; + /** @var Package $cldrPackage */ + $cldrPackage = null; + + foreach ($packages as $package) { + if ($package->getName() === 'lib-icu') { + $icuPackage = $package; + } + + if ($package->getName() === 'lib-cldr') { + $cldrPackage = $package; + } + } + + self::assertNotNull($icuPackage, 'Expected to find lib-icu in packages'); + self::assertNotNull($cldrPackage, 'Expected to find lib-cldr in packages'); + + self::assertSame(3, substr_count($icuPackage->getVersion(), '.'), 'Expected to find real ICU version'); + self::assertSame(3, substr_count($cldrPackage->getVersion(), '.'), 'Expected to find real CLDR version'); + } } From 5a02ea6a9622476a63515f34b1b2ef26c05ac405 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Thu, 30 Jul 2020 14:29:48 +0200 Subject: [PATCH 11/52] Check that class exists --- src/Composer/Repository/PlatformRepository.php | 8 +++++--- tests/Composer/Test/Repository/PlatformRepositoryTest.php | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 848576aa3..5d240fa9c 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -154,9 +154,11 @@ class PlatformRepository extends ArrayRepository break; case 'intl': - # Add a seperate version for the CLDR library version - $cldrVersion = \ResourceBundle::create('root', 'ICUDATA-curr', false)->get('Version'); - $this->addLibrary('cldr', 'The unicode CLDR project', $cldrVersion); + if (class_exists('ResourceBundle', false)) { + # Add a seperate version for the CLDR library version + $cldrVersion = \ResourceBundle::create('root', 'ICUDATA-curr', false)->get('Version'); + $this->addLibrary('cldr', 'The unicode CLDR project', $cldrVersion); + } $name = 'icu'; $description = 'The ICU unicode and globalization support library'; diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index 20dd295fc..e254720fb 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -69,12 +69,15 @@ class PlatformRepositoryTest extends TestCase { $this->assertSame('4.0.1.0-dev', $package->getVersion()); } - public function testICULibraryVersion() - { + public function testICULibraryVersion() { if (!defined('INTL_ICU_VERSION')) { $this->markTestSkipped('Test only work with ext-intl present'); } + if (!class_exists('ResourceBundle', false)) { + $this->markTestSkipped('Test only work with ResourceBundle class present'); + } + $platformRepository = new PlatformRepository(); $packages = $platformRepository->getPackages(); From 79813b2f7772cb6bf6a7b066fd1743a337ddd3eb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 14:24:04 +0200 Subject: [PATCH 12/52] Fix detection of git refs to be more strict --- src/Composer/Repository/Vcs/VcsDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 37946da23..f174f8527 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -81,7 +81,7 @@ abstract class VcsDriver implements VcsDriverInterface */ protected function shouldCache($identifier) { - return $this->cache && preg_match('{[a-f0-9]{40}}i', $identifier); + return $this->cache && preg_match('{^[a-f0-9]{40}$}iD', $identifier); } /** From 7bcde1481d08bde910a2da9c7dcfa4fc572dd211 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 14:36:52 +0200 Subject: [PATCH 13/52] Fix git downloader syntax for windows cmd when updating packages, fixes #9089 --- src/Composer/Downloader/GitDownloader.php | 4 ++-- tests/Composer/Test/Downloader/GitDownloaderTest.php | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 177644735..f15539a87 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -146,10 +146,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface if (!empty($this->cachedPackages[$target->getId()][$ref])) { $msg = "Checking out ".$this->getShortHash($ref).' from cache'; - $command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%'; + $command = '(git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer)) && git remote set-url composer %sanitizedUrl%'; } else { $msg = "Checking out ".$this->getShortHash($ref); - $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%'; + $command = '(git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)) && git remote set-url composer %sanitizedUrl%'; if (getenv('COMPOSER_DISABLE_NETWORK')) { throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting'); } diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 6618618e1..97b6db9ac 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -390,7 +390,7 @@ class GitDownloaderTest extends TestCase public function testUpdate() { - $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'"); + $expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'"); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) @@ -421,7 +421,7 @@ class GitDownloaderTest extends TestCase public function testUpdateWithNewRepoUrl() { - $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'"); + $expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'"); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) @@ -496,8 +496,8 @@ composer https://github.com/old/url (push) */ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails() { - $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'"); - $expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'git@github.com:composer/composer'"); + $expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'"); + $expectedGitUpdateCommand2 = $this->winCompat("(git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'git@github.com:composer/composer'"); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) @@ -540,8 +540,8 @@ composer https://github.com/old/url (push) public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover() { - $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."'"); - $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'"); + $expectedFirstGitUpdateCommand = $this->winCompat("(git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."'"); + $expectedSecondGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'"); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) From 12d6759888963a09e19944d54e9d1bbcc803f105 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 14:38:35 +0200 Subject: [PATCH 14/52] Fail hard instead of skipping branches/tags quietly when parsing VCS repos if 401/403 are returned, fixes #9087 --- src/Composer/Repository/VcsRepository.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 24f85053c..08c831b98 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -265,6 +265,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } + if ($e->getCode() === 401 || $e->getCode() === 403) { + throw $e; + } } if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).''); @@ -350,6 +353,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } + if ($e->getCode() === 401 || $e->getCode() === 403) { + throw $e; + } if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)'); } From 41318576a359705c32ce5560a3188b5c54d62fb1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 14:43:30 +0200 Subject: [PATCH 15/52] Update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce4da235..354234a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### [2.0.0-beta1] 2020-08-xx + + * Breaking: Zip archives loaded by artifact repositories must now have a composer.json on top level, or a max of one folder on top level of the archive + * Added --no-dev support to `show` and `outdated` commands to skip dev requirements + * Added support for multiple --repository flags being passed into the `create-project` command, only useful in combination with `--add-repository` to persist them to composer.json + * Added a new optional `list` API endpoint for v2-format composer repositories, see [UPGRADE](UPGRADE-2.0.md) for details + * Fixed `show -a` command not listing anything + * Fixed solver bug where it ended in a "Reached invalid decision id 0" + * Fixed updates of git-installed packages on windows + * Lots of minor bug fixes + ### [2.0.0-alpha2] 2020-06-24 * Added parallel installation of packages (requires OSX/Linux/WSL, and that `unzip` is present in PATH) @@ -921,6 +932,7 @@ * Initial release +[2.0.0-beta1]: https://github.com/composer/composer/compare/2.0.0-alpha2...2.0.0-beta1 [2.0.0-alpha2]: https://github.com/composer/composer/compare/2.0.0-alpha1...2.0.0-alpha2 [2.0.0-alpha1]: https://github.com/composer/composer/compare/1.10.7...2.0.0-alpha1 [1.10.9]: https://github.com/composer/composer/compare/1.10.8...1.10.9 From 5bd61ac55c45e031e749f4b0234821636ba2a6c5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 15:43:28 +0200 Subject: [PATCH 16/52] Cache versions data to avoid redownloading it twice during self-update --- src/Composer/SelfUpdate/Versions.php | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Composer/SelfUpdate/Versions.php b/src/Composer/SelfUpdate/Versions.php index 6a0a1bdbf..c2c54e984 100644 --- a/src/Composer/SelfUpdate/Versions.php +++ b/src/Composer/SelfUpdate/Versions.php @@ -26,6 +26,7 @@ class Versions private $rfs; private $config; private $channel; + private $versionsData; public function __construct(Config $config, RemoteFilesystem $rfs) { @@ -63,13 +64,7 @@ class Versions public function getLatest($channel = null) { - if ($this->config->get('disable-tls') === true) { - $protocol = 'http'; - } else { - $protocol = 'https'; - } - - $versions = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false)); + $versions = $this->getVersionsData(); foreach ($versions[$channel ?: $this->getChannel()] as $version) { if ($version['min-php'] <= PHP_VERSION_ID) { @@ -79,4 +74,19 @@ class Versions throw new \LogicException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); } + + private function getVersionsData() + { + if (!$this->versionsData) { + if ($this->config->get('disable-tls') === true) { + $protocol = 'http'; + } else { + $protocol = 'https'; + } + + $this->versionsData = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false)); + } + + return $this->versionsData; + } } From 387e8289939284f1b3b67c392cd58b260e4e365c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 16:10:51 +0200 Subject: [PATCH 17/52] Promote next major version when running stable self-update, and prevent self-update from automatically upgrading to the next major release --- src/Composer/Command/SelfUpdateCommand.php | 28 ++++++++++++++++++++++ src/Composer/SelfUpdate/Versions.php | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 5ac7fa1f1..d4d13364e 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -135,8 +135,36 @@ EOT $latest = $versionsUtil->getLatest(); $latestStable = $versionsUtil->getLatest('stable'); + try { + $latestPreview = $versionsUtil->getLatest('preview'); + } catch (\UnexpectedValueException $e) { + $latestPreview = $latestStable; + } $latestVersion = $latest['version']; $updateVersion = $input->getArgument('version') ?: $latestVersion; + $currentMajorVersion = preg_replace('{^(\d+).*}', '$1', Composer::getVersion()); + $updateMajorVersion = preg_replace('{^(\d+).*}', '$1', $updateVersion); + $previewMajorVersion = preg_replace('{^(\d+).*}', '$1', $latestPreview['version']); + + if ($versionsUtil->getChannel() === 'stable' && !$input->getArgument('version')) { + // if requesting stable channel and no specific version, avoid automatically upgrading to the next major + // simply output a warning that the next major stable is available and let users upgrade to it manually + if ($currentMajorVersion < $updateMajorVersion) { + $skippedVersion = $updateVersion; + + $versionsUtil->setChannel($currentMajorVersion); + + $latest = $versionsUtil->getLatest(); + $latestStable = $versionsUtil->getLatest('stable'); + $latestVersion = $latest['version']; + $updateVersion = $latestVersion; + + $io->writeError('A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://github.com/composer/composer/releases for changelogs.'); + } elseif ($currentMajorVersion < $previewMajorVersion) { + // promote next major version if available in preview + $io->writeError('A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.'); + } + } if ($requestedChannel && is_numeric($requestedChannel) && substr($latestStable['version'], 0, 1) !== $requestedChannel) { $io->writeError('Warning: You forced the install of '.$latestVersion.' via --'.$requestedChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.'); diff --git a/src/Composer/SelfUpdate/Versions.php b/src/Composer/SelfUpdate/Versions.php index c2c54e984..7a0835ae8 100644 --- a/src/Composer/SelfUpdate/Versions.php +++ b/src/Composer/SelfUpdate/Versions.php @@ -72,7 +72,7 @@ class Versions } } - throw new \LogicException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); + throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')'); } private function getVersionsData() From 00f712a7c48aa88c837c3571dd3578855a70e9bd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 21:00:43 +0200 Subject: [PATCH 18/52] Revert "Allow specifying a version requirement for CLDR" --- .../Repository/PlatformRepository.php | 37 +++++-------------- .../Repository/PlatformRepositoryTest.php | 35 ------------------ 2 files changed, 10 insertions(+), 62 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 5d240fa9c..ecd4ca256 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -154,14 +154,7 @@ class PlatformRepository extends ArrayRepository break; case 'intl': - if (class_exists('ResourceBundle', false)) { - # Add a seperate version for the CLDR library version - $cldrVersion = \ResourceBundle::create('root', 'ICUDATA-curr', false)->get('Version'); - $this->addLibrary('cldr', 'The unicode CLDR project', $cldrVersion); - } - - $name = 'icu'; - $description = 'The ICU unicode and globalization support library'; + $name = 'ICU'; if (defined('INTL_ICU_VERSION')) { $prettyVersion = INTL_ICU_VERSION; } else { @@ -235,7 +228,15 @@ class PlatformRepository extends ArrayRepository continue 2; } - $this->addLibrary($name, $description, $prettyVersion); + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + continue; + } + + $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); + $lib->setDescription($description); + $this->addPackage($lib); } $hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; @@ -345,22 +346,4 @@ class PlatformRepository extends ArrayRepository { return 'ext-' . str_replace(' ', '-', $name); } - - /** - * @param string $name - * @param string $description - * @param string $prettyVersion - */ - private function addLibrary($name, $description, $prettyVersion) - { - try { - $version = $this->versionParser->normalize($prettyVersion); - } catch (\UnexpectedValueException $e) { - return; - } - - $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); - $lib->setDescription($description); - $this->addPackage($lib); - } } diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index e254720fb..aa51a2fc6 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -12,7 +12,6 @@ namespace Composer\Test\Repository; -use Composer\Package\Package; use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; use Composer\Util\Platform; @@ -68,38 +67,4 @@ class PlatformRepositoryTest extends TestCase { $this->assertNotNull($package, 'failed to find HHVM package'); $this->assertSame('4.0.1.0-dev', $package->getVersion()); } - - public function testICULibraryVersion() { - if (!defined('INTL_ICU_VERSION')) { - $this->markTestSkipped('Test only work with ext-intl present'); - } - - if (!class_exists('ResourceBundle', false)) { - $this->markTestSkipped('Test only work with ResourceBundle class present'); - } - - $platformRepository = new PlatformRepository(); - $packages = $platformRepository->getPackages(); - - /** @var Package $icuPackage */ - $icuPackage = null; - /** @var Package $cldrPackage */ - $cldrPackage = null; - - foreach ($packages as $package) { - if ($package->getName() === 'lib-icu') { - $icuPackage = $package; - } - - if ($package->getName() === 'lib-cldr') { - $cldrPackage = $package; - } - } - - self::assertNotNull($icuPackage, 'Expected to find lib-icu in packages'); - self::assertNotNull($cldrPackage, 'Expected to find lib-cldr in packages'); - - self::assertSame(3, substr_count($icuPackage->getVersion(), '.'), 'Expected to find real ICU version'); - self::assertSame(3, substr_count($cldrPackage->getVersion(), '.'), 'Expected to find real CLDR version'); - } } From 019febb5fa40f13c41282a0c2a3981a7f318c331 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 2 Aug 2020 15:10:54 +0100 Subject: [PATCH 19/52] Use consistent phpdoc nullable syntax --- src/Composer/InstalledVersions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/InstalledVersions.php b/src/Composer/InstalledVersions.php index c2c6dbd3e..7bf2c0686 100644 --- a/src/Composer/InstalledVersions.php +++ b/src/Composer/InstalledVersions.php @@ -46,7 +46,7 @@ class InstalledVersions * * @param VersionParser $parser Install composer/semver to have access to this class and functionality * @param string $packageName - * @param ?string $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package * * @return bool */ From b112f90b73ac31fdb88ae16b9a6172ad74a00a5d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 3 Aug 2020 11:34:31 +0200 Subject: [PATCH 20/52] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 388832a6e..0e67556cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### [1.10.10] 2020-08-03 + + * Fixed `create-project` not triggering events while installing the root package + * Fixed PHP 8 compatibility issue + * Fixed `self-update` to avoid automatically upgrading to the next major version once it becomes stable + ### [1.10.9] 2020-07-16 * Fixed Bitbucket redirect loop when credentials are outdated @@ -873,6 +879,7 @@ * Initial release +[1.10.10]: https://github.com/composer/composer/compare/1.10.9...1.10.10 [1.10.9]: https://github.com/composer/composer/compare/1.10.8...1.10.9 [1.10.8]: https://github.com/composer/composer/compare/1.10.7...1.10.8 [1.10.7]: https://github.com/composer/composer/compare/1.10.6...1.10.7 From f1ba37bf8bc3815631b8169b7d29e70b430503d1 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Mon, 3 Aug 2020 12:35:04 +0200 Subject: [PATCH 21/52] Fix the example link for the list endpoint The autolinking from GFM does not include the * char in the link. Using an explicit link in the markdown fixes it. --- UPGRADE-2.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index df2b41359..c4635814d 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -80,4 +80,4 @@ The providers-api is optional, but if you implement it it should return packages This is also optional, it should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. -It must return an array of package names as `{"packageNames": ["a/b", "c/d"]}`. See https://packagist.org/packages/list.json?filter=composer/* for example. +It must return an array of package names as `{"packageNames": ["a/b", "c/d"]}`. See for example. From 54aac000ba78f8d6848fe4602710d41c3758c5e2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 3 Aug 2020 12:55:15 +0200 Subject: [PATCH 22/52] Update UPGRADE-2.0.md --- UPGRADE-2.0.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index c4635814d..a2da9c2e2 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -81,3 +81,5 @@ The providers-api is optional, but if you implement it it should return packages This is also optional, it should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. It must return an array of package names as `{"packageNames": ["a/b", "c/d"]}`. See for example. + +It should return the names of package which names match the filter (or all names if no filter is present). Replace/provide rules should not be considered here. From 2709d943afa301a693ad91014ef8de1bbacc73d3 Mon Sep 17 00:00:00 2001 From: Frank Prins <25006490+PrinsFrank@users.noreply.github.com> Date: Mon, 10 Aug 2020 17:36:34 +0200 Subject: [PATCH 23/52] Move note about when it is not necessary to commit the lockfile from the "Updating dependencies to their latest version" section to the "Commit your composer.lock file to version control" section --- doc/01-basic-usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 5dd1726bd..45e1cc727 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -142,6 +142,9 @@ reinstalling the project you can feel confident the dependencies installed are still working even if your dependencies released many new versions since then. (See note below about using the `update` command.) +> **Note:** For libraries it is not necessary to commit the lock +> file, see also: [Libraries - Lock file](02-libraries.md#lock-file). + ## Updating dependencies to their latest versions As mentioned above, the `composer.lock` file prevents you from automatically getting @@ -165,9 +168,6 @@ If you only want to install, upgrade or remove one dependency, you can explicitl php composer.phar update monolog/monolog [...] ``` -> **Note:** For libraries it is not necessary to commit the lock -> file, see also: [Libraries - Lock file](02-libraries.md#lock-file). - ## Packagist [Packagist](https://packagist.org/) is the main Composer repository. A Composer From c0309f22d7050df0e93c85483ec441613c005b5b Mon Sep 17 00:00:00 2001 From: Ryan Aslett Date: Mon, 10 Aug 2020 12:51:48 -0700 Subject: [PATCH 24/52] Update PathDownloader.php If a path repository points at a directory that is managed by composer installers, the path that gets set ends up being relative, and this check fails to see that the source is already present, and therefore removes it. Since ->install is already using realpath around the $path argument, remove should as well. For an example repository that demonstrates this bug See: https://github.com/ryanaslett/pathrepotestcase --- src/Composer/Downloader/PathDownloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 190267c5c..94c6fb71b 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -180,7 +180,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter { $realUrl = realpath($package->getDistUrl()); - if ($path === $realUrl) { + if (realpath($path) === $realUrl) { if ($output) { $this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path"); } From 7649c8438da5db345fac9fa68b5029fc874f2325 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 11 Aug 2020 09:42:16 +0200 Subject: [PATCH 25/52] Fix exception when using create-project in current directory, fixes #9073 --- src/Composer/Downloader/ArchiveDownloader.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 7cf19deee..ba19aa58b 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -46,10 +46,17 @@ abstract class ArchiveDownloader extends FileDownloader $this->io->writeError('Extracting archive', false); } - $this->filesystem->emptyDirectory($path); + $vendorDir = $this->config->get('vendor-dir'); + + // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains + // the archive to be extracted. This is the case when installing with create-project in the current directory + // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. + if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path).DIRECTORY_SEPARATOR)) { + $this->filesystem->emptyDirectory($path); + } do { - $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); + $temporaryDir = $vendorDir.'/composer/'.substr(md5(uniqid('', true)), 0, 8); } while (is_dir($temporaryDir)); $this->addCleanupPath($package, $temporaryDir); From 826db3db5e16b0836e9f015d17a3584cd5fd2d64 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 12 Aug 2020 11:11:20 +0200 Subject: [PATCH 26/52] Used locked repo only if it is present --- src/Composer/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 40a041927..b98a88c12 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -387,7 +387,7 @@ class Installer $this->io->writeError('Updating dependencies'); // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata - if ($this->updateMirrors) { + if ($this->updateMirrors && $lockedRepository) { foreach ($lockedRepository->getPackages() as $lockedPackage) { $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); } From ff757e649cc10bfc19eee0900353035cb42912e5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 12 Aug 2020 12:41:19 +0200 Subject: [PATCH 27/52] Use pool to match packages to avoid getting packages without ids, fixes #9094 --- src/Composer/Command/ShowCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 5541b53a8..635d56bf3 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -578,8 +578,8 @@ EOT $matchedPackage = null; $versions = array(); - $matches = $repositorySet->findPackages($name, $constraint); $pool = $repositorySet->createPoolForPackage($name); + $matches = $pool->whatProvides($name, $constraint); foreach ($matches as $index => $package) { // select an exact match if it is in the installed repo and no specific version was required if (null === $version && $installedRepo->hasPackage($package)) { From 657ae5519ee7f23d56329a8c64f778c4162554f7 Mon Sep 17 00:00:00 2001 From: Wissem Riahi Date: Wed, 12 Aug 2020 20:30:58 +0200 Subject: [PATCH 28/52] Add support for TAR in Artifact packages (#9105) --- doc/05-repositories.md | 2 +- .../Repository/ArtifactRepository.php | 21 +++++- src/Composer/Util/Tar.php | 68 +++++++++++++++++ .../Repository/ArtifactRepositoryTest.php | 8 ++ .../artifacts/jsonInRooTarFile.tar.gz | Bin 0 -> 333 bytes .../Test/Util/Fixtures/Tar/empty.tar.gz | Bin 0 -> 29 bytes .../Test/Util/Fixtures/Tar/folder.tar.gz | Bin 0 -> 195 bytes .../Test/Util/Fixtures/Tar/multiple.tar.gz | Bin 0 -> 465 bytes .../Test/Util/Fixtures/Tar/nojson.tar.gz | Bin 0 -> 122 bytes .../Test/Util/Fixtures/Tar/root.tar.gz | Bin 0 -> 164 bytes .../Test/Util/Fixtures/Tar/subfolders.tar.gz | Bin 0 -> 220 bytes tests/Composer/Test/Util/TarTest.php | 71 ++++++++++++++++++ 12 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/Composer/Util/Tar.php create mode 100644 tests/Composer/Test/Repository/Fixtures/artifacts/jsonInRooTarFile.tar.gz create mode 100644 tests/Composer/Test/Util/Fixtures/Tar/empty.tar.gz create mode 100644 tests/Composer/Test/Util/Fixtures/Tar/folder.tar.gz create mode 100644 tests/Composer/Test/Util/Fixtures/Tar/multiple.tar.gz create mode 100644 tests/Composer/Test/Util/Fixtures/Tar/nojson.tar.gz create mode 100644 tests/Composer/Test/Util/Fixtures/Tar/root.tar.gz create mode 100644 tests/Composer/Test/Util/Fixtures/Tar/subfolders.tar.gz create mode 100644 tests/Composer/Test/Util/TarTest.php diff --git a/doc/05-repositories.md b/doc/05-repositories.md index b32d92690..12a9dced0 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -516,7 +516,7 @@ There are some cases, when there is no ability to have one of the previously mentioned repository types online, even the VCS one. Typical example could be cross-organisation library exchange through built artifacts. Of course, most of the times they are private. To simplify maintenance, one can simply use a -repository of type `artifact` with a folder containing ZIP archives of those +repository of type `artifact` with a folder containing ZIP or TAR archives of those private packages: ```json diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index f8b068218..4f03fc7eb 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -16,6 +16,7 @@ use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\LoaderInterface; +use Composer\Util\Tar; use Composer\Util\Zip; /** @@ -66,7 +67,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS); $iterator = new \RecursiveIteratorIterator($directory); - $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i'); + $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar|tar|gz|tgz)$/i'); foreach ($regex as $file) { /* @var $file \SplFileInfo */ if (!$file->isFile()) { @@ -89,8 +90,22 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { $json = null; + $fileType = null; + $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION); + if (in_array($fileExtension, array('gz', 'tar', 'tgz'), true)) { + $fileType = 'tar'; + } else if ($fileExtension === 'zip') { + $fileType = 'zip'; + } else { + throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.'); + } + try { - $json = Zip::getComposerJson($file->getPathname()); + if ($fileType === 'tar') { + $json = Tar::getComposerJson($file->getPathname()); + } else { + $json = Zip::getComposerJson($file->getPathname()); + } } catch (\Exception $exception) { $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE); } @@ -101,7 +116,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); $package['dist'] = array( - 'type' => 'zip', + 'type' => $fileType, 'url' => strtr($file->getPathname(), '\\', '/'), 'shasum' => sha1_file($file->getRealPath()), ); diff --git a/src/Composer/Util/Tar.php b/src/Composer/Util/Tar.php new file mode 100644 index 000000000..cae69c906 --- /dev/null +++ b/src/Composer/Util/Tar.php @@ -0,0 +1,68 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Wissem Riahi + */ +class Tar +{ + /** + * @param string $pathToArchive + * + * @return string|null + */ + public static function getComposerJson($pathToArchive) + { + $phar = new \PharData($pathToArchive); + + if (!$phar->valid()) { + return null; + } + + return self::extractComposerJsonFromFolder($phar); + } + + /** + * @param \PharData $phar + * + * @throws \RuntimeException + * + * @return string + */ + private static function extractComposerJsonFromFolder(\PharData $phar) + { + if (isset($phar['composer.json'])) { + return $phar['composer.json']->getContent(); + } + + $topLevelPaths = array(); + foreach ($phar as $folderFile) { + $name = $folderFile->getBasename(); + + if ($folderFile->isDir()) { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths))); + } + } + } + + $composerJsonPath = key($topLevelPaths).'/composer.json'; + if ($topLevelPaths && isset($phar[$composerJsonPath])) { + return $phar[$composerJsonPath]->getContent(); + } + + throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory'); + } +} diff --git a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php index 506a033c4..ad7a69009 100644 --- a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php @@ -36,6 +36,7 @@ class ArtifactRepositoryTest extends TestCase 'vendor1/package2-4.3.2', 'vendor3/package1-5.4.3', 'test/jsonInRoot-1.0.0', + 'test/jsonInRootTarFile-1.0.0', 'test/jsonInFirstLevel-1.0.0', //The files not-an-artifact.zip and jsonSecondLevel are not valid //artifacts and do not get detected. @@ -52,6 +53,13 @@ class ArtifactRepositoryTest extends TestCase sort($foundPackages); $this->assertSame($expectedPackages, $foundPackages); + + $tarPackage = array_filter($repo->getPackages(), function (BasePackage $package) { + return $package->getPrettyName() === 'test/jsonInRootTarFile'; + }); + $this->assertCount(1, $tarPackage); + $tarPackage = array_pop($tarPackage); + $this->assertSame('tar', $tarPackage->getDistType()); } public function testAbsoluteRepoUrlCreatesAbsoluteUrlPackages() diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInRooTarFile.tar.gz b/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInRooTarFile.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..7d2938703399ca129fd262a7395a6622c59f9efa GIT binary patch literal 333 zcmV-T0kZxdiwFQ?9VA}>1MSw)PJ=KM24Jpzij!U&)|QqgK7fh0W-ox+85RLUJ6&RY zcZ(x4O2Eui7Ty2doC^vZXwPZauBor;>!w*2?Pd937fF1abE7rA$9xyLRJ+(Ckr^Sl z$aThv3uS~#NdH1E0~_ayHl*@gI%li#abehMHSwJOJNEI9Sbv>=yY+36`mK-mj_^Nn zr6%TI8WJ)KS2G=!0Cmg2n1pz|6vmzS{4cHZWqnWnb3ArhFq1+3yUopA*+$(44(b2W zh_3!6&(Z(qFqr?_rh06gZPV^boOgWN|Bd|E|HEFug;6R)|DVGP5vz;JvI}Be?_cZ} zgzr}bJ16#JTUR!9H%O#B<&0j>j-5HgFxLNL9s>_P|H*Iz&VRGO`R}w%*1wF?0De&a fLJQRY98muN00000000000RPh)c3m}w04M+e-5#Sy literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Tar/empty.tar.gz b/tests/Composer/Test/Util/Fixtures/Tar/empty.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..805860f87bb28c1dc32e06f59dd2f0709df2998f GIT binary patch literal 29 kcmb2|=3o#qQHWs3}P&Uon0=Bj|;px|V+F&Xa4mx=nfF8hOKI=8a}vy?-%Z6Q{gB z_y{+ocU)Pm>S=U|QR>8metJD8f!|(Y$ynm$)CuwvV uobI-N|9{#lh1aXLo!h1*z3=e4SDU}gtE)C*KnC+|77BFZqFaQ9EV_TB| literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Tar/multiple.tar.gz b/tests/Composer/Test/Util/Fixtures/Tar/multiple.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..179a5bd97ee99501b5abe1125e911774d5daaaf2 GIT binary patch literal 465 zcmV;?0WSU@iwFQR+#_EA1MS+;Yl1)=2k^VrL$ntk_jm+*ZKs|Jdy;5>L4Uv&%b<}N zS?pm9{L^=Jzgpxh2B~d*KOkSBOHY2Dd1<_wE+_M8^uWp^r3|DLoAkX+N=KO_!gd2& z2A(gK64G{EUozp9*r%OUx*RTaSKcRS8pXx!bhGjJ$ad&BcKJud+xm}_=_pz_Wt<1= zx_jQK`YU|~1j^Ox(pG`vp#D`jsJ~rz{T&DOuTrl5-TvTtuuK-w`Sx+V{#yD3l9$?kt{+M*?=vA;dq1^@s6TsCf^bz5&Y0N?_x zuMdJUq|8im`n*Be-20A2DJVnA%p|9F4az2Ei_)SLlp$qik}g`DCLf&k%xQ+gX%@l- zW%#Q@2DlnGCam@E{}U^m{{z53#+&^?zsGhHf_wwLyzH%KtoSCNVg4+<+xxDd3@I~{ zxDfyVs9Ns%-)j=jlQdd%-_m4uo_(Br{_FY&&wm4@d_4cF!be+Zo!KysI*+0=PLl1J z&}o0R>+T(tE%*LU&Ewy^|AX;gmBR7wVEh9B0000000000000000000009>js7lA{| H08jt`E6wP7 literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Tar/nojson.tar.gz b/tests/Composer/Test/Util/Fixtures/Tar/nojson.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d6a64264d9c6c0e3ad93f5051bd2b831a3978871 GIT binary patch literal 122 zcmb2|=3vmdr5w+|{Pw&xSA&5-OJe^d=^ZDZ70Piliz!JQef|50-|mwC*L{q2HdoHN zwaiG+Q*nm&@v|w90@gTZzg;^u-R$1ppzz9bue^M6UmvS@KSw+_=V`2B)lxh8 zbuTaV6;Hiwxb#B({n^)d3a$`P1P0@YoxKb5bti(B5o7~ ztBCKjnJ$KOk}nC}bes%KN?BMd&edJ2%+00tgwjSAsxYxbNR?}C6JduQ?4%3ZvW=tc z79mv4{l3^%*ALd|_i?R1BE + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Tar; +use Composer\Test\TestCase; + +/** + * @author Wissem Riahi + */ +class TarTest extends TestCase +{ + public function testReturnsNullifTheTarIsNotFound() + { + $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/invalid.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheTarIsEmpty() + { + $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/empty.tar.gz'); + $this->assertNull($result); + } + + /** + * @expectedException \RuntimeException + */ + public function testThrowsExceptionIfTheTarHasNoComposerJson() + { + Tar::getComposerJson(__DIR__.'/Fixtures/Tar/nojson.tar.gz'); + } + + /** + * @expectedException \RuntimeException + */ + public function testThrowsExceptionIfTheComposerJsonIsInASubSubfolder() + { + Tar::getComposerJson(__DIR__.'/Fixtures/Tar/subfolders.tar.gz'); + } + + public function testReturnsComposerJsonInTarRoot() + { + $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/root.tar.gz'); + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsComposerJsonInFirstFolder() + { + $result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/folder.tar.gz'); + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + /** + * @expectedException \RuntimeException + */ + public function testMultipleTopLevelDirsIsInvalid() + { + Tar::getComposerJson(__DIR__.'/Fixtures/Tar/multiple.tar.gz'); + } +} From 7e1ef19a5a544af35a3b063652659637c711fb2a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 14:53:46 +0200 Subject: [PATCH 29/52] Expand library version checking capabilities (closes #9093) --- src/Composer/Platform/HhvmDetector.php | 59 + src/Composer/Platform/Runtime.php | 94 ++ .../Repository/PlatformRepository.php | 426 ++++-- src/Composer/Util/Version.php | 104 ++ .../Test/Platform/HhvmDetectorTest.php | 97 ++ .../Repository/PlatformRepositoryTest.php | 1196 ++++++++++++++++- tests/Composer/Test/Util/VersionTest.php | 131 ++ 7 files changed, 1958 insertions(+), 149 deletions(-) create mode 100644 src/Composer/Platform/HhvmDetector.php create mode 100644 src/Composer/Platform/Runtime.php create mode 100644 src/Composer/Util/Version.php create mode 100644 tests/Composer/Test/Platform/HhvmDetectorTest.php create mode 100644 tests/Composer/Test/Util/VersionTest.php diff --git a/src/Composer/Platform/HhvmDetector.php b/src/Composer/Platform/HhvmDetector.php new file mode 100644 index 000000000..a0524e758 --- /dev/null +++ b/src/Composer/Platform/HhvmDetector.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; + +class HhvmDetector +{ + private static $hhvmVersion; + private $executableFinder; + private $processExecutor; + + public function __construct(ExecutableFinder $executableFinder = null, ProcessExecutor $processExecutor = null) { + + $this->executableFinder = $executableFinder; + $this->processExecutor = $processExecutor; + } + + public function reset() + { + self::$hhvmVersion = null; + } + + public function getVersion() { + if (null !== self::$hhvmVersion) { + return self::$hhvmVersion ?: null; + } + + self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; + if (self::$hhvmVersion === null && !Platform::isWindows()) { + self::$hhvmVersion = false; + $this->executableFinder = $this->executableFinder ?: new ExecutableFinder(); + $hhvmPath = $this->executableFinder->find('hhvm'); + if ($hhvmPath !== null) { + $this->processExecutor = $this->processExecutor ?: new ProcessExecutor(); + $exitCode = $this->processExecutor->execute( + ProcessExecutor::escape($hhvmPath). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + self::$hhvmVersion + ); + if ($exitCode !== 0) { + self::$hhvmVersion = false; + } + } + } + return self::$hhvmVersion; + } +} diff --git a/src/Composer/Platform/Runtime.php b/src/Composer/Platform/Runtime.php new file mode 100644 index 000000000..baf450df1 --- /dev/null +++ b/src/Composer/Platform/Runtime.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +class Runtime +{ + /** + * @param string $constant + * @param class-string $class + * @return bool + */ + public function hasConstant($constant, $class = null) { + return defined(ltrim($class.'::'.$constant, ':')); + } + + /** + * @param bool $constant + * @param class-string $class + * @return mixed + */ + public function getConstant($constant, $class = null) { + return constant(ltrim($class.'::'.$constant, ':')); + } + + /** + * @param callable $callable + * @param array $arguments + * @return mixed + */ + public function invoke($callable, array $arguments = array()) { + return call_user_func_array($callable, $arguments); + } + + /** + * @param class-string $class + * @return bool + */ + public function hasClass($class) { + return class_exists($class, false); + } + + /** + * @param class-string $class + * @param array $arguments + * @return object + */ + public function construct($class, array $arguments = array()) { + if (empty($arguments)) { + return new $class; + } + + $refl = new \ReflectionClass($class); + return $refl->newInstanceArgs($arguments); + } + + /** @return string[] */ + public function getExtensions() + { + return get_loaded_extensions(); + } + + /** + * @param string $extension + * @return string + */ + public function getExtensionVersion($extension) + { + return phpversion($extension); + } + + /** + * @param string $extension + * @return string + */ + public function getExtensionInfo($extension) + { + $reflector = new \ReflectionExtension($extension); + + ob_start(); + $reflector->info(); + + return ob_get_clean(); + } +} diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index c88604a3b..899b177cf 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -14,14 +14,16 @@ namespace Composer\Repository; use Composer\Composer; use Composer\Package\CompletePackage; +use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; +use Composer\Platform\HhvmDetector; +use Composer\Platform\Runtime; use Composer\Plugin\PluginInterface; -use Composer\Util\ProcessExecutor; +use Composer\Semver\Constraint\Constraint; use Composer\Util\Silencer; -use Composer\Util\Platform; +use Composer\Util\Version; use Composer\XdebugHandler\XdebugHandler; -use Symfony\Component\Process\ExecutableFinder; /** * @author Jordi Boggiano @@ -30,7 +32,9 @@ class PlatformRepository extends ArrayRepository { const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-(?:plugin|runtime)-api)$}iD'; - private static $hhvmVersion; + /** + * @var VersionParser + */ private $versionParser; /** @@ -42,11 +46,13 @@ class PlatformRepository extends ArrayRepository */ private $overrides = array(); - private $process; + private $runtime; + private $hhvmDetector; - public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null) + public function __construct(array $packages = array(), array $overrides = array(), Runtime $runtime = null, HhvmDetector $hhvmDetector = null) { - $this->process = $process; + $this->runtime = $runtime ?: new Runtime(); + $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector(); foreach ($overrides as $name => $version) { $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version); } @@ -88,10 +94,10 @@ class PlatformRepository extends ArrayRepository $this->addPackage($composerRuntimeApi); try { - $prettyVersion = PHP_VERSION; + $prettyVersion = $this->runtime->getConstant('PHP_VERSION'); $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { - $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', PHP_VERSION); + $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION')); $version = $this->versionParser->normalize($prettyVersion); } @@ -99,19 +105,19 @@ class PlatformRepository extends ArrayRepository $php->setDescription('The PHP interpreter'); $this->addPackage($php); - if (PHP_DEBUG) { + if ($this->runtime->getConstant('PHP_DEBUG')) { $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); $this->addPackage($phpdebug); } - if (defined('PHP_ZTS') && PHP_ZTS) { + if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) { $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); $this->addPackage($phpzts); } - if (PHP_INT_SIZE === 8) { + if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) { $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); $php64->setDescription('The PHP interpreter, 64bit'); $this->addPackage($php64); @@ -119,13 +125,13 @@ class PlatformRepository extends ArrayRepository // The AF_INET6 constant is only defined if ext-sockets is available but // IPv6 support might still be available. - if (defined('AF_INET6') || Silencer::call('inet_pton', '::') !== false) { + if ($this->runtime->hasConstant('AF_INET6') || Silencer::call(array($this->runtime, 'invoke'), array('inet_pton', '::')) !== false) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); } - $loadedExtensions = get_loaded_extensions(); + $loadedExtensions = $this->runtime->getExtensions(); // Extensions scanning foreach ($loadedExtensions as $name) { @@ -133,9 +139,7 @@ class PlatformRepository extends ArrayRepository continue; } - $reflExt = new \ReflectionExtension($name); - $prettyVersion = $reflExt->getVersion(); - $this->addExtension($name, $prettyVersion); + $this->addExtension($name, $this->runtime->getExtensionVersion($name)); } // Check for Xdebug in a restarted process @@ -147,112 +151,317 @@ class PlatformRepository extends ArrayRepository // Doing it this way to know that functions or constants exist before // relying on them. foreach ($loadedExtensions as $name) { - $prettyVersion = null; - $description = 'The '.$name.' PHP library'; switch ($name) { + case 'amqp': + $info = $this->runtime->getExtensionInfo($name); + + // librabbitmq version => 0.9.0 + if (preg_match('/^librabbitmq version => (?.+)$/m', $info, $librabbitmqMatches)) { + $this->addLibrary($name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); + } + + // AMQP protocol version => 0-9-1 + if (preg_match('/^AMQP protocol version => (?.+)$/m', $info, $protocolMatches)) { + $this->addLibrary($name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); + } + break; + + case 'bz2': + $info = $this->runtime->getExtensionInfo($name); + + // BZip2 Version => 1.0.6, 6-Sept-2010 + if (preg_match('/^BZip2 Version => (?.*),/m', $info, $matches)) { + $this->addLibrary($name, $matches['version']); + } + break; + case 'curl': - $curlVersion = curl_version(); - $prettyVersion = $curlVersion['version']; + $curlVersion = $this->runtime->invoke('curl_version'); + $this->addLibrary($name, $curlVersion['version']); + + $info = $this->runtime->getExtensionInfo($name); + + // SSL Version => OpenSSL/1.0.1t + if (preg_match('{^SSL Version => (?[^/]+)/(?.+)$}m', $info, $sslMatches)) { + $library = strtolower($sslMatches['library']); + if ($library === 'openssl') { + $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); + $this->addLibrary($name.'-openssl'.($isFips ? '-fips': ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', array(), $isFips ? array('curl-openssl'): array()); + } else { + $this->addLibrary($name.'-'.$library, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', array('curl-openssl')); + } + } + + // libSSH Version => libssh2/1.4.3 + if (preg_match('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}m', $info, $sshMatches)) { + $this->addLibrary($name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); + } + + // ZLib Version => 1.2.8 + if (preg_match('{^ZLib Version => (?.+)$}m', $info, $zlibMatches)) { + $this->addLibrary($name.'-zlib', $zlibMatches['version'], 'curl zlib version'); + } break; - case 'iconv': - $prettyVersion = ICONV_VERSION; + case 'date': + $info = $this->runtime->getExtensionInfo($name); + + // timelib version => 2018.03 + if (preg_match('/^timelib version => (?.+)$/m', $info, $timelibMatches)) { + $this->addLibrary($name.'-timelib', $timelibMatches['version'], 'date timelib version'); + } + + // Timezone Database => internal + if (preg_match('/^Timezone Database => (?internal|external)$/m', $info, $zoneinfoSourceMatches)) { + $external = $zoneinfoSourceMatches['source'] === 'external'; + if (preg_match('/^"Olson" Timezone Database Version => (?.+?)(\.system)?$/m', $info, $zoneinfoMatches)) { + // If the timezonedb is provided by ext/timezonedb, register that version as a replacement + if ($external && in_array('timezonedb', $loadedExtensions, true)) { + $this->addLibrary('timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', array($name.'-zoneinfo')); + } else { + $this->addLibrary($name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); + } + } + } break; - case 'intl': - $name = 'ICU'; - if (defined('INTL_ICU_VERSION')) { - $prettyVersion = INTL_ICU_VERSION; - } else { - $reflector = new \ReflectionExtension('intl'); + case 'fileinfo': + $info = $this->runtime->getExtensionInfo($name); - ob_start(); - $reflector->info(); - $output = ob_get_clean(); + // libmagic => 537 + if (preg_match('/^^libmagic => (?.+)$/m', $info, $magicMatches)) { + $this->addLibrary($name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); + } + break; - preg_match('/^ICU version => (.*)$/m', $output, $matches); - $prettyVersion = $matches[1]; + case 'gd': + $this->addLibrary($name, $this->runtime->getConstant('GD_VERSION')); + + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^libJPEG Version => (?.+?)(?: compatible)?$/m', $info, $libjpegMatches)) { + $this->addLibrary($name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); + } + + if (preg_match('/^libPNG Version => (?.+)$/m', $info, $libpngMatches)) { + $this->addLibrary($name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); + } + + if (preg_match('/^FreeType Version => (?.+)$/m', $info, $freetypeMatches)) { + $this->addLibrary($name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); + } + + if (preg_match('/^libXpm Version => (?\d+)$/m', $info, $libxpmMatches)) { + $this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId($libxpmMatches['versionId']), 'libxpm version for gd'); } break; + case 'gmp': + $this->addLibrary($name, $this->runtime->getConstant('GMP_VERSION')); + break; + + case 'iconv': + $this->addLibrary($name, $this->runtime->getConstant('ICONV_VERSION')); + break; + + case 'intl': + $info = $this->runtime->getExtensionInfo($name); + + $description = 'The ICU unicode and globalization support library'; + // Truthy check is for testing only so we can make the condition fail + if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { + $this->addLibrary('icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); + } elseif (preg_match('/^ICU version => (?.+)$/m', $info, $matches)) { + $this->addLibrary('icu', $matches['version'], $description); + } + + // ICU TZData version => 2019c + if (preg_match('/^ICU TZData version => (?.*)$/m', $info, $zoneinfoMatches)) { + $this->addLibrary('icu-zoneinfo', Version::parseZoneinfoVersion($zoneinfoMatches['version']), 'zoneinfo ("Olson") database for icu'); + } + + # Add a separate version for the CLDR library version + if ($this->runtime->hasClass('ResourceBundle')) { + $cldrVersion = $this->runtime->invoke(array('ResourceBundle', 'create'), array('root', 'ICUDATA', false))->get('Version'); + $this->addLibrary('icu-cldr', $cldrVersion, 'ICU CLDR project version'); + } + + if ($this->runtime->hasClass('IntlChar')) { + $this->addLibrary('icu-unicode', implode('.', array_slice($this->runtime->invoke(array('IntlChar', 'getUnicodeVersion')), 0, 3)), 'ICU unicode version'); + } + break; + case 'imagick': - $imagick = new \Imagick(); - $imageMagickVersion = $imagick->getVersion(); + $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org - preg_match('/^ImageMagick ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches); - if (isset($matches[2])) { - $prettyVersion = "{$matches[1]}.{$matches[2]}"; - } else { - $prettyVersion = $matches[1]; + preg_match('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches); + $version = $matches['version']; + if (isset($matches['patch'])) { + $version .= '.'.$matches['patch']; + } + + $this->addLibrary($name.'-imagemagick', $version, null, array('imagick')); + break; + + case 'ldap': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^Vendor Version => (?\d+)$/m', $info, $matches) && preg_match('/^Vendor Name => (?.+)$/m', $info, $vendorMatches)) { + $this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId($matches['versionId']), $vendorMatches['vendor'].' version of ldap'); } break; case 'libxml': - $prettyVersion = LIBXML_DOTTED_VERSION; + // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml + $libxmlProvides = array_map(function($extension) { + return $extension . '-libxml'; + }, array_intersect($loadedExtensions, array('dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'))); + $this->addLibrary($name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', array(), $libxmlProvides); + + break; + + case 'mbstring': + $info = $this->runtime->getExtensionInfo($name); + + // libmbfl version => 1.3.2 + if (preg_match('/^libmbfl version => (?.+)$/m', $info, $libmbflMatches)) { + $this->addLibrary($name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); + } + + if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { + $this->addLibrary($name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); + + // Multibyte regex (oniguruma) version => 5.9.5 + // oniguruma version => 6.9.0 + } elseif (preg_match('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/m', $info, $onigurumaMatches)) { + $this->addLibrary($name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); + } + + break; + + case 'memcached': + $info = $this->runtime->getExtensionInfo($name); + + // libmemcached version => 1.0.18 + if (preg_match('/^libmemcached version => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-libmemcached', $matches['version'], 'libmemcached version'); + } break; case 'openssl': - $prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) { - if (empty($match[2])) { - return $match[1]; - } - - // OpenSSL versions add another letter when they reach Z. - // e.g. OpenSSL 0.9.8zh 3 Dec 2015 - - if (!preg_match('{^z*[a-z]$}', $match[2])) { - // 0.9.8abc is garbage - return 0; - } - - $len = strlen($match[2]); - $patchVersion = ($len - 1) * 26; // All Z - $patchVersion += ord($match[2][$len - 1]) - 96; - - return $match[1].'.'.$patchVersion; - }, OPENSSL_VERSION_TEXT); - - $description = OPENSSL_VERSION_TEXT; + // OpenSSL 1.1.1g 21 Apr 2020 + if (preg_match('{^(?:OpenSSL|LibreSSL)?\s*(?\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { + $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); + $this->addLibrary($name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), array(), $isFips ? array($name) : array()); + } break; case 'pcre': - $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION); + $this->addLibrary($name, preg_replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); + + $info = $this->runtime->getExtensionInfo($name); + + // PCRE Unicode Version => 12.1.0 + if (preg_match('/^PCRE Unicode Version => (?.+)$/m', $info, $pcreUnicodeMatches)) { + $this->addLibrary($name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); + } + break; - case 'uuid': - $prettyVersion = phpversion('uuid'); + case 'mysqlnd': + case 'pdo_mysql': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { + $this->addLibrary($name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); + } + break; + + case 'mongodb': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^libmongoc bundled version => (?.+)$/m', $info, $libmongocMatches)) { + $this->addLibrary($name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); + } + + if (preg_match('/^libbson bundled version => (?.+)$/m', $info, $libbsonMatches)) { + $this->addLibrary($name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); + } + break; + + case 'pgsql': + case 'pdo_pgsql': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^PostgreSQL\(libpq\) Version => (?.*)$/m', $info, $matches)) { + $this->addLibrary($name.'-libpq', $matches['version'], 'libpq for '.$name); + } + break; + + case 'libsodium': + case 'sodium': + $this->addLibrary('libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + break; + + case 'sqlite3': + case 'pdo_sqlite': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^SQLite Library => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-sqlite', $matches['version']); + } + break; + + case 'ssh2': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^libssh2 version => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-libssh2', $matches['version']); + } break; case 'xsl': - $prettyVersion = LIBXSLT_DOTTED_VERSION; + $this->addLibrary('libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, array('xsl')); + + $info = $this->runtime->getExtensionInfo('xsl'); + if (preg_match('/^libxslt compiled against libxml Version => (?.+)$/m', $info, $matches)) { + $this->addLibrary('libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); + } + break; + + case 'yaml': + $info = $this->runtime->getExtensionInfo('yaml'); + + if (preg_match('/^LibYAML Version => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-libyaml', $matches['version'], 'libyaml version of yaml'); + } break; case 'zip': - if (defined('ZipArchive::LIBZIP_VERSION')) { - $prettyVersion = \ZipArchive::LIBZIP_VERSION; - } else { - continue 2; + if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { + $this->addLibrary($name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION','ZipArchive'), null, array('zip')); } + break; + + case 'zlib': + if ($this->runtime->hasConstant('ZLIB_VERSION')) { + $this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION')); + + // Linked Version => 1.2.8 + } elseif (preg_match('/^Linked Version => (?.+)$/m', $this->runtime->getExtensionInfo($name), $matches)) { + $this->addLibrary($name, $matches['version']); + } + break; default: - // None handled extensions have no special cases, skip - continue 2; + break; } - - try { - $version = $this->versionParser->normalize($prettyVersion); - } catch (\UnexpectedValueException $e) { - continue; - } - - $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); - $lib->setDescription($description); - $this->addPackage($lib); } - if ($hhvmVersion = self::getHHVMVersion($this->process)) { + $hhvmVersion = $this->hhvmDetector->getVersion(); + if ($hhvmVersion) { try { $prettyVersion = $hhvmVersion; $version = $this->versionParser->normalize($prettyVersion); @@ -337,6 +546,11 @@ class PlatformRepository extends ArrayRepository $packageName = $this->buildPackageName($name); $ext = new CompletePackage($packageName, $version, $prettyVersion); $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); + + if ($name === 'uuid') { + $ext->setReplaces(array(new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version)))); + } + $this->addPackage($ext); } @@ -349,28 +563,34 @@ class PlatformRepository extends ArrayRepository return 'ext-' . str_replace(' ', '-', $name); } - private static function getHHVMVersion(ProcessExecutor $process = null) + /** + * @param string $name + * @param string $prettyVersion + * @param string|null $description + * @param string[] $replaces + * @param string[] $provides + */ + private function addLibrary($name, $prettyVersion, $description = null, array $replaces = array(), array $provides = array()) { - if (null !== self::$hhvmVersion) { - return self::$hhvmVersion ?: null; + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + return; } - self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; - if (self::$hhvmVersion === null && !Platform::isWindows()) { - self::$hhvmVersion = false; - $finder = new ExecutableFinder(); - $hhvmPath = $finder->find('hhvm'); - if ($hhvmPath !== null) { - $process = $process ?: new ProcessExecutor(); - $exitCode = $process->execute( - ProcessExecutor::escape($hhvmPath). - ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', - self::$hhvmVersion - ); - if ($exitCode !== 0) { - self::$hhvmVersion = false; - } - } + if ($description === null) { + $description = 'The '.$name.' library'; } + + $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); + $lib->setDescription($description); + + $links = function ($alias) use ($name, $version) { + return new Link('lib-'.$name, 'lib-'.$alias, new Constraint('=', $version)); + }; + $lib->setReplaces(array_map($links, $replaces)); + $lib->setProvides(array_map($links, $provides)); + + $this->addPackage($lib); } } diff --git a/src/Composer/Util/Version.php b/src/Composer/Util/Version.php new file mode 100644 index 000000000..1907816c0 --- /dev/null +++ b/src/Composer/Util/Version.php @@ -0,0 +1,104 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Lars Strojny + */ +class Version +{ + /** + * @param string $opensslVersion + * @param bool $isFips + * @return string|null + */ + public static function parseOpenssl($opensslVersion, &$isFips) + { + $isFips = false; + + if (!preg_match('/^(?[0-9.]+)(?[a-z]{0,2})?(?(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)?$/', $opensslVersion, $matches)) { + return null; + } + + $isFips = strpos($matches['suffix'], 'fips') !== false; + $suffix = strtr('-'.ltrim($matches['suffix'], '-'), array('-fips' => '', '-pre' => '-alpha')); + $patch = self::convertAlphaVersionToIntVersion($matches['patch']); + + return rtrim($matches['version'].'.'.$patch.$suffix, '-'); + } + + /** + * @param string $libjpegVersion + * @return string|null + */ + public static function parseLibjpeg($libjpegVersion) + { + if (!preg_match('/^(?\d+)(?[a-z]*)$/', $libjpegVersion, $matches)) { + return null; + } + + return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']); + } + + /** + * @param string $zoneinfoVersion + * @return string|null + */ + public static function parseZoneinfoVersion($zoneinfoVersion) + { + if (!preg_match('/^(?\d{4})(?[a-z]*)$/', $zoneinfoVersion, $matches)) { + return null; + } + + return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']); + } + + /** + * "" => 0, "a" => 1, "zg" => 33 + * + * @param string $alpha + * @return int + */ + private static function convertAlphaVersionToIntVersion($alpha) + { + return strlen($alpha) * (-ord('a')+1) + array_sum(array_map('ord', str_split($alpha))); + } + + /** + * @param int $versionId + * @return string + */ + public static function convertLibxpmVersionId($versionId) + { + return self::convertVersionId($versionId, 100); + } + + /** + * @param int $versionId + * @return string + */ + public static function convertOpenldapVersionId($versionId) + { + return self::convertVersionId($versionId, 100); + } + + private static function convertVersionId($versionId, $base) + { + return sprintf( + '%d.%d.%d', + $versionId / ($base * $base), + ($versionId / $base) % $base, + $versionId % $base + ); + } +} diff --git a/tests/Composer/Test/Platform/HhvmDetectorTest.php b/tests/Composer/Test/Platform/HhvmDetectorTest.php new file mode 100644 index 000000000..3e7e63228 --- /dev/null +++ b/tests/Composer/Test/Platform/HhvmDetectorTest.php @@ -0,0 +1,97 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Platform; + +use Composer\Platform\HhvmDetector; +use Composer\Test\TestCase; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; + +class HhvmDetectorTest extends TestCase +{ + private $hhvmDetector; + + protected function setUp() + { + $this->hhvmDetector = new HhvmDetector(); + $this->hhvmDetector->reset(); + } + + public function testHHVMVersionWhenExecutingInHHVM() { + if (!defined('HHVM_VERSION_ID')) { + self::markTestSkipped('Not running with HHVM'); + return; + } + $version = $this->hhvmDetector->getVersion(); + self::assertSame(self::versionIdToVersion(), $version); + } + + public function testHHVMVersionWhenExecutingInPHP() { + if (defined('HHVM_VERSION_ID')) { + self::markTestSkipped('Running with HHVM'); + return; + } + if (PHP_VERSION_ID < 50400) { + self::markTestSkipped('Test only works on PHP 5.4+'); + return; + } + if (Platform::isWindows()) { + self::markTestSkipped('Test does not run on Windows'); + return; + } + $finder = new ExecutableFinder(); + $hhvm = $finder->find('hhvm'); + if ($hhvm === null) { + self::markTestSkipped('HHVM is not installed'); + } + + $detectedVersion = $this->hhvmDetector->getVersion(); + self::assertNotNull($detectedVersion, 'Failed to detect HHVM version'); + + $process = new ProcessExecutor(); + $exitCode = $process->execute( + ProcessExecutor::escape($hhvm). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + $version + ); + self::assertSame(0, $exitCode); + + self::assertSame(self::getVersionParser()->normalize($version), self::getVersionParser()->normalize($detectedVersion)); + } + + /** @runInSeparateProcess */ + public function testHHVMVersionWhenRunningInHHVMWithMockedConstant() + { + if (!defined('HHVM_VERSION_ID')) { + define('HHVM_VERSION', '2.2.1'); + define('HHVM_VERSION_ID', 20201); + } + $version = $this->hhvmDetector->getVersion(); + self::assertSame(self::getVersionParser()->normalize(self::versionIdToVersion()), self::getVersionParser()->normalize($version)); + } + + private static function versionIdToVersion() + { + if (!defined('HHVM_VERSION_ID')) { + return null; + } + + return sprintf( + '%d.%d.%d', + HHVM_VERSION_ID / 10000, + (HHVM_VERSION_ID / 100) % 100, + HHVM_VERSION_ID % 100 + ); + } +} diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index 519aadb31..d90fcbc73 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -12,65 +12,1169 @@ namespace Composer\Test\Repository; +use Composer\Package\Package; use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; -use Composer\Util\ProcessExecutor; -use Composer\Package\Version\VersionParser; -use Composer\Util\Platform; -use Symfony\Component\Process\ExecutableFinder; +use PHPUnit\Framework\Assert; class PlatformRepositoryTest extends TestCase { - public function testHHVMVersionWhenExecutingInHHVM() + public function testHhvmPackage() { - if (!defined('HHVM_VERSION_ID')) { - $this->markTestSkipped('Not running with HHVM'); - return; - } - $repository = new PlatformRepository(); - $package = $repository->findPackage('hhvm', '*'); - $this->assertNotNull($package, 'failed to find HHVM package'); - $this->assertSame( - sprintf('%d.%d.%d', - HHVM_VERSION_ID / 10000, - (HHVM_VERSION_ID / 100) % 100, - HHVM_VERSION_ID % 100 + $hhvmDetector = $this->getMockBuilder('Composer\Platform\HhvmDetector')->getMock(); + $platformRepository = new PlatformRepository(array(), array(), null, $hhvmDetector); + + $hhvmDetector + ->method('getVersion') + ->willReturn('2.1.0'); + + $hhvm = $platformRepository->findPackage('hhvm', '*'); + self::assertNotNull($hhvm, 'hhvm found'); + + self::assertSame('2.1.0', $hhvm->getPrettyVersion()); + } + + public function getPhpFlavorTestCases() + { + return array( + array( + array( + 'PHP_VERSION' => '7.1.33', + ), + array( + 'php' => '7.1.33' + ) ), - $package->getPrettyVersion() + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'PHP_DEBUG' => true, + ), + array( + 'php' => '7.2.31', + 'php-debug' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'PHP_ZTS' => true, + ), + array( + 'php' => '7.2.31', + 'php-zts' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'PHP_INT_SIZE' => 8, + ), + array( + 'php' => '7.2.31', + 'php-64bit' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'AF_INET6' => 30, + ), + array( + 'php' => '7.2.31', + 'php-ipv6' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + ), + array( + 'php' => '7.2.31', + 'php-ipv6' => '7.2.31', + ), + array( + array('inet_pton', array('::'), ''), + ) + ) ); } - public function testHHVMVersionWhenExecutingInPHP() + /** @dataProvider getPhpFlavorTestCases */ + public function testPhpVersion(array $constants, array $packages, array $functions = array()) { - if (defined('HHVM_VERSION_ID')) { - $this->markTestSkipped('Running with HHVM'); - return; - } - if (PHP_VERSION_ID < 50400) { - $this->markTestSkipped('Test only works on PHP 5.4+'); - return; - } - if (Platform::isWindows()) { - $this->markTestSkipped('Test does not run on Windows'); - return; - } - $finder = new ExecutableFinder(); - $hhvm = $finder->find('hhvm'); - if ($hhvm === null) { - $this->markTestSkipped('HHVM is not installed'); - } - $repository = new PlatformRepository(array(), array()); - $package = $repository->findPackage('hhvm', '*'); - $this->assertNotNull($package, 'failed to find HHVM package'); + $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); + $runtime + ->method('getExtensions') + ->willReturn(array()); + $runtime + ->method('hasConstant') + ->willReturnMap( + array_map(function($constant) { + return array($constant, null, true); + }, array_keys($constants)) + ); + $runtime + ->method('getConstant') + ->willReturnMap( + array_map(function($constant, $value) { + return array($constant, null, $value); + }, array_keys($constants), array_values($constants)) + ); + $runtime + ->method('invoke') + ->willReturnMap($functions); - $process = new ProcessExecutor(); - $exitCode = $process->execute( - ProcessExecutor::escape($hhvm). - ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', - $version + $repository = new PlatformRepository(array(), array(), $runtime); + foreach ($packages as $packageName => $version) { + $package = $repository->findPackage($packageName, '*'); + self::assertNotNull($package, sprintf('Expected to find package "%s"', $packageName)); + self::assertSame($version, $package->getPrettyVersion(), sprintf('Expected package "%s" version to be %s, got %s', $packageName, $version, $package->getPrettyVersion())); + } + } + + public static function getLibraryTestCases() + { + return array( + 'amqp' => array( + 'amqp', + ' + +amqp + +Version => 1.9.4 +Revision => release +Compiled => Nov 19 2019 @ 08:44:26 +AMQP protocol version => 0-9-1 +librabbitmq version => 0.9.0 +Default max channels per connection => 256 +Default max frame size => 131072 +Default heartbeats interval => 0', + array( + 'lib-amqp-protocol' => '0.9.1', + 'lib-amqp-librabbitmq' => '0.9.0', + ) + ), + 'bz2' => array( + 'bz2', + ' +bz2 + +BZip2 Support => Enabled +Stream Wrapper support => compress.bzip2:// +Stream Filter support => bzip2.decompress, bzip2.compress +BZip2 Version => 1.0.5, 6-Sept-2010', + array('lib-bz2' => '1.0.5') + ), + 'curl' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.38.0 +Age => 3 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => No +GSSAPI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.0.1t +ZLib Version => 1.2.8 +libSSH Version => libssh2/1.4.3 + +Directive => Local Value => Master Value +curl.cainfo => no value => no value', + array( + 'lib-curl' => '2.0.0', + 'lib-curl-openssl' => '1.0.1.20', + 'lib-curl-zlib' => '1.2.8', + 'lib-curl-libssh2' => '1.4.3', + ), + array(array('curl_version', array(), array('version' => '2.0.0'))) + ), + + 'curl: OpenSSL fips version' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.38.0 +Age => 3 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => No +GSSAPI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.0.1t-fips +ZLib Version => 1.2.8 +libSSH Version => libssh2/1.4.3 + +Directive => Local Value => Master Value +curl.cainfo => no value => no value', + array( + 'lib-curl' => '2.0.0', + 'lib-curl-openssl-fips' => array('1.0.1.20', array(), array('lib-curl-openssl')), + 'lib-curl-zlib' => '1.2.8', + 'lib-curl-libssh2' => '1.4.3', + ), + array(array('curl_version', array(), array('version' => '2.0.0'))) + ), + 'curl: gnutls' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.22.0 +Age => 3 +Features +AsynchDNS => No +CharConv => No +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => No +SSL => Yes +SSPI => No +TLS-SRP => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, pop3, pop3s, rtmp, rtsp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => GnuTLS/2.12.14 +ZLib Version => 1.2.3.4', + array( + 'lib-curl' => '7.22.0', + 'lib-curl-zlib' => '1.2.3.4', + 'lib-curl-gnutls' => array('2.12.14', array('lib-curl-openssl')), + ), + array(array('curl_version', array(), array('version' => '7.22.0'))) + ), + 'curl: NSS' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.24.0 +Age => 3 +Features +AsynchDNS => Yes +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +Largefile => Yes +NTLM => Yes +SPNEGO => No +SSL => Yes +SSPI => No +krb4 => No +libz => Yes +CharConv => No +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-redhat-linux-gnu +SSL Version => NSS/3.13.3.0 +ZLib Version => 1.2.5 +libSSH Version => libssh2/1.4.1', + array( + 'lib-curl' => '7.24.0', + 'lib-curl-nss' => array('3.13.3.0', array('lib-curl-openssl')), + 'lib-curl-zlib' => '1.2.5', + 'lib-curl-libssh2' => '1.4.1', + ), + array(array('curl_version', array(), array('version' => '7.24.0'))) + ), + 'curl: libssh not libssh2' => array( + 'curl', + ' + +curl + +cURL support => enabled +cURL Information => 7.68.0 +Age => 5 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => Yes +GSSAPI => Yes +KERBEROS5 => Yes +UNIX_SOCKETS => Yes +PSL => Yes +HTTPS_PROXY => Yes +MULTI_SSL => No +BROTLI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.1.1g +ZLib Version => 1.2.11 +libSSH Version => libssh/0.9.3/openssl/zlib', + array( + 'lib-curl' => '7.68.0', + 'lib-curl-openssl' => '1.1.1.7', + 'lib-curl-zlib' => '1.2.11', + 'lib-curl-libssh' => '0.9.3', + ), + array(array('curl_version', array(), array('version' => '7.68.0'))), + ), + 'date' => array( + 'date', + ' +date + +date/time support => enabled +timelib version => 2018.03 +"Olson" Timezone Database Version => 2020.1 +Timezone Database => external +Default timezone => Europe/Berlin', + array( + 'lib-date-timelib' => '2018.03', + 'lib-date-zoneinfo' => '2020.1', + ) + ), + 'date: before timelib was extracted' => array( + 'date', + ' +date + +date/time support => enabled +"Olson" Timezone Database Version => 2013.2 +Timezone Database => internal +Default timezone => Europe/Amsterdam', + array( + 'lib-date-zoneinfo' => '2013.2', + 'lib-date-timelib' => false, + ) + ), + 'date: internal zoneinfo' => array( + array('date', 'timezonedb'), + ' +date + +date/time support => enabled +"Olson" Timezone Database Version => 2020.1 +Timezone Database => internal +Default timezone => UTC', + array('lib-date-zoneinfo' => '2020.1') + ), + 'date: external zoneinfo' => array( + array('date', 'timezonedb'), + ' +date + +date/time support => enabled +"Olson" Timezone Database Version => 2020.1 +Timezone Database => external +Default timezone => UTC', + array('lib-timezonedb-zoneinfo' => array('2020.1', array('lib-date-zoneinfo'))) + ), + 'date: zoneinfo 0.system' => array( + 'date', + ' + + +date/time support => enabled +timelib version => 2018.03 +"Olson" Timezone Database Version => 0.system +Timezone Database => internal +Default timezone => Europe/Berlin + +Directive => Local Value => Master Value +date.timezone => no value => no value +date.default_latitude => 31.7667 => 31.7667 +date.default_longitude => 35.2333 => 35.2333 +date.sunset_zenith => 90.583333 => 90.583333 +date.sunrise_zenith => 90.583333 => 90.583333', + array( + 'lib-date-zoneinfo' => '0', + 'lib-date-timelib' => '2018.03', + ) + ), + 'fileinfo' => array( + 'fileinfo', + ' +fileinfo + +fileinfo support => enabled +libmagic => 537', + array('lib-fileinfo-libmagic' => '537') + ), + 'gd' => array( + 'gd', + ' +gd + +GD Support => enabled +GD Version => bundled (2.1.0 compatible) +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.10.0 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 9 compatible +PNG Support => enabled +libPNG Version => 1.6.34 +WBMP Support => enabled +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1', + array( + 'lib-gd' => '1.2.3', + 'lib-gd-freetype' => '2.10.0', + 'lib-gd-libjpeg' => '9.0', + 'lib-gd-libpng' => '1.6.34', + ), + array(), + array(array('GD_VERSION', null, '1.2.3')) + ), + 'gd: libjpeg version variation' => array( + 'gd', + ' +gd + +GD Support => enabled +GD Version => bundled (2.1.0 compatible) +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.9.1 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 6b +PNG Support => enabled +libPNG Version => 1.6.35 +WBMP Support => enabled +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1', + array( + 'lib-gd' => '1.2.3', + 'lib-gd-freetype' => '2.9.1', + 'lib-gd-libjpeg' => '6.2', + 'lib-gd-libpng' => '1.6.35', + ), + array(), + array(array('GD_VERSION', null, '1.2.3')) + ), + 'gd: libxpm' => array( + 'gd', + ' +gd + +GD Support => enabled +GD headers Version => 2.2.5 +GD library Version => 2.2.5 +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.6.3 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 6b +PNG Support => enabled +libPNG Version => 1.6.28 +WBMP Support => enabled +XPM Support => enabled +libXpm Version => 30411 +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1', + array( + 'lib-gd' => '2.2.5', + 'lib-gd-freetype' => '2.6.3', + 'lib-gd-libjpeg' => '6.2', + 'lib-gd-libpng' => '1.6.28', + 'lib-gd-libxpm' => '3.4.11', + ), + array(), + array(array('GD_VERSION', null, '2.2.5')) + ), + 'iconv' => array( + 'iconv', + null, + array('lib-iconv' => '1.2.4'), + array(), + array(array('ICONV_VERSION', null, '1.2.4')) + ), + 'gmp' => array( + 'gmp', + null, + array('lib-gmp' => '6.1.0'), + array(), + array(array('GMP_VERSION', null, '6.1.0')) + ), + 'intl' => array( + 'intl', + ' +intl + +Internationalization support => enabled +ICU version => 57.1 +ICU Data version => 57.1 +ICU TZData version => 2016b +ICU Unicode version => 8.0 + +Directive => Local Value => Master Value +intl.default_locale => no value => no value +intl.error_level => 0 => 0 +intl.use_exceptions => 0 => 0', + array( + 'lib-icu' => '100', + 'lib-icu-cldr' => ResourceBundleStub::STUB_VERSION, + 'lib-icu-unicode' => '7.0.0', + 'lib-icu-zoneinfo' => '2016.2', + ), + array( + array(array('ResourceBundle', 'create'), array('root', 'ICUDATA', false), new ResourceBundleStub()), + array(array('IntlChar', 'getUnicodeVersion'), array(), array(7, 0, 0, 0)), + ), + array(array('INTL_ICU_VERSION', null, '100')), + array( + array('ResourceBundle'), + array('IntlChar'), + ) + ), + 'intl: INTL_ICU_VERSION not defined' => array( + 'intl', + ' +intl + +Internationalization support => enabled +version => 1.1.0 +ICU version => 57.1 +ICU Data version => 57.1', + array('lib-icu' => '57.1'), + ), + 'imagick: 6.x' => array( + 'imagick', + null, + array('lib-imagick-imagemagick' => array('6.2.9', array('lib-imagick'))), + array(), + array(), + array(array('Imagick', array(), new ImagickStub('ImageMagick 6.2.9 Q16 x86_64 2018-05-18 http://www.imagemagick.org'))) + ), + 'imagick: 7.x' => array( + 'imagick', + null, + array('lib-imagick-imagemagick' => array('7.0.8.34', array('lib-imagick'))), + array(), + array(), + array(array('Imagick', array(), new ImagickStub('ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org'))) + ), + 'ldap' => array( + 'ldap', + ' +ldap + +LDAP Support => enabled +RCS Version => $Id: 5f1913de8e05a346da913956f81e0c0d8991c7cb $ +Total Links => 0/unlimited +API Version => 3001 +Vendor Name => OpenLDAP +Vendor Version => 20450 +SASL Support => Enabled + +Directive => Local Value => Master Value +ldap.max_links => Unlimited => Unlimited', + array('lib-ldap-openldap' => '2.4.50') + ), + 'libxml' => array( + 'libxml', + null, + array('lib-libxml' => '2.1.5'), + array(), + array(array('LIBXML_DOTTED_VERSION', null, '2.1.5')) + ), + 'libxml: related extensions' => array( + array('libxml', 'dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'), + null, + array('lib-libxml' => array('2.1.5', array(), array('lib-dom-libxml', 'lib-simplexml-libxml', 'lib-xml-libxml', 'lib-xmlreader-libxml', 'lib-xmlwriter-libxml'))), + array(), + array(array('LIBXML_DOTTED_VERSION', null, '2.1.5')) + ), + 'mbstring' => array( + 'mbstring', + ' +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) version => 6.1.3', + array( + 'lib-mbstring-libmbfl' => '1.3.2', + 'lib-mbstring-oniguruma' => '7.0.0', + ), + array(), + array(array('MB_ONIGURUMA_VERSION', null, '7.0.0')) + ), + 'mbstring: no MB_ONIGURUMA constant' => array( + 'mbstring', + ' +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) version => 6.1.3', + array( + 'lib-mbstring-libmbfl' => '1.3.2', + 'lib-mbstring-oniguruma' => '6.1.3', + ) + ), + 'mbstring: no MB_ONIGURUMA constant <7.40' => array( + 'mbstring', + ' +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 +oniguruma version => 6.9.4 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) backtrack check => On', + array( + 'lib-mbstring-libmbfl' => '1.3.2', + 'lib-mbstring-oniguruma' => '6.9.4', + ), + ), + 'memcached' => array( + 'memcached', + ' +memcached + +memcached support => enabled +Version => 3.1.5 +libmemcached version => 1.0.18 +SASL support => yes +Session support => yes +igbinary support => yes +json support => yes +msgpack support => yes', + array('lib-memcached-libmemcached' => '1.0.18') + ), + 'openssl' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g 21 Apr 2020')) + ), + 'openssl: two letters suffix' => array( + 'openssl', + null, + array('lib-openssl' => '0.9.8.33'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 0.9.8zg 21 Apr 2020')) + ), + 'openssl: pre release is treated as alpha' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-alpha1'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-pre1 21 Apr 2020')) + ), + 'openssl: beta release' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-beta2'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-beta2 21 Apr 2020')) + ), + 'openssl: alpha release' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-alpha4'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-alpha4 21 Apr 2020')) + ), + 'openssl: rc release' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-rc2'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-rc2 21 Apr 2020')) + ), + 'openssl: fips' => array( + 'openssl', + null, + array('lib-openssl-fips' => array('1.1.1.7', array(), array('lib-openssl'))), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-fips 21 Apr 2020')) + ), + 'openssl: LibreSSL' => array( + 'openssl', + null, + array('lib-openssl' => '2.0.1.0'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'LibreSSL 2.0.1')) + ), + 'mysqlnd' => array( + 'mysqlnd', + ' + mysqlnd + +mysqlnd => enabled +Version => mysqlnd 5.0.11-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $ +Compression => supported +core SSL => supported +extended SSL => supported +Command buffer size => 4096 +Read buffer size => 32768 +Read timeout => 31536000 +Collecting statistics => Yes +Collecting memory statistics => Yes +Tracing => n/a +Loaded plugins => mysqlnd,debug_trace,auth_plugin_mysql_native_password,auth_plugin_mysql_clear_password,auth_plugin_sha256_password +API Extensions => pdo_mysql,mysqli', + array('lib-mysqlnd-mysqlnd' => '5.0.11-dev') + ), + 'pdo_mysql' => array( + 'pdo_mysql', + ' + pdo_mysql + +PDO Driver for MySQL => enabled +Client API version => mysqlnd 5.0.10-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $ + +Directive => Local Value => Master Value +pdo_mysql.default_socket => /tmp/mysql.sock => /tmp/mysql.sock', + array('lib-pdo_mysql-mysqlnd' => '5.0.10-dev') + ), + 'mongodb' => array( + 'mongodb', + ' + mongodb + +MongoDB support => enabled +MongoDB extension version => 1.6.1 +MongoDB extension stability => stable +libbson bundled version => 1.15.2 +libmongoc bundled version => 1.15.2 +libmongoc SSL => enabled +libmongoc SSL library => OpenSSL +libmongoc crypto => enabled +libmongoc crypto library => libcrypto +libmongoc crypto system profile => disabled +libmongoc SASL => disabled +libmongoc ICU => enabled +libmongoc compression => enabled +libmongoc compression snappy => disabled +libmongoc compression zlib => enabled + +Directive => Local Value => Master Value +mongodb.debug => no value => no value', + array( + 'lib-mongodb-libmongoc' => '1.15.2', + 'lib-mongodb-libbson' => '1.15.2', + ) + ), + 'pcre' => array( + 'pcre', + ' +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 10.33 2019-04-16 +PCRE Unicode Version => 11.0.0 +PCRE JIT Support => enabled +PCRE JIT Target => x86 64bit (little endian + unaligned)', + array( + 'lib-pcre' => '10.33', + 'lib-pcre-unicode' => '11.0.0', + ), + array(), + array(array('PCRE_VERSION', null, '10.33 2019-04-16')) + ), + 'pcre: no unicode version included' => array( + 'pcre', + ' +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 8.38 2015-11-23 + +Directive => Local Value => Master Value +pcre.backtrack_limit => 1000000 => 1000000 +pcre.recursion_limit => 100000 => 100000 + ', + array( + 'lib-pcre' => '8.38', + ), + array(), + array(array('PCRE_VERSION', null, '8.38 2015-11-23')) + ), + 'pgsql' => array( + 'pgsql', + ' +pgsql + +PostgreSQL Support => enabled +PostgreSQL(libpq) Version => 12.2 +PostgreSQL(libpq) => PostgreSQL 12.3 on x86_64-apple-darwin18.7.0, compiled by Apple clang version 11.0.0 (clang-1100.0.33.17), 64-bit +Multibyte character support => enabled +SSL support => enabled +Active Persistent Links => 0 +Active Links => 0 + +Directive => Local Value => Master Value +pgsql.allow_persistent => On => On +pgsql.max_persistent => Unlimited => Unlimited +pgsql.max_links => Unlimited => Unlimited +pgsql.auto_reset_persistent => Off => Off +pgsql.ignore_notice => Off => Off +pgsql.log_notice => Off => Off', + array('lib-pgsql-libpq' => '12.2') + ), + 'pdo_pgsql' => array( + 'pdo_pgsql', + ' + pdo_pgsql + +PDO Driver for PostgreSQL => enabled +PostgreSQL(libpq) Version => 12.1 +Module version => 7.1.33 +Revision => $Id: 9c5f356c77143981d2e905e276e439501fe0f419 $', + array('lib-pdo_pgsql-libpq' => '12.1') + ), + 'libsodium' => array( + 'libsodium', + null, + array('lib-libsodium' => '1.0.17'), + array(), + array(array('SODIUM_LIBRARY_VERSION', null, '1.0.17')) + ), + 'libsodium: different extension name' => array( + 'sodium', + null, + array('lib-libsodium' => '1.0.15'), + array(), + array(array('SODIUM_LIBRARY_VERSION', null, '1.0.15')) + ), + 'pdo_sqlite' => array( + 'pdo_sqlite', + ' +pdo_sqlite + +PDO Driver for SQLite 3.x => enabled +SQLite Library => 3.32.3 + ', + array('lib-pdo_sqlite-sqlite' => '3.32.3') + ), + 'sqlite3' => array( + 'sqlite3', + ' +sqlite3 + +SQLite3 support => enabled +SQLite3 module version => 7.1.33 +SQLite Library => 3.31.0 + +Directive => Local Value => Master Value +sqlite3.extension_dir => no value => no value +sqlite3.defensive => 1 => 1', + array('lib-sqlite3-sqlite' => '3.31.0') + ), + 'ssh2' => array( + 'ssh2', + ' +ssh2 + +SSH2 support => enabled +extension version => 1.2 +libssh2 version => 1.8.0 +banner => SSH-2.0-libssh2_1.8.0', + array('lib-ssh2-libssh2' => '1.8.0') + ), + 'yaml' => array( + 'yaml', + ' + yaml + +LibYAML Support => enabled +Module Version => 2.0.2 +LibYAML Version => 0.2.2 + +Directive => Local Value => Master Value +yaml.decode_binary => 0 => 0 +yaml.decode_timestamp => 0 => 0 +yaml.decode_php => 0 => 0 +yaml.output_canonical => 0 => 0 +yaml.output_indent => 2 => 2 +yaml.output_width => 80 => 80', + array('lib-yaml-libyaml' => '0.2.2') + ), + 'xsl' => array( + 'xsl', + ' +xsl + +XSL => enabled +libxslt Version => 1.1.33 +libxslt compiled against libxml Version => 2.9.8 +EXSLT => enabled +libexslt Version => 1.1.29', + array( + 'lib-libxslt' => array('1.1.29', array('lib-xsl')), + 'lib-libxslt-libxml' => '2.9.8', + ), + array(), + array(array('LIBXSLT_DOTTED_VERSION', null, '1.1.29')) + ), + 'zip' => array( + 'zip', + null, + array('lib-zip-libzip' => array('1.5.0', array('lib-zip'))), + array(), + array(array('LIBZIP_VERSION', 'ZipArchive', '1.5.0')), + ), + 'zlib' => array( + 'zlib', + null, + array('lib-zlib' => '1.2.10'), + array(), + array(array('ZLIB_VERSION', null, '1.2.10')), + ), + 'zlib: no constant present' => array( + 'zlib', + ' +zlib + +ZLib Support => enabled +Stream Wrapper => compress.zlib:// +Stream Filter => zlib.inflate, zlib.deflate +Compiled Version => 1.2.8 +Linked Version => 1.2.11', + array('lib-zlib' => '1.2.11'), + ), ); - $parser = new VersionParser; + } - $this->assertSame($parser->normalize($version), $package->getVersion()); + /** + * @dataProvider getLibraryTestCases + * + * @param string|string[] $extensions + * @param string|null $info + * @param array $expectations + * @param array $functions + * @param array $constants + * @param array $classes + */ + public function testLibraryInformation( + $extensions, + $info, + array $expectations, + array $functions = array(), + array $constants = array(), + array $classDefinitions = array() + ) + { + $extensions = (array)$extensions; + + $extensionVersion = '100.200.300'; + + $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); + $runtime + ->method('getExtensions') + ->willReturn($extensions); + + + $runtime + ->method('getExtensionVersion') + ->willReturnMap( + array_map(function($extension) use ($extensionVersion) { + return array($extension, $extensionVersion); + }, $extensions) + ); + + $runtime + ->method('getExtensionInfo') + ->willReturnMap( + array_map(function ($extension) use ($info) { + return array($extension, $info); + }, $extensions) + ); + + $runtime + ->method('invoke') + ->willReturnMap($functions); + + $constants[] = array('PHP_VERSION', null, '7.1.0'); + $runtime + ->method('hasConstant') + ->willReturnMap( + array_map( + function ($constantDefintion) { return array($constantDefintion[0], $constantDefintion[1], true); }, + $constants + ) + ); + $runtime + ->method('getConstant') + ->willReturnMap($constants); + + $runtime + ->method('hasClass') + ->willReturnMap( + array_map( + function ($classDefinition) { return array($classDefinition[0], true); }, + $classDefinitions + ) + ); + $runtime + ->method('construct') + ->willReturnMap($classDefinitions); + + $platformRepository = new PlatformRepository(array(), array(), $runtime); + + $expectations = array_map(function ($expectation) { + return array_replace(array(null, array(), array()), (array) $expectation); + }, $expectations); + + $libraries = array_map( + function ($package) { + return $package['name']; + }, array_filter( + $platformRepository->search('lib', PlatformRepository::SEARCH_NAME), + function ($package) { + return strpos($package['name'], 'lib-') === 0; + } + ) + ); + $expectedLibraries = array_merge(array_keys(array_filter($expectations,function($expectation) { return $expectation[0] !== false; }))); + self::assertCount(count(array_filter($expectedLibraries)), $libraries, sprintf('Expected: %s, got %s', var_export($expectedLibraries, true), var_export($libraries, true))); + + $expectations = array_merge($expectations, array_combine(array_map(function($extension) { + return 'ext-'.$extension; + }, $extensions), array_fill(0, count($extensions), array($extensionVersion, array(), array())))); + + foreach ($expectations as $packageName => $expectation) { + list($expectedVersion, $expectedReplaces, $expectedProvides) = $expectation; + + $package = $platformRepository->findPackage($packageName, '*'); + if ($expectedVersion === false) { + self::assertNull($package, sprintf('Expected to not find package "%s"', $packageName)); + } else { + self::assertNotNull($package, sprintf('Expected to find package "%s"', $packageName)); + self::assertSame($expectedVersion, $package->getPrettyVersion(), sprintf('Expected version %s for %s', $expectedVersion, $packageName)); + $this->assertPackageLinks('replaces', $expectedReplaces, $package, $package->getReplaces()); + $this->assertPackageLinks('provides', $expectedProvides, $package, $package->getProvides()); + } + } + } + + private function assertPackageLinks($context, array $expectedLinks, Package $sourcePackage, array $links) + { + self::assertCount(count($expectedLinks), $links, sprintf('%s: expected package count to match', $context)); + + foreach ($links as $link) { + self::assertSame($sourcePackage->getName(), $link->getSource()); + self::assertContains($link->getTarget(), $expectedLinks, sprintf('%s: package %s not in %s', $context, $link->getTarget(), var_export($expectedLinks, true))); + self::assertTrue($link->getConstraint()->matches($this->getVersionConstraint('=', $sourcePackage->getVersion()))); + } + } +} + +class ResourceBundleStub { + const STUB_VERSION = '32.0.1'; + + public static function create($locale, $bundleName, $fallback) { + Assert::assertSame(3, func_num_args()); + Assert::assertSame('root', $locale); + Assert::assertSame('ICUDATA', $bundleName); + Assert::assertFalse($fallback); + + return new self(); + } + + public function get($field) { + Assert::assertSame(1, func_num_args()); + Assert::assertSame('Version', $field); + + return self::STUB_VERSION; + } +} + +class ImagickStub { + private $versionString; + + public function __construct($versionString) { + $this->versionString = $versionString; + } + + public function getVersion() { + Assert::assertSame(0, func_num_args()); + + return array('versionString' => $this->versionString); } } diff --git a/tests/Composer/Test/Util/VersionTest.php b/tests/Composer/Test/Util/VersionTest.php new file mode 100644 index 000000000..907e1e472 --- /dev/null +++ b/tests/Composer/Test/Util/VersionTest.php @@ -0,0 +1,131 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Version; +use Composer\Test\TestCase; + +/** + * @author Lars Strojny + */ +class VersionTest extends TestCase +{ + /** + * Create normalized test data set + * + * 1) Clone OpenSSL repository + * 2) git log --pretty=%h --all -- crypto/opensslv.h include/openssl/opensslv.h | while read hash ; do (git show $hash:crypto/opensslv.h; git show $hash:include/openssl/opensslv.h) | grep "define OPENSSL_VERSION_TEXT" ; done > versions.txt + * 3) cat versions.txt | awk -F "OpenSSL " '{print $2}' | awk -F " " '{print $1}' | sed -e "s:\([0-9]*\.[0-9]*\.[0-9]*\):1.2.3:g" -e "s:1\.2\.3[a-z]\(-.*\)\{0,1\}$:1.2.3a\1:g" -e "s:1\.2\.3[a-z]\{2\}\(-.*\)\{0,1\}$:1.2.3zh\1:g" -e "s:beta[0-9]:beta3:g" -e "s:pre[0-9]*:pre2:g" | sort | uniq + */ + public static function getOpenSslVersions() + { + return array( + // Generated + array('1.2.3', '1.2.3.0'), + array('1.2.3-beta3', '1.2.3.0-beta3'), + array('1.2.3-beta3-dev', '1.2.3.0-beta3-dev'), + array('1.2.3-beta3-fips', '1.2.3.0-beta3', true), + array('1.2.3-beta3-fips-dev', '1.2.3.0-beta3-dev', true), + array('1.2.3-dev', '1.2.3.0-dev'), + array('1.2.3-fips', '1.2.3.0', true), + array('1.2.3-fips-beta3', '1.2.3.0-beta3', true), + array('1.2.3-fips-beta3-dev', '1.2.3.0-beta3-dev', true), + array('1.2.3-fips-dev', '1.2.3.0-dev', true), + array('1.2.3-pre2', '1.2.3.0-alpha2'), + array('1.2.3-pre2-dev', '1.2.3.0-alpha2-dev'), + array('1.2.3-pre2-fips', '1.2.3.0-alpha2', true), + array('1.2.3-pre2-fips-dev', '1.2.3.0-alpha2-dev', true), + array('1.2.3a', '1.2.3.1'), + array('1.2.3a-beta3','1.2.3.1-beta3'), + array('1.2.3a-beta3-dev', '1.2.3.1-beta3-dev'), + array('1.2.3a-dev', '1.2.3.1-dev'), + array('1.2.3a-dev-fips', '1.2.3.1-dev', true), + array('1.2.3a-fips', '1.2.3.1', true), + array('1.2.3a-fips-beta3', '1.2.3.1-beta3', true), + array('1.2.3a-fips-dev', '1.2.3.1-dev', true), + array('1.2.3beta3', '1.2.3.0-beta3'), + array('1.2.3beta3-dev', '1.2.3.0-beta3-dev'), + array('1.2.3zh', '1.2.3.34'), + array('1.2.3zh-dev', '1.2.3.34-dev'), + array('1.2.3zh-fips', '1.2.3.34',true), + array('1.2.3zh-fips-dev', '1.2.3.34-dev', true), + // Additional cases + array('1.2.3zh-fips-rc3', '1.2.3.34-rc3', true, '1.2.3.34-RC3'), + array('1.2.3zh-alpha10-fips', '1.2.3.34-alpha10', true), + // Check that alphabetical patch levels overflow correctly + array('1.2.3', '1.2.3.0'), + array('1.2.3a', '1.2.3.1'), + array('1.2.3z', '1.2.3.26'), + array('1.2.3za', '1.2.3.27'), + array('1.2.3zy', '1.2.3.51'), + array('1.2.3zz', '1.2.3.52'), + ); + } + + /** + * @dataProvider getOpenSslVersions + * @param string $input + * @param string $parsedVersion + * @param bool $fipsExpected + * @param string|null $normalizedVersion + */ + public function testParseOpensslVersions($input, $parsedVersion, $fipsExpected = false, $normalizedVersion = null) + { + self::assertSame($parsedVersion, Version::parseOpenssl($input, $isFips)); + self::assertSame($fipsExpected, $isFips); + + $normalizedVersion = $normalizedVersion ? $normalizedVersion : $parsedVersion; + self::assertSame($normalizedVersion, $this->getVersionParser()->normalize($parsedVersion)); + } + + public function getLibJpegVersions() + { + return array( + array('9', '9.0'), + array('9a', '9.1'), + array('9b', '9.2'), + // Never seen in the wild, just for overflow correctness + array('9za', '9.27'), + ); + } + + /** + * @dataProvider getLibJpegVersions + * @param string $input + * @param string $parsedVersion + */ + public function testParseLibjpegVersion($input, $parsedVersion) + { + self::assertSame($parsedVersion, Version::parseLibjpeg($input)); + } + + public function getZoneinfoVersions() + { + return array( + array('2019c', '2019.3'), + array('2020a', '2020.1'), + // Never happened so far but fixate overflow behavior + array('2020za', '2020.27'), + ); + } + + /** + * @dataProvider getZoneinfoVersions + * @param string $input + * @param string $parsedVersion + */ + public function testParseZoneinfoVersion($input, $parsedVersion) + { + self::assertSame($parsedVersion, Version::parseZoneinfoVersion($input)); + } +} From 4d20e6f5d651b815ff16adf2a5ceeb3b77dd1bcb Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 13 Aug 2020 15:31:35 +0200 Subject: [PATCH 30/52] Move Version util to Platform namespace, fix CS nitpicks, make regexes case insensitive for robustness, refs #9093 --- src/Composer/Platform/HhvmDetector.php | 8 ++- src/Composer/Platform/Runtime.php | 15 +++-- src/Composer/{Util => Platform}/Version.php | 2 +- .../Repository/PlatformRepository.php | 62 +++++++++---------- .../Test/Platform/HhvmDetectorTest.php | 6 +- .../Test/{Util => Platform}/VersionTest.php | 4 +- 6 files changed, 53 insertions(+), 44 deletions(-) rename src/Composer/{Util => Platform}/Version.php (98%) rename tests/Composer/Test/{Util => Platform}/VersionTest.php (98%) diff --git a/src/Composer/Platform/HhvmDetector.php b/src/Composer/Platform/HhvmDetector.php index a0524e758..ac63d047b 100644 --- a/src/Composer/Platform/HhvmDetector.php +++ b/src/Composer/Platform/HhvmDetector.php @@ -21,8 +21,8 @@ class HhvmDetector private $executableFinder; private $processExecutor; - public function __construct(ExecutableFinder $executableFinder = null, ProcessExecutor $processExecutor = null) { - + public function __construct(ExecutableFinder $executableFinder = null, ProcessExecutor $processExecutor = null) + { $this->executableFinder = $executableFinder; $this->processExecutor = $processExecutor; } @@ -32,7 +32,8 @@ class HhvmDetector self::$hhvmVersion = null; } - public function getVersion() { + public function getVersion() + { if (null !== self::$hhvmVersion) { return self::$hhvmVersion ?: null; } @@ -54,6 +55,7 @@ class HhvmDetector } } } + return self::$hhvmVersion; } } diff --git a/src/Composer/Platform/Runtime.php b/src/Composer/Platform/Runtime.php index baf450df1..82211ec88 100644 --- a/src/Composer/Platform/Runtime.php +++ b/src/Composer/Platform/Runtime.php @@ -19,7 +19,8 @@ class Runtime * @param class-string $class * @return bool */ - public function hasConstant($constant, $class = null) { + public function hasConstant($constant, $class = null) + { return defined(ltrim($class.'::'.$constant, ':')); } @@ -28,7 +29,8 @@ class Runtime * @param class-string $class * @return mixed */ - public function getConstant($constant, $class = null) { + public function getConstant($constant, $class = null) + { return constant(ltrim($class.'::'.$constant, ':')); } @@ -37,7 +39,8 @@ class Runtime * @param array $arguments * @return mixed */ - public function invoke($callable, array $arguments = array()) { + public function invoke($callable, array $arguments = array()) + { return call_user_func_array($callable, $arguments); } @@ -45,7 +48,8 @@ class Runtime * @param class-string $class * @return bool */ - public function hasClass($class) { + public function hasClass($class) + { return class_exists($class, false); } @@ -54,7 +58,8 @@ class Runtime * @param array $arguments * @return object */ - public function construct($class, array $arguments = array()) { + public function construct($class, array $arguments = array()) + { if (empty($arguments)) { return new $class; } diff --git a/src/Composer/Util/Version.php b/src/Composer/Platform/Version.php similarity index 98% rename from src/Composer/Util/Version.php rename to src/Composer/Platform/Version.php index 1907816c0..ab7cae0c4 100644 --- a/src/Composer/Util/Version.php +++ b/src/Composer/Platform/Version.php @@ -10,7 +10,7 @@ * file that was distributed with this source code. */ -namespace Composer\Util; +namespace Composer\Platform; /** * @author Lars Strojny diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 899b177cf..850b40acb 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -19,10 +19,10 @@ use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; use Composer\Platform\HhvmDetector; use Composer\Platform\Runtime; +use Composer\Platform\Version; use Composer\Plugin\PluginInterface; use Composer\Semver\Constraint\Constraint; use Composer\Util\Silencer; -use Composer\Util\Version; use Composer\XdebugHandler\XdebugHandler; /** @@ -156,12 +156,12 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // librabbitmq version => 0.9.0 - if (preg_match('/^librabbitmq version => (?.+)$/m', $info, $librabbitmqMatches)) { + if (preg_match('/^librabbitmq version => (?.+)$/im', $info, $librabbitmqMatches)) { $this->addLibrary($name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); } // AMQP protocol version => 0-9-1 - if (preg_match('/^AMQP protocol version => (?.+)$/m', $info, $protocolMatches)) { + if (preg_match('/^AMQP protocol version => (?.+)$/im', $info, $protocolMatches)) { $this->addLibrary($name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); } break; @@ -170,7 +170,7 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // BZip2 Version => 1.0.6, 6-Sept-2010 - if (preg_match('/^BZip2 Version => (?.*),/m', $info, $matches)) { + if (preg_match('/^BZip2 Version => (?.*),/im', $info, $matches)) { $this->addLibrary($name, $matches['version']); } break; @@ -182,7 +182,7 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // SSL Version => OpenSSL/1.0.1t - if (preg_match('{^SSL Version => (?[^/]+)/(?.+)$}m', $info, $sslMatches)) { + if (preg_match('{^SSL Version => (?[^/]+)/(?.+)$}im', $info, $sslMatches)) { $library = strtolower($sslMatches['library']); if ($library === 'openssl') { $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); @@ -193,12 +193,12 @@ class PlatformRepository extends ArrayRepository } // libSSH Version => libssh2/1.4.3 - if (preg_match('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}m', $info, $sshMatches)) { + if (preg_match('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}im', $info, $sshMatches)) { $this->addLibrary($name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); } // ZLib Version => 1.2.8 - if (preg_match('{^ZLib Version => (?.+)$}m', $info, $zlibMatches)) { + if (preg_match('{^ZLib Version => (?.+)$}im', $info, $zlibMatches)) { $this->addLibrary($name.'-zlib', $zlibMatches['version'], 'curl zlib version'); } break; @@ -207,14 +207,14 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // timelib version => 2018.03 - if (preg_match('/^timelib version => (?.+)$/m', $info, $timelibMatches)) { + if (preg_match('/^timelib version => (?.+)$/im', $info, $timelibMatches)) { $this->addLibrary($name.'-timelib', $timelibMatches['version'], 'date timelib version'); } // Timezone Database => internal - if (preg_match('/^Timezone Database => (?internal|external)$/m', $info, $zoneinfoSourceMatches)) { + if (preg_match('/^Timezone Database => (?internal|external)$/im', $info, $zoneinfoSourceMatches)) { $external = $zoneinfoSourceMatches['source'] === 'external'; - if (preg_match('/^"Olson" Timezone Database Version => (?.+?)(\.system)?$/m', $info, $zoneinfoMatches)) { + if (preg_match('/^"Olson" Timezone Database Version => (?.+?)(\.system)?$/im', $info, $zoneinfoMatches)) { // If the timezonedb is provided by ext/timezonedb, register that version as a replacement if ($external && in_array('timezonedb', $loadedExtensions, true)) { $this->addLibrary('timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', array($name.'-zoneinfo')); @@ -229,7 +229,7 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // libmagic => 537 - if (preg_match('/^^libmagic => (?.+)$/m', $info, $magicMatches)) { + if (preg_match('/^libmagic => (?.+)$/im', $info, $magicMatches)) { $this->addLibrary($name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); } break; @@ -239,19 +239,19 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); - if (preg_match('/^libJPEG Version => (?.+?)(?: compatible)?$/m', $info, $libjpegMatches)) { + if (preg_match('/^libJPEG Version => (?.+?)(?: compatible)?$/im', $info, $libjpegMatches)) { $this->addLibrary($name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); } - if (preg_match('/^libPNG Version => (?.+)$/m', $info, $libpngMatches)) { + if (preg_match('/^libPNG Version => (?.+)$/im', $info, $libpngMatches)) { $this->addLibrary($name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); } - if (preg_match('/^FreeType Version => (?.+)$/m', $info, $freetypeMatches)) { + if (preg_match('/^FreeType Version => (?.+)$/im', $info, $freetypeMatches)) { $this->addLibrary($name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); } - if (preg_match('/^libXpm Version => (?\d+)$/m', $info, $libxpmMatches)) { + if (preg_match('/^libXpm Version => (?\d+)$/im', $info, $libxpmMatches)) { $this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId($libxpmMatches['versionId']), 'libxpm version for gd'); } @@ -272,16 +272,16 @@ class PlatformRepository extends ArrayRepository // Truthy check is for testing only so we can make the condition fail if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { $this->addLibrary('icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); - } elseif (preg_match('/^ICU version => (?.+)$/m', $info, $matches)) { + } elseif (preg_match('/^ICU version => (?.+)$/im', $info, $matches)) { $this->addLibrary('icu', $matches['version'], $description); } // ICU TZData version => 2019c - if (preg_match('/^ICU TZData version => (?.*)$/m', $info, $zoneinfoMatches)) { + if (preg_match('/^ICU TZData version => (?.*)$/im', $info, $zoneinfoMatches)) { $this->addLibrary('icu-zoneinfo', Version::parseZoneinfoVersion($zoneinfoMatches['version']), 'zoneinfo ("Olson") database for icu'); } - # Add a separate version for the CLDR library version + // Add a separate version for the CLDR library version if ($this->runtime->hasClass('ResourceBundle')) { $cldrVersion = $this->runtime->invoke(array('ResourceBundle', 'create'), array('root', 'ICUDATA', false))->get('Version'); $this->addLibrary('icu-cldr', $cldrVersion, 'ICU CLDR project version'); @@ -308,7 +308,7 @@ class PlatformRepository extends ArrayRepository case 'ldap': $info = $this->runtime->getExtensionInfo($name); - if (preg_match('/^Vendor Version => (?\d+)$/m', $info, $matches) && preg_match('/^Vendor Name => (?.+)$/m', $info, $vendorMatches)) { + if (preg_match('/^Vendor Version => (?\d+)$/im', $info, $matches) && preg_match('/^Vendor Name => (?.+)$/im', $info, $vendorMatches)) { $this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId($matches['versionId']), $vendorMatches['vendor'].' version of ldap'); } break; @@ -326,7 +326,7 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // libmbfl version => 1.3.2 - if (preg_match('/^libmbfl version => (?.+)$/m', $info, $libmbflMatches)) { + if (preg_match('/^libmbfl version => (?.+)$/im', $info, $libmbflMatches)) { $this->addLibrary($name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); } @@ -335,7 +335,7 @@ class PlatformRepository extends ArrayRepository // Multibyte regex (oniguruma) version => 5.9.5 // oniguruma version => 6.9.0 - } elseif (preg_match('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/m', $info, $onigurumaMatches)) { + } elseif (preg_match('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/im', $info, $onigurumaMatches)) { $this->addLibrary($name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); } @@ -345,7 +345,7 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // libmemcached version => 1.0.18 - if (preg_match('/^libmemcached version => (?.+)$/m', $info, $matches)) { + if (preg_match('/^libmemcached version => (?.+)$/im', $info, $matches)) { $this->addLibrary($name.'-libmemcached', $matches['version'], 'libmemcached version'); } break; @@ -364,7 +364,7 @@ class PlatformRepository extends ArrayRepository $info = $this->runtime->getExtensionInfo($name); // PCRE Unicode Version => 12.1.0 - if (preg_match('/^PCRE Unicode Version => (?.+)$/m', $info, $pcreUnicodeMatches)) { + if (preg_match('/^PCRE Unicode Version => (?.+)$/im', $info, $pcreUnicodeMatches)) { $this->addLibrary($name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); } @@ -382,11 +382,11 @@ class PlatformRepository extends ArrayRepository case 'mongodb': $info = $this->runtime->getExtensionInfo($name); - if (preg_match('/^libmongoc bundled version => (?.+)$/m', $info, $libmongocMatches)) { + if (preg_match('/^libmongoc bundled version => (?.+)$/im', $info, $libmongocMatches)) { $this->addLibrary($name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); } - if (preg_match('/^libbson bundled version => (?.+)$/m', $info, $libbsonMatches)) { + if (preg_match('/^libbson bundled version => (?.+)$/im', $info, $libbsonMatches)) { $this->addLibrary($name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); } break; @@ -395,7 +395,7 @@ class PlatformRepository extends ArrayRepository case 'pdo_pgsql': $info = $this->runtime->getExtensionInfo($name); - if (preg_match('/^PostgreSQL\(libpq\) Version => (?.*)$/m', $info, $matches)) { + if (preg_match('/^PostgreSQL\(libpq\) Version => (?.*)$/im', $info, $matches)) { $this->addLibrary($name.'-libpq', $matches['version'], 'libpq for '.$name); } break; @@ -409,7 +409,7 @@ class PlatformRepository extends ArrayRepository case 'pdo_sqlite': $info = $this->runtime->getExtensionInfo($name); - if (preg_match('/^SQLite Library => (?.+)$/m', $info, $matches)) { + if (preg_match('/^SQLite Library => (?.+)$/im', $info, $matches)) { $this->addLibrary($name.'-sqlite', $matches['version']); } break; @@ -417,7 +417,7 @@ class PlatformRepository extends ArrayRepository case 'ssh2': $info = $this->runtime->getExtensionInfo($name); - if (preg_match('/^libssh2 version => (?.+)$/m', $info, $matches)) { + if (preg_match('/^libssh2 version => (?.+)$/im', $info, $matches)) { $this->addLibrary($name.'-libssh2', $matches['version']); } break; @@ -426,7 +426,7 @@ class PlatformRepository extends ArrayRepository $this->addLibrary('libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, array('xsl')); $info = $this->runtime->getExtensionInfo('xsl'); - if (preg_match('/^libxslt compiled against libxml Version => (?.+)$/m', $info, $matches)) { + if (preg_match('/^libxslt compiled against libxml Version => (?.+)$/im', $info, $matches)) { $this->addLibrary('libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); } break; @@ -434,7 +434,7 @@ class PlatformRepository extends ArrayRepository case 'yaml': $info = $this->runtime->getExtensionInfo('yaml'); - if (preg_match('/^LibYAML Version => (?.+)$/m', $info, $matches)) { + if (preg_match('/^LibYAML Version => (?.+)$/im', $info, $matches)) { $this->addLibrary($name.'-libyaml', $matches['version'], 'libyaml version of yaml'); } break; @@ -450,7 +450,7 @@ class PlatformRepository extends ArrayRepository $this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION')); // Linked Version => 1.2.8 - } elseif (preg_match('/^Linked Version => (?.+)$/m', $this->runtime->getExtensionInfo($name), $matches)) { + } elseif (preg_match('/^Linked Version => (?.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) { $this->addLibrary($name, $matches['version']); } break; diff --git a/tests/Composer/Test/Platform/HhvmDetectorTest.php b/tests/Composer/Test/Platform/HhvmDetectorTest.php index 3e7e63228..600ef7193 100644 --- a/tests/Composer/Test/Platform/HhvmDetectorTest.php +++ b/tests/Composer/Test/Platform/HhvmDetectorTest.php @@ -28,7 +28,8 @@ class HhvmDetectorTest extends TestCase $this->hhvmDetector->reset(); } - public function testHHVMVersionWhenExecutingInHHVM() { + public function testHHVMVersionWhenExecutingInHHVM() + { if (!defined('HHVM_VERSION_ID')) { self::markTestSkipped('Not running with HHVM'); return; @@ -37,7 +38,8 @@ class HhvmDetectorTest extends TestCase self::assertSame(self::versionIdToVersion(), $version); } - public function testHHVMVersionWhenExecutingInPHP() { + public function testHHVMVersionWhenExecutingInPHP() + { if (defined('HHVM_VERSION_ID')) { self::markTestSkipped('Running with HHVM'); return; diff --git a/tests/Composer/Test/Util/VersionTest.php b/tests/Composer/Test/Platform/VersionTest.php similarity index 98% rename from tests/Composer/Test/Util/VersionTest.php rename to tests/Composer/Test/Platform/VersionTest.php index 907e1e472..5d5452143 100644 --- a/tests/Composer/Test/Util/VersionTest.php +++ b/tests/Composer/Test/Platform/VersionTest.php @@ -10,9 +10,9 @@ * file that was distributed with this source code. */ -namespace Composer\Test\Util; +namespace Composer\Test\Platform; -use Composer\Util\Version; +use Composer\Platform\Version; use Composer\Test\TestCase; /** From c845d66818aa159a730aa4f0d21a75ad34e2e3b0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 13 Aug 2020 15:31:57 +0200 Subject: [PATCH 31/52] Lowercase ext- package names, refs #9093 --- src/Composer/Repository/PlatformRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 850b40acb..5a8f70627 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -560,7 +560,7 @@ class PlatformRepository extends ArrayRepository */ private function buildPackageName($name) { - return 'ext-' . str_replace(' ', '-', $name); + return 'ext-' . str_replace(' ', '-', strtolower($name)); } /** From 2279b6fdad4ed2f095b6d3db15c9e58eb65c8c6d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Aug 2020 15:57:39 +0200 Subject: [PATCH 32/52] phpstan natively sends github action formatted errors no need to use cs2pr for now --- .github/workflows/phpstan.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index d2894e09b..20cc33f3d 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -34,7 +34,6 @@ jobs: extensions: "intl, zip" ini-values: "memory_limit=-1" php-version: "${{ matrix.php-version }}" - tools: "cs2pr" - name: "Determine composer cache directory" id: "determine-composer-cache-directory" @@ -53,4 +52,4 @@ jobs: - name: Run PHPStan run: | bin/composer require --dev phpstan/phpstan:^0.12.26 phpunit/phpunit:^7.5 --with-all-dependencies - vendor/bin/phpstan analyse --configuration=phpstan/config.neon || vendor/bin/phpstan analyse --configuration=phpstan/config.neon --error-format=checkstyle | cs2pr + vendor/bin/phpstan analyse --configuration=phpstan/config.neon From fdff3aeaba2183e236f99dea4b284559b85f7e23 Mon Sep 17 00:00:00 2001 From: Markus Staab <47448731+clxmstaab@users.noreply.github.com> Date: Thu, 13 Aug 2020 16:37:32 +0200 Subject: [PATCH 33/52] emit github action formatted error messages (#9120) --- .github/workflows/continuous-integration.yml | 1 + src/Composer/Console/Application.php | 22 ++++++++++- src/Composer/Console/GithubActionError.php | 40 ++++++++++++++++++++ src/Composer/Installer.php | 31 ++++++++++++--- 4 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/Composer/Console/GithubActionError.php diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 107aa2530..a57c5a804 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -11,6 +11,7 @@ on: env: COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist" COMPOSER_UPDATE_FLAGS: "" + COMPOSER_TESTS_ARE_RUNNING: "1" SYMFONY_PHPUNIT_VERSION: "8.3" SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT: "1" diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 622938476..acd0e4c0a 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -22,6 +22,7 @@ use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Seld\JsonLint\ParsingException; use Composer\Command; use Composer\Composer; use Composer\Factory; @@ -191,6 +192,20 @@ class Application extends BaseApplication } } catch (NoSslException $e) { // suppress these as they are not relevant at this point + } catch (ParsingException $e) { + $details = $e->getDetails(); + + $file = realpath(Factory::getComposerFile()); + + $line = null; + if ($details && isset($details['line'])) { + $line = $details['line']; + } + + $ghe = new GithubActionError($this->io); + $ghe->emit($e->getMessage(), $file, $line); + + throw $e; } $this->hasPluginCommands = true; @@ -237,7 +252,7 @@ class Application extends BaseApplication if (function_exists('posix_getuid') && posix_getuid() === 0) { if ($commandName !== 'self-update' && $commandName !== 'selfupdate') { $io->writeError('Do not run Composer as root/super user! See https://getcomposer.org/root for details'); - + if ($io->isInteractive()) { if (!$io->askConfirmation('Continue as root/super user [yes]? ', true)) { return 1; @@ -307,8 +322,13 @@ class Application extends BaseApplication } catch (ScriptExecutionException $e) { return (int) $e->getCode(); } catch (\Exception $e) { + $ghe = new GithubActionError($this->io); + $ghe->emit($e->getMessage()); + $this->hintCommonErrors($e); + restore_error_handler(); + throw $e; } } diff --git a/src/Composer/Console/GithubActionError.php b/src/Composer/Console/GithubActionError.php new file mode 100644 index 000000000..bbf5eaca1 --- /dev/null +++ b/src/Composer/Console/GithubActionError.php @@ -0,0 +1,40 @@ +io = $io; + } + + /** + * @param string $message + * @param null|string $file + * @param null|int $line + */ + public function emit($message, $file = null, $line = null) + { + if (getenv('GITHUB_ACTIONS') && !getenv('COMPOSER_TESTS_ARE_RUNNING')) { + // newlines need to be encoded + // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + $message = str_replace("\n", '%0A', $message); + + if ($file && $line) { + $this->io->write("::error file=". $file .",line=". $line ."::". $message); + } elseif ($file) { + $this->io->write("::error file=". $file ."::". $message); + } else { + $this->io->write("::error ::". $message); + } + } + } +} diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index b98a88c12..f8c788e10 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -13,6 +13,7 @@ namespace Composer; use Composer\Autoload\AutoloadGenerator; +use Composer\Console\GithubActionError; use Composer\DependencyResolver\DefaultPolicy; use Composer\DependencyResolver\LocalRepoTransaction; use Composer\DependencyResolver\LockTransaction; @@ -412,12 +413,18 @@ class Installer $ruleSetSize = $solver->getRuleSetSize(); $solver = null; } catch (SolverProblemsException $e) { - $this->io->writeError('Your requirements could not be resolved to an installable set of packages.', true, IOInterface::QUIET); - $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose())); + $err = 'Your requirements could not be resolved to an installable set of packages.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError($prettyProblem); if (!$this->devMode) { $this->io->writeError('Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.', true, IOInterface::QUIET); } + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); + return max(1, $e->getCode()); } @@ -571,10 +578,16 @@ class Installer $nonDevLockTransaction = $solver->solve($request, $this->ignorePlatformReqs); $solver = null; } catch (SolverProblemsException $e) { - $this->io->writeError('Unable to find a compatible set of packages based on your non-dev requirements alone.', true, IOInterface::QUIET); + $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.'); $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.'); - $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true)); + $this->io->writeError($prettyProblem); + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); return max(1, $e->getCode()); } @@ -637,8 +650,14 @@ class Installer return 1; } } catch (SolverProblemsException $e) { - $this->io->writeError('Your lock file does not contain a compatible set of packages. Please run composer update.', true, IOInterface::QUIET); - $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose())); + $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.'; + $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()); + + $this->io->writeError(''. $err .'', true, IOInterface::QUIET); + $this->io->writeError($prettyProblem); + + $ghe = new GithubActionError($this->io); + $ghe->emit($err."\n".$prettyProblem); return max(1, $e->getCode()); } From 3be62a9fdaa1cafbe8a990686317609a875bfa6f Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Fri, 14 Aug 2020 17:45:41 +0100 Subject: [PATCH 34/52] Fix openssl_free_key deprecation notice in PHP 8 --- src/Composer/Command/SelfUpdateCommand.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index d4d13364e..348451e76 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -271,7 +271,12 @@ TAGSPUBKEY $signature = json_decode($signature, true); $signature = base64_decode($signature['sha384']); $verified = 1 === openssl_verify(file_get_contents($tempFilename), $signature, $pubkeyid, $algo); - openssl_free_key($pubkeyid); + + // PHP 8 automatically frees the key instance and deprecates the function + if (PHP_VERSION_ID < 80000) { + openssl_free_key($pubkeyid); + } + if (!$verified) { throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified'); } From f5c2bdb783cdcafd8b3c19611a353ef4187be59d Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 18 Aug 2020 10:23:09 +0100 Subject: [PATCH 35/52] Use latest cache action --- .github/workflows/continuous-integration.yml | 2 +- .github/workflows/phpstan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index a57c5a804..a70a01cba 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -109,7 +109,7 @@ jobs: run: "echo \"::set-output name=directory::$(composer config cache-dir)\"" - name: "Cache dependencies installed with composer" - uses: "actions/cache@v1" + uses: "actions/cache@v2" with: path: "${{ steps.determine-composer-cache-directory.outputs.directory }}" key: "php-${{ matrix.php-version }}-symfony-php-unit-version-${{ env.SYMFONY_PHPUNIT_VERSION }}-${{ hashFiles('**/composer.lock') }}" diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 20cc33f3d..4e4ce79d2 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -40,7 +40,7 @@ jobs: run: "echo \"::set-output name=directory::$(composer config cache-dir)\"" - name: "Cache dependencies installed with composer" - uses: "actions/cache@v1" + uses: "actions/cache@v2" with: path: "${{ steps.determine-composer-cache-directory.outputs.directory }}" key: "php-${{ matrix.php-version }}-symfony-php-unit-version-${{ env.SYMFONY_PHPUNIT_VERSION }}-${{ hashFiles('**/composer.lock') }}" From 99d4b802fb7cca04b244f77815bbe919f5af062c Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 18 Aug 2020 10:23:26 +0100 Subject: [PATCH 36/52] Bumped minimum phpstan versions --- .github/workflows/phpstan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 4e4ce79d2..d92872609 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -51,5 +51,5 @@ jobs: - name: Run PHPStan run: | - bin/composer require --dev phpstan/phpstan:^0.12.26 phpunit/phpunit:^7.5 --with-all-dependencies + bin/composer require --dev phpstan/phpstan:^0.12.37 phpunit/phpunit:^7.5.20 --with-all-dependencies vendor/bin/phpstan analyse --configuration=phpstan/config.neon From 4e06aa051a7d27966fe9b0bd548cc2fc0e6fa55f Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Tue, 18 Aug 2020 16:00:44 +0200 Subject: [PATCH 37/52] Check if inet_pton() exists --- src/Composer/Platform/Runtime.php | 9 +++++++++ src/Composer/Repository/PlatformRepository.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Composer/Platform/Runtime.php b/src/Composer/Platform/Runtime.php index 82211ec88..3c77a9250 100644 --- a/src/Composer/Platform/Runtime.php +++ b/src/Composer/Platform/Runtime.php @@ -34,6 +34,15 @@ class Runtime return constant(ltrim($class.'::'.$constant, ':')); } + /** + * @param string $fn + * @return bool + */ + public function hasFunction($fn) + { + return function_exists($fn); + } + /** * @param callable $callable * @param array $arguments diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 5a8f70627..c72c88f14 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -125,7 +125,7 @@ class PlatformRepository extends ArrayRepository // The AF_INET6 constant is only defined if ext-sockets is available but // IPv6 support might still be available. - if ($this->runtime->hasConstant('AF_INET6') || Silencer::call(array($this->runtime, 'invoke'), array('inet_pton', '::')) !== false) { + if ($this->runtime->hasConstant('AF_INET6') || ($this->runtime->hasFunction('inet_pton') && Silencer::call(array($this->runtime, 'invoke'), array('inet_pton', '::')) !== false)) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); From 99fd5c7b493471e2437ccfa7992656719b9e9c74 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Tue, 18 Aug 2020 16:05:40 +0200 Subject: [PATCH 38/52] Add tests --- .../Repository/PlatformRepositoryTest.php | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index d90fcbc73..a608ed7ff 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -95,13 +95,28 @@ class PlatformRepositoryTest extends TestCase ), array( array('inet_pton', array('::'), ''), + ), + array( + array('inet_pton', true), + ) + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + ), + array( + 'php' => '7.2.31', + ), + array(), + array( + 'inet_pton' => false, ) ) ); } /** @dataProvider getPhpFlavorTestCases */ - public function testPhpVersion(array $constants, array $packages, array $functions = array()) + public function testPhpVersion(array $constants, array $packages, array $functionMap = array(), array $functionExists = array()) { $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); $runtime @@ -123,7 +138,11 @@ class PlatformRepositoryTest extends TestCase ); $runtime ->method('invoke') - ->willReturnMap($functions); + ->willReturnMap($functionMap); + + $runtime + ->method('hasFunction') + ->willReturnMap($functionExists); $repository = new PlatformRepository(array(), array(), $runtime); foreach ($packages as $packageName => $version) { From a83588f568ff6b92fcedc0a2788372bbaa4fa6c2 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Tue, 18 Aug 2020 16:30:47 +0200 Subject: [PATCH 39/52] The proper fix --- .../Repository/PlatformRepository.php | 2 +- .../Repository/PlatformRepositoryTest.php | 51 ++++++++++++++----- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index c72c88f14..ac6d49525 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -125,7 +125,7 @@ class PlatformRepository extends ArrayRepository // The AF_INET6 constant is only defined if ext-sockets is available but // IPv6 support might still be available. - if ($this->runtime->hasConstant('AF_INET6') || ($this->runtime->hasFunction('inet_pton') && Silencer::call(array($this->runtime, 'invoke'), array('inet_pton', '::')) !== false)) { + if ($this->runtime->hasConstant('AF_INET6') || Silencer::call(array($this->runtime, 'invoke'), 'inet_pton', array('::')) !== false) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index a608ed7ff..c96a00f51 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -96,9 +96,6 @@ class PlatformRepositoryTest extends TestCase array( array('inet_pton', array('::'), ''), ), - array( - array('inet_pton', true), - ) ), array( array( @@ -107,16 +104,15 @@ class PlatformRepositoryTest extends TestCase array( 'php' => '7.2.31', ), - array(), array( - 'inet_pton' => false, - ) - ) + array('inet_pton', array('::'), false), + ), + ), ); } /** @dataProvider getPhpFlavorTestCases */ - public function testPhpVersion(array $constants, array $packages, array $functionMap = array(), array $functionExists = array()) + public function testPhpVersion(array $constants, array $packages, array $functions = array()) { $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); $runtime @@ -138,11 +134,7 @@ class PlatformRepositoryTest extends TestCase ); $runtime ->method('invoke') - ->willReturnMap($functionMap); - - $runtime - ->method('hasFunction') - ->willReturnMap($functionExists); + ->willReturnMap($functions); $repository = new PlatformRepository(array(), array(), $runtime); foreach ($packages as $packageName => $version) { @@ -152,6 +144,39 @@ class PlatformRepositoryTest extends TestCase } } + public function testInetPtonRegressiom() + { + $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); + + $runtime + ->expects(self::once()) + ->method('invoke') + ->with('inet_pton', array('::')) + ->willReturn(false); + $runtime + ->method('hasConstant') + ->willReturnMap( + array( + array('PHP_ZTS', false), + array('AF_INET6', false), + ) + ); + $runtime + ->method('getExtensions') + ->willReturn(array()); + $runtime + ->method('getConstant') + ->willReturnMap( + array( + array('PHP_VERSION', null, '7.0.0'), + array('PHP_DEBUG', null, false), + ) + ); + $repository = new PlatformRepository(array(), array(), $runtime); + $package = $repository->findPackage('php-ipv6', '*'); + self::assertNull($package); + } + public static function getLibraryTestCases() { return array( From 3e750b69f42e063abb1fd5560c9589dbd3be2ed8 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Tue, 18 Aug 2020 16:31:46 +0200 Subject: [PATCH 40/52] Fix name --- tests/Composer/Test/Repository/PlatformRepositoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index c96a00f51..2da09c1dd 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -144,7 +144,7 @@ class PlatformRepositoryTest extends TestCase } } - public function testInetPtonRegressiom() + public function testInetPtonRegression() { $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); From f262feebec342733785e304333498715de524a59 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Sat, 22 Aug 2020 20:07:13 +0300 Subject: [PATCH 41/52] fixing error message for higher repository priority, when higher repo has only a dev-branch --- src/Composer/DependencyResolver/Problem.php | 6 +-- src/Composer/Repository/RepositorySet.php | 2 +- .../installer/repositories-priorities4.test | 50 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/repositories-priorities4.test diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 6d94b068b..c455a36ea 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -279,11 +279,11 @@ class Problem return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.'); } - // check if the package is found when bypassing the constraint check - if ($packages = $repositorySet->findPackages($packageName, null)) { + // check if the package is found when bypassing the constraint check and stability checks + if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { // we must first verify if a valid package would be found in a lower priority repository if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { - $higherRepoPackages = $repositorySet->findPackages($packageName, null); + $higherRepoPackages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); $nextRepoPackages = array(); $nextRepo = null; diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 9417dec97..04db0becd 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -193,7 +193,7 @@ class RepositorySet } } - return $candidates; + return $result; } public function getProviders($packageName) diff --git a/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test b/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test new file mode 100644 index 000000000..7e251002f --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test @@ -0,0 +1,50 @@ +--TEST-- +Packages found in a higher priority repository won't be considered as found if version does not match +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "ruflin/elastica", + "version": "dev-outdated-branch" + } + ] + }, + { + "type": "package", + "package": [ + { + "name": "friendsofsymfony/elastica-bundle", + "version": "dev-foobar-master", + "require": { + "ruflin/elastica": "2.*" + } + } + ] + }, + { + "type": "composer", + "url": "https://repo.packagist.org" + } + ], + "require": { + "friendsofsymfony/elastica-bundle": "dev-foobar-master" + } +} + +--RUN-- +update +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires friendsofsymfony/elastica-bundle dev-foobar-master -> satisfiable by friendsofsymfony/elastica-bundle[dev-foobar-master]. + - friendsofsymfony/elastica-bundle dev-foobar-master requires ruflin/elastica 2.* -> satisfiable by ruflin/elastica[2.0.0, ..., 2.3.3] from composer repo (https://repo.packagist.org) but ruflin/elastica[dev-outdated-branch] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance. + +--EXPECT-- +--EXPECT-EXIT-CODE-- +2 From e745e5965619b01850c11d6dc73e3a34599d7021 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Sat, 22 Aug 2020 20:11:15 +0300 Subject: [PATCH 42/52] updated repositories-priorities4.test --- .../Test/Fixtures/installer/repositories-priorities4.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test b/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test index 7e251002f..f124c76e1 100644 --- a/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test +++ b/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test @@ -1,5 +1,5 @@ --TEST-- -Packages found in a higher priority repository won't be considered as found if version does not match +Packages found in a higher priority repository take precedence even if they are not found in the requested version case #2 --COMPOSER-- { "repositories": [ From a16f32484bdd35fd344ce808cff90c6dc3b059a8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 14 Aug 2020 15:55:32 +0100 Subject: [PATCH 43/52] Downloader: add a max_file_size to prevent too big files to be downloaded --- .../MaxFileSizeExceededException.php | 7 +++++ src/Composer/Util/Http/CurlDownloader.php | 13 +++++++++ src/Composer/Util/RemoteFilesystem.php | 29 +++++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 src/Composer/Downloader/MaxFileSizeExceededException.php diff --git a/src/Composer/Downloader/MaxFileSizeExceededException.php b/src/Composer/Downloader/MaxFileSizeExceededException.php new file mode 100644 index 000000000..f50b52e79 --- /dev/null +++ b/src/Composer/Downloader/MaxFileSizeExceededException.php @@ -0,0 +1,7 @@ +jobs[$i]['progress']; $this->jobs[$i]['progress'] = $progress; + if (isset($this->jobs[$i]['options']['max_file_size'])) { + // Compare max_file_size with the content-length header this value will be -1 until the header is parsed + if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes'); + } + + // Compare max_file_size with the download size in bytes + if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes'); + } + } + // TODO //$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress); } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 615de0adb..676438c92 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -13,6 +13,7 @@ namespace Composer\Util; use Composer\Config; +use Composer\Downloader\MaxFileSizeExceededException; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; @@ -244,6 +245,11 @@ class RemoteFilesystem $degradedPackagist = true; } + $maxFileSize = null; + if (isset($options['max_file_size'])) { + $maxFileSize = $options['max_file_size']; + } + $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); $actualContextOptions = stream_context_get_options($ctx); @@ -273,7 +279,7 @@ class RemoteFilesystem }); $http_response_header = array(); try { - $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header); + $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize); if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); @@ -532,23 +538,34 @@ class RemoteFilesystem /** * Get contents of remote URL. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param resource $context The stream context + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param resource $context The stream context + * @param int $maxFileSize The maximum allowed file size * * @return string|false The response contents or false on failure */ - protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null) + protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null, $maxFileSize = null) { $result = false; try { $e = null; - $result = file_get_contents($fileUrl, false, $context); + if ($maxFileSize !== null) { + $result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize); + } else { + // passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes + $result = file_get_contents($fileUrl, false, $context); + } + } catch (\Throwable $e) { } catch (\Exception $e) { } + if ($maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes'); + } + $responseHeaders = isset($http_response_header) ? $http_response_header : array(); if (null !== $e) { From 45246aca221cc9e6bbeb76c68cf7ca3263a2d08c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 23 Aug 2020 15:05:38 +0200 Subject: [PATCH 44/52] Update deps, fixes #9125 --- composer.lock | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/composer.lock b/composer.lock index bbadaea6b..2ec2cc059 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.2.7", + "version": "1.2.8", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" + "reference": "8a7ecad675253e4654ea05505233285377405215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", - "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8a7ecad675253e4654ea05505233285377405215", + "reference": "8a7ecad675253e4654ea05505233285377405215", "shasum": "" }, "require": { @@ -60,22 +60,21 @@ "ssl", "tls" ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.2.7" - }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, + { + "url": "https://github.com/composer", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2020-04-08T08:27:21+00:00" + "time": "2020-08-23T12:54:47+00:00" }, { "name": "composer/semver", @@ -214,16 +213,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51" + "reference": "ebd27a9866ae8254e873866f795491f02418c5a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", - "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ebd27a9866ae8254e873866f795491f02418c5a5", + "reference": "ebd27a9866ae8254e873866f795491f02418c5a5", "shasum": "" }, "require": { @@ -268,7 +267,7 @@ "type": "tidelift" } ], - "time": "2020-06-04T11:16:35+00:00" + "time": "2020-08-19T10:27:58+00:00" }, { "name": "justinrainbow/json-schema", @@ -388,16 +387,16 @@ }, { "name": "seld/jsonlint", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1" + "reference": "3d5eb71705adfa34bd34b993400622932b2f62fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", - "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/3d5eb71705adfa34bd34b993400622932b2f62fd", + "reference": "3d5eb71705adfa34bd34b993400622932b2f62fd", "shasum": "" }, "require": { @@ -433,10 +432,6 @@ "parser", "validator" ], - "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/master" - }, "funding": [ { "url": "https://github.com/Seldaek", @@ -447,7 +442,7 @@ "type": "tidelift" } ], - "time": "2020-04-30T19:05:18+00:00" + "time": "2020-08-13T09:07:59+00:00" }, { "name": "seld/phar-utils", @@ -724,7 +719,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.18.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -800,7 +795,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.18.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", From 2646f09c2eaeb39614b29226809fc0fc67766292 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 23 Aug 2020 15:19:32 +0200 Subject: [PATCH 45/52] Update lock --- composer.lock | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index d3d577d9c..86f89f991 100644 --- a/composer.lock +++ b/composer.lock @@ -60,6 +60,11 @@ "ssl", "tls" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.2.8" + }, "funding": [ { "url": "https://packagist.com", @@ -281,7 +286,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/1.4.2" + "source": "https://github.com/composer/xdebug-handler/tree/1.4.3" }, "funding": [ { @@ -514,6 +519,10 @@ "parser", "validator" ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/master" + }, "funding": [ { "url": "https://github.com/Seldaek", @@ -944,7 +953,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/master" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.18.1" }, "funding": [ { From 4d837836411ded944d63b03124af19048e133817 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 23 Aug 2020 16:03:00 +0200 Subject: [PATCH 46/52] Fix test to avoid network usage --- .../installer/repositories-priorities4.test | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test b/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test index f124c76e1..9ef597daa 100644 --- a/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test +++ b/tests/Composer/Test/Fixtures/installer/repositories-priorities4.test @@ -25,8 +25,17 @@ Packages found in a higher priority repository take precedence even if they are ] }, { - "type": "composer", - "url": "https://repo.packagist.org" + "type": "package", + "package": [ + { + "name": "ruflin/elastica", + "version": "2.0.0" + }, + { + "name": "ruflin/elastica", + "version": "2.0.1" + } + ] } ], "require": { @@ -43,7 +52,7 @@ Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires friendsofsymfony/elastica-bundle dev-foobar-master -> satisfiable by friendsofsymfony/elastica-bundle[dev-foobar-master]. - - friendsofsymfony/elastica-bundle dev-foobar-master requires ruflin/elastica 2.* -> satisfiable by ruflin/elastica[2.0.0, ..., 2.3.3] from composer repo (https://repo.packagist.org) but ruflin/elastica[dev-outdated-branch] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance. + - friendsofsymfony/elastica-bundle dev-foobar-master requires ruflin/elastica 2.* -> satisfiable by ruflin/elastica[2.0.0, 2.0.1] from package repo (defining 2 packages) but ruflin/elastica[dev-outdated-branch] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance. --EXPECT-- --EXPECT-EXIT-CODE-- From 448daea69687a557a9fc9a05b80646ba02295e91 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 23 Aug 2020 16:48:07 +0200 Subject: [PATCH 47/52] Add support for detecting packages not matching only due to minimum stability --- src/Composer/DependencyResolver/Problem.php | 40 ++++++++++++------- .../installer/repositories-priorities5.test | 35 ++++++++++++++++ 2 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/repositories-priorities5.test diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index c455a36ea..9d4405330 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -276,27 +276,19 @@ class Problem // check if the package is found when bypassing stability checks if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { + // we must first verify if a valid package would be found in a lower priority repository + if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { + return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $constraint, $packages, $allReposPackages, 'minimum-stability'); + } + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.'); } - // check if the package is found when bypassing the constraint check and stability checks + // check if the package is found when bypassing the constraint and stability checks if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) { // we must first verify if a valid package would be found in a lower priority repository if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) { - $higherRepoPackages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES); - $nextRepoPackages = array(); - $nextRepo = null; - - foreach ($allReposPackages as $package) { - if ($nextRepo === null || $nextRepo === $package->getRepository()) { - $nextRepoPackages[] = $package; - $nextRepo = $package->getRepository(); - } else { - break; - } - } - - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.'); + return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $constraint, $packages, $allReposPackages, 'constraint'); } return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.'); @@ -396,6 +388,24 @@ class Problem return false; } + private static function computeCheckForLowerPrioRepo($isVerbose, $packageName, $constraint, array $higherRepoPackages, array $allReposPackages, $reason) + { + $nextRepoPackages = array(); + $nextRepo = null; + + foreach ($allReposPackages as $package) { + if ($nextRepo === null || $nextRepo === $package->getRepository()) { + $nextRepoPackages[] = $package; + $nextRepo = $package->getRepository(); + } else { + break; + } + } + + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.'); + + } + /** * Turns a constraint into text usable in a sentence describing a request * diff --git a/tests/Composer/Test/Fixtures/installer/repositories-priorities5.test b/tests/Composer/Test/Fixtures/installer/repositories-priorities5.test new file mode 100644 index 000000000..7a196988e --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/repositories-priorities5.test @@ -0,0 +1,35 @@ +--TEST-- +Packages found in a higher priority repository take precedence even if they are not found in the requested version case #3 +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "foo/a", "version": "2.0.0-dev" } + ] + }, + { + "type": "package", + "package": [ + { "name": "foo/a", "version": "2.0.0" } + ] + } + ], + "require": { + "foo/a": "2.*" + } +} +--RUN-- +update +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires foo/a 2.*, it is satisfiable by foo/a[2.0.0] from package repo (defining 1 package) but foo/a[2.0.0-dev] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your minimum-stability and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance. + +--EXPECT-- +--EXPECT-EXIT-CODE-- +2 From d140a842fa92067aaec5553cc8fbd98e5943f2d8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 24 Aug 2020 13:53:07 +0100 Subject: [PATCH 48/52] RemoteFilesystem: avoid warning when setting max file size --- src/Composer/Util/RemoteFilesystem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 676438c92..9ed465355 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -248,6 +248,7 @@ class RemoteFilesystem $maxFileSize = null; if (isset($options['max_file_size'])) { $maxFileSize = $options['max_file_size']; + unset($options['max_file_size']); } $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); From b847c4dc3a7bee6c47ed2d2f62de263077e0ab80 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Aug 2020 08:58:43 +0200 Subject: [PATCH 49/52] Validate licenses correctly even when proprietary is combined with some other license, fixes #9144 --- .../Package/Loader/ValidatingArrayLoader.php | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index ad2344187..8c8c62fab 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -111,25 +111,27 @@ class ValidatingArrayLoader implements LoaderInterface if (is_array($this->config['license']) || is_string($this->config['license'])) { $licenses = (array) $this->config['license']; - // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer - foreach ($licenses as $key => $license) { - if ('proprietary' === $license) { - unset($licenses[$key]); - } - } - $licenseValidator = new SpdxLicenses(); - if (count($licenses) === 1 && !$licenseValidator->validate($licenses) && $licenseValidator->validate(trim($licenses[0]))) { - $this->warnings[] = sprintf( - 'License %s must not contain extra spaces, make sure to trim it.', - json_encode($this->config['license']) - ); - } elseif (array() !== $licenses && !$licenseValidator->validate($licenses)) { - $this->warnings[] = sprintf( - 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . - 'If the software is closed-source, you may use "proprietary" as license.', - json_encode($this->config['license']) - ); + foreach ($licenses as $license) { + // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer + if ('proprietary' === $license) { + continue; + } + $licenseToValidate = str_replace('proprietary', 'MIT', $license); + if (!$licenseValidator->validate($licenseToValidate)) { + if ($licenseValidator->validate(trim($licenseToValidate))) { + $this->warnings[] = sprintf( + 'License %s must not contain extra spaces, make sure to trim it.', + json_encode($license) + ); + } else { + $this->warnings[] = sprintf( + 'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL . + 'If the software is closed-source, you may use "proprietary" as license.', + json_encode($license) + ); + } + } } } } From 6186c7f36f509c7476f4ffe947ad35704301b1ec Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Aug 2020 11:05:28 +0200 Subject: [PATCH 50/52] Fix handling of root aliases in partial updates, fixes #9110 --- src/Composer/Installer.php | 6 +- ...tial-update-from-lock-with-root-alias.test | 77 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f8c788e10..523ecca6b 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -390,7 +390,11 @@ class Installer // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata if ($this->updateMirrors && $lockedRepository) { foreach ($lockedRepository->getPackages() as $lockedPackage) { - $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); + // exclude alias packages here as for root aliases, both alias and aliased are + // present in the lock repo and we only want to require the aliased version + if (!$lockedPackage instanceof AliasPackage) { + $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); + } } } else { $links = array_merge($this->package->getRequires(), $this->package->getDevRequires()); diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test new file mode 100644 index 000000000..b5d195a46 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock-with-root-alias.test @@ -0,0 +1,77 @@ +--TEST-- +Partial update from lock file with root aliases should work +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" } }, + { "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" } }, + { "name": "c/aliased", "version": "1.0.0" }, + { "name": "c/aliased", "version": "2.0.0" } + ] + } + ], + "require": { + "a/dep": "*", + "b/dep": "*", + "c/aliased": "1.0.0 as 2.0.0" + } +} +--LOCK-- +{ + "packages": [ + { "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" } }, + { "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" } }, + { "name": "c/aliased", "version": "1.0.0" } + ], + "packages-dev": [], + "aliases": [ + { + "package": "c/aliased", + "version": "1.0.0.0", + "alias": "2.0.0", + "alias_normalized": "2.0.0.0" + } + ], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--INSTALLED-- +[ + { "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" } }, + { "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" } }, + { "name": "c/aliased", "version": "1.0.0" } +] +--RUN-- +update --lock +--EXPECT-LOCK-- +{ + "packages": [ + { "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" }, "type": "library" }, + { "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" }, "type": "library" }, + { "name": "c/aliased", "version": "1.0.0", "type": "library" } + ], + "packages-dev": [], + "aliases": [ + { + "package": "c/aliased", + "version": "1.0.0.0", + "alias": "2.0.0", + "alias_normalized": "2.0.0.0" + } + ], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--EXPECT-- +Marking c/aliased (2.0.0) as installed, alias of c/aliased (1.0.0) From 875a4784edccd9ff6ace21d64850776c031ee9c6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Aug 2020 13:54:29 +0200 Subject: [PATCH 51/52] Reorg config class a little --- src/Composer/Config.php | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 455ae6db7..4d03a4971 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -211,6 +211,7 @@ class Config public function get($key, $flags = 0) { switch ($key) { + // strings/paths with env var and {$refs} support case 'vendor-dir': case 'bin-dir': case 'process-timeout': @@ -234,20 +235,33 @@ class Config return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); + // booleans with env var support case 'htaccess-protect': - $value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT'); - if (false === $value) { - $value = $this->config[$key]; - } - return $value !== 'false' && (bool) $value; + // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config + $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); + $val = $this->getComposerEnv($env); + if (false === $val) { + $val = $this->config[$key]; + } + return $val !== 'false' && (bool) $val; + + // booleans without env var support + case 'disable-tls': + case 'secure-http': + case 'use-github-api': + case 'lock': + return $this->config[$key] !== 'false' && (bool) $this->config[$key]; + + // ints without env var support case 'cache-ttl': return (int) $this->config[$key]; + // numbers with kb/mb/gb support, without env var support case 'cache-files-maxsize': if (!preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $this->config[$key], $matches)) { throw new \RuntimeException( - "Could not parse the value of 'cache-files-maxsize': {$this->config[$key]}" + "Could not parse the value of '$key': {$this->config[$key]}" ); } $size = $matches[1]; @@ -269,6 +283,7 @@ class Config return $size; + // special cases below case 'cache-files-ttl': if (isset($this->config[$key])) { return (int) $this->config[$key]; @@ -326,14 +341,6 @@ class Config return $protos; - case 'disable-tls': - return $this->config[$key] !== 'false' && (bool) $this->config[$key]; - case 'secure-http': - return $this->config[$key] !== 'false' && (bool) $this->config[$key]; - case 'use-github-api': - return $this->config[$key] !== 'false' && (bool) $this->config[$key]; - case 'lock': - return $this->config[$key] !== 'false' && (bool) $this->config[$key]; default: if (!isset($this->config[$key])) { return null; From 90332f1dbd2b0d22f6e0272cfe4bc0d759e5dc84 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 25 Aug 2020 13:55:32 +0200 Subject: [PATCH 52/52] Add a readonly mode to the cache, fixes #9150 --- doc/06-config.md | 10 +++++--- res/composer-schema.json | 4 +++ src/Composer/Cache.php | 25 ++++++++++++++++--- src/Composer/Command/ClearCacheCommand.php | 1 + src/Composer/Config.php | 2 ++ src/Composer/Downloader/FileDownloader.php | 2 +- src/Composer/Factory.php | 1 + .../Repository/ComposerRepository.php | 11 +++++--- .../Repository/Vcs/BitbucketDriver.php | 1 + src/Composer/Repository/Vcs/GitDriver.php | 1 + src/Composer/Repository/Vcs/GitHubDriver.php | 1 + src/Composer/Repository/Vcs/GitLabDriver.php | 1 + src/Composer/Repository/Vcs/SvnDriver.php | 1 + 13 files changed, 51 insertions(+), 10 deletions(-) diff --git a/doc/06-config.md b/doc/06-config.md index b7e5e8e45..ccbdb3b07 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -85,11 +85,11 @@ gitlab.com the domain names must be also specified with the ## gitlab-token -A list of domain names and private tokens. Private token can be either simple -string, or array with username and token. For example using `{"gitlab.com": +A list of domain names and private tokens. Private token can be either simple +string, or array with username and token. For example using `{"gitlab.com": "privatetoken"}` as the value of this option will use `privatetoken` to access private repositories on gitlab. Using `{"gitlab.com": {"username": "gitlabuser", - "token": "privatetoken"}}` will use both username and token for gitlab deploy + "token": "privatetoken"}}` will use both username and token for gitlab deploy token functionality (https://docs.gitlab.com/ee/user/project/deploy_tokens/) Please note: If the package is not hosted at gitlab.com the domain names must be also specified with the @@ -204,6 +204,10 @@ downloads. When the garbage collection is periodically ran, this is the maximum size the cache will be able to use. Older (less used) files will be removed first until the cache fits. +## cache-read-only + +Defaults to `false`. Whether to use the Composer cache in read-only mode. + ## bin-compat Defaults to `auto`. Determines the compatibility of the binaries to be installed. diff --git a/res/composer-schema.json b/res/composer-schema.json index 04db6d3a3..8aa401580 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -237,6 +237,10 @@ "type": ["string", "integer"], "description": "The cache max size for the files cache, defaults to \"300MiB\"." }, + "cache-read-only": { + "type": ["boolean"], + "description": "Whether to use the Composer cache in read-only mode." + }, "bin-compat": { "enum": ["auto", "full"], "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)." diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 2a4e7756f..97be1e1a2 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -30,19 +30,22 @@ class Cache private $enabled = true; private $allowlist; private $filesystem; + private $readOnly; /** * @param IOInterface $io * @param string $cacheDir location of the cache * @param string $allowlist List of characters that are allowed in path names (used in a regex character class) * @param Filesystem $filesystem optional filesystem instance + * @param bool $readOnly whether the cache is in readOnly mode */ - public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null) + public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null, $readOnly = false) { $this->io = $io; $this->root = rtrim($cacheDir, '/\\') . '/'; $this->allowlist = $allowlist; $this->filesystem = $filesystem ?: new Filesystem(); + $this->readOnly = (bool) $readOnly; if (!self::isUsable($cacheDir)) { $this->enabled = false; @@ -59,6 +62,22 @@ class Cache } } + /** + * @param bool $readOnly + */ + public function setReadOnly($readOnly) + { + $this->readOnly = (bool) $readOnly; + } + + /** + * @return bool + */ + public function isReadOnly() + { + return $this->readOnly; + } + public static function isUsable($path) { return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); @@ -90,7 +109,7 @@ class Cache public function write($file, $contents) { - if ($this->enabled) { + if ($this->enabled && !$this->readOnly) { $file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file); $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); @@ -128,7 +147,7 @@ class Cache */ public function copyFrom($file, $source) { - if ($this->enabled) { + if ($this->enabled && !$this->readOnly) { $file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file); $this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index 2f511641e..bdbdd80cf 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -59,6 +59,7 @@ EOT continue; } $cache = new Cache($io, $cachePath); + $cache->setReadOnly($config->get('cache-read-only')); if (!$cache->isEnabled()) { $io->writeError("Cache is not enabled ($key): $cachePath"); diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 4d03a4971..54d2e360c 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -41,6 +41,7 @@ class Config 'cache-ttl' => 15552000, // 6 months 'cache-files-ttl' => null, // fallback to cache-ttl 'cache-files-maxsize' => '300MiB', + 'cache-read-only' => false, 'bin-compat' => 'auto', 'discard-changes' => false, 'autoloader-suffix' => null, @@ -236,6 +237,7 @@ class Config return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); // booleans with env var support + case 'cache-read-only': case 'htaccess-protect': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 8fbb48e9c..9fa498af8 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -187,7 +187,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $url = reset($urls); $cacheKey = $url['cacheKey']; - if ($cache) { + if ($cache && !$cache->isReadOnly()) { $self->lastCacheWrites[$package->getName()] = $cacheKey; $cache->copyFrom($cacheKey, $fileName); } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 7fd9af344..218a53457 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -477,6 +477,7 @@ class Factory $cache = null; if ($config->get('cache-files-ttl') > 0) { $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); + $cache->setReadOnly($config->get('cache-read-only')); } $fs = new Filesystem($process); diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index d3db3069c..e5706e04d 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -130,6 +130,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->io = $io; $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$~'); + $this->cache->setReadOnly($config->get('cache-read-only')); $this->versionParser = new VersionParser(); $this->loader = new ArrayLoader($this->versionParser); $this->httpDownloader = $httpDownloader; @@ -1071,7 +1072,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $data = $response->decodeJson(); HttpDownloader::outputWarnings($this->io, $this->url, $data); - if ($cacheKey) { + if ($cacheKey && !$this->cache->isReadOnly()) { if ($storeLastModifiedTime) { $lastModifiedDate = $response->getHeader('last-modified'); if ($lastModifiedDate) { @@ -1155,7 +1156,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $data['last-modified'] = $lastModifiedDate; $json = json_encode($data); } - $this->cache->write($cacheKey, $json); + if (!$this->cache->isReadOnly()) { + $this->cache->write($cacheKey, $json); + } return $data; } catch (\Exception $e) { @@ -1238,7 +1241,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $data['last-modified'] = $lastModifiedDate; $json = JsonFile::encode($data, JsonFile::JSON_UNESCAPED_SLASHES | JsonFile::JSON_UNESCAPED_UNICODE); } - $cache->write($cacheKey, $json); + if (!$cache->isReadOnly()) { + $cache->write($cacheKey, $json); + } $repo->freshMetadataUrls[$filename] = true; return $data; diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index d23b32dd2..c1781342f 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -60,6 +60,7 @@ abstract class BitbucketDriver extends VcsDriver $this->repository, )) ); + $this->cache->setReadOnly($this->config->get('cache-read-only')); } /** diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index c7672c619..499a5a9df 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -75,6 +75,7 @@ class GitDriver extends VcsDriver $this->getBranches(); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl)); + $this->cache->setReadOnly($this->config->get('cache-read-only')); } /** diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 83e353321..bbbbd6ecb 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -58,6 +58,7 @@ class GitHubDriver extends VcsDriver $this->originUrl = 'github.com'; } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){ $this->setupGitDriver($this->url); diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 22f884d7f..2987b3b94 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -105,6 +105,7 @@ class GitLabDriver extends VcsDriver $this->repository = preg_replace('#(\.git)$#', '', $match['repo']); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); + $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->fetchProject(); } diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 097103487..87c9781b6 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -78,6 +78,7 @@ class SvnDriver extends VcsDriver } $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl)); + $this->cache->setReadOnly($this->config->get('cache-read-only')); $this->getBranches(); $this->getTags();