273

I'm trying to use $sanitize provider and the ng-bind-htm-unsafe directive to allow my controller to inject HTML into a DIV.

However, I can't get it to work.

<div ng-bind-html-unsafe="{{preview_data.preview.embed.html}}"></div>

I discovered that it is because it was removed from AngularJS (thanks).

But without ng-bind-html-unsafe, I get this error:

http://errors.angularjs.org/undefined/$sce/unsafe

simeg
  • 1,889
  • 2
  • 26
  • 34
metalaureate
  • 7,572
  • 9
  • 54
  • 93

10 Answers10

356

Instead of declaring a function in your scope, as suggested by Alex, you can convert it to a simple filter :

angular.module('myApp')
    .filter('to_trusted', ['$sce', function($sce){
        return function(text) {
            return $sce.trustAsHtml(text);
        };
    }]);

Then you can use it like this :

<div ng-bind-html="preview_data.preview.embed.html | to_trusted"></div>

And here is a working example : http://jsfiddle.net/leeroy/6j4Lg/1/

Leeroy Brun
  • 4,468
  • 2
  • 16
  • 12
  • 3
    I have a small collection of useful tools for angular on [github](https://github.com/capaj/ng-tools), I will include this filter in those tools if you won't mind. This is IMHO the best solution when you trust the html. – Capaj Apr 07 '14 at 08:44
  • @Capaj No problem, but if you add a link to this answer this would be greatly appreciated. :-) http://stackoverflow.com/a/21254635 – Leeroy Brun Apr 08 '14 at 09:41
  • Very nice. this works like a charm on nested repeats! – Jelle Verzijden Jul 08 '15 at 21:22
  • 1
    This seems like a MUCH better solution than coding for each controller. Just a quick filter and done! I used it with repeating table rows, simple as pie.... `` – Phil Nicholas Oct 06 '15 at 02:47
  • 2
    angular.module('myApp').filter('trustAsHtml', ['$sce', function($sce) { return $sce.trustAsHtml }]); – bradw2k Nov 11 '15 at 19:04
  • Every solution that involves blessing the HTML as trusted introduces a XSS vulnerability. Please see the answer suggesting ngSanitize below (stackoverflow.com/a/25679834/22227) for a safer fix. – Michele Spagnuolo Feb 22 '16 at 20:11
275

You indicated that you're using Angular 1.2.0... as one of the other comments indicated, ng-bind-html-unsafe has been deprecated.

Instead, you'll want to do something like this:

<div ng-bind-html="preview_data.preview.embed.htmlSafe"></div>

In your controller, inject the $sce service, and mark the HTML as "trusted":

myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
  // ...
  $scope.preview_data.preview.embed.htmlSafe = 
     $sce.trustAsHtml(preview_data.preview.embed.html);
}

Note that you'll want to be using 1.2.0-rc3 or newer. (They fixed a bug in rc3 that prevented "watchers" from working properly on trusted HTML.)

simeg
  • 1,889
  • 2
  • 26
  • 34
ijprest
  • 4,208
  • 1
  • 19
  • 12
  • 2
    I tried using the above but it breaks my code. Seems you need to prepend '$scope' before the function definition -- perhaps it was "understood" at one time, but no longer. The following should work: `myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {` – Dexygen Jan 28 '14 at 18:36
  • 4
    You can look more information about $sce [here](https://docs.angularjs.org/api/ng/service/$sce) just to pursue curiosity! ;) – genuinefafa Jul 08 '14 at 14:33
  • 5
    Note that this will likely cause an XSS security issue in your code. See the answer suggesting `ngSanitize` below (http://stackoverflow.com/a/25679834/22227) for an alternative, safer fix. – Martin Probst Jan 30 '16 at 20:12
  • Why this is a bad idea: https://docs.google.com/presentation/d/1fBwDABfa2RdWxSFwadq8qD7soDIcqGzSvANkm1nlNas/pub?start=false&loop=false&delayms=3000&slide=id.g103580afa1_1_851 – user857990 Mar 11 '16 at 13:16
  • 1
    `trustAsHtml` does what it says, it trusts *any* incoming html code, which can result in Cross-Site Scripting (XSS) attacks – Aleksey Solovey Apr 12 '19 at 16:59
124
  1. You need to make sure that sanitize.js is loaded. For example, load it from https://ajax.googleapis.com/ajax/libs/angularjs/[LAST_VERSION]/angular-sanitize.min.js
  2. you need to include ngSanitize module on your app eg: var app = angular.module('myApp', ['ngSanitize']);
  3. you just need to bind with ng-bind-html the original html content. No need to do anything else in your controller. The parsing and conversion is automatically done by the ngBindHtml directive. (Read the How does it work section on this: $sce). So, in your case <div ng-bind-html="preview_data.preview.embed.html"></div> would do the work.
p.matsinopoulos
  • 7,655
  • 6
  • 44
  • 92
113

For me, the simplest and most flexible solution is:

<div ng-bind-html="to_trusted(preview_data.preview.embed.html)"></div>

And add function to your controller:

$scope.to_trusted = function(html_code) {
    return $sce.trustAsHtml(html_code);
}

Don't forget add $sce to your controller's initialization.

Ken Smith
  • 20,305
  • 15
  • 100
  • 147
Alex
  • 1,139
  • 1
  • 7
  • 2
  • Seems more straightforward to have the controller returned the trusted html in $scope – Northstrider Jun 26 '14 at 22:23
  • 1
    This can throw infinite loop on $sce, do something like: $scope.trusted = {}; $scope.to_trusted = function(html_code) { return $scope.trusted[html_code] || ($scope.trusted[html_code] = $sce.trustAsHtml(html_code)); }; – AO_ Jul 17 '14 at 11:37
  • 1
    Every solution that involves blessing the HTML as trusted introduces a XSS vulnerability. Please see the answer suggesting ngSanitize below (stackoverflow.com/a/25679834/22227) for a safer fix. – Michele Spagnuolo Feb 22 '16 at 20:11
69

The best solution to this in my opinion is this:

  1. Create a custom filter which can be in a common.module.js file for example - used through out your app:

    var app = angular.module('common.module', []);
    
    // html filter (render text as html)
    app.filter('html', ['$sce', function ($sce) { 
        return function (text) {
            return $sce.trustAsHtml(text);
        };    
    }])
    
  2. Usage:

    <span ng-bind-html="yourDataValue | html"></span>
    

Now - I don't see why the directive ng-bind-html does not trustAsHtml as part of its function - seems a bit daft to me that it doesn't

Anyway - that's the way I do it - 67% of the time, it works ever time.

ThisClark
  • 14,352
  • 10
  • 69
  • 100
Paul
  • 783
  • 6
  • 2
  • You can use the following regex to do a find and replace: regex: ng-bind-html-unsafe="((?:(?!").)*)" replacement: ng-bind-html="($1) | html" with the above filter. – George Donev Dec 23 '15 at 12:07
  • 2
    Every solution that involves blessing the HTML as trusted introduces a XSS vulnerability. Please see the answer suggesting ngSanitize below (stackoverflow.com/a/25679834/22227) for a safer fix. – Michele Spagnuolo Feb 22 '16 at 20:11
7

You can create your own simple unsafe html binding, of course if you use user input it could be a security risk.

App.directive('simpleHtml', function() {
  return function(scope, element, attr) {
    scope.$watch(attr.simpleHtml, function (value) {
      element.html(scope.$eval(attr.simpleHtml));
    })
  };
})
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113
5

You do not need to use {{ }} inside of ng-bind-html-unsafe:

<div ng-bind-html-unsafe="preview_data.preview.embed.html"></div>

Here's an example: http://plnkr.co/edit/R7JmGIo4xcJoBc1v4iki?p=preview

The {{ }} operator is essentially just a shorthand for ng-bind, so what you were trying amounts to a binding inside a binding, which doesn't work.

ksimons
  • 3,797
  • 17
  • 17
  • However, if I remove it, I get nothing injected. And the docs are highly confusing, using a single } http://docs-angularjs-org-dev.appspot.com/api/ng.directive:ngBindHtmlUnsafe – metalaureate Oct 16 '13 at 23:18
  • Very odd. I've just tested it to be sure and for me it worked as expected. I agree the single { } are a bit confusing in the docs, but they're meant as a representation of an expression, not as literals in the string. I've updated my answer with a working plunk. – ksimons Oct 16 '13 at 23:28
  • Also, if you're using 1.2.0 already, see the comments here as ng-bind-html-unsafe has been removed: http://docs.angularjs.org/api/ng.directive:ngBindHtml – ksimons Oct 16 '13 at 23:29
  • 2
    I am using 1.2. :( Grrr! How can one inject unsafe HTML? I get this error without it: http://errors.angularjs.org/undefined/$sce/unsafe – metalaureate Oct 16 '13 at 23:51
  • The `{{}}` operator was causing my issue with binding failing, thanks for the hint! – Campbeln Jul 24 '14 at 06:01
2

I've had a similar problem. Still couldn't get content from my markdown files hosted on github.

After setting up a whitelist (with added github domain) to the $sceDelegateProvider in app.js it worked like a charm.

Description: Using a whitelist instead of wrapping as trusted if you load content from a different urls.

Docs: $sceDelegateProvider and ngInclude (for fetching, compiling and including external HTML fragment)

Lahmizzar
  • 487
  • 6
  • 6
2

Strict Contextual Escaping can be disabled entirely, allowing you to inject html using ng-html-bind. This is an unsafe option, but helpful when testing.

Example from the AngularJS documentation on $sce:

angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
  // Completely disable SCE.  For demonstration purposes only!
  // Do not use in new projects.
  $sceProvider.enabled(false);
});

Attaching the above config section to your app will allow you inject html into ng-html-bind, but as the doc remarks:

SCE gives you a lot of security benefits for little coding overhead. It will be much harder to take an SCE disabled application and either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE for cases where you have a lot of existing code that was written before SCE was introduced and you're migrating them a module at a time.

Sean Fahey
  • 1,850
  • 3
  • 26
  • 36
2

You can use filter like this

angular.module('app').filter('trustAs', ['$sce', 
    function($sce) {
        return function (input, type) {
            if (typeof input === "string") {
                return $sce.trustAs(type || 'html', input);
            }
            console.log("trustAs filter. Error. input isn't a string");
            return "";
        };
    }
]);

usage

<div ng-bind-html="myData | trustAs"></div>

it can be used for other resource types, for example source link for iframes and other types declared here

BotanMan
  • 1,357
  • 12
  • 25