Skip to content

Commit

Permalink
[TASK] Refactor and and guard namespace extraction (#451)
Browse files Browse the repository at this point in the history
Refactors the namespace validation from occurring in
the TemplateProcessor, to occur instead when parsing
the template. Behavior remains unchanged.

Also guards several potentially empty matches of
namespaces from being iterated over, causing fatal
PHP errors.
  • Loading branch information
NamelessCoder authored and mbrodala committed Jul 23, 2019
1 parent 865890c commit 2d2d5db
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 62 deletions.
10 changes: 8 additions & 2 deletions src/Core/Parser/TemplateParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,12 @@ protected function openingViewHelperTagHandler(ParsingState $state, $namespaceId
protected function initializeViewHelperAndAddItToStack(ParsingState $state, $namespaceIdentifier, $methodIdentifier, $argumentsObjectTree)
{
$viewHelperResolver = $this->renderingContext->getViewHelperResolver();
if (!$viewHelperResolver->isNamespaceValid($namespaceIdentifier)) {
if ($viewHelperResolver->isNamespaceIgnored($namespaceIdentifier)) {
return null;
}
if (!$viewHelperResolver->isNamespaceValid($namespaceIdentifier)) {
throw new UnknownNamespaceException('Unknown Namespace: ' . $namespaceIdentifier);
}
try {
$currentViewHelperNode = new ViewHelperNode(
$this->renderingContext,
Expand Down Expand Up @@ -398,9 +401,12 @@ protected function initializeViewHelperAndAddItToStack(ParsingState $state, $nam
protected function closingViewHelperTagHandler(ParsingState $state, $namespaceIdentifier, $methodIdentifier)
{
$viewHelperResolver = $this->renderingContext->getViewHelperResolver();
if (!$viewHelperResolver->isNamespaceValid($namespaceIdentifier)) {
if ($viewHelperResolver->isNamespaceIgnored($namespaceIdentifier)) {
return false;
}
if (!$viewHelperResolver->isNamespaceValid($namespaceIdentifier)) {
throw new UnknownNamespaceException('Unknown Namespace: ' . $namespaceIdentifier);
}
$lastStackElement = $state->popNodeFromStack();
if (!($lastStackElement instanceof ViewHelperNode)) {
throw new Exception('You closed a templating tag which you never opened!', 1224485838);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public function preProcessSource($templateSource)
{
$templateSource = $this->replaceCdataSectionsByEmptyLines($templateSource);
$templateSource = $this->registerNamespacesFromTemplateSource($templateSource);
$this->throwExceptionsForUnhandledNamespaces($templateSource);
return $templateSource;
}

Expand Down Expand Up @@ -125,70 +124,35 @@ public function registerNamespacesFromTemplateSource($templateSource)
substr($templateSource, strrpos($templateSource, $closingTag) + strlen($closingTag));
}
} else {
if (!empty($namespaces)) {
$namespaceAttributesToRemove = [];
foreach ($namespaces as $namespace) {
if (!$viewHelperResolver->isNamespaceIgnored($namespace[1])) {
$namespaceAttributesToRemove[] = preg_quote($namespace[1], '/') . '="' . preg_quote($namespace[2], '/') . '"';
}
}
if (count($namespaceAttributesToRemove)) {
$matchWithRemovedNamespaceAttributes = preg_replace('/(?:\\s*+xmlns:(?:' . implode('|', $namespaceAttributesToRemove) . ')\\s*+)++/', ' ', $matches[0]);
$templateSource = str_replace($matches[0], $matchWithRemovedNamespaceAttributes, $templateSource);
$namespaceAttributesToRemove = [];
foreach ($namespaces as $namespace) {
if (!$viewHelperResolver->isNamespaceIgnored($namespace[1])) {
$namespaceAttributesToRemove[] = preg_quote($namespace[1], '/') . '="' . preg_quote($namespace[2], '/') . '"';
}
}
if (count($namespaceAttributesToRemove)) {
$matchWithRemovedNamespaceAttributes = preg_replace('/(?:\\s*+xmlns:(?:' . implode('|', $namespaceAttributesToRemove) . ')\\s*+)++/', ' ', $matches[0]);
$templateSource = str_replace($matches[0], $matchWithRemovedNamespaceAttributes, $templateSource);
}
}
}

preg_match_all(static::NAMESPACE_DECLARATION, $templateSource, $namespaces);
foreach ($namespaces['identifier'] as $key => $identifier) {
$namespace = $namespaces['phpNamespace'][$key];
if (strlen($namespace) === 0) {
$namespace = null;
}
$viewHelperResolver->addNamespace($identifier, $namespace);
}
foreach ($namespaces[0] as $removal) {
$templateSource = str_replace($removal, '', $templateSource);
}

return $templateSource;
}

/**
* Throw an UnknownNamespaceException for any unknown and not ignored
* namespace inside the template string
*
* @param string $templateSource
* @return void
*/
public function throwExceptionsForUnhandledNamespaces($templateSource)
{
$viewHelperResolver = $this->renderingContext->getViewHelperResolver();
$splitTemplate = preg_split(Patterns::$SPLIT_PATTERN_TEMPLATE_DYNAMICTAGS, $templateSource, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach ($splitTemplate as $templateElement) {
if (preg_match(Patterns::$SCAN_PATTERN_TEMPLATE_VIEWHELPERTAG, $templateElement, $matchedVariables) > 0) {
if (!$viewHelperResolver->isNamespaceValidOrIgnored($matchedVariables['NamespaceIdentifier'])) {
throw new UnknownNamespaceException('Unknown Namespace: ' . htmlspecialchars($matchedVariables[0]));
if (!empty($namespaces['identifier'])) {
// There are no namespace declarations using curly-brace syntax.
foreach ($namespaces['identifier'] as $key => $identifier) {
$namespace = $namespaces['phpNamespace'][$key];
if (strlen($namespace) === 0) {
$namespace = null;
}
continue;
} elseif (preg_match(Patterns::$SCAN_PATTERN_TEMPLATE_CLOSINGVIEWHELPERTAG, $templateElement, $matchedVariables) > 0) {
continue;
$viewHelperResolver->addNamespace($identifier, $namespace);
}

$sections = preg_split(Patterns::$SPLIT_PATTERN_SHORTHANDSYNTAX, $templateElement, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach ($sections as $section) {
if (preg_match(Patterns::$SCAN_PATTERN_SHORTHANDSYNTAX_OBJECTACCESSORS, $section, $matchedVariables) > 0) {
preg_match_all(Patterns::$SPLIT_PATTERN_SHORTHANDSYNTAX_VIEWHELPER, $section, $shorthandViewHelpers, PREG_SET_ORDER);
if (is_array($shorthandViewHelpers) === true) {
foreach ($shorthandViewHelpers as $shorthandViewHelper) {
if (!$viewHelperResolver->isNamespaceValidOrIgnored($shorthandViewHelper['NamespaceIdentifier'])) {
throw new UnknownNamespaceException('Unknown Namespace: ' . $shorthandViewHelper['NamespaceIdentifier']);
}
}
}
}
foreach ($namespaces[0] as $removal) {
$templateSource = str_replace($removal, '', $templateSource);
}

}

return $templateSource;
}
}
47 changes: 43 additions & 4 deletions tests/Unit/Core/Parser/TemplateParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
use TYPO3Fluid\Fluid\Core\Parser\TemplateParser;
use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessorInterface;
use TYPO3Fluid\Fluid\Core\Parser\UnknownNamespaceException;
use TYPO3Fluid\Fluid\Core\Variables\StandardVariableProvider;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperResolver;
Expand All @@ -39,9 +40,28 @@ class TemplateParserTest extends UnitTestCase
/**
* @test
*/
public function testInitializeViewHelperAndAddItToStackReturnsFalseIfNamespaceNotValid()
public function testInitializeViewHelperAndAddItToStackReturnsFalseIfNamespaceIgnored()
{
$resolver = $this->getMock(ViewHelperResolver::class, ['isNamespaceValid']);
$resolver = $this->getMock(ViewHelperResolver::class, ['isNamespaceIgnored']);
$resolver->expects($this->once())->method('isNamespaceIgnored')->willReturn(true);
$context = new RenderingContextFixture();
$context->setViewHelperResolver($resolver);
$templateParser = new TemplateParser();
$templateParser->setRenderingContext($context);
$method = new \ReflectionMethod($templateParser, 'initializeViewHelperAndAddItToStack');
$method->setAccessible(true);
$result = $method->invokeArgs($templateParser, [new ParsingState(), 'f', 'render', []]);
$this->assertNull($result);
}

/**
* @test
*/
public function testInitializeViewHelperAndAddItToStackThrowsExceptionIfNamespaceInvalid()
{
$this->setExpectedException(UnknownNamespaceException::class);
$resolver = $this->getMock(ViewHelperResolver::class, ['isNamespaceIgnored', 'isNamespaceValid']);
$resolver->expects($this->once())->method('isNamespaceIgnored')->willReturn(false);
$resolver->expects($this->once())->method('isNamespaceValid')->willReturn(false);
$context = new RenderingContextFixture();
$context->setViewHelperResolver($resolver);
Expand All @@ -56,9 +76,28 @@ public function testInitializeViewHelperAndAddItToStackReturnsFalseIfNamespaceNo
/**
* @test
*/
public function testClosingViewHelperTagHandlerReturnsFalseIfNamespaceNotValid()
public function testClosingViewHelperTagHandlerReturnsFalseIfNamespaceIgnored()
{
$resolver = $this->getMock(ViewHelperResolver::class, ['isNamespaceIgnored']);
$resolver->expects($this->once())->method('isNamespaceIgnored')->willReturn(true);
$context = new RenderingContextFixture();
$context->setViewHelperResolver($resolver);
$templateParser = new TemplateParser();
$templateParser->setRenderingContext($context);
$method = new \ReflectionMethod($templateParser, 'closingViewHelperTagHandler');
$method->setAccessible(true);
$result = $method->invokeArgs($templateParser, [new ParsingState(), 'f', 'render']);
$this->assertFalse($result);
}

/**
* @test
*/
public function testClosingViewHelperTagHandlerThrowsExceptionIfNamespaceInvalid()
{
$resolver = $this->getMock(ViewHelperResolver::class, ['isNamespaceValid']);
$this->setExpectedException(UnknownNamespaceException::class);
$resolver = $this->getMock(ViewHelperResolver::class, ['isNamespaceValid', 'isNamespaceIgnored']);
$resolver->expects($this->once())->method('isNamespaceIgnored')->willReturn(false);
$resolver->expects($this->once())->method('isNamespaceValid')->willReturn(false);
$context = new RenderingContextFixture();
$context->setViewHelperResolver($resolver);
Expand Down

0 comments on commit 2d2d5db

Please sign in to comment.