comparison 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
comparison
equal deleted inserted replaced
3:307d7a7fd348 4:a9cd425dd02b
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 }