annotate core/modules/media/src/Controller/OEmbedIframeController.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@17 1 <?php
Chris@17 2
Chris@17 3 namespace Drupal\media\Controller;
Chris@17 4
Chris@17 5 use Drupal\Component\Utility\Crypt;
Chris@17 6 use Drupal\Core\Cache\CacheableMetadata;
Chris@17 7 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
Chris@18 8 use Drupal\Core\Render\HtmlResponse;
Chris@17 9 use Drupal\Core\Render\RenderContext;
Chris@17 10 use Drupal\Core\Render\RendererInterface;
Chris@17 11 use Drupal\Core\Url;
Chris@17 12 use Drupal\media\IFrameMarkup;
Chris@17 13 use Drupal\media\IFrameUrlHelper;
Chris@17 14 use Drupal\media\OEmbed\ResourceException;
Chris@17 15 use Drupal\media\OEmbed\ResourceFetcherInterface;
Chris@17 16 use Drupal\media\OEmbed\UrlResolverInterface;
Chris@17 17 use Psr\Log\LoggerInterface;
Chris@17 18 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@17 19 use Symfony\Component\HttpFoundation\Request;
Chris@17 20 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
Chris@17 21
Chris@17 22 /**
Chris@17 23 * Controller which renders an oEmbed resource in a bare page (without blocks).
Chris@17 24 *
Chris@17 25 * This controller is meant to render untrusted third-party HTML returned by
Chris@17 26 * an oEmbed provider in an iframe, so as to mitigate the potential dangers of
Chris@17 27 * of displaying third-party markup (i.e., XSS). The HTML returned by this
Chris@17 28 * controller should not be trusted, and should *never* be displayed outside
Chris@17 29 * of an iframe.
Chris@17 30 *
Chris@17 31 * @internal
Chris@17 32 * This is an internal part of the oEmbed system and should only be used by
Chris@17 33 * oEmbed-related code in Drupal core.
Chris@17 34 */
Chris@17 35 class OEmbedIframeController implements ContainerInjectionInterface {
Chris@17 36
Chris@17 37 /**
Chris@17 38 * The oEmbed resource fetcher service.
Chris@17 39 *
Chris@17 40 * @var \Drupal\media\OEmbed\ResourceFetcherInterface
Chris@17 41 */
Chris@17 42 protected $resourceFetcher;
Chris@17 43
Chris@17 44 /**
Chris@17 45 * The oEmbed URL resolver service.
Chris@17 46 *
Chris@17 47 * @var \Drupal\media\OEmbed\UrlResolverInterface
Chris@17 48 */
Chris@17 49 protected $urlResolver;
Chris@17 50
Chris@17 51 /**
Chris@17 52 * The renderer service.
Chris@17 53 *
Chris@17 54 * @var \Drupal\Core\Render\RendererInterface
Chris@17 55 */
Chris@17 56 protected $renderer;
Chris@17 57
Chris@17 58 /**
Chris@17 59 * The logger channel.
Chris@17 60 *
Chris@17 61 * @var \Psr\Log\LoggerInterface
Chris@17 62 */
Chris@17 63 protected $logger;
Chris@17 64
Chris@17 65 /**
Chris@17 66 * The iFrame URL helper service.
Chris@17 67 *
Chris@17 68 * @var \Drupal\media\IFrameUrlHelper
Chris@17 69 */
Chris@17 70 protected $iFrameUrlHelper;
Chris@17 71
Chris@17 72 /**
Chris@17 73 * Constructs an OEmbedIframeController instance.
Chris@17 74 *
Chris@17 75 * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
Chris@17 76 * The oEmbed resource fetcher service.
Chris@17 77 * @param \Drupal\media\OEmbed\UrlResolverInterface $url_resolver
Chris@17 78 * The oEmbed URL resolver service.
Chris@17 79 * @param \Drupal\Core\Render\RendererInterface $renderer
Chris@17 80 * The renderer service.
Chris@17 81 * @param \Psr\Log\LoggerInterface $logger
Chris@17 82 * The logger channel.
Chris@17 83 * @param \Drupal\media\IFrameUrlHelper $iframe_url_helper
Chris@17 84 * The iFrame URL helper service.
Chris@17 85 */
Chris@17 86 public function __construct(ResourceFetcherInterface $resource_fetcher, UrlResolverInterface $url_resolver, RendererInterface $renderer, LoggerInterface $logger, IFrameUrlHelper $iframe_url_helper) {
Chris@17 87 $this->resourceFetcher = $resource_fetcher;
Chris@17 88 $this->urlResolver = $url_resolver;
Chris@17 89 $this->renderer = $renderer;
Chris@17 90 $this->logger = $logger;
Chris@17 91 $this->iFrameUrlHelper = $iframe_url_helper;
Chris@17 92 }
Chris@17 93
Chris@17 94 /**
Chris@17 95 * {@inheritdoc}
Chris@17 96 */
Chris@17 97 public static function create(ContainerInterface $container) {
Chris@17 98 return new static(
Chris@17 99 $container->get('media.oembed.resource_fetcher'),
Chris@17 100 $container->get('media.oembed.url_resolver'),
Chris@17 101 $container->get('renderer'),
Chris@17 102 $container->get('logger.factory')->get('media'),
Chris@17 103 $container->get('media.oembed.iframe_url_helper')
Chris@17 104 );
Chris@17 105 }
Chris@17 106
Chris@17 107 /**
Chris@17 108 * Renders an oEmbed resource.
Chris@17 109 *
Chris@17 110 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@17 111 * The request object.
Chris@17 112 *
Chris@17 113 * @return \Symfony\Component\HttpFoundation\Response
Chris@17 114 * The response object.
Chris@17 115 *
Chris@17 116 * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
Chris@17 117 * Will be thrown if the 'hash' parameter does not match the expected hash
Chris@17 118 * of the 'url' parameter.
Chris@17 119 */
Chris@17 120 public function render(Request $request) {
Chris@17 121 $url = $request->query->get('url');
Chris@17 122 $max_width = $request->query->getInt('max_width', NULL);
Chris@17 123 $max_height = $request->query->getInt('max_height', NULL);
Chris@17 124
Chris@17 125 // Hash the URL and max dimensions, and ensure it is equal to the hash
Chris@17 126 // parameter passed in the query string.
Chris@17 127 $hash = $this->iFrameUrlHelper->getHash($url, $max_width, $max_height);
Chris@17 128 if (!Crypt::hashEquals($hash, $request->query->get('hash', ''))) {
Chris@17 129 throw new AccessDeniedHttpException('This resource is not available');
Chris@17 130 }
Chris@17 131
Chris@17 132 // Return a response instead of a render array so that the frame content
Chris@17 133 // will not have all the blocks and page elements normally rendered by
Chris@17 134 // Drupal.
Chris@18 135 $response = new HtmlResponse();
Chris@17 136 $response->addCacheableDependency(Url::createFromRequest($request));
Chris@17 137
Chris@17 138 try {
Chris@17 139 $resource_url = $this->urlResolver->getResourceUrl($url, $max_width, $max_height);
Chris@17 140 $resource = $this->resourceFetcher->fetchResource($resource_url);
Chris@17 141
Chris@18 142 $placeholder_token = Crypt::randomBytesBase64(55);
Chris@18 143
Chris@17 144 // Render the content in a new render context so that the cacheability
Chris@17 145 // metadata of the rendered HTML will be captured correctly.
Chris@17 146 $element = [
Chris@17 147 '#theme' => 'media_oembed_iframe',
Chris@17 148 // Even though the resource HTML is untrusted, IFrameMarkup::create()
Chris@17 149 // will create a trusted string. The only reason this is okay is
Chris@17 150 // because we are serving it in an iframe, which will mitigate the
Chris@17 151 // potential dangers of displaying third-party markup.
Chris@17 152 '#media' => IFrameMarkup::create($resource->getHtml()),
Chris@17 153 '#cache' => [
Chris@17 154 // Add the 'rendered' cache tag as this response is not processed by
Chris@17 155 // \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse().
Chris@17 156 'tags' => ['rendered'],
Chris@17 157 ],
Chris@18 158 '#attached' => [
Chris@18 159 'html_response_attachment_placeholders' => [
Chris@18 160 'styles' => '<css-placeholder token="' . $placeholder_token . '">',
Chris@18 161 ],
Chris@18 162 'library' => [
Chris@18 163 'media/oembed.frame',
Chris@18 164 ],
Chris@18 165 ],
Chris@18 166 '#placeholder_token' => $placeholder_token,
Chris@17 167 ];
Chris@17 168 $content = $this->renderer->executeInRenderContext(new RenderContext(), function () use ($resource, $element) {
Chris@17 169 return $this->renderer->render($element);
Chris@17 170 });
Chris@17 171 $response
Chris@17 172 ->setContent($content)
Chris@18 173 ->setAttachments($element['#attached'])
Chris@17 174 ->addCacheableDependency($resource)
Chris@17 175 ->addCacheableDependency(CacheableMetadata::createFromRenderArray($element));
Chris@17 176 }
Chris@17 177 catch (ResourceException $e) {
Chris@17 178 // Prevent the response from being cached.
Chris@17 179 $response->setMaxAge(0);
Chris@17 180
Chris@17 181 // The oEmbed system makes heavy use of exception wrapping, so log the
Chris@17 182 // entire exception chain to help with troubleshooting.
Chris@17 183 do {
Chris@17 184 // @todo Log additional information from ResourceException, to help with
Chris@17 185 // debugging, in https://www.drupal.org/project/drupal/issues/2972846.
Chris@17 186 $this->logger->error($e->getMessage());
Chris@17 187 $e = $e->getPrevious();
Chris@17 188 } while ($e);
Chris@17 189 }
Chris@17 190
Chris@17 191 return $response;
Chris@17 192 }
Chris@17 193
Chris@17 194 }