diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 1616f18..c3de7e8 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1940,6 +1940,23 @@ function install_check_translations($langcode, $server_pattern) { } } + // Installations on Windows can run into limitations with MAX_PATH if the + // Drupal root directory is too deep in the filesystem. Generally this shows + // up in cached Twig templates because template suggestions can arbitrarily + // lengthen a filename. There is no predictable root directory depth beyond + // which Drupal will not function correctly on Windows, thus we only warn if + // Drupal is being installed more than 100 characters deep in the filesystem. + if (substr(PHP_OS, 0, 3) == 'WIN') { + $depth = strlen(realpath(DRUPAL_ROOT)); + if ($depth > 100) { + $requirements['max_path_on_windows'] = [ + 'title' => t('Windows installation'), + 'description' => t('The path length to the Drupal root directory (@depth characters) is greater than 100 characters. This may cause problems when running on a Windows server. It is recommended to install Drupal on Windows in a shorter path.', ['@depth' => $depth]), + 'severity' => REQUIREMENT_WARNING, + ]; + } + } + return $requirements; } diff --git a/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php index 3c5d55d..fa3b1ff 100644 --- a/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php +++ b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFastFileStorage.php @@ -2,6 +2,8 @@ namespace Drupal\Component\PhpStorage; +use \Drupal\Component\Utility\Crypt; + /** * Stores PHP code in files with securely hashed names. * @@ -130,7 +132,8 @@ public function getFullPath($name, &$directory = NULL, &$directory_mtime = NULL) if (!isset($directory_mtime)) { $directory_mtime = file_exists($directory) ? filemtime($directory) : 0; } - return $directory . '/' . hash_hmac('sha256', $name, $this->secret . $directory_mtime) . '.php'; + $hashed_name = Crypt::shortHash($name, $this->secret . $directory_mtime) . '.php'; + return $directory . '/' . $hashed_name; } /** diff --git a/core/lib/Drupal/Component/Utility/Crypt.php b/core/lib/Drupal/Component/Utility/Crypt.php index ace4ebd..082086b 100644 --- a/core/lib/Drupal/Component/Utility/Crypt.php +++ b/core/lib/Drupal/Component/Utility/Crypt.php @@ -131,4 +131,35 @@ public static function randomBytesBase64($count = 32) { return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(static::randomBytes($count))); } + /** + * Generates a shortened sha256 hash. While there is always the risk of + * collision (when different $data generate the same hash), shortened hashes + * increase that risk. This function should only be used when that additional + * risk is acceptable. + * + * @param string $data + * Message to be hashed. + * @param string $key + * Shared secret key. + * @param int $length + * Hash length. Defaults to 10 characters. Maximum 50 characters. + * + * @return string + * A sha256 base 36 encoded shortened hash. + * + * @throws \InvalidArgumentException + * When the $length argument is longer than 50. + */ + public static function shortHash($data, $key = NULL, $length = 10) { + if ($length > 50) { + throw new \InvalidArgumentException(sprintf('shortHash length %s is longer than the maximum allowed of 50.', $length)); + } + if ($key) { + return substr(base_convert(hash_hmac('sha256', $data, $key), 16, 36), 0, $length); + } + else { + return substr(base_convert(hash('sha256', $data), 16, 36), 0, $length); + } + } + } diff --git a/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php b/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php index b9f7cae..76495b2 100644 --- a/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php +++ b/core/lib/Drupal/Core/Template/TwigPhpStorageCache.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\PhpStorage\PhpStorageFactory; +use Drupal\Component\Utility\Crypt; /** * Provides an alternate cache storage for Twig using PhpStorage. @@ -67,7 +68,7 @@ protected function storage() { * {@inheritdoc} */ public function generateKey($name, $className) { - $hash = hash('sha256', $className); + $hash = Crypt::shortHash($className); if (strpos($name, '{# inline_template_start #}') === 0) { // $name is an inline template, and can have characters that are not valid diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php index deaf033..f932e8b 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Html; use Drupal\Core\Site\Settings; use Drupal\KernelTests\KernelTestBase; +use Drupal\Component\Utility\Crypt; /** * Tests the twig environment. @@ -87,7 +88,7 @@ public function testInlineTemplate() { $cache = $environment->getCache(); $class = $environment->getTemplateClass($name); - $expected = $hash . '_inline-template' . '_' . hash('sha256', $class); + $expected = $hash . '_inline-template_' . Crypt::shortHash($class); $this->assertEqual($expected, $cache->generateKey($name, $class)); } diff --git a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php index 64d17eb..149daf7 100644 --- a/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php +++ b/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFileStorageBase.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\Component\PhpStorage; +use \Drupal\Component\Utility\Crypt; + /** * Base test class for MTime protected storage. */ @@ -77,7 +79,7 @@ public function testSecurity() { $expected_directory = $expected_root_directory . '/' . $name; } $directory_mtime = filemtime($expected_directory); - $expected_filename = $expected_directory . '/' . hash_hmac('sha256', $name, $this->secret . $directory_mtime) . '.php'; + $expected_filename = $expected_directory . '/' . Crypt::shortHash($name, $this->secret . $directory_mtime) . '.php'; // Ensure the file exists and that it and the containing directory have // minimal permissions. fileperms() can return high bits unrelated to diff --git a/core/tests/Drupal/Tests/Component/Utility/CryptTest.php b/core/tests/Drupal/Tests/Component/Utility/CryptTest.php index ff1e6cc..e1fc45c 100644 --- a/core/tests/Drupal/Tests/Component/Utility/CryptTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/CryptTest.php @@ -4,6 +4,7 @@ use Drupal\Tests\UnitTestCase; use Drupal\Component\Utility\Crypt; +use InvalidArgumentException; /** * Tests random byte generation. @@ -80,6 +81,42 @@ public function testHmacBase64Invalid($data, $key) { } /** + * Tests shortHash function. + * + * @dataProvider providerTestShortHash + * @covers ::shortHash + * + * @param string $data + * Data to hash. + * @param string $key + * Key to use in hashing process. + * @param int $length + * Trim the returned hash to $length characters. + * @param string $expected_hash + * The expected hash. + */ + public function testShortHash($data, $key, $length, $expected_hash) { + if (is_null($length)) { + $this->assertSame($expected_hash, Crypt::shortHash($data, $key)); + } + else { + $this->assertSame($expected_hash, Crypt::shortHash($data, $key, $length)); + } + } + + + /** + * Tests the shortHash method with invalid parameters. + * + * @covers ::shortHash + */ + public function testShortHashInvalid() { + $this->setExpectedException(InvalidArgumentException::class, 'shortHash length 51 is longer than the maximum allowed of 50.'); + Crypt::shortHash('Length is too long', 'secret', 51); + } + + + /** * Provides data for self::testHashBase64(). * * @return array Test data. @@ -147,4 +184,45 @@ public function providerTestHmacBase64Invalid() { ); } + /** + * Provides data for self::testShortHash(). + * + * @return array + * Test data. + */ + public function providerTestShortHash() { + return [ + [ + 'data' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', + 'key' => 'secret-key', + 'length' => NULL, + 'expected_hash' => '3gulzih2ms', + ], + [ + 'data' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum', + 'key' => NULL, + 'length' => NULL, + 'expected_hash' => '3js5i38cnx', + ], + [ + 'data' => 'Lorem', + 'key' => NULL, + 'length' => NULL, + 'expected_hash' => 'oo8723opxr', + ], + [ + 'data' => 'Tests a non-default value for the length parameter.', + 'key' => NULL, + 'length' => 24, + 'expected_hash' => '3jwgcm60kgg0wock8s84kkk0', + ], + [ + 'data' => 'Tests a non-default value for the length parameter.', + 'key' => NULL, + 'length' => 50, + 'expected_hash' => '3jwgcm60kgg0wock8s84kkk00g0s04ogowws4ooocwcgss8484', + ], + ]; + } + }