Skip to content

Commit fec7c28

Browse files
JeanMechethePunderWoman
authored andcommitted
fix(core): Error on invalid APP_ID (#63252)
An invalid APP_ID could be responsible to generating broken CSS selectors. (eg `:` is an example for a character that breaks a selector by being a separator for pseudo-selectors.) We now throw an error if the provided value is not alphanumerical PR Close #63252
1 parent eeb07dd commit fec7c28

File tree

7 files changed

+72
-0
lines changed

7 files changed

+72
-0
lines changed

goldens/public-api/core/errors.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ export const enum RuntimeErrorCode {
7272
// (undocumented)
7373
INJECTOR_ALREADY_DESTROYED = 205,
7474
// (undocumented)
75+
INVALID_APP_ID = 211,
76+
// (undocumented)
7577
INVALID_BINDING_TARGET = 316,
7678
// (undocumented)
7779
INVALID_DIFFER_INPUT = 900,

packages/core/src/application/application_tokens.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import {ENVIRONMENT_INITIALIZER, inject, StaticProvider} from '../di';
910
import {InjectionToken} from '../di/injection_token';
11+
import {RuntimeError, RuntimeErrorCode} from '../errors';
1012
import {getDocument} from '../render3/interfaces/document';
1113

1214
/**
@@ -47,6 +49,24 @@ export const APP_ID = new InjectionToken<string>(ngDevMode ? 'AppId' : '', {
4749
/** Default value of the `APP_ID` token. */
4850
const DEFAULT_APP_ID = 'ng';
4951

52+
/** Initializer check for the validity of the APP_ID */
53+
export const validAppIdInitializer: StaticProvider = {
54+
provide: ENVIRONMENT_INITIALIZER,
55+
multi: true,
56+
useValue: () => {
57+
const appId = inject(APP_ID);
58+
const isAlphanumeric = /^[a-zA-Z0-9\-_]+$/.test(appId);
59+
60+
if (!isAlphanumeric) {
61+
throw new RuntimeError(
62+
RuntimeErrorCode.INVALID_APP_ID,
63+
`APP_ID value "${appId}" is not alphanumeric. ` +
64+
`The APP_ID must be a string of alphanumeric characters. (a-zA-Z0-9), hyphens (-) and underscores (_) are allowed.`,
65+
);
66+
}
67+
},
68+
};
69+
5070
/**
5171
* A function that is executed when a platform is initialized.
5272
*

packages/core/src/application/create_application.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {errorHandlerEnvironmentInitializer} from '../error_handler';
2222
import {RuntimeError, RuntimeErrorCode} from '../errors';
2323
import {PlatformRef} from '../platform/platform_ref';
2424
import {internalProvideZoneChangeDetection} from '../change_detection/scheduling/ng_zone_scheduling';
25+
import {validAppIdInitializer} from './application_tokens';
2526

2627
const ZONELESS_BY_DEFAULT = true;
2728

@@ -69,6 +70,7 @@ export function internalCreateApplication(config: {
6970
provideZonelessChangeDetectionInternal(),
7071
ZONELESS_BY_DEFAULT ? [] : internalProvideZoneChangeDetection({}),
7172
errorHandlerEnvironmentInitializer,
73+
...(ngDevMode ? [validAppIdInitializer] : []),
7274
...(appProviders || []),
7375
];
7476
const adapter = new EnvironmentNgModuleRefAdapter({

packages/core/src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const enum RuntimeErrorCode {
4343
MISSING_INJECTION_TOKEN = 208,
4444
INVALID_MULTI_PROVIDER = -209,
4545
MISSING_DOCUMENT = 210,
46+
INVALID_APP_ID = 211,
4647

4748
// Template Errors
4849
MULTIPLE_COMPONENTS_MATCH = -300,

packages/core/src/platform/platform_ref.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {compileNgModuleFactory} from '../application/application_ngmodule_factory_compiler';
1010
import {BootstrapOptions, optionsReducer} from '../application/application_ref';
11+
import {validAppIdInitializer} from '../application/application_tokens';
1112
import {provideZonelessChangeDetectionInternal} from '../change_detection/scheduling/zoneless_scheduling_impl';
1213
import {Injectable, Injector, StaticProvider} from '../di';
1314
import {errorHandlerEnvironmentInitializer} from '../error_handler';
@@ -77,6 +78,7 @@ export class PlatformRef {
7778
...defaultZoneCdProviders,
7879
...(_additionalApplicationProviders ?? []),
7980
errorHandlerEnvironmentInitializer,
81+
...(ngDevMode ? [validAppIdInitializer] : []),
8082
];
8183
_additionalApplicationProviders = undefined;
8284
const moduleRef = createNgModuleRefWithProviders(

packages/platform-browser/test/browser/bootstrap_spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
destroyPlatform,
4646
providePlatformInitializer,
4747
ɵcreateOrReusePlatformInjector as createOrReusePlatformInjector,
48+
APP_ID,
4849
} from '@angular/core';
4950
import {ɵLog as Log, inject, TestBed} from '@angular/core/testing';
5051
import {BrowserModule} from '../../index';
@@ -801,6 +802,24 @@ describe('bootstrap factory method', () => {
801802
}, done.fail);
802803
});
803804

805+
it('should throw an error if the provided APP_ID is invalid', (done) => {
806+
const logger = new MockConsole();
807+
const errorHandler = new ErrorHandler();
808+
(errorHandler as any)._console = logger as any;
809+
810+
const refPromise = bootstrap(HelloRootCmp, [{provide: APP_ID, useValue: 'foo:bar'}]);
811+
refPromise.then(
812+
() => fail(),
813+
(reason) => {
814+
expect(reason.message).toContain(
815+
`NG0211: APP_ID value "foo:bar" is not alphanumeric. The APP_ID must be a string of alphanumeric characters.`,
816+
);
817+
done();
818+
return null;
819+
},
820+
);
821+
});
822+
804823
describe('change detection', () => {
805824
const log: string[] = [];
806825

packages/platform-browser/test/browser/bootstrap_standalone_spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
PlatformRef,
1919
ɵR3Injector as R3Injector,
2020
ɵNoopNgZone as NoopNgZone,
21+
APP_ID,
2122
} from '@angular/core';
2223
import {withBody} from '@angular/private/testing';
2324

@@ -250,4 +251,29 @@ describe('bootstrapApplication for standalone components', () => {
250251
expect(document.body.textContent).toBe('');
251252
}),
252253
);
254+
255+
it('should throw and error if the APP_ID is not valid', async () => {
256+
withBody('<test-app></test-app>', async () => {
257+
@Component({
258+
selector: 'test-app',
259+
template: ``,
260+
imports: [],
261+
})
262+
class StandaloneCmp {}
263+
264+
try {
265+
await bootstrapApplication(StandaloneCmp, {
266+
providers: [{provide: APP_ID, useValue: 'foo:bar'}],
267+
});
268+
269+
// we expect the bootstrap process to fail because of the invalid APP_ID value
270+
fail('Expected to throw');
271+
} catch (e: unknown) {
272+
expect(e).toBeInstanceOf(Error);
273+
expect((e as Error).message).toContain(
274+
'APP_ID value "foo:bar" is not alphanumeric. The APP_ID must be a string of alphanumeric characters.',
275+
);
276+
}
277+
});
278+
});
253279
});

0 commit comments

Comments
 (0)