diff --git a/vendor/magento/framework/Escaper.php b/vendor/magento/framework/Escaper.php index 7e608b7719c3e..09bebe8f13611 100644 --- a/vendor/magento/framework/Escaper.php +++ b/vendor/magento/framework/Escaper.php @@ -369,8 +369,9 @@ public function escapeJsQuote($data, $quote = '\'') */ public function escapeXssInUrl($data) { - $data = $this->decodeHtmlEntitiesToFixedPoint((string)$data); + $data = (string)$data; $this->getTranslateInline()->processResponseBody($data); + $data = $this->decodeHtmlEntitiesToFixedPoint($data); return htmlspecialchars( $this->escapeScriptIdentifiers($data), diff --git a/vendor/magento/framework/Filter/Template.php b/vendor/magento/framework/Filter/Template.php index 39bf4c23e16e3..c2d8214ac3c22 100644 --- a/vendor/magento/framework/Filter/Template.php +++ b/vendor/magento/framework/Filter/Template.php @@ -21,6 +21,7 @@ use Magento\Framework\Stdlib\StringUtils; use Magento\Framework\Filter\Template\SignatureProvider; use Magento\Framework\Filter\Template\FilteringDepthMeter; +use Magento\Framework\Filter\Template\DeferredDirectiveRegistry; /** * Template filter @@ -105,6 +106,8 @@ class Template implements \Zend_Filter_Interface /** * @var SignatureProvider|null + * @deprecated Signature-based deferral was superseded by out-of-band deferral via DeferredDirectiveRegistry. + * @see DeferredDirectiveRegistry */ private $signatureProvider; @@ -113,6 +116,11 @@ class Template implements \Zend_Filter_Interface */ private $filteringDepthMeter; + /** + * @var DeferredDirectiveRegistry|null + */ + private $deferredDirectiveRegistry; + /** * @param StringUtils $string * @param array $variables @@ -120,6 +128,7 @@ class Template implements \Zend_Filter_Interface * @param VariableResolverInterface|null $variableResolver * @param SignatureProvider|null $signatureProvider * @param FilteringDepthMeter|null $filteringDepthMeter + * @param DeferredDirectiveRegistry|null $deferredDirectiveRegistry */ public function __construct( StringUtils $string, @@ -127,7 +136,8 @@ public function __construct( $directiveProcessors = [], VariableResolverInterface $variableResolver = null, SignatureProvider $signatureProvider = null, - FilteringDepthMeter $filteringDepthMeter = null + FilteringDepthMeter $filteringDepthMeter = null, + DeferredDirectiveRegistry $deferredDirectiveRegistry = null ) { $this->string = $string; $this->setVariables($variables); @@ -141,6 +151,9 @@ public function __construct( $this->filteringDepthMeter = $filteringDepthMeter ?? ObjectManager::getInstance() ->get(FilteringDepthMeter::class); + $this->deferredDirectiveRegistry = $deferredDirectiveRegistry ?? ObjectManager::getInstance() + ->get(DeferredDirectiveRegistry::class); + if (empty($directiveProcessors)) { $this->directiveProcessors = [ 'depend' => ObjectManager::getInstance()->get(DependDirective::class), @@ -206,54 +219,65 @@ public function filter($value) $this->filteringDepthMeter->descend(); // Processing of template directives. - $templateDirectivesResults = $this->processDirectives($value); + $value = $this->resolveDirectives($value); - foreach ($templateDirectivesResults as $result) { - $value = str_replace($result['directive'], $result['output'], $value); + // At the root template, resolve directives that child templates deferred to it. + if ($this->filteringDepthMeter->showMark() === 1) { + $value = $this->resolveDeferredDirectives($value); } - // Processing of deferred directives received from child templates - // or nested directives. - $deferredDirectivesResults = $this->processDirectives($value, true); + $value = $this->afterFilter($value); - foreach ($deferredDirectivesResults as $result) { - $value = str_replace($result['directive'], $result['output'], $value); - } + $this->filteringDepthMeter->ascend(); - 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 - ); - } + return $value; + } + + /** + * Defers a directive to the root template and returns the placeholder that represents it. + * + * @param string $directive + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function deferToParent(string $directive): string + { + return $this->deferredDirectiveRegistry->defer($directive); + } + + /** + * Resolves directives that child templates deferred to the root template. + * + * @param string $value + * @return string + * @throws \Exception + */ + private function resolveDeferredDirectives(string $value): string + { + foreach ($this->deferredDirectiveRegistry->getAll() as $placeholder => $directive) { + if (strpos($value, $placeholder) === false) { + continue; } - } - $value = $this->afterFilter($value); + $value = str_replace($placeholder, $this->resolveDirectives($directive), $value); + } - $this->filteringDepthMeter->ascend(); + $this->deferredDirectiveRegistry->clear(); return $value; } /** - * Processes template directives and returns an array that contains results produced by each directive. + * Processes directives and applies the results produced by each directive to the given value. * * @param string $value - * @param bool $isSigned * - * @return array + * @return string * * @throws InvalidArgumentException * @throws LocalizedException */ - private function processDirectives($value, $isSigned = false): array + private function resolveDirectives(string $value): string { $results = []; @@ -266,10 +290,6 @@ private function processDirectives($value, $isSigned = false): array $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); @@ -282,39 +302,42 @@ private function processDirectives($value, $isSigned = false): array } } - return $results; + return $this->applyDirectivesResults($value, array_unique($results, SORT_REGULAR)); } /** - * Modifies given regular expression pattern to be able to recognize signed directives. + * Applies results produced by directives. * - * @param string $pattern + * @param string $value + * @param array $results * * @return string - * - * @throws LocalizedException */ - private function embedSignatureIntoPattern(string $pattern): string + private function applyDirectivesResults(string $value, array $results): string { - $signature = $this->signatureProvider->get(); + $processedResults = []; + + foreach ($results as $result) { + foreach ($processedResults as $processedResult) { + $result['directive'] = str_replace( + $processedResult['directive'], + $processedResult['output'], + $result['directive'] + ); + } - $closingDelimiters = [ - '(' => ')', - '{' => '}', - '[' => ']', - '<' => '>' - ]; + $value = str_replace($result['directive'], $result['output'], $value); - $closingDelimiter = $openingDelimiter = substr(trim($pattern), 0, 1); + if (isset($result['callbacks'])) { + foreach ($result['callbacks'] as $callback) { + $this->addAfterFilterCallback($callback); + } + } - if (array_key_exists($openingDelimiter, $closingDelimiters)) { - $closingDelimiter = $closingDelimiters[$openingDelimiter]; + $processedResults[] = $result; } - $pattern = substr_replace($pattern, $signature, strpos($pattern, $openingDelimiter) + 1, 0); - $pattern = substr_replace($pattern, $signature, strrpos($pattern, $closingDelimiter), 0); - - return $pattern; + return $value; } /** diff --git a/vendor/magento/framework/Filter/Template/DeferredDirectiveRegistry.php b/vendor/magento/framework/Filter/Template/DeferredDirectiveRegistry.php new file mode 100644 index 0000000000000..c80b0e1848c51 --- /dev/null +++ b/vendor/magento/framework/Filter/Template/DeferredDirectiveRegistry.php @@ -0,0 +1,92 @@ + deferred directive text. + * + * @var string[] + */ + private array $directives = []; + + /** + * Per-request secret salt used to derive placeholder tokens deterministically. + * + * @var string|null + */ + private ?string $salt = null; + + /** + * @param Random $random + */ + public function __construct(Random $random) + { + $this->random = $random; + } + + /** + * Register a directive as deferred and return the placeholder token that represents it. + * + * @param string $directive + * @return string + * @throws LocalizedException + */ + public function defer(string $directive): string + { + if ($this->salt === null) { + $this->salt = $this->random->getRandomString(self::TOKEN_LENGTH); + } + + $placeholder = ':deferred:' . hash('sha256', $this->salt . $directive) . ':'; + + $this->directives[$placeholder] = $directive; + + return $placeholder; + } + + /** + * Get all registered deferrals keyed by their placeholder token. + * + * @return string[] + */ + public function getAll(): array + { + return $this->directives; + } + + /** + * Forget all registered deferrals. + * + * @return void + */ + public function clear(): void + { + $this->directives = []; + } +} diff --git a/vendor/magento/framework/Filter/Template/SignatureProvider.php b/vendor/magento/framework/Filter/Template/SignatureProvider.php index 3e476f3e5d79e..5a12ed78c5f1e 100644 --- a/vendor/magento/framework/Filter/Template/SignatureProvider.php +++ b/vendor/magento/framework/Filter/Template/SignatureProvider.php @@ -13,6 +13,10 @@ * 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}}). + * + * @deprecated Signature-based deferral was superseded by out-of-band deferral via + * \Magento\Framework\Filter\Template\DeferredDirectiveRegistry. + * @see \Magento\Framework\Filter\Template\DeferredDirectiveRegistry */ class SignatureProvider { diff --git a/vendor/magento/framework/Lock/Backend/FileLock.php b/vendor/magento/framework/Lock/Backend/FileLock.php index dd8eda9621667..08b949f56ad9f 100644 --- a/vendor/magento/framework/Lock/Backend/FileLock.php +++ b/vendor/magento/framework/Lock/Backend/FileLock.php @@ -161,7 +161,7 @@ public function unlock(string $name): bool */ private function getLockPath(string $name): string { - return $this->path . $name; + return $this->path . hash('sha256', $name); } /** diff --git a/vendor/magento/framework/Validator/EmailAddress.php b/vendor/magento/framework/Validator/EmailAddress.php index e1eb12be4f652..45a26cc600d16 100644 --- a/vendor/magento/framework/Validator/EmailAddress.php +++ b/vendor/magento/framework/Validator/EmailAddress.php @@ -9,6 +9,13 @@ class EmailAddress extends \Zend_Validate_EmailAddress implements \Magento\Framework\Validator\ValidatorInterface { + /** + * Quoted Pattern + * + * @var string + */ + private const QUOTED_LOCAL_PART_PATTERN = '/^"/'; + /** * Instantiates hostname validator for local use. * TLD validation is off by default. @@ -38,4 +45,18 @@ public function setValidateTld(bool $shouldValidate) { $this->getHostnameValidator()->setValidateTld($shouldValidate); } + + /** + * @inheritdoc + */ + public function isValid($value) + { + if (is_string($value) && preg_match(self::QUOTED_LOCAL_PART_PATTERN, $value)) { + $this->_setValue($value); + $this->_error(self::INVALID_FORMAT); + return false; + } + + return parent::isValid($value); + } } diff --git a/vendor/magento/framework/Validator/HTML/ConfigurableWYSIWYGValidator.php b/vendor/magento/framework/Validator/HTML/ConfigurableWYSIWYGValidator.php index a401899e1052c..e28d4cd774c33 100644 --- a/vendor/magento/framework/Validator/HTML/ConfigurableWYSIWYGValidator.php +++ b/vendor/magento/framework/Validator/HTML/ConfigurableWYSIWYGValidator.php @@ -24,12 +24,19 @@ class ConfigurableWYSIWYGValidator implements WYSIWYGValidatorInterface . '(\\\\x64\\\\x61\\\\x74\\\\x61(\\\\x3a|:|%3A)))/i'; /** - * Pattern to detect malformed syntax + * Pattern to detect malformed or unsupported markup syntax. * * @var string */ private static string $contentFiltrationPattern = - '#<\w+[^>]*/[^\s/>]+/[^>]*=|<\w+/[^\s>]*on\w+\s*=|<\w+[^>]*//[^>]*=|(' + . '|<\s*<[a-zA-Z]' + . '|\x00' + . '|<\w+[^>]*/[^\s/>]+/[^>]*=' + . '|<\w+/[^\s>]*on\w+\s*=' + . '|<\w+[^>]*//[^>]*=' + . '|(? 1; - $hasOtherMalicious = count($matches[0]) > 0 && !isset($valueCounts['loadHTML($content, LIBXML_HTML_NOIMPLIED); restore_error_handler(); diff --git a/vendor/magento/framework/View/Element/UiComponent/DataProvider/Sanitizer.php b/vendor/magento/framework/View/Element/UiComponent/DataProvider/Sanitizer.php index d49e83d1a99e0..be9a9f86a2c18 100644 --- a/vendor/magento/framework/View/Element/UiComponent/DataProvider/Sanitizer.php +++ b/vendor/magento/framework/View/Element/UiComponent/DataProvider/Sanitizer.php @@ -54,7 +54,7 @@ function ($datum, string $key) use (&$config, &$toProcess) : void { !is_bool($config) && !array_key_exists($key, $config) ) && (is_string($datum) || $datum instanceof Phrase) - && preg_match('/\$\{.+\}/s', (string)$datum) + && $this->validateSyntax((string)$datum) ) { //Templating is not disabled for all properties or for this property specifically //Property is a string that contains template syntax so we are disabling it's rendering @@ -74,6 +74,17 @@ function ($datum, string $key) use (&$config, &$toProcess) : void { return $data; } + /** + * Validate value syntax. + * + * @param string $value + * @return bool + */ + private function validateSyntax(string $value): bool + { + return strpos($value, '${') !== false && strpos($value, '${{') === false; + } + /** * Sanitize a component's metadata. *