mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-29 05:15:38 +08:00
fix(core): parse URLs using the browser's DOM API
This commit is contained in:
1
angularFiles.js
vendored
1
angularFiles.js
vendored
@@ -30,6 +30,7 @@ angularFiles = {
|
|||||||
'src/ng/httpBackend.js',
|
'src/ng/httpBackend.js',
|
||||||
'src/ng/locale.js',
|
'src/ng/locale.js',
|
||||||
'src/ng/timeout.js',
|
'src/ng/timeout.js',
|
||||||
|
'src/ng/urlUtils.js',
|
||||||
|
|
||||||
'src/ng/filter.js',
|
'src/ng/filter.js',
|
||||||
'src/ng/filter/filter.js',
|
'src/ng/filter/filter.js',
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ function publishExternalAPI(angular){
|
|||||||
$sniffer: $SnifferProvider,
|
$sniffer: $SnifferProvider,
|
||||||
$templateCache: $TemplateCacheProvider,
|
$templateCache: $TemplateCacheProvider,
|
||||||
$timeout: $TimeoutProvider,
|
$timeout: $TimeoutProvider,
|
||||||
$window: $WindowProvider
|
$window: $WindowProvider,
|
||||||
|
$$urlUtils: $$UrlUtilsProvider
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -274,9 +274,9 @@ function $CompileProvider($provide) {
|
|||||||
|
|
||||||
this.$get = [
|
this.$get = [
|
||||||
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
|
||||||
'$controller', '$rootScope', '$document',
|
'$controller', '$rootScope', '$document', '$$urlUtils',
|
||||||
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
|
||||||
$controller, $rootScope, $document) {
|
$controller, $rootScope, $document, $$urlUtils) {
|
||||||
|
|
||||||
var Attributes = function(element, attr) {
|
var Attributes = function(element, attr) {
|
||||||
this.$$element = element;
|
this.$$element = element;
|
||||||
@@ -319,24 +319,23 @@ function $CompileProvider($provide) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeName = nodeName_(this.$$element);
|
||||||
|
|
||||||
// sanitize a[href] and img[src] values
|
// sanitize a[href] and img[src] values
|
||||||
nodeName = nodeName_(this.$$element);
|
|
||||||
if ((nodeName === 'A' && key === 'href') ||
|
if ((nodeName === 'A' && key === 'href') ||
|
||||||
(nodeName === 'IMG' && key === 'src')){
|
(nodeName === 'IMG' && key === 'src')) {
|
||||||
urlSanitizationNode.setAttribute('href', value);
|
// NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
|
||||||
|
if (!msie || msie >= 8 ) {
|
||||||
// href property always returns normalized absolute url, so we can match against that
|
normalizedVal = $$urlUtils.resolve(value);
|
||||||
normalizedVal = urlSanitizationNode.href;
|
if (normalizedVal !== '') {
|
||||||
if (normalizedVal !== '') {
|
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
|
||||||
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
|
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
|
||||||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
|
this[key] = value = 'unsafe:' + normalizedVal;
|
||||||
this[key] = value = 'unsafe:' + normalizedVal;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (writeAttr !== false) {
|
if (writeAttr !== false) {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
this.$$element.removeAttr(attrName);
|
this.$$element.removeAttr(attrName);
|
||||||
|
|||||||
@@ -29,43 +29,6 @@ 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 = SERVER_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.
|
* Returns a function that provides access to parsed headers.
|
||||||
*
|
*
|
||||||
@@ -168,8 +131,8 @@ function $HttpProvider() {
|
|||||||
*/
|
*/
|
||||||
var responseInterceptorFactories = this.responseInterceptors = [];
|
var responseInterceptorFactories = this.responseInterceptors = [];
|
||||||
|
|
||||||
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
|
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', '$$urlUtils',
|
||||||
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
|
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector, $$urlUtils) {
|
||||||
|
|
||||||
var defaultCache = $cacheFactory('$http');
|
var defaultCache = $cacheFactory('$http');
|
||||||
|
|
||||||
@@ -657,7 +620,7 @@ function $HttpProvider() {
|
|||||||
config.headers = headers;
|
config.headers = headers;
|
||||||
config.method = uppercase(config.method);
|
config.method = uppercase(config.method);
|
||||||
|
|
||||||
var xsrfValue = isSameDomain(config.url, $browser.url())
|
var xsrfValue = $$urlUtils.isSameOrigin(config.url)
|
||||||
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
|
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
|
||||||
: undefined;
|
: undefined;
|
||||||
if (xsrfValue) {
|
if (xsrfValue) {
|
||||||
|
|||||||
111
src/ng/urlUtils.js
Normal file
111
src/ng/urlUtils.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
function $$UrlUtilsProvider() {
|
||||||
|
this.$get = ['$window', '$document', function($window, $document) {
|
||||||
|
var urlParsingNode = $document[0].createElement("a"),
|
||||||
|
originUrl = resolve($window.location.href, true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* Normalizes and optionally parses a URL.
|
||||||
|
*
|
||||||
|
* NOTE: This is a private service. The API is subject to change unpredictably in any commit.
|
||||||
|
*
|
||||||
|
* Implementation Notes for non-IE browsers
|
||||||
|
* ----------------------------------------
|
||||||
|
* Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM,
|
||||||
|
* results both in the normalizing and parsing of the URL. Normalizing means that a relative
|
||||||
|
* URL will be resolved into an absolute URL in the context of the application document.
|
||||||
|
* Parsing means that the anchor node's host, hostname, protocol, port, pathname and related
|
||||||
|
* properties are all populated to reflect the normalized URL. This approach has wide
|
||||||
|
* compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See
|
||||||
|
* http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
|
||||||
|
*
|
||||||
|
* Implementation Notes for IE
|
||||||
|
* ---------------------------
|
||||||
|
* IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other
|
||||||
|
* browsers. However, the parsed components will not be set if the URL assigned did not specify
|
||||||
|
* them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
|
||||||
|
* work around that by performing the parsing in a 2nd step by taking a previously normalized
|
||||||
|
* URL (e.g. by assining to a.href) and assigning it a.href again. This correctly populates the
|
||||||
|
* properties such as protocol, hostname, port, etc.
|
||||||
|
*
|
||||||
|
* IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one
|
||||||
|
* uses the inner HTML approach to assign the URL as part of an HTML snippet -
|
||||||
|
* http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL.
|
||||||
|
* Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception.
|
||||||
|
* Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that
|
||||||
|
* method and IE < 8 is unsupported.
|
||||||
|
*
|
||||||
|
* References:
|
||||||
|
* http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
|
||||||
|
* http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
|
||||||
|
* http://url.spec.whatwg.org/#urlutils
|
||||||
|
* https://github.com/angular/angular.js/pull/2902
|
||||||
|
* http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
|
||||||
|
*
|
||||||
|
* @param {string} url The URL to be parsed.
|
||||||
|
* @param {boolean=} parse When true, returns an object for the parsed URL. Otherwise, returns
|
||||||
|
* a single string that is the normalized URL.
|
||||||
|
* @returns {object|string} When parse is true, returns the normalized URL as a string.
|
||||||
|
* Otherwise, returns an object with the following members.
|
||||||
|
*
|
||||||
|
* | member name | Description |
|
||||||
|
* |===============|================|
|
||||||
|
* | href | A normalized version of the provided URL if it was not an absolute URL |
|
||||||
|
* | protocol | The protocol including the trailing colon |
|
||||||
|
* | host | The host and port (if the port is non-default) of the normalizedUrl |
|
||||||
|
*
|
||||||
|
* These fields from the UrlUtils interface are currently not needed and hence not returned.
|
||||||
|
*
|
||||||
|
* | member name | Description |
|
||||||
|
* |===============|================|
|
||||||
|
* | hostname | The host without the port of the normalizedUrl |
|
||||||
|
* | pathname | The path following the host in the normalizedUrl |
|
||||||
|
* | hash | The URL hash if present |
|
||||||
|
* | search | The query string |
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function resolve(url, parse) {
|
||||||
|
var href = url;
|
||||||
|
if (msie) {
|
||||||
|
// Normalize before parse. Refer Implementation Notes on why this is
|
||||||
|
// done in two steps on IE.
|
||||||
|
urlParsingNode.setAttribute("href", href);
|
||||||
|
href = urlParsingNode.href;
|
||||||
|
}
|
||||||
|
urlParsingNode.setAttribute('href', href);
|
||||||
|
|
||||||
|
if (!parse) {
|
||||||
|
return urlParsingNode.href;
|
||||||
|
}
|
||||||
|
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
|
||||||
|
return {
|
||||||
|
href: urlParsingNode.href,
|
||||||
|
protocol: urlParsingNode.protocol,
|
||||||
|
host: urlParsingNode.host
|
||||||
|
// Currently unused and hence commented out.
|
||||||
|
// hostname: urlParsingNode.hostname,
|
||||||
|
// port: urlParsingNode.port,
|
||||||
|
// pathname: urlParsingNode.pathname,
|
||||||
|
// hash: urlParsingNode.hash,
|
||||||
|
// search: urlParsingNode.search
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resolve: resolve,
|
||||||
|
/**
|
||||||
|
* Parse a request URL and determine whether this is a same-origin request as the application document.
|
||||||
|
*
|
||||||
|
* @param {string} requestUrl The url of the request.
|
||||||
|
* @returns {boolean} Whether the request is for the same origin as the application document.
|
||||||
|
*/
|
||||||
|
isSameOrigin: function isSameOrigin(requestUrl) {
|
||||||
|
var parsed = resolve(requestUrl, true);
|
||||||
|
return (parsed.protocol === originUrl.protocol &&
|
||||||
|
parsed.host === originUrl.host);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}
|
||||||
@@ -1476,25 +1476,4 @@ describe('$http', function() {
|
|||||||
|
|
||||||
$httpBackend.verifyNoOutstandingExpectation = noop;
|
$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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
31
test/ng/urlUtilsSpec.js
Normal file
31
test/ng/urlUtilsSpec.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('$$urlUtils', function() {
|
||||||
|
describe('parse', function() {
|
||||||
|
it('should normalize a relative url', inject(function($$urlUtils) {
|
||||||
|
expect($$urlUtils.resolve("foo")).toMatch(/^https?:\/\/[^/]+\/foo$/);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should parse relative URL into component pieces', inject(function($$urlUtils) {
|
||||||
|
var parsed = $$urlUtils.resolve("foo", true);
|
||||||
|
expect(parsed.href).toMatch(/https?:\/\//);
|
||||||
|
expect(parsed.protocol).toMatch(/^https?:/);
|
||||||
|
expect(parsed.host).not.toBe("");
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isSameOrigin', function() {
|
||||||
|
it('should support various combinations of urls', inject(function($$urlUtils, $document) {
|
||||||
|
expect($$urlUtils.isSameOrigin('path')).toBe(true);
|
||||||
|
var origin = $$urlUtils.resolve($document[0].location.href, true);
|
||||||
|
expect($$urlUtils.isSameOrigin('//' + origin.host + '/path')).toBe(true);
|
||||||
|
// Different domain.
|
||||||
|
expect($$urlUtils.isSameOrigin('http://example.com/path')).toBe(false);
|
||||||
|
// Auto fill protocol.
|
||||||
|
expect($$urlUtils.isSameOrigin('//example.com/path')).toBe(false);
|
||||||
|
// Should not match when the ports are different.
|
||||||
|
// This assumes that the test is *not* running on port 22 (very unlikely).
|
||||||
|
expect($$urlUtils.isSameOrigin('//' + origin.hostname + ':22/path')).toBe(false);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user