Skip to content

Commit b4e8399

Browse files
Keen Yee LiauAndrewKushnir
Keen Yee Liau
authored andcommitted
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
1 parent d8c9f42 commit b4e8399

File tree

4 files changed

+119
-24
lines changed

4 files changed

+119
-24
lines changed

goldens/public-api/platform-server/platform-server.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export declare const BEFORE_APP_SERIALIZED: InjectionToken<(() => void | Promise
33
export declare const INITIAL_CONFIG: InjectionToken<PlatformConfig>;
44

55
export declare interface PlatformConfig {
6+
baseUrl?: string;
67
document?: string;
78
url?: string;
89
useAbsoluteUrl?: boolean;

packages/platform-server/src/location.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {Subject} from 'rxjs';
1212
import * as url from 'url';
1313
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
1414

15-
1615
function parseUrl(urlStr: string) {
1716
const parsedUrl = url.parse(urlStr);
1817
return {
@@ -43,16 +42,28 @@ export class ServerPlatformLocation implements PlatformLocation {
4342
constructor(
4443
@Inject(DOCUMENT) private _doc: any, @Optional() @Inject(INITIAL_CONFIG) _config: any) {
4544
const config = _config as PlatformConfig | null;
46-
if (!!config && !!config.url) {
47-
const parsedUrl = parseUrl(config.url);
48-
this.hostname = parsedUrl.hostname;
49-
this.protocol = parsedUrl.protocol;
50-
this.port = parsedUrl.port;
51-
this.pathname = parsedUrl.pathname;
52-
this.search = parsedUrl.search;
53-
this.hash = parsedUrl.hash;
45+
if (!config) {
46+
return;
47+
}
48+
if (config.url) {
49+
const url = parseUrl(config.url);
50+
this.protocol = url.protocol;
51+
this.hostname = url.hostname;
52+
this.port = url.port;
53+
this.pathname = url.pathname;
54+
this.search = url.search;
55+
this.hash = url.hash;
5456
this.href = _doc.location.href;
5557
}
58+
if (config.useAbsoluteUrl) {
59+
if (!config.baseUrl) {
60+
throw new Error(`"PlatformConfig.baseUrl" must be set if "useAbsoluteUrl" is true`);
61+
}
62+
const url = parseUrl(config.baseUrl);
63+
this.protocol = url.protocol;
64+
this.hostname = url.hostname;
65+
this.port = url.port;
66+
}
5667
}
5768

5869
getBaseHrefFromDOM(): string {

packages/platform-server/src/tokens.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,26 @@ export interface PlatformConfig {
2020
*/
2121
document?: string;
2222
/**
23-
* The URL for the current application state. This is
24-
* used for initializing the platform's location and
25-
* for setting absolute URL resolution for HTTP requests.
23+
* The URL for the current application state. This is used for initializing
24+
* the platform's location. `protocol`, `hostname`, and `port` will be
25+
* overridden if `baseUrl` is set.
2626
* @default none
2727
*/
2828
url?: string;
2929
/**
30-
* Whether to append the absolute URL to any relative HTTP
31-
* requests. If set to true, this logic executes prior to
32-
* any HTTP interceptors that may run later on in the request.
30+
* Whether to append the absolute URL to any relative HTTP requests. If set to
31+
* true, this logic executes prior to any HTTP interceptors that may run later
32+
* on in the request. `baseUrl` must be supplied if this flag is enabled.
3333
* @default false
3434
*/
3535
useAbsoluteUrl?: boolean;
36+
/**
37+
* The base URL for resolving absolute URL for HTTP requests. It must be set
38+
* if `useAbsoluteUrl` is true, and must consist of protocol, hostname,
39+
* and optional port. This option has no effect if `useAbsoluteUrl` is not
40+
* enabled.
41+
*/
42+
baseUrl?: string;
3643
}
3744

3845
/**

packages/platform-server/test/integration_spec.ts

+85-9
Original file line numberDiff line numberDiff line change
@@ -794,10 +794,62 @@ describe('platform-server integration', () => {
794794
}));
795795

796796
describe('relative requests', () => {
797+
it('will throw if "useAbsoluteUrl" is true but "baseUrl" is not provided', async () => {
798+
const platform = platformDynamicServer([{
799+
provide: INITIAL_CONFIG,
800+
useValue: {
801+
document: '<app></app>',
802+
url: 'http://localhost',
803+
useAbsoluteUrl: true,
804+
},
805+
}]);
806+
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
807+
expect(() => appRef.injector.get(PlatformLocation))
808+
.toThrowError(/"PlatformConfig\.baseUrl" must be set if "useAbsoluteUrl" is true/);
809+
});
810+
811+
it('will resolve absolute url using "baseUrl"', async () => {
812+
const platform = platformDynamicServer([{
813+
provide: INITIAL_CONFIG,
814+
useValue: {
815+
document: '<app></app>',
816+
url: 'http://localhost',
817+
useAbsoluteUrl: true,
818+
baseUrl: 'https://angular.io:8080',
819+
},
820+
}]);
821+
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
822+
const location = appRef.injector.get(PlatformLocation);
823+
expect(location.protocol).toBe('https:');
824+
expect(location.hostname).toBe('angular.io');
825+
expect(location.port).toBe('8080');
826+
});
827+
828+
it('"baseUrl" has no effect if "useAbsoluteUrl" is not enabled', async () => {
829+
const platform = platformDynamicServer([{
830+
provide: INITIAL_CONFIG,
831+
useValue: {
832+
document: '<app></app>',
833+
url: 'http://localhost',
834+
baseUrl: 'https://angular.io:8080',
835+
},
836+
}]);
837+
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
838+
const location = appRef.injector.get(PlatformLocation);
839+
expect(location.protocol).toBe('http:');
840+
expect(location.hostname).toBe('localhost');
841+
expect(location.port).toBe('');
842+
});
843+
797844
it('correctly maps to absolute URL request with base config', async () => {
798845
const platform = platformDynamicServer([{
799846
provide: INITIAL_CONFIG,
800-
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: true}
847+
useValue: {
848+
document: '<app></app>',
849+
url: 'http://localhost',
850+
baseUrl: 'http://localhost',
851+
useAbsoluteUrl: true,
852+
}
801853
}]);
802854
const ref = await platform.bootstrapModule(HttpClientExampleModule);
803855
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@@ -831,7 +883,12 @@ describe('platform-server integration', () => {
831883
it('correctly maps to absolute URL request with port', async () => {
832884
const platform = platformDynamicServer([{
833885
provide: INITIAL_CONFIG,
834-
useValue: {document: '<app></app>', url: 'http://localhost:5000', useAbsoluteUrl: true}
886+
useValue: {
887+
document: '<app></app>',
888+
url: 'http://localhost:5000',
889+
baseUrl: 'http://localhost',
890+
useAbsoluteUrl: true,
891+
}
835892
}]);
836893
const ref = await platform.bootstrapModule(HttpClientExampleModule);
837894
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@@ -848,7 +905,12 @@ describe('platform-server integration', () => {
848905
it('correctly maps to absolute URL request with two slashes', async () => {
849906
const platform = platformDynamicServer([{
850907
provide: INITIAL_CONFIG,
851-
useValue: {document: '<app></app>', url: 'http://localhost/', useAbsoluteUrl: true}
908+
useValue: {
909+
document: '<app></app>',
910+
url: 'http://localhost/',
911+
baseUrl: 'http://localhost',
912+
useAbsoluteUrl: true,
913+
}
852914
}]);
853915
const ref = await platform.bootstrapModule(HttpClientExampleModule);
854916
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@@ -865,7 +927,12 @@ describe('platform-server integration', () => {
865927
it('correctly maps to absolute URL request with no slashes', async () => {
866928
const platform = platformDynamicServer([{
867929
provide: INITIAL_CONFIG,
868-
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: true}
930+
useValue: {
931+
document: '<app></app>',
932+
url: 'http://localhost',
933+
baseUrl: 'http://localhost',
934+
useAbsoluteUrl: true,
935+
}
869936
}]);
870937
const ref = await platform.bootstrapModule(HttpClientExampleModule);
871938
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@@ -882,8 +949,12 @@ describe('platform-server integration', () => {
882949
it('correctly maps to absolute URL request with longer url and no slashes', async () => {
883950
const platform = platformDynamicServer([{
884951
provide: INITIAL_CONFIG,
885-
useValue:
886-
{document: '<app></app>', url: 'http://localhost/path/page', useAbsoluteUrl: true}
952+
useValue: {
953+
document: '<app></app>',
954+
url: 'http://localhost/path/page',
955+
baseUrl: 'http://localhost',
956+
useAbsoluteUrl: true,
957+
}
887958
}]);
888959
const ref = await platform.bootstrapModule(HttpClientExampleModule);
889960
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@@ -900,8 +971,12 @@ describe('platform-server integration', () => {
900971
it('correctly maps to absolute URL request with longer url and slashes', async () => {
901972
const platform = platformDynamicServer([{
902973
provide: INITIAL_CONFIG,
903-
useValue:
904-
{document: '<app></app>', url: 'http://localhost/path/page', useAbsoluteUrl: true}
974+
useValue: {
975+
document: '<app></app>',
976+
url: 'http://localhost/path/page',
977+
baseUrl: 'http://localhost',
978+
useAbsoluteUrl: true,
979+
}
905980
}]);
906981
const ref = await platform.bootstrapModule(HttpClientExampleModule);
907982
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
@@ -922,7 +997,8 @@ describe('platform-server integration', () => {
922997
useValue: {
923998
document: '<base href="http://other"><app></app>',
924999
url: 'http://localhost/path/page',
925-
useAbsoluteUrl: true
1000+
baseUrl: 'http://localhost',
1001+
useAbsoluteUrl: true,
9261002
}
9271003
}]);
9281004
const ref = await platform.bootstrapModule(HttpClientExampleModule);

0 commit comments

Comments
 (0)