public
Last active

jQuery CORS Plugin - transparently add CORS support for IE8+ in jQuery using XDomainRequest. Support cookies.

  • Download Gist
jquery.xdomain.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
PROJECT MOVED TO https://github.com/Ovea/cors
 
/**
* https://gist.github.com/1114981
*
* By default, support transferring session cookie with XDomainRequest for IE. The cookie value is by default 'jsessionid'
*
* You can change the session cookie value like this, before including this script:
*
* window.SESSION_COOKIE_NAME = 'PHP_SESSION';
* window.SESSION_COOKIE_NAME = 'ID';
*
* Or if you want to disable cookie session support:
*
* window.SESSION_COOKIE_NAME = null;
*
* ================
* MINIFIED VERSION
* ================
* (function(c){if(c.browser.msie&&"XDomainRequest"in window&&!("__jquery_xdomain__"in c)){c.__jquery_xdomain__=c.support.cors=!0;var e=function(a){return"object"===c.type(a)?a:(a=/^(((([^:\/#\?]+:)?(?:\/\/((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?]+)(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/.exec(a))?{href:a[0]||"",hrefNoHash:a[1]||"",hrefNoSearch:a[2]||"",domain:a[3]||"",protocol:a[4]||"",authority:a[5]||"",username:a[7]||"",password:a[8]||"",host:a[9]||"", hostname:a[10]||"",port:a[11]||"",pathname:a[12]||"",directory:a[13]||"",filename:a[14]||"",search:a[15]||"",hash:a[16]||""}:{}},f=c.ajaxSettings.xhr,d="SESSION_COOKIE_NAME"in window?window.SESSION_COOKIE_NAME:"jsessionid",g=e(document.location.href).domain;c.ajaxSettings.xhr=function(){var a=e(this.url).domain;if(""===a||a===g)return f.call(c.ajaxSettings);try{var b=new XDomainRequest;if(!b.setRequestHeader)b.setRequestHeader=c.noop;if(!b.getAllResponseHeaders)b.getAllResponseHeaders=c.noop;if(d){var h= b.open;b.open=function(){var a=RegExp("(?:^|; )"+d+"=([^;]*)","i").exec(document.cookie);if(a=a&&a[1]){var b=arguments[1].indexOf("?");arguments[1]=-1==b?arguments[1]+(";"+d+"="+a):arguments[1].substring(0,b)+";"+d+"="+a+arguments[1].substring(b)}return h.apply(this,arguments)}}b.onload=function(){if("function"===typeof b.onreadystatechange)b.readyState=4,b.status=200,b.onreadystatechange.call(b,null,!1)};b.onerror=b.ontimeout=function(){if("function"===typeof b.onreadystatechange)b.readyState=4, b.status=500,b.onreadystatechange.call(b,null,!1)};return b}catch(i){}}}})(jQuery);
* ================
*/
(function ($) {
 
var ns = '__jquery_xdomain__',
sc = 'SESSION_COOKIE_NAME';
 
if ($.browser.msie && 'XDomainRequest' in window && !(ns in $)) {
 
$[ns] = $.support.cors = true;
 
function parseUrl(url) {
if ($.type(url) === "object") {
return url;
}
var matches = /^(((([^:\/#\?]+:)?(?:\/\/((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?]+)(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/.exec(url);
return matches ? {
href:matches[0] || "",
hrefNoHash:matches[1] || "",
hrefNoSearch:matches[2] || "",
domain:matches[3] || "",
protocol:matches[4] || "",
authority:matches[5] || "",
username:matches[7] || "",
password:matches[8] || "",
host:matches[9] || "",
hostname:matches[10] || "",
port:matches[11] || "",
pathname:matches[12] || "",
directory:matches[13] || "",
filename:matches[14] || "",
search:matches[15] || "",
hash:matches[16] || ""
} : {};
}
 
var oldxhr = $.ajaxSettings.xhr,
sessionCookie = sc in window ? window[sc] : "jsessionid",
domain = parseUrl(document.location.href).domain;
 
$.ajaxSettings.xhr = function () {
var target = parseUrl(this.url).domain;
if (target === "" || target === domain) {
return oldxhr.call($.ajaxSettings)
} else {
try {
var xdr = new XDomainRequest();
if (!xdr.setRequestHeader) {
xdr.setRequestHeader = $.noop;
}
if (!xdr.getAllResponseHeaders) {
xdr.getAllResponseHeaders = $.noop;
}
if (sessionCookie) {
var open = xdr.open;
xdr.open = function () {
var cookie = new RegExp('(?:^|; )' + sessionCookie + '=([^;]*)', 'i').exec(document.cookie);
cookie = cookie && cookie[1];
if (cookie) {
var q = arguments[1].indexOf('?');
if (q == -1) {
arguments[1] += ';' + sessionCookie + '=' + cookie;
} else {
arguments[1] = arguments[1].substring(0, q) + ';' + sessionCookie + '=' + cookie + arguments[1].substring(q);
}
}
return open.apply(this, arguments);
};
}
xdr.onload = function () {
if (typeof xdr.onreadystatechange === 'function') {
xdr.readyState = 4;
xdr.status = 200;
xdr.onreadystatechange.call(xdr, null, false);
}
};
xdr.onerror = xdr.ontimeout = function () {
if (typeof xdr.onreadystatechange === 'function') {
xdr.readyState = 4;
xdr.status = 500;
xdr.onreadystatechange.call(xdr, null, false);
}
};
return xdr;
} catch (e) {
}
}
};
 
}
})(jQuery);

When using request like this "/api/local.php", parseDomain returns empty string instead of actual domain. So $.ajaxSettings.xhr actually thinks that this is cross-domain request beause domains don't match.

I propose following fix (line 93-95):

    $.ajaxSettings.xhr = function() {
        var domain = parseUrl(this.url).domain;
        return domain === "" || domain === parseUrl(document.location.href).domain ?

It did not work for me that you override "createStandardXHR". This is because my code is complicated, with a lot of custom headers and cookies. The solution that worked for me is quite easier:

    if(!$.ajaxSettings.oldXhr) {
        $.ajaxSettings.oldXhr = $.ajaxSettings.xhr;
    }

    if ($.browser.msie && window.XDomainRequest) {
        $.support.cors = true;
        $.ajaxSettings.xhr = function() {
            var domain = parseUrl(this.url).domain;
            return domain === "" || domain === parseUrl(document.location.href).domain ?
                $.ajaxSettings.oldXhr() :
                createXDR();
        };
    }

})(jQuery);

My change makes functions createStandardXHR and createActiveXHR redundant. Please let me know what you think of it?

Hi,

the JQuery documentation mentions a workaround for a Firefox bug, copied below. Does your solution include the workaround? Does it preclude it?

Thanks!

var _super = jQuery.ajaxSettings.xhr;
jQuery.ajaxSettings.xhr = function () {
var xhr = _super(),
getAllResponseHeaders = xhr.getAllResponseHeaders;

xhr.getAllResponseHeaders = function () {
    if ( getAllResponseHeaders() ) {
        return getAllResponseHeaders();
    }
    var allHeaders = "";
    $( ["Cache-Control", "Content-Language", "Content-Type",
            "Expires", "Last-Modified", "Pragma"] ).each(function (i, header_name) {

        if ( xhr.getResponseHeader( header_name ) ) {
            allHeaders += header_name + ": " + xhr.getResponseHeader( header_name ) + "\n";
        }
        return allHeaders;
    });
};
return xhr;

};

Hi,

Thank you all for your additions !

@warpech: you're right, I'll update the solution with your comment. It's better :-)
@yaronf: I think you should be able to still use it, but the xhr function you will wrap will be the one supporting IE Cors. But note that for IE XDomainRequest, this object does not support headers (set and get) at all...

thx @mathieucarbou. I subited my changes as a fork, maybe you will find it useful. I removed sessionCookie thing because I didn't find it useful in my usage scenario. Probably you still need it.

And by the way, the sessionCookie functionality breaks if the variable
is not defined by the user (tested on Chrome). Had to add:

 var sessionCookie = ((typeof SESSION_COOKIE_NAME == "undefined")? 

null: SESSION_COOKIE_NAME) || "jsessionid";

(there's probably a cleaner way).

On 12/15/2011 06:16 PM, Marcin Warpechowski wrote:

thx @mathieucarbou. I subited my changes as a fork, maybe you will find it useful. I removed sessionCookie thing because I didn't find it useful in my usage scenario. Probably you still need it.


Reply to this email directly or view it on GitHub:
https://gist.github.com/1114981

yes. for IE also I think it breaks... I am currently fixing it ;-)

Can you also integrate the Firefox fix, while we're at it?

On 12/15/2011 06:42 PM, Mathieu Carbou wrote:

yes. for IE also I think it breaks... I am currently fixing it ;-)


Reply to this email directly or view it on GitHub:
https://gist.github.com/1114981

Hum... not sure it should be included there: this is a xdomain support for IE. I don't think it's a good idea to include there a temporary patch for Firefox.

Yes, but many people use JQuery exactly for the portability that it
provides.

I can see your point, especially since the fix is orthogonal to your
code. However I haven't tested on FF yet :-)

On 12/15/2011 07:25 PM, Mathieu Carbou wrote:

Hum... not sure it should be included there: this is a xdomain support for IE. I don't think it's a good idea to include there a temporary patch for Firefox.


Reply to this email directly or view it on GitHub:
https://gist.github.com/1114981

We are using FF a lot in our development and as a client platform and for the moment we didn't need this patch. Whereas we need the xdomain support for IE since some clients are still sadly on IE...

I can confirm that the new version (https://gist.github.com/1114981/c396670e5d9d53fa1bae6e8cff2f3c3f9583b248) works on IE. I have improved its memory footprint and also avoided parsing the location.href each time an ajax request is made.

Please see the following blog post:

http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e

I believe you may want to add the following two methods (even if the function they call is blank), to work around the issue reported in the blog post. For example, around line 88, add in the following:

xdr.onprogress = function() {};
xdr.ontimeout = function() {};

Of course you may want to put something useful in the functions, but (at minimum) define them.

Thank you !
I will uplaod a new version of this plugin soon, which support cookies, to be able to send and receive cookies through xdomain requests

One additional note, on our main website we still have 10% of our users on IE7, which doesn't have XDomainRequest. I don't want to have JSONP copies of my JSON just for those 10%. However, I found another project on github.com that would fix things for IE7 users (requires Flash, but hey, that's what you get for using that old of a browser). Perhaps merging it with your extension would make for one nice and powerful CORS extension for jQuery as I am sure others also still have to support IE7. The project in question is the following:

https://github.com/borisreitman/CrossXHR

If you feel it would be practical to do this merge/addition, just let me know and I will see if I can make some time to make a clean merge. Of course, if you like this idea and have time to make the change before I do, I won't be offended :-) Thanks again for your nice plugin.

At first I thought this might work for us to add CORS support in IE8+, but I don't think it won't work for my situation. We are using an HttpOnly "rememberMe" cookie for authorization. Because it is an HttpOnly cookie, the browser can't read the cookie, so there would be no way for the JS to rewrite the cookie into the request.

Any ideas? For our project, using HttpOnly cookies is a requirement that we cannot change. Looks like we're going to have to go with EasyXDM or something similar.

Yes of course this is the drawback... HttpOnly cannot be used :-(

Oups! Correction: it works ! We are both using this in 2 projects and yes it works. The reason is that with CORS on IE, since you cannot transport cookies in headers, they can be passed through the request parameters. Thus accessible on server-side (our remember-me implementation first check in request cookies then in request param to see if the rememberMe key is there).
Then if a remember-me cookie is changed or set on server-side, since IE CORS cannot sent the Set-Cookie header, the header is sent within the request body. This is done with our Java Cors Filter which enhances the request body and supports Gzip. So on clients-side, if some cookies are found in an enhanced request body, cookies are written with javascript and thus get back in javascript for next request.
So HttpOnly is clearly ignored for IE CORS to be able to keep sessions active and recover them.

NB: you should check https://github.com/Ovea/cors. It is deployed in Maven repository.

Thanks for the information, that is pretty slick. And I have been checking out the Ovea/cors project.

But I'm still not sure how it would solve our problem. We have a static html page. When the page loads, an AJAX call is made to get the current user information. So assume that the user logged in during a previous session and got a rememberMe cookie. Then another day, in a new session, he loads the page again and an AJAX call is made to load the current user. How would that ajax call pass the rememberMe cookie to the server?

Our plugin does it automatically by getting the cookies previously registered for this domain.

Hi,
I'm trying to set a cookie using your ie CORS solution but for some reason the cookie doesn't seem to be set in IE9.
I am using the setcookie PHP function to set the cookie.
Can you advise?

I don't get how this is supposed to work. Will it make the $.ajax() method magically support CORS on IE9 (by using the XDomainRequest)?

Because, when I add it into my codebase (by including the JavaScript after the jQuery JavaScript), IE9 CORS still does not work. I get a "Security error" alert.

Do I also need to tinker with the Access-Control-Allow-Origin type headers that the remote server returns?

I'm having some issues. When the "resp" variable is created, my array comes back with an empty string for the header. I see that the method parseResponse is returning my empty header, but I have no idea why it's empty. Does the parseResponse method look for JSON formatted data? I'm currently using text/html when I do my GET. Any help would be great. Thanks for the plugin!

This plugin is exactly what I am looking for to upload content to S3 on IE8/IE9.
But I am receiving an error from S3 "The specified method is not allowed against this resource" - the method is POST.
The error occurs on the first request to S3 (after I correctly receive the json data from my backend).
Any suggestions?

I am having the same problem as phornby mention two posts above. Any ideas?

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.