Mercurial > hg > cmmr2012-drupal-site
diff core/modules/media/src/Controller/OEmbedIframeController.php @ 4:a9cd425dd02b
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:11:55 +0000 |
parents | |
children | 12f9dff5fda9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/media/src/Controller/OEmbedIframeController.php Thu Feb 28 13:11:55 2019 +0000 @@ -0,0 +1,182 @@ +<?php + +namespace Drupal\media\Controller; + +use Drupal\Component\Utility\Crypt; +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\CacheableResponse; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Render\RenderContext; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Url; +use Drupal\media\IFrameMarkup; +use Drupal\media\IFrameUrlHelper; +use Drupal\media\OEmbed\ResourceException; +use Drupal\media\OEmbed\ResourceFetcherInterface; +use Drupal\media\OEmbed\UrlResolverInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; + +/** + * 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 oEmbed system and should only be used by + * oEmbed-related code in Drupal core. + */ +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 the 'hash' parameter does not match the expected hash + * of the 'url' parameter. + */ + public function render(Request $request) { + $url = $request->query->get('url'); + $max_width = $request->query->getInt('max_width', NULL); + $max_height = $request->query->getInt('max_height', NULL); + + // 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 (!Crypt::hashEquals($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 CacheableResponse(); + $response->addCacheableDependency(Url::createFromRequest($request)); + + try { + $resource_url = $this->urlResolver->getResourceUrl($url, $max_width, $max_height); + $resource = $this->resourceFetcher->fetchResource($resource_url); + + // 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', + // 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'], + ], + ]; + $content = $this->renderer->executeInRenderContext(new RenderContext(), function () use ($resource, $element) { + return $this->renderer->render($element); + }); + $response + ->setContent($content) + ->addCacheableDependency($resource) + ->addCacheableDependency(CacheableMetadata::createFromRenderArray($element)); + } + 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; + } + +}