diff --git a/Neos.Fusion/Classes/Core/Cache/ParserCache.php b/Neos.Fusion/Classes/Core/Cache/ParserCache.php
index 0e83c20def2..86e760bec2d 100644
--- a/Neos.Fusion/Classes/Core/Cache/ParserCache.php
+++ b/Neos.Fusion/Classes/Core/Cache/ParserCache.php
@@ -61,7 +61,12 @@ public function cacheForFusionFile(?string $contextPathAndFilename, \Closure $ge
if (str_contains($contextPathAndFilename, 'nodetypes://')) {
$contextPathAndFilename = $this->getAbsolutePathForNodeTypesUri($contextPathAndFilename);
}
- $identifier = $this->getCacheIdentifierForFile($contextPathAndFilename);
+ $fusionFileRealPath = realpath($contextPathAndFilename);
+ if ($fusionFileRealPath === false) {
+ // should not happen as the file would not been able to be read in the first place.
+ throw new \RuntimeException("Couldn't resolve realpath for: '$contextPathAndFilename'", 1705409467);
+ }
+ $identifier = $this->getCacheIdentifierForAbsoluteUnixStyleFilePathWithoutDirectoryTraversal($fusionFileRealPath);
return $this->cacheForIdentifier($identifier, $generateValueToCache);
}
@@ -76,11 +81,16 @@ public function cacheForDsl(string $identifier, string $code, \Closure $generate
private function cacheForIdentifier(string $identifier, \Closure $generateValueToCache): mixed
{
- if ($this->parsePartialsCache->has($identifier)) {
- return $this->parsePartialsCache->get($identifier);
+ $value = $this->parsePartialsCache->get($identifier);
+ if ($value !== false) {
+ return $value;
}
$value = $generateValueToCache();
- $this->parsePartialsCache->set($identifier, $value);
+ if ($value !== false) {
+ // in the rare edge-case of a fusion dsl returning `false` we cannot cache it,
+ // as the above get would be ignored. This is an acceptable compromise.
+ $this->parsePartialsCache->set($identifier, $value);
+ }
return $value;
}
diff --git a/Neos.Fusion/Classes/Core/Cache/ParserCacheFlusher.php b/Neos.Fusion/Classes/Core/Cache/ParserCacheFlusher.php
index 9d02094ebd0..04a4b0c13ac 100644
--- a/Neos.Fusion/Classes/Core/Cache/ParserCacheFlusher.php
+++ b/Neos.Fusion/Classes/Core/Cache/ParserCacheFlusher.php
@@ -50,7 +50,10 @@ public function flushPartialCacheOnFileChanges($fileMonitorIdentifier, array $ch
$identifiersToFlush = [];
foreach ($changedFiles as $changedFile => $status) {
- $identifiersToFlush[] = $this->getCacheIdentifierForFile($changedFile);
+ // flow already returns absolute file paths from the file monitor, so we don't have to call realpath.
+ // attempting to use realpath can even result in an error as the file might be removed and thus no realpath can be resolved via file system.
+ // https://github.com/neos/neos-development-collection/pull/4509
+ $identifiersToFlush[] = $this->getCacheIdentifierForAbsoluteUnixStyleFilePathWithoutDirectoryTraversal($changedFile);
}
if ($identifiersToFlush !== []) {
diff --git a/Neos.Fusion/Classes/Core/Cache/ParserCacheIdentifierTrait.php b/Neos.Fusion/Classes/Core/Cache/ParserCacheIdentifierTrait.php
index 86fcf9d6a0a..23fd8a76b36 100644
--- a/Neos.Fusion/Classes/Core/Cache/ParserCacheIdentifierTrait.php
+++ b/Neos.Fusion/Classes/Core/Cache/ParserCacheIdentifierTrait.php
@@ -19,7 +19,7 @@
trait ParserCacheIdentifierTrait
{
/**
- * creates a comparable hash of the dsl type and content to be used as cache identifier
+ * Creates a comparable hash of the dsl type and content to be used as cache identifier
*/
private function getCacheIdentifierForDslCode(string $identifier, string $code): string
{
@@ -27,18 +27,24 @@ private function getCacheIdentifierForDslCode(string $identifier, string $code):
}
/**
- * creates a comparable hash of the absolute, resolved $fusionFileName
+ * Creates a comparable hash of the absolute-unix-style-file-path-without-directory-traversal
*
- * @throws \InvalidArgumentException
+ * something like
+ * - /Users/marc/Code/neos-project/Packages/Neos
+ *
+ * its crucial that the path
+ * - is absolute
+ * - the path separator is in unix style: forward-slash /
+ * - doesn't contain directory traversal /../ or /./
+ *
+ * to be absolutely sure the path matches the criteria, {@see realpath} can be used.
+ *
+ * if the path does not match the criteria, a different hash as expected will be generated and caching will break.
*/
- private function getCacheIdentifierForFile(string $fusionFileName): string
- {
- $realPath = realpath($fusionFileName);
- if ($realPath === false) {
- throw new \InvalidArgumentException("Couldn't resolve realpath for: '$fusionFileName'");
- }
-
- $realFusionFilePathWithoutRoot = str_replace(FLOW_PATH_ROOT, '', $realPath);
- return 'file_' . md5($realFusionFilePathWithoutRoot);
+ private function getCacheIdentifierForAbsoluteUnixStyleFilePathWithoutDirectoryTraversal(
+ string $absoluteUnixStyleFilePathWithoutDirectoryTraversal
+ ): string {
+ $filePathWithoutRoot = str_replace(FLOW_PATH_ROOT, '', $absoluteUnixStyleFilePathWithoutDirectoryTraversal);
+ return 'file_' . md5($filePathWithoutRoot);
}
}
diff --git a/Neos.Fusion/Classes/Core/ObjectTreeParser/ExceptionMessage/MessageLinePart.php b/Neos.Fusion/Classes/Core/ObjectTreeParser/ExceptionMessage/MessageLinePart.php
index 26907f82676..10bbed5a31a 100644
--- a/Neos.Fusion/Classes/Core/ObjectTreeParser/ExceptionMessage/MessageLinePart.php
+++ b/Neos.Fusion/Classes/Core/ObjectTreeParser/ExceptionMessage/MessageLinePart.php
@@ -37,10 +37,11 @@ public function linePrint(int $offset = 0): string
public function char(int $index = 0): string
{
- if ($index < 0) {
- return mb_substr($this->linePart, $index, 1);
+ if ($index < 0 && mb_strlen($this->linePart) < abs($index)) {
+ // prevent mb_substr returning first char if out of bounds
+ return '';
}
- return mb_substr($this->linePart, $index, $index + 1);
+ return mb_substr($this->linePart, $index, 1);
}
public function charPrint(int $index = 0): string
diff --git a/Neos.Fusion/Classes/Core/ObjectTreeParser/Lexer.php b/Neos.Fusion/Classes/Core/ObjectTreeParser/Lexer.php
index 063922e2935..38bd9bea985 100644
--- a/Neos.Fusion/Classes/Core/ObjectTreeParser/Lexer.php
+++ b/Neos.Fusion/Classes/Core/ObjectTreeParser/Lexer.php
@@ -44,12 +44,14 @@ class Lexer
Token::MULTILINE_COMMENT => <<<'REGEX'
`^
/\* # start of a comment '/*'
- [^*]* # match everything until special case '*'
+ [^*]* # consume until special case '*'
+ \*+ # consume all '*'
(?:
- \*[^/] # if after the '*' there is a '/' break, else continue
- [^*]* # until the special case '*' is encountered - unrolled loop following Jeffrey Friedl
+ [^/] # break if its the end: '/'
+ [^*]* # unrolled loop following Jeffrey E.F. Friedl
+ \*+
)*
- \*/ # the end of a comment.
+ / # the end of a comment.
`x
REGEX,
diff --git a/Neos.Fusion/Tests/Unit/Core/Fixtures/ParserTestFusionComments01.fusion b/Neos.Fusion/Tests/Unit/Core/Fixtures/ParserTestFusionComments01.fusion
index 50831893170..d5067e395d9 100644
--- a/Neos.Fusion/Tests/Unit/Core/Fixtures/ParserTestFusionComments01.fusion
+++ b/Neos.Fusion/Tests/Unit/Core/Fixtures/ParserTestFusionComments01.fusion
@@ -57,7 +57,17 @@ comment with // ane more comment
Here comes some comment with # and /* and // in it
*/
+/**
+ * php doc style comment
+ */
+/***
+comment with multiple stars uneven
+***/
-// another edge-case mentioned in NEOS-864
+/**
+comment with multiple stars even
+**/
+
+// another edge-case mentioned in NEOS-864 (no new line at the end)
#include: Pages/**/*.fusion
\ No newline at end of file
diff --git a/Neos.Fusion/Tests/Unit/Core/Parser/ParserExceptionTest.php b/Neos.Fusion/Tests/Unit/Core/Parser/ParserExceptionTest.php
index ecd3494aea8..e1ad9f9376d 100644
--- a/Neos.Fusion/Tests/Unit/Core/Parser/ParserExceptionTest.php
+++ b/Neos.Fusion/Tests/Unit/Core/Parser/ParserExceptionTest.php
@@ -11,6 +11,7 @@
* source code.
*/
+use Neos\Fusion\Core\ObjectTreeParser\ExceptionMessage\MessageLinePart;
use Neos\Fusion\Core\Parser;
use Neos\Fusion\Core\Cache\ParserCache;
use Neos\Fusion\Core\ObjectTreeParser\Exception\ParserException;
@@ -195,6 +196,11 @@ public function unclosedStatements(): \Generator
'Unclosed comment.'
];
+ yield 'unclosed multiline comment with multiple stars' => [
+ '/***',
+ 'Unclosed comment.'
+ ];
+
yield 'unclosed eel expression' => [
'a = ${',
'Unclosed eel expression.'
@@ -286,4 +292,26 @@ public function itMatchesThePartialExceptionMessage($fusion, $expectedMessage):
self::assertSame($expectedMessage, $e->getHelperMessagePart());
}
}
+
+ /**
+ * @test
+ */
+ public function messageLinePartWorks()
+ {
+ $part = new MessageLinePart('abcd');
+
+ self::assertSame('', $part->char(-5));
+ self::assertSame('a', $part->char(-4));
+ self::assertSame('b', $part->char(-3));
+ self::assertSame('c', $part->char(-2));
+ self::assertSame('d', $part->char(-1));
+ self::assertSame('a', $part->char());
+ self::assertSame('a', $part->char(0));
+ self::assertSame('b', $part->char(1));
+ self::assertSame('c', $part->char(2));
+ self::assertSame('d', $part->char(3));
+ self::assertSame('', $part->char(4));
+ self::assertSame('abcd', $part->line());
+ self::assertSame('bcd', $part->line(1));
+ }
}
diff --git a/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html b/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html
index 06ec12fa5d7..fc7edbcaf03 100644
--- a/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html
+++ b/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html
@@ -57,14 +57,16 @@