Skip to content

Commit

Permalink
Add support for disabled checkboxes, task-list-items
Browse files Browse the repository at this point in the history
These changes add support for allowing only certain values for a property and
to define required attributes.

Closes GH-12.
Closes GH-13.

Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com>

Co-authored-by: Arystan <ari7even@gmail.com>
  • Loading branch information
wooorm and metalsm1th committed Jan 9, 2019
1 parent 4d8ffce commit ba16c15
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 12 deletions.
16 changes: 15 additions & 1 deletion lib/github.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
"s",
"strike",
"summary",
"details"
"details",
"input"
],
"attributes": {
"a": [
Expand All @@ -110,6 +111,13 @@
"src",
"longDesc"
],
"input": [
["type", "checkbox"],
["disabled", true]
],
"li": [
["className", "task-list-item"]
],
"div": [
"itemScope",
"itemType"
Expand Down Expand Up @@ -196,5 +204,11 @@
"width",
"itemProp"
]
},
"required": {
"input": {
"type": "checkbox",
"disabled": true
}
}
}
62 changes: 51 additions & 11 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module.exports = wrapper

var own = {}.hasOwnProperty

var allData = 'data*'

var NODES = {
root: {children: all},
doctype: handleDoctype,
Expand Down Expand Up @@ -134,48 +136,62 @@ function all(schema, children, node, stack) {
function handleProperties(schema, properties, node, stack) {
var name = handleTagName(schema, node.tagName, node, stack)
var attrs = schema.attributes
var reqs = schema.required || /* istanbul ignore next */ {}
var props = properties || {}
var result = {}
var allowed
var required
var definition
var prop
var value

allowed = own.call(attrs, name) ? attrs[name] : []
allowed = [].concat(allowed, attrs['*'])
allowed = xtend(
toPropertyValueMap(attrs['*']),
toPropertyValueMap(own.call(attrs, name) ? attrs[name] : [])
)

for (prop in props) {
value = props[prop]

if (
allowed.indexOf(prop) === -1 &&
!(data(prop) && allowed.indexOf('data*') !== -1)
) {
if (own.call(allowed, prop)) {
definition = allowed[prop]
} else if (data(prop) && own.call(allowed, allData)) {
definition = allowed[allData]
} else {
continue
}

if (value && typeof value === 'object' && 'length' in value) {
value = handlePropertyValues(schema, value, prop)
value = handlePropertyValues(schema, value, prop, definition)
} else {
value = handlePropertyValue(schema, value, prop)
value = handlePropertyValue(schema, value, prop, definition)
}

if (value !== null && value !== undefined) {
result[prop] = value
}
}

required = own.call(reqs, name) ? reqs[name] : {}

for (prop in required) {
if (!own.call(result, prop)) {
result[prop] = required[prop]
}
}

return result
}

// Sanitize a property value which is a list.
function handlePropertyValues(schema, values, prop) {
function handlePropertyValues(schema, values, prop, definition) {
var length = values.length
var result = []
var index = -1
var value

while (++index < length) {
value = handlePropertyValue(schema, values[index], prop)
value = handlePropertyValue(schema, values[index], prop, definition)

if (value !== null && value !== undefined) {
result.push(value)
Expand All @@ -186,7 +202,7 @@ function handlePropertyValues(schema, values, prop) {
}

// Sanitize a property value.
function handlePropertyValue(schema, value, prop) {
function handlePropertyValue(schema, value, prop, definition) {
if (
typeof value !== 'boolean' &&
typeof value !== 'number' &&
Expand All @@ -199,6 +215,10 @@ function handlePropertyValue(schema, value, prop) {
return null
}

if (definition.length !== 0 && definition.indexOf(value) === -1) {
return null
}

if (schema.clobber.indexOf(prop) !== -1) {
value = schema.clobberPrefix + value
}
Expand Down Expand Up @@ -314,6 +334,26 @@ function handleValue(schema, value) {
return typeof value === 'string' ? value : ''
}

// Create a map from a list of props or a list of properties and values.
function toPropertyValueMap(values) {
var result = {}
var length = values.length

This comment has been minimized.

Copy link
@sebglow

sebglow Feb 20, 2019

If the value is undefined, the value.length causes error (cannot read property 'length' of undefined)

var index = -1
var value

while (++index < length) {
value = values[index]

if (value && typeof value === 'object' && 'length' in value) {
result[value[0]] = value.slice(1)
} else {
result[value] = []
}
}

return result
}

// Allow `value`.
function allow(schema, value) {
return value
Expand Down
38 changes: 38 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,44 @@ properties.
}
```

Instead of a single string (such as `type`), which allows any value of that
attribute, it’s also possible to provide an array (such as `['type',
'checkbox']`), where the first entry is the key, and the other entries are
allowed values of that property.

This is how the default GitHub schema allows only disabled checkbox inputs:

```js
"attributes": {
// ...
"input": [
["type", "checkbox"],
["disabled", true]
],
// ...
}
```

###### `required`

Map of tag-names to required attributes and their default values
(`Object.<Object.<*>>`).
If the properties in such a required attributes object do not exist on an
element, they are added and set to the specified value.

Note that properties are first checked based on the schema at `attributes`,
so properties could be removed by that step and then added again through
`required`.

```js
"required": {
"input": {
"type": "checkbox",
"disabled": true
}
}
```

###### `tagNames`

List of allowed tag-names (`Array.<string>`).
Expand Down
104 changes: 104 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,110 @@ test('sanitize()', function(t) {
})
})

st.deepEqual(
sanitize(h('input')),
h('input', {type: 'checkbox', disabled: true}),
'should allow only disabled checkbox inputs'
)

st.deepEqual(
sanitize(h('input', {type: 'text'})),
h('input', {type: 'checkbox', disabled: true}),
'should not allow text inputs'
)

st.deepEqual(
sanitize(h('input', {type: 'checkbox', disabled: false})),
h('input', {type: 'checkbox', disabled: true}),
'should not allow enabled inputs'
)

st.deepEqual(
sanitize(h('ol', [h('li')])),
h('ol', [h('li')]),
'should allow list items'
)

st.deepEqual(
sanitize(h('ol', [h('li', {className: ['foo', 'bar']})])),
h('ol', [h('li', {className: []})]),
'should not allow classes on list items'
)

st.deepEqual(
sanitize(h('ol', [h('li', {className: ['foo', 'task-list-item']})])),
h('ol', [h('li', {className: ['task-list-item']})]),
'should only allow `task-list-item` as a class on list items'
)

st.deepEqual(
sanitize(h('select')),
u('root', []),
'should ignore some elements by default'
)

st.deepEqual(
sanitize(h('select'), merge(gh, {tagNames: ['select']})),
h('select'),
'should support allowing elements through the schema'
)

st.deepEqual(
sanitize(
h('select', {autoComplete: true}),
merge(gh, {tagNames: ['select']})
),
h('select'),
'should ignore attributes for new elements'
)

st.deepEqual(
sanitize(
h('select', {autoComplete: true}),
merge(gh, {
tagNames: ['select'],
attributes: {select: ['autoComplete']}
})
),
h('select', {autoComplete: true}),
'should support allowing attributes for new elements through the schema'
)

st.deepEqual(
sanitize(
h('div', [h('select', {form: 'one'}), h('select', {form: 'two'})]),
merge(gh, {
tagNames: ['select'],
attributes: {select: [['form', 'one']]}
})
),
h('div', [h('select', {form: 'one'}), h('select')]),
'should support a list of valid values on new attributes'
)

st.deepEqual(
sanitize(
h('div', [
h('select', {form: 'alpha'}),
h('select', {form: 'bravo'}),
h('select', {}),
h('select', {form: false})
]),
merge(gh, {
tagNames: ['select'],
attributes: {select: [['form', 'alpha']]},
required: {select: {form: 'alpha'}}
})
),
h('div', [
h('select', {form: 'alpha'}),
h('select', {form: 'alpha'}),
h('select', {form: 'alpha'}),
h('select', {form: 'alpha'})
]),
'should support required attributes'
)

st.end()
})

Expand Down

0 comments on commit ba16c15

Please sign in to comment.