Commit 0ced7697 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'jdb/fix-jupyter-notebooks-rendering' into 'master'

Jdb/fix jupyter notebooks rendering

See merge request gitlab-org/gitlab!49067
parents 280a3103 0a1173d6
<script>
/* eslint-disable vue/no-v-html */
import { GlSafeHtmlDirective } from '@gitlab/ui';
import { sanitize } from '~/lib/dompurify';
import Prompt from '../prompt.vue';
......@@ -7,6 +7,9 @@ export default {
components: {
Prompt,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
},
props: {
count: {
type: Number,
......@@ -23,9 +26,7 @@ export default {
},
computed: {
sanitizedOutput() {
return sanitize(this.rawCode, {
ALLOWED_ATTR: ['src'],
});
return sanitize(this.rawCode);
},
showOutput() {
return this.index === 0;
......@@ -37,6 +38,6 @@ export default {
<template>
<div class="output">
<prompt type="Out" :count="count" :show-output="showOutput" />
<div class="gl-overflow-auto" v-html="sanitizedOutput"></div>
<div v-safe-html="sanitizedOutput" class="gl-overflow-auto"></div>
</div>
</template>
......@@ -31,6 +31,8 @@ export default {
return 'text/plain';
} else if (output.data['image/png']) {
return 'image/png';
} else if (output.data['image/jpeg']) {
return 'image/jpeg';
} else if (output.data['text/html']) {
return 'text/html';
} else if (output.data['image/svg+xml']) {
......@@ -53,6 +55,8 @@ export default {
return CodeOutput;
} else if (output.data['image/png']) {
return ImageOutput;
} else if (output.data['image/jpeg']) {
return ImageOutput;
} else if (output.data['text/html']) {
return HtmlOutput;
} else if (output.data['image/svg+xml']) {
......
import Prism from 'prismjs';
import 'prismjs/components/prism-python';
import 'prismjs/plugins/custom-class/prism-custom-class';
Prism.plugins.customClass.map({
comment: 'c',
error: 'err',
operator: 'o',
constant: 'kc',
namespace: 'kn',
keyword: 'k',
string: 's',
number: 'm',
'attr-name': 'na',
builtin: 'nb',
entity: 'ni',
function: 'nf',
tag: 'nt',
variable: 'nv',
});
import 'prismjs/themes/prism.css';
export default Prism;
---
title: Fix Jupyter notebook code and image rendering
merge_request: 49067
author:
type: fixed
/**
* Jupyter notebooks handles the following data types
* that are to be handled by `html.vue`
*
* 'text/html';
* 'image/svg+xml';
*
* This file sets up fixtures for each of these types
* NOTE: The inputs are taken directly from data derived from the
* jupyter notebook file used to test nbview here:
* https://nbviewer.jupyter.org/github/ipython/ipython-in-depth/blob/master/examples/IPython%20Kernel/Rich%20Output.ipynb
*/
export default [
[
'protocol-based JS injection: simple, no spaces',
'text/html table',
{
input: `<a href="javascript:alert('XSS');">foo</a>`,
output: '<a>foo</a>',
input: [
'<table>\n',
'<tr>\n',
'<th>Header 1</th>\n',
'<th>Header 2</th>\n',
'</tr>\n',
'<tr>\n',
'<td>row 1, cell 1</td>\n',
'<td>row 1, cell 2</td>\n',
'</tr>\n',
'<tr>\n',
'<td>row 2, cell 1</td>\n',
'<td>row 2, cell 2</td>\n',
'</tr>\n',
'</table>',
].join(''),
output: '<table>',
},
],
// Note: style is sanitized out
[
'protocol-based JS injection: simple, spaces before',
'text/html style',
{
input: `<a href="javascript :alert('XSS');">foo</a>`,
output: '<a>foo</a>',
input: [
'<style type="text/css">\n',
'\n',
'circle {\n',
' fill: rgb(31, 119, 180);\n',
' fill-opacity: .25;\n',
' stroke: rgb(31, 119, 180);\n',
' stroke-width: 1px;\n',
'}\n',
'\n',
'.leaf circle {\n',
' fill: #ff7f0e;\n',
' fill-opacity: 1;\n',
'}\n',
'\n',
'text {\n',
' font: 10px sans-serif;\n',
'}\n',
'\n',
'</style>',
].join(''),
output: '<!---->',
},
],
// Note: iframe is sanitized out
[
'protocol-based JS injection: simple, spaces after',
'text/html iframe',
{
input: `<a href="javascript: alert('XSS');">foo</a>`,
output: '<a>foo</a>',
input: [
'\n',
' <iframe\n',
' width="400"\n',
' height="300"\n',
' src="https://www.youtube.com/embed/sjfsUzECqK0"\n',
' frameborder="0"\n',
' allowfullscreen\n',
' ></iframe>\n',
' ',
].join(''),
output: '<!---->',
},
],
[
'protocol-based JS injection: simple, spaces before and after',
'image/svg+xml',
{
input: `<a href="javascript : alert('XSS');">foo</a>`,
output: '<a>foo</a>',
input: [
'<svg height="115.02pt" id="svg2" version="1.0" width="388.84pt" xmlns="http://www.w3.org/2000/svg">\n',
' <g>\n',
' <path d="M 184.61344,61.929363 C 184.61344,47.367213 180.46118,39.891193 172.15666,39.481813" style="fill:#646464;fill-opacity:1"/>\n',
' </g>\n',
'</svg>',
].join(),
output: '<svg height="115.02pt" id="svg2"',
},
],
[
'protocol-based JS injection: preceding colon',
{
input: `<a href=":javascript:alert('XSS');">foo</a>`,
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: UTF-8 encoding',
{
input: '<a href="javascript&#58;">foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: long UTF-8 encoding',
{
input: '<a href="javascript&#0058;">foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: long UTF-8 encoding without semicolons',
{
input:
'<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: hex encoding',
{
input: '<a href="javascript&#x3A;">foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: long hex encoding',
{
input: '<a href="javascript&#x003A;">foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: hex encoding without semicolons',
{
input:
'<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: null char',
{
input: '<a href=java\u0000script:alert("XSS")>foo</a>',
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: invalid URL char',
{ input: '<img src=javascript:alert("XSS")>', output: '<img>' },
],
[
'protocol-based JS injection: Unicode',
{
input: `<a href="\u0001java\u0003script:alert('XSS')">foo</a>`,
output: '<a>foo</a>',
},
],
[
'protocol-based JS injection: spaces and entities',
{
input: `<a href=" &#14; javascript:alert('XSS');">foo</a>`,
output: '<a>foo</a>',
},
],
[
'img on error',
{
input: '<img src="x" onerror="alert(document.domain)" />',
output: '<img src="x">',
},
],
['style tags are removed', { input: '<style>.foo {}</style> Foo', output: 'Foo' }],
];
import Vue from 'vue';
import htmlOutput from '~/notebook/cells/output/html.vue';
import { mount } from '@vue/test-utils';
import HtmlOutput from '~/notebook/cells/output/html.vue';
import sanitizeTests from './html_sanitize_fixtures';
describe('html output cell', () => {
function createComponent(rawCode) {
const Component = Vue.extend(htmlOutput);
return new Component({
return mount(HtmlOutput, {
propsData: {
rawCode,
count: 0,
index: 0,
},
}).$mount();
});
}
it.each(sanitizeTests)('sanitizes output for: %p', (name, { input, output }) => {
const vm = createComponent(input);
const outputEl = [...vm.$el.querySelectorAll('div')].pop();
expect(outputEl.innerHTML).toEqual(output);
vm.$destroy();
expect(vm.html()).toContain(output);
});
});
......@@ -9,7 +9,7 @@ describe('Highlight library', () => {
const el = document.createElement('div');
el.innerHTML = Prism.highlight('console.log("a");', Prism.languages.javascript);
expect(el.querySelector('.s')).not.toBeNull();
expect(el.querySelector('.nf')).not.toBeNull();
expect(el.querySelector('.string')).not.toBeNull();
expect(el.querySelector('.function')).not.toBeNull();
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment