1

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!

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
David Notik
  • 2,026
  • 15
  • 21

2 Answers2

3

You need to pass a NodeValidator which is configured to allow specific tags and attributes.

This question HTML Tags Within Internationalized Strings In Polymer.dart contains an example of a custom element that allows to pass HTML using polymer expressions. The implementation demonstrates how to use NodeValidator.

Instead of ..allowTextElements() you can allow other sets of elements/attributes or custom elements/attributes. Autocompletion should show the possible options.

See also
- Dart, why does using innerHtml to set shadow root content work but appendHtml doesn't?
- Dart Removing disallowed attribute after editor upgraded
- Mustache Template usage in Dart
- in dart parse HTML string to DOM

I got it working with

NodeValidator nodeValidator = new NodeValidatorBuilder()
    ..allowNavigation(new MiamiHeraldUrlPolicy());

aContainer.setInnerHtml('<a href="http://miamiherald.typepad.com/the-starting-gate/2014/09/news-.html">bla</a>', 
    validator: nodeValidator);
class MiamiHeraldUrlPolicy implements UriPolicy {
  MiamiHeraldUrlPolicy();

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

  bool allowsUri(String uri) {
    return regex.hasMatch(uri);
  }
}
Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • What output do you get? When tags/attributes are removed this is logged to the console. – Günter Zöchbauer Sep 30 '14 at 06:36
  • Even w/ the more liberal `allowHtml5()` I get: Removing disallowed attribute +ItemActivities Removing disallowed attribute – David Notik Sep 30 '14 at 06:37
  • FWIW: https://github.com/woven/dart-communities/commit/fe9fe3f6b937aad34bad9c4efade7ddfa9a73dd9 Off to bed, to return soon. – David Notik Sep 30 '14 at 06:51
  • Great. Makes sense. Got it working without the safe-html element. I wonder how, given the .setInnerHtml(), to get it working w/ that element? – David Notik Sep 30 '14 at 18:12
  • I'm almost there, but the issue I had is when I passed `model="{{formatText(someText)}}"` it returned and injected the actual HTML into the attribute thus breaking the whole page. So I then tried to move the format function into safe-html, like https://gist.github.com/davenotik/d3fa3b7c02333055d747, but I'm getting a hard to trace error: `Exception caught during observer callback: TypeError: undefined is not a function`. – David Notik Sep 30 '14 at 21:09
3

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.

David Notik
  • 2,026
  • 15
  • 21
  • Shouldn't it be enough to ensure `html` is not `null`? `if(html == null) html = ''` before `content.setInnerHtml(html,...` ? – Günter Zöchbauer Oct 01 '14 at 04:13
  • Not quite, because `html` is not what's `null`, it's the result of `this.text = '';` or similar attempts. https://github.com/woven/dart-communities/blob/3450a02141e5c748efde178adb7b26dbdcf37573/lib/src/client/components/widgets/safe_html/safe_html.dart It's like it writes a `null` right inside `` before the other stuff prints – only in Safari after pub build. – David Notik Oct 01 '14 at 04:20
  • I don't have Safari to test but you assign `html` after `this.text = '';`. What is `this.text` supposed to do anyway?. Why are you using a ``? `` is not supposed to have content. `` is just a placeholder for Polymer to know where to project the children of the polymer element. – Günter Zöchbauer Oct 01 '14 at 04:26
  • Ok so I can simply read the innerHtml to get the literal html, then process it and set back the validated stuff, no content tag needed? – David Notik Oct 01 '14 at 04:34
  • If you use the `` tag you can manipulate the children/innerHtml of `` like you did with `this.text = '';` to get the desired effect. If you don't use a `` tag you can manipulate the `shadowRoot.children` or add a placeholder tag of some kind (div, span, ...) into the shadowDOM of `` (like I did in the linked example) and manipulate its children/innherHtml. – Günter Zöchbauer Oct 01 '14 at 04:59
  • Ok, I've updated w/ what's now working. Gone are the null issues post dart2js. How's it look to you? One thing that feels odd is how I can't just do `String fragment = this.text; this.setInnerHtml(fragment, validator: validator);` and instead need the container so the fragment gets inserted into the shadowRoot. – David Notik Oct 01 '14 at 17:56
  • 1
    Yes and thank you! See comments above and edited answer. Thoughts? – David Notik Oct 02 '14 at 06:20