8000 Ajax: Support binary data (including FormData) · jquery/jquery@a7ed9a7 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a7ed9a7

Browse files
authored
Ajax: Support binary data (including FormData)
Two changes have been applied: * prefilters are now applied before data is converted to a string; this allows prefilters to disable such a conversion * a prefilter for binary data is added; it disables data conversion for non-string non-plain-object `data`; for `FormData` bodies, it removes manually-set `Content-Type` header - this is required as browsers need to append their own boundary to the header Ref gh-4150 Closes gh-5197
1 parent 0b9c503 commit a7ed9a7

File tree

8 files changed

+109
-5
lines changed

8 files changed

+109
-5
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"karma-qunit": "4.1.2",
5656
"karma-webkit-launcher": "2.1.0",
5757
"load-grunt-tasks": "5.1.0",
58+
"multiparty": "4.2.3",
5859
"native-promise-only": "0.8.1",
5960
"playwright-webkit": "1.29.2",
6061
"promises-aplus-tests": "2.1.2",

src/ajax.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,14 +562,14 @@ jQuery.extend( {
562562
}
563563
}
564564

565+
// Apply prefilters
566+
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
567+
565568
// Convert data if not already a string
566569
if ( s.data && s.processData && typeof s.data !== "string" ) {
567570
s.data = jQuery.param( s.data, s.traditional );
568571
}
569572

570-
// Apply prefilters
571-
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
572-
573573
// If request was aborted inside a prefilter, stop there
574574
if ( completed ) {
575575
return jqXHR;

src/ajax/binary.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import jQuery from "../core.js";
2+
3+
import "../ajax.js";
4+
5+
jQuery.ajaxPrefilter( function( s ) {
6+
7+
// Binary data needs to be passed to XHR as-is without stringification.
8+
if ( typeof s.data !== "string" && !jQuery.isPlainObject( s.data ) ) {
9+
s.processData = false;
10+
}
11+
12+
// `Content-Type` for requests with `FormData` bodies needs to be set
13+
// by the browser as it needs to append the `boundary` it generated.
14+
if ( s.data instanceof window.FormData ) {
15+
s.contentType = false;
16+
}
17+
} );

src/jquery.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import "./ajax.js";
2323
import "./ajax/xhr.js";
2424
import "./ajax/script.js";
2525
import "./ajax/jsonp.js";
26+
import "./ajax/binary.js";
2627
import "./ajax/load.js";
2728
import "./core/parseXML.js";
2829
import "./core/parseHTML.js";

test/data/mock.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ protected function xmlOverJsonp( $req ) {
124124
echo "$cleanCallback($text)\n";
125125
}
126126

127+
protected function formData( $req ) {
128+
$prefix = 'multipart/form-data; boundary=--';
129+
$contentTypeValue = $req->headers[ 'CONTENT-TYPE' ];
130+
if ( substr( $contentTypeValue, 0, strlen( $prefix ) ) === $prefix ) {
131+
echo 'key1 -> ' . $_POST[ 'key1' ] . ', key2 -> ' . $_POST[ 'key2' ];
132+
} else {
133+
echo 'Incorrect Content-Type: ' . $contentTypeValue .
134+
"\nExpected prefix: " . $prefix;
135+
}
136+
}
137+
127138
protected function error( $req ) {
128139
header( 'HTTP/1.0 400 Bad Request' );
129140
if ( isset( $req->query['json'] ) ) {

test/data/testinit.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,11 @@ function url( value ) {
174174
}
175175

176176
// Ajax testing helper
177-
this.ajaxTest = function( title, expect, options ) {
178-
QUnit.test( title, function( assert ) {
177+
this.ajaxTest = function( title, expect, options, wrapper ) {
178+
if ( !wrapper ) {
179+
wrapper = QUnit.test;
180+
}
181+
wrapper.call( QUnit, title, function( assert ) {
179182
assert.expect( expect );
180183
var requestOptions;
181184

test/middleware-mockserver.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const url = require( "url" );
44
const fs = require( "fs" );
55
const getRawBody = require( "raw-body" );
6+
const multiparty = require( "multiparty" );
67

78
let cspLog = "";
89

@@ -141,6 +142,19 @@ const mocks = {
141142
resp.writeHead( 200 );
142143
resp.end( `${ cleanCallback( callback ) }(${ JSON.stringify( body ) })\n` );
143144
},
145+
formData: function( req, resp, next ) {
146+
const prefix = "multipart/form-data; boundary=--";
147+
const contentTypeValue = req.headers[ "content-type" ];
148+
resp.writeHead( 200 );
149+
if ( ( prefix || "" ).startsWith( prefix ) ) {
150+
getMultiPartContent( req ).then( function( { fields = {} } ) {
151+
resp.end( `key1 -> ${ fields.key1 }, key2 -> ${ fields.key2 }` );
152+
}, next );
153+
} else {
154+
resp.end( `Incorrect Content-Type: ${ contentTypeValue
155+
}\nExpected prefix: ${ prefix }` );
156+
}
157+
},
144158
error: function( req, resp ) {
145159
if ( req.query.json ) {
146160
resp.writeHead( 400, { "content-type": "application/json" } );
@@ -363,4 +377,18 @@ function getBody( req ) {
363377
} );
364378
}
365379

380+
function getMultiPartContent( req ) {
381+
return new Promise( function( resolve ) {
382+
if ( req.method !== "POST" ) {
383+
resolve( "" );
384+
return;
385+
}
386+
387+
const form = new multiparty.Form();
388+
form.parse( req, function( _err, fields, files ) {
389+
resolve( { fields, files } );
390+
} );
391+
} );
392+
}
393+
366394
module.exports = MockserverMiddlewareFactory;

test/unit/ajax.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3105,4 +3105,47 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re
31053105
assert.ok( jQuery.active === 0, "ajax active counter should be zero: " + jQuery.active );
31063106
} );
31073107

3108+
ajaxTest( "jQuery.ajax() - FormData", 1, function( assert ) {
3109+
var formData = new FormData();
3110+
formData.append( "key1", "value1" );
3111+
formData.append( "key2", "value2" );
3112+
3113+
return {
3114+
url: url( "mock.php?action=formData" ),
3115+
method: "post",
3116+
data: formData,
3117+
success: function( data ) {
3118+
assert.strictEqual( data, "key1 -> value1, key2 -> value2",
3119+
"FormData sent correctly" );
3120+
}
3121+
};
3122+
} );
3123+
3124+
ajaxTest( "jQuery.ajax() - URLSearchParams", 1, function( assert ) {
3125+
var urlSearchParams = new URLSearchParams();
3126+
urlSearchParams.append( "name", "peter" );
3127+
3128+
return {
3129+
url: url( "mock.php?action=name" ),
3130+
method: "post",
3131+
data: urlSearchParams,
3132+
success: function( data ) {
3133+
assert.strictEqual( data, "pan", "URLSearchParams sent correctly" );
3134+
}
3135+
};
3136+
}, QUnit.testUnlessIE );
3137+
3138+
ajaxTest( "jQuery.ajax() - Blob", 1, function( assert ) {
3139+
var blob = new Blob( [ "name=peter" ], { type: "text/plain" } );
3140+
3141+
return {
3142+
url: url( "mock.php?action=name" ),
3143+
method: "post",
3144+
data: blob,
3145+
success: function( data ) {
3146+
assert.strictEqual( data, "pan", "Blob sent correctly" );
3147+
}
3148+
};
3149+
} );
3150+
31083151
} )();

0 commit comments

Comments
 (0)
0