From b21125ff2dfc1036d69f9c1fdb7b5fb1d84098d6 Mon Sep 17 00:00:00 2001 From: Kevin Cunnane Date: Tue, 30 Apr 2019 14:21:20 -0700 Subject: [PATCH] Fix markdown security and enable most CSS (#5263) * Fix markdown security and enable most CSS Stops using our sanitizer and instead disables HTML in markdown engine - This was blocking Note because it converted > to > - It's slightly more strict in that it fully disables HTML unless trusted. Will need to improve handling of Trusted to support this in a future PR Adds in correct CSS, both from .css file in markdown extension and from built-into all webviews global CSS - Fix #3765 standard markdown support - Fix Support of Notes by bringing correct styles - Fix code block colorization - Fix link handling so it's not bolded / gets underlined on hover - Fixes table rendering (for markdown and HTML tables) * Reduce scope of CSS changes - Removed some CSS that wasn't needed or caused issues - Scoped most things under the preview section not the whole component * Avoid markdown html block by sanitizing after render * Fix pre node not overflowing - This was a bug in existing implementation too --- build/gulpfile.hygiene.js | 2 +- .../notebook/cellViews/media/highlight.css | 182 ++++++++++++++ .../notebook/cellViews/media/markdown.css | 231 ++++++++++++++++++ .../notebook/cellViews/textCell.component.ts | 12 +- .../parts/notebook/notebookStyles.ts | 42 +++- 5 files changed, 462 insertions(+), 7 deletions(-) create mode 100644 src/sql/workbench/parts/notebook/cellViews/media/highlight.css create mode 100644 src/sql/workbench/parts/notebook/cellViews/media/markdown.css diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index a7521f66c2..1d9c521ba3 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -134,7 +134,7 @@ const copyrightFilter = [ '!src/sql/workbench/parts/notebook/outputs/common/renderMimeInterfaces.ts', '!src/sql/workbench/parts/notebook/outputs/common/outputProcessor.ts', '!src/sql/workbench/parts/notebook/outputs/common/mimemodel.ts', - '!src/sql/workbench/parts/notebook/cellViews/media/output.css', + '!src/sql/workbench/parts/notebook/cellViews/media/*.css', '!src/sql/base/browser/ui/table/plugins/rowSelectionModel.plugin.ts', '!src/sql/base/browser/ui/table/plugins/rowDetailView.ts', '!src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts', diff --git a/src/sql/workbench/parts/notebook/cellViews/media/highlight.css b/src/sql/workbench/parts/notebook/cellViews/media/highlight.css new file mode 100644 index 0000000000..e7fbed4d8a --- /dev/null +++ b/src/sql/workbench/parts/notebook/cellViews/media/highlight.css @@ -0,0 +1,182 @@ +/* +https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs2015.css +*/ +/* + * Visual Studio 2015 dark style + * Author: Nicolas LLOBERA + */ + + +.notebook-preview .hljs-keyword, +.notebook-preview .hljs-literal, +.notebook-preview .hljs-symbol, +.notebook-preview .hljs-name { + color: #569CD6; +} +.notebook-preview .hljs-link { + color: #569CD6; + text-decoration: underline; +} + +.notebook-preview .hljs-built_in, +.notebook-preview .hljs-type { + color: #4EC9B0; +} + +.notebook-preview .hljs-number, +.notebook-preview .hljs-class { + color: #B8D7A3; +} + +.notebook-preview .hljs-string, +.notebook-preview .hljs-meta-string { + color: #D69D85; +} + +.notebook-preview .hljs-regexp, +.notebook-preview .hljs-template-tag { + color: #9A5334; +} + +.notebook-preview .hljs-subst, +.notebook-preview .hljs-function, +.notebook-preview .hljs-title, +.notebook-preview .hljs-params, +.notebook-preview .hljs-formula { + color: #DCDCDC; +} + +.notebook-preview .hljs-comment, +.notebook-preview .hljs-quote { + color: #57A64A; + font-style: italic; +} + +.notebook-preview .hljs-doctag { + color: #608B4E; +} + +.notebook-preview .hljs-meta, +.notebook-preview .hljs-meta-keyword, +.notebook-preview .hljs-tag { + color: #9B9B9B; +} + +.notebook-preview .hljs-variable, +.notebook-preview .hljs-template-variable { + color: #BD63C5; +} + +.notebook-preview .hljs-attr, +.notebook-preview .hljs-attribute, +.notebook-preview .hljs-builtin-name { + color: #9CDCFE; +} + +.notebook-preview .hljs-section { + color: gold; +} + +.notebook-preview .hljs-emphasis { + font-style: italic; +} + +.notebook-preview .hljs-strong { + font-weight: bold; +} + +/*.hljs-code { + font-family:'Monospace'; +}*/ + +.notebook-preview .hljs-bullet, +.notebook-preview .hljs-selector-tag, +.notebook-preview .hljs-selector-id, +.notebook-preview .hljs-selector-class, +.notebook-preview .hljs-selector-attr, +.notebook-preview .hljs-selector-pseudo { + color: #D7BA7D; +} + +.notebook-preview .hljs-addition { + background-color: var(--vscode-diffEditor-insertedTextBackground, rgba(155, 185, 85, 0.2)); + color: rgb(155, 185, 85); + display: inline-block; + width: 100%; +} + +.notebook-preview .hljs-deletion { + background: var(--vscode-diffEditor-removedTextBackground, rgba(255, 0, 0, 0.2)); + color: rgb(255, 0, 0); + display: inline-block; + width: 100%; +} + + +/* +From https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs.css +*/ +/* + +Visual Studio-like style based on original C# coloring by Jason Diamond + +*/ + +.notebook-preview .hljs-function, +.notebook-preview .hljs-params { + color: inherit; +} + +.notebook-preview .hljs-comment, +.notebook-preview .hljs-quote, +.notebook-preview .hljs-variable { + color: #008000; +} + +.notebook-preview .hljs-keyword, +.notebook-preview .hljs-selector-tag, +.notebook-preview .hljs-built_in, +.notebook-preview .hljs-name, +.notebook-preview .hljs-tag { + color: #00f; +} + +.notebook-preview .hljs-string, +.notebook-preview .hljs-title, +.notebook-preview .hljs-section, +.notebook-preview .hljs-attribute, +.notebook-preview .hljs-literal, +.notebook-preview .hljs-template-tag, +.notebook-preview .hljs-template-variable, +.notebook-preview .hljs-type { + color: #a31515; +} + +.notebook-preview .hljs-selector-attr, +.notebook-preview .hljs-selector-pseudo, +.notebook-preview .hljs-meta { + color: #2b91af; +} + +.notebook-preview .hljs-doctag { + color: #808080; +} + +.notebook-preview .hljs-attr { + color: #f00; +} + +.notebook-preview .hljs-symbol, +.notebook-preview .hljs-bullet, +.notebook-preview .hljs-link { + color: #00b0e8; +} + + +.notebook-preview .hljs-emphasis { + font-style: italic; +} + +.notebook-preview .hljs-strong { + font-weight: bold; +} \ No newline at end of file diff --git a/src/sql/workbench/parts/notebook/cellViews/media/markdown.css b/src/sql/workbench/parts/notebook/cellViews/media/markdown.css new file mode 100644 index 0000000000..30c469860b --- /dev/null +++ b/src/sql/workbench/parts/notebook/cellViews/media/markdown.css @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.notebook-preview { + font-size: 14px; + line-height: 22px; + word-wrap: break-word; +} + +.notebook-preview #code-csp-warning { + position: fixed; + top: 0; + right: 0; + color: white; + margin: 16px; + text-align: center; + font-size: 12px; + font-family: sans-serif; + background-color:#444444; + cursor: pointer; + padding: 6px; + box-shadow: 1px 1px 1px rgba(0,0,0,.25); +} + +.notebook-preview #code-csp-warning:hover { + text-decoration: none; + background-color:#007acc; + box-shadow: 2px 2px 2px rgba(0,0,0,.25); +} + + +.notebook-preview .scrollBeyondLastLine { + margin-bottom: calc(100vh - 22px); +} + +.notebook-preview .showEditorSelection .code-line { + position: relative; +} + +.notebook-preview .showEditorSelection .code-active-line:before, +.notebook-preview .showEditorSelection .code-line:hover:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: -12px; + height: 100%; +} + +.notebook-preview .showEditorSelection li.code-active-line:before, +.notebook-preview .showEditorSelection li.code-line:hover:before { + left: -30px; +} + +.notebook-preview .showEditorSelection .code-active-line:before { + border-left: 3px solid rgba(0, 0, 0, 0.15); +} + +.notebook-preview .showEditorSelection .code-line:hover:before { + border-left: 3px solid rgba(0, 0, 0, 0.40); +} + +.notebook-preview .showEditorSelection .code-line .code-line:hover:before { + border-left: none; +} + +.vs-dark .notebook-preview .showEditorSelection .code-active-line:before { + border-left: 3px solid rgba(255, 255, 255, 0.4); +} + +.vs-dark .notebook-preview .showEditorSelection .code-line:hover:before { + border-left: 3px solid rgba(255, 255, 255, 0.60); +} + +.vs-dark .notebook-preview .showEditorSelection .code-line .code-line:hover:before { + border-left: none; +} + +.hc-black .notebook-preview .showEditorSelection .code-active-line:before { + border-left: 3px solid rgba(255, 160, 0, 0.7); +} + +.hc-black .notebook-preview .showEditorSelection .code-line:hover:before { + border-left: 3px solid rgba(255, 160, 0, 1); +} + +.hc-black .notebook-preview .showEditorSelection .code-line .code-line:hover:before { + border-left: none; +} + +.notebook-preview img { + max-width: 100%; + max-height: 100%; +} + +.notebookEditor a { + text-decoration: none; +} + +.notebookEditor a:hover { + text-decoration: underline; +} + +.notebook-preview a:focus, +.notebook-preview input:focus, +.notebook-preview select:focus, +.notebook-preview textarea:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; +} + +.notebook-preview hr { + border: 0; + height: 2px; + border-bottom: 2px solid; +} + +.notebook-preview h1 { + padding-bottom: 0.3em; + line-height: 1.2; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +.notebook-preview h1, .notebook-preview h2, .notebook-preview h3 { + font-weight: normal; +} + +.notebook-preview h1 code, +.notebook-preview h2 code, +.notebook-preview h3 code, +.notebook-preview h4 code, +.notebook-preview h5 code, +.notebook-preview h6 code { + font-size: inherit; + line-height: auto; +} + +.notebook-preview table { + border-collapse: collapse; +} + +.notebook-preview table > thead > tr > th { + text-align: left; + border-bottom: 1px solid; +} + +.notebook-preview table > thead > tr > th, +.notebook-preview table > thead > tr > td, +.notebook-preview table > tbody > tr > th, +.notebook-preview .notebook-preview table > tbody > tr > td { + padding: 5px 10px; +} + +.notebook-preview table > tbody > tr + tr > td { + border-top: 1px solid; +} + +.notebook-preview blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; +} + +.notebook-preview code { + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-size: 12px; + line-height: 19px; +} + +.notebook-preview pre { + white-space: pre-wrap; +} + +.notebook-preview .mac code { + font-size: 12px; + line-height: 18px; +} + +.notebook-preview pre:not(.hljs), +.notebook-preview pre.hljs code > div { + padding: 16px; + border-radius: 3px; + overflow: auto; +} + +/** Theming */ + +.notebook-preview pre code { + color: var(--vscode-editor-foreground); +} + + +.notebook-preview pre { + background-color: rgba(220, 220, 220, 0.4); +} + +.vs-dark .notebook-preview pre { + background-color: rgba(10, 10, 10, 0.4); +} + +.hc-black .notebook-preview pre { + background-color: rgb(0, 0, 0); +} + +.hc-black .notebook-preview h1 { + border-color: rgb(0, 0, 0); +} + +.notebook-preview table > thead > tr > th { + border-color: rgba(0, 0, 0, 0.69); +} + +.vs-dark .notebook-preview table > thead > tr > th { + border-color: rgba(255, 255, 255, 0.69); +} + +.notebook-preview h1, +.notebook-preview hr, +.notebook-preview table > tbody > tr + tr > td { + border-color: rgba(0, 0, 0, 0.18); +} + +.vs-dark .notebook-preview h1, +.vs-dark .notebook-preview hr, +.vs-dark .notebook-preview table > tbody > tr + tr > td { + border-color: rgba(255, 255, 255, 0.18); +} diff --git a/src/sql/workbench/parts/notebook/cellViews/textCell.component.ts b/src/sql/workbench/parts/notebook/cellViews/textCell.component.ts index 65b191591e..9d9412efee 100644 --- a/src/sql/workbench/parts/notebook/cellViews/textCell.component.ts +++ b/src/sql/workbench/parts/notebook/cellViews/textCell.component.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./textCell'; +import 'vs/css!./media/markdown'; +import 'vs/css!./media/highlight'; import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, OnChanges, SimpleChange, HostListener, AfterContentInit } from '@angular/core'; import * as path from 'path'; @@ -66,6 +68,7 @@ export class TextCellComponent extends CellView implements OnInit, AfterContentI } private _content: string; + private _lastTrustedMode: boolean; private isEditMode: boolean; private _sanitizer: ISanitizer; private _model: NotebookModel; @@ -192,15 +195,19 @@ export class TextCellComponent extends CellView implements OnInit, AfterContentI * Sanitizes the data to be shown in markdown cell */ private updatePreview() { - if (this._content !== this.cellModel.source || this.cellModel.source.length === 0) { + let trustedChanged = this.cellModel && this._lastTrustedMode !== this.cellModel.trustedMode; + let contentChanged = this._content !== this.cellModel.source || this.cellModel.source.length === 0; + if (trustedChanged || contentChanged) { + this._lastTrustedMode = this.cellModel.trustedMode; if (!this.cellModel.source && !this.isEditMode) { this._content = localize('doubleClickEdit', 'Double-click to edit'); } else { - this._content = this.sanitizeContent(this.cellModel.source); + this._content = this.cellModel.source; } this._commandService.executeCommand('notebook.showPreview', this.cellModel.notebookModel.notebookUri, this._content).then((htmlcontent) => { htmlcontent = this.convertVscodeResourceToFileInSubDirectories(htmlcontent); + htmlcontent = this.sanitizeContent(htmlcontent); let outputElement = this.output.nativeElement; outputElement.innerHTML = htmlcontent; }); @@ -214,7 +221,6 @@ export class TextCellComponent extends CellView implements OnInit, AfterContentI } return content; } - // Only replace vscode-resource with file when in the same (or a sub) directory // This matches Jupyter Notebook viewer behavior private convertVscodeResourceToFileInSubDirectories(htmlContent: string): string { diff --git a/src/sql/workbench/parts/notebook/notebookStyles.ts b/src/sql/workbench/parts/notebook/notebookStyles.ts index 0934ec3b01..2ca4003a37 100644 --- a/src/sql/workbench/parts/notebook/notebookStyles.ts +++ b/src/sql/workbench/parts/notebook/notebookStyles.ts @@ -6,7 +6,7 @@ import 'vs/css!./notebook'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND } from 'vs/workbench/common/theme'; -import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vscode-xterm'; import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; @@ -179,8 +179,6 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi if (linkForeground) { collector.addRule(` .notebookEditor a:link { - text-decoration: none; - font-weight: bold; color: ${linkForeground}; } `); @@ -195,5 +193,43 @@ export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDi } `); } + + // Styling for markdown cells & links in notebooks. + // This matches the values used by default in all web views + if (linkForeground) { + collector.addRule(` + .notebookEditor a:link { + color: ${linkForeground}; + } + `); + } + let activeForeground = theme.getColor(textLinkActiveForeground); + if (activeForeground) { + collector.addRule(` + .notebookEditor a:hover { + color: ${activeForeground}; + } + `); + } + let preformatForeground = theme.getColor(textPreformatForeground); + if (preformatForeground) { + collector.addRule(` + .notebook-preview code { + color: ${preformatForeground}; + } + `); + } + + + let blockQuoteBackground = theme.getColor(textBlockQuoteBackground); + let blockQuoteBorder = theme.getColor(textBlockQuoteBorder); + if (preformatForeground) { + collector.addRule(` + .notebookEditor blockquote { + background: ${blockQuoteBackground}; + border-color: ${blockQuoteBorder}; + } + `); + } }); } \ No newline at end of file