Skip to content

Instantly share code, notes, and snippets.

@jissereitsma
Last active October 16, 2022 12:18
Embed
What would you like to do?
Patch Magento for APSB22-48 (2.4.3, 2.3.7-p3, 2.3.6-p1, 2.2.11 and possibly others)
diff --git a/Filter/Template.php b/Filter/Template.php
index 8a3b350f3ab..39bf4c23e16 100644
--- a/Filter/Template.php
+++ b/Filter/Template.php
@@ -9,7 +9,9 @@
*/
namespace Magento\Framework\Filter;
+use InvalidArgumentException;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filter\DirectiveProcessor\DependDirective;
use Magento\Framework\Filter\DirectiveProcessor\ForDirective;
use Magento\Framework\Filter\DirectiveProcessor\IfDirective;
@@ -17,6 +19,8 @@ use Magento\Framework\Filter\DirectiveProcessor\LegacyDirective;
use Magento\Framework\Filter\DirectiveProcessor\TemplateDirective;
use Magento\Framework\Filter\DirectiveProcessor\VarDirective;
use Magento\Framework\Stdlib\StringUtils;
+use Magento\Framework\Filter\Template\SignatureProvider;
+use Magento\Framework\Filter\Template\FilteringDepthMeter;
/**
* Template filter
@@ -99,17 +103,31 @@ class Template implements \Zend_Filter_Interface
*/
private $variableResolver;
+ /**
+ * @var SignatureProvider|null
+ */
+ private $signatureProvider;
+
+ /**
+ * @var FilteringDepthMeter|null
+ */
+ private $filteringDepthMeter;
+
/**
* @param StringUtils $string
* @param array $variables
* @param DirectiveProcessorInterface[] $directiveProcessors
* @param VariableResolverInterface|null $variableResolver
+ * @param SignatureProvider|null $signatureProvider
+ * @param FilteringDepthMeter|null $filteringDepthMeter
*/
public function __construct(
StringUtils $string,
$variables = [],
$directiveProcessors = [],
- VariableResolverInterface $variableResolver = null
+ VariableResolverInterface $variableResolver = null,
+ SignatureProvider $signatureProvider = null,
+ FilteringDepthMeter $filteringDepthMeter = null
) {
$this->string = $string;
$this->setVariables($variables);
@@ -117,6 +135,12 @@ class Template implements \Zend_Filter_Interface
$this->variableResolver = $variableResolver ?? ObjectManager::getInstance()
->get(VariableResolverInterface::class);
+ $this->signatureProvider = $signatureProvider ?? ObjectManager::getInstance()
+ ->get(SignatureProvider::class);
+
+ $this->filteringDepthMeter = $filteringDepthMeter ?? ObjectManager::getInstance()
+ ->get(FilteringDepthMeter::class);
+
if (empty($directiveProcessors)) {
$this->directiveProcessors = [
'depend' => ObjectManager::getInstance()->get(DependDirective::class),
@@ -172,25 +196,125 @@ class Template implements \Zend_Filter_Interface
*/
public function filter($value)
{
+ if (!is_string($value)) {
+ throw new InvalidArgumentException(__(
+ 'Argument \'value\' must be type of string, %1 given.',
+ gettype($value)
+ )->render());
+ }
+
+ $this->filteringDepthMeter->descend();
+
+ // Processing of template directives.
+ $templateDirectivesResults = $this->processDirectives($value);
+
+ foreach ($templateDirectivesResults as $result) {
+ $value = str_replace($result['directive'], $result['output'], $value);
+ }
+
+ // Processing of deferred directives received from child templates
+ // or nested directives.
+ $deferredDirectivesResults = $this->processDirectives($value, true);
+
+ foreach ($deferredDirectivesResults as $result) {
+ $value = str_replace($result['directive'], $result['output'], $value);
+ }
+
+ if ($this->filteringDepthMeter->showMark() > 1) {
+ // Signing own deferred directives (if any).
+ $signature = $this->signatureProvider->get();
+
+ foreach ($templateDirectivesResults as $result) {
+ if ($result['directive'] === $result['output']) {
+ $value = str_replace(
+ $result['output'],
+ $signature . $result['output'] . $signature,
+ $value
+ );
+ }
+ }
+ }
+
+ $value = $this->afterFilter($value);
+
+ $this->filteringDepthMeter->ascend();
+
+ return $value;
+ }
+
+ /**
+ * Processes template directives and returns an array that contains results produced by each directive.
+ *
+ * @param string $value
+ * @param bool $isSigned
+ *
+ * @return array
+ *
+ * @throws InvalidArgumentException
+ * @throws LocalizedException
+ */
+ private function processDirectives($value, $isSigned = false): array
+ {
+ $results = [];
+
foreach ($this->directiveProcessors as $directiveProcessor) {
if (!$directiveProcessor instanceof DirectiveProcessorInterface) {
- throw new \InvalidArgumentException(
+ throw new InvalidArgumentException(
'Directive processors must implement ' . DirectiveProcessorInterface::class
);
}
- if (preg_match_all($directiveProcessor->getRegularExpression(), $value, $constructions, PREG_SET_ORDER)) {
+ $pattern = $directiveProcessor->getRegularExpression();
+
+ if ($isSigned) {
+ $pattern = $this->embedSignatureIntoPattern($pattern);
+ }
+
+ if (preg_match_all($pattern, $value, $constructions, PREG_SET_ORDER)) {
foreach ($constructions as $construction) {
$replacedValue = $directiveProcessor->process($construction, $this, $this->templateVars);
- $value = str_replace($construction[0], $replacedValue, $value);
+ $results[] = [
+ 'directive' => $construction[0],
+ 'output' => $replacedValue
+ ];
}
}
}
- $value = $this->afterFilter($value);
+ return $results;
+ }
- return $value;
+ /**
+ * Modifies given regular expression pattern to be able to recognize signed directives.
+ *
+ * @param string $pattern
+ *
+ * @return string
+ *
+ * @throws LocalizedException
+ */
+ private function embedSignatureIntoPattern(string $pattern): string
+ {
+ $signature = $this->signatureProvider->get();
+
+ $closingDelimiters = [
+ '(' => ')',
+ '{' => '}',
+ '[' => ']',
+ '<' => '>'
+ ];
+
+ $closingDelimiter = $openingDelimiter = substr(trim($pattern), 0, 1);
+
+ if (array_key_exists($openingDelimiter, $closingDelimiters)) {
+ $closingDelimiter = $closingDelimiters[$openingDelimiter];
+ }
+
+ $pattern = substr_replace($pattern, $signature, strpos($pattern, $openingDelimiter) + 1, 0);
+ $pattern = substr_replace($pattern, $signature, strrpos($pattern, $closingDelimiter), 0);
+
+ return $pattern;
}
/**
diff --git a/Filter/Template/FilteringDepthMeter.php b/Filter/Template/FilteringDepthMeter.php
new file mode 100644
index 0000000000000..57257325be797
--- /dev/null
+++ b/Filter/Template/FilteringDepthMeter.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Framework\Filter\Template;
+
+/**
+ * Meter of template filtering depth.
+ *
+ * Records and provides template/directive filtering depth (filtering recursion).
+ * Filtering depth 1 means that template or directive is root and has no parents.
+ */
+class FilteringDepthMeter
+{
+ /**
+ * @var int
+ */
+ private $depth = 0;
+
+ /**
+ * Increases filtering depth.
+ *
+ * @return void
+ */
+ public function descend()
+ {
+ $this->depth++;
+ }
+
+ /**
+ * Decreases filtering depth.
+ *
+ * @return void
+ */
+ public function ascend()
+ {
+ $this->depth--;
+ }
+
+ /**
+ * Shows current filtering depth.
+ *
+ * @return int
+ */
+ public function showMark(): int
+ {
+ return $this->depth;
+ }
+}
diff --git a/Filter/Template/SignatureProvider.php b/Filter/Template/SignatureProvider.php
new file mode 100644
index 0000000000000..3e476f3e5d79e
--- /dev/null
+++ b/Filter/Template/SignatureProvider.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Framework\Filter\Template;
+
+/**
+ * Provider of a signature.
+ *
+ * Provides a signature which should be used to sign deferred directives
+ * (directives that should be processed in scope of a parent template
+ * instead of own scope, e.g. {{inlinecss}}).
+ */
+class SignatureProvider
+{
+ /**
+ * @var string|null
+ */
+ private $signature;
+
+ /**
+ * @var \Magento\Framework\Math\Random
+ */
+ private $random;
+
+ /**
+ * @param \Magento\Framework\Math\Random $random
+ */
+ public function __construct(
+ \Magento\Framework\Math\Random $random
+ ) {
+ $this->random = $random;
+ }
+
+ /**
+ * Generates a random string which will be used as a signature during runtime.
+ *
+ * @return string
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function get(): string
+ {
+ if ($this->signature === null) {
+ $this->signature = $this->random->getRandomString(32);
+ }
+
+ return $this->signature;
+ }
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment