diff --git a/packages/platform-server/src/location.ts b/packages/platform-server/src/location.ts
index bba5d6ae78d..5e7c00908c1 100644
--- a/packages/platform-server/src/location.ts
+++ b/packages/platform-server/src/location.ts
@@ -9,19 +9,45 @@
import {DOCUMENT, LocationChangeEvent, LocationChangeListener, PlatformLocation, ɵgetDOM as getDOM} from '@angular/common';
import {Inject, Injectable, Optional, ɵWritable as Writable} from '@angular/core';
import {Subject} from 'rxjs';
-import * as url from 'url';
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
-function parseUrl(urlStr: string) {
- const parsedUrl = url.parse(urlStr);
+const RESOLVE_PROTOCOL = 'resolve:';
+
+function parseUrl(urlStr: string): {
+ hostname: string,
+ protocol: string,
+ port: string,
+ pathname: string,
+ search: string,
+ hash: string,
+} {
+ let {hostname, protocol, port, pathname, search, hash} = new URL(urlStr, RESOLVE_PROTOCOL + '//');
+
+ /**
+ * TODO(alanagius): Remove the below in version 18.
+ * The following are done to maintain the same behaviour as `url.parse`.
+ * The main differences are;
+ * - `pathname` is always suffixed with a `/`.
+ * - `port` is empty when `http:` protocol and port in url is `80`
+ * - `port` is empty when `https:` protocol and port in url is `443`
+ */
+ if (protocol !== RESOLVE_PROTOCOL && port === '' && /\:(80|443)/.test(urlStr)) {
+ port = protocol === 'http:' ? '80' : '443';
+ }
+
+ if (protocol === RESOLVE_PROTOCOL && urlStr.charAt(0) !== '/') {
+ pathname = pathname.slice(1); // Remove leading slash.
+ }
+ // END TODO
+
return {
- hostname: parsedUrl.hostname || '',
- protocol: parsedUrl.protocol || '',
- port: parsedUrl.port || '',
- pathname: parsedUrl.pathname || '',
- search: parsedUrl.search || '',
- hash: parsedUrl.hash || '',
+ hostname,
+ protocol: protocol === RESOLVE_PROTOCOL ? '' : protocol,
+ port,
+ pathname,
+ search,
+ hash,
};
}
diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts
index 06087cdc4bd..2e0e75a4c4f 100644
--- a/packages/platform-server/test/integration_spec.ts
+++ b/packages/platform-server/test/integration_spec.ts
@@ -707,6 +707,58 @@ describe('platform-server integration', () => {
location.pushState(null, 'Test', '/foo#bar');
});
});
+
+ describe('Legacy (Remove once url normalization is removed)', () => {
+ it('parses and returns pathname without a slash', async () => {
+ const platform = platformServer([{
+ provide: INITIAL_CONFIG,
+ useValue: {document: '', url: 'deep/path?query#hash'}
+ }]);
+
+ const appRef = await platform.bootstrapModule(ExampleModule);
+
+ const location = appRef.injector.get(PlatformLocation);
+ expect(location.hostname).toBe('');
+ expect(location.protocol).toBe('');
+ expect(location.port).toBe('');
+ expect(location.pathname).toBe('deep/path');
+ expect(location.search).toBe('?query');
+ expect(location.hash).toBe('#hash');
+ });
+ it('port is set to 443 when using https protocol', async () => {
+ const platform = platformServer([{
+ provide: INITIAL_CONFIG,
+ useValue: {document: '', url: 'https://test.com:443/deep/path?query#hash'}
+ }]);
+
+ const appRef = await platform.bootstrapModule(ExampleModule);
+
+ const location = appRef.injector.get(PlatformLocation);
+ expect(location.hostname).toBe('test.com');
+ expect(location.protocol).toBe('https:');
+ expect(location.port).toBe('443');
+ expect(location.pathname).toBe('/deep/path');
+ expect(location.search).toBe('?query');
+ expect(location.hash).toBe('#hash');
+ });
+
+ it('port is set to 80 when using http protocol', async () => {
+ const platform = platformServer([{
+ provide: INITIAL_CONFIG,
+ useValue: {document: '', url: 'http://test.com:80/deep/path?query#hash'}
+ }]);
+
+ const appRef = await platform.bootstrapModule(ExampleModule);
+
+ const location = appRef.injector.get(PlatformLocation);
+ expect(location.hostname).toBe('test.com');
+ expect(location.protocol).toBe('http:');
+ expect(location.port).toBe('80');
+ expect(location.pathname).toBe('/deep/path');
+ expect(location.search).toBe('?query');
+ expect(location.hash).toBe('#hash');
+ });
+ });
});
describe('render', () => {