fix(platform-server): Resolve absolute URL from baseUrl (#39334)

This commit fixes a bug when `useAbsoluteUrl` is set to true and
`ServerPlatformLocation` infers the base url from the supplied
`url`. User should explicitly set the `baseUrl` when they turn on
`useAbsoluteUrl`.

Breaking change:
If you use `useAbsoluteUrl` to setup `platform-server`, you now need to
also specify `baseUrl`.
We are intentionally making this a breaking change in a minor release,
because if `useAbsoluteUrl` is set to `true` then the behavior of the
application could be unpredictable, resulting in issues that are hard to
discover but could be affecting production environments.

PR Close #39334
This commit is contained in:
Keen Yee Liau 2020-10-19 15:23:37 -07:00 committed by Andrew Kushnir
parent d33eaa64a2
commit 7768aeb62f
4 changed files with 119 additions and 24 deletions

View File

@ -3,6 +3,7 @@ export declare const BEFORE_APP_SERIALIZED: InjectionToken<(() => void | Promise
export declare const INITIAL_CONFIG: InjectionToken<PlatformConfig>;
export declare interface PlatformConfig {
baseUrl?: string;
document?: string;
url?: string;
useAbsoluteUrl?: boolean;

View File

@ -12,7 +12,6 @@ import {Subject} from 'rxjs';
import * as url from 'url';
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
function parseUrl(urlStr: string) {
const parsedUrl = url.parse(urlStr);
return {
@ -43,16 +42,28 @@ export class ServerPlatformLocation implements PlatformLocation {
constructor(
@Inject(DOCUMENT) private _doc: any, @Optional() @Inject(INITIAL_CONFIG) _config: any) {
const config = _config as PlatformConfig | null;
if (!!config && !!config.url) {
const parsedUrl = parseUrl(config.url);
this.hostname = parsedUrl.hostname;
this.protocol = parsedUrl.protocol;
this.port = parsedUrl.port;
this.pathname = parsedUrl.pathname;
this.search = parsedUrl.search;
this.hash = parsedUrl.hash;
if (!config) {
return;
}
if (config.url) {
const url = parseUrl(config.url);
this.protocol = url.protocol;
this.hostname = url.hostname;
this.port = url.port;
this.pathname = url.pathname;
this.search = url.search;
this.hash = url.hash;
this.href = _doc.location.href;
}
if (config.useAbsoluteUrl) {
if (!config.baseUrl) {
throw new Error(`"PlatformConfig.baseUrl" must be set if "useAbsoluteUrl" is true`);
}
const url = parseUrl(config.baseUrl);
this.protocol = url.protocol;
this.hostname = url.hostname;
this.port = url.port;
}
}
getBaseHrefFromDOM(): string {

View File

@ -20,19 +20,26 @@ export interface PlatformConfig {
*/
document?: string;
/**
* The URL for the current application state. This is
* used for initializing the platform's location and
* for setting absolute URL resolution for HTTP requests.
* The URL for the current application state. This is used for initializing
* the platform's location. `protocol`, `hostname`, and `port` will be
* overridden if `baseUrl` is set.
* @default none
*/
url?: string;
/**
* Whether to append the absolute URL to any relative HTTP
* requests. If set to true, this logic executes prior to
* any HTTP interceptors that may run later on in the request.
* Whether to append the absolute URL to any relative HTTP requests. If set to
* true, this logic executes prior to any HTTP interceptors that may run later
* on in the request. `baseUrl` must be supplied if this flag is enabled.
* @default false
*/
useAbsoluteUrl?: boolean;
/**
* The base URL for resolving absolute URL for HTTP requests. It must be set
* if `useAbsoluteUrl` is true, and must consist of protocol, hostname,
* and optional port. This option has no effect if `useAbsoluteUrl` is not
* enabled.
*/
baseUrl?: string;
}
/**

View File

@ -794,10 +794,62 @@ describe('platform-server integration', () => {
}));
describe('relative requests', () => {
it('will throw if "useAbsoluteUrl" is true but "baseUrl" is not provided', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {
document: '<app></app>',
url: 'http://localhost',
useAbsoluteUrl: true,
},
}]);
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
expect(() => appRef.injector.get(PlatformLocation))
.toThrowError(/"PlatformConfig\.baseUrl" must be set if "useAbsoluteUrl" is true/);
});
it('will resolve absolute url using "baseUrl"', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {
document: '<app></app>',
url: 'http://localhost',
useAbsoluteUrl: true,
baseUrl: 'https://angular.io:8080',
},
}]);
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
const location = appRef.injector.get(PlatformLocation);
expect(location.protocol).toBe('https:');
expect(location.hostname).toBe('angular.io');
expect(location.port).toBe('8080');
});
it('"baseUrl" has no effect if "useAbsoluteUrl" is not enabled', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {
document: '<app></app>',
url: 'http://localhost',
baseUrl: 'https://angular.io:8080',
},
}]);
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
const location = appRef.injector.get(PlatformLocation);
expect(location.protocol).toBe('http:');
expect(location.hostname).toBe('localhost');
expect(location.port).toBe('');
});
it('correctly maps to absolute URL request with base config', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: true}
useValue: {
document: '<app></app>',
url: 'http://localhost',
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@ -831,7 +883,12 @@ describe('platform-server integration', () => {
it('correctly maps to absolute URL request with port', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<app></app>', url: 'http://localhost:5000', useAbsoluteUrl: true}
useValue: {
document: '<app></app>',
url: 'http://localhost:5000',
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@ -848,7 +905,12 @@ describe('platform-server integration', () => {
it('correctly maps to absolute URL request with two slashes', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<app></app>', url: 'http://localhost/', useAbsoluteUrl: true}
useValue: {
document: '<app></app>',
url: 'http://localhost/',
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@ -865,7 +927,12 @@ describe('platform-server integration', () => {
it('correctly maps to absolute URL request with no slashes', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: true}
useValue: {
document: '<app></app>',
url: 'http://localhost',
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@ -882,8 +949,12 @@ describe('platform-server integration', () => {
it('correctly maps to absolute URL request with longer url and no slashes', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue:
{document: '<app></app>', url: 'http://localhost/path/page', useAbsoluteUrl: true}
useValue: {
document: '<app></app>',
url: 'http://localhost/path/page',
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@ -900,8 +971,12 @@ describe('platform-server integration', () => {
it('correctly maps to absolute URL request with longer url and slashes', async () => {
const platform = platformDynamicServer([{
provide: INITIAL_CONFIG,
useValue:
{document: '<app></app>', url: 'http://localhost/path/page', useAbsoluteUrl: true}
useValue: {
document: '<app></app>',
url: 'http://localhost/path/page',
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@ -922,7 +997,8 @@ describe('platform-server integration', () => {
useValue: {
document: '<base href="http://other"><app></app>',
url: 'http://localhost/path/page',
useAbsoluteUrl: true
baseUrl: 'http://localhost',
useAbsoluteUrl: true,
}
}]);
const ref = await platform.bootstrapModule(HttpClientExampleModule);