##// END OF EJS Templates
Merge Security Pull Request: google-caja...
MinRK -
r15674:f33c5e99 merge
parent child Browse files
Show More
@@ -0,0 +1,126 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2014 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Utilities
10 //============================================================================
11 IPython.namespace('IPython.security');
12
13 IPython.security = (function (IPython) {
14 "use strict";
15
16 var utils = IPython.utils;
17
18 var noop = function (x) { return x; };
19
20 var caja;
21 if (window && window.html) {
22 caja = window.html;
23 caja.html4 = window.html4;
24 caja.sanitizeStylesheet = window.sanitizeStylesheet;
25 }
26
27 var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
28 // add trusting data-attributes to the default sanitizeAttribs from caja
29 // this function is mostly copied from the caja source
30 var ATTRIBS = caja.html4.ATTRIBS;
31 for (var i = 0; i < attribs.length; i += 2) {
32 var attribName = attribs[i];
33 if (attribName.substr(0,5) == 'data-') {
34 var attribKey = '*::' + attribName;
35 if (!ATTRIBS.hasOwnProperty(attribKey)) {
36 ATTRIBS[attribKey] = 0;
37 }
38 }
39 }
40 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
41 };
42
43 var sanitize_css = function (css, tagPolicy) {
44 // sanitize CSS
45 // like sanitize_html, but for CSS
46 // called by sanitize_stylesheets
47 return caja.sanitizeStylesheet(
48 window.location.pathname,
49 css,
50 {
51 containerClass: null,
52 idSuffix: '',
53 tagPolicy: tagPolicy,
54 virtualizeAttrName: noop
55 },
56 noop
57 );
58 };
59
60 var sanitize_stylesheets = function (html, tagPolicy) {
61 // sanitize just the css in style tags in a block of html
62 // called by sanitize_html, if allow_css is true
63 var h = $("<div/>").append(html);
64 var style_tags = h.find("style");
65 if (!style_tags.length) {
66 // no style tags to sanitize
67 return html;
68 }
69 style_tags.each(function(i, style) {
70 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
71 });
72 return h.html();
73 };
74
75 var sanitize_html = function (html, allow_css) {
76 // sanitize HTML
77 // if allow_css is true (default: false), CSS is sanitized as well.
78 // otherwise, CSS elements and attributes are simply removed.
79 var html4 = caja.html4;
80
81 if (allow_css) {
82 // allow sanitization of style tags,
83 // not just scrubbing
84 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
85 html4.ATTRIBS.style = html4.atype.STYLE;
86 } else {
87 // scrub all CSS
88 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
89 html4.ATTRIBS.style = html4.atype.SCRIPT;
90 }
91
92 var record_messages = function (msg, opts) {
93 console.log("HTML Sanitizer", msg, opts);
94 };
95
96 var policy = function (tagName, attribs) {
97 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
98 return {
99 'attribs': sanitizeAttribs(tagName, attribs,
100 noop, noop, record_messages)
101 };
102 } else {
103 record_messages(tagName + " removed", {
104 change: "removed",
105 tagName: tagName
106 });
107 }
108 };
109
110 var sanitized = caja.sanitizeWithPolicy(html, policy);
111
112 if (allow_css) {
113 // sanitize style tags as stylesheets
114 sanitized = sanitize_stylesheets(result.sanitized, policy);
115 }
116
117 return sanitized;
118 };
119
120 return {
121 caja: caja,
122 sanitize_html: sanitize_html
123 };
124
125 }(IPython));
126
@@ -0,0 +1,57 b''
1 safe_tests = [
2 "<p>Hi there</p>",
3 '<h1 class="foo">Hi There!</h1>',
4 '<a data-cite="foo">citation</a>',
5 '<div><span>Hi There</span></div>',
6 ];
7
8 unsafe_tests = [
9 "<script>alert(999);</script>",
10 '<a onmouseover="alert(999)">999</a>',
11 '<a onmouseover=alert(999)>999</a>',
12 '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
13 '<IMG SRC=# onmouseover="alert(999)">',
14 '<<SCRIPT>alert(999);//<</SCRIPT>',
15 '<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >',
16 '<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">',
17 '<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert(999);">',
18 '<IFRAME SRC="javascript:alert(999);"></IFRAME>',
19 '<IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME>',
20 '<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>',
21 // CSS is scrubbed
22 '<style src="http://untrusted/style.css"></style>',
23 '<style>div#notebook { background-color: alert-red; }</style>',
24 '<div style="background-color: alert-red;"></div>',
25 ];
26
27 var truncate = function (s, n) {
28 // truncate a string with an ellipsis
29 if (s.length > n) {
30 return s.substr(0, n-3) + '...';
31 } else {
32 return s;
33 }
34 };
35
36 casper.notebook_test(function () {
37 this.each(safe_tests, function (self, item) {
38 var sanitized = self.evaluate(function (item) {
39 return IPython.security.sanitize_html(item);
40 }, item);
41
42 // string equality may be too strict, but it works for now
43 this.test.assertEquals(sanitized, item, "Safe: '" + truncate(item, 32) + "'");
44 });
45
46 this.each(unsafe_tests, function (self, item) {
47 var sanitized = self.evaluate(function (item) {
48 return IPython.security.sanitize_html(item);
49 }, item);
50
51 this.test.assertNotEquals(sanitized, item,
52 "Sanitized: '" + truncate(item, 32) +
53 "' => '" + truncate(sanitized, 32) + "'"
54 );
55 this.test.assertEquals(sanitized.indexOf("alert"), -1, "alert removed");
56 });
57 }); No newline at end of file
@@ -278,7 +278,7 b' class FileNotebookManager(NotebookManager):'
278 278 nb = current.read(f, u'json')
279 279 except Exception as e:
280 280 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
281 self.mark_trusted_cells(nb, path, name)
281 self.mark_trusted_cells(nb, name, path)
282 282 model['content'] = nb
283 283 return model
284 284
@@ -303,7 +303,7 b' class FileNotebookManager(NotebookManager):'
303 303 os_path = self._get_os_path(new_name, new_path)
304 304 nb = current.to_notebook_json(model['content'])
305 305
306 self.check_and_sign(nb, new_path, new_name)
306 self.check_and_sign(nb, new_name, new_path)
307 307
308 308 if 'name' in nb['metadata']:
309 309 nb['metadata']['name'] = u''
@@ -286,5 +286,3 b' default_handlers = ['
286 286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
287 287 ]
288 288
289
290
@@ -52,7 +52,7 b' class NotebookManager(LoggingConfigurable):'
52 52 Parameters
53 53 ----------
54 54 path : string
55 The
55 The path to check
56 56
57 57 Returns
58 58 -------
@@ -224,22 +224,54 b' class NotebookManager(LoggingConfigurable):'
224 224 def log_info(self):
225 225 self.log.info(self.info_string())
226 226
227 # NotebookManager methods provided for use in subclasses.
227 def trust_notebook(self, name, path=''):
228 """Explicitly trust a notebook
228 229
229 def check_and_sign(self, nb, path, name):
230 Parameters
231 ----------
232 name : string
233 The filename of the notebook
234 path : string
235 The notebook's directory
236 """
237 model = self.get_notebook(name, path)
238 nb = model['content']
239 self.log.warn("Trusting notebook %s/%s", path, name)
240 self.notary.mark_cells(nb, True)
241 self.save_notebook(model, name, path)
242
243 def check_and_sign(self, nb, name, path=''):
230 244 """Check for trusted cells, and sign the notebook.
231 245
232 246 Called as a part of saving notebooks.
247
248 Parameters
249 ----------
250 nb : dict
251 The notebook structure
252 name : string
253 The filename of the notebook
254 path : string
255 The notebook's directory
233 256 """
234 257 if self.notary.check_cells(nb):
235 258 self.notary.sign(nb)
236 259 else:
237 260 self.log.warn("Saving untrusted notebook %s/%s", path, name)
238 261
239 def mark_trusted_cells(self, nb, path, name):
262 def mark_trusted_cells(self, nb, name, path=''):
240 263 """Mark cells as trusted if the notebook signature matches.
241 264
242 265 Called as a part of loading notebooks.
266
267 Parameters
268 ----------
269 nb : dict
270 The notebook structure
271 name : string
272 The filename of the notebook
273 path : string
274 The notebook's directory
243 275 """
244 276 trusted = self.notary.check_signature(nb)
245 277 if not trusted:
@@ -2,12 +2,15 b''
2 2 """Tests for the notebook manager."""
3 3 from __future__ import print_function
4 4
5 import logging
5 6 import os
6 7
7 8 from tornado.web import HTTPError
8 9 from unittest import TestCase
9 10 from tempfile import NamedTemporaryFile
10 11
12 from IPython.nbformat import current
13
11 14 from IPython.utils.tempdir import TemporaryDirectory
12 15 from IPython.utils.traitlets import TraitError
13 16 from IPython.html.utils import url_path_join
@@ -55,6 +58,17 b' class TestFileNotebookManager(TestCase):'
55 58
56 59 class TestNotebookManager(TestCase):
57 60
61 def setUp(self):
62 self._temp_dir = TemporaryDirectory()
63 self.td = self._temp_dir.name
64 self.notebook_manager = FileNotebookManager(
65 notebook_dir=self.td,
66 log=logging.getLogger()
67 )
68
69 def tearDown(self):
70 self._temp_dir.cleanup()
71
58 72 def make_dir(self, abs_path, rel_path):
59 73 """make subdirectory, rel_path is the relative path
60 74 to that directory from the location where the server started"""
@@ -64,10 +78,29 b' class TestNotebookManager(TestCase):'
64 78 except OSError:
65 79 print("Directory already exists: %r" % os_path)
66 80
81 def add_code_cell(self, nb):
82 output = current.new_output("display_data", output_javascript="alert('hi');")
83 cell = current.new_code_cell("print('hi')", outputs=[output])
84 if not nb.worksheets:
85 nb.worksheets.append(current.new_worksheet())
86 nb.worksheets[0].cells.append(cell)
87
88 def new_notebook(self):
89 nbm = self.notebook_manager
90 model = nbm.create_notebook()
91 name = model['name']
92 path = model['path']
93
94 full_model = nbm.get_notebook(name, path)
95 nb = full_model['content']
96 self.add_code_cell(nb)
97
98 nbm.save_notebook(full_model, name, path)
99 return nb, name, path
100
67 101 def test_create_notebook(self):
68 with TemporaryDirectory() as td:
102 nm = self.notebook_manager
69 103 # Test in root directory
70 nm = FileNotebookManager(notebook_dir=td)
71 104 model = nm.create_notebook()
72 105 assert isinstance(model, dict)
73 106 self.assertIn('name', model)
@@ -86,10 +119,8 b' class TestNotebookManager(TestCase):'
86 119 self.assertEqual(model['path'], sub_dir.strip('/'))
87 120
88 121 def test_get_notebook(self):
89 with TemporaryDirectory() as td:
90 # Test in root directory
122 nm = self.notebook_manager
91 123 # Create a notebook
92 nm = FileNotebookManager(notebook_dir=td)
93 124 model = nm.create_notebook()
94 125 name = model['name']
95 126 path = model['path']
@@ -115,10 +146,8 b' class TestNotebookManager(TestCase):'
115 146 self.assertEqual(model2['path'], sub_dir.strip('/'))
116 147
117 148 def test_update_notebook(self):
118 with TemporaryDirectory() as td:
119 # Test in root directory
149 nm = self.notebook_manager
120 150 # Create a notebook
121 nm = FileNotebookManager(notebook_dir=td)
122 151 model = nm.create_notebook()
123 152 name = model['name']
124 153 path = model['path']
@@ -155,10 +184,8 b' class TestNotebookManager(TestCase):'
155 184 self.assertRaises(HTTPError, nm.get_notebook, name, path)
156 185
157 186 def test_save_notebook(self):
158 with TemporaryDirectory() as td:
159 # Test in the root directory
187 nm = self.notebook_manager
160 188 # Create a notebook
161 nm = FileNotebookManager(notebook_dir=td)
162 189 model = nm.create_notebook()
163 190 name = model['name']
164 191 path = model['path']
@@ -192,9 +219,9 b' class TestNotebookManager(TestCase):'
192 219 self.assertEqual(model['path'], sub_dir.strip('/'))
193 220
194 221 def test_save_notebook_with_script(self):
195 with TemporaryDirectory() as td:
222 nm = self.notebook_manager
196 223 # Create a notebook
197 nm = FileNotebookManager(notebook_dir=td)
224 model = nm.create_notebook()
198 225 nm.save_script = True
199 226 model = nm.create_notebook()
200 227 name = model['name']
@@ -207,17 +234,13 b' class TestNotebookManager(TestCase):'
207 234 model = nm.save_notebook(full_model, name, path)
208 235
209 236 # Check that the script was created
210 py_path = os.path.join(td, os.path.splitext(name)[0]+'.py')
237 py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
211 238 assert os.path.exists(py_path), py_path
212 239
213 240 def test_delete_notebook(self):
214 with TemporaryDirectory() as td:
215 # Test in the root directory
241 nm = self.notebook_manager
216 242 # Create a notebook
217 nm = FileNotebookManager(notebook_dir=td)
218 model = nm.create_notebook()
219 name = model['name']
220 path = model['path']
243 nb, name, path = self.new_notebook()
221 244
222 245 # Delete the notebook
223 246 nm.delete_notebook(name, path)
@@ -226,13 +249,10 b' class TestNotebookManager(TestCase):'
226 249 self.assertRaises(HTTPError, nm.get_notebook, name, path)
227 250
228 251 def test_copy_notebook(self):
229 with TemporaryDirectory() as td:
230 # Test in the root directory
231 # Create a notebook
232 nm = FileNotebookManager(notebook_dir=td)
252 nm = self.notebook_manager
233 253 path = u'å b'
234 254 name = u'nb √.ipynb'
235 os.mkdir(os.path.join(td, path))
255 os.mkdir(os.path.join(nm.notebook_dir, path))
236 256 orig = nm.create_notebook({'name' : name}, path=path)
237 257
238 258 # copy with unspecified name
@@ -243,3 +263,44 b' class TestNotebookManager(TestCase):'
243 263 copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
244 264 self.assertEqual(copy2['name'], u'copy 2.ipynb')
245 265
266 def test_trust_notebook(self):
267 nbm = self.notebook_manager
268 nb, name, path = self.new_notebook()
269
270 untrusted = nbm.get_notebook(name, path)['content']
271 assert not nbm.notary.check_cells(untrusted)
272
273 # print(untrusted)
274 nbm.trust_notebook(name, path)
275 trusted = nbm.get_notebook(name, path)['content']
276 # print(trusted)
277 assert nbm.notary.check_cells(trusted)
278
279 def test_mark_trusted_cells(self):
280 nbm = self.notebook_manager
281 nb, name, path = self.new_notebook()
282
283 nbm.mark_trusted_cells(nb, name, path)
284 for cell in nb.worksheets[0].cells:
285 if cell.cell_type == 'code':
286 assert not cell.trusted
287
288 nbm.trust_notebook(name, path)
289 nb = nbm.get_notebook(name, path)['content']
290 for cell in nb.worksheets[0].cells:
291 if cell.cell_type == 'code':
292 assert cell.trusted
293
294 def test_check_and_sign(self):
295 nbm = self.notebook_manager
296 nb, name, path = self.new_notebook()
297
298 nbm.mark_trusted_cells(nb, name, path)
299 nbm.check_and_sign(nb, name, path)
300 assert not nbm.notary.check_signature(nb)
301
302 nbm.trust_notebook(name, path)
303 nb = nbm.get_notebook(name, path)['content']
304 nbm.mark_trusted_cells(nb, name, path)
305 nbm.check_and_sign(nb, name, path)
306 assert nbm.notary.check_signature(nb)
@@ -488,7 +488,6 b' IPython.utils = (function (IPython) {'
488 488 }
489 489 }
490 490
491
492 491 return {
493 492 regex_split : regex_split,
494 493 uuid : uuid,
@@ -1,1 +1,1 b''
1 Subproject commit 2f8958788c7e0416e5c44f532e9630a658df11fd
1 Subproject commit 7a9ba818b3e13123621cb5ff336c002d49470f55
@@ -133,6 +133,20 b' var IPython = (function (IPython) {'
133 133 });
134 134 this.element.find('#restore_checkpoint').click(function () {
135 135 });
136 this.element.find('#trust_notebook').click(function () {
137 IPython.notebook.trust_notebook();
138 });
139 $([IPython.events]).on('trust_changed.Notebook', function (event, trusted) {
140 if (trusted) {
141 that.element.find('#trust_notebook')
142 .addClass("disabled")
143 .find("a").text("Trusted Notebook");
144 } else {
145 that.element.find('#trust_notebook')
146 .removeClass("disabled")
147 .find("a").text("Trust Notebook");
148 }
149 });
136 150 this.element.find('#kill_and_exit').click(function () {
137 151 IPython.notebook.session.delete();
138 152 setTimeout(function(){
@@ -110,6 +110,10 b' var IPython = (function (IPython) {'
110 110 that.dirty = data.value;
111 111 });
112 112
113 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
114 that.trusted = data.value;
115 });
116
113 117 $([IPython.events]).on('select.Cell', function (event, data) {
114 118 var index = that.find_cell_index(data.cell);
115 119 that.select(index);
@@ -1607,6 +1611,7 b' var IPython = (function (IPython) {'
1607 1611 // Save the metadata and name.
1608 1612 this.metadata = content.metadata;
1609 1613 this.notebook_name = data.name;
1614 var trusted = true;
1610 1615 // Only handle 1 worksheet for now.
1611 1616 var worksheet = content.worksheets[0];
1612 1617 if (worksheet !== undefined) {
@@ -1627,7 +1632,14 b' var IPython = (function (IPython) {'
1627 1632
1628 1633 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1629 1634 new_cell.fromJSON(cell_data);
1635 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1636 trusted = false;
1637 }
1638 }
1630 1639 }
1640 if (trusted != this.trusted) {
1641 this.trusted = trusted;
1642 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1631 1643 }
1632 1644 if (content.worksheets.length > 1) {
1633 1645 IPython.dialog.modal({
@@ -1654,8 +1666,13 b' var IPython = (function (IPython) {'
1654 1666 var cells = this.get_cells();
1655 1667 var ncells = cells.length;
1656 1668 var cell_array = new Array(ncells);
1669 var trusted = true;
1657 1670 for (var i=0; i<ncells; i++) {
1658 cell_array[i] = cells[i].toJSON();
1671 var cell = cells[i];
1672 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1673 trusted = false;
1674 }
1675 cell_array[i] = cell.toJSON();
1659 1676 }
1660 1677 var data = {
1661 1678 // Only handle 1 worksheet for now.
@@ -1665,6 +1682,10 b' var IPython = (function (IPython) {'
1665 1682 }],
1666 1683 metadata : this.metadata
1667 1684 };
1685 if (trusted != this.trusted) {
1686 this.trusted = trusted;
1687 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1688 }
1668 1689 return data;
1669 1690 };
1670 1691
@@ -1786,6 +1807,54 b' var IPython = (function (IPython) {'
1786 1807 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1787 1808 };
1788 1809
1810 /**
1811 * Explicitly trust the output of this notebook.
1812 *
1813 * @method trust_notebook
1814 */
1815 Notebook.prototype.trust_notebook = function (extra_settings) {
1816 var body = $("<div>").append($("<p>")
1817 .text("A trusted IPython notebook may execute hidden malicious code ")
1818 .append($("<strong>")
1819 .append(
1820 $("<em>").text("when you open it")
1821 )
1822 ).append(".").append(
1823 " Selecting trust will immediately reload this notebook in a trusted state."
1824 ).append(
1825 " For more information, see the "
1826 ).append($("<a>").attr("href", "http://ipython.org/security.html")
1827 .text("IPython security documentation")
1828 ).append(".")
1829 );
1830
1831 var nb = this;
1832 IPython.dialog.modal({
1833 title: "Trust this notebook?",
1834 body: body,
1835
1836 buttons: {
1837 Cancel : {},
1838 Trust : {
1839 class : "btn-danger",
1840 click : function () {
1841 var cells = nb.get_cells();
1842 for (var i = 0; i < cells.length; i++) {
1843 var cell = cells[i];
1844 if (cell.cell_type == 'code') {
1845 cell.output_area.trusted = true;
1846 }
1847 }
1848 $([IPython.events]).on('notebook_saved.Notebook', function () {
1849 window.location.reload();
1850 });
1851 nb.save_notebook();
1852 }
1853 }
1854 }
1855 });
1856 };
1857
1789 1858 Notebook.prototype.new_notebook = function(){
1790 1859 var path = this.notebook_path;
1791 1860 var base_url = this.base_url;
@@ -480,6 +480,7 b' var IPython = (function (IPython) {'
480 480
481 481 OutputArea.safe_outputs = {
482 482 'text/plain' : true,
483 'text/latex' : true,
483 484 'image/png' : true,
484 485 'image/jpeg' : true
485 486 };
@@ -489,18 +490,20 b' var IPython = (function (IPython) {'
489 490 var type = OutputArea.display_order[type_i];
490 491 var append = OutputArea.append_map[type];
491 492 if ((json[type] !== undefined) && append) {
493 var value = json[type];
492 494 if (!this.trusted && !OutputArea.safe_outputs[type]) {
493 // not trusted show warning and do not display
494 var content = {
495 text : "Untrusted " + type + " output ignored.",
496 stream : "stderr"
497 }
498 this.append_stream(content);
495 // not trusted, sanitize HTML
496 if (type==='text/html' || type==='text/svg') {
497 value = IPython.security.sanitize_html(value);
498 } else {
499 // don't display if we don't know how to sanitize it
500 console.log("Ignoring untrusted " + type + " output.");
499 501 continue;
500 502 }
503 }
501 504 var md = json.metadata || {};
502 var toinsert = append.apply(this, [json[type], md, element]);
503 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
505 var toinsert = append.apply(this, [value, md, element]);
506 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
504 507 return true;
505 508 }
506 509 }
@@ -21,6 +21,7 b' var IPython = (function (IPython) {'
21 21
22 22 // TextCell base class
23 23 var keycodes = IPython.keyboard.keycodes;
24 var security = IPython.security;
24 25
25 26 /**
26 27 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
@@ -344,23 +345,12 b' var IPython = (function (IPython) {'
344 345 text = text_and_math[0];
345 346 math = text_and_math[1];
346 347 var html = marked.parser(marked.lexer(text));
347 html = $(IPython.mathjaxutils.replace_math(html, math));
348 // Links in markdown cells should open in new tabs.
348 html = IPython.mathjaxutils.replace_math(html, math);
349 html = security.sanitize_html(html);
350 html = $(html);
351 // links in markdown cells should open in new tabs
349 352 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
350 try {
351 // TODO: This HTML needs to be treated as potentially dangerous
352 // user input and should be handled before set_rendered.
353 353 this.set_rendered(html);
354 } catch (e) {
355 console.log("Error running Javascript in Markdown:");
356 console.log(e);
357 this.set_rendered(
358 $("<div/>")
359 .append($("<div/>").text('Error rendering Markdown!').addClass("js-error"))
360 .append($("<div/>").text(e.toString()).addClass("js-error"))
361 .html()
362 );
363 }
364 354 this.element.find('div.input_area').hide();
365 355 this.element.find("div.text_cell_render").show();
366 356 this.typeset();
@@ -528,7 +518,9 b' var IPython = (function (IPython) {'
528 518 text = text_and_math[0];
529 519 math = text_and_math[1];
530 520 var html = marked.parser(marked.lexer(text));
531 var h = $(IPython.mathjaxutils.replace_math(html, math));
521 html = IPython.mathjaxutils.replace_math(html, math);
522 html = security.sanitize_html(html);
523 var h = $(html);
532 524 // add id and linkback anchor
533 525 var hash = h.text().replace(/ /g, '-');
534 526 h.attr('id', hash);
@@ -538,13 +530,10 b' var IPython = (function (IPython) {'
538 530 .attr('href', '#' + hash)
539 531 .text('¶')
540 532 );
541 // TODO: This HTML needs to be treated as potentially dangerous
542 // user input and should be handled before set_rendered.
543 533 this.set_rendered(h);
544 this.typeset();
545 this.element.find('div.input_area').hide();
534 this.element.find('div.text_cell_input').hide();
546 535 this.element.find("div.text_cell_render").show();
547
536 this.typeset();
548 537 }
549 538 return cont;
550 539 };
@@ -7,4 +7,4 b''
7 7 @import "outputarea.less";
8 8 @import "renderedhtml.less";
9 9 @import "textcell.less";
10 @import "widgets.less";
10 @import "../../widgets/less/widgets.less";
@@ -86,7 +86,10 b' class="notebook_app"'
86 86 </ul>
87 87 </li>
88 88 <li class="divider"></li>
89
89 <li id="trust_notebook"
90 title="Trust the output of this notebook">
91 <a href="#" >Trust Notebook</a></li>
92 <li class="divider"></li>
90 93 <li id="kill_and_exit"
91 94 title="Shutdown this notebook's kernel, and close this window">
92 95 <a href="#" >Close and halt</a></li>
@@ -291,6 +294,7 b' class="notebook_app"'
291 294
292 295 {{super()}}
293 296
297 <script src="{{ static_url("components/google-caja/html-css-sanitizer-minified.js") }}" charset="utf-8"></script>
294 298 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
295 299 <script type="text/javascript">
296 300 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
@@ -318,6 +322,7 b' class="notebook_app"'
318 322 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
319 323 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
320 324 <script src="{{ static_url("base/js/keyboard.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("base/js/security.js") }}" type="text/javascript" charset="utf-8"></script>
321 326 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
322 327 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
323 328 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
@@ -150,6 +150,7 b' def find_package_data():'
150 150 pjoin(components, "backbone", "backbone-min.js"),
151 151 pjoin(components, "bootstrap", "bootstrap", "js", "bootstrap.min.js"),
152 152 pjoin(components, "font-awesome", "font", "*.*"),
153 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
153 154 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
154 155 pjoin(components, "jquery", "jquery.min.js"),
155 156 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
General Comments 0
You need to be logged in to leave comments. Login now