Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UHF-3883: Make sure image styles are run through PHP #28

Merged
merged 3 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion helfi_proxy.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
tags:
# Must be run before PathProcessorFront (200 weight).
- { name: path_processor_inbound, priority: 201 }
# Must to be run after LanguageProcessorLanguage (100 weight).
# Must be run after LanguageProcessorLanguage (100 weight).
- { name: path_processor_outbound, priority: 99 }

cache_context.site_prefix:
Expand Down
132 changes: 97 additions & 35 deletions src/ProxyManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,6 @@ public function __construct(ConfigFactoryInterface $configFactory) {
$this->config = $configFactory->get('helfi_proxy.settings');
}

/**
* Converts the given URL to relative.
*
* @param string|null $value
* The value.
*
* @return string|null
* the value.
*/
private function convertAbsoluteToRelative(?string $value) : ? string {
if (!$value) {
return $value;
}
$parts = parse_url($value);

// Value is already relative.
if (empty($parts['host'])) {
return $value;
}

if (isset($parts['path'])) {
return $parts['path'] . (isset($parts['query']) ? '?' . $parts['query'] : NULL);
}
return $value;
}

/**
* Checks if given URL is hosted from a CDN.
*
Expand Down Expand Up @@ -95,6 +69,24 @@ private function isCdnAddress(?string $value) : bool {
return FALSE;
}

/**
* Checks whether the asset is hosted locally.
*
* @param string $value
* The path to given asset.
*
* @return bool
* TRUE if asset is local.
*/
private function isLocalAsset(string $value) : bool {
return match(TRUE) {
str_starts_with($value, '/sites'),
str_starts_with($value, '/core'),
str_starts_with($value, '/themes') => TRUE,
default => FALSE,
};
}

/**
* Handles tags with 'alwaysAbsolute' option.
*
Expand All @@ -107,12 +99,24 @@ private function isCdnAddress(?string $value) : bool {
* The value.
*/
private function handleAlwaysAbsolute(Request $request, string $value) : ? string {
$value = $this->convertAbsoluteToRelative($value);
if (!$value) {
return $value;
}
$parts = parse_url($value);

// Skip non-relative values.
if (!str_starts_with($value, '/')) {
// Value is already relative.
if (empty($parts['host']) || empty($parts['path'])) {
return $value;
}

// Skip non-local assets.
if (!$this->isLocalAsset($parts['path'])) {
return $value;
}

if (isset($parts['path'])) {
$value = $parts['path'] . (isset($parts['query']) ? '?' . $parts['query'] : NULL);
}
return sprintf('%s%s', $request->getSchemeAndHttpHost(), $this->addAssetPath($value));
}

Expand All @@ -138,14 +142,16 @@ private function addAssetPath(string $value) : ? string {
* The value.
* @param string $separator
* The separator.
* @param callable $callback
* The callback to run value through.
*
* @return string|null
* The value.
*/
private function handleMultiValue(string $value, string $separator) : ? string {
private function handleMultiValue(string $value, string $separator, callable $callback) : ? string {
$parts = [];
foreach (explode($separator, $value) as $item) {
$parts[] = $this->addAssetPath(trim($item));
$parts[] = $callback(trim($item));
}
return implode($separator, $parts);
}
Expand Down Expand Up @@ -176,6 +182,53 @@ private function handleSitePrefix(Request $request, string $value) : ? string {
return sprintf('%s/%s', $prefix, ltrim($value, '/'));
}

/**
* Checks if the given value is an image style.
*
* @param string $value
* The value.
*
* @return bool
* TRUE if image is image style.
*/
private function isImageStyle(string $value) : bool {
return str_contains($value, '/files/styles/');
}

/**
* Special handling for image styles.
*
* @param \Drupal\helfi_proxy\Selector\Selector $tag
* The tag.
* @param string $value
* The value with domain added to it.
*
* @return string|null
* The image style url.
*/
private function handleImageStyle(Selector $tag, string $value) : ? string {
if ($tag->multipleValues) {
return $this->handleMultiValue($value, $tag->multivalueSeparator,
function (string $value): string {
return $this->addDomain($value);
});
}
return $this->addDomain($value);
}

/**
* Adds domain to relative URL.
*
* @param string $value
* The value.
*
* @return string
* The value with domain added to it.
*/
private function addDomain(string $value) : string {
return sprintf('//%s/%s', $this->getHostname(), ltrim($value, '/'));
}

/**
* {@inheritdoc}
*/
Expand All @@ -184,9 +237,14 @@ public function getAttributeValue(Request $request, Selector $tag, ?string $valu
if (!$value || $this->isCdnAddress($value)) {
return $value;
}
// Certain elements are absolute URLs already (such as og:image:url)
// so we need to convert them to relative first and then back to
// absolute.

// Image styles need special handling because they need to be run through
// PHP before they are uploaded to CDN.
if ($this->isImageStyle($value)) {
return $this->handleImageStyle($tag, $value);
}
// Certain elements might be absolute URLs already (such as og:image:url).
// Make sure locally hosted files are always served from correct domain.
if ($tag->alwaysAbsolute) {
return $this->handleAlwaysAbsolute($request, $value);
}
Expand All @@ -202,7 +260,11 @@ public function getAttributeValue(Request $request, Selector $tag, ?string $valu
}

if ($tag->multipleValues) {
return $this->handleMultiValue($value, $tag->multivalueSeparator);
return $this->handleMultiValue($value, $tag->multivalueSeparator,
function (string $value) : string {
return $this->addAssetPath($value);
}
);
}

return $this->addAssetPath($value);
Expand Down
1 change: 1 addition & 0 deletions src/ProxyTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function getHostname() : string {
}

$variables = [
'DRUPAL_BACKEND_DOMAIN',
'DRUPAL_ROUTES',
'DRUPAL_REVERSE_PROXY_ADDRESS',
];
Expand Down
2 changes: 1 addition & 1 deletion src/Selector/Selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Selector {
* @param string $xpath
* The tag.
* @param string $attribute
* The atribute.
* The attribute.
* @param bool $alwaysAbsolute
* Whether the given value needs to be converted to relative url.
* @param bool $sitePrefix
Expand Down
6 changes: 3 additions & 3 deletions tests/src/Kernel/ProxyManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ public function testMetaTags() : void {
$this->setAssetPath('test-assets');

foreach (['og:image', 'og:image:url'] as $tag) {
$this->assertEquals('http://' . $this->getHostname() . '/test-assets/path/to/og-image.png', $this->proxyManager()->getAttributeValue($request, Selectors::get($tag), 'https://www.hel.fi/path/to/og-image.png'));
$this->assertEquals('http://' . $this->getHostname() . '/test-assets/themes/hdbt/og-image.png', $this->proxyManager()->getAttributeValue($request, Selectors::get($tag), 'https://www.hel.fi/themes/hdbt/og-image.png'));
}
$this->assertEquals('http://' . $this->getHostname() . '/test-assets/path/to/og-image.png', $this->proxyManager()->getAttributeValue($request, Selectors::get('twitter:image'), 'https://www.hel.fi/path/to/og-image.png'));
$this->assertEquals('http://' . $this->getHostname() . '/test-assets/themes/hdbt/og-image.png', $this->proxyManager()->getAttributeValue($request, Selectors::get('twitter:image'), 'https://www.hel.fi/themes/hdbt/og-image.png'));
}

/**
Expand Down Expand Up @@ -217,7 +217,7 @@ public function testSourceSrcSet() : void {

foreach ($values as $value) {
$this->assertEquals(
'/test-assets' . $value,
'//' . $this->getHostname() . $value,
$this->proxyManager()->getAttributeValue($request, Selectors::get('source'), $value)
);
}
Expand Down