Commit ba455a1d authored by alexpott's avatar alexpott

Issue #2606772 by mikeker, pwolanin, YesCT, david_garcia, alexpott, cilefen,...

Issue #2606772 by mikeker, pwolanin, YesCT, david_garcia, alexpott, cilefen, xjm, Cottser, catch, joelpittet, effulgentsia, kevla, dawehner: Long Twig cache directories can cause failures on some filesystems
parent cd05ee42
......@@ -2,6 +2,7 @@
namespace Drupal\Core\Template;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\PhpStorage\PhpStorageFactory;
......@@ -17,6 +18,11 @@
*/
class TwigPhpStorageCache implements \Twig_CacheInterface {
/**
* The maximum length for each part of the cache key suffix.
*/
const SUFFIX_SUBSTRING_LENGTH = 25;
/**
* The cache object used for auto-refresh via mtime.
*
......@@ -67,20 +73,28 @@ protected function storage() {
* {@inheritdoc}
*/
public function generateKey($name, $className) {
$hash = hash('sha256', $className);
if (strpos($name, '{# inline_template_start #}') === 0) {
// $name is an inline template, and can have characters that are not valid
// for a filename. $hash is unique for each inline template so we just use
// the generic name 'inline-template' here.
// for a filename. $suffix is unique for each inline template so we just
// use the generic name 'inline-template' here.
$name = 'inline-template';
}
else {
$name = basename($name);
}
// The first part is what is invalidated.
return $this->templateCacheFilenamePrefix . '_' . $name . '_' . $hash;
// Windows (and some encrypted Linux systems) only support 255 characters in
// a path. On Windows a requirements error is displayed and installation is
// blocked if Drupal's public files path is longer than 120 characters.
// Thus, to always be less than 255, file paths may not be more than 135
// characters long. Using the default PHP file storage class, the Twig cache
// file path will be 124 characters long at most, which provides a margin of
// safety.
$suffix = substr($name, 0, self::SUFFIX_SUBSTRING_LENGTH) . '_';
$suffix .= substr(Crypt::hashBase64($className), 0, self::SUFFIX_SUBSTRING_LENGTH);
// The cache prefix is what gets invalidated.
return $this->templateCacheFilenamePrefix . '_' . $suffix;
}
/**
......
......@@ -904,6 +904,23 @@ function system_requirements($phase) {
}
}
// 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 and other public files with long directory or
// file names. There is no definite root directory depth below which Drupal is
// guaranteed to function correctly on Windows. Since problems are likely
// with more than 100 characters in the Drupal root path, show an error.
if (substr(PHP_OS, 0, 3) == 'WIN') {
$depth = strlen(realpath(DRUPAL_ROOT . '/' . PublicStream::basePath()));
if ($depth > 120) {
$requirements['max_path_on_windows'] = [
'title' => t('Windows installation depth'),
'description' => t('The public files directory path is %depth characters. Paths longer than 120 characters will cause problems on Windows.', ['%depth' => $depth]),
'severity' => REQUIREMENT_ERROR,
];
}
}
return $requirements;
}
......
......@@ -2,8 +2,10 @@
namespace Drupal\KernelTests\Core\Theme;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Core\Site\Settings;
use Drupal\Core\Template\TwigPhpStorageCache;
use Drupal\KernelTests\KernelTestBase;
/**
......@@ -87,7 +89,7 @@ public function testInlineTemplate() {
$cache = $environment->getCache();
$class = $environment->getTemplateClass($name);
$expected = $prefix . '_inline-template' . '_' . hash('sha256', $class);
$expected = $prefix . '_inline-template_' . substr(Crypt::hashBase64($class), 0, TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH);
$this->assertEqual($expected, $cache->generateKey($name, $class));
}
......@@ -116,6 +118,20 @@ public function testCacheFilename() {
// static cache.
$environment = \Drupal::service('twig');
// A template basename greater than the constant
// TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH should get truncated.
$cache = $environment->getCache();
$long_name = 'core/modules/system/templates/block--system-messages-block.html.twig';
$this->assertGreaterThan(TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH, strlen(basename($long_name)));
$class = $environment->getTemplateClass($long_name);
$key = $cache->generateKey($long_name, $class);
$prefix = $environment->getTwigCachePrefix();
// The key should consist of the prefix, an underscore, and two strings
// each truncated to length TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH
// separated by an underscore.
$expected = strlen($prefix) + 2 + 2 * TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH;
$this->assertEquals($expected, strlen($key));
$original_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig');
\Drupal::getContainer()->set('twig', NULL);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment