Fixes #8297. Makes sure response headers with empty values are handled properly and...
[jquery.git] / src / ajax.js
index 4830e3d..6414e8c 100644 (file)
@@ -4,7 +4,7 @@ var r20 = /%20/g,
        rbracket = /\[\]$/,
        rCRLF = /\r?\n/g,
        rhash = /#.*$/,
-       rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
+       rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
        rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
        // #7653, #8125, #8152: local protocol detection
        rlocalProtocol = /(?:^file|^widget|\-extension):$/,
@@ -15,6 +15,10 @@ var r20 = /%20/g,
        rselectTextarea = /^(?:select|textarea)/i,
        rspacesAjax = /\s+/,
        rts = /([?&])_=[^&]*/,
+       rucHeaders = /(^|\-)([a-z])/g,
+       rucHeadersFunc = function( _, $1, $2 ) {
+               return $1 + $2.toUpperCase();
+       },
        rurl = /^([\w\+\.\-]+:)\/\/([^\/?#:]*)(?::(\d+))?/,
 
        // Keep a copy of the old load method
@@ -160,7 +164,7 @@ jQuery.fn.extend({
                        if ( jQuery.isFunction( params ) ) {
                                // We assume that it's the callback
                                callback = params;
-                               params = null;
+                               params = undefined;
 
                        // Otherwise, build a param string
                        } else if ( typeof params === "object" ) {
@@ -252,7 +256,7 @@ jQuery.each( [ "get", "post" ], function( i, method ) {
                if ( jQuery.isFunction( data ) ) {
                        type = type || callback;
                        callback = data;
-                       data = null;
+                       data = undefined;
                }
 
                return jQuery.ajax({
@@ -268,18 +272,34 @@ jQuery.each( [ "get", "post" ], function( i, method ) {
 jQuery.extend({
 
        getScript: function( url, callback ) {
-               return jQuery.get( url, null, callback, "script" );
+               return jQuery.get( url, undefined, callback, "script" );
        },
 
        getJSON: function( url, data, callback ) {
                return jQuery.get( url, data, callback, "json" );
        },
 
-       ajaxSetup: function( settings ) {
-               jQuery.extend( true, jQuery.ajaxSettings, settings );
-               if ( settings.context ) {
-                       jQuery.ajaxSettings.context = settings.context;
+       // Creates a full fledged settings object into target
+       // with both ajaxSettings and settings fields.
+       // If target is omitted, writes into ajaxSettings.
+       ajaxSetup: function ( target, settings ) {
+               if ( !settings ) {
+                       // Only one parameter, we extend ajaxSettings
+                       settings = target;
+                       target = jQuery.extend( true, jQuery.ajaxSettings, settings );
+               } else {
+                       // target was provided, we extend into it
+                       jQuery.extend( true, target, jQuery.ajaxSettings, settings );
+               }
+               // Flatten fields we don't want deep extended
+               for( var field in { context: 1, url: 1 } ) {
+                       if ( field in settings ) {
+                               target[ field ] = settings[ field ];
+                       } else if( field in jQuery.ajaxSettings ) {
+                               target[ field ] = jQuery.ajaxSettings[ field ];
+                       }
                }
+               return target;
        },
 
        ajaxSettings: {
@@ -356,18 +376,15 @@ jQuery.extend({
                options = options || {};
 
                var // Create the final options object
-                       s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ),
+                       s = jQuery.ajaxSetup( {}, options ),
                        // Callbacks context
-                       // We force the original context if it exists
-                       // or take it from jQuery.ajaxSettings otherwise
-                       // (plain objects used as context get extended)
-                       callbackContext =
-                               ( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s,
+                       callbackContext = s.context || s,
                        // Context for global events
                        // It's the callbackContext if one was provided in the options
-                       // and if it's a DOM node
-                       globalEventContext = callbackContext !== s && callbackContext.nodeType ?
-                               jQuery( callbackContext ) : jQuery.event,
+                       // and if it's a DOM node or a jQuery collection
+                       globalEventContext = callbackContext !== s &&
+                               ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+                                               jQuery( callbackContext ) : jQuery.event,
                        // Deferreds
                        deferred = jQuery.Deferred(),
                        completeDeferred = jQuery._Deferred(),
@@ -399,8 +416,8 @@ jQuery.extend({
 
                                // Caches the header
                                setRequestHeader: function( name, value ) {
-                                       if ( state === 0 ) {
-                                               requestHeaders[ name.toLowerCase() ] = value;
+                                       if ( !state ) {
+                                               requestHeaders[ name.toLowerCase().replace( rucHeaders, rucHeadersFunc ) ] = value;
                                        }
                                        return this;
                                },
@@ -422,7 +439,15 @@ jQuery.extend({
                                                }
                                                match = responseHeaders[ key.toLowerCase() ];
                                        }
-                                       return match || null;
+                                       return match === undefined ? null : match;
+                               },
+
+                               // Overrides response content-type header
+                               overrideMimeType: function( type ) {
+                                       if ( !state ) {
+                                               s.mimeType = type;
+                                       }
+                                       return this;
                                },
 
                                // Cancel the request
@@ -439,7 +464,7 @@ jQuery.extend({
                // Callback for when everything is done
                // It is defined here because jslint complains if it is declared
                // at the end of the function (which would be more logical and readable)
-               function done( status, statusText, responses, headers) {
+               function done( status, statusText, responses, headers ) {
 
                        // Called once
                        if ( state === 2 ) {
@@ -508,7 +533,7 @@ jQuery.extend({
                                // We extract error from statusText
                                // then normalize statusText and status for non-aborts
                                error = statusText;
-                               if( status ) {
+                               if( !statusText || status ) {
                                        statusText = "error";
                                        if ( status < 0 ) {
                                                status = 0;
@@ -573,7 +598,7 @@ jQuery.extend({
                // Remove hash character (#7531: and string promotion)
                // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
                // We also use the url parameter if available
-               s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+               s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
 
                // Extract dataTypes list
                s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
@@ -640,28 +665,28 @@ jQuery.extend({
 
                // Set the correct header, if data is being sent
                if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
-                       requestHeaders[ "content-type" ] = s.contentType;
+                       requestHeaders[ "Content-Type" ] = s.contentType;
                }
 
                // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
                if ( s.ifModified ) {
                        ifModifiedKey = ifModifiedKey || s.url;
                        if ( jQuery.lastModified[ ifModifiedKey ] ) {
-                               requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ ifModifiedKey ];
+                               requestHeaders[ "If-Modified-Since" ] = jQuery.lastModified[ ifModifiedKey ];
                        }
                        if ( jQuery.etag[ ifModifiedKey ] ) {
-                               requestHeaders[ "if-none-match" ] = jQuery.etag[ ifModifiedKey ];
+                               requestHeaders[ "If-None-Match" ] = jQuery.etag[ ifModifiedKey ];
                        }
                }
 
                // Set the Accepts header for the server, depending on the dataType
-               requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+               requestHeaders.Accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
                        s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
                        s.accepts[ "*" ];
 
                // Check for headers option
                for ( i in s.headers ) {
-                       requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
+                       jqXHR.setRequestHeader( i, s.headers[ i ] );
                }
 
                // Allow custom headers/mimetypes and early abort
@@ -684,8 +709,7 @@ jQuery.extend({
                if ( !transport ) {
                        done( -1, "No Transport" );
                } else {
-                       // Set state as sending
-                       state = jqXHR.readyState = 1;
+                       jqXHR.readyState = 1;
                        // Send global event
                        if ( fireGlobals ) {
                                globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
@@ -698,6 +722,7 @@ jQuery.extend({
                        }
 
                        try {
+                               state = 1;
                                transport.send( requestHeaders, done );
                        } catch (e) {
                                // Propagate exception as error if not done
@@ -826,7 +851,7 @@ function ajaxHandleResponses( s, jqXHR, responses ) {
        while( dataTypes[ 0 ] === "*" ) {
                dataTypes.shift();
                if ( ct === undefined ) {
-                       ct = jqXHR.getResponseHeader( "content-type" );
+                       ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
                }
        }