-
Notifications
You must be signed in to change notification settings - Fork 153
Description
The Issue
Assuming this is your controller:
myApp.controller('TestController', function ($scope) {
$scope.one = 1;
$scope.someUnsafeHtml = "<b>{{ one }} + {{ one }} = {{ one + one }}</b>";
});And this is your HTML
<div translate>
{{ someUnsafeHtml }}
</div>AngularJS and/or angular-gettext will make sure, that someUnsafeHtml is properly escaped. So far, so good.
However, as soon as you use the translate-params-some-unsafe-html="someUnsafeHtml" attribute, someUnsafeHtml is no longer escaped.
<div translate translate-params-some-unsafe-html="someUnsafeHtml">
{{ someUnsafeHtml }}
</div>Interesting enough, the string also seems to be not escaped, when using a different attribute name and empty value (translate-params-nothing):
<div translate translate-params-nothing>
{{ someUnsafeHtml }}
</div>I believe that this behaviour is not the intended behaviour, as this means that there is a potential security issue as soon as one uses the translate-params- attributes is used with user generated content (of course, one should always sanitize input variables before adding them to the database).
I have created a jsfiddle with Angular 1.5.10 and angular-gettext 2.3.10 that displays some other cases aswell: https://jsfiddle.net/wy0fu3hd/9/
I believe that it should be possible to render HTML, if and only if you specifically mark the content as trusted HTML content using $sce
myApp.controller('TestController', function ($scope) {
$scope.one = 1;
$scope.someHtml = $sce.trustAsHtml("<b>{{ one }} + {{ one }} = {{ one + one }}</b>");
});and specially use the translate-params- attribute
<div translate translate-params-some-html="someHtml">
{{ someHtml }}
</div>Though it might make sense that this is only supported using some additional attribute, e.g.:
<div translate translate-htmlparams-some-html="someHtml">
{{ someHtml }}
</div>Also paging @IShotTheSheriff on this, as the original concept is from him (issue #285).
Analysis
This is happening because of the getString method in gettextCatalog (see https://github.com/rubenv/angular-gettext/blob/master/src/catalog.js#L243-L249):
getString: function (string, scope, context) {
var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage);
string = this.getStringFormFor(this.currentLanguage, string, 1, context) ||
this.getStringFormFor(fallbackLanguage, string, 1, context) ||
prefixDebug(string);
string = scope ? $interpolate(string)(scope) : string;
return addTranslatedMarkers(string);
},Especially the part where it says $interpolate(string)(scope).
The $interpolate factory/service is called when a scope is passed to the getString function. This happens when using the translate-params-* attribute, as this defines an interpolationContext:
function handleInterpolationContext(scope, attrs, update) {
// ...
var interpolationContext = angular.extend({}, scope);
var unwatchers = [];
attributes.forEach(function (attribute) {
var unwatch = scope.$watch(attrs[attribute], function (newVal) {
var key = getCtxAttr(attribute);
interpolationContext[key] = newVal;
update(interpolationContext);
});
unwatchers.push(unwatch);
});
// ...
}Here var interpolationContext = angular.extend({}, scope); creates a new scope, which is then getting the values of the passed parameters of translate-params-*: interpolationContext[key] = newVal;
Then the update(interpolationContext) method is called, which calls the getString() method from above with the interpolationContext as scope.
By calling $interpolate here, we will end up with an interpolated string, which is eventually set as the main content of the element in the update() method:
function update(interpolationContext) {
interpolationContext = interpolationContext || null;
// ...
translated = gettextCatalog.getString(msgid, interpolationContext, translateContext);
// ...
var newWrapper = angular.element('<span>' + translated + '</span>');
$compile(newWrapper.contents())(scope);
var newContents = newWrapper.contents();
// ...
}Solution
I am not sure, how to solve this problem yet. Surely it has something to do with the combination of $interpolate and $compile aswell as the concatenation '<span>' + translated + '</span>'.
I am however sure, that it should be possible to inject HTML, if specified by the developer.