IE iframe doesn't handle application/json response properly

StringbeanNinja picture StringbeanNinja · Jul 17, 2013 · Viewed 9.2k times · Source

I am currently upgrading parts of an ASP.NET MVC website to be more RESTful, using ASP.NET Web API. One of the features we are moving to a more RESTful design is file upload. For the client, we are using a jquery plugin, ajaxForm, to wrap the creation of an iframe which will submit the form containing the file input element. This was working great with ASP.NET MVC.

When changing it to use our Web API endpoint, which returns a response with a Content-Type of "application/json", we noticed problems with Internet Explorer 9. It appears the ajaxForm success function was never called. From what I can tell, it appears that the iframe in IE interprets the body of a response with a Content-Type of "application/json" as a file attachment to be downloaded. This means it never fires the iframe's "loaded" event, which means the ajaxForm onload event handler will never be triggered, and our ajaxForm success function will never be called.

We noticed the issue in IE 7 as well, but we could not recreate the issue in the latest release versions of Firefox or Chrome, even when forcing them to use an iframe rather than File API with FormData.

To resolve this issue for now, I am now forcing the response Content-Type back to "text/plain", which is what we were returning previously from the ASP.NET MVC controller actions which handled file upload. This makes everything work again.

My questions:

  • Is there a way I can keep the Web API response Content-Type as "application/json" and have IE interpret it correctly?
  • Is there a better way of doing file upload when using IE and Web API? Maybe a different plugin or better technique?

Additional Restrictions: I cannot use ActiveX or Flash for this website. I can use a different plugin, but only if it has general cross-browser support. (IE, Chrome, Firefox, Safari, etc)

My HTML:

<form id="uploadFormId" action="" method="post" enctype="multipart/form-data" encoding="multipart/form-data">
    <input type="file" name="files[]"/>
</form>

My javascript:

function onFileChange( e ) {
    if ( e.type === e.originalEvent.type ) {
        var filePath = $( e.currentTarget ).val();
        if ( filePath !== '' ) {
            $( this ).closest( 'form' ).submit();
        }
    }
};

$( function() {
    $( '#uploadFormId' ).ajaxForm( {
        url: "api/Files/1234",
        dataType: 'json',
        success: function ( response ) { 
            alert( response );
        },
        error: function ( xhr, status, error ) { 
            alert( status );
        }
    } );
    $( '#uploadFormId input[type="file"]' ).bind( 'change', onFileChange );
});

"application/json" response headers (doesn't work in IE):

Cache-Control:no-cache
Content-Length:337
Content-Type:application/json; charset=utf-8
Date:Wed, 17 Jul 2013 13:10:47 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/8.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET

"text/plain" response headers (works in IE):

Cache-Control:no-cache
Content-Length:322
Content-Type:text/plain
Date:Wed, 17 Jul 2013 13:17:24 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/8.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET

Answer

bobince picture bobince · Jul 17, 2013

When ajaxForm uses the iframe submission mode, the response from the call is necessarily rendered in the body of the iframe. This means it must be a content type that the browser can render—generally HTML, but text/plain would also happen to work. However the browser can't render application/json as a page.

There is a specific problem with using text/plain too, in that browsers may content-sniff it, and treat the resource as HTML if there's something that looks like an HTML tag in the data. If your JSON comes back with user-supplied data in it, that could allow someone to inject executable JavaScript into your site (XSS attack).

As suggested by the ajaxForm doc you're expected to detect when the call comes from an iframe post instead of AJAX, and return a text/html response with a textarea wrapper in that case:

To account for the challenges of script and JSON responses when using the iframe mode, the Form Plugin allows these responses to be embedded in a textarea element and it is recommended that you do so for these response types when used in conjuction with file uploads and older browsers.