Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

Commit 84a398b

Browse files
aparzimattrbeck
authored andcommitted
fix(core): improve the warning for bindings on elements inside ICU expressions
Directives and bindings are not supported on elements inside ICU expressions, because ICU content is processed at runtime as plain HTML. Using one anyway, e.g. `<b [title]="title">` inside a plural case, used to crash with a cryptic `InvalidCharacterError` from `setAttribute`. Since the ICU attribute allowlist was introduced, such attributes are dropped with a generic "ignoring unknown attribute name" warning that points to the XSS security guide, which doesn't tell developers what is actually wrong. Detect binding-like attribute names (`[prop]`, `(event)`, `*structural`, `#ref`, `@trigger`) while parsing ICU content and report them in development mode with a dedicated `NG0700` warning that names the attribute and the element and explains that only static attributes are allowed inside ICU cases. The attribute is still ignored, as before. Fixes #37298 (cherry picked from commit 8523b79)
1 parent 566e9b6 commit 84a398b

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

‎packages/core/src/render3/i18n/i18n_parse.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../../util/ng_i18n_closure_mode';
1010

1111
import {XSS_SECURITY_URL} from '../../error_details_base_url';
1212
import {checkSecurityContext, SecurityContext} from '../../sanitization/dom_security_schema';
13+
import {formatRuntimeError, RuntimeErrorCode} from '../../errors';
1314
import {getTemplateContent, VALID_ATTRS, VALID_ELEMENTS} from '../../sanitization/html_sanitizer';
1415
import {getInertBodyHelper} from '../../sanitization/inert_body';
1516
import {
@@ -814,6 +815,21 @@ function walkIcuTree(
814815
const elAttrs = element.attributes;
815816
for (let i = 0; i < elAttrs.length; i++) {
816817
const attr = elAttrs.item(i)!;
818+
if (ngDevMode && isBindingLikeAttributeName(attr.name)) {
819+
// Directives and bindings are not supported inside ICU expressions, since ICU
820+
// content is processed at runtime as plain HTML. Such attributes are dropped
821+
// below as unknown attributes; explain why instead of reporting them as unsafe.
822+
console.warn(
823+
formatRuntimeError(
824+
RuntimeErrorCode.INVALID_I18N_STRUCTURE,
825+
`ICU expressions cannot contain bindings or structural directives. ` +
826+
`The attribute "${attr.name}" on the <${tagName}> element inside an ICU ` +
827+
`expression is not supported and will be ignored. ` +
828+
`Only static attributes are allowed inside ICU cases.`,
829+
),
830+
);
831+
continue;
832+
}
817833
const lowerAttrName = attr.name.toLowerCase();
818834
const hasBinding = !!attr.value.match(BINDING_REGEXP);
819835
const namespaceUri = element.namespaceURI;
@@ -1010,3 +1026,17 @@ function i18nResolveSanitizer(attrName: string, tagName?: string): SanitizerFn |
10101026
return null;
10111027
}
10121028
}
1029+
1030+
/**
1031+
* Whether an attribute name uses an Angular binding syntax (`[prop]`, `(event)`, `*structural`,
1032+
* `#ref`, `@trigger`) rather than being a plain HTML attribute.
1033+
*/
1034+
function isBindingLikeAttributeName(attrName: string): boolean {
1035+
return (
1036+
attrName.startsWith('[') ||
1037+
attrName.startsWith('(') ||
1038+
attrName.startsWith('*') ||
1039+
attrName.startsWith('#') ||
1040+
attrName.startsWith('@')
1041+
);
1042+
}

‎packages/core/test/acceptance/i18n_spec.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3603,6 +3603,34 @@ describe('runtime i18n', () => {
36033603
expect(textContent.replace(/\s+/g, ' ').trim()).toEqual(`before| 10 seconds |after.`);
36043604
});
36053605

3606+
it('should warn with a descriptive message for a binding on an element inside an ICU', () => {
3607+
const warnSpy = spyOn(console, 'warn');
3608+
3609+
@Component({
3610+
template: `
3611+
<div i18n>
3612+
{count, plural, =0 {just now} other {one <b [title]="title">minute</b> ago}}
3613+
</div>
3614+
`,
3615+
})
3616+
class MyApp {
3617+
count = 1;
3618+
title = 'Angular';
3619+
}
3620+
3621+
const fixture = TestBed.createComponent(MyApp);
3622+
fixture.detectChanges();
3623+
3624+
// The binding is ignored, but the rest of the ICU case is rendered.
3625+
expect(fixture.nativeElement.textContent.replace(/\s+/g, ' ').trim()).toEqual('one minute ago');
3626+
expect(fixture.nativeElement.querySelector('b').getAttribute('title')).toBeNull();
3627+
expect(warnSpy).toHaveBeenCalledOnceWith(
3628+
jasmine.stringMatching(
3629+
/NG0700: ICU expressions cannot contain bindings or structural directives\. The attribute "\[title\]" on the <b> element inside an ICU expression is not supported and will be ignored/,
3630+
),
3631+
);
3632+
});
3633+
36063634
it('should render attributes defined in ICUs', () => {
36073635
// NOTE: This test is extracted from g3.
36083636
@Component({

0 commit comments

Comments
 (0)