class OEmbedIframeController in Drupal 10
Same name and namespace in other branches
- 8 core/modules/media/src/Controller/OEmbedIframeController.php \Drupal\media\Controller\OEmbedIframeController
- 9 core/modules/media/src/Controller/OEmbedIframeController.php \Drupal\media\Controller\OEmbedIframeController
Controller which renders an oEmbed resource in a bare page (without blocks).
This controller is meant to render untrusted third-party HTML returned by an oEmbed provider in an iframe, so as to mitigate the potential dangers of of displaying third-party markup (i.e., XSS). The HTML returned by this controller should not be trusted, and should *never* be displayed outside of an iframe.
@internal This is an internal part of the media system in Drupal core and may be subject to change in minor releases. This class should not be instantiated or extended by external code.
Hierarchy
- class \Drupal\media\Controller\OEmbedIframeController implements ContainerInjectionInterface
Expanded class hierarchy of OEmbedIframeController
1 file declares its use of OEmbedIframeController
- OEmbedIframeControllerTest.php in core/
modules/ media/ tests/ src/ Kernel/ OEmbedIframeControllerTest.php
File
- core/
modules/ media/ src/ Controller/ OEmbedIframeController.php, line 37
Namespace
Drupal\media\ControllerView source
class OEmbedIframeController implements ContainerInjectionInterface {
/**
* The oEmbed resource fetcher service.
*
* @var \Drupal\media\OEmbed\ResourceFetcherInterface
*/
protected $resourceFetcher;
/**
* The oEmbed URL resolver service.
*
* @var \Drupal\media\OEmbed\UrlResolverInterface
*/
protected $urlResolver;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The logger channel.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The iFrame URL helper service.
*
* @var \Drupal\media\IFrameUrlHelper
*/
protected $iFrameUrlHelper;
/**
* Constructs an OEmbedIframeController instance.
*
* @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
* The oEmbed resource fetcher service.
* @param \Drupal\media\OEmbed\UrlResolverInterface $url_resolver
* The oEmbed URL resolver service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Psr\Log\LoggerInterface $logger
* The logger channel.
* @param \Drupal\media\IFrameUrlHelper $iframe_url_helper
* The iFrame URL helper service.
*/
public function __construct(ResourceFetcherInterface $resource_fetcher, UrlResolverInterface $url_resolver, RendererInterface $renderer, LoggerInterface $logger, IFrameUrlHelper $iframe_url_helper) {
$this->resourceFetcher = $resource_fetcher;
$this->urlResolver = $url_resolver;
$this->renderer = $renderer;
$this->logger = $logger;
$this->iFrameUrlHelper = $iframe_url_helper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('media.oembed.resource_fetcher'), $container
->get('media.oembed.url_resolver'), $container
->get('renderer'), $container
->get('logger.factory')
->get('media'), $container
->get('media.oembed.iframe_url_helper'));
}
/**
* Renders an oEmbed resource.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response object.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Will be thrown if either
* - the 'hash' parameter does not match the expected hash of the 'url'
* parameter;
* - the iframe_domain is set in media.settings and does not match the host
* in the request.
*/
public function render(Request $request) {
// @todo Move domain check logic to a separate method.
$allowed_domain = \Drupal::config('media.settings')
->get('iframe_domain');
if ($allowed_domain) {
$allowed_host = parse_url($allowed_domain, PHP_URL_HOST);
$host = parse_url($request
->getSchemeAndHttpHost(), PHP_URL_HOST);
if ($allowed_host !== $host) {
throw new AccessDeniedHttpException('This resource is not available');
}
}
$url = $request->query
->get('url');
$max_width = $request->query
->getInt('max_width');
$max_height = $request->query
->getInt('max_height');
// Hash the URL and max dimensions, and ensure it is equal to the hash
// parameter passed in the query string.
$hash = $this->iFrameUrlHelper
->getHash($url, $max_width, $max_height);
if (!hash_equals($hash, $request->query
->get('hash', ''))) {
throw new AccessDeniedHttpException('This resource is not available');
}
// Return a response instead of a render array so that the frame content
// will not have all the blocks and page elements normally rendered by
// Drupal.
$response = new HtmlResponse('', HtmlResponse::HTTP_OK, [
'Content-Type' => 'text/html; charset=UTF-8',
]);
$response
->addCacheableDependency(Url::createFromRequest($request));
try {
$resource_url = $this->urlResolver
->getResourceUrl($url, $max_width, $max_height);
$resource = $this->resourceFetcher
->fetchResource($resource_url);
$placeholder_token = Crypt::randomBytesBase64(55);
// Render the content in a new render context so that the cacheability
// metadata of the rendered HTML will be captured correctly.
$element = [
'#theme' => 'media_oembed_iframe',
'#resource' => $resource,
// Even though the resource HTML is untrusted, IFrameMarkup::create()
// will create a trusted string. The only reason this is okay is
// because we are serving it in an iframe, which will mitigate the
// potential dangers of displaying third-party markup.
'#media' => IFrameMarkup::create($resource
->getHtml()),
'#cache' => [
// Add the 'rendered' cache tag as this response is not processed by
// \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse().
'tags' => [
'rendered',
],
],
'#attached' => [
'html_response_attachment_placeholders' => [
'styles' => '<css-placeholder token="' . $placeholder_token . '">',
],
'library' => [
'media/oembed.frame',
],
],
'#placeholder_token' => $placeholder_token,
];
$context = new RenderContext();
$content = $this->renderer
->executeInRenderContext($context, function () use ($element) {
return $this->renderer
->render($element);
});
$response
->setContent($content)
->setAttachments($element['#attached'])
->addCacheableDependency($resource)
->addCacheableDependency(CacheableMetadata::createFromRenderArray($element));
// Modules and themes implementing hook_media_oembed_iframe_preprocess()
// can add additional #cache and #attachments to a render array. If this
// occurs, the render context won't be empty, and we need to ensure the
// added metadata is bubbled up to the response.
// @see \Drupal\Core\Theme\ThemeManager::render()
if (!$context
->isEmpty()) {
$bubbleable_metadata = $context
->pop();
assert($bubbleable_metadata instanceof BubbleableMetadata);
$response
->addCacheableDependency($bubbleable_metadata);
$response
->addAttachments($bubbleable_metadata
->getAttachments());
}
} catch (ResourceException $e) {
// Prevent the response from being cached.
$response
->setMaxAge(0);
// The oEmbed system makes heavy use of exception wrapping, so log the
// entire exception chain to help with troubleshooting.
do {
// @todo Log additional information from ResourceException, to help with
// debugging, in https://www.drupal.org/project/drupal/issues/2972846.
$this->logger
->error($e
->getMessage());
$e = $e
->getPrevious();
} while ($e);
}
return $response;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
OEmbedIframeController:: |
protected | property | The iFrame URL helper service. | |
OEmbedIframeController:: |
protected | property | The logger channel. | |
OEmbedIframeController:: |
protected | property | The renderer service. | |
OEmbedIframeController:: |
protected | property | The oEmbed resource fetcher service. | |
OEmbedIframeController:: |
protected | property | The oEmbed URL resolver service. | |
OEmbedIframeController:: |
public static | function |
Instantiates a new instance of this class. Overrides ContainerInjectionInterface:: |
|
OEmbedIframeController:: |
public | function | Renders an oEmbed resource. | |
OEmbedIframeController:: |
public | function | Constructs an OEmbedIframeController instance. |