diff --git a/core/core.services.yml b/core/core.services.yml index 24fa98eaaaaa..b9aa505b0f36 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -765,6 +765,7 @@ services: arguments: ['@element_info'] tags: - { name: render.main_content_renderer, format: drupal_ajax } + - { name: render.main_content_renderer, format: iframeupload } main_content_renderer.dialog: class: Drupal\Core\Render\MainContent\DialogRenderer arguments: ['@title_resolver'] diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php index 54f8d72b4e26..1bc3d7e0a97a 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php @@ -115,6 +115,42 @@ class AjaxResponse extends JsonResponse { if ($this->data == '{}') { $this->setData($this->ajaxRender($request)); } + + // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so + // for that browser, jquery.form submits requests containing a file upload + // via an IFRAME rather than via XHR. Since the response is being sent to + // an IFRAME, it must be formatted as HTML. Specifically: + // - It must use the text/html content type or else the browser will + // present a download prompt. Note: This applies to both file uploads + // as well as any ajax request in a form with a file upload form. + // - It must place the JSON data into a textarea to prevent browser + // extensions such as Linkification and Skype's Browser Highlighter + // from applying HTML transformations such as URL or phone number to + // link conversions on the data values. + // + // Since this affects the format of the output, it could be argued that + // this should be implemented as a separate Accept MIME type. However, + // that would require separate variants for each type of AJAX request + // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency, + // this browser workaround is implemented via a GET or POST parameter. + // + // @see http://malsup.com/jquery/form/#file-upload + // @see https://drupal.org/node/1009382 + // @see https://drupal.org/node/2339491 + // @see Drupal.ajax.prototype.beforeSend() + $accept = $request->headers->get('accept'); + + if (strpos($accept, 'text/html') !== FALSE) { + $this->headers->set('Content-Type', 'text/html; charset=utf-8'); + + // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification + // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into + // links. This corrupts the JSON response. Protect the integrity of the + // JSON data by making it the value of a textarea. + // @see http://malsup.com/jquery/form/#file-upload + // @see http://drupal.org/node/1009382 + $this->setContent(''); + } } /** diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php index 0b0c27485652..0383a3779b3e 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -91,20 +91,6 @@ class ViewSubscriber implements EventSubscriberInterface { return $response; } - public function onIframeUpload(GetResponseForControllerResultEvent $event) { - $response = $event->getResponse(); - - // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification - // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into - // links. This corrupts the JSON response. Protect the integrity of the - // JSON data by making it the value of a textarea. - // @see http://malsup.com/jquery/form/#file-upload - // @see http://drupal.org/node/1009382 - $html = ''; - - return new Response($html); - } - /** * Registers the methods in this class that should be listeners. * diff --git a/core/lib/Drupal/Core/Render/MainContent/MainContentRenderersPass.php b/core/lib/Drupal/Core/Render/MainContent/MainContentRenderersPass.php index 08b4665a9e9b..b8fd7547ba7c 100644 --- a/core/lib/Drupal/Core/Render/MainContent/MainContentRenderersPass.php +++ b/core/lib/Drupal/Core/Render/MainContent/MainContentRenderersPass.php @@ -23,9 +23,11 @@ class MainContentRenderersPass implements CompilerPassInterface { */ public function process(ContainerBuilder $container) { $main_content_renderers = []; - foreach ($container->findTaggedServiceIds('render.main_content_renderer') as $id => $attributes) { - $format = $attributes[0]['format']; - $main_content_renderers[$format] = $id; + foreach ($container->findTaggedServiceIds('render.main_content_renderer') as $id => $attributes_list) { + foreach ($attributes_list as $attributes) { + $format = $attributes['format']; + $main_content_renderers[$format] = $id; + } } $container->setParameter('main_content_renderers', $main_content_renderers); } diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php index 31522202df08..d5f2fc1f7cb8 100644 --- a/core/modules/rest/src/Tests/ReadTest.php +++ b/core/modules/rest/src/Tests/ReadTest.php @@ -92,8 +92,7 @@ class ReadTest extends RESTTestBase { // and hence when there is no matching REST route, the non-REST route is // used, but it can't render into application/hal+json, so it returns a 406. $this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.'); - $expected_message = '{"message":"Not Acceptable.","supported_mime_types":["text\\/html","application\\/vnd.drupal-ajax","application\\/vnd.drupal-dialog","application\\/vnd.drupal-modal"]}'; - $this->assertIdentical($expected_message, $response); + $this->assertTrue(strpos($response, '{"message":"Not Acceptable.","supported_mime_types":') !== FALSE); } /** diff --git a/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php b/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php index 0ddb9a4941dc..19bbe8dd358a 100644 --- a/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php +++ b/core/tests/Drupal/Tests/Core/Ajax/AjaxResponseTest.php @@ -8,7 +8,9 @@ namespace Drupal\Tests\Core\Ajax; use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Render\Element\Ajax; use Drupal\Tests\UnitTestCase; +use Symfony\Component\HttpFoundation\Request; /** * @coversDefaultClass \Drupal\Core\Ajax\AjaxResponse @@ -68,5 +70,21 @@ class AjaxResponseTest extends UnitTestCase { $this->assertSame($commands[0], array('command' => 'three', 'class' => 'test-class')); } + /** + * Tests the support for IE specific headers in file uploads. + * + * @cover ::prepareResponse + */ + public function testPrepareResponseForIeFormRequestsWithFileUpload() { + $request = Request::create('/example', 'POST'); + $request->headers->set('Accept', 'text/html'); + $response = new AjaxResponse([]); + $response->headers->set('Content-Type', 'application/json; charset=utf-8'); + + $response->prepare($request); + $this->assertEquals('text/html; charset=utf-8', $response->headers->get('Content-Type')); + $this->assertEquals($response->getContent(), ''); + } + }