Browse Source

Merge branch 'master' into 2.0

Jordi Boggiano 5 years ago
parent
commit
6c4357a7ed
39 changed files with 491 additions and 135 deletions
  1. 2 0
      .travis.yml
  2. 4 0
      doc/03-cli.md
  3. 13 0
      doc/articles/handling-private-packages-with-satis.md
  4. 6 0
      src/Composer/Command/CheckPlatformReqsCommand.php
  5. 17 5
      src/Composer/Command/InitCommand.php
  6. 15 6
      src/Composer/Command/RequireCommand.php
  7. 3 0
      src/Composer/Console/Application.php
  8. 6 2
      src/Composer/DependencyResolver/RuleSetGenerator.php
  9. 0 2
      src/Composer/Downloader/PerforceDownloader.php
  10. 3 1
      src/Composer/EventDispatcher/EventDispatcher.php
  11. 8 0
      src/Composer/IO/BaseIO.php
  12. 3 2
      src/Composer/Json/JsonManipulator.php
  13. 4 64
      src/Composer/Repository/ArtifactRepository.php
  14. 4 0
      src/Composer/Repository/Vcs/GitHubDriver.php
  15. 14 14
      src/Composer/Repository/Vcs/GitLabDriver.php
  16. 44 0
      src/Composer/Script/Event.php
  17. 3 0
      src/Composer/Util/ErrorHandler.php
  18. 10 1
      src/Composer/Util/GitLab.php
  19. 0 2
      src/Composer/Util/Perforce.php
  20. 4 0
      src/Composer/Util/RemoteFilesystem.php
  21. 0 2
      src/Composer/Util/TlsHelper.php
  22. 3 0
      src/Composer/Util/Url.php
  23. 108 0
      src/Composer/Util/Zip.php
  24. 4 4
      tests/Composer/Test/AllFunctionalTest.php
  25. 0 7
      tests/Composer/Test/DependencyResolver/RuleSetTest.php
  26. 16 0
      tests/Composer/Test/Json/JsonManipulatorTest.php
  27. 0 1
      tests/Composer/Test/Package/Loader/ArrayLoaderTest.php
  28. 0 9
      tests/Composer/Test/Repository/Vcs/FossilDriverTest.php
  29. 0 9
      tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
  30. 80 0
      tests/Composer/Test/Script/EventTest.php
  31. BIN
      tests/Composer/Test/Util/Fixtures/Zip/empty.zip
  32. BIN
      tests/Composer/Test/Util/Fixtures/Zip/folder.zip
  33. BIN
      tests/Composer/Test/Util/Fixtures/Zip/multiple.zip
  34. BIN
      tests/Composer/Test/Util/Fixtures/Zip/nojson.zip
  35. BIN
      tests/Composer/Test/Util/Fixtures/Zip/root.zip
  36. BIN
      tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip
  37. 0 3
      tests/Composer/Test/Util/GitHubTest.php
  38. 0 1
      tests/Composer/Test/Util/GitLabTest.php
  39. 117 0
      tests/Composer/Test/Util/ZipTest.php

+ 2 - 0
.travis.yml

@@ -30,9 +30,11 @@ matrix:
       env:
       env:
         - deps=high
         - deps=high
     - php: nightly
     - php: nightly
+    - php: 7.4snapshot
   fast_finish: true
   fast_finish: true
   allow_failures:
   allow_failures:
     - php: nightly
     - php: nightly
+    - php: 7.4snapshot
 
 
 before_install:
 before_install:
   # disable xdebug if available
   # disable xdebug if available

+ 4 - 0
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
 to verify that a production server has all the extensions needed to run a
 project after installing it for example.
 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
 ## global
 
 
 The global command allows you to run other commands like `install`, `remove`, `require`
 The global command allows you to run other commands like `install`, `remove`, `require`

+ 13 - 0
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
 because any VCS repository might contain (on any branch) one of the selected
 packages.
 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
 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:
 it, pass the VCS repository URL as an optional argument:
 
 

+ 6 - 0
src/Composer/Command/CheckPlatformReqsCommand.php

@@ -34,6 +34,8 @@ class CheckPlatformReqsCommand extends BaseCommand
                 <<<EOT
                 <<<EOT
 Checks that your PHP and extensions versions match the platform requirements of the installed packages.
 Checks that your PHP and extensions versions match the platform requirements of the installed packages.
 
 
+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.
+
 <info>php composer.phar check-platform-reqs</info>
 <info>php composer.phar check-platform-reqs</info>
 
 
 EOT
 EOT
@@ -49,6 +51,10 @@ EOT
             $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
             $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
         } else {
         } else {
             $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
             $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();
             $requires += $composer->getPackage()->getDevRequires();
         }
         }
         foreach ($requires as $require => $link) {
         foreach ($requires as $require => $link) {

+ 17 - 5
src/Composer/Command/InitCommand.php

@@ -168,13 +168,25 @@ EOT
         if ($repositories) {
         if ($repositories) {
             $config = Factory::createConfig($io);
             $config = Factory::createConfig($io);
             $repos = array(new PlatformRepository);
             $repos = array(new PlatformRepository);
+            $createDefaultPackagistRepo = true;
             foreach ($repositories as $repo) {
             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);
             $this->repos = new CompositeRepository($repos);
             unset($repos, $config, $repositories);
             unset($repos, $config, $repositories);

+ 15 - 6
src/Composer/Command/RequireCommand.php

@@ -26,6 +26,7 @@ use Composer\Plugin\PluginEvents;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
+use Composer\Util\Silencer;
 
 
 /**
 /**
  * @author Jérémy Romey <jeremy@free-agent.fr>
  * @author Jérémy Romey <jeremy@free-agent.fr>
@@ -103,11 +104,6 @@ EOT
 
 
             return 1;
             return 1;
         }
         }
-        if (!is_writable($this->file)) {
-            $io->writeError('<error>'.$this->file.' is not writable.</error>');
-
-            return 1;
-        }
 
 
         if (filesize($this->file) === 0) {
         if (filesize($this->file) === 0) {
             file_put_contents($this->file, "{\n}\n");
             file_put_contents($this->file, "{\n}\n");
@@ -116,6 +112,14 @@ EOT
         $this->json = new JsonFile($this->file);
         $this->json = new JsonFile($this->file);
         $this->composerBackup = file_get_contents($this->json->getPath());
         $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('<error>'.$this->file.' is not writable.</error>');
+
+            return 1;
+        }
+
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $repos = $composer->getRepositoryManager()->getRepositories();
         $repos = $composer->getRepositoryManager()->getRepositories();
 
 
@@ -141,7 +145,12 @@ EOT
 
 
         // validate requirements format
         // validate requirements format
         $versionParser = new VersionParser();
         $versionParser = new VersionParser();
-        foreach ($requirements as $constraint) {
+        foreach ($requirements as $package => $constraint) {
+            if (strtolower($package) === $composer->getPackage()->getName()) {
+                $io->writeError(sprintf('<error>Root package \'%s\' cannot require itself in its composer.json</error>', $package));
+
+                return 1;
+            }
             $versionParser->parseConstraints($constraint);
             $versionParser->parseConstraints($constraint);
         }
         }
 
 

+ 3 - 0
src/Composer/Console/Application.php

@@ -379,6 +379,9 @@ class Application extends BaseApplication
     public function resetComposer()
     public function resetComposer()
     {
     {
         $this->composer = null;
         $this->composer = null;
+        if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) {
+            $this->getIO()->resetAuthentications();
+        }
     }
     }
 
 
     /**
     /**

+ 6 - 2
src/Composer/DependencyResolver/RuleSetGenerator.php

@@ -197,7 +197,7 @@ class RuleSetGenerator
         }
         }
     }
     }
 
 
-    protected function addConflictRules()
+    protected function addConflictRules($ignorePlatformReqs = false)
     {
     {
         /** @var PackageInterface $package */
         /** @var PackageInterface $package */
         foreach ($this->addedPackages as $package) {
         foreach ($this->addedPackages as $package) {
@@ -206,6 +206,10 @@ class RuleSetGenerator
                     continue;
                     continue;
                 }
                 }
 
 
+                if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
+                    continue;
+                }
+
                 /** @var PackageInterface $possibleConflict */
                 /** @var PackageInterface $possibleConflict */
                 foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
                 foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
                     $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);
                     $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);
@@ -304,7 +308,7 @@ class RuleSetGenerator
 
 
         $this->addRulesForJobs($ignorePlatformReqs);
         $this->addRulesForJobs($ignorePlatformReqs);
 
 
-        $this->addConflictRules();
+        $this->addConflictRules($ignorePlatformReqs);
 
 
         // Remove references to packages
         // Remove references to packages
         $this->addedPackages = $this->addedPackagesByNames = null;
         $this->addedPackages = $this->addedPackagesByNames = null;

+ 0 - 2
src/Composer/Downloader/PerforceDownloader.php

@@ -87,8 +87,6 @@ class PerforceDownloader extends VcsDownloader
     public function getLocalChanges(PackageInterface $package, $path)
     public function getLocalChanges(PackageInterface $package, $path)
     {
     {
         $this->io->writeError('Perforce driver does not check for local changes before overriding', true);
         $this->io->writeError('Perforce driver does not check for local changes before overriding', true);
-
-        return null;
     }
     }
 
 
     /**
     /**

+ 3 - 1
src/Composer/EventDispatcher/EventDispatcher.php

@@ -200,7 +200,9 @@ class EventDispatcher
 
 
                     try {
                     try {
                         /** @var InstallerEvent $event */
                         /** @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) {
                     } catch (ScriptExecutionException $e) {
                         $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
                         $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
                         throw $e;
                         throw $e;

+ 8 - 0
src/Composer/IO/BaseIO.php

@@ -28,6 +28,14 @@ abstract class BaseIO implements IOInterface
         return $this->authentications;
         return $this->authentications;
     }
     }
 
 
+    /**
+     * {@inheritDoc}
+     */
+    public function resetAuthentications()
+    {
+        $this->authentications = array();
+    }
+
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */

+ 3 - 2
src/Composer/Json/JsonManipulator.php

@@ -326,9 +326,10 @@ class JsonManipulator
         }
         }
 
 
         // try and find a match for the subkey
         // 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"
             // 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 = '';
                 $bestMatch = '';
                 foreach ($matches[0] as $match) {
                 foreach ($matches[0] as $match) {
                     if (strlen($bestMatch) < strlen($match)) {
                     if (strlen($bestMatch) < strlen($match)) {

+ 4 - 64
src/Composer/Repository/ArtifactRepository.php

@@ -16,6 +16,7 @@ use Composer\IO\IOInterface;
 use Composer\Json\JsonFile;
 use Composer\Json\JsonFile;
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\Loader\LoaderInterface;
 use Composer\Package\Loader\LoaderInterface;
+use Composer\Util\Zip;
 
 
 /**
 /**
  * @author Serge Smertin <serg.smertin@gmail.com>
  * @author Serge Smertin <serg.smertin@gmail.com>
@@ -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)
     private function getComposerInformation(\SplFileInfo $file)
     {
     {
-        $zip = new \ZipArchive();
-        if ($zip->open($file->getPathname()) !== true) {
-            return false;
-        }
-
-        if (0 == $zip->numFiles) {
-            $zip->close();
+        $json = Zip::getComposerJson($file->getPathname());
 
 
+        if (null === $json) {
             return false;
             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(
         $package['dist'] = array(
             'type' => 'zip',
             'type' => 'zip',
             'url' => strtr($file->getPathname(), '\\', '/'),
             'url' => strtr($file->getPathname(), '\\', '/'),

+ 4 - 0
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -308,6 +308,10 @@ class GitHubDriver extends VcsDriver
      */
      */
     protected function generateSshUrl()
     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';
         return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
     }
     }
 
 

+ 14 - 14
src/Composer/Repository/Vcs/GitLabDriver.php

@@ -68,9 +68,9 @@ class GitLabDriver extends VcsDriver
     private $isPrivate = true;
     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 = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)(?::(?P<port>[0-9]+))?/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';
     const URL_REGEX = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)(?::(?P<port>[0-9]+))?/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';
 
 
@@ -95,11 +95,10 @@ class GitLabDriver extends VcsDriver
             ? $match['scheme']
             ? $match['scheme']
             : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https')
             : (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);
         $this->namespace = implode('/', $urlParts);
@@ -260,10 +259,7 @@ class GitLabDriver extends VcsDriver
      */
      */
     public function getApiUrl()
     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()
     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';
         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'];
         $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
         $urlParts = explode('/', $match['parts']);
         $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;
             return false;
         }
         }
 
 
@@ -495,16 +495,16 @@ class GitLabDriver extends VcsDriver
      * @param  array       $urlParts
      * @param  array       $urlParts
      * @return bool|string
      * @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;
             return $guessedDomain;
         }
         }
 
 
         while (null !== ($part = array_shift($urlParts))) {
         while (null !== ($part = array_shift($urlParts))) {
             $guessedDomain .= '/' . $part;
             $guessedDomain .= '/' . $part;
 
 
-            if (in_array($guessedDomain, $configuredDomains)) {
+            if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{/}', ':'.$portNumber.'/', $guessedDomain, 1), $configuredDomains))) {
                 return $guessedDomain;
                 return $guessedDomain;
             }
             }
         }
         }

+ 44 - 0
src/Composer/Script/Event.php

@@ -39,6 +39,11 @@ class Event extends BaseEvent
      */
      */
     private $devMode;
     private $devMode;
 
 
+    /**
+     * @var BaseEvent
+     */
+    private $originatingEvent;
+
     /**
     /**
      * Constructor.
      * Constructor.
      *
      *
@@ -55,6 +60,7 @@ class Event extends BaseEvent
         $this->composer = $composer;
         $this->composer = $composer;
         $this->io = $io;
         $this->io = $io;
         $this->devMode = $devMode;
         $this->devMode = $devMode;
+        $this->originatingEvent = null;
     }
     }
 
 
     /**
     /**
@@ -86,4 +92,42 @@ class Event extends BaseEvent
     {
     {
         return $this->devMode;
         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;
+    }
 }
 }

+ 3 - 0
src/Composer/Util/ErrorHandler.php

@@ -33,6 +33,7 @@ class ErrorHandler
      *
      *
      * @static
      * @static
      * @throws \ErrorException
      * @throws \ErrorException
+     * @return bool
      */
      */
     public static function handle($level, $message, $file, $line)
     public static function handle($level, $message, $file, $line)
     {
     {
@@ -63,6 +64,8 @@ class ErrorHandler
                 }, array_slice(debug_backtrace(), 2))));
                 }, array_slice(debug_backtrace(), 2))));
             }
             }
         }
         }
+
+        return true;
     }
     }
 
 
     /**
     /**

+ 10 - 1
src/Composer/Util/GitLab.php

@@ -57,7 +57,10 @@ class GitLab
      */
      */
     public function authorizeOAuth($originUrl)
     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;
             return false;
         }
         }
 
 
@@ -77,6 +80,12 @@ class GitLab
             return true;
             return true;
         }
         }
 
 
+        if (isset($authTokens[$bcOriginUrl])) {
+            $this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token');
+
+            return true;
+        }
+
         return false;
         return false;
     }
     }
 
 

+ 0 - 2
src/Composer/Util/Perforce.php

@@ -363,8 +363,6 @@ class Perforce
         while ($line !== false) {
         while ($line !== false) {
             $line = fgets($pipe);
             $line = fgets($pipe);
         }
         }
-
-        return;
     }
     }
 
 
     public function windowsLogin($password)
     public function windowsLogin($password)

+ 4 - 0
src/Composer/Util/RemoteFilesystem.php

@@ -286,6 +286,8 @@ class RemoteFilesystem
                 $errorMessage .= "\n";
                 $errorMessage .= "\n";
             }
             }
             $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
             $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
+
+            return true;
         });
         });
         try {
         try {
             $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);
             $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);
@@ -459,6 +461,8 @@ class RemoteFilesystem
                     $errorMessage .= "\n";
                     $errorMessage .= "\n";
                 }
                 }
                 $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg);
                 $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg);
+
+                return true;
             });
             });
             $result = (bool) file_put_contents($fileName, $result);
             $result = (bool) file_put_contents($fileName, $result);
             restore_error_handler();
             restore_error_handler();

+ 0 - 2
src/Composer/Util/TlsHelper.php

@@ -19,8 +19,6 @@ use Composer\CaBundle\CaBundle;
  */
  */
 final class TlsHelper
 final class TlsHelper
 {
 {
-    private static $useOpensslParse;
-
     /**
     /**
      * Match hostname against a certificate.
      * Match hostname against a certificate.
      *
      *

+ 3 - 0
src/Composer/Util/Url.php

@@ -70,6 +70,9 @@ class Url
         }
         }
 
 
         $origin = (string) parse_url($url, PHP_URL_HOST);
         $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)) {
         if (strpos($origin, '.github.com') === (strlen($origin) - 11)) {
             return 'github.com';
             return 'github.com';

+ 108 - 0
src/Composer/Util/Zip.php

@@ -0,0 +1,108 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <andreas.schempp@terminal42.ch>
+ */
+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;
+    }
+}

+ 4 - 4
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;
                 continue;
             }
             }
 
 
             // Handle section headers.
             // Handle section headers.
             if (null === $section) {
             if (null === $section) {
-                $section = $tokens[$i];
+                $section = $token;
                 continue;
                 continue;
             }
             }
 
 
-            $sectionData = $tokens[$i];
+            $sectionData = $token;
 
 
             // Allow sections to validate, or modify their section data.
             // Allow sections to validate, or modify their section data.
             switch ($section) {
             switch ($section) {

+ 0 - 7
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));
         $this->assertContains('JOB     : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($pool));
     }
     }
-
-    private function getRuleMock()
-    {
-        return $this->getMockBuilder('Composer\DependencyResolver\Rule')
-            ->disableOriginalConstructor()
-            ->getMock();
-    }
 }
 }

+ 16 - 0
tests/Composer/Test/Json/JsonManipulatorTest.php

@@ -1448,6 +1448,22 @@ class JsonManipulatorTest extends TestCase
     "repositories": {
     "repositories": {
     }
     }
 }
 }
+',
+            ),
+            'works on simple ones escaped slash' => array(
+                '{
+    "repositories": {
+        "foo\/bar": {
+            "bar": "baz"
+        }
+    }
+}',
+                'foo/bar',
+                true,
+                '{
+    "repositories": {
+    }
+}
 ',
 ',
             ),
             ),
             'works on simple ones middle' => array(
             'works on simple ones middle' => array(

+ 0 - 1
tests/Composer/Test/Package/Loader/ArrayLoaderTest.php

@@ -148,7 +148,6 @@ class ArrayLoaderTest extends TestCase
     {
     {
         $package = $this->loader->load($config);
         $package = $this->loader->load($config);
         $dumper = new ArrayDumper;
         $dumper = new ArrayDumper;
-        $expectedConfig = $config;
         $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config);
         $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config);
         $this->assertEquals($expectedConfig, $dumper->dump($package));
         $this->assertEquals($expectedConfig, $dumper->dump($package));
     }
     }

+ 0 - 9
tests/Composer/Test/Repository/Vcs/FossilDriverTest.php

@@ -40,15 +40,6 @@ class FossilDriverTest extends TestCase
         $fs->removeDirectory($this->home);
         $fs->removeDirectory($this->home);
     }
     }
 
 
-    private function getCmd($cmd)
-    {
-        if (Platform::isWindows()) {
-            return strtr($cmd, "'", '"');
-        }
-
-        return $cmd;
-    }
-
     public static function supportProvider()
     public static function supportProvider()
     {
     {
         return array(
         return array(

+ 0 - 9
tests/Composer/Test/Repository/Vcs/SvnDriverTest.php

@@ -71,15 +71,6 @@ class SvnDriverTest extends TestCase
         $svn->initialize();
         $svn->initialize();
     }
     }
 
 
-    private function getCmd($cmd)
-    {
-        if (Platform::isWindows()) {
-            return strtr($cmd, "'", '"');
-        }
-
-        return $cmd;
-    }
-
     public static function supportProvider()
     public static function supportProvider()
     {
     {
         return array(
         return array(

+ 80 - 0
tests/Composer/Test/Script/EventTest.php

@@ -0,0 +1,80 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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;
+    }
+}

BIN
tests/Composer/Test/Util/Fixtures/Zip/empty.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/folder.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/multiple.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/nojson.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/root.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip


+ 0 - 3
tests/Composer/Test/Util/GitHubTest.php

@@ -24,12 +24,9 @@ use RecursiveIteratorIterator;
  */
  */
 class GitHubTest extends TestCase
 class GitHubTest extends TestCase
 {
 {
-    private $username = 'username';
     private $password = 'password';
     private $password = 'password';
-    private $authcode = 'authcode';
     private $message = 'mymessage';
     private $message = 'mymessage';
     private $origin = 'github.com';
     private $origin = 'github.com';
-    private $token = 'githubtoken';
 
 
     public function testUsernamePasswordAuthenticationFlow()
     public function testUsernamePasswordAuthenticationFlow()
     {
     {

+ 0 - 1
tests/Composer/Test/Util/GitLabTest.php

@@ -24,7 +24,6 @@ class GitLabTest extends TestCase
 {
 {
     private $username = 'username';
     private $username = 'username';
     private $password = 'password';
     private $password = 'password';
-    private $authcode = 'authcode';
     private $message = 'mymessage';
     private $message = 'mymessage';
     private $origin = 'gitlab.com';
     private $origin = 'gitlab.com';
     private $token = 'gitlabtoken';
     private $token = 'gitlabtoken';

+ 117 - 0
tests/Composer/Test/Util/ZipTest.php

@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <andreas.schempp@terminal42.ch>
+ */
+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);
+    }
+}