Browse Source

Cleaner handling of security

Steven Silvester 8 years ago
parent
commit
e837249be1

+ 7 - 3
src/markdownwidget/plugin.ts

@@ -9,6 +9,10 @@ import {
   DocumentRegistry, IWidgetFactoryOptions
 } from '../docregistry';
 
+import {
+  RenderMime
+} from '../rendermime';
+
 import {
   MarkdownWidgetFactory
 } from './widget';
@@ -30,8 +34,8 @@ const TEXTEDITOR_ICON_CLASS = 'jp-ImageTextEditor';
 export
 const markdownHandlerExtension = {
   id: 'jupyter.extensions.RenderedMarkdown',
-  requires: [DocumentRegistry],
-  activate: (app: Application, registry: DocumentRegistry) => {
+  requires: [DocumentRegistry, RenderMime],
+  activate: (app: Application, registry: DocumentRegistry, rendermime: RenderMime) => {
     let options: IWidgetFactoryOptions = {
       fileExtensions: ['.md'],
       displayName: 'Rendered Markdown',
@@ -39,7 +43,7 @@ const markdownHandlerExtension = {
       preferKernel: false,
       canStartKernel: false
     };
-    let factory = new MarkdownWidgetFactory();
+    let factory = new MarkdownWidgetFactory(rendermime);
     let icon = `${PORTRAIT_ICON_CLASS} ${TEXTEDITOR_ICON_CLASS}`;
     factory.widgetCreated.connect((sender, widget) => {
       widget.title.icon = icon;

+ 20 - 17
src/markdownwidget/widget.ts

@@ -22,8 +22,8 @@ import {
 } from '../docregistry';
 
 import {
-  MarkdownRenderer
-} from '../renderers';
+  RenderMime
+} from '../rendermime';
 
 
 /**
@@ -40,12 +40,13 @@ class MarkdownWidget extends Widget {
   /**
    * Construct a new markdown widget.
    */
-  constructor(context: IDocumentContext<IDocumentModel>) {
+  constructor(context: IDocumentContext<IDocumentModel>, rendermime: RenderMime) {
     super();
     this.addClass(MD_CLASS);
     this.layout = new PanelLayout();
     this.title.text = context.path.split('/').pop();
-    this._renderer = new MarkdownRenderer();
+    this._rendermime = rendermime;
+    rendermime.resolver = context;
     this._context = context;
 
     context.pathChanged.connect((c, path) => {
@@ -68,20 +69,12 @@ class MarkdownWidget extends Widget {
    * Handle an `update-request` message to the widget.
    */
   protected onUpdateRequest(msg: Message): void {
-    let renderer = this._renderer;
     let context = this._context;
     let model = context.model;
     let layout = this.layout as PanelLayout;
-    renderer.transform({
-      mimetype: 'text/markdown',
-      source: model.toString(),
-      resolver: context
-    }).then(source => {
-      let widget = renderer.render({
-        mimetype: 'text/markdown',
-        source,
-        resolver: context
-      });
+    this._rendermime.render({
+     'text/markdown': model.toString(),
+    }).then(widget => {
       if (layout.childCount()) {
         layout.childAt(0).dispose();
       }
@@ -89,7 +82,7 @@ class MarkdownWidget extends Widget {
     });
   }
 
-  private _renderer: MarkdownRenderer = null;
+  private _rendermime: RenderMime = null;
   private _context: IDocumentContext<IDocumentModel> = null;
 }
 
@@ -99,12 +92,22 @@ class MarkdownWidget extends Widget {
  */
 export
 class MarkdownWidgetFactory extends ABCWidgetFactory<MarkdownWidget, IDocumentModel> {
+  /**
+   * Construct a new markdown widget factory.
+   */
+  constructor(rendermime: RenderMime) {
+    super();
+    this._rendermime = rendermime;
+  }
+
   /**
    * Create a new widget given a context.
    */
   createNew(context: IDocumentContext<IDocumentModel>, kernel?: IKernel.IModel): MarkdownWidget {
-    let widget = new MarkdownWidget(context);
+    let widget = new MarkdownWidget(context, this._rendermime.clone());
     this.widgetCreated.emit(widget);
     return widget;
   }
+
+  private _rendermime: RenderMime = null;
 }

+ 2 - 0
src/notebook/cells/widget.ts

@@ -668,6 +668,8 @@ class MarkdownCellWidget extends BaseCellWidget {
           this._markdownWidget = widget || new Widget();
           this._markdownWidget.addClass(MARKDOWN_CONTENT_CLASS);
           (this.layout as PanelLayout).addChild(this._markdownWidget);
+        }).catch(err => {
+          console.error(err);
         });
       } else {
         this._markdownWidget.show();

+ 21 - 65
src/renderers/index.ts

@@ -160,18 +160,15 @@ class HTMLRenderer implements RenderMime.IRenderer {
     return false;
   }
 
-  /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    return options.source;
-  }
-
   /**
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
-    let w = new HTMLWidget(options.source);
+    let source = options.source;
+    if (options.sanitizer) {
+      source = options.sanitizer.sanitize(source);
+    }
+    let w = new HTMLWidget(source);
     resolveUrls(w.node, options.resolver);
     return w;
   }
@@ -202,13 +199,6 @@ class ImageRenderer implements RenderMime.IRenderer {
     return true;
   }
 
-  /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    return options.source;
-  }
-
   /**
    * Render the transformed mime bundle.
    */
@@ -247,20 +237,13 @@ class TextRenderer implements RenderMime.IRenderer {
     return true;
   }
 
-  /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    let data = escape_for_html(options.source);
-    return `<pre>${ansi_to_html(data)}</pre>`;
-  }
-
   /**
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
     let w = new Widget();
-    w.node.innerHTML = options.source;
+    let data = escape_for_html(options.source);
+    w.node.innerHTML = `<pre>${ansi_to_html(data)}</pre>`;
     w.addClass(RENDERED_CLASS);
     return w;
   }
@@ -291,13 +274,6 @@ class JavascriptRenderer implements RenderMime.IRenderer {
     return false;
   }
 
-  /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    return options.source;
-  }
-
   /**
    * Render the transformed mime bundle.
    */
@@ -337,19 +313,16 @@ class SVGRenderer implements RenderMime.IRenderer {
     return false;
   }
 
-  /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    return options.source;
-  }
-
   /**
    * Render the transformed mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
+    let source = options.source;
+    if (options.sanitizer) {
+      source = options.sanitizer.sanitize(source);
+    }
     let w = new Widget();
-    w.node.innerHTML = options.source;
+    w.node.innerHTML = source;
     let svgElement = w.node.getElementsByTagName('svg')[0];
     if (!svgElement) {
       throw new Error('SVGRender: Error: Failed to create <svg> element');
@@ -385,13 +358,6 @@ class PDFRenderer implements RenderMime.IRenderer {
     return false;
   }
 
-  /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    return options.source;
-  }
-
   /**
    * Render the transformed mime bundle.
    */
@@ -433,14 +399,7 @@ class LatexRenderer implements RenderMime.IRenderer  {
   }
 
   /**
-   * Transform the input bundle.
-   */
-  transform(options: RenderMime.IRenderOptions): string {
-    return options.source;
-  }
-
-  /**
-   * Render the transformed mime bundle.
+   * Render the mime bundle.
    */
   render(options: RenderMime.IRenderOptions): Widget {
     return new LatexWidget(options.source);
@@ -473,9 +432,9 @@ class MarkdownRenderer implements RenderMime.IRenderer {
   }
 
   /**
-   * Transform the input bundle.
+   * Render the mime bundle.
    */
-  transform(options: RenderMime.IRenderOptions): Promise<string> {
+  render(options: RenderMime.IRenderOptions): Promise<Widget> {
     let parts = removeMath(options.source);
     let renderer = new marked.Renderer();
     renderer.link = (href: string, title: string, text: string) => {
@@ -496,22 +455,19 @@ class MarkdownRenderer implements RenderMime.IRenderer {
       out += '>';
       return out;
     };
-    return new Promise<string>((resolve, reject) => {
+    return new Promise<Widget>((resolve, reject) => {
       marked(parts['text'], { renderer }, (err, content) => {
         if (err) {
           reject(err);
         }
-        resolve(replaceMath(content, parts['math']));
+        content = replaceMath(content, parts['math']);
+        if (options.sanitizer) {
+          content = options.sanitizer.sanitize(content);
+        }
+        resolve(new HTMLWidget(content));
       });
     });
   }
-
-  /**
-   * Render the transformed mime bundle.
-   */
-  render(options: RenderMime.IRenderOptions): Widget {
-    return new HTMLWidget(options.source);
-  }
 }
 
 

+ 15 - 27
src/rendermime/index.ts

@@ -6,7 +6,7 @@ import {
 } from 'phosphor-widget';
 
 import {
-  ISanitizer, defaultSanitizer
+  ISanitizer
 } from '../sanitizer';
 
 
@@ -31,7 +31,7 @@ class RenderMime {
       this._renderers[mime] = options.renderers[mime];
     }
     this._order = options.order.slice();
-    this._sanitizer = options.sanitizer || defaultSanitizer;
+    this._sanitizer = options.sanitizer;
     this._resolver = options.resolver || null;
   }
 
@@ -60,17 +60,12 @@ class RenderMime {
     let options = {
       mimetype,
       source: bundle[mimetype],
-      resolver: this._resolver
+      resolver: this._resolver,
+      sanitizer: trusted ? null : this._sanitizer
     };
     let renderer = this._renderers[mimetype];
-    let transform = renderer.transform(options);
-    return Promise.resolve(transform).then(content => {
-      if (!trusted && renderer.sanitizable(mimetype)) {
-        content = this._sanitizer.sanitize(content);
-      }
-      options.source = content;
-      return renderer.render(options);
-    });
+    let render = renderer.render(options);
+    return Promise.resolve(render);
   }
 
   /**
@@ -181,10 +176,8 @@ namespace RenderMime {
 
     /**
      * The sanitizer used to sanitize html inputs.
-     *
-     * The default is a shared
      */
-    sanitizer?: ISanitizer;
+    sanitizer: ISanitizer;
 
     /**
      * The initial resolver object.
@@ -226,24 +219,12 @@ namespace RenderMime {
      */
     sanitizable(mimetype: string): boolean;
 
-    /**
-     * Transform the input bundle.
-     *
-     * @param options - The options used for transforming.
-     *
-     */
-    transform(options: IRenderOptions): string | Promise<string>;
-
     /**
      * Render the transformed mime bundle.
      *
      * @param options - The options used for rendering.
-     *
-     * #### Notes
-     * It is assumed that the data has been run through [[transform]]
-     * and has been sanitized if necessary.
      */
-    render(options: IRenderOptions): Widget;
+    render(options: IRenderOptions): Widget | Promise<Widget>;
   }
 
   /**
@@ -265,6 +246,13 @@ namespace RenderMime {
      * The url resolver.
      */
     resolver: IResolver;
+
+    /**
+     * An optional html santizer.
+     *
+     * If given, should be used to sanitize raw html.
+     */
+    sanitizer?: ISanitizer;
   }
 
   /**

+ 4 - 3
src/rendermime/plugin.ts

@@ -11,8 +11,9 @@ import {
 } from '../renderers';
 
 import {
-  Widget
-} from 'phosphor-widget';
+  defaultSanitizer
+} from '../sanitizer';
+
 
 
 /**
@@ -40,6 +41,6 @@ const renderMimeProvider = {
         order.push(m);
       }
     }
-    return new RenderMime({ renderers, order });
+    return new RenderMime({ renderers, order, sanitizer: defaultSanitizer });
   }
 };