AngularJS and Sanitize - Sanitize HTML without ngBind Directive - javascript

Lets supose we need to sanitize an HTML string and we can't use ng-bind-html directive, for example:
<span data-toggle="tooltip" title="Edit {{customer.name}}">Text</span>
If we have special chars in customer.name this line would be printed as the html version like é and we want é instead.
I have tested with:
$sce.trustAsHtml(customer.name)
$sce.parseAsHtml(customer.name)
But nothing can "translate" this html. How can be this done?
A short explanation would be: how to sanitize html inside a directive (not in the body with ng-bind-html).

It doesn't have to be so complicated.
Instead, use setAttribute and textContent (V.S. innerHTML) on elements, and browsers themselves will take care of sanitizing for you.
// To set element attributes
$span.setAttribute("title", "Edit" + customer.name);
// To set element content
$span.textContent = customer.name;
For more details, see the post here. These are one time bindings of course, so if you need updates, just throw $watch in the midst.

From an oficial documentation:
ngBindHtml uses $sce.parseAsHtml(binding expression). Here's the actual code (slightly simplified):
var ngBindHtmlDirective = ['$sce', function($sce) {
return function(scope, element, attr) {
scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
element.html(value || '');
});
};
}];
so I think all you need is $sce.parseAsHtml (https://docs.angularjs.org/api/ng/service/$sce#parseAsHtml).
If you are not able to persuade angular to print HTML anyway, you can try to use
customer.name.replace(/é/g, String.fromCharCode(233));
You can find some basic codes here: http://www.javascripter.net/faq/accentedcharacters.htm
That should work, but it is not definitely the best solution. You should always use ng-bind-html.

Related

How to use ng-translate with variables resolved from controller?

I'm using ng-tranlate for i18n.
I'd like to combine a translated label together with a variable resolved from controller binding. How can I achieve the following?
<div translate="my.lang.text">some more: {{controller.attribute}}</div>
This does not work, and ng-translate ignores any content between the divs. why?
The translate directive will replace the content of the element with the translation you pass to it.
The use case you are describing looks like a parameterized translation. If you want to keep the use of the directive, you could pass the variable through the translate-values directive:
<div translate="my.lang.text"
translate-values="{value: 'some more: ' + controller.attribute}"></div>
You have to specify that your translation is parameterized:
JSON
"my.lang.text": "This is a parameterized string {value}"
I believe the translate directive replaces all the element's content with the translation.
In this case, you probably want to use the translate filter instead.
<div>{{'my.lang.text' | translate}} some more: {{controller.attribute}}</div>
As an alternative, you could also avoid this issue by giving the translated value it's own element.
<div><span translate="my.lang.text"></span> some more: {{controller.attribute}}</div>
If the translation is always intended to have have a value appended to it, then using a parameterized translation is probably the best solution (as suggested by Michael https://stackoverflow.com/a/33419608/90305)

Include an HTML/CSS/JS file as code, not rendered

I have a page that I am using ng-include to pull in a file as rendered HTML. I would like to include that same file as code, next to it.
Is there a way to do this in Angular, or jQuery? I want to be able to include HTML, CSS and potentially JS files as code that would be run through a code-renderer, like Prism or something similar.
This is similar to jsfiddle or codepen, in that you can see the code and rendered view in the same window, but I do not need them to be connected (ie. edit the code to see the rendered result.) I just want to pull both views from the same file.
I am currently using an Angular directive to loop through a JSON file and push out blocks of HTML according to what is listed in the JSON. My directive is:
app.directive('patterns', function() {
return {
restrict: 'E',
require: ['^ngType', 'ngTemplate'],
scope: {
ngType: '#',
ngTemplate: '#'
},
templateUrl: 'patterns.html',
controller: function($scope, $http, $sanitize) {
var theType = $scope.ngType;
$http.get('indexes/json/' + theType + '.json')
.then(function(result) {
$scope.patterns = result.data;
});
},
controllerAs: 'patterns',
link: function(scope, iElement, iAttrs, ctrl) {
scope.getType(iAttrs.ngType);
}
}
});
And I want to add something that also uses pattern.anchor (based off an "anchor" key I have in my JSON) to grab a particular file and output only the code. I can currently use pattern.anchor with ng-include to output rendered HTML, but not only code.
Yes, with angular you can use ngSanitize or the $sanitize service.
There's a simple example available here: http://codepen.io/tipsoftheday/pen/jthks
angular.module('expressionsEscaping', ['ngSanitize'])
.controller('ExpressionsEscapingCtrl', function ($scope, $sanitize) {
$scope.msg = 'Hello, <b>World</b>!';
$scope.safeMsg = $sanitize($scope.msg);
});
and a more complex example available in the Angular documentation here: https://docs.angularjs.org/api/ngSanitize/service/$sanitize.
Ampersand-Delineated Character Entity
In the code, replace all angle brackets < and > with a character entity code < and >. The entity codes get rendered as angle brackets but don't get processed as such. Unfortunately, this doesn't preserve the formatting of your file (read on to see how to do that), since all whitespace gets compressed to a single non-breaking space.
<strong> The strong tag is shown rather
than rendered, but all whitespace characters
still get compressed into a single space.
However, this <br /> break tag gets rendered rather
than shown. </strong>
HTML provides a block-level element called pre. Anything inside the pre tag is considered pre-rendered and the browser displays it as-is. Whitespace in this block does not get condensed. This will preserve the formatting of your code.
<pre>
<strong> In this block, if I add extra spaces, they are shown.
If I move to a newline, that is shown as well.
HTML tags still need to have<br />their angle brackets replaced
in order to be shown rather than rendered.</strong>
</pre>
If you are using JavaScript/AJAX to include the file, you can first do a string replace function to replace the angle brackets with the character entity codes. If you are doing server-side includes, you can do something similar with your server-side language of choice.
If you want to do all of this automatically, Mike Feltman suggested a method using Angular.

$compile link function apparently not applying scope

I'm trying to compile some HTML on my controller like so:
$scope.online = networkService.isOnline();
var wrapper = angular.element(document.createElement("i"));
var compiledContents = $compile($scope.chapter.contents);
var linkedContents = compiledContents($scope);
wrapper.append(linkedContents);
$scope.chapter.linkedContents = wrapper.html();
Where the HTML being compiled has a few elements with a ng-if='online' there. But the compiled HTML always comes out with those elements commented, as if online was not true (which it is - I even got to the point where I added a console.log(scope.online) in Angular's $compile function and it printed true).
Am I missing anything here?
wrapper.html() gives back a string representing the inner HTML of the element. You are then assigning that string to $scope.chapter.linkedContents. Although it is not clear from your question how and where you are actually using $scope.chapter.linkedContents, one thing is certain - this string is definitely not "linked" in any way.
The actual "linkedContents" are the elements that should end up in the DOM. In your case, it is the wrapper element with its contents, but again - unclear, how, if ever, it ends up in the DOM.
But you shouldn't even be dealing with DOM in a controller. Controllers are DOM agnostic, so right there you should see a big warning sign that you are doing something wrong. Make sure that you understand the role of a controller, a directive, etc...
I think I understand what the problem you are trying to solve. You get some dynamic uncompiled HTML (or actual elements) - i.e. $scope.chapter.contents and you need to have it placed in the DOM and compiled/linked.
Typically, to bind HTML one would use ng-bind-html (assuming it's either trusted or sanitization is on):
<div ng-bind-html="chapter.contents">
</div>
But this would not be $compiled. To compile, I'd suggest writing your own directive that would work similar to ng-bind-html, but would also compile it:
<div compile-html="chapter.contents">
</div>
Then, the directive would take the content, compile/link it against some scope (say, child scope) and append it to the element hosting the directive compileHtml.
.directive("compileHTML", function($compile, $parse){
return {
scope: true,
link: function(scope, element, attrs){
// get the HTML content
var html = $parse(attrs.compileHtml)(scope);
element.empty();
// DISCLAIMER: I'm not dealing with sanitization here,
// but you should keep it in mind
$compile(html)(scope, function cloneAttachFn(prelinkContent){
element.append(prelinkContent);
});
}
};
})

Is there a direct way of appending text to a DOM element with jQuery?

Is there a more direct way of writing the following in jQuery?
var $b = $('b');
$b.text($b.text() + ', World!!');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<b>Hello</b>
This seams like something jQuery would have existing functionality for, as vanilla JavaScript can do it by direct access to the property.
document.querySelector('b').innerText += ', World!!';
I looked into the .append() method, however it appears that it isn't designed for appending text, even though it works:
$('b').append(', World!!');
Also the Additional Notes section warns of XSS vulnerabilities when using .append(), as it can potentially execute code.
No. As you have already pointed out, the cleanest way is by modifying the property directly.
To do that in jQuery you can get the DOM element reference from within the object:
var $b = $('b');
$b[0].innerText += ', World!!';
JSFiddle
Or,
You could pass a function to .text(), which isn't any 'cleaner' but can be very helpful if you want to use the context:
var $b = $('b');
$b.text(function(_,v){
return v += ', World!!';
});
JSFiddle
Or,
If it really bugs you, introduce your own jQuery method:
jQuery.fn.appendText = function(a){
return this.each(function(){
$(this).text(function(_,v){
return v += a;
});
});
};
For use like so:
$b.appendText(', World!!');
JSFiddle
'This seams like something jQuery would have existing functionality for, as vanilla JavaScript can do it by direct access to the property.'
Is probably exactly why jQuery doesn't implement its own method to do so. Why waste valuable bytes with a method that will carry out something that is so easily done with vanilla JavaScript?
use text()
$('b').text("Hello");
We need to be aware that this method escapes the string provided as
necessary so that it will render correctly in HTML. To do so, it calls
the DOM method .createTextNode(), which replaces special characters
with their HTML entity equivalents (such as < for <)
When you use .text() jQuery uses createTextNode internally, which escapes all special characters.

Is there a way to make jQuery output *actual markup*?

When using jQuery to dynamically build markup, it sometimes becomes useful to have it return the actual HTML that it's generating as a string, rather than as a bunch of jQuery objects. Is there a way to do this? For example, here:
$("<strong></strong>").text("Hi there!");
I want to be able to extract the plain-text string
"<strong>Hi there!</strong>"
so that I can cache it remotely. Is there a way to do this?
Yes, you can use the html() function
i.e.
$("").text("Hi There!").html();
Will return 'Hi There!'
Keep in mind this uses innerHTML, so
$("<div><b>Foo</b></div>").html();
will return
<b>Foo</b>
As a result you'll need to wrap your code in a surrounding div or span.
You can use a outerHTML plugin for that. Here is one:
jQuery.fn.outerHTML = function(s) {
return (s)
? this.before(s).remove()
: jQuery("<p>").append(this.eq(0).clone()).html();
}
Usage:
alert($("<strong></strong>").text("foo").outerHTML());
// alerts <strong>foo</strong>
Just call .html() to get HTML from any element, including generated ones. This is from a Chrome developer tool session:
> $("<div><span>blerg</span></div>")
Object
> $("<div><span>blerg</span></div>").html()
<span>blerg</span>
You can see, the first one returned an object, the second returns text.

Categories

Resources