aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xangularFiles.js1
-rwxr-xr-xsrc/AngularPublic.js3
-rw-r--r--src/ng/compile.js25
-rw-r--r--src/ng/http.js43
-rw-r--r--src/ng/urlUtils.js111
-rw-r--r--test/ng/httpSpec.js21
-rw-r--r--test/ng/urlUtilsSpec.js31
7 files changed, 160 insertions, 75 deletions
diff --git a/angularFiles.js b/angularFiles.js
index a9d636b1..b93283a7 100755
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -30,6 +30,7 @@ angularFiles = {
'src/ng/httpBackend.js',
'src/ng/locale.js',
'src/ng/timeout.js',
+ 'src/ng/urlUtils.js',
'src/ng/filter.js',
'src/ng/filter/filter.js',
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index 03d465ff..99f6cdbd 100755
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -125,7 +125,8 @@ function publishExternalAPI(angular){
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
$timeout: $TimeoutProvider,
- $window: $WindowProvider
+ $window: $WindowProvider,
+ $$urlUtils: $$UrlUtilsProvider
});
}
]);
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 7d2b6dc7..46ebe71a 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -274,9 +274,9 @@ function $CompileProvider($provide) {
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
- '$controller', '$rootScope', '$document',
+ '$controller', '$rootScope', '$document', '$$urlUtils',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
- $controller, $rootScope, $document) {
+ $controller, $rootScope, $document, $$urlUtils) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -319,24 +319,23 @@ function $CompileProvider($provide) {
}
}
+ nodeName = nodeName_(this.$$element);
// sanitize a[href] and img[src] values
- nodeName = nodeName_(this.$$element);
if ((nodeName === 'A' && key === 'href') ||
- (nodeName === 'IMG' && key === 'src')){
- urlSanitizationNode.setAttribute('href', value);
-
- // href property always returns normalized absolute url, so we can match against that
- normalizedVal = urlSanitizationNode.href;
- if (normalizedVal !== '') {
- if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
- (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
- this[key] = value = 'unsafe:' + normalizedVal;
+ (nodeName === 'IMG' && key === 'src')) {
+ // NOTE: $$urlUtils.resolve() doesn't support IE < 8 so we don't sanitize for that case.
+ if (!msie || msie >= 8 ) {
+ normalizedVal = $$urlUtils.resolve(value);
+ if (normalizedVal !== '') {
+ if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
+ (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
+ this[key] = value = 'unsafe:' + normalizedVal;
+ }
}
}
}
-
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
diff --git a/src/ng/http.js b/src/ng/http.js
index a44da3a4..2aedeacb 100644
--- a/src/ng/http.js
+++ b/src/ng/http.js
@@ -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.
*
@@ -168,8 +131,8 @@ function $HttpProvider() {
*/
var responseInterceptorFactories = this.responseInterceptors = [];
- this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
- function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
+ this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', '$$urlUtils',
+ function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector, $$urlUtils) {
var defaultCache = $cacheFactory('$http');
@@ -657,7 +620,7 @@ function $HttpProvider() {
config.headers = headers;
config.method = uppercase(config.method);
- var xsrfValue = isSameDomain(config.url, $browser.url())
+ var xsrfValue = $$urlUtils.isSameOrigin(config.url)
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
diff --git a/src/ng/urlUtils.js b/src/ng/urlUtils.js
new file mode 100644
index 00000000..e19f9860
--- /dev/null
+++ b/src/ng/urlUtils.js
@@ -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);
+ }
+ };
+ }];
+}
diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js
index cefc88e1..ec1cb7f1 100644
--- a/test/ng/httpSpec.js
+++ b/test/ng/httpSpec.js
@@ -1476,25 +1476,4 @@ 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);
- });
- });
});
diff --git a/test/ng/urlUtilsSpec.js b/test/ng/urlUtilsSpec.js
new file mode 100644
index 00000000..57043a5a
--- /dev/null
+++ b/test/ng/urlUtilsSpec.js
@@ -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);
+ }));
+ });
+});