Skip to content
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

HTML sanitizer for descriptions. #2785

Merged
merged 32 commits into from Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2881e35
HTML sanitizer for descriptions.
zerline Feb 16, 2020
b79173f
Schema update.
zerline Feb 16, 2020
6b56c15
'description_html' can be modified.
zerline Feb 17, 2020
414ddd5
Attempt for a javascript-side sanitization. Based on DOMPurifier.
zerline May 18, 2020
e1025a9
Document all ToggleButton parameters.
zerline Jun 7, 2020
6a58fd5
Using sanitize-html.
zerline Jun 7, 2020
05b4079
TS compiling.
zerline Jun 7, 2020
e97263b
Adding a plaintext sanitizer (ie tags stripper).
zerline Jun 7, 2020
689d2c0
Schema update.
zerline Jun 7, 2020
17451ec
Merge branch 'master' into HTMLSanitizer
zerline Jun 7, 2020
7a03bb4
Putting things in the right place.
zerline Jun 8, 2020
fc0abfd
Dependency fix.
zerline Jun 8, 2020
99655c3
Test fix.
zerline Jun 8, 2020
58c1a58
Adding <span>.
zerline Jun 8, 2020
b64213a
Test notebook.
zerline Jun 8, 2020
5514bb9
Adding 'style' attribute.
zerline Jun 8, 2020
4b84b90
Little more styling.
zerline Jun 8, 2020
7646c54
No HTML is possible inside the tag '<button>'.
zerline Jun 8, 2020
2e8c38c
s/description_html/description_allow_html
zerline Jun 8, 2020
7f296f0
This almost works, but does not help to get a proper display for desc…
zerline Jun 15, 2020
85382cd
Question of length.
zerline Jun 15, 2020
1056286
Testing with Mathjax.
zerline Jun 15, 2020
dd5ef1f
Syntax correctness for lint.
zerline Jun 15, 2020
f28b5fc
Using LaTeX functions copied from Jupyterlab.
zerline Jul 1, 2020
3945f22
s/let/const.
zerline Jul 1, 2020
a6da1ad
Change plain text to just use textContent directly.
jasongrout Jul 10, 2021
d65b7c2
Merge remote-tracking branch 'origin/master' into HTMLSanitizer
jasongrout Jul 10, 2021
32665c4
Remove style tags during description sanitization
jasongrout Jul 10, 2021
be8adc4
Merge commit '280302e3cfc5262d805d658c910969fe0db50606' into HTMLSani…
jasongrout Jul 10, 2021
9a235d1
Lint
jasongrout Jul 10, 2021
7762fb7
Fix spec for new date and time pickers.
jasongrout Jul 10, 2021
df33205
Merge branch 'master' into HTMLSanitizer
jasongrout Jul 13, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions ipywidgets/widgets/widget_bool.py
Expand Up @@ -54,8 +54,14 @@ class ToggleButton(_Bool):
value of the toggle button: True-pressed, False-unpressed
description : str
description displayed next to the button
description_html : boolean
accept HTML in the description
icon: str
font-awesome icon name
style: instance of DescriptionStyle
styling customizations
button_style: enum
button predefined styling
"""
_view_name = Unicode('ToggleButtonView').tag(sync=True)
_model_name = Unicode('ToggleButtonModel').tag(sync=True)
Expand Down
3 changes: 2 additions & 1 deletion ipywidgets/widgets/widget_description.py
Expand Up @@ -3,7 +3,7 @@

"""Contains the DOMWidget class"""

from traitlets import Unicode
from traitlets import Bool, Unicode
from .widget import Widget, widget_serialization, register
from .trait_types import InstanceDict
from .widget_style import Style
Expand All @@ -21,6 +21,7 @@ class DescriptionWidget(DOMWidget, CoreWidget):
"""Widget that has a description label to the side."""
_model_name = Unicode('DescriptionModel').tag(sync=True)
description = Unicode('', help="Description of the control.").tag(sync=True)
description_html = Bool(False, help="Accept HTML in the description.").tag(sync=True)
jasongrout marked this conversation as resolved.
Show resolved Hide resolved
style = InstanceDict(DescriptionStyle, help="Styling customizations").tag(sync=True, **widget_serialization)

def _repr_keys(self):
Expand Down
4 changes: 3 additions & 1 deletion packages/base-manager/package.json
Expand Up @@ -35,14 +35,16 @@
"@jupyter-widgets/base": "^4.0.0-alpha.0",
"@jupyterlab/services": "^5.0.2",
"@lumino/coreutils": "^1.4.2",
"base64-js": "^1.2.1"
"base64-js": "^1.2.1",
"sanitize-html": "^1.20"
jasongrout marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@types/base64-js": "^1.2.5",
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "^7.1.0",
"@types/expect.js": "^0.3.29",
"@types/mocha": "^5.2.7",
"@types/sanitize-html": "^1.20",
"@types/sinon": "^7.0.13",
"@types/sinon-chai": "^3.2.2",
"chai": "^4.0.0",
Expand Down
47 changes: 47 additions & 0 deletions packages/base-manager/src/manager-base.ts
Expand Up @@ -25,9 +25,48 @@ import {
} from '@jupyter-widgets/base';

import { base64ToBuffer, bufferToBase64, hexToBuffer } from './utils';
import sanitize from 'sanitize-html';

const PROTOCOL_MAJOR_VERSION = PROTOCOL_VERSION.split('.', 1)[0];

/**
* Strip unwanted tags from plaintext descriptions.
*/
function default_plaintext_sanitize(s: string): string {
return sanitize(s, {
allowedTags: [],
allowedAttributes: {}
});
}

/**
* Sanitize HTML-formatted descriptions.
*/
function default_inline_sanitize(html: string): string {
return sanitize(html, {
allowedTags: [
'a',
'abbr',
'b',
'code',
'em',
'i',
'img',
'li',
'ol',
'strong',
'style',
'ul'
],
allowedAttributes: {
'*': ['aria-*', 'title'],
a: ['href'],
img: ['src'],
style: ['media']
}
});
}

export interface IState extends PartialJSONObject {
buffers?: IBase64Buffers[];
model_name: string;
Expand Down Expand Up @@ -462,6 +501,14 @@ export abstract class ManagerBase implements IWidgetManager {
return Promise.resolve(url);
}

plaintext_sanitize(s: string): string {
return default_plaintext_sanitize(s);
}

inline_sanitize(s: string): string {
return default_inline_sanitize(s);
}

/**
* The comm target name to register
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/base/src/manager.ts
Expand Up @@ -190,4 +190,7 @@ export interface IWidgetManager {
* The default implementation just returns the original url.
*/
resolveUrl(url: string): Promise<string>;

plaintext_sanitize(s: string): string;
inline_sanitize(s: string): string;
}
7 changes: 7 additions & 0 deletions packages/base/test/src/dummy-manager.ts
Expand Up @@ -316,6 +316,13 @@ export class DummyManager implements widgets.IWidgetManager {
return Promise.resolve(url);
}

plaintext_sanitize(s: string): string {
return s;
}
inline_sanitize(s: string): string {
return s;
}

/**
* Dictionary of model ids and model instance promises
*/
Expand Down
15 changes: 12 additions & 3 deletions packages/controls/src/widget_bool.ts
Expand Up @@ -79,10 +79,19 @@ export class CheckboxView extends DescriptionView {
return;
}
const description = this.model.get('description');
this.descriptionSpan.innerHTML = description;
const plaintext_description = this.model.widget_manager.plaintext_sanitize(
jasongrout marked this conversation as resolved.
Show resolved Hide resolved
description
);
if (this.model.get('description_html')) {
this.descriptionSpan.innerHTML = this.model.widget_manager.inline_sanitize(
description
);
} else {
this.descriptionSpan.textContent = plaintext_description;
}
this.typeset(this.descriptionSpan);
this.descriptionSpan.title = description;
this.checkbox.title = description;
this.descriptionSpan.title = plaintext_description;
this.checkbox.title = plaintext_description;
}

/**
Expand Down
18 changes: 16 additions & 2 deletions packages/controls/src/widget_description.ts
Expand Up @@ -40,7 +40,8 @@ export class DescriptionModel extends DOMWidgetModel {
_model_module: '@jupyter-widgets/controls',
_view_module_version: JUPYTER_CONTROLS_VERSION,
_model_module_version: JUPYTER_CONTROLS_VERSION,
description: ''
description: '',
description_html: false
};
}
}
Expand All @@ -53,6 +54,11 @@ export class DescriptionView extends DOMWidgetView {
this.label.style.display = 'none';

this.listenTo(this.model, 'change:description', this.updateDescription);
this.listenTo(
this.model,
'change:description_html',
this.updateDescription
);
this.listenTo(this.model, 'change:tabbable', this.updateTabindex);
this.updateDescription();
this.updateTabindex();
Expand All @@ -68,7 +74,15 @@ export class DescriptionView extends DOMWidgetView {
if (description.length === 0) {
this.label.style.display = 'none';
} else {
this.label.innerHTML = description;
if (this.model.get('description_html')) {
this.label.innerHTML = this.model.widget_manager.inline_sanitize(
description
);
} else {
this.label.textContent = this.model.widget_manager.plaintext_sanitize(
description
);
}
this.typeset(this.label);
this.label.style.display = '';
}
Expand Down
1 change: 1 addition & 0 deletions packages/html-manager/package.json
Expand Up @@ -51,6 +51,7 @@
"devDependencies": {
"@types/mocha": "^5.2.7",
"@types/node": "^12.7.0",
"@types/sanitize-html": "^1.20",
"chai": "^4.0.0",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
Expand Down