Skip to content

Commit

Permalink
feat(directive): add whitelist for allowed attributes on element
Browse files Browse the repository at this point in the history
fix #11
  • Loading branch information
Demivan committed Oct 14, 2019
1 parent 31301d7 commit ee9c516
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 2 deletions.
99 changes: 97 additions & 2 deletions src/vue/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,98 @@ import { VNode } from 'vue/types/vnode'
import { warn } from '../util/warn'
import { FluentVueObject } from '../types'

// This part is from fluent-dom library
const LOCALIZABLE_ATTRIBUTES = {
'http://www.w3.org/1999/xhtml': {
global: ['title', 'aria-label', 'aria-valuetext', 'aria-moz-hint'],
a: ['download'],
area: ['download', 'alt'],
// value is special-cased in isAttrNameLocalizable
input: ['alt', 'placeholder'],
menuitem: ['label'],
menu: ['label'],
optgroup: ['label'],
option: ['label'],
track: ['label'],
img: ['alt'],
textarea: ['placeholder'],
th: ['abbr']
},
'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul': {
global: ['accesskey', 'aria-label', 'aria-valuetext', 'aria-moz-hint', 'label'],
key: ['key', 'keycode'],
textbox: ['placeholder'],
toolbarbutton: ['tooltiptext']
}
} as any

/**
* Check if attribute is allowed for the given element.
*
* This method is used by the sanitizer when the translation markup contains
* DOM attributes, or when the translation has traits which map to DOM
* attributes.
*
* `explicitlyAllowed` can be passed as a list of attributes explicitly
* allowed on this element.
*
* @param {string} name
* @param {Element} element
* @param {Array} explicitlyAllowed
* @returns {boolean}
* @private
*/
function isAttrNameLocalizable(
name: string,
element: HTMLElement,
explicitlyAllowed: Array<string> | null = null
) {
if (explicitlyAllowed && explicitlyAllowed.includes(name)) {
return true
}

if (element.namespaceURI === null) {
return false
}

const allowed = LOCALIZABLE_ATTRIBUTES[element.namespaceURI]
if (!allowed) {
return false
}

const attrName = name.toLowerCase()
const elemName = element.localName

// Is it a globally safe attribute?
if (allowed.global.includes(attrName)) {
return true
}

// Are there no allowed attributes for this element?
if (!allowed[elemName]) {
return false
}

// Is it allowed on this element?
if (allowed[elemName].includes(attrName)) {
return true
}

// Special case for value on HTML inputs with type button, reset, submit
if (
element.namespaceURI === 'http://www.w3.org/1999/xhtml' &&
elemName === 'input' &&
attrName === 'value'
) {
const type = (element as HTMLInputElement).type.toLowerCase()
if (type === 'submit' || type === 'button' || type === 'reset') {
return true
}
}

return false
}

function translate(el: HTMLElement, fluent: FluentVueObject, binding: DirectiveBinding) {
const key = binding.arg

Expand All @@ -22,8 +114,11 @@ function translate(el: HTMLElement, fluent: FluentVueObject, binding: DirectiveB
el.textContent = fluent.formatPattern(bundle, msg.value, binding.value)
}

for (const [attr] of Object.entries(binding.modifiers)) {
el.setAttribute(attr, fluent.formatPattern(bundle, msg.attributes[attr], binding.value))
const allowedAttrs = Object.keys(binding.modifiers)
for (const [attr, attrValue] of Object.entries(msg.attributes)) {
if (isAttrNameLocalizable(attr, el, allowedAttrs)) {
el.setAttribute(attr, fluent.formatPattern(bundle, attrValue, binding.value))
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions test/vue/__snapshots__/directive.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`directive automaticaly binds whitelisted attrs 1`] = `<a aria-label="Hello ⁨John⁩">Text</a>`;

exports[`directive can translate DOM attributes 1`] = `<a href="/foo" aria-label="Localized aria">Hello ⁨John⁩</a>`;

exports[`directive can use parameters 1`] = `<a href="/foo">Hello ⁨John⁩</a>`;
Expand Down
24 changes: 24 additions & 0 deletions test/vue/directive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,30 @@ describe('directive', () => {
expect(mounted).toMatchSnapshot()
})

it('automaticaly binds whitelisted attrs', () => {
// Arrange
bundle.addResource(
new FluentResource(ftl`
link = Text
.aria-label = Hello {$name}
.not-allowed = Not allowed attrs value
`)
)

const component = {
data: () => ({
name: 'John'
}),
template: `<a v-t:link="{ name }">Fallback</a>`
}

// Act
const mounted = mount(component, options)

// Assert
expect(mounted).toMatchSnapshot()
})

it('works without fallbacks', () => {
// Arrange
bundle.addResource(
Expand Down

0 comments on commit ee9c516

Please sign in to comment.