Skip to content

Commit 6d13644

Browse files
authored
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
1 parent b02a257 commit 6d13644

File tree

2 files changed

+98
-34
lines changed

2 files changed

+98
-34
lines changed

src/ajax/script.js

+18-10
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,28 @@ import "../ajax.js";
66
function canUseScriptTag( s ) {
77

88
// A script tag can only be used for async, cross domain or forced-by-attrs requests.
9+
// Requests with headers cannot use a script tag. However, when both `scriptAttrs` &
10+
// `headers` options are specified, both are impossible to satisfy together; we
11+
// prefer `scriptAttrs` then.
912
// Sync requests remain handled differently to preserve strict script ordering.
10-
return s.crossDomain || s.scriptAttrs ||
13+
return s.scriptAttrs || (
14+
!s.headers &&
15+
(
16+
s.crossDomain ||
1117

12-
// When dealing with JSONP (`s.dataTypes` include "json" then)
13-
// don't use a script tag so that error responses still may have
14-
// `responseJSON` set. Continue using a script tag for JSONP requests that:
15-
// * are cross-domain as AJAX requests won't work without a CORS setup
16-
// * have `scriptAttrs` set as that's a script-only functionality
17-
// Note that this means JSONP requests violate strict CSP script-src settings.
18-
// A proper solution is to migrate from using JSONP to a CORS setup.
19-
( s.async && jQuery.inArray( "json", s.dataTypes ) < 0 );
18+
// When dealing with JSONP (`s.dataTypes` include "json" then)
19+
// don't use a script tag so that error responses still may have
20+
// `responseJSON` set. Continue using a script tag for JSONP requests that:
21+
// * are cross-domain as AJAX requests won't work without a CORS setup
22+
// * have `scriptAttrs` set as that's a script-only functionality
23+
// Note that this means JSONP requests violate strict CSP script-src settings.
24+
// A proper solution is to migrate from using JSONP to a CORS setup.
25+
( s.async && jQuery.inArray( "json", s.dataTypes ) < 0 )
26+
)
27+
);
2028
}
2129

22-
// Install script dataType. Don't specify `content.script` so that an explicit
30+
// Install script dataType. Don't specify `contents.script` so that an explicit
2331
// `dataType: "script"` is required (see gh-2432, gh-4822)
2432
jQuery.ajaxSetup( {
2533
accepts: {

test/unit/ajax.js

+80-24
Original file line numberDiff line numberDiff line change
@@ -71,35 +71,91 @@ QUnit.module( "ajax", {
7171
};
7272
} );
7373

74-
ajaxTest( "jQuery.ajax() - custom attributes for script tag", 5,
75-
function( assert ) {
76-
return {
77-
create: function( options ) {
78-
var xhr;
79-
options.method = "POST";
80-
options.dataType = "script";
81-
options.scriptAttrs = { id: "jquery-ajax-test", async: "async" };
82-
xhr = jQuery.ajax( url( "mock.php?action=script" ), options );
83-
assert.equal( jQuery( "#jquery-ajax-test" ).attr( "async" ), "async", "attr value" );
84-
return xhr;
85-
},
86-
beforeSend: function( _jqXhr, settings ) {
87-
assert.strictEqual( settings.type, "GET", "Type changed to GET" );
88-
},
89-
success: function() {
90-
assert.ok( true, "success" );
91-
},
92-
complete: function() {
93-
assert.ok( true, "complete" );
94-
}
95-
};
96-
}
97-
);
74+
jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) {
75+
ajaxTest( "jQuery.ajax() - custom attributes for script tag" + label, 5,
76+
function( assert ) {
77+
return {
78+
create: function( options ) {
79+
var xhr;
80+
options.crossDomain = crossDomain;
81+
options.method = "POST";
82+
options.dataType = "script";
83+
options.scriptAttrs = { id: "jquery-ajax-test", async: "async" };
84+
xhr = jQuery.ajax( url( "mock.php?action=script" ), options );
85+
assert.equal( jQuery( "#jquery-ajax-test" ).attr( "async" ), "async", "attr value" );
86+
return xhr;
87+
},
88+
beforeSend: function( _jqXhr, settings ) {
89+
assert.strictEqual( settings.type, "GET", "Type changed to GET" );
90+
},
91+
success: function() {
92+
assert.ok( true, "success" );
93+
},
94+
complete: function() {
95+
assert.ok( true, "complete" );
96+
}
97+
};
98+
}
99+
);
100+
101+
ajaxTest( "jQuery.ajax() - headers for script transport" + label, 3,
102+
function( assert ) {
103+
return {
104+
create: function( options ) {
105+
Globals.register( "corsCallback" );
106+
window.corsCallback = function( response ) {
107+
assert.strictEqual( response.headers[ "x-custom-test-header" ],
108+
"test value", "Custom header sent" );
109+
};
110+
options.crossDomain = crossDomain;
111+
options.dataType = "script";
112+
options.headers = { "x-custom-test-header": "test value" };
113+
return jQuery.ajax( url( "mock.php?action=script&callback=corsCallback" ), options );
114+
},
115+
success: function() {
116+
assert.ok( true, "success" );
117+
},
118+
complete: function() {
119+
assert.ok( true, "complete" );
120+
}
121+
};
122+
}
123+
);
124+
125+
ajaxTest( "jQuery.ajax() - scriptAttrs winning over headers" + label, 4,
126+
function( assert ) {
127+
return {
128+
create: function( options ) {
129+
var xhr;
130+
Globals.register( "corsCallback" );
131+
window.corsCallback = function( response ) {
132+
assert.ok( !response.headers[ "x-custom-test-header" ],
133+
"headers losing with scriptAttrs" );
134+
};
135+
options.crossDomain = crossDomain;
136+
options.dataType = "script";
137+
options.scriptAttrs = { id: "jquery-ajax-test", async: "async" };
138+
options.headers = { "x-custom-test-header": "test value" };
139+
xhr = jQuery.ajax( url( "mock.php?action=script&callback=corsCallback" ), options );
140+
assert.equal( jQuery( "#jquery-ajax-test" ).attr( "async" ), "async", "attr value" );
141+
return xhr;
142+
},
143+
success: function() {
144+
assert.ok( true, "success" );
145+
},
146+
complete: function() {
147+
assert.ok( true, "complete" );
148+
}
149+
};
150+
}
151+
);
152+
} );
98153

99154
ajaxTest( "jQuery.ajax() - execute JS when dataType option is provided", 3,
100155
function( assert ) {
101156
return {
102157
create: function( options ) {
158+
Globals.register( "corsCallback" );
103159
options.crossDomain = true;
104160
options.dataType = "script";
105161
return jQuery.ajax( url( "mock.php?action=script&header=ecma" ), options );

0 commit comments

Comments
 (0)