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