From 742ef2b23bf9906ceb57aede15f7e9016bf63ddd Mon Sep 17 00:00:00 2001 From: Benjamin Dalsass Date: Tue, 26 Jul 2022 10:30:37 +0200 Subject: [PATCH] Update pelago/emogrifier bundle (^6.0.0) >> files omitted --- lib/firebase/php-jwt/src/CachedKeySet.php | 229 ++++ lib/pelago/emogrifier/phpunit.xml | 23 + .../src/Caching/SimpleStringCache.php | 87 ++ lib/pelago/emogrifier/src/Css/CssDocument.php | 187 +++ lib/pelago/emogrifier/src/Css/StyleRule.php | 83 ++ lib/pelago/emogrifier/src/CssInliner.php | 1196 +++++++++++++++++ .../HtmlProcessor/AbstractHtmlProcessor.php | 472 +++++++ .../HtmlProcessor/CssToAttributeConverter.php | 303 +++++ .../src/HtmlProcessor/HtmlNormalizer.php | 16 + .../src/HtmlProcessor/HtmlPruner.php | 137 ++ .../src/Utilities/ArrayIntersector.php | 57 + .../src/Utilities/CssConcatenator.php | 181 +++ lib/sabberworm/php-css-parser/CHANGELOG.md | 241 ++++ lib/sabberworm/php-css-parser/LICENSE | 21 + lib/sabberworm/php-css-parser/README.md | 632 +++++++++ lib/sabberworm/php-css-parser/composer.json | 69 + .../src/CSSList/AtRuleBlockList.php | 83 ++ .../src/CSSList/CSSBlockList.php | 143 ++ .../php-css-parser/src/CSSList/CSSList.php | 479 +++++++ .../php-css-parser/src/CSSList/Document.php | 172 +++ .../php-css-parser/src/CSSList/KeyFrame.php | 104 ++ .../php-css-parser/src/Comment/Comment.php | 71 + .../src/Comment/Commentable.php | 25 + .../php-css-parser/src/OutputFormat.php | 334 +++++ .../php-css-parser/src/OutputFormatter.php | 231 ++++ lib/sabberworm/php-css-parser/src/Parser.php | 60 + .../src/Parsing/OutputException.php | 18 + .../src/Parsing/ParserState.php | 516 +++++++ .../src/Parsing/SourceException.php | 32 + .../src/Parsing/UnexpectedEOFException.php | 12 + .../src/Parsing/UnexpectedTokenException.php | 51 + .../php-css-parser/src/Property/AtRule.php | 34 + .../src/Property/CSSNamespace.php | 154 +++ .../php-css-parser/src/Property/Charset.php | 129 ++ .../php-css-parser/src/Property/Import.php | 137 ++ .../src/Property/KeyframeSelector.php | 23 + .../php-css-parser/src/Property/Selector.php | 138 ++ .../php-css-parser/src/Renderable.php | 21 + .../php-css-parser/src/Rule/Rule.php | 392 ++++++ .../php-css-parser/src/RuleSet/AtRuleSet.php | 73 + .../src/RuleSet/DeclarationBlock.php | 831 ++++++++++++ .../php-css-parser/src/RuleSet/RuleSet.php | 326 +++++ .../php-css-parser/src/Settings.php | 89 ++ .../php-css-parser/src/Value/CSSFunction.php | 73 + .../php-css-parser/src/Value/CSSString.php | 105 ++ .../php-css-parser/src/Value/CalcFunction.php | 89 ++ .../src/Value/CalcRuleValueList.php | 24 + .../php-css-parser/src/Value/Color.php | 166 +++ .../php-css-parser/src/Value/LineName.php | 65 + .../src/Value/PrimitiveValue.php | 14 + .../src/Value/RuleValueList.php | 15 + .../php-css-parser/src/Value/Size.php | 209 +++ .../php-css-parser/src/Value/URL.php | 82 ++ .../php-css-parser/src/Value/Value.php | 198 +++ .../php-css-parser/src/Value/ValueList.php | 100 ++ 55 files changed, 9752 insertions(+) create mode 100644 lib/firebase/php-jwt/src/CachedKeySet.php create mode 100644 lib/pelago/emogrifier/phpunit.xml create mode 100644 lib/pelago/emogrifier/src/Caching/SimpleStringCache.php create mode 100644 lib/pelago/emogrifier/src/Css/CssDocument.php create mode 100644 lib/pelago/emogrifier/src/Css/StyleRule.php create mode 100644 lib/pelago/emogrifier/src/CssInliner.php create mode 100644 lib/pelago/emogrifier/src/HtmlProcessor/AbstractHtmlProcessor.php create mode 100644 lib/pelago/emogrifier/src/HtmlProcessor/CssToAttributeConverter.php create mode 100644 lib/pelago/emogrifier/src/HtmlProcessor/HtmlNormalizer.php create mode 100644 lib/pelago/emogrifier/src/HtmlProcessor/HtmlPruner.php create mode 100644 lib/pelago/emogrifier/src/Utilities/ArrayIntersector.php create mode 100644 lib/pelago/emogrifier/src/Utilities/CssConcatenator.php create mode 100644 lib/sabberworm/php-css-parser/CHANGELOG.md create mode 100644 lib/sabberworm/php-css-parser/LICENSE create mode 100644 lib/sabberworm/php-css-parser/README.md create mode 100644 lib/sabberworm/php-css-parser/composer.json create mode 100644 lib/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php create mode 100644 lib/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php create mode 100644 lib/sabberworm/php-css-parser/src/CSSList/CSSList.php create mode 100644 lib/sabberworm/php-css-parser/src/CSSList/Document.php create mode 100644 lib/sabberworm/php-css-parser/src/CSSList/KeyFrame.php create mode 100644 lib/sabberworm/php-css-parser/src/Comment/Comment.php create mode 100644 lib/sabberworm/php-css-parser/src/Comment/Commentable.php create mode 100644 lib/sabberworm/php-css-parser/src/OutputFormat.php create mode 100644 lib/sabberworm/php-css-parser/src/OutputFormatter.php create mode 100644 lib/sabberworm/php-css-parser/src/Parser.php create mode 100644 lib/sabberworm/php-css-parser/src/Parsing/OutputException.php create mode 100644 lib/sabberworm/php-css-parser/src/Parsing/ParserState.php create mode 100644 lib/sabberworm/php-css-parser/src/Parsing/SourceException.php create mode 100644 lib/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php create mode 100644 lib/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php create mode 100644 lib/sabberworm/php-css-parser/src/Property/AtRule.php create mode 100644 lib/sabberworm/php-css-parser/src/Property/CSSNamespace.php create mode 100644 lib/sabberworm/php-css-parser/src/Property/Charset.php create mode 100644 lib/sabberworm/php-css-parser/src/Property/Import.php create mode 100644 lib/sabberworm/php-css-parser/src/Property/KeyframeSelector.php create mode 100644 lib/sabberworm/php-css-parser/src/Property/Selector.php create mode 100644 lib/sabberworm/php-css-parser/src/Renderable.php create mode 100644 lib/sabberworm/php-css-parser/src/Rule/Rule.php create mode 100644 lib/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php create mode 100644 lib/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php create mode 100644 lib/sabberworm/php-css-parser/src/RuleSet/RuleSet.php create mode 100644 lib/sabberworm/php-css-parser/src/Settings.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/CSSFunction.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/CSSString.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/CalcFunction.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/Color.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/LineName.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/PrimitiveValue.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/RuleValueList.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/Size.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/URL.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/Value.php create mode 100644 lib/sabberworm/php-css-parser/src/Value/ValueList.php diff --git a/lib/firebase/php-jwt/src/CachedKeySet.php b/lib/firebase/php-jwt/src/CachedKeySet.php new file mode 100644 index 000000000..e2215b309 --- /dev/null +++ b/lib/firebase/php-jwt/src/CachedKeySet.php @@ -0,0 +1,229 @@ + + */ +class CachedKeySet implements ArrayAccess +{ + /** + * @var string + */ + private $jwksUri; + /** + * @var ClientInterface + */ + private $httpClient; + /** + * @var RequestFactoryInterface + */ + private $httpFactory; + /** + * @var CacheItemPoolInterface + */ + private $cache; + /** + * @var ?int + */ + private $expiresAfter; + /** + * @var ?CacheItemInterface + */ + private $cacheItem; + /** + * @var array + */ + private $keySet; + /** + * @var string + */ + private $cacheKey; + /** + * @var string + */ + private $cacheKeyPrefix = 'jwks'; + /** + * @var int + */ + private $maxKeyLength = 64; + /** + * @var bool + */ + private $rateLimit; + /** + * @var string + */ + private $rateLimitCacheKey; + /** + * @var int + */ + private $maxCallsPerMinute = 10; + /** + * @var string|null + */ + private $defaultAlg; + + public function __construct( + string $jwksUri, + ClientInterface $httpClient, + RequestFactoryInterface $httpFactory, + CacheItemPoolInterface $cache, + int $expiresAfter = null, + bool $rateLimit = false, + string $defaultAlg = null + ) { + $this->jwksUri = $jwksUri; + $this->httpClient = $httpClient; + $this->httpFactory = $httpFactory; + $this->cache = $cache; + $this->expiresAfter = $expiresAfter; + $this->rateLimit = $rateLimit; + $this->defaultAlg = $defaultAlg; + $this->setCacheKeys(); + } + + /** + * @param string $keyId + * @return Key + */ + public function offsetGet($keyId): Key + { + if (!$this->keyIdExists($keyId)) { + throw new OutOfBoundsException('Key ID not found'); + } + return $this->keySet[$keyId]; + } + + /** + * @param string $keyId + * @return bool + */ + public function offsetExists($keyId): bool + { + return $this->keyIdExists($keyId); + } + + /** + * @param string $offset + * @param Key $value + */ + public function offsetSet($offset, $value): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset): void + { + throw new LogicException('Method not implemented'); + } + + private function keyIdExists(string $keyId): bool + { + if (null === $this->keySet) { + $item = $this->getCacheItem(); + // Try to load keys from cache + if ($item->isHit()) { + // item found! Return it + $jwks = $item->get(); + $this->keySet = JWK::parseKeySet(json_decode($jwks, true), $this->defaultAlg); + } + } + + if (!isset($this->keySet[$keyId])) { + if ($this->rateLimitExceeded()) { + return false; + } + $request = $this->httpFactory->createRequest('get', $this->jwksUri); + $jwksResponse = $this->httpClient->sendRequest($request); + $jwks = (string) $jwksResponse->getBody(); + $this->keySet = JWK::parseKeySet(json_decode($jwks, true), $this->defaultAlg); + + if (!isset($this->keySet[$keyId])) { + return false; + } + + $item = $this->getCacheItem(); + $item->set($jwks); + if ($this->expiresAfter) { + $item->expiresAfter($this->expiresAfter); + } + $this->cache->save($item); + } + + return true; + } + + private function rateLimitExceeded(): bool + { + if (!$this->rateLimit) { + return false; + } + + $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); + if (!$cacheItem->isHit()) { + $cacheItem->expiresAfter(1); // # of calls are cached each minute + } + + $callsPerMinute = (int) $cacheItem->get(); + if (++$callsPerMinute > $this->maxCallsPerMinute) { + return true; + } + $cacheItem->set($callsPerMinute); + $this->cache->save($cacheItem); + return false; + } + + private function getCacheItem(): CacheItemInterface + { + if (\is_null($this->cacheItem)) { + $this->cacheItem = $this->cache->getItem($this->cacheKey); + } + + return $this->cacheItem; + } + + private function setCacheKeys(): void + { + if (empty($this->jwksUri)) { + throw new RuntimeException('JWKS URI is empty'); + } + + // ensure we do not have illegal characters + $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri); + + // add prefix + $key = $this->cacheKeyPrefix . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($key) > $this->maxKeyLength) { + $key = substr(hash('sha256', $key), 0, $this->maxKeyLength); + } + + $this->cacheKey = $key; + + if ($this->rateLimit) { + // add prefix + $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($rateLimitKey) > $this->maxKeyLength) { + $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength); + } + + $this->rateLimitCacheKey = $rateLimitKey; + } + } +} diff --git a/lib/pelago/emogrifier/phpunit.xml b/lib/pelago/emogrifier/phpunit.xml new file mode 100644 index 000000000..1f869bac1 --- /dev/null +++ b/lib/pelago/emogrifier/phpunit.xml @@ -0,0 +1,23 @@ + + + + + tests + + + + + + src + + + diff --git a/lib/pelago/emogrifier/src/Caching/SimpleStringCache.php b/lib/pelago/emogrifier/src/Caching/SimpleStringCache.php new file mode 100644 index 000000000..aa7c462fa --- /dev/null +++ b/lib/pelago/emogrifier/src/Caching/SimpleStringCache.php @@ -0,0 +1,87 @@ +set($key, $value); + * … + * if ($cache->has($key) { + * $cachedValue = $cache->get($value); + * } + * ``` + * + * @internal + */ +class SimpleStringCache +{ + /** + * @var array + */ + private $values = []; + + /** + * Checks whether there is an entry stored for the given key. + * + * @param string $key the key to check; must not be empty + * + * @throws \InvalidArgumentException + */ + public function has(string $key): bool + { + $this->assertNotEmptyKey($key); + + return isset($this->values[$key]); + } + + /** + * Returns the entry stored for the given key, and throws an exception if the value does not exist + * (which helps keep the return type simple). + * + * @param string $key the key to of the item to retrieve; must not be empty + * + * @return string the retrieved value; may be empty + * + * @throws \BadMethodCallException + */ + public function get(string $key): string + { + if (!$this->has($key)) { + throw new \BadMethodCallException('You can only call `get` with a key for an existing value.', 1625996246); + } + + return $this->values[$key]; + } + + /** + * Sets or overwrites an entry. + * + * @param string $key the key to of the item to set; must not be empty + * @param string $value the value to set; can be empty + * + * @throws \BadMethodCallException + */ + public function set(string $key, string $value): void + { + $this->assertNotEmptyKey($key); + + $this->values[$key] = $value; + } + + /** + * @throws \InvalidArgumentException + */ + private function assertNotEmptyKey(string $key): void + { + if ($key === '') { + throw new \InvalidArgumentException('Please provide a non-empty key.', 1625995840); + } + } +} diff --git a/lib/pelago/emogrifier/src/Css/CssDocument.php b/lib/pelago/emogrifier/src/Css/CssDocument.php new file mode 100644 index 000000000..65f1b376a --- /dev/null +++ b/lib/pelago/emogrifier/src/Css/CssDocument.php @@ -0,0 +1,187 @@ +parse(); + $this->sabberwormCssDocument = $sabberwormCssDocument; + } + + /** + * Collates the media query, selectors and declarations for individual rules from the parsed CSS, in order. + * + * @param array $allowedMediaTypes + * + * @return array + */ + public function getStyleRulesData(array $allowedMediaTypes): array + { + $ruleMatches = []; + /** @var CssRenderable $rule */ + foreach ($this->sabberwormCssDocument->getContents() as $rule) { + if ($rule instanceof CssAtRuleBlockList) { + $containingAtRule = $this->getFilteredAtIdentifierAndRule($rule, $allowedMediaTypes); + if (\is_string($containingAtRule)) { + /** @var CssRenderable $nestedRule */ + foreach ($rule->getContents() as $nestedRule) { + if ($nestedRule instanceof CssDeclarationBlock) { + $ruleMatches[] = new StyleRule($nestedRule, $containingAtRule); + } + } + } + } elseif ($rule instanceof CssDeclarationBlock) { + $ruleMatches[] = new StyleRule($rule); + } + } + + return $ruleMatches; + } + + /** + * Renders at-rules from the parsed CSS that are valid and not conditional group rules (i.e. not rules such as + * `@media` which contain style rules whose data is returned by {@see getStyleRulesData}). Also does not render + * `@charset` rules; these are discarded (only UTF-8 is supported). + * + * @return string + */ + public function renderNonConditionalAtRules(): string + { + $this->isImportRuleAllowed = true; + /** @var array $cssContents */ + $cssContents = $this->sabberwormCssDocument->getContents(); + $atRules = \array_filter($cssContents, [$this, 'isValidAtRuleToRender']); + + if ($atRules === []) { + return ''; + } + + $atRulesDocument = new SabberwormCssDocument(); + $atRulesDocument->setContents($atRules); + + /** @var string $renderedRules */ + $renderedRules = $atRulesDocument->render(); + return $renderedRules; + } + + /** + * @param CssAtRuleBlockList $rule + * @param array $allowedMediaTypes + * + * @return ?string + * If the nested at-rule is supported, it's opening declaration (e.g. "@media (max-width: 768px)") is + * returned; otherwise the return value is null. + */ + private function getFilteredAtIdentifierAndRule(CssAtRuleBlockList $rule, array $allowedMediaTypes): ?string + { + $result = null; + + if ($rule->atRuleName() === 'media') { + /** @var string $mediaQueryList */ + $mediaQueryList = $rule->atRuleArgs(); + [$mediaType] = \explode('(', $mediaQueryList, 2); + if (\trim($mediaType) !== '') { + $escapedAllowedMediaTypes = \array_map( + static function (string $allowedMediaType): string { + return \preg_quote($allowedMediaType, '/'); + }, + $allowedMediaTypes + ); + $mediaTypesMatcher = \implode('|', $escapedAllowedMediaTypes); + $isAllowed = \preg_match('/^\\s*+(?:only\\s++)?+(?:' . $mediaTypesMatcher . ')/i', $mediaType) > 0; + } else { + $isAllowed = true; + } + + if ($isAllowed) { + $result = '@media ' . $mediaQueryList; + } + } + + return $result; + } + + /** + * Tests if a CSS rule is an at-rule that should be passed though and copied to a `