Safely injecting HTML into a Polymer template

Issue

In a custom element, I try to replace an item’s body text with HTML for line breaks and links. It was working when I just handled line breaks (nl2br()), but no longer when handling links (linkify()).

  get formattedBody {
    if (item.isEmpty) return 'Loading...';
    return "${InputFormatter.nl2br(InputFormatter.linkify(item['body']))}";
  }

  itemChanged() {
    // Trick to respect line breaks.
    HtmlElement body = $['body'];
    body.innerHtml = formattedBody;
  }

I get a warm and safe security message e.g.:

Removing disallowed attribute <A href="http://miamiherald.typepad.com/the-starting-gate/2014/09/news-.html">

I also tried setInnerHtml(), to no avail.

Ideas? Thank you!

Solution

I’ve got a <safe-html> element working. Verified on Chrome and even post-dart2js on Safari. Vote up this answer if you’d like me to turn it into a lib available on pub.dartlang.org.

Usage:

<safe-html validator="{{nodeValidator}}">{{someHtml}}</safe-html>

(Passing in your own validator is optional. Without it, we’ll use a default.)

safe_html.html:

<link rel="import" href="../../../../../../../packages/polymer/polymer.html">
<polymer-element name="safe-html">
  <template>
    <div id="container"></div>
  </template>

  <script type="application/dart" src='safe_html.dart'></script>
</polymer-element>

safe_html.dart:

library safe_html;

import 'dart:async';
import "dart:html";

import "package:polymer/polymer.dart";

@CustomTag("safe-html")
class SafeHtml extends PolymerElement  {
  @published NodeValidator validator = new NodeValidatorBuilder()
    ..allowHtml5(uriPolicy: new DefaultUriPolicy());

  SafeHtml.created() : super.created();

  addFragment() {
    DivElement container = $['container'];
    String fragment =  this.text;
    container.setInnerHtml(fragment, // Set the fragment in a safe way.
      validator: validator);
    this.text = ""; // Clear the original fragment passed to the element.
  }

  attached() {
    addFragment();
  }
}

class DefaultUriPolicy implements UriPolicy {
  DefaultUriPolicy();

  // Allow all external, absolute URLs.
  RegExp regex = new RegExp(r'(?:http://|https://|//)?.*');

  bool allowsUri(String uri) {
    return regex.hasMatch(uri);
  }
}

If you choose to pass your own NodeValidator, do it by specifying a getter in the parent element that uses <safe-html>:

NodeValidator get nodeValidator => new NodeValidatorBuilder()
..allowHtml5(uriPolicy: new ItemUrlPolicy());

As you can see I reference a UriPolicy which I keep in a separate file like uri_policy.dart:

import 'dart:html';

class ItemUrlPolicy implements UriPolicy {
  ItemUrlPolicy();

  RegExp regex = new RegExp(r'(?:http://|https://|//)?.*');

  bool allowsUri(String uri) {
    return regex.hasMatch(uri);
  }
}

With lots of help from Günter Zöchbauer and the authors of other helpful posts on SO.

Answered By – David Notik

Answer Checked By – Mary Flores (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *