diff --git a/src/ng/urlUtils.js b/src/ng/urlUtils.js index e821c08a3e9f..57880542191c 100644 --- a/src/ng/urlUtils.js +++ b/src/ng/urlUtils.js @@ -7,8 +7,14 @@ // exactly the behavior needed here. There is little value is mocking these out for this // service. var urlParsingNode = document.createElement("a"); +/* +Matches paths for file protocol on windows, +such as /C:/foo/bar, and captures only /foo/bar. +*/ +var windowsFilePathExp = /^\/?.*?:(\/.*)/; var originUrl = urlResolve(window.location.href, true); + /** * * Implementation Notes for non-IE browsers @@ -27,7 +33,7 @@ var originUrl = urlResolve(window.location.href, true); * 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 + * URL (e.g. by assigning 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 @@ -62,7 +68,9 @@ var originUrl = urlResolve(window.location.href, true); * */ function urlResolve(url) { - var href = url; + var href = url, + pathname; + if (msie) { // Normalize before parse. Refer Implementation Notes on why this is // done in two steps on IE. @@ -72,7 +80,34 @@ function urlResolve(url) { urlParsingNode.setAttribute('href', href); - // $$urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + // ":"" protocol occurs on IE/Win for file:// + /* jeffbcross and tbosch say: + Per section 3.3 of http://www.ietf.org/rfc/rfc3986.txt, + the first path segment of a relative path reference + cannot contain a colon, so it is right to assume that + if the first section of the path contains a colon, it + is invalid. + + However, to be safe, this check is only obliberating + the first segment if the LAST character of the segment + is a colon. + + In Windows, on an anchor node on documents loaded from + the filesystem, the browser will return a pathname + prefixed with the drive name ('/C:/path') when a + pathname without a drive is set: + * a.setAttribute('href', '/foo') + * a.pathname === '/C:/foo' //true + + Inside of Angular, we're always using pathnames that + do not include drive names for routing. + */ + + pathname = removeWindowsDriveName(urlParsingNode.pathname); + pathname = (pathname.charAt(0) === '/') ? pathname : '/' + pathname; + + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils return { href: urlParsingNode.href, protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', @@ -81,9 +116,15 @@ function urlResolve(url) { hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', hostname: urlParsingNode.hostname, port: urlParsingNode.port, - pathname: urlParsingNode.pathname && urlParsingNode.pathname.charAt(0) === '/' ? - urlParsingNode.pathname : '/' + urlParsingNode.pathname + pathname: pathname }; + + function removeWindowsDriveName (path) { + var firstPathSegmentMatch; + + firstPathSegmentMatch = windowsFilePathExp.exec(path); + return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; + } } diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index 30f2f81aa0a4..7fdd5cff62dd 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -10,6 +10,49 @@ describe('$location', function() { jqLite(document).off('click'); }); + + describe('File Protocol', function () { + var urlParsingNodePlaceholder; + + beforeEach(inject(function ($sniffer) { + if ($sniffer.msie) return; + + urlParsingNodePlaceholder = urlParsingNode; + + //temporarily overriding the DOM element + //with output from IE, if not in IE + urlParsingNode = { + hash : "#/C:/", + host : "", + hostname : "", + href : "file:///C:/base#!/C:/foo", + pathname : "/C:/foo", + port : "", + protocol : "file:", + search : "", + setAttribute: angular.noop + }; + })); + + + afterEach(inject(function ($sniffer) { + if ($sniffer.msie) return; + //reset urlParsingNode + urlParsingNode = urlParsingNodePlaceholder; + expect(urlParsingNode.pathname).not.toBe('/C:/foo'); + })); + + + it('should not spill over to path() on WIN', function (){ + //See issue #4680 for details + url = new LocationHashbangUrl('file:///base', '#!'); + url.$$parse('file:///base#!/foo?a=b&c#hash'); + + expect(url.path()).toBe('/foo'); + }); + }); + + describe('NewUrl', function() { beforeEach(function() { url = new LocationHtml5Url('http://www.domain.com:9877/');