Ajax: Support headers for script transport even when cross-domain

The AJAX script transport has two versions: XHR + `jQuery.globalEval` or
appending a script tag (note that `jQuery.globalEval` also appends a
script tag now, but inline). The former cannot support the `headers`
option which has so far not been taken into account.

For jQuery 3.x, the main consequence was the option not being respected
for cross-domain requests. Since in 4.x we use the latter way more
often, the option was being ignored in more cases.

The transport now checks whether the `headers` option is specified and
uses the XHR way unless `scriptAttrs` are specified as well.

Fixes gh-5142
Closes gh-5193
This commit is contained in:
Michał Gołębiowski-Owczarek 2023-02-01 13:40:55 +01:00 committed by GitHub
parent b02a257f98
commit 6d1364431b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 34 deletions

View File

@ -6,8 +6,14 @@ import "../ajax.js";
function canUseScriptTag( s ) { function canUseScriptTag( s ) {
// A script tag can only be used for async, cross domain or forced-by-attrs requests. // A script tag can only be used for async, cross domain or forced-by-attrs requests.
// Requests with headers cannot use a script tag. However, when both `scriptAttrs` &
// `headers` options are specified, both are impossible to satisfy together; we
// prefer `scriptAttrs` then.
// Sync requests remain handled differently to preserve strict script ordering. // Sync requests remain handled differently to preserve strict script ordering.
return s.crossDomain || s.scriptAttrs || return s.scriptAttrs || (
!s.headers &&
(
s.crossDomain ||
// When dealing with JSONP (`s.dataTypes` include "json" then) // When dealing with JSONP (`s.dataTypes` include "json" then)
// don't use a script tag so that error responses still may have // don't use a script tag so that error responses still may have
@ -16,10 +22,12 @@ function canUseScriptTag( s ) {
// * have `scriptAttrs` set as that's a script-only functionality // * have `scriptAttrs` set as that's a script-only functionality
// Note that this means JSONP requests violate strict CSP script-src settings. // Note that this means JSONP requests violate strict CSP script-src settings.
// A proper solution is to migrate from using JSONP to a CORS setup. // A proper solution is to migrate from using JSONP to a CORS setup.
( s.async && jQuery.inArray( "json", s.dataTypes ) < 0 ); ( s.async && jQuery.inArray( "json", s.dataTypes ) < 0 )
)
);
} }
// Install script dataType. Don't specify `content.script` so that an explicit // Install script dataType. Don't specify `contents.script` so that an explicit
// `dataType: "script"` is required (see gh-2432, gh-4822) // `dataType: "script"` is required (see gh-2432, gh-4822)
jQuery.ajaxSetup( { jQuery.ajaxSetup( {
accepts: { accepts: {

View File

@ -71,11 +71,13 @@ QUnit.module( "ajax", {
}; };
} ); } );
ajaxTest( "jQuery.ajax() - custom attributes for script tag", 5, jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) {
ajaxTest( "jQuery.ajax() - custom attributes for script tag" + label, 5,
function( assert ) { function( assert ) {
return { return {
create: function( options ) { create: function( options ) {
var xhr; var xhr;
options.crossDomain = crossDomain;
options.method = "POST"; options.method = "POST";
options.dataType = "script"; options.dataType = "script";
options.scriptAttrs = { id: "jquery-ajax-test", async: "async" }; options.scriptAttrs = { id: "jquery-ajax-test", async: "async" };
@ -96,10 +98,64 @@ QUnit.module( "ajax", {
} }
); );
ajaxTest( "jQuery.ajax() - headers for script transport" + label, 3,
function( assert ) {
return {
create: function( options ) {
Globals.register( "corsCallback" );
window.corsCallback = function( response ) {
assert.strictEqual( response.headers[ "x-custom-test-header" ],
"test value", "Custom header sent" );
};
options.crossDomain = crossDomain;
options.dataType = "script";
options.headers = { "x-custom-test-header": "test value" };
return jQuery.ajax( url( "mock.php?action=script&callback=corsCallback" ), options );
},
success: function() {
assert.ok( true, "success" );
},
complete: function() {
assert.ok( true, "complete" );
}
};
}
);
ajaxTest( "jQuery.ajax() - scriptAttrs winning over headers" + label, 4,
function( assert ) {
return {
create: function( options ) {
var xhr;
Globals.register( "corsCallback" );
window.corsCallback = function( response ) {
assert.ok( !response.headers[ "x-custom-test-header" ],
"headers losing with scriptAttrs" );
};
options.crossDomain = crossDomain;
options.dataType = "script";
options.scriptAttrs = { id: "jquery-ajax-test", async: "async" };
options.headers = { "x-custom-test-header": "test value" };
xhr = jQuery.ajax( url( "mock.php?action=script&callback=corsCallback" ), options );
assert.equal( jQuery( "#jquery-ajax-test" ).attr( "async" ), "async", "attr value" );
return xhr;
},
success: function() {
assert.ok( true, "success" );
},
complete: function() {
assert.ok( true, "complete" );
}
};
}
);
} );
ajaxTest( "jQuery.ajax() - execute JS when dataType option is provided", 3, ajaxTest( "jQuery.ajax() - execute JS when dataType option is provided", 3,
function( assert ) { function( assert ) {
return { return {
create: function( options ) { create: function( options ) {
Globals.register( "corsCallback" );
options.crossDomain = true; options.crossDomain = true;
options.dataType = "script"; options.dataType = "script";
return jQuery.ajax( url( "mock.php?action=script&header=ecma" ), options ); return jQuery.ajax( url( "mock.php?action=script&header=ecma" ), options );