From d19432a332c21935d42087db706e50c5259063ea Mon Sep 17 00:00:00 2001 From: Juliusz Gonera Date: Thu, 27 Feb 2014 17:51:30 -0800 Subject: [PATCH] mediawiki.api: Use FormData for POST requests when supported 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 | 50 +++++++++++++++---- .../mediawiki.api/mediawiki.api.test.js | 19 +++++++ 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/resources/src/mediawiki.api/mediawiki.api.js b/resources/src/mediawiki.api/mediawiki.api.js index f300672554..b37e2a6e8d 100644 --- a/resources/src/mediawiki.api/mediawiki.api.js +++ b/resources/src/mediawiki.api/mediawiki.api.js @@ -131,7 +131,7 @@ 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 ); @@ -141,14 +141,46 @@ 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, diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js index dab35f60d0..4ee8afa2ed 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -46,6 +46,25 @@ } ); } ); + 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 ); -- 2.20.1