Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix($http): only set X-XSFR-TOKEN header for same-domain request
Browse files Browse the repository at this point in the history
This is needed to prevent CORS preflight checks. The XSFR token
is quite useless for CORS requests anyway.

BREAKING CHANGE: X-XSFR-TOKEN is no longer send for cross domain
requests. This shouldn't affect any known production service.

Closes #1096
  • Loading branch information
rkirov authored and IgorMinar committed Nov 26, 2012
1 parent 3a75b11 commit fce100a
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 2 deletions.
43 changes: 41 additions & 2 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,43 @@ function parseHeaders(headers) {
}


var IS_SAME_DOMAIN_URL_MATCH = /^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/;


/**
* Parse a request and location URL and determine whether this is a same-domain request.
*
* @param {string} requestUrl The url of the request.
* @param {string} locationUrl The current browser location url.
* @returns {boolean} Whether the request is for the same domain.
*/
function isSameDomain(requestUrl, locationUrl) {
var match = IS_SAME_DOMAIN_URL_MATCH.exec(requestUrl);
// if requestUrl is relative, the regex does not match.
if (match == null) return true;

var domain1 = {
protocol: match[2],
host: match[4],
port: int(match[6]) || DEFAULT_PORTS[match[2]] || null,
// IE8 sets unmatched groups to '' instead of undefined.
relativeProtocol: match[2] === undefined || match[2] === ''
};

match = URL_MATCH.exec(locationUrl);
var domain2 = {
protocol: match[1],
host: match[3],
port: int(match[5]) || DEFAULT_PORTS[match[1]] || null
};

return (domain1.protocol == domain2.protocol || domain1.relativeProtocol) &&
domain1.host == domain2.host &&
(domain1.port == domain2.port || (domain1.relativeProtocol &&
domain2.port == DEFAULT_PORTS[domain2.protocol]));
}


/**
* Returns a function that provides access to parsed headers.
*
Expand Down Expand Up @@ -345,7 +382,7 @@ function $HttpProvider() {
* to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
* called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
* runs on your domain could read the cookie, your server can be assured that the XHR came from
* JavaScript running on your domain.
* JavaScript running on your domain. The header will not be set for cross-domain requests.
*
* To take advantage of this, your server needs to set a token in a JavaScript readable session
* cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
Expand Down Expand Up @@ -476,7 +513,9 @@ function $HttpProvider() {
var reqTransformFn = config.transformRequest || defaults.transformRequest,
respTransformFn = config.transformResponse || defaults.transformResponse,
defHeaders = defaults.headers,
reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
xsrfToken = isSameDomain(config.url, $browser.url()) ?
$browser.cookies()['XSRF-TOKEN'] : undefined,
reqHeaders = extend({'X-XSRF-TOKEN': xsrfToken},
defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
promise;
Expand Down
32 changes: 32 additions & 0 deletions test/ng/httpSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,17 @@ describe('$http', function() {
$httpBackend.flush();
});

it('should not set XSRF cookie for cross-domain requests', inject(function($browser) {
$browser.cookies('XSRF-TOKEN', 'secret');
$browser.url('http://host.com/base');
$httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) {
return headers['X-XSRF-TOKEN'] === undefined;
}).respond('');

$http({url: 'http://www.test.com/url', method: 'GET', headers: {}});
$httpBackend.flush();
}));


it('should not send Content-Type header if request data/body is undefined', function() {
$httpBackend.expect('POST', '/url', undefined, function(headers) {
Expand Down Expand Up @@ -1005,4 +1016,25 @@ describe('$http', function() {

$httpBackend.verifyNoOutstandingExpectation = noop;
});

describe('isSameDomain', function() {
it('should support various combinations of urls', function() {
expect(isSameDomain('path/morepath',
'http://www.adomain.com')).toBe(true);
expect(isSameDomain('http://www.adomain.com/path',
'http://www.adomain.com')).toBe(true);
expect(isSameDomain('//www.adomain.com/path',
'http://www.adomain.com')).toBe(true);
expect(isSameDomain('//www.adomain.com/path',
'https://www.adomain.com')).toBe(true);
expect(isSameDomain('//www.adomain.com/path',
'http://www.adomain.com:1234')).toBe(false);
expect(isSameDomain('https://www.adomain.com/path',
'http://www.adomain.com')).toBe(false);
expect(isSameDomain('http://www.adomain.com:1234/path',
'http://www.adomain.com')).toBe(false);
expect(isSameDomain('http://www.anotherdomain.com/path',
'http://www.adomain.com')).toBe(false);
});
});
});

0 comments on commit fce100a

Please sign in to comment.