Switch to dompurify for sanitizing markdown content (#131950)
* Switch to dompurify for sanitizing markdown content Switches us from using `insane` to instead use `dompurify`, which seems to be better maintained and also has some nice features, such as built-in trusted types support I've tried to port over our existing sanitizer settings as best as possible, but there's not always a 1:1 mapping between how insane works and how dompurify does. I'd like to get this change in early in the iteration to catch potential regressions * Remove logging and renaming param * Move dompurify to browser layer * Fixing tests and how we check valid attributes * Allow innerhtml in specific files * Use isEqualNode instead of checking innerHTML directly innerHTML can return different results on different browsers. Use `isEqualNode` instead * Reapply fix for trusted types * Enable ALLOW_UNKNOWN_PROTOCOLS I beleive this is required since we allow links to commands and loading images over remote * in -> of * Fix check of protocol * Enable two more safe tags
This commit is contained in:
parent
82a3d2645f
commit
474d4951d8
|
@ -3,7 +3,7 @@
|
|||
**/vs/css.build.js
|
||||
**/vs/css.js
|
||||
**/vs/loader.js
|
||||
**/insane/**
|
||||
**/dompurify/**
|
||||
**/marked/**
|
||||
**/semver/**
|
||||
**/test/**/*.js
|
||||
|
|
|
@ -37,7 +37,7 @@ module.exports.indentationFilter = [
|
|||
'!src/vs/css.js',
|
||||
'!src/vs/css.build.js',
|
||||
'!src/vs/loader.js',
|
||||
'!src/vs/base/common/insane/insane.js',
|
||||
'!src/vs/base/browser/dompurify/*',
|
||||
'!src/vs/base/common/marked/marked.js',
|
||||
'!src/vs/base/common/semver/semver.js',
|
||||
'!src/vs/base/node/terminateProcess.sh',
|
||||
|
@ -134,7 +134,7 @@ module.exports.jsHygieneFilter = [
|
|||
'!src/vs/nls.js',
|
||||
'!src/vs/css.build.js',
|
||||
'!src/vs/nls.build.js',
|
||||
'!src/**/insane.js',
|
||||
'!src/**/dompurify.js',
|
||||
'!src/**/marked.js',
|
||||
'!src/**/semver.js',
|
||||
'!**/test/**',
|
||||
|
|
|
@ -29,5 +29,8 @@
|
|||
"ban-worker-calls": [
|
||||
"vs/base/worker/defaultWorkerFactory.ts",
|
||||
"vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts"
|
||||
],
|
||||
"ban-domparser-parsefromstring": [
|
||||
"vs/base/test/browser/markdownRenderer.test.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
|||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { insane, InsaneOptions } from 'vs/base/common/insane/insane';
|
||||
import * as dompurify from 'vs/base/browser/dompurify/dompurify';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileAccess, RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -1361,52 +1361,41 @@ export function detectFullscreen(): IDetectedFullscreen | null {
|
|||
|
||||
// -- sanitize and trusted html
|
||||
|
||||
function _extInsaneOptions(opts: InsaneOptions, allowedAttributesForAll: string[]): InsaneOptions {
|
||||
|
||||
let allowedAttributes: Record<string, string[]> = opts.allowedAttributes ?? {};
|
||||
|
||||
if (opts.allowedTags) {
|
||||
for (let tag of opts.allowedTags) {
|
||||
let array = allowedAttributes[tag];
|
||||
if (!array) {
|
||||
array = allowedAttributesForAll;
|
||||
} else {
|
||||
array = array.concat(allowedAttributesForAll);
|
||||
}
|
||||
allowedAttributes[tag] = array;
|
||||
}
|
||||
}
|
||||
|
||||
return { ...opts, allowedAttributes };
|
||||
}
|
||||
|
||||
const _ttpSafeInnerHtml = window.trustedTypes?.createPolicy('safeInnerHtml', {
|
||||
createHTML(value, options: InsaneOptions) {
|
||||
return insane(value, options);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Sanitizes the given `value` and reset the given `node` with it.
|
||||
*/
|
||||
export function safeInnerHtml(node: HTMLElement, value: string): void {
|
||||
const options: dompurify.Config = {
|
||||
ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
|
||||
ALLOWED_ATTR: ['href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code', 'width', 'height', 'align', 'x-dispatch', 'required', 'checked', 'placeholder'],
|
||||
RETURN_DOM: false,
|
||||
RETURN_DOM_FRAGMENT: false,
|
||||
};
|
||||
|
||||
const options = _extInsaneOptions({
|
||||
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
|
||||
allowedAttributes: {
|
||||
'a': ['href', 'x-dispatch'],
|
||||
'button': ['data-href', 'x-dispatch'],
|
||||
'input': ['type', 'placeholder', 'checked', 'required'],
|
||||
'label': ['for'],
|
||||
'select': ['required'],
|
||||
'span': ['data-command', 'role'],
|
||||
'textarea': ['name', 'placeholder', 'required'],
|
||||
},
|
||||
allowedSchemes: ['http', 'https', 'command']
|
||||
}, ['class', 'id', 'role', 'tabindex']);
|
||||
const allowedProtocols = [Schemas.http, Schemas.https, Schemas.command];
|
||||
|
||||
const html = _ttpSafeInnerHtml?.createHTML(value, options) ?? insane(value, options);
|
||||
node.innerHTML = html as string;
|
||||
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
|
||||
dompurify.addHook('afterSanitizeAttributes', (node) => {
|
||||
// build an anchor to map URLs to
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
// check all href/src attributes for validity
|
||||
for (const attr in ['href', 'src']) {
|
||||
if (node.hasAttribute(attr)) {
|
||||
anchor.href = node.getAttribute(attr) as string;
|
||||
if (!allowedProtocols.includes(anchor.protocol)) {
|
||||
node.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const html = dompurify.sanitize(value, { ...options, RETURN_TRUSTED_TYPE: true });
|
||||
node.innerHTML = html as unknown as string;
|
||||
} finally {
|
||||
dompurify.removeHook('afterSanitizeAttributes');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
17
src/vs/base/browser/dompurify/cgmanifest.json
Normal file
17
src/vs/base/browser/dompurify/cgmanifest.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"name": "dompurify",
|
||||
"repositoryUrl": "https://github.com/cure53/DOMPurify",
|
||||
"commitHash": "6cfcdf56269b892550af80baa7c1fa5b680e5db7"
|
||||
}
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
"version": "2.3.1"
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
104
src/vs/base/browser/dompurify/dompurify.d.ts
vendored
Normal file
104
src/vs/base/browser/dompurify/dompurify.d.ts
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Type definitions for DOM Purify 2.2
|
||||
// Project: https://github.com/cure53/DOMPurify
|
||||
// Definitions by: Dave Taylor https://github.com/davetayls
|
||||
// Samira Bazuzi <https://github.com/bazuzi>
|
||||
// FlowCrypt <https://github.com/FlowCrypt>
|
||||
// Exigerr <https://github.com/Exigerr>
|
||||
// Piotr Błażejewicz <https://github.com/peterblazejewicz>
|
||||
// Nicholas Ellul <https://github.com/NicholasEllul>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
export as namespace DOMPurify;
|
||||
export = DOMPurify;
|
||||
|
||||
declare const DOMPurify: createDOMPurifyI;
|
||||
|
||||
interface createDOMPurifyI extends DOMPurify.DOMPurifyI {
|
||||
(window?: Window): DOMPurify.DOMPurifyI;
|
||||
}
|
||||
|
||||
declare namespace DOMPurify {
|
||||
interface DOMPurifyI {
|
||||
sanitize(source: string | Node): string;
|
||||
sanitize(source: string | Node, config: Config & { RETURN_TRUSTED_TYPE: true }): TrustedHTML;
|
||||
sanitize(source: string | Node, config: Config & { RETURN_DOM_FRAGMENT?: false | undefined; RETURN_DOM?: false | undefined }): string;
|
||||
sanitize(source: string | Node, config: Config & { RETURN_DOM_FRAGMENT: true }): DocumentFragment;
|
||||
sanitize(source: string | Node, config: Config & { RETURN_DOM: true }): HTMLElement;
|
||||
sanitize(source: string | Node, config: Config): string | HTMLElement | DocumentFragment;
|
||||
|
||||
addHook(hook: 'uponSanitizeElement', cb: (currentNode: Element, data: SanitizeElementHookEvent, config: Config) => void): void;
|
||||
addHook(hook: 'uponSanitizeAttribute', cb: (currentNode: Element, data: SanitizeAttributeHookEvent, config: Config) => void): void;
|
||||
addHook(hook: HookName, cb: (currentNode: Element, data: HookEvent, config: Config) => void): void;
|
||||
|
||||
setConfig(cfg: Config): void;
|
||||
clearConfig(): void;
|
||||
isValidAttribute(tag: string, attr: string, value: string): boolean;
|
||||
|
||||
removeHook(entryPoint: HookName): void;
|
||||
removeHooks(entryPoint: HookName): void;
|
||||
removeAllHooks(): void;
|
||||
|
||||
version: string;
|
||||
removed: any[];
|
||||
isSupported: boolean;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
ADD_ATTR?: string[] | undefined;
|
||||
ADD_DATA_URI_TAGS?: string[] | undefined;
|
||||
ADD_TAGS?: string[] | undefined;
|
||||
ALLOW_DATA_ATTR?: boolean | undefined;
|
||||
ALLOWED_ATTR?: string[] | undefined;
|
||||
ALLOWED_TAGS?: string[] | undefined;
|
||||
FORBID_ATTR?: string[] | undefined;
|
||||
FORBID_TAGS?: string[] | undefined;
|
||||
FORCE_BODY?: boolean | undefined;
|
||||
KEEP_CONTENT?: boolean | undefined;
|
||||
/**
|
||||
* change the default namespace from HTML to something different
|
||||
*/
|
||||
NAMESPACE?: string | undefined;
|
||||
RETURN_DOM?: boolean | undefined;
|
||||
RETURN_DOM_FRAGMENT?: boolean | undefined;
|
||||
/**
|
||||
* This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
|
||||
* might cause XSS from attacks hidden in closed shadowroots in case the browser
|
||||
* supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
|
||||
*/
|
||||
RETURN_DOM_IMPORT?: boolean | undefined;
|
||||
RETURN_TRUSTED_TYPE?: boolean | undefined;
|
||||
SANITIZE_DOM?: boolean | undefined;
|
||||
WHOLE_DOCUMENT?: boolean | undefined;
|
||||
ALLOWED_URI_REGEXP?: RegExp | undefined;
|
||||
SAFE_FOR_TEMPLATES?: boolean | undefined;
|
||||
ALLOW_UNKNOWN_PROTOCOLS?: boolean | undefined;
|
||||
USE_PROFILES?: false | { mathMl?: boolean | undefined; svg?: boolean | undefined; svgFilters?: boolean | undefined; html?: boolean | undefined } | undefined;
|
||||
IN_PLACE?: boolean | undefined;
|
||||
}
|
||||
|
||||
type HookName =
|
||||
| 'beforeSanitizeElements'
|
||||
| 'uponSanitizeElement'
|
||||
| 'afterSanitizeElements'
|
||||
| 'beforeSanitizeAttributes'
|
||||
| 'uponSanitizeAttribute'
|
||||
| 'afterSanitizeAttributes'
|
||||
| 'beforeSanitizeShadowDOM'
|
||||
| 'uponSanitizeShadowNode'
|
||||
| 'afterSanitizeShadowDOM';
|
||||
|
||||
type HookEvent = SanitizeElementHookEvent | SanitizeAttributeHookEvent | null;
|
||||
|
||||
interface SanitizeElementHookEvent {
|
||||
tagName: string;
|
||||
allowedTags: { [key: string]: boolean };
|
||||
}
|
||||
|
||||
interface SanitizeAttributeHookEvent {
|
||||
attrName: string;
|
||||
attrValue: string;
|
||||
keepAttr: boolean;
|
||||
allowedAttributes: { [key: string]: boolean };
|
||||
forceKeepAttr?: boolean | undefined;
|
||||
}
|
||||
}
|
1376
src/vs/base/browser/dompurify/dompurify.js
Normal file
1376
src/vs/base/browser/dompurify/dompurify.js
Normal file
File diff suppressed because it is too large
Load diff
377
src/vs/base/browser/dompurify/dompurify.license.txt
Normal file
377
src/vs/base/browser/dompurify/dompurify.license.txt
Normal file
|
@ -0,0 +1,377 @@
|
|||
DOMPurify
|
||||
Copyright 2015 Mario Heiderich
|
||||
|
||||
DOMPurify is free software; you can redistribute it and/or modify it under the
|
||||
terms of either:
|
||||
|
||||
a) the Apache License Version 2.0, or
|
||||
b) the Mozilla Public License Version 2.0
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as dompurify from 'vs/base/browser/dompurify/dompurify';
|
||||
import { DomEmitter } from 'vs/base/browser/event';
|
||||
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
@ -13,7 +14,6 @@ import { Event } from 'vs/base/common/event';
|
|||
import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
|
||||
import { markdownEscapeEscapedIcons } from 'vs/base/common/iconLabels';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { insane, InsaneOptions } from 'vs/base/common/insane/insane';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
|
@ -32,12 +32,6 @@ export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
|||
baseUrl?: URI;
|
||||
}
|
||||
|
||||
const _ttpInsane = window.trustedTypes?.createPolicy('insane', {
|
||||
createHTML(value, options: InsaneOptions): string {
|
||||
return insane(value, options);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Low-level way create a html element from a markdown string.
|
||||
*
|
||||
|
@ -220,7 +214,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
// Use our own sanitizer so that we can let through only spans.
|
||||
// Otherwise, we'd be letting all html be rendered.
|
||||
// If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize.
|
||||
// We always pass the output through insane after this so that we don't rely on
|
||||
// We always pass the output through dompurify after this so that we don't rely on
|
||||
// marked for sanitization.
|
||||
markedOptions.sanitizer = (html: string): string => {
|
||||
const match = markdown.isTrusted ? html.match(/^(<span[^>]+>)|(<\/\s*span>)$/) : undefined;
|
||||
|
@ -242,9 +236,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
}
|
||||
|
||||
const renderedMarkdown = marked.parse(value, markedOptions);
|
||||
|
||||
// sanitize with insane
|
||||
element.innerHTML = sanitizeRenderedMarkdown(markdown, renderedMarkdown) as string;
|
||||
element.innerHTML = sanitizeRenderedMarkdown(markdown, renderedMarkdown) as unknown as string;
|
||||
|
||||
// signal that async code blocks can be now be inserted
|
||||
signalInnerHTML!();
|
||||
|
@ -266,12 +258,49 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
function sanitizeRenderedMarkdown(
|
||||
options: { isTrusted?: boolean },
|
||||
renderedMarkdown: string,
|
||||
): string | TrustedHTML {
|
||||
const insaneOptions = getInsaneOptions(options);
|
||||
return _ttpInsane?.createHTML(renderedMarkdown, insaneOptions) ?? insane(renderedMarkdown, insaneOptions);
|
||||
): TrustedHTML {
|
||||
const { config, allowedSchemes } = getSanitizerOptions(options);
|
||||
dompurify.addHook('uponSanitizeAttribute', (element, e) => {
|
||||
if (e.attrName === 'style' || e.attrName === 'class') {
|
||||
if (element.tagName === 'SPAN') {
|
||||
if (e.attrName === 'style') {
|
||||
e.keepAttr = /^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/.test(e.attrValue);
|
||||
return;
|
||||
} else if (e.attrName === 'class') {
|
||||
e.keepAttr = /^codicon codicon-[a-z\-]+( codicon-modifier-[a-z\-]+)?$/.test(e.attrValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.keepAttr = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// build an anchor to map URLs to
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
|
||||
dompurify.addHook('afterSanitizeAttributes', (node) => {
|
||||
// check all href/src attributes for validity
|
||||
for (const attr of ['href', 'src']) {
|
||||
if (node.hasAttribute(attr)) {
|
||||
anchor.href = node.getAttribute(attr) as string;
|
||||
if (!allowedSchemes.includes(anchor.protocol.replace(/:$/, ''))) {
|
||||
node.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
return dompurify.sanitize(renderedMarkdown, { ...config, RETURN_TRUSTED_TYPE: true });
|
||||
} finally {
|
||||
dompurify.removeHook('uponSanitizeAttribute');
|
||||
dompurify.removeHook('afterSanitizeAttributes');
|
||||
}
|
||||
}
|
||||
|
||||
function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOptions {
|
||||
function getSanitizerOptions(options: { readonly isTrusted?: boolean }): { config: dompurify.Config, allowedSchemes: string[] } {
|
||||
const allowedSchemes = [
|
||||
Schemas.http,
|
||||
Schemas.https,
|
||||
|
@ -288,33 +317,16 @@ function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOpti
|
|||
}
|
||||
|
||||
return {
|
||||
allowedSchemes,
|
||||
// allowedTags should included everything that markdown renders to.
|
||||
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
|
||||
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
|
||||
// HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension-
|
||||
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
|
||||
allowedAttributes: {
|
||||
'a': ['href', 'name', 'target', 'data-href'],
|
||||
'img': ['src', 'title', 'alt', 'width', 'height'],
|
||||
'div': ['class', 'data-code'],
|
||||
'span': ['class', 'style'],
|
||||
// https://github.com/microsoft/vscode/issues/95937
|
||||
'th': ['align'],
|
||||
'td': ['align']
|
||||
config: {
|
||||
// allowedTags should included everything that markdown renders to.
|
||||
// Since we have our own sanitize function for marked, it's possible we missed some tag so let dompurify make sure.
|
||||
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
|
||||
// HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension-
|
||||
ALLOWED_TAGS: ['ul', 'li', 'p', 'b', 'i', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
|
||||
ALLOWED_ATTR: ['href', 'data-href', 'target', 'title', 'src', 'alt', 'class', 'style', 'data-code', 'width', 'height', 'align'],
|
||||
ALLOW_UNKNOWN_PROTOCOLS: true,
|
||||
},
|
||||
filter(token: { tag: string; attrs: { readonly [key: string]: string; }; }): boolean {
|
||||
if (token.tag === 'span' && options.isTrusted) {
|
||||
if (token.attrs['style'] && (Object.keys(token.attrs).length === 1)) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
} else if (token.attrs['class']) {
|
||||
// The class should match codicon rendering in src\vs\base\common\codicons.ts
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-modifier-[a-z\-]+)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
allowedSchemes
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "git",
|
||||
"git": {
|
||||
"name": "insane",
|
||||
"repositoryUrl": "https://github.com/bevacqua/insane",
|
||||
"commitHash": "7f5a809f44a37e7d11ee5343b2d8bca4be6ba4ae"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "2.6.2"
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
17
src/vs/base/common/insane/insane.d.ts
vendored
17
src/vs/base/common/insane/insane.d.ts
vendored
|
@ -1,17 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface InsaneOptions {
|
||||
readonly allowedSchemes?: readonly string[],
|
||||
readonly allowedTags?: readonly string[],
|
||||
readonly allowedAttributes?: { readonly [key: string]: string[] },
|
||||
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
|
||||
}
|
||||
|
||||
export function insane(
|
||||
html: string,
|
||||
options?: InsaneOptions,
|
||||
strict?: boolean,
|
||||
): string;
|
|
@ -1,474 +0,0 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2015 Nicolas Bevacqua
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
let __insane_func;
|
||||
|
||||
(function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw a.code = "MODULE_NOT_FOUND", a } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = "function" == typeof require && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({
|
||||
1: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var toMap = require('./toMap');
|
||||
var uris = ['background', 'base', 'cite', 'href', 'longdesc', 'src', 'usemap'];
|
||||
|
||||
module.exports = {
|
||||
uris: toMap(uris) // attributes that have an href and hence need to be sanitized
|
||||
};
|
||||
|
||||
}, { "./toMap": 10 }], 2: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var defaults = {
|
||||
allowedAttributes: {
|
||||
'*': ['title', 'accesskey'],
|
||||
a: ['href', 'name', 'target', 'aria-label'],
|
||||
iframe: ['allowfullscreen', 'frameborder', 'src'],
|
||||
img: ['src', 'alt', 'title', 'aria-label']
|
||||
},
|
||||
allowedClasses: {},
|
||||
allowedSchemes: ['http', 'https', 'mailto'],
|
||||
allowedTags: [
|
||||
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark',
|
||||
'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table',
|
||||
'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
|
||||
],
|
||||
filter: null
|
||||
};
|
||||
|
||||
module.exports = defaults;
|
||||
|
||||
}, {}], 3: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var toMap = require('./toMap');
|
||||
var voids = ['area', 'br', 'col', 'hr', 'img', 'wbr', 'input', 'base', 'basefont', 'link', 'meta'];
|
||||
|
||||
module.exports = {
|
||||
voids: toMap(voids)
|
||||
};
|
||||
|
||||
}, { "./toMap": 10 }], 4: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var he = require('he');
|
||||
var assign = require('assignment');
|
||||
var parser = require('./parser');
|
||||
var sanitizer = require('./sanitizer');
|
||||
var defaults = require('./defaults');
|
||||
|
||||
function insane(html, options, strict) {
|
||||
var buffer = [];
|
||||
var configuration = strict === true ? options : assign({}, defaults, options);
|
||||
var handler = sanitizer(buffer, configuration);
|
||||
|
||||
parser(html, handler);
|
||||
|
||||
return buffer.join('');
|
||||
}
|
||||
|
||||
insane.defaults = defaults;
|
||||
module.exports = insane;
|
||||
__insane_func = insane;
|
||||
|
||||
}, { "./defaults": 2, "./parser": 7, "./sanitizer": 8, "assignment": 6, "he": 9 }], 5: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
module.exports = function lowercase(string) {
|
||||
return typeof string === 'string' ? string.toLowerCase() : string;
|
||||
};
|
||||
|
||||
}, {}], 6: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
function assignment(result) {
|
||||
var stack = Array.prototype.slice.call(arguments, 1);
|
||||
var item;
|
||||
var key;
|
||||
while (stack.length) {
|
||||
item = stack.shift();
|
||||
for (key in item) {
|
||||
if (item.hasOwnProperty(key)) {
|
||||
if (Object.prototype.toString.call(result[key]) === '[object Object]') {
|
||||
result[key] = assignment(result[key], item[key]);
|
||||
} else {
|
||||
result[key] = item[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = assignment;
|
||||
|
||||
}, {}], 7: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var he = require('he');
|
||||
var lowercase = require('./lowercase');
|
||||
var attributes = require('./attributes');
|
||||
var elements = require('./elements');
|
||||
var rstart = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/;
|
||||
var rend = /^<\s*\/\s*([\w:-]+)[^>]*>/;
|
||||
var rattrs = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g;
|
||||
var rtag = /^</;
|
||||
var rtagend = /^<\s*\//;
|
||||
|
||||
function createStack() {
|
||||
var stack = [];
|
||||
stack.lastItem = function lastItem() {
|
||||
return stack[stack.length - 1];
|
||||
};
|
||||
return stack;
|
||||
}
|
||||
|
||||
function parser(html, handler) {
|
||||
var stack = createStack();
|
||||
var last = html;
|
||||
var chars;
|
||||
|
||||
while (html) {
|
||||
parsePart();
|
||||
}
|
||||
parseEndTag(); // clean up any remaining tags
|
||||
|
||||
function parsePart() {
|
||||
chars = true;
|
||||
parseTag();
|
||||
|
||||
var same = html === last;
|
||||
last = html;
|
||||
|
||||
if (same) { // discard, because it's invalid
|
||||
html = '';
|
||||
}
|
||||
}
|
||||
|
||||
function parseTag() {
|
||||
if (html.substr(0, 4) === '<!--') { // comments
|
||||
parseComment();
|
||||
} else if (rtagend.test(html)) {
|
||||
parseEdge(rend, parseEndTag);
|
||||
} else if (rtag.test(html)) {
|
||||
parseEdge(rstart, parseStartTag);
|
||||
}
|
||||
parseTagDecode();
|
||||
}
|
||||
|
||||
function parseEdge(regex, parser) {
|
||||
var match = html.match(regex);
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(regex, parser);
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
function parseComment() {
|
||||
var index = html.indexOf('-->');
|
||||
if (index >= 0) {
|
||||
if (handler.comment) {
|
||||
handler.comment(html.substring(4, index));
|
||||
}
|
||||
html = html.substring(index + 3);
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
function parseTagDecode() {
|
||||
if (!chars) {
|
||||
return;
|
||||
}
|
||||
var text;
|
||||
var index = html.indexOf('<');
|
||||
if (index >= 0) {
|
||||
text = html.substring(0, index);
|
||||
html = html.substring(index);
|
||||
} else {
|
||||
text = html;
|
||||
html = '';
|
||||
}
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
}
|
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) {
|
||||
var attrs = {};
|
||||
var low = lowercase(tagName);
|
||||
var u = elements.voids[low] || !!unary;
|
||||
|
||||
rest.replace(rattrs, attrReplacer);
|
||||
|
||||
if (!u) {
|
||||
stack.push(low);
|
||||
}
|
||||
if (handler.start) {
|
||||
handler.start(low, attrs, u);
|
||||
}
|
||||
|
||||
function attrReplacer(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
||||
if (doubleQuotedValue === void 0 && singleQuotedValue === void 0 && unquotedValue === void 0) {
|
||||
attrs[name] = void 0; // attribute is like <button disabled></button>
|
||||
} else {
|
||||
attrs[name] = he.decode(doubleQuotedValue || singleQuotedValue || unquotedValue || '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag(tag, tagName) {
|
||||
var i;
|
||||
var pos = 0;
|
||||
var low = lowercase(tagName);
|
||||
if (low) {
|
||||
for (pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos] === low) {
|
||||
break; // find the closest opened tag of the same type
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pos >= 0) {
|
||||
for (i = stack.length - 1; i >= pos; i--) {
|
||||
if (handler.end) { // close all the open elements, up the stack
|
||||
handler.end(stack[i]);
|
||||
}
|
||||
}
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = parser;
|
||||
|
||||
}, { "./attributes": 1, "./elements": 3, "./lowercase": 5, "he": 9 }], 8: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var he = require('he');
|
||||
var lowercase = require('./lowercase');
|
||||
var attributes = require('./attributes');
|
||||
var elements = require('./elements');
|
||||
|
||||
function sanitizer(buffer, options) {
|
||||
var last;
|
||||
var context;
|
||||
var o = options || {};
|
||||
|
||||
reset();
|
||||
|
||||
return {
|
||||
start: start,
|
||||
end: end,
|
||||
chars: chars
|
||||
};
|
||||
|
||||
function out(value) {
|
||||
buffer.push(value);
|
||||
}
|
||||
|
||||
function start(tag, attrs, unary) {
|
||||
var low = lowercase(tag);
|
||||
|
||||
if (context.ignoring) {
|
||||
ignore(low); return;
|
||||
}
|
||||
if ((o.allowedTags || []).indexOf(low) === -1) {
|
||||
ignore(low); return;
|
||||
}
|
||||
if (o.filter && !o.filter({ tag: low, attrs: attrs })) {
|
||||
ignore(low); return;
|
||||
}
|
||||
|
||||
out('<');
|
||||
out(low);
|
||||
Object.keys(attrs).forEach(parse);
|
||||
out(unary ? '/>' : '>');
|
||||
|
||||
function parse(key) {
|
||||
var value = attrs[key];
|
||||
var classesOk = (o.allowedClasses || {})[low] || [];
|
||||
var attrsOk = (o.allowedAttributes || {})[low] || [];
|
||||
attrsOk = attrsOk.concat((o.allowedAttributes || {})['*'] || []);
|
||||
var valid;
|
||||
var lkey = lowercase(key);
|
||||
if (lkey === 'class' && attrsOk.indexOf(lkey) === -1) {
|
||||
value = value.split(' ').filter(isValidClass).join(' ').trim();
|
||||
valid = value.length;
|
||||
} else {
|
||||
valid = attrsOk.indexOf(lkey) !== -1 && (attributes.uris[lkey] !== true || testUrl(value));
|
||||
}
|
||||
if (valid) {
|
||||
out(' ');
|
||||
out(key);
|
||||
if (typeof value === 'string') {
|
||||
out('="');
|
||||
out(he.encode(value));
|
||||
out('"');
|
||||
}
|
||||
}
|
||||
function isValidClass(className) {
|
||||
return classesOk && classesOk.indexOf(className) !== -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function end(tag) {
|
||||
var low = lowercase(tag);
|
||||
var allowed = (o.allowedTags || []).indexOf(low) !== -1;
|
||||
if (allowed) {
|
||||
if (context.ignoring === false) {
|
||||
out('</');
|
||||
out(low);
|
||||
out('>');
|
||||
} else {
|
||||
unignore(low);
|
||||
}
|
||||
} else {
|
||||
unignore(low);
|
||||
}
|
||||
}
|
||||
|
||||
function testUrl(text) {
|
||||
var start = text[0];
|
||||
if (start === '#' || start === '/') {
|
||||
return true;
|
||||
}
|
||||
var colon = text.indexOf(':');
|
||||
if (colon === -1) {
|
||||
return true;
|
||||
}
|
||||
var questionmark = text.indexOf('?');
|
||||
if (questionmark !== -1 && colon > questionmark) {
|
||||
return true;
|
||||
}
|
||||
var hash = text.indexOf('#');
|
||||
if (hash !== -1 && colon > hash) {
|
||||
return true;
|
||||
}
|
||||
return o.allowedSchemes.some(matches);
|
||||
|
||||
function matches(scheme) {
|
||||
return text.indexOf(scheme + ':') === 0;
|
||||
}
|
||||
}
|
||||
|
||||
function chars(text) {
|
||||
if (context.ignoring === false) {
|
||||
out(o.transformText ? o.transformText(text) : text);
|
||||
}
|
||||
}
|
||||
|
||||
function ignore(tag) {
|
||||
if (elements.voids[tag]) {
|
||||
return;
|
||||
}
|
||||
if (context.ignoring === false) {
|
||||
context = { ignoring: tag, depth: 1 };
|
||||
} else if (context.ignoring === tag) {
|
||||
context.depth++;
|
||||
}
|
||||
}
|
||||
|
||||
function unignore(tag) {
|
||||
if (context.ignoring === tag) {
|
||||
if (--context.depth <= 0) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
context = { ignoring: false, depth: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = sanitizer;
|
||||
|
||||
}, { "./attributes": 1, "./elements": 3, "./lowercase": 5, "he": 9 }], 9: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
var escapes = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
var unescapes = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'"
|
||||
};
|
||||
var rescaped = /(&|<|>|"|')/g;
|
||||
var runescaped = /[&<>"']/g;
|
||||
|
||||
function escapeHtmlChar(match) {
|
||||
return escapes[match];
|
||||
}
|
||||
function unescapeHtmlChar(match) {
|
||||
return unescapes[match];
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text == null ? '' : String(text).replace(runescaped, escapeHtmlChar);
|
||||
}
|
||||
|
||||
function unescapeHtml(html) {
|
||||
return html == null ? '' : String(html).replace(rescaped, unescapeHtmlChar);
|
||||
}
|
||||
|
||||
escapeHtml.options = unescapeHtml.options = {};
|
||||
|
||||
module.exports = {
|
||||
encode: escapeHtml,
|
||||
escape: escapeHtml,
|
||||
decode: unescapeHtml,
|
||||
unescape: unescapeHtml,
|
||||
version: '1.0.0-browser'
|
||||
};
|
||||
|
||||
}, {}], 10: [function (require, module, exports) {
|
||||
'use strict';
|
||||
|
||||
function toMap(list) {
|
||||
return list.reduce(asKey, {});
|
||||
}
|
||||
|
||||
function asKey(accumulator, item) {
|
||||
accumulator[item] = true;
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
module.exports = toMap;
|
||||
|
||||
}, {}]
|
||||
}, {}, [4]);
|
||||
|
||||
// ESM-comment-begin
|
||||
define(function() { return { insane: __insane_func }; });
|
||||
// ESM-comment-end
|
||||
|
||||
// ESM-uncomment-begin
|
||||
// export var insane = __insane_func;
|
||||
// ESM-uncomment-end
|
|
@ -1,20 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2015 Nicolas Bevacqua
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -6,48 +6,56 @@
|
|||
import * as assert from 'assert';
|
||||
import { renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer';
|
||||
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
suite('MarkdownRenderer', () => {
|
||||
suite('Images', () => {
|
||||
function strToNode(str: string): HTMLElement {
|
||||
return new DOMParser().parseFromString(str, 'text/html').body.firstChild as HTMLElement;
|
||||
}
|
||||
|
||||
test('image rendering conforms to default', () => {
|
||||
const markdown = { value: `![image](someimageurl 'caption')` };
|
||||
function assertNodeEquals(actualNode: HTMLElement, expectedHtml: string) {
|
||||
const expectedNode = strToNode(expectedHtml);
|
||||
assert.ok(
|
||||
actualNode.isEqualNode(expectedNode),
|
||||
`Expected: ${expectedNode.outerHTML}\nActual: ${actualNode.outerHTML}`);
|
||||
}
|
||||
|
||||
suite('MarkdownRenderer', () => {
|
||||
suite('Sanitization', () => {
|
||||
test('Should not render images with unknown schemes', () => {
|
||||
const markdown = { value: `![image](no-such://example.com/cat.gif)` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
assert.strictEqual(result.innerHTML, '<p><img alt="image"></p>');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Images', () => {
|
||||
test('image rendering conforms to default', () => {
|
||||
const markdown = { value: `![image](http://example.com/cat.gif 'caption')` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
assertNodeEquals(result, '<div><p><img title="caption" alt="image" src="http://example.com/cat.gif"></p></div>');
|
||||
});
|
||||
|
||||
test('image rendering conforms to default without title', () => {
|
||||
const markdown = { value: `![image](someimageurl)` };
|
||||
const markdown = { value: `![image](http://example.com/cat.gif)` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
assertNodeEquals(result, '<div><p><img alt="image" src="http://example.com/cat.gif"></p></div>');
|
||||
});
|
||||
|
||||
test('image width from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|width=100px 'caption')` });
|
||||
assertNodeEquals(result, `<div><p><img width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
|
||||
});
|
||||
|
||||
test('image height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|height=100 'caption')` });
|
||||
assertNodeEquals(result, `<div><p><img height="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
|
||||
});
|
||||
|
||||
test('image width and height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
|
||||
const result: HTMLElement = renderMarkdown({ value: `![image](http://example.com/cat.gif|height=200,width=100 'caption')` });
|
||||
assertNodeEquals(result, `<div><p><img height="200" width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('ThemeIcons Support On', () => {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview:; object-src 'self'; script-src 'self' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget editorGhostText domLineBreaksComputer editorViewLayer diffReview extensionHostWorker insane notebookRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost;">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget editorGhostText domLineBreaksComputer editorViewLayer diffReview extensionHostWorker dompurify notebookRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost;">
|
||||
</head>
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview:; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget editorGhostText domLineBreaksComputer editorViewLayer diffReview extensionHostWorker insane notebookRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost;">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget editorGhostText domLineBreaksComputer editorViewLayer diffReview extensionHostWorker dompurify notebookRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost;">
|
||||
</head>
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
|
|
@ -57,7 +57,7 @@ import { generateUuid } from 'vs/base/common/uuid';
|
|||
import { platform } from 'vs/base/common/process';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer';
|
||||
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dompurify from 'vs/base/browser/dompurify/dompurify';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { insane } from 'vs/base/common/insane/insane';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export const DEFAULT_MARKDOWN_STYLES = `
|
||||
body {
|
||||
|
@ -149,27 +150,42 @@ code > div {
|
|||
|
||||
`;
|
||||
|
||||
function removeEmbeddedSVGs(documentContent: string): string {
|
||||
return insane(documentContent, {
|
||||
allowedTags: [
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
|
||||
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote', 'dl', 'dt',
|
||||
'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details',
|
||||
'caption', 'figure', 'figcaption', 'abbr', 'bdo', 'cite', 'dfn', 'mark', 'small', 'span', 'time', 'wbr', 'checkbox', 'checklist', 'vertically-centered'
|
||||
],
|
||||
allowedAttributes: {
|
||||
'*': [
|
||||
'align',
|
||||
],
|
||||
img: ['src', 'alt', 'title', 'aria-label', 'width', 'height', 'centered'],
|
||||
span: ['class'],
|
||||
checkbox: ['on-checked', 'checked-on', 'label', 'class']
|
||||
},
|
||||
allowedSchemes: ['http', 'https', 'command'],
|
||||
filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
|
||||
return token.tag !== 'svg';
|
||||
const allowedProtocols = [Schemas.http, Schemas.https, Schemas.command];
|
||||
function sanitize(documentContent: string): string {
|
||||
|
||||
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
|
||||
dompurify.addHook('afterSanitizeAttributes', (node) => {
|
||||
// build an anchor to map URLs to
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
// check all href/src attributes for validity
|
||||
for (const attr in ['href', 'src']) {
|
||||
if (node.hasAttribute(attr)) {
|
||||
anchor.href = node.getAttribute(attr) as string;
|
||||
if (!allowedProtocols.includes(anchor.protocol)) {
|
||||
node.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
return dompurify.sanitize(documentContent, {
|
||||
ALLOWED_TAGS: [
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
|
||||
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote', 'dl', 'dt',
|
||||
'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details',
|
||||
'caption', 'figure', 'figcaption', 'abbr', 'bdo', 'cite', 'dfn', 'mark', 'small', 'span', 'time', 'wbr', 'checkbox', 'checklist', 'vertically-centered'
|
||||
],
|
||||
ALLOWED_ATTR: [
|
||||
'href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code',
|
||||
'width', 'height', 'align', 'x-dispatch',
|
||||
'required', 'checked', 'placeholder', 'on-checked', 'checked-on',
|
||||
],
|
||||
});
|
||||
} finally {
|
||||
dompurify.removeHook('afterSanitizeAttributes');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,7 +197,7 @@ export async function renderMarkdownDocument(
|
|||
text: string,
|
||||
extensionService: IExtensionService,
|
||||
modeService: IModeService,
|
||||
shouldRemoveEmbeddedSVGs: boolean = true,
|
||||
shouldSanitize: boolean = true,
|
||||
): Promise<string> {
|
||||
|
||||
const highlight = (code: string, lang: string, callback: ((error: any, code: string) => void) | undefined): any => {
|
||||
|
@ -203,10 +219,9 @@ export async function renderMarkdownDocument(
|
|||
return new Promise<string>((resolve, reject) => {
|
||||
marked(text, { highlight }, (err, value) => err ? reject(err) : resolve(value));
|
||||
}).then(raw => {
|
||||
if (shouldRemoveEmbeddedSVGs) {
|
||||
return removeEmbeddedSVGs(raw);
|
||||
}
|
||||
else {
|
||||
if (shouldSanitize) {
|
||||
return sanitize(raw);
|
||||
} else {
|
||||
return raw;
|
||||
}
|
||||
});
|
|
@ -23,7 +23,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { asText, IRequestService } from 'vs/platform/request/common/request';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer';
|
||||
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer';
|
||||
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
|
||||
import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
|
|
|
@ -45,7 +45,7 @@ import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
|||
import { Link } from 'vs/platform/opener/browser/link';
|
||||
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer';
|
||||
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
|
Loading…
Reference in a new issue