-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[I18n] Compile html in angular directive #23684
Changes from all commits
8d14ffc
6486b93
0a8490e
71115a2
81b29c0
c551b36
ad5ba8c
de46c9b
61c4f2b
2b441cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,47 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`i18nDirective inserts correct translation html content 1`] = ` | ||
<div | ||
class="ng-scope ng-isolate-scope" | ||
i18n-default-message="Default message" | ||
i18n-id="id" | ||
> | ||
Default message | ||
</div> | ||
`; | ||
|
||
exports[`i18nDirective inserts correct translation html content with values 1`] = `"default-message word"`; | ||
|
||
exports[`i18nDirective inserts correct translation html content with values 2`] = `"default-message anotherWord"`; | ||
|
||
exports[`i18nDirective sanitizes defaultMessage 1`] = ` | ||
<div | ||
class="ng-scope ng-isolate-scope" | ||
i18n-default-message="Dangerous default message, {unsafe_value}" | ||
i18n-id="id" | ||
i18n-values="{ unsafe_value: '<div i18n-id=\\"id2\\" i18n-default-message=\\"<script></script>inner message\\" />' }" | ||
> | ||
Dangerous default message, | ||
<div | ||
class="ng-scope ng-isolate-scope" | ||
i18n-default-message="<script></script>inner message" | ||
i18n-id="id2" | ||
> | ||
<script></script>inner message | ||
</div> | ||
</div> | ||
`; | ||
|
||
exports[`i18nDirective sanitizes safe value 1`] = ` | ||
<div | ||
class="ng-scope ng-isolate-scope" | ||
i18n-default-message="Default message, {value}" | ||
i18n-id="id" | ||
i18n-values="{ value: '<div ng-click=\\"dangerousAction()\\"></div>' }" | ||
> | ||
Default message, | ||
<div | ||
class="ng-scope" | ||
/> | ||
</div> | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ | |
* under the License. | ||
*/ | ||
|
||
import { IDirective, IRootElementService, IScope } from 'angular'; | ||
import { ICompileService, IDirective, IRootElementService, IScope } from 'angular'; | ||
|
||
import { I18nServiceType } from './provider'; | ||
|
||
|
@@ -27,7 +27,11 @@ interface I18nScope extends IScope { | |
id: string; | ||
} | ||
|
||
export function i18nDirective(i18n: I18nServiceType): IDirective<I18nScope> { | ||
export function i18nDirective( | ||
i18n: I18nServiceType, | ||
$compile: ICompileService, | ||
$sanitize: (html: string) => string | ||
): IDirective<I18nScope> { | ||
return { | ||
restrict: 'A', | ||
scope: { | ||
|
@@ -38,20 +42,39 @@ export function i18nDirective(i18n: I18nServiceType): IDirective<I18nScope> { | |
link($scope, $element) { | ||
if ($scope.values) { | ||
$scope.$watchCollection('values', () => { | ||
setHtmlContent($element, $scope, i18n); | ||
setHtmlContent($element, $scope, $sanitize, i18n); | ||
$compile($element.contents() as any)($scope.$parent); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: I'm still afraid of this "global" compile.... with this even <span i18n-id="kbn.management.someId"
i18n-default-message="Some text that seems safe: {val}"
i18n-values="{ val: '\{\{controller.helpLink=\'http://attacker-site/\';\'value\'\}\}' }">
</span> Is there any way we can compile just I know it's becoming complex, but I feel uncomfortable with how it's currently. Basically we have 3 cases (sorted by popularity):
Am I over-engineering this (or missing something) @spalger @LeanidShutau? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good point. I believe we can do it. It looks like in this case we do not need to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have to be honest, I don't have enough context to say if this is over-engineering or a comprehensive solution. Since I've been asked to review this and it seems like the overall direction is still in flux I'd really like to chat about what's going on here and how/why we're doing things this way. The PR description and the linked issue have basically no details and I just don't know enough to review this... |
||
}); | ||
} else { | ||
setHtmlContent($element, $scope, i18n); | ||
setHtmlContent($element, $scope, $sanitize, i18n); | ||
} | ||
}, | ||
}; | ||
} | ||
|
||
function setHtmlContent($element: IRootElementService, $scope: I18nScope, i18n: I18nServiceType) { | ||
function setHtmlContent( | ||
$element: IRootElementService, | ||
$scope: I18nScope, | ||
$sanitize: (html: string) => string, | ||
i18n: I18nServiceType | ||
) { | ||
const values = Object.entries($scope.values || {}).reduce( | ||
(result, [key, value]) => { | ||
if (key.startsWith('unsafe_')) { | ||
result[key] = value; | ||
} else { | ||
result[key] = $sanitize(value); | ||
} | ||
|
||
return result; | ||
}, | ||
{} as Record<string, any> | ||
); | ||
|
||
$element.html( | ||
i18n($scope.id, { | ||
values: $scope.values, | ||
defaultMessage: $scope.defaultMessage, | ||
values, | ||
defaultMessage: ($scope.defaultMessage || '').replace(/\</g, '<').replace(/\>/g, '>'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why do we need |
||
}) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@azasypkin I believe now it is safe to compile html because:
defaultMessage
.defaultMessage
for some language, before insertingdefaultMessage
to DOM we check passed string using$sanitize
angular service which removes all unsafe tags likescript
and directives.For example we have such text in
defaultMessage
:after using $sanitize service we will get: