OEmbedIframeController.php

Same filename and directory in other branches
  1. 9 core/modules/media/src/Controller/OEmbedIframeController.php
  2. 10 core/modules/media/src/Controller/OEmbedIframeController.php
  3. 11.x core/modules/media/src/Controller/OEmbedIframeController.php

Namespace

Drupal\media\Controller

File

core/modules/media/src/Controller/OEmbedIframeController.php

View source
<?php

namespace Drupal\media\Controller;

use Drupal\Component\Utility\Crypt;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Render\HtmlResponse;
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 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.
 */
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 (!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();
        $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',
                // 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,
            ];
            $content = $this->renderer
                ->executeInRenderContext(new RenderContext(), function () use ($resource, $element) {
                return $this->renderer
                    ->render($element);
            });
            $response->setContent($content)
                ->setAttachments($element['#attached'])
                ->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;
    }

}

Classes

Title Deprecated Summary
OEmbedIframeController Controller which renders an oEmbed resource in a bare page (without blocks).

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.