mediawiki.api: Use FormData for POST requests when supported
authorJuliusz Gonera <jgonera@wikimedia.org>
Fri, 28 Feb 2014 01:51:30 +0000 (17:51 -0800)
committerTimo Tijhof <krinklemail@gmail.com>
Tue, 8 Apr 2014 21:29:22 +0000 (14:29 -0700)
Prefer using FormData for POST requests with contentType set to
'multipart/form-data'. POST requests tend to contain large
strings, which inflate with escaping. FormData keeps that small
through literals with boundaries instead of heavy escaping.

Also, FormData is needed for file uploads via AJAX.

Change-Id: Ibb652e4740aca0a710c0ad426796d98217753842

resources/src/mediawiki.api/mediawiki.api.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js

index f300672..b37e2a6 100644 (file)
                        var token,
                                apiDeferred = $.Deferred(),
                                msg = 'Use of mediawiki.api callback params is deprecated. Use the Promise instead.',
-                               xhr;
+                               xhr, key, formData;
 
                        parameters = $.extend( {}, this.defaults.parameters, parameters );
                        ajaxOptions = $.extend( {}, this.defaults.ajax, ajaxOptions );
                                token = parameters.token;
                                delete parameters.token;
                        }
-                       // Some deployed MediaWiki >= 1.17 forbid periods in URLs, due to an IE XSS bug
-                       // So let's escape them here. See bug #28235
-                       // This works because jQuery accepts data as a query string or as an Object
-                       ajaxOptions.data = $.param( parameters ).replace( /\./g, '%2E' );
-
-                       // If we extracted a token parameter, add it back in.
-                       if ( token ) {
-                               ajaxOptions.data += '&token=' + encodeURIComponent( token );
+
+                       // If multipart/form-data has been requested and emulation is possible, emulate it
+                       if (
+                               ajaxOptions.type === 'POST' &&
+                               window.FormData &&
+                               ajaxOptions.contentType === 'multipart/form-data'
+                       ) {
+
+                               formData = new FormData();
+
+                               for ( key in parameters ) {
+                                       formData.append( key, parameters[key] );
+                               }
+                               // If we extracted a token parameter, add it back in.
+                               if ( token ) {
+                                       formData.append( 'token', token );
+                               }
+
+                               ajaxOptions.data = formData;
+
+                               // Prevent jQuery from mangling our FormData object
+                               ajaxOptions.processData = false;
+                               // Prevent jQuery from overriding the Content-Type header
+                               ajaxOptions.contentType = false;
+                       } else {
+                               // Some deployed MediaWiki >= 1.17 forbid periods in URLs, due to an IE XSS bug
+                               // So let's escape them here. See bug #28235
+                               // This works because jQuery accepts data as a query string or as an Object
+                               ajaxOptions.data = $.param( parameters ).replace( /\./g, '%2E' );
+
+                               // If we extracted a token parameter, add it back in.
+                               if ( token ) {
+                                       ajaxOptions.data += '&token=' + encodeURIComponent( token );
+                               }
+
+                               if ( ajaxOptions.contentType === 'multipart/form-data' ) {
+                                       // We were asked to emulate but can't, so drop the Content-Type header, otherwise
+                                       // it'll be wrong and the server will fail to decode the POST body
+                                       delete ajaxOptions.contentType;
+                               }
                        }
 
                        // Backwards compatibility: Before MediaWiki 1.20,
index dab35f6..4ee8afa 100644 (file)
                } );
        } );
 
+       QUnit.test( 'FormData support', function ( assert ) {
+               QUnit.expect( 2 );
+
+               var api = new mw.Api();
+
+               api.post( { action: 'test' }, { contentType: 'multipart/form-data' } );
+
+               this.server.respond( function ( request ) {
+                       if ( window.FormData ) {
+                               assert.ok( !request.url.match( /action=/), 'Request has no query string' );
+                               assert.ok( request.requestBody instanceof FormData, 'Request uses FormData body' );
+                       } else {
+                               assert.ok( !request.url.match( /action=test/), 'Request has no query string' );
+                               assert.equal( request.requestBody, 'action=test&format=json', 'Request uses query string body' );
+                       }
+                       request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
+               } );
+       } );
+
        QUnit.test( 'Deprecated callback methods', function ( assert ) {
                QUnit.expect( 3 );