Skip to content

Aio tt #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: aio-tt
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions aio/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "site:build",
"headers": {
"Content-Security-Policy-Report-Only": "require-trusted-types-for 'script'; trusted-types angular angular#unsafe-bypass analytics google#safe"
}
},
"configurations": {
"next": {
"browserTarget": "site:build:next"
Expand Down
2 changes: 2 additions & 0 deletions aio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@
"@angular/platform-browser-dynamic": "12.0.5",
"@angular/router": "12.0.5",
"@angular/service-worker": "12.0.5",
"@types/trusted-types": "^2.0.0",
"@webcomponents/custom-elements": "1.4.3",
"rxjs": "^6.6.7",
"safevalues": "^0.1.3",
"tslib": "^2.2.0",
"zone.js": "~0.11.4"
},
Expand Down
106 changes: 64 additions & 42 deletions aio/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="trusted-types" />
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
Expand Down Expand Up @@ -46,52 +47,67 @@ import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';

import { environment } from '../environments/environment';

import {scriptUrl} from 'safevalues';
import {concatHtmls} from 'safevalues/builders/html_builders';
import {assertIsTemplateObject} from 'safevalues/implementation/safe_string_literal';

import {htmlFromStringKnownToSatisfyTypeContract} from 'safevalues/unsafe/reviewed';

function svg(constantSvg: TemplateStringsArray): TrustedHTML {
assertIsTemplateObject(constantSvg, false, 'This needs to be static');
return htmlFromStringKnownToSatisfyTypeContract(constantSvg[0], 'static SVG markup');
}

// These are the hardcoded inline svg sources to be used by the `<mat-icon>` component.
// tslint:disable: max-line-length
export const svgIconProviders = [
{
provide: SVG_ICONS,
useValue: {
name: 'close',
svgSource:
'<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />' +
'<path d="M0 0h24v24H0z" fill="none" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />`,
svg`<path d="M0 0h24v24H0z" fill="none" />`,
svg`</svg>`,
),
},
multi: true,
},
{
provide: SVG_ICONS,
useValue: {
name: 'insert_comment',
svgSource:
'<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />' +
'<path d="M0 0h24v24H0z" fill="none" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />`,
svg`<path d="M0 0h24v24H0z" fill="none" />`,
svg`</svg>`,
),
},
multi: true,
},
{
provide: SVG_ICONS,
useValue: {
name: 'keyboard_arrow_right',
svgSource:
'<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z" />`,
svg`</svg>`,
),
},
multi: true,
},
{
provide: SVG_ICONS,
useValue: {
name: 'menu',
svgSource:
'<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />`,
svg`</svg>`,
),
},
multi: true,
},
Expand All @@ -102,15 +118,16 @@ export const svgIconProviders = [
useValue: {
namespace: 'logos',
name: 'github',
svgSource:
'<svg focusable="false" viewBox="0 0 51.8 50.4" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M25.9,0.2C11.8,0.2,0.3,11.7,0.3,25.8c0,11.3,7.3,20.9,17.5,24.3c1.3,0.2,1.7-0.6,1.7-1.2c0-0.6,0-2.6,0-4.8' +
'c-7.1,1.5-8.6-3-8.6-3c-1.2-3-2.8-3.7-2.8-3.7c-2.3-1.6,0.2-1.6,0.2-1.6c2.6,0.2,3.9,2.6,3.9,2.6c2.3,3.9,6,2.8,7.5,2.1' +
'c0.2-1.7,0.9-2.8,1.6-3.4c-5.7-0.6-11.7-2.8-11.7-12.7c0-2.8,1-5.1,2.6-6.9c-0.3-0.7-1.1-3.3,0.3-6.8c0,0,2.1-0.7,7,2.6' +
'c2-0.6,4.2-0.9,6.4-0.9c2.2,0,4.4,0.3,6.4,0.9c4.9-3.3,7-2.6,7-2.6c1.4,3.5,0.5,6.1,0.3,6.8c1.6,1.8,2.6,4.1,2.6,6.9' +
'c0,9.8-6,12-11.7,12.6c0.9,0.8,1.7,2.4,1.7,4.7c0,3.4,0,6.2,0,7c0,0.7,0.5,1.5,1.8,1.2c10.2-3.4,17.5-13,17.5-24.3' +
'C51.5,11.7,40.1,0.2,25.9,0.2z" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 51.8 50.4" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M25.9,0.2C11.8,0.2,0.3,11.7,0.3,25.8c0,11.3,7.3,20.9,17.5,24.3c1.3,0.2,1.7-0.6,1.7-1.2c0-0.6,0-2.6,0-4.8`,
svg`c-7.1,1.5-8.6-3-8.6-3c-1.2-3-2.8-3.7-2.8-3.7c-2.3-1.6,0.2-1.6,0.2-1.6c2.6,0.2,3.9,2.6,3.9,2.6c2.3,3.9,6,2.8,7.5,2.1`,
svg`c0.2-1.7,0.9-2.8,1.6-3.4c-5.7-0.6-11.7-2.8-11.7-12.7c0-2.8,1-5.1,2.6-6.9c-0.3-0.7-1.1-3.3,0.3-6.8c0,0,2.1-0.7,7,2.6`,
svg`c2-0.6,4.2-0.9,6.4-0.9c2.2,0,4.4,0.3,6.4,0.9c4.9-3.3,7-2.6,7-2.6c1.4,3.5,0.5,6.1,0.3,6.8c1.6,1.8,2.6,4.1,2.6,6.9`,
svg`c0,9.8-6,12-11.7,12.6c0.9,0.8,1.7,2.4,1.7,4.7c0,3.4,0,6.2,0,7c0,0.7,0.5,1.5,1.8,1.2c10.2-3.4,17.5-13,17.5-24.3`,
svg`C51.5,11.7,40.1,0.2,25.9,0.2z" />`,
svg`</svg>`,
),
},
multi: true,
},
Expand All @@ -119,14 +136,15 @@ export const svgIconProviders = [
useValue: {
namespace: 'logos',
name: 'twitter',
svgSource:
'<svg focusable="false" viewBox="0 0 50 59" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M50,9.3c-1.8,0.8-3.8,1.4-5.9,1.6c2.1-1.3,3.7-3.3,4.5-5.7c-2,1.2-4.2,2-6.5,2.5c-1.9-2-4.5-3.2-7.5-3.2' +
'c-5.7,0-10.3,4.6-10.3,10.3c0,0.8,0.1,1.6,0.3,2.3C16.1,16.7,8.5,12.6,3.5,6.4c-0.9,1.5-1.4,3.3-1.4,5.2c0,3.6,1.8,6.7,4.6,8.5' +
'C5,20,3.4,19.6,2,18.8c0,0,0,0.1,0,0.1c0,5,3.5,9.1,8.2,10.1c-0.9,0.2-1.8,0.4-2.7,0.4c-0.7,0-1.3-0.1-1.9-0.2' +
'c1.3,4.1,5.1,7,9.6,7.1c-3.5,2.8-7.9,4.4-12.7,4.4c-0.8,0-1.6,0-2.4-0.1c4.5,2.9,9.9,4.6,15.7,4.6c18.9,0,29.2-15.6,29.2-29.2' +
'c0-0.4,0-0.9,0-1.3C46.9,13.2,48.6,11.4,50,9.3z" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 50 59" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M50,9.3c-1.8,0.8-3.8,1.4-5.9,1.6c2.1-1.3,3.7-3.3,4.5-5.7c-2,1.2-4.2,2-6.5,2.5c-1.9-2-4.5-3.2-7.5-3.2`,
svg`c-5.7,0-10.3,4.6-10.3,10.3c0,0.8,0.1,1.6,0.3,2.3C16.1,16.7,8.5,12.6,3.5,6.4c-0.9,1.5-1.4,3.3-1.4,5.2c0,3.6,1.8,6.7,4.6,8.5`,
svg`C5,20,3.4,19.6,2,18.8c0,0,0,0.1,0,0.1c0,5,3.5,9.1,8.2,10.1c-0.9,0.2-1.8,0.4-2.7,0.4c-0.7,0-1.3-0.1-1.9-0.2`,
svg`c1.3,4.1,5.1,7,9.6,7.1c-3.5,2.8-7.9,4.4-12.7,4.4c-0.8,0-1.6,0-2.4-0.1c4.5,2.9,9.9,4.6,15.7,4.6c18.9,0,29.2-15.6,29.2-29.2`,
svg`c0-0.4,0-0.9,0-1.3C46.9,13.2,48.6,11.4,50,9.3z" />`,
svg`</svg>`,
),
},
multi: true,
},
Expand All @@ -135,12 +153,13 @@ export const svgIconProviders = [
useValue: {
namespace: 'logos',
name: 'youtube',
svgSource:
'<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' +
'<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77' +
'C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42' +
'c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z" />' +
'</svg>',
svgSource: concatHtmls(
svg`<svg focusable="false" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">`,
svg`<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77`,
svg`C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42`,
svg`c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z" />`,
svg`</svg>`,
),
},
multi: true,
},
Expand All @@ -160,7 +179,10 @@ export const svgIconProviders = [
MatToolbarModule,
SwUpdatesModule,
SharedModule,
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production}),
ServiceWorkerModule.register(
// Make sure service worker is loaded with a TrustedScriptURL
scriptUrl`/ngsw-worker.js` as unknown as string,
{enabled: environment.production}),
],
declarations: [
AppComponent,
Expand Down
6 changes: 4 additions & 2 deletions aio/src/app/custom-elements/code/code-example.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* tslint:disable component-selector */
import { Component, HostBinding, ElementRef, ViewChild, Input, AfterViewInit } from '@angular/core';
import { CodeComponent } from './code.component';
import { htmlFromStringKnownToSatisfyTypeContract } from 'safevalues/unsafe/reviewed';

/**
* An embeddable code block that displays nicely formatted code.
Expand Down Expand Up @@ -85,7 +86,8 @@ export class CodeExampleComponent implements AfterViewInit {

ngAfterViewInit() {
const contentElem = this.content.nativeElement;
this.aioCode.code = contentElem.innerHTML;
contentElem.innerHTML = ''; // Remove DOM nodes that are no longer needed.
this.aioCode.code = htmlFromStringKnownToSatisfyTypeContract(
contentElem.innerHTML, 'existing innerHTML content');
contentElem.textContent = ''; // Remove DOM nodes that are no longer needed.
}
}
8 changes: 5 additions & 3 deletions aio/src/app/custom-elements/code/code-tabs.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* tslint:disable component-selector */
import { AfterViewInit, Component, ElementRef, Input, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import {htmlFromStringKnownToSatisfyTypeContract} from 'safevalues/unsafe/reviewed';
import { CodeComponent } from './code.component';

export interface TabInfo {
class: string;
code: string;
code: TrustedHTML;
path: string;
region: string;

Expand Down Expand Up @@ -67,7 +68,7 @@ export class CodeTabsComponent implements OnInit, AfterViewInit {
// NOTE:
// In IE11, doing this also empties the `<code-pane>` nodes captured in `codeExamples` ¯\_(ツ)_/¯
// Only remove the unnecessary nodes after having captured the `<code-pane>` contents.
contentElem.innerHTML = '';
contentElem.innerText = '';
}

ngAfterViewInit() {
Expand All @@ -80,7 +81,8 @@ export class CodeTabsComponent implements OnInit, AfterViewInit {
private getTabInfo(tabContent: Element): TabInfo {
return {
class: tabContent.getAttribute('class') || '',
code: tabContent.innerHTML,
code: htmlFromStringKnownToSatisfyTypeContract(
tabContent.innerHTML, 'existing innerHTML content'),
path: tabContent.getAttribute('path') || '',
region: tabContent.getAttribute('region') || '',

Expand Down
29 changes: 18 additions & 11 deletions aio/src/app/custom-elements/code/code.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { PrettyPrinter } from './pretty-printer.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import {htmlEscape, unwrapHtmlForSink} from 'safevalues';
import {htmlFromStringKnownToSatisfyTypeContract} from 'safevalues/unsafe/reviewed';

/**
* Formatted Code Block
Expand Down Expand Up @@ -48,17 +50,19 @@ export class CodeComponent implements OnChanges {
private codeText: string;

/** Code that should be formatted with current inputs and displayed in the view. */
set code(code: string) {
set code(code: TrustedHTML) {
this._code = code;

if (!this._code || !this._code.trim()) {
if (!this._code.toString() || !this._code.toString().trim()) {
this.showMissingCodeMessage();
} else {
this.formatDisplayedCode();
}
}
get code(): string { return this._code; }
_code: string;
get code(): TrustedHTML {
return this._code;
}
_code: TrustedHTML;

/** Whether the copy button should be shown. */
@Input() hideCopy: boolean;
Expand Down Expand Up @@ -130,15 +134,16 @@ export class CodeComponent implements OnChanges {
/** Sets the message showing that the code could not be found. */
private showMissingCodeMessage() {
const src = this.path ? this.path + (this.region ? '#' + this.region : '') : '';
const srcMsg = src ? ` for\n${src}` : '.';
this.setCodeHtml(`<p class="code-missing">The code sample is missing${srcMsg}</p>`);
const srcMsg = `The code sample is missing${src ? ` for\n${src}` : '.'}`;
this.setCodeHtml(htmlFromStringKnownToSatisfyTypeContract(
`<p class="code-missing">${htmlEscape(srcMsg)}</p>`, 'message is properly escaped'));
}

/** Sets the innerHTML of the code container to the provided code string. */
private setCodeHtml(formattedCode: string) {
private setCodeHtml(formattedCode: TrustedHTML) {
// **Security:** Code example content is provided by docs authors and as such its considered to
// be safe for innerHTML purposes.
this.codeContainer.nativeElement.innerHTML = formattedCode;
this.codeContainer.nativeElement.innerHTML = unwrapHtmlForSink(formattedCode);
}

/** Gets the textContent of the displayed code element. */
Expand Down Expand Up @@ -176,16 +181,18 @@ export class CodeComponent implements OnChanges {
}
}

function leftAlign(text: string): string {
function leftAlign(text: TrustedHTML): TrustedHTML {
let indent = Number.MAX_VALUE;

const lines = text.split('\n');
const lines = text.toString().split('\n');
lines.forEach(line => {
const lineIndent = line.search(/\S/);
if (lineIndent !== -1) {
indent = Math.min(lineIndent, indent);
}
});

return lines.map(line => line.substr(indent)).join('\n').trim();
return htmlFromStringKnownToSatisfyTypeContract(
lines.map(line => line.substr(indent)).join('\n').trim(),
'safe manipulation of existing trusted HTML');
}
10 changes: 6 additions & 4 deletions aio/src/app/custom-elements/code/pretty-printer.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Injectable } from '@angular/core';
import { htmlFromStringKnownToSatisfyTypeContract } from 'safevalues/unsafe/reviewed';

import { from, Observable } from 'rxjs';
import { first, map, share } from 'rxjs/operators';

import { Logger } from 'app/shared/logger.service';

type PrettyPrintOne = (code: string, language?: string, linenums?: number | boolean) => string;
type PrettyPrintOne = (code: TrustedHTML, language?: string, linenums?: number|boolean) => string;

/**
* Wrapper around the prettify.js library
Expand Down Expand Up @@ -45,13 +46,14 @@ export class PrettyPrinter {
* - number: do display but start at the given number
* @returns Observable<string> - Observable of formatted code
*/
formatCode(code: string, language?: string, linenums?: number | boolean) {
formatCode(code: TrustedHTML, language?: string, linenums?: number|boolean) {
return this.prettyPrintOne.pipe(
map(ppo => {
try {
return ppo(code, language, linenums);
return htmlFromStringKnownToSatisfyTypeContract(
ppo(code, language, linenums), 'prettify.js modifies already trusted HTML inline');
} catch (err) {
const msg = `Could not format code that begins '${code.substr(0, 50)}...'.`;
const msg = `Could not format code that begins '${code.toString().substr(0, 50)}...'.`;
console.error(msg, err);
throw new Error(msg);
}
Expand Down
6 changes: 5 additions & 1 deletion aio/src/app/custom-elements/lazy-custom-element.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Component, ElementRef, Input, OnInit } from '@angular/core';
import { Logger } from 'app/shared/logger.service';
import {unwrapHtmlForSink} from 'safevalues';
import {htmlFromStringKnownToSatisfyTypeContract} from 'safevalues/unsafe/reviewed';
import { ElementsLoader } from './elements-loader';

@Component({
Expand All @@ -21,7 +23,9 @@ export class LazyCustomElementComponent implements OnInit {
return;
}

this.elementRef.nativeElement.innerHTML = `<${this.selector}></${this.selector}>`;
this.elementRef.nativeElement.innerHTML =
unwrapHtmlForSink(htmlFromStringKnownToSatisfyTypeContract(
`<${this.selector}></${this.selector}>`, 'selector is validated'));
this.elementsLoader.loadCustomElement(this.selector);
}
}
9 changes: 8 additions & 1 deletion aio/src/app/documents/document-contents.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
export interface DocumentContents {
export interface UnsafeDocumentContents {
/** The unique identifier for this document */
id: string;
/** The HTML to display in the doc viewer */
contents: string|null;
}

export interface DocumentContents {
/** The unique identifier for this document */
id: string;
/** The HTML to display in the doc viewer */
contents: TrustedHTML|null;
}
Loading