From 956f75f0689c73c8ea6798c8dec5d7ee38948bc7 Mon Sep 17 00:00:00 2001 From: "Wataru.Kasahara" Date: Tue, 5 Jan 2021 20:08:04 +0900 Subject: [PATCH 1/3] docs: fix the docs because the interface does not exist (#40308) PR Close #40308 --- .../my-lib/schematics/my-service/index.ts | 50 ++++++++++++------- aio/content/guide/schematics-for-libraries.md | 7 ++- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/aio/content/examples/schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts b/aio/content/examples/schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts index a36cf29acd74..a94cde317446 100644 --- a/aio/content/examples/schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts +++ b/aio/content/examples/schematics-for-libraries/projects/my-lib/schematics/my-service/index.ts @@ -6,37 +6,53 @@ import { chain, mergeWith } from '@angular-devkit/schematics'; -import { strings, normalize, experimental } from '@angular-devkit/core'; +import { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core'; // #enddocregion schematics-imports import { Schema as MyServiceSchema } from './schema'; // #enddocregion schema-imports -export function myService(options: MyServiceSchema): Rule { - return (tree: Tree) => { - const workspaceConfig = tree.read('/angular.json'); - if (!workspaceConfig) { - throw new SchematicsException('Could not find Angular workspace configuration'); - } +function createHost(tree: Tree): workspaces.WorkspaceHost { + return { + async readFile(path: string): Promise { + const data = tree.read(path); + if (!data) { + throw new SchematicsException('File not found.'); + } + return virtualFs.fileBufferToString(data); + }, + async writeFile(path: string, data: string): Promise { + return tree.overwrite(path, data); + }, + async isDirectory(path: string): Promise { + return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; + }, + async isFile(path: string): Promise { + return tree.exists(path); + }, + }; +} - // convert workspace to string - const workspaceContent = workspaceConfig.toString(); +export function myService(options: MyServiceSchema): Rule { + return async (tree: Tree) => { + const host = createHost(tree); + const { workspace } = await workspaces.readWorkspace('/', host); - // parse workspace string into JSON object - const workspace: experimental.workspace.WorkspaceSchema = JSON.parse(workspaceContent); // #enddocregion workspace + +// #docregion project-info // #docregion project-fallback if (!options.project) { - options.project = workspace.defaultProject; + options.project = workspace.extensions.defaultProject; } // #enddocregion project-fallback -// #docregion project-info - const projectName = options.project as string; - - const project = workspace.projects[projectName]; + const project = workspace.projects.get(options.project); + if (!project) { + throw new SchematicsException(`Invalid project name: ${options.project}`); + } - const projectType = project.projectType === 'application' ? 'app' : 'lib'; + const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib'; // #enddocregion project-info // #docregion path diff --git a/aio/content/guide/schematics-for-libraries.md b/aio/content/guide/schematics-for-libraries.md index e2520c5520b8..727bb9c54a2a 100644 --- a/aio/content/guide/schematics-for-libraries.md +++ b/aio/content/guide/schematics-for-libraries.md @@ -218,7 +218,8 @@ The `Tree` methods give you access to the complete file tree in your workspace, ### Get the project configuration -1. To determine the destination project, use the `Tree.read()` method to read the contents of the workspace configuration file, `angular.json`, at the root of the workspace. +1. To determine the destination project, use the `workspaces.readWorkspace` method to read the contents of the workspace configuration file, `angular.json`. + To use `workspaces.readWorkspace` you need to create a `workspaces.WorkspaceHost` from the `Tree`. Add the following code to your factory function. @@ -226,9 +227,7 @@ The `Tree` methods give you access to the complete file tree in your workspace, * Be sure to check that the context exists and throw the appropriate error. - * After reading the contents into a string, parse the configuration into a JSON object, typed to the `WorkspaceSchema`. - -1. The `WorkspaceSchema` contains all the properties of the workspace configuration, including a `defaultProject` value for determining which project to use if not provided. +1. The `WorkspaceDefinition`, `extensions` property includes a `defaultProject` value for determining which project to use if not provided. We will use that value as a fallback, if no project is explicitly specified in the `ng generate` command. From 365ac5e68eeebe307d3c654e106b54146a64fbb2 Mon Sep 17 00:00:00 2001 From: "S. Iftekhar Hossain" Date: Tue, 26 Jan 2021 04:26:09 +0600 Subject: [PATCH 2/3] docs: fixed issue with missing code sample by rearranging import to proper docregion (#40565) Fixes #40558 PR Close #40565 --- .../testing/src/app/banner/banner-initial.component.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts b/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts index 7842b82159dc..6ded93febffd 100644 --- a/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts +++ b/aio/content/examples/testing/src/app/banner/banner-initial.component.spec.ts @@ -1,14 +1,14 @@ // #docplaster -// #docregion import-by -// #enddocregion import-by // #docregion import-debug-element import { DebugElement } from '@angular/core'; // #enddocregion import-debug-element // #docregion v1 import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; // #enddocregion v1 +// #docregion import-by +import { By } from '@angular/platform-browser'; +// #enddocregion import-by import { BannerComponent } from './banner-initial.component'; /* From 6bf99e0edaedc6b67e765faa996da867420bf3e7 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 22 Jan 2021 10:05:17 -0800 Subject: [PATCH 3/3] fix(core): fix possible XSS attack in development through SSR (#40525) This is a follow up fix for https://github.com/angular/angular/pull/40136/commits/894286dd0c92b5af223364237e63798e18b14f58. It turns out that comments can be closed in several ways: - `` - `` - `` All of the above are valid ways to close comment per: https://html.spec.whatwg.org/multipage/syntax.html#comments The new fix surrounds `<` and `>` with zero width space so that it renders in the same way, but it prevents the comment to be closed eagerly. PR Close #40525 --- packages/core/src/util/dom.ts | 36 ++++++++++----- .../core/test/acceptance/security_spec.ts | 45 +++++++++++-------- packages/core/test/util/dom_spec.ts | 26 ++++++++++- 3 files changed, 76 insertions(+), 31 deletions(-) diff --git a/packages/core/src/util/dom.ts b/packages/core/src/util/dom.ts index 805daa5a926b..f55098583e12 100644 --- a/packages/core/src/util/dom.ts +++ b/packages/core/src/util/dom.ts @@ -6,15 +6,27 @@ * found in the LICENSE file at https://angular.io/license */ -const END_COMMENT = /-->/g; -const END_COMMENT_ESCAPED = '-\u200B-\u200B>'; +/** + * Disallowed strings in the comment. + * + * see: https://html.spec.whatwg.org/multipage/syntax.html#comments + */ +const COMMENT_DISALLOWED = /^>|^->||--!>|)/; +const COMMENT_DELIMITER_ESCAPED = '\u200B$1\u200B'; /** - * Escape the content of the strings so that it can be safely inserted into a comment node. + * Escape the content of comment strings so that it can be safely inserted into a comment node. * * The issue is that HTML does not specify any way to escape comment end text inside the comment. - * `". -->`. Above the `"-->"` is meant to be text not - * an end to the comment. This can be created programmatically through DOM APIs. + * Consider: `" or + * "--!>" at the end. -->`. Above the `"-->"` is meant to be text not an end to the comment. This + * can be created programmatically through DOM APIs. (`` and replace - * it with `-_-_>` where the `_` is a zero width space `\u200B`. The result is that if a comment - * contains `-->` text it will render normally but it will not cause the HTML parser to close the - * comment. + * This function escapes the comment text by looking for comment delimiters (`<` and `>`) and + * surrounding them with `_>_` where the `_` is a zero width space `\u200B`. The result is that if a + * comment contains any of the comment start/end delimiters (such as `` or `--!>`) the + * text it will render normally but it will not cause the HTML parser to close/open the comment. * - * @param value text to make safe for comment node by escaping the comment close character sequence + * @param value text to make safe for comment node by escaping the comment open/close character + * sequence. */ export function escapeCommentText(value: string): string { - return value.replace(END_COMMENT, END_COMMENT_ESCAPED); + return value.replace( + COMMENT_DISALLOWED, (text) => text.replace(COMMENT_DELIMITER, COMMENT_DELIMITER_ESCAPED)); } \ No newline at end of file diff --git a/packages/core/test/acceptance/security_spec.ts b/packages/core/test/acceptance/security_spec.ts index 0376dcc1cf71..afc8b9ccd40d 100644 --- a/packages/core/test/acceptance/security_spec.ts +++ b/packages/core/test/acceptance/security_spec.ts @@ -11,24 +11,33 @@ import {TestBed} from '@angular/core/testing'; describe('comment node text escaping', () => { - it('should not be possible to do XSS through comment reflect data', () => { - @Component({template: `
`}) - class XSSComp { - xssValue: string = '--> -->'; - } + // see: https://html.spec.whatwg.org/multipage/syntax.html#comments + ['>', // self closing + '-->', // standard closing + '--!>', // alternate closing + '', // embedded comment. + ].forEach((xssValue) => { + it('should not be possible to do XSS through comment reflect data when writing: ' + xssValue, + () => { + @Component({template: `
`}) + class XSSComp { + // ngIf serializes the `xssValue` into a comment for debugging purposes. + xssValue: string = xssValue + ''; + } - TestBed.configureTestingModule({declarations: [XSSComp]}); - const fixture = TestBed.createComponent(XSSComp); - fixture.detectChanges(); - const div = fixture.nativeElement.querySelector('div') as HTMLElement; - // Serialize into a string to mimic SSR serialization. - const html = div.innerHTML; - // This must be escaped or we have XSS. - expect(html).not.toContain('-->` - const script = div.querySelector('script'); - expect(script).toBeFalsy(); + TestBed.configureTestingModule({declarations: [XSSComp]}); + const fixture = TestBed.createComponent(XSSComp); + fixture.detectChanges(); + const div = fixture.nativeElement.querySelector('div') as HTMLElement; + // Serialize into a string to mimic SSR serialization. + const html = div.innerHTML; + // This must be escaped or we have XSS. + expect(html).not.toContain('-->` + const script = div.querySelector('script'); + expect(script).toBeFalsy(); + }); }); }); \ No newline at end of file diff --git a/packages/core/test/util/dom_spec.ts b/packages/core/test/util/dom_spec.ts index 8d552ccf2ea0..c62618f01707 100644 --- a/packages/core/test/util/dom_spec.ts +++ b/packages/core/test/util/dom_spec.ts @@ -14,13 +14,35 @@ describe('comment node text escaping', () => { expect(escapeCommentText('text')).toEqual('text'); }); + it('should escape "<" or ">"', () => { + expect(escapeCommentText('')).toEqual('\u200b>\u200b--\u200b>\u200b'); + }); + it('should escape end marker', () => { - expect(escapeCommentText('before-->after')).toEqual('before-\u200b-\u200b>after'); + expect(escapeCommentText('before-->after')).toEqual('before--\u200b>\u200bafter'); }); it('should escape multiple markers', () => { expect(escapeCommentText('before-->inline-->after')) - .toEqual('before-\u200b-\u200b>inline-\u200b-\u200b>after'); + .toEqual('before--\u200b>\u200binline--\u200b>\u200bafter'); + }); + + it('should caver the spec', () => { + // https://html.spec.whatwg.org/multipage/syntax.html#comments + expect(escapeCommentText('>')).toEqual('\u200b>\u200b'); + expect(escapeCommentText('->')).toEqual('-\u200b>\u200b'); + expect(escapeCommentText('')).toEqual('--\u200b>\u200b'); + expect(escapeCommentText('--!>')).toEqual('--!\u200b>\u200b'); + expect(escapeCommentText('')).toEqual('.>'); + expect(escapeCommentText('.->')).toEqual('.->'); + expect(escapeCommentText('