diff -u b/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php --- b/core/modules/rest/src/Plugin/views/display/RestExport.php +++ b/core/modules/rest/src/Plugin/views/display/RestExport.php @@ -284,13 +284,20 @@ $header = []; $header['Content-type'] = $this->getMimeType(); - // This is "safe" markup in the sense that other than in View preview, this - // isn't actually "markup". Since there's no markup, there's no markup in - // which to perform an XSS injection. In the case of Views preview, all that - // is added is a
tag, which again cannot contain an XSS vector. - // @todo Decide how to support non-HTML in the render API in - // https://www.drupal.org/node/2501313. - $output['#markup'] = SafeMarkup::set($output['#markup']); + if ($this->view->getRequest()->getFormat($header['Content-type']) !== 'html') { + // This display plugin is primarily for returning non-HTML formats. + // However, we still invoke the renderer to collect cacheability metadata. + // Because the renderer is designed for HTML rendering, it filters + // #markup for XSS unless it is already known to be safe, but that filter + // only works for HTML. Therefore, we mark the contents as safe to bypass + // the filter. So long as we are returning this in a non-HTML response + // (checked above), this is safe, because an XSS attack only works when + // executed by an HTML agent. + // @todo Decide how to support non-HTML in the render API in + // https://www.drupal.org/node/2501313. + $output['#markup'] = SafeMarkup::set($output['#markup']); + } + $response = new CacheableResponse($this->renderer->renderRoot($output), 200); $cache_metadata = CacheableMetadata::createFromRenderArray($output);