diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php index eb1ca57a5fdf0ba42e8e9ace7208a6711dc593c3..9755c1abbceb8fb0e84bdb46c3cd747420f0ce97 100644 --- a/core/lib/Drupal/Core/Cache/CacheCollector.php +++ b/core/lib/Drupal/Core/Cache/CacheCollector.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Cache; +use Drupal\Component\Utility\Crypt; use Drupal\Core\DestructableInterface; use Drupal\Core\Lock\LockBackendInterface; @@ -232,7 +233,7 @@ protected function updateCache($lock = TRUE) { // Lock cache writes to help avoid stampedes. $cid = $this->getCid(); - $lock_name = $cid . ':' . __CLASS__; + $lock_name = $this->normalizeLockName($cid . ':' . __CLASS__); if (!$lock || $this->lock->acquire($lock_name)) { // Set and delete operations invalidate the cache item. Try to also load // an eventually invalidated cache entry, only update an invalidated cache @@ -264,6 +265,30 @@ protected function updateCache($lock = TRUE) { $this->keysToRemove = array(); } + /** + * Normalizes a cache ID in order to comply with database limitations. + * + * @param string $cid + * The passed in cache ID. + * + * @return string + * An ASCII-encoded cache ID that is at most 255 characters long. + */ + protected function normalizeLockName($cid) { + // Nothing to do if the ID is a US ASCII string of 255 characters or less. + $cid_is_ascii = mb_check_encoding($cid, 'ASCII'); + if (strlen($cid) <= 255 && $cid_is_ascii) { + return $cid; + } + // Return a string that uses as much as possible of the original cache ID + // with the hash appended. + $hash = Crypt::hashBase64($cid); + if (!$cid_is_ascii) { + return $hash; + } + return substr($cid, 0, 255 - strlen($hash)) . $hash; + } + /** * {@inheritdoc} */ diff --git a/core/modules/system/src/Tests/Menu/MenuRouterTest.php b/core/modules/system/src/Tests/Menu/MenuRouterTest.php index 42332e06c7ac5810a9cd3aa759a0a29cfabe2ff6..7ea4e0f5c4e14e2e7f2582c9320d5b31e0a9dbe0 100644 --- a/core/modules/system/src/Tests/Menu/MenuRouterTest.php +++ b/core/modules/system/src/Tests/Menu/MenuRouterTest.php @@ -204,6 +204,7 @@ protected function doTestExoticPath() { "éøïвβä¸åœ‹æ›¸Ûž"; // Characters from various non-ASCII alphabets. $this->drupalGet($path); $this->assertRaw('This is the menuTestCallback content.'); + $this->assertNoText(t('The website encountered an unexpected error. Please try again later.')); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Cache/CacheCollectorTest.php b/core/tests/Drupal/KernelTests/Core/Cache/CacheCollectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e9ca67a0a6e7acfc94f9846607ef615fe831c626 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Cache/CacheCollectorTest.php @@ -0,0 +1,79 @@ +<?php +/** + * @file \Drupal\KernelTests\Core\Cache\CacheCollectorTest. + */ + +namespace Drupal\KernelTests\Core\Cache; + +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\Core\Cache\CacheCollectorHelper; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Tests DatabaseBackend cache tag implementation. + * + * @group Cache + */ +class CacheCollectorTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['system']; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->installSchema('system', ['semaphore']); + } + + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + parent::register($container); + // Change container to database cache backends. + $container + ->register('cache_factory', 'Drupal\Core\Cache\CacheFactory') + ->addArgument(new Reference('settings')) + ->addMethodCall('setContainer', [new Reference('service_container')]); + + // Change container to use database lock backends. + $container + ->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend') + ->addArgument(new Reference('database')); + } + + /** + * Tests setting and invalidating + * + * @dataProvider providerTestInvalidCharacters + */ + public function testCacheCollector($cid, $key, $value) { + $collector = new CacheCollectorHelper($cid, $this->container->get('cache.default'), $this->container->get('lock')); + $this->assertNull($collector->get($key)); + $collector->set($key, $value); + $this->assertEquals($value, $collector->get($key)); + $collector->destruct(); + // @todo Shouldn't this be empty after destruction? + $this->assertEquals($value, $collector->get($key)); + } + + /** + * Data provider for ::testCacheCollector(). + */ + public function providerTestInvalidCharacters() { + return [ + // Nothing special. + ['foo', 'bar', 'baz'], + // Invalid characters in CID. + ['éøïвβä¸åœ‹æ›¸Ûž', 'foo', 'bar'], + // Really long CID. + [$this->randomString(1024), 'foo', 'bar'], + ]; + } + +}