diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index c3af979eb74..af7b73239e3 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -8648,34 +8648,6 @@ runInEachFileSystem((os: string) => { expect(trim(jsContents)).toContain(trim(hostBindingsFn)); }); - it('should generate sanitizers for URL properties in SVG script fn in Component', () => { - env.write( - 'test.ts', - ` - import {Component} from '@angular/core'; - - @Component({ - selector: 'test-cmp', - template: \` - - - - \`, - }) - export class TestCmp { - attr = './script.js'; - } - `, - ); - - env.driveMain(); - - const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - 'i0.ɵɵattribute("href", ctx.attr, i0.ɵɵsanitizeResourceUrl, "xlink")("href", ctx.attr, i0.ɵɵsanitizeResourceUrl);', - ); - }); - it('should not generate sanitizers for URL properties in hostBindings fn in Component', () => { env.write( `test.ts`, diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index 179752e79f7..12a674b7021 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -115,12 +115,6 @@ export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} { ['object', ['codebase', 'data']], ]); - // The below are for Script SVG - // See: https://developer.mozilla.org/en-US/docs/Web/API/SVGScriptElement/href - registerContext(SecurityContext.RESOURCE_URL, SVG_NAMESPACE, [ - ['script', ['src', 'href', 'xlink:href']], - ]); - // Keep this in sync with SECURITY_SENSITIVE_ELEMENTS in packages/core/src/sanitization/sanitization.ts // The `unknown` elements refer to cases when we need to validate the input/binding in a directive (host bindings) // and the directive can be applied to multiple different elements (with different tag names). In this case we generate diff --git a/packages/compiler/src/template_parser/template_preparser.ts b/packages/compiler/src/template_parser/template_preparser.ts index 5097352081c..2c5889cae6c 100644 --- a/packages/compiler/src/template_parser/template_preparser.ts +++ b/packages/compiler/src/template_parser/template_preparser.ts @@ -14,8 +14,8 @@ const LINK_ELEMENT = 'link'; const LINK_STYLE_REL_ATTR = 'rel'; const LINK_STYLE_HREF_ATTR = 'href'; const LINK_STYLE_REL_VALUE = 'stylesheet'; -const STYLE_ELEMENT = 'style'; -const SCRIPT_ELEMENT = 'script'; +const STYLE_ELEMENTS: ReadonlySet = new Set([':svg:style', 'style']); +const SCRIPT_ELEMENTS: ReadonlySet = new Set([':svg:script', 'script']); const NG_NON_BINDABLE_ATTR = 'ngNonBindable'; const NG_PROJECT_AS = 'ngProjectAs'; @@ -25,7 +25,8 @@ export function preparseElement(ast: html.Element): PreparsedElement { let relAttr: string | null = null; let nonBindable = false; let projectAs = ''; - ast.attrs.forEach((attr) => { + + for (const attr of ast.attrs) { const lcAttrName = attr.name.toLowerCase(); if (lcAttrName == NG_CONTENT_SELECT_ATTR) { selectAttr = attr.value; @@ -40,15 +41,18 @@ export function preparseElement(ast: html.Element): PreparsedElement { projectAs = attr.value; } } - }); - selectAttr = normalizeNgContentSelect(selectAttr); + } + + // Normalize selector to '*' if empty + selectAttr ||= '*'; + const nodeName = ast.name.toLowerCase(); let type = PreparsedElementType.OTHER; if (isNgContent(nodeName)) { type = PreparsedElementType.NG_CONTENT; - } else if (nodeName == STYLE_ELEMENT) { + } else if (STYLE_ELEMENTS.has(nodeName)) { type = PreparsedElementType.STYLE; - } else if (nodeName == SCRIPT_ELEMENT) { + } else if (SCRIPT_ELEMENTS.has(nodeName)) { type = PreparsedElementType.SCRIPT; } else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) { type = PreparsedElementType.STYLESHEET; @@ -73,10 +77,3 @@ export class PreparsedElement { public projectAs: string, ) {} } - -function normalizeNgContentSelect(selectAttr: string | null): string { - if (selectAttr === null || selectAttr.length === 0) { - return '*'; - } - return selectAttr; -} diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts index 58f3ad3d42a..2194ac810f3 100644 --- a/packages/core/src/sanitization/sanitization.ts +++ b/packages/core/src/sanitization/sanitization.ts @@ -219,8 +219,7 @@ const RESOURCE_MAP: Record | undefined> 'frame': {'src': true}, 'iframe': {'src': true}, 'media': {'src': true}, - 'script': {'src': true, 'href': true, 'xlink:href': true}, - ':svg:script': {'src': true, 'href': true, 'xlink:href': true}, + 'base': {'href': true}, 'link': {'href': true}, 'object': {'data': true, 'codebase': true}, diff --git a/packages/core/test/acceptance/security_spec.ts b/packages/core/test/acceptance/security_spec.ts index 9488ee01d6f..4122b9aa40a 100644 --- a/packages/core/test/acceptance/security_spec.ts +++ b/packages/core/test/acceptance/security_spec.ts @@ -902,3 +902,17 @@ describe('Component host element validation', () => { } }); }); + +describe('SVG `, + changeDetection: ChangeDetectionStrategy.Eager, + }) + class TestCmp {} + + const fixture = TestBed.createComponent(TestCmp); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('script')).toBeFalsy(); + }); +}); diff --git a/packages/core/test/sanitization/sanitization_spec.ts b/packages/core/test/sanitization/sanitization_spec.ts index 6558cfce647..867b6dccb1b 100644 --- a/packages/core/test/sanitization/sanitization_spec.ts +++ b/packages/core/test/sanitization/sanitization_spec.ts @@ -117,7 +117,7 @@ describe('sanitization', () => { [SecurityContext.RESOURCE_URL, ɵɵsanitizeResourceUrl], ]); Object.entries(schema).forEach(([key, context]) => { - if (context === SecurityContext.URL || SecurityContext.RESOURCE_URL) { + if (context === SecurityContext.URL || context === SecurityContext.RESOURCE_URL) { const [tag, prop] = key.split('|'); const contexts = contextsByProp.get(prop) || new Set(); contexts.add(context); @@ -136,7 +136,7 @@ describe('sanitization', () => { expect(getUrlSanitizer('IFRAME', 'SRC')).toEqual(ɵɵsanitizeResourceUrl); expect(getUrlSanitizer('IFRAME', 'src')).toEqual(ɵɵsanitizeResourceUrl); expect(getUrlSanitizer('iframe', 'SRC')).toEqual(ɵɵsanitizeResourceUrl); - expect(getUrlSanitizer('ScRiPt', 'xLiNk:HrEf')).toEqual(ɵɵsanitizeResourceUrl); + expect(getUrlSanitizer('ScRiPt', 'xLiNk:HrEf')).toEqual(ɵɵsanitizeUrl); expect(getUrlSanitizer('A', 'HREF')).toEqual(ɵɵsanitizeUrl); }); @@ -149,8 +149,8 @@ describe('sanitization', () => { expect(() => ɵɵsanitizeUrlOrResourceUrl('http://server', 'iframe', 'SRC')).toThrowError(ERROR); - expect(() => ɵɵsanitizeUrlOrResourceUrl('http://server', 'ScRiPt', 'xLiNk:HrEf')).toThrowError( - ERROR, + expect(ɵɵsanitizeUrlOrResourceUrl('javascript:true', 'ScRiPt', 'xLiNk:HrEf')).toEqual( + 'unsafe:javascript:true', ); expect(ɵɵsanitizeUrlOrResourceUrl('javascript:true', 'A', 'HREF')).toEqual(