diff --git a/composer.json b/composer.json index 18fa5270b2..a1d27bcfb5 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "require": { "composer/installers": "^1.0.24", "wikimedia/composer-merge-plugin": "^1.4", - "drupal/core": "self.version" + "drupal/core": "self.version", + "drupal/core-vendor-hardening": "*" }, "require-dev": { "behat/mink": "1.7.x-dev", @@ -67,9 +68,6 @@ "pre-install-cmd": "Drupal\\Core\\Composer\\Composer::ensureComposerVersion", "pre-update-cmd": "Drupal\\Core\\Composer\\Composer::ensureComposerVersion", "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump", - "post-autoload-dump": "Drupal\\Core\\Composer\\Composer::ensureHtaccess", - "post-package-install": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup", - "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup", "phpcs": "phpcs --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --", "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --" }, @@ -81,6 +79,10 @@ { "type": "path", "url": "core" + }, + { + "type": "path", + "url": "composer/Plugin/VendorHardening" } ] } diff --git a/composer.lock b/composer.lock index 6df5b6acf9..c4156c5a66 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": "36ce6886f9438a31a1d93784286214df", + "content-hash": "458d5e8febfbcec786681ac009ac587c", "packages": [ { "name": "asm89/stack-cors", @@ -902,6 +902,36 @@ ], "description": "Drupal is an open source content management platform powering millions of websites and applications." }, + { + "name": "drupal/core-vendor-hardening", + "version": "8.8.x-dev", + "dist": { + "type": "path", + "url": "composer/Plugin/VendorHardening", + "reference": "2db54f089065dedbe4a040b01f7b527f2bad68f6" + }, + "require": { + "composer-plugin-api": "^1.1", + "php": ">=7.0.8" + }, + "type": "composer-plugin", + "extra": { + "class": "Drupal\\Composer\\Plugin\\VendorHardening\\VendorHardeningPlugin" + }, + "autoload": { + "psr-4": { + "Drupal\\Composer\\Plugin\\VendorHardening\\": "." + } + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Hardens the vendor directory for when it's in the docroot.", + "homepage": "https://www.drupal.org/project/drupal", + "keywords": [ + "drupal" + ] + }, { "name": "easyrdf/easyrdf", "version": "0.9.1", diff --git a/composer/Plugin/VendorHardening/VendorHardeningPlugin.php b/composer/Plugin/VendorHardening/VendorHardeningPlugin.php index 7e36e0e9e1..caf3b2d2ba 100644 --- a/composer/Plugin/VendorHardening/VendorHardeningPlugin.php +++ b/composer/Plugin/VendorHardening/VendorHardeningPlugin.php @@ -5,12 +5,13 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer\PackageEvent; +use Composer\Installer\PackageEvents; use Composer\IO\IOInterface; +use Composer\Package\CompletePackage; use Composer\Plugin\PluginInterface; +use Composer\Script\Event; use Composer\Script\ScriptEvents; use Composer\Util\Filesystem; -use Composer\Script\Event; -use Composer\Installer\PackageEvents; /** * A Composer plugin to clean out your project's vendor directory. @@ -70,6 +71,8 @@ public static function getSubscribedEvents() { ScriptEvents::POST_AUTOLOAD_DUMP => 'onPostAutoloadDump', ScriptEvents::POST_UPDATE_CMD => 'onPostCmd', ScriptEvents::POST_INSTALL_CMD => 'onPostCmd', + PackageEvents::PRE_PACKAGE_INSTALL => 'onPrePackageInstall', + PackageEvents::PRE_PACKAGE_UPDATE => 'onPrePackageUpdate', PackageEvents::POST_PACKAGE_INSTALL => 'onPostPackageInstall', PackageEvents::POST_PACKAGE_UPDATE => 'onPostPackageUpdate', ]; @@ -95,6 +98,28 @@ public function onPostCmd(Event $event) { $this->cleanAllPackages($this->composer->getConfig()->get('vendor-dir')); } + /** + * PRE_PACKAGE_INSTALL event handler. + * + * @param \Composer\Installer\PackageEvent $event + */ + public function onPrePackageInstall(PackageEvent $event) { + /** @var \Composer\Package\CompletePackage $package */ + $package = $event->getOperation()->getPackage(); + $this->removeBinBeforeCleanup($package); + } + + /** + * PRE_PACKAGE_INSTALL event handler. + * + * @param \Composer\Installer\PackageEvent $event + */ + public function onPrePackageUpdate(PackageEvent $event) { + /** @var \Composer\Package\CompletePackage $package */ + $package = $event->getOperation()->getTargetPackage(); + $this->removeBinBeforeCleanup($package); + } + /** * POST_PACKAGE_INSTALL event handler. * @@ -119,6 +144,63 @@ public function onPostPackageUpdate(PackageEvent $event) { $this->cleanPackage($this->composer->getConfig()->get('vendor-dir'), $package_name); } + /** + * Remove bin config for packages that would have the bin file removed. + * + * @param \Composer\Package\CompletePackage $package + * + * @see https://www.drupal.org/project/drupal/issues/3082866 + */ + protected function removeBinBeforeCleanup(CompletePackage $package) { + // Only do this if there are binaries and cleanup paths. + $binaries = $package->getBinaries(); + $clean_paths = $this->config->getPathsForPackage($package->getName()); + if (!$binaries || !$clean_paths) { + return; + } + // Make a filesystem model to explore. This is a keyed array that looks like + // all the places that will be removed by cleanup. 'tests/src' becomes + // $filesystem['tests']['src'] = TRUE; + $filesystem = []; + foreach ($clean_paths as $clean_path) { + $clean_pieces = explode("/", $clean_path); + $current = &$filesystem; + foreach ($clean_pieces as $clean_piece) { + $current = &$current[$clean_piece]; + } + $current = TRUE; + } + // Explore the filesystem with our bin config. + $unset_these_binaries = []; + foreach ($binaries as $binary) { + $binary_pieces = explode('/', $binary); + $current = &$filesystem; + foreach ($binary_pieces as $binary_piece) { + if (!isset($current[$binary_piece])) { + break; + } + else { + // Value of TRUE means we're at the end of the path. + if ($current[$binary_piece] === TRUE) { + $unset_these_binaries[$binary] = $binary; + break; + } + } + $current = &$filesystem[$binary_piece]; + } + } + if ($unset_these_binaries) { + $this->io->writeError(sprintf('%sModifying bin config for %s which overlaps with cleanup directories.', str_repeat(' ', 4), $package->getName())); + $modified_binaries = []; + foreach ($binaries as $binary) { + if (!in_array($binary, $unset_these_binaries)) { + $modified_binaries[] = $binary; + } + } + $package->setBinaries($modified_binaries); + } + } + /** * Gets a list of all installed packages from Composer. *