Skip to content

Commit

Permalink
Add escapeParameterHtml parameter. #1002 (#1009)
Browse files Browse the repository at this point in the history
Co-authored-by: Gardar Hauksson <[email protected]>
  • Loading branch information
gardarh and Gardar Hauksson authored Oct 6, 2020
1 parent ce45265 commit db6edc3
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 1 deletion.
1 change: 1 addition & 0 deletions decls/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ declare type I18nOptions = {
sharedMessages?: LocaleMessage,
postTranslation?: PostTranslationHandler,
componentInstanceCreatedListener?: ComponentInstanceCreatedListener,
escapeParameterHtml?: boolean,
};

declare type IntlAvailability = {
Expand Down
34 changes: 34 additions & 0 deletions examples/formatting/escape-parameter-html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Escape parameter HTML example</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-i18n.esm.browser.js"></script>
</head>
<body>
<div id="app">
<p v-html="displayMsg"></p>
</div>
<script>
const i18n = new VueI18n({
locale: 'en',
escapeParameterHtml: true,
messages: {
en: {
message: 'User input: <b>{value}</b>',
}
}
})
new Vue({
el: '#app',
i18n: i18n,
computed: {
displayMsg() {
return this.$t('message', {value: '<img src="" onError="alert(42)">'})
}
}
})
</script>
</body>
</html>
9 changes: 8 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
remove,
includes,
merge,
numberFormatKeys
numberFormatKeys,
escapeParams
} from './util'
import BaseFormatter from './format'
import I18nPath from './path'
Expand Down Expand Up @@ -59,6 +60,7 @@ export default class VueI18n {
_componentInstanceCreatedListener: ?ComponentInstanceCreatedListener
_preserveDirectiveContent: boolean
_warnHtmlInMessage: WarnHtmlInMessageLevel
_escapeParameterHtml: boolean
_postTranslation: ?PostTranslationHandler
pluralizationRules: {
[lang: string]: (choice: number, choicesLength: number) => number
Expand Down Expand Up @@ -111,6 +113,7 @@ export default class VueI18n {
this.pluralizationRules = options.pluralizationRules || {}
this._warnHtmlInMessage = options.warnHtmlInMessage || 'off'
this._postTranslation = options.postTranslation || null
this._escapeParameterHtml = options.escapeParameterHtml || false

/**
* @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
Expand Down Expand Up @@ -650,6 +653,10 @@ export default class VueI18n {
if (!key) { return '' }
const parsedArgs = parseArgs(...values)
if(this._escapeParameterHtml) {
parsedArgs.params = escapeParams(parsedArgs.params)
}
const locale: Locale = parsedArgs.locale || _locale
let ret: any = this._translate(
Expand Down
33 changes: 33 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,36 @@ export function looseEqual (a: any, b: any): boolean {
return false
}
}

/**
* Sanitizes html special characters from input strings. For mitigating risk of XSS attacks.
* @param rawText The raw input from the user that should be escaped.
*/
function escapeHtml(rawText: string): string {
return rawText
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
/**
* Escapes html tags and special symbols from all provided params which were returned from parseArgs().params.
* This method performs an in-place operation on the params object.
*
* @param {any} params Parameters as provided from `parseArgs().params`.
* May be either an array of strings or a string->any map.
*
* @returns The manipulated `params` object.
*/
export function escapeParams(params: any): any {
if(params != null) {
Object.keys(params).forEach(key => {
if(typeof(params[key]) == 'string') {
params[key] = escapeHtml(params[key])
}
})
}
return params
}
29 changes: 29 additions & 0 deletions test/unit/escape_parameter_html.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const messages = {
en: {
listformat: '{0}',
nameformat: '{key}',
}
}

describe('escapeParameterHtml', () => {
it('Replacement parameters are escaped when escapeParameterHtml: true.', () => {
const i18n = new VueI18n({
locale: 'en',
messages,
escapeParameterHtml: true
})
assert(i18n.t('nameformat', { key: '<&"\'>' }) === '&lt;&amp;&quot;&apos;&gt;')
assert(i18n.t('listformat', ['<&"\'>']) === '&lt;&amp;&quot;&apos;&gt;')
assert(i18n.tc('nameformat', 1, { key: '<&"\'>' }).toString() === '&lt;&amp;&quot;&apos;&gt;')
assert(i18n.tc('listformat', 1, ['<&"\'>']).toString() === '&lt;&amp;&quot;&apos;&gt;')
})
it('Replacement parameters are not escaped when escapeParameterHtml: undefined.', () => {
const i18n = new VueI18n({
locale: 'en',
messages,
})
assert(i18n.t('nameformat', { key: '<&"\'>' }) === '<&"\'>')
assert(i18n.t('listformat', ['<&"\'>']) === '<&"\'>')

})
})
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ declare namespace VueI18n {
sharedMessages?: LocaleMessages;
postTranslation?: PostTranslationHandler;
componentInstanceCreatedListener?: ComponentInstanceCreatedListener;
escapeParameterHtml?: boolean;
}
}

Expand Down
17 changes: 17 additions & 0 deletions vuepress/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,23 @@ A handler for getting notified when component-local instance was created. The ha
This handler is useful when extending the root VueI18n instance and wanting to also apply those extensions to component-local instance.
#### espaceParameterHtml
> 8.22+
* **Type:** `Boolean`
* **Default:** `false`
If `escapeParameterHtml` is configured as true then interpolation parameters are escaped before the message is translated.
This is useful when translation output is used in `v-html` and the translation resource contains html markup (e.g. `<b>`
around a user provided value). This usage pattern mostly occurs when passing precomputed text strings into UI compontents.
The escape process involves replacing the following symbols with their respective HTML character entities: `<`, `>`, `"`, `'`, `&`.
Setting `escapeParameterHtml` as true should not break existing functionality but provides a safeguard against a subtle
type of XSS attack vectors.
### Properties
#### locale
Expand Down

0 comments on commit db6edc3

Please sign in to comment.