From a034c6207eca1922804ec186c2da6d55082d63b8 Mon Sep 17 00:00:00 2001
From: Helmut Hummel <typo3@helhum.io>
Date: Mon, 13 Sep 2021 18:24:56 +0200
Subject: [PATCH] [BUGFIX] Restrict composer-runtime-api to 2.1

With https://review.typo3.org/c/Packages/TYPO3.CMS/+/71075
usage of InstalledVersions class was introduced.
More specifically the install_path of the root package.

However install paths of packages are only available
in composer-runtime-api 2.1, therefore we need to
require this a minimum version.

As drive by change the usage of Composer runtime API
is encapsulated in the Environment class and
path normalisation is put there as well, to not require
normalisation for each package.

composer req composer-runtime-api:^2.1
composer req composer-runtime-api:^2.1 -d typo3/sysext/core/ --no-update

Resolves: #95237
Related: #94996
Related: #95171
Releases: master

Change-Id: Ibe8f676830327b1036be8074e0dc38e54743b479
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/71083
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
---
 composer.json                                 |  4 +--
 composer.lock                                 |  4 +--
 .../Composer/PackageArtifactBuilder.php       | 13 +++++----
 .../sysext/core/Classes/Core/Environment.php  | 27 +++++++++++++++++++
 typo3/sysext/core/Classes/Package/Package.php |  7 +++--
 typo3/sysext/core/composer.json               |  2 +-
 6 files changed, 43 insertions(+), 14 deletions(-)

diff --git a/composer.json b/composer.json
index c22b206bd19c..d49a26ea1b37 100644
--- a/composer.json
+++ b/composer.json
@@ -38,7 +38,7 @@
 		"ext-pcre": "*",
 		"ext-session": "*",
 		"ext-xml": "*",
-		"composer-runtime-api": "^2.0",
+		"composer-runtime-api": "^2.1",
 		"bacon/bacon-qr-code": "^2.0",
 		"christian-riesen/base32": "^1.6",
 		"cogpowered/finediff": "~0.3.1",
@@ -50,8 +50,8 @@
 		"egulias/email-validator": "^3.1",
 		"enshrined/svg-sanitize": "^0.14.1",
 		"guzzlehttp/guzzle": "^7.3.0",
-		"guzzlehttp/psr7": "^1.7.0 || ^2.0",
 		"guzzlehttp/promises": "^1.4.0",
+		"guzzlehttp/psr7": "^1.7.0 || ^2.0",
 		"nikic/php-parser": "^4.10.4",
 		"phpdocumentor/reflection-docblock": "^5.2",
 		"phpdocumentor/type-resolver": "^1.4",
diff --git a/composer.lock b/composer.lock
index 994fca3ac938..dfe49e158eb1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "e2dcfec64d2bf1cd1df99b52b5c362ca",
+    "content-hash": "1b0a189cecb7784c25b2a2e33e2de299",
     "packages": [
         {
             "name": "bacon/bacon-qr-code",
@@ -8275,7 +8275,7 @@
         "ext-pcre": "*",
         "ext-session": "*",
         "ext-xml": "*",
-        "composer-runtime-api": "^2.0"
+        "composer-runtime-api": "^2.1"
     },
     "platform-dev": [],
     "platform-overrides": {
diff --git a/typo3/sysext/core/Classes/Composer/PackageArtifactBuilder.php b/typo3/sysext/core/Classes/Composer/PackageArtifactBuilder.php
index f05a8edc17b7..2b143f473058 100644
--- a/typo3/sysext/core/Classes/Composer/PackageArtifactBuilder.php
+++ b/typo3/sysext/core/Classes/Composer/PackageArtifactBuilder.php
@@ -47,6 +47,7 @@ use TYPO3\CMS\Core\Utility\PathUtility;
 class PackageArtifactBuilder extends PackageManager implements InstallerScript
 {
     private Event $event;
+    private Config $config;
 
     /**
      * Array of package keys that are installed by Composer but have no relation to TYPO3 extension API
@@ -78,11 +79,12 @@ class PackageArtifactBuilder extends PackageManager implements InstallerScript
     public function run(Event $event): bool
     {
         $this->event = $event;
+        $this->config = Config::load($this->event->getComposer(), $this->event->getIO());
         $composer = $this->event->getComposer();
         $basePath = Config::load($composer)->get('base-dir');
         $this->packagesBasePath = $basePath . '/';
         foreach ($this->extractPackageMapFromComposer() as [$composerPackage, $path, $extensionKey]) {
-            $packagePath = PathUtility::sanitizeTrailingSeparator($path ?: $basePath);
+            $packagePath = PathUtility::sanitizeTrailingSeparator($path);
             $package = new Package($this, $extensionKey, $packagePath, true);
             $package->makePathRelative(new Filesystem(), $basePath);
             $package->getPackageMetaData()->setVersion($composerPackage->getPrettyVersion());
@@ -180,17 +182,18 @@ class PackageArtifactBuilder extends PackageManager implements InstallerScript
      */
     private function handleRootPackage(PackageInterface $rootPackage, string $extensionKey): array
     {
-        if ($rootPackage->getType() !== 'typo3-cms-extension' || !file_exists($this->packagesBasePath . 'Resources/Public/')) {
-            return [$rootPackage, $this->packagesBasePath, $extensionKey];
+        $baseDir = $this->config->get('base-dir');
+        if ($rootPackage->getType() !== 'typo3-cms-extension' || !file_exists($baseDir . '/Resources/Public/')) {
+            return [$rootPackage, $baseDir, $extensionKey];
         }
         $composer = $this->event->getComposer();
         $typo3ExtensionInstallPath = $composer->getInstallationManager()->getInstaller('typo3-cms-extension')->getInstallPath($rootPackage);
         $filesystem = new Filesystem();
         $filesystem->ensureDirectoryExists(dirname($typo3ExtensionInstallPath));
         if (!file_exists($typo3ExtensionInstallPath) && !$filesystem->isSymlinkedDirectory($typo3ExtensionInstallPath)) {
-            $filesystem->relativeSymlink($this->packagesBasePath, $typo3ExtensionInstallPath);
+            $filesystem->relativeSymlink($baseDir, $typo3ExtensionInstallPath);
         }
-        if (realpath($this->packagesBasePath) !== realpath($typo3ExtensionInstallPath)) {
+        if (realpath($baseDir) !== realpath($typo3ExtensionInstallPath)) {
             $this->event->getIO()->warning('The root package is of type "typo3-cms-extension" and has public resources, but could not be linked to typo3conf/ext directory, because target directory already exits.');
         }
 
diff --git a/typo3/sysext/core/Classes/Core/Environment.php b/typo3/sysext/core/Classes/Core/Environment.php
index 8ce8c9eb3f76..dfe0ac1d5612 100644
--- a/typo3/sysext/core/Classes/Core/Environment.php
+++ b/typo3/sysext/core/Classes/Core/Environment.php
@@ -17,6 +17,9 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Core;
 
+use Composer\InstalledVersions;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
 /**
  * This class is initialized once in the SystemEnvironmentBuilder, and can then
  * be used throughout the application to access common variables
@@ -70,6 +73,11 @@ class Environment
      */
     protected static $projectPath;
 
+    /**
+     * @var string
+     */
+    protected static $composerRootPath;
+
     /**
      * @var string
      */
@@ -126,6 +134,7 @@ class Environment
         self::$composerMode = $composerMode;
         self::$context = $context;
         self::$projectPath = $projectPath;
+        self::$composerRootPath = $composerMode ? PathUtility::getCanonicalPath(InstalledVersions::getRootPackage()['install_path']) : '';
         self::$publicPath = $publicPath;
         self::$varPath = $varPath;
         self::$configPath = $configPath;
@@ -181,6 +190,24 @@ class Environment
         return self::$projectPath;
     }
 
+    /**
+     * In most cases in composer-mode setups this is the same as project path.
+     * However since the project path is configurable, the paths may differ.
+     * In future versions this configurability will go away and this method will be removed.
+     * This path is only required for some internal path handling regarding package paths until then.
+     * @internal
+     *
+     * @return string The absolute path to the composer root directory without the trailing slash
+     */
+    public static function getComposerRootPath(): string
+    {
+        if (self::$composerMode === false) {
+            throw new \BadMethodCallException('Composer root path is only available in Composer mode', 1631700480);
+        }
+
+        return self::$composerRootPath;
+    }
+
     /**
      * The public web folder where index.php (= the frontend application) is put, without trailing slash.
      * For non-composer installations, the project path = the public path.
diff --git a/typo3/sysext/core/Classes/Package/Package.php b/typo3/sysext/core/Classes/Package/Package.php
index be1b7e36ac0c..2b5d2eb97246 100644
--- a/typo3/sysext/core/Classes/Package/Package.php
+++ b/typo3/sysext/core/Classes/Package/Package.php
@@ -15,12 +15,11 @@
 
 namespace TYPO3\CMS\Core\Package;
 
-use Composer\InstalledVersions;
 use Composer\Util\Filesystem;
+use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Package\Exception\InvalidPackageKeyException;
 use TYPO3\CMS\Core\Package\Exception\InvalidPackagePathException;
 use TYPO3\CMS\Core\Package\MetaData\PackageConstraint;
-use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
  * A Package representing the details of an extension and/or a composer package
@@ -235,7 +234,7 @@ class Package implements PackageInterface
         }
         $this->isRelativePackagePath = false;
 
-        return $this->packagePath = PathUtility::getCanonicalPath(InstalledVersions::getRootPackage()['install_path'] . '/' . $this->packagePath) . '/';
+        return $this->packagePath = Environment::getComposerRootPath() . '/' . $this->packagePath;
     }
 
     /**
@@ -248,7 +247,7 @@ class Package implements PackageInterface
     public function makePathRelative(Filesystem $filesystem, string $composerRootPath): void
     {
         $this->isRelativePackagePath = true;
-        $this->packagePath = $filesystem->findShortestPath($composerRootPath, $this->packagePath, true);
+        $this->packagePath = ($composerRootPath . '/') === $this->packagePath ? '' : $filesystem->findShortestPath($composerRootPath, $this->packagePath, true) . '/';
     }
 
     /**
diff --git a/typo3/sysext/core/composer.json b/typo3/sysext/core/composer.json
index a45bc7c679a9..30184367c653 100644
--- a/typo3/sysext/core/composer.json
+++ b/typo3/sysext/core/composer.json
@@ -26,7 +26,7 @@
 		"ext-pcre": "*",
 		"ext-session": "*",
 		"ext-xml": "*",
-		"composer-runtime-api": "^2.0",
+		"composer-runtime-api": "^2.1",
 		"bacon/bacon-qr-code": "^2.0",
 		"christian-riesen/base32": "^1.6",
 		"cogpowered/finediff": "~0.3.1",
-- 
GitLab