diff --git a/.travis.yml b/.travis.yml index a06922904..a0d3c3871 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,9 +30,11 @@ matrix: env: - deps=high - php: nightly + - php: 7.4snapshot fast_finish: true allow_failures: - php: nightly + - php: 7.4snapshot before_install: # disable xdebug if available diff --git a/doc/03-cli.md b/doc/03-cli.md index c04522733..08beb716b 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -259,6 +259,10 @@ match the platform requirements of the installed packages. This can be used to verify that a production server has all the extensions needed to run a project after installing it for example. +Unlike update/install, this command will ignore config.platform settings and +check the real platform packages so you can be certain you have the required +platform dependencies. + ## global The global command allows you to run other commands like `install`, `remove`, `require` diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index cdf31f6e4..3ef604fe7 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -112,6 +112,19 @@ Note that this will still need to pull and scan all of your VCS repositories because any VCS repository might contain (on any branch) one of the selected packages. +If you want to scan only the selected package and not all VCS repositories you need +to declare a *name* for all your package (this only work on VCS repositories type) : + +```json +{ + "repositories": [ + { "name": "company/privaterepo", "type": "vcs", "url": "https://github.com/mycompany/privaterepo" }, + { "name": "private/repo", "type": "vcs", "url": "http://svn.example.org/private/repo" }, + { "name": "mycompany/privaterepo2", "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" } + ] +} +``` + If you want to scan only a single repository and update all packages found in it, pass the VCS repository URL as an optional argument: diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index de7f4b4d3..195a2c490 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -34,6 +34,8 @@ class CheckPlatformReqsCommand extends BaseCommand <<php composer.phar check-platform-reqs EOT @@ -49,6 +51,10 @@ EOT $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); } else { $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); + // fallback to lockfile if installed repo is empty + if (!$dependencies) { + $dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages(); + } $requires += $composer->getPackage()->getDevRequires(); } foreach ($requires as $require => $link) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index ac4b26ec8..08f891014 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -168,13 +168,25 @@ EOT if ($repositories) { $config = Factory::createConfig($io); $repos = array(new PlatformRepository); + $createDefaultPackagistRepo = true; foreach ($repositories as $repo) { - $repos[] = RepositoryFactory::fromString($io, $config, $repo); + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo); + if ( + (isset($repoConfig['packagist']) && $repoConfig === array('packagist' => false)) + || (isset($repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false)) + ) { + $createDefaultPackagistRepo = false; + continue; + } + $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig); + } + + if ($createDefaultPackagistRepo) { + $repos[] = RepositoryFactory::createRepo($io, $config, array( + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + )); } - $repos[] = RepositoryFactory::createRepo($io, $config, array( - 'type' => 'composer', - 'url' => 'https://repo.packagist.org', - )); $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 8a06d46ad..876371635 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -26,6 +26,7 @@ use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\IO\IOInterface; +use Composer\Util\Silencer; /** * @author Jérémy Romey @@ -103,11 +104,6 @@ EOT return 1; } - if (!is_writable($this->file)) { - $io->writeError(''.$this->file.' is not writable.'); - - return 1; - } if (filesize($this->file) === 0) { file_put_contents($this->file, "{\n}\n"); @@ -116,6 +112,14 @@ EOT $this->json = new JsonFile($this->file); $this->composerBackup = file_get_contents($this->json->getPath()); + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { + $io->writeError(''.$this->file.' is not writable.'); + + return 1; + } + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $repos = $composer->getRepositoryManager()->getRepositories(); @@ -141,7 +145,12 @@ EOT // validate requirements format $versionParser = new VersionParser(); - foreach ($requirements as $constraint) { + foreach ($requirements as $package => $constraint) { + if (strtolower($package) === $composer->getPackage()->getName()) { + $io->writeError(sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); + + return 1; + } $versionParser->parseConstraints($constraint); } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index a91f96f0d..a9fb2e117 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -379,6 +379,9 @@ class Application extends BaseApplication public function resetComposer() { $this->composer = null; + if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) { + $this->getIO()->resetAuthentications(); + } } /** diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 9aaf564a5..3c31c23eb 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -197,7 +197,7 @@ class RuleSetGenerator } } - protected function addConflictRules() + protected function addConflictRules($ignorePlatformReqs = false) { /** @var PackageInterface $package */ foreach ($this->addedPackages as $package) { @@ -206,6 +206,10 @@ class RuleSetGenerator continue; } + if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { + continue; + } + /** @var PackageInterface $possibleConflict */ foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) { $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true); @@ -304,7 +308,7 @@ class RuleSetGenerator $this->addRulesForJobs($ignorePlatformReqs); - $this->addConflictRules(); + $this->addConflictRules($ignorePlatformReqs); // Remove references to packages $this->addedPackages = $this->addedPackagesByNames = null; diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index 0427ec8c8..777866714 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -87,8 +87,6 @@ class PerforceDownloader extends VcsDownloader public function getLocalChanges(PackageInterface $package, $path) { $this->io->writeError('Perforce driver does not check for local changes before overriding', true); - - return null; } /** diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 05e34dda1..c6cc73879 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -200,7 +200,9 @@ class EventDispatcher try { /** @var InstallerEvent $event */ - $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); + $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); + $scriptEvent->setOriginatingEvent($event); + $return = $this->dispatch($scriptName, $scriptEvent); } catch (ScriptExecutionException $e) { $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); throw $e; diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index b63b59484..09d9d1663 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -28,6 +28,14 @@ abstract class BaseIO implements IOInterface return $this->authentications; } + /** + * {@inheritDoc} + */ + public function resetAuthentications() + { + $this->authentications = array(); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 8fe6a9f0a..e64a56f71 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -326,9 +326,10 @@ class JsonManipulator } // try and find a match for the subkey - if ($this->pregMatch('{"'.preg_quote($name).'"\s*:}i', $children)) { + $keyRegex = str_replace('/', '\\\\?/', preg_quote($name)); + if ($this->pregMatch('{"'.$keyRegex.'"\s*:}i', $children)) { // find best match for the value of "name" - if (preg_match_all('{'.self::$DEFINES.'"'.preg_quote($name).'"\s*:\s*(?:(?&json))}x', $children, $matches)) { + if (preg_match_all('{'.self::$DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) { $bestMatch = ''; foreach ($matches[0] as $match) { if (strlen($bestMatch) < strlen($match)) { diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 079d34c54..aff80e4cd 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\Zip; /** * @author Serge Smertin @@ -80,76 +81,15 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito } } - /** - * Find a file by name, returning the one that has the shortest path. - * - * @param \ZipArchive $zip - * @param string $filename - * @return bool|int - */ - private function locateFile(\ZipArchive $zip, $filename) - { - $indexOfShortestMatch = false; - $lengthOfShortestMatch = -1; - - 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; - } - - if (strpos($directoryName, '\\') !== false || - strpos($directoryName, '/') !== false) { - //composer.json files below first directory are rejected - 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; - } - } - } - } - - return $indexOfShortestMatch; - } - private function getComposerInformation(\SplFileInfo $file) { - $zip = new \ZipArchive(); - if ($zip->open($file->getPathname()) !== true) { + $json = Zip::getComposerJson($file->getPathname()); + + if (null === $json) { return false; } - if (0 == $zip->numFiles) { - $zip->close(); - - return false; - } - - $foundFileIndex = $this->locateFile($zip, 'composer.json'); - if (false === $foundFileIndex) { - $zip->close(); - - return false; - } - - $configurationFileName = $zip->getNameIndex($foundFileIndex); - $zip->close(); - - $composerFile = "zip://{$file->getPathname()}#$configurationFileName"; - $json = file_get_contents($composerFile); - - $package = JsonFile::parseJson($json, $composerFile); + $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); $package['dist'] = array( 'type' => 'zip', 'url' => strtr($file->getPathname(), '\\', '/'), diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index ee6456a89..b5c3fb14a 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -308,6 +308,10 @@ class GitHubDriver extends VcsDriver */ protected function generateSshUrl() { + if (false !== strpos($this->originUrl, ':')) { + return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 1e2775ff7..f1d813b90 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -68,9 +68,9 @@ class GitLabDriver extends VcsDriver private $isPrivate = true; /** - * @var int port number + * @var bool true if the origin has a port number or a path component in it */ - protected $portNumber; + private $hasNonstandardOrigin = false; const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; @@ -95,11 +95,10 @@ class GitLabDriver extends VcsDriver ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') ; - $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts); + $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); - if (!empty($match['port']) && true === is_numeric($match['port'])) { - // If it is an HTTP based URL, and it has a port - $this->portNumber = (int) $match['port']; + if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) { + $this->hasNonstandardOrigin = true; } $this->namespace = implode('/', $urlParts); @@ -260,10 +259,7 @@ class GitLabDriver extends VcsDriver */ public function getApiUrl() { - $domainName = $this->originUrl; - $portNumber = (true === is_numeric($this->portNumber)) ? sprintf(':%s', $this->portNumber) : ''; - - return $this->scheme.'://'.$domainName.$portNumber.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); + return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); } /** @@ -362,6 +358,10 @@ class GitLabDriver extends VcsDriver */ protected function generateSshUrl() { + if ($this->hasNonstandardOrigin) { + return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git'; + } + return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; } @@ -464,7 +464,7 @@ class GitLabDriver extends VcsDriver $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; $urlParts = explode('/', $match['parts']); - if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts)) { + if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { return false; } @@ -495,16 +495,16 @@ class GitLabDriver extends VcsDriver * @param array $urlParts * @return bool|string */ - private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts) + private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber) { - if (in_array($guessedDomain, $configuredDomains)) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { return $guessedDomain; } while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; - if (in_array($guessedDomain, $configuredDomains)) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{/}', ':'.$portNumber.'/', $guessedDomain, 1), $configuredDomains))) { return $guessedDomain; } } diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index 138f43c1a..5fab172bf 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -39,6 +39,11 @@ class Event extends BaseEvent */ private $devMode; + /** + * @var BaseEvent + */ + private $originatingEvent; + /** * Constructor. * @@ -55,6 +60,7 @@ class Event extends BaseEvent $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; + $this->originatingEvent = null; } /** @@ -86,4 +92,42 @@ class Event extends BaseEvent { return $this->devMode; } + + /** + * Set the originating event. + * + * @return \Composer\EventDispatcher\Event|null + */ + public function getOriginatingEvent() + { + return $this->originatingEvent; + } + + /** + * Set the originating event. + * + * @param \Composer\EventDispatcher\Event $event + * @return $this + */ + public function setOriginatingEvent(BaseEvent $event) + { + $this->originatingEvent = $this->calculateOriginatingEvent($event); + + return $this; + } + + /** + * Returns the upper-most event in chain. + * + * @param \Composer\EventDispatcher\Event $event + * @return \Composer\EventDispatcher\Event + */ + private function calculateOriginatingEvent(BaseEvent $event) + { + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } + + return $event; + } } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 83e6b5ede..c4dabd1d7 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -33,6 +33,7 @@ class ErrorHandler * * @static * @throws \ErrorException + * @return bool */ public static function handle($level, $message, $file, $line) { @@ -63,6 +64,8 @@ class ErrorHandler }, array_slice(debug_backtrace(), 2)))); } } + + return true; } /** diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index b2dbf2836..fb2489b01 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -57,7 +57,10 @@ class GitLab */ public function authorizeOAuth($originUrl) { - if (!in_array($originUrl, $this->config->get('gitlab-domains'), true)) { + // before composer 1.9, origin URLs had no port number in them + $bcOriginUrl = preg_replace('{:\d+}', '', $originUrl); + + if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) { return false; } @@ -77,6 +80,12 @@ class GitLab return true; } + if (isset($authTokens[$bcOriginUrl])) { + $this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token'); + + return true; + } + return false; } diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index 31ddeffec..52080d663 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -363,8 +363,6 @@ class Perforce while ($line !== false) { $line = fgets($pipe); } - - return; } public function windowsLogin($password) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index c6ba4085c..d9803835d 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -286,6 +286,8 @@ class RemoteFilesystem $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); + + return true; }); try { $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header); @@ -459,6 +461,8 @@ class RemoteFilesystem $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg); + + return true; }); $result = (bool) file_put_contents($fileName, $result); restore_error_handler(); diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index 34336d06c..a53212f2d 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -19,8 +19,6 @@ use Composer\CaBundle\CaBundle; */ final class TlsHelper { - private static $useOpensslParse; - /** * Match hostname against a certificate. * diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index c01677522..b12a2d54d 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -70,6 +70,9 @@ class Url } $origin = (string) parse_url($url, PHP_URL_HOST); + if ($port = parse_url($url, PHP_URL_PORT)) { + $origin .= ':'.$port; + } if (strpos($origin, '.github.com') === (strlen($origin) - 11)) { return 'github.com'; diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php new file mode 100644 index 000000000..8c79d106c --- /dev/null +++ b/src/Composer/Util/Zip.php @@ -0,0 +1,108 @@ + + * 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 Andreas Schempp + */ +class Zip +{ + /** + * Gets content of the root composer.json inside a ZIP archive. + * + * @param string $pathToZip + * @param string $filename + * + * @return string|null + */ + public static function getComposerJson($pathToZip) + { + if (!extension_loaded('zip')) { + throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); + } + + $zip = new \ZipArchive(); + if ($zip->open($pathToZip) !== true) { + return null; + } + + if (0 == $zip->numFiles) { + $zip->close(); + + return null; + } + + $foundFileIndex = self::locateFile($zip, 'composer.json'); + if (false === $foundFileIndex) { + $zip->close(); + + return null; + } + + $content = null; + $configurationFileName = $zip->getNameIndex($foundFileIndex); + $stream = $zip->getStream($configurationFileName); + + if (false !== $stream) { + $content = stream_get_contents($stream); + } + + $zip->close(); + + return $content; + } + + /** + * Find a file by name, returning the one that has the shortest path. + * + * @param \ZipArchive $zip + * @param string $filename + * + * @return bool|int + */ + private static function locateFile(\ZipArchive $zip, $filename) + { + $indexOfShortestMatch = false; + $lengthOfShortestMatch = -1; + + 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; + } + + if (strpos($directoryName, '\\') !== false || + strpos($directoryName, '/') !== false) { + //composer.json files below first directory are rejected + 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; + } + } + } + } + + return $indexOfShortestMatch; + } +} diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 7a3ef3ee0..5e8ebb5c4 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -162,18 +162,18 @@ class AllFunctionalTest extends TestCase } }; - for ($i = 0, $c = count($tokens); $i < $c; $i++) { - if ('' === $tokens[$i] && null === $section) { + foreach ($tokens as $token) { + if ('' === $token && null === $section) { continue; } // Handle section headers. if (null === $section) { - $section = $tokens[$i]; + $section = $token; continue; } - $sectionData = $tokens[$i]; + $sectionData = $token; // Allow sections to validate, or modify their section data. switch ($section) { diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index 150e48607..70f030333 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -152,11 +152,4 @@ class RuleSetTest extends TestCase $this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($pool)); } - - private function getRuleMock() - { - return $this->getMockBuilder('Composer\DependencyResolver\Rule') - ->disableOriginalConstructor() - ->getMock(); - } } diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index d8bc7c200..8bc7831af 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -1448,6 +1448,22 @@ class JsonManipulatorTest extends TestCase "repositories": { } } +', + ), + 'works on simple ones escaped slash' => array( + '{ + "repositories": { + "foo\/bar": { + "bar": "baz" + } + } +}', + 'foo/bar', + true, + '{ + "repositories": { + } +} ', ), 'works on simple ones middle' => array( diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index e3b9dc491..ca1cec0db 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -148,7 +148,6 @@ class ArrayLoaderTest extends TestCase { $package = $this->loader->load($config); $dumper = new ArrayDumper; - $expectedConfig = $config; $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config); $this->assertEquals($expectedConfig, $dumper->dump($package)); } diff --git a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php index cbb4342e9..f39a3b8f7 100644 --- a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php @@ -40,15 +40,6 @@ class FossilDriverTest extends TestCase $fs->removeDirectory($this->home); } - private function getCmd($cmd) - { - if (Platform::isWindows()) { - return strtr($cmd, "'", '"'); - } - - return $cmd; - } - public static function supportProvider() { return array( diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 946c198f2..b43cbbc2a 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -71,15 +71,6 @@ class SvnDriverTest extends TestCase $svn->initialize(); } - private function getCmd($cmd) - { - if (Platform::isWindows()) { - return strtr($cmd, "'", '"'); - } - - return $cmd; - } - public static function supportProvider() { return array( diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php new file mode 100644 index 000000000..b7c8cd9ff --- /dev/null +++ b/tests/Composer/Test/Script/EventTest.php @@ -0,0 +1,80 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Script; + +use Composer\Composer; +use Composer\Config; +use Composer\Script\Event; +use Composer\Test\TestCase; + +class EventTest extends TestCase +{ + public function testEventSetsOriginatingEvent() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); + + $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); + + $scriptEvent = new Event('test', $composer, $io, true); + + $this->assertNull( + $scriptEvent->getOriginatingEvent(), + 'originatingEvent is initialized as null' + ); + + $scriptEvent->setOriginatingEvent($originatingEvent); + + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return test event' + ); + } + + public function testEventCalculatesNestedOriginatingEvent() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); + + $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); + $intermediateEvent = new Event('intermediate', $composer, $io, true); + $intermediateEvent->setOriginatingEvent($originatingEvent); + + $scriptEvent = new Event('test', $composer, $io, true); + $scriptEvent->setOriginatingEvent($intermediateEvent); + + $this->assertNotSame( + $intermediateEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD NOT return intermediate events' + ); + + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return upper-most event' + ); + } + + private function createComposerInstance() + { + $composer = new Composer; + $config = new Config; + $composer->setConfig($config); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $composer->setPackage($package); + + return $composer; + } +} diff --git a/tests/Composer/Test/Util/Fixtures/Zip/empty.zip b/tests/Composer/Test/Util/Fixtures/Zip/empty.zip new file mode 100644 index 000000000..15cb0ecb3 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/empty.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/folder.zip b/tests/Composer/Test/Util/Fixtures/Zip/folder.zip new file mode 100644 index 000000000..72b17b542 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/folder.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip new file mode 100644 index 000000000..db8c50302 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip b/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip new file mode 100644 index 000000000..e536b956c Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/root.zip b/tests/Composer/Test/Util/Fixtures/Zip/root.zip new file mode 100644 index 000000000..fd08f4d34 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/root.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip b/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip new file mode 100644 index 000000000..93060bea2 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip differ diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 1893486e0..4e21d5f77 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -24,12 +24,9 @@ use RecursiveIteratorIterator; */ class GitHubTest extends TestCase { - private $username = 'username'; private $password = 'password'; - private $authcode = 'authcode'; private $message = 'mymessage'; private $origin = 'github.com'; - private $token = 'githubtoken'; public function testUsernamePasswordAuthenticationFlow() { diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 611b25256..6037e7a0b 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -24,7 +24,6 @@ class GitLabTest extends TestCase { private $username = 'username'; private $password = 'password'; - private $authcode = 'authcode'; private $message = 'mymessage'; private $origin = 'gitlab.com'; private $token = 'gitlabtoken'; diff --git a/tests/Composer/Test/Util/ZipTest.php b/tests/Composer/Test/Util/ZipTest.php new file mode 100644 index 000000000..c47373274 --- /dev/null +++ b/tests/Composer/Test/Util/ZipTest.php @@ -0,0 +1,117 @@ + + * 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\Zip; +use PHPUnit\Framework\TestCase; + +/** + * @author Andreas Schempp + */ +class ZipTest extends TestCase +{ + public function testThrowsExceptionIfZipExcentionIsNotLoaded() + { + if (extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is loaded.'); + } + + $this->setExpectedException('\RuntimeException', 'The Zip Util requires PHP\'s zip extension'); + + Zip::getComposerJson(''); + } + + public function testReturnsNullifTheZipIsNotFound() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/invalid.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheZipIsEmpty() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/empty.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheZipHasNoComposerJson() + { + 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); + } + + public function testReturnsNullIfTheComposerJsonIsInASubSubfolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip'); + + $this->assertNull($result); + } + + public function testReturnsComposerJsonInZipRoot() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/root.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsComposerJsonInFirstFolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsRootComposerJsonAndSkipsSubfolders() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } +}