Skip to content

Commit ba16c15

Browse files
wooormmetalsm1th
andauthored
Add support for disabled checkboxes, task-list-items
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 <[email protected]> Co-authored-by: Arystan <[email protected]>
1 parent 4d8ffce commit ba16c15

File tree

4 files changed

+208
-12
lines changed

4 files changed

+208
-12
lines changed

lib/github.json

+15-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
"s",
101101
"strike",
102102
"summary",
103-
"details"
103+
"details",
104+
"input"
104105
],
105106
"attributes": {
106107
"a": [
@@ -110,6 +111,13 @@
110111
"src",
111112
"longDesc"
112113
],
114+
"input": [
115+
["type", "checkbox"],
116+
["disabled", true]
117+
],
118+
"li": [
119+
["className", "task-list-item"]
120+
],
113121
"div": [
114122
"itemScope",
115123
"itemType"
@@ -196,5 +204,11 @@
196204
"width",
197205
"itemProp"
198206
]
207+
},
208+
"required": {
209+
"input": {
210+
"type": "checkbox",
211+
"disabled": true
212+
}
199213
}
200214
}

lib/index.js

+51-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module.exports = wrapper
77

88
var own = {}.hasOwnProperty
99

10+
var allData = 'data*'
11+
1012
var NODES = {
1113
root: {children: all},
1214
doctype: handleDoctype,
@@ -134,48 +136,62 @@ function all(schema, children, node, stack) {
134136
function handleProperties(schema, properties, node, stack) {
135137
var name = handleTagName(schema, node.tagName, node, stack)
136138
var attrs = schema.attributes
139+
var reqs = schema.required || /* istanbul ignore next */ {}
137140
var props = properties || {}
138141
var result = {}
139142
var allowed
143+
var required
144+
var definition
140145
var prop
141146
var value
142147

143-
allowed = own.call(attrs, name) ? attrs[name] : []
144-
allowed = [].concat(allowed, attrs['*'])
148+
allowed = xtend(
149+
toPropertyValueMap(attrs['*']),
150+
toPropertyValueMap(own.call(attrs, name) ? attrs[name] : [])
151+
)
145152

146153
for (prop in props) {
147154
value = props[prop]
148155

149-
if (
150-
allowed.indexOf(prop) === -1 &&
151-
!(data(prop) && allowed.indexOf('data*') !== -1)
152-
) {
156+
if (own.call(allowed, prop)) {
157+
definition = allowed[prop]
158+
} else if (data(prop) && own.call(allowed, allData)) {
159+
definition = allowed[allData]
160+
} else {
153161
continue
154162
}
155163

156164
if (value && typeof value === 'object' && 'length' in value) {
157-
value = handlePropertyValues(schema, value, prop)
165+
value = handlePropertyValues(schema, value, prop, definition)
158166
} else {
159-
value = handlePropertyValue(schema, value, prop)
167+
value = handlePropertyValue(schema, value, prop, definition)
160168
}
161169

162170
if (value !== null && value !== undefined) {
163171
result[prop] = value
164172
}
165173
}
166174

175+
required = own.call(reqs, name) ? reqs[name] : {}
176+
177+
for (prop in required) {
178+
if (!own.call(result, prop)) {
179+
result[prop] = required[prop]
180+
}
181+
}
182+
167183
return result
168184
}
169185

170186
// Sanitize a property value which is a list.
171-
function handlePropertyValues(schema, values, prop) {
187+
function handlePropertyValues(schema, values, prop, definition) {
172188
var length = values.length
173189
var result = []
174190
var index = -1
175191
var value
176192

177193
while (++index < length) {
178-
value = handlePropertyValue(schema, values[index], prop)
194+
value = handlePropertyValue(schema, values[index], prop, definition)
179195

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

188204
// Sanitize a property value.
189-
function handlePropertyValue(schema, value, prop) {
205+
function handlePropertyValue(schema, value, prop, definition) {
190206
if (
191207
typeof value !== 'boolean' &&
192208
typeof value !== 'number' &&
@@ -199,6 +215,10 @@ function handlePropertyValue(schema, value, prop) {
199215
return null
200216
}
201217

218+
if (definition.length !== 0 && definition.indexOf(value) === -1) {
219+
return null
220+
}
221+
202222
if (schema.clobber.indexOf(prop) !== -1) {
203223
value = schema.clobberPrefix + value
204224
}
@@ -314,6 +334,26 @@ function handleValue(schema, value) {
314334
return typeof value === 'string' ? value : ''
315335
}
316336

337+
// Create a map from a list of props or a list of properties and values.
338+
function toPropertyValueMap(values) {
339+
var result = {}
340+
var length = values.length
341+
var index = -1
342+
var value
343+
344+
while (++index < length) {
345+
value = values[index]
346+
347+
if (value && typeof value === 'object' && 'length' in value) {
348+
result[value[0]] = value.slice(1)
349+
} else {
350+
result[value] = []
351+
}
352+
}
353+
354+
return result
355+
}
356+
317357
// Allow `value`.
318358
function allow(schema, value) {
319359
return value

readme.md

+38
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,44 @@ properties.
136136
}
137137
```
138138

139+
Instead of a single string (such as `type`), which allows any value of that
140+
attribute, it’s also possible to provide an array (such as `['type',
141+
'checkbox']`), where the first entry is the key, and the other entries are
142+
allowed values of that property.
143+
144+
This is how the default GitHub schema allows only disabled checkbox inputs:
145+
146+
```js
147+
"attributes": {
148+
// ...
149+
"input": [
150+
["type", "checkbox"],
151+
["disabled", true]
152+
],
153+
// ...
154+
}
155+
```
156+
157+
###### `required`
158+
159+
Map of tag-names to required attributes and their default values
160+
(`Object.<Object.<*>>`).
161+
If the properties in such a required attributes object do not exist on an
162+
element, they are added and set to the specified value.
163+
164+
Note that properties are first checked based on the schema at `attributes`,
165+
so properties could be removed by that step and then added again through
166+
`required`.
167+
168+
```js
169+
"required": {
170+
"input": {
171+
"type": "checkbox",
172+
"disabled": true
173+
}
174+
}
175+
```
176+
139177
###### `tagNames`
140178

141179
List of allowed tag-names (`Array.<string>`).

test.js

+104
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,110 @@ test('sanitize()', function(t) {
487487
})
488488
})
489489

490+
st.deepEqual(
491+
sanitize(h('input')),
492+
h('input', {type: 'checkbox', disabled: true}),
493+
'should allow only disabled checkbox inputs'
494+
)
495+
496+
st.deepEqual(
497+
sanitize(h('input', {type: 'text'})),
498+
h('input', {type: 'checkbox', disabled: true}),
499+
'should not allow text inputs'
500+
)
501+
502+
st.deepEqual(
503+
sanitize(h('input', {type: 'checkbox', disabled: false})),
504+
h('input', {type: 'checkbox', disabled: true}),
505+
'should not allow enabled inputs'
506+
)
507+
508+
st.deepEqual(
509+
sanitize(h('ol', [h('li')])),
510+
h('ol', [h('li')]),
511+
'should allow list items'
512+
)
513+
514+
st.deepEqual(
515+
sanitize(h('ol', [h('li', {className: ['foo', 'bar']})])),
516+
h('ol', [h('li', {className: []})]),
517+
'should not allow classes on list items'
518+
)
519+
520+
st.deepEqual(
521+
sanitize(h('ol', [h('li', {className: ['foo', 'task-list-item']})])),
522+
h('ol', [h('li', {className: ['task-list-item']})]),
523+
'should only allow `task-list-item` as a class on list items'
524+
)
525+
526+
st.deepEqual(
527+
sanitize(h('select')),
528+
u('root', []),
529+
'should ignore some elements by default'
530+
)
531+
532+
st.deepEqual(
533+
sanitize(h('select'), merge(gh, {tagNames: ['select']})),
534+
h('select'),
535+
'should support allowing elements through the schema'
536+
)
537+
538+
st.deepEqual(
539+
sanitize(
540+
h('select', {autoComplete: true}),
541+
merge(gh, {tagNames: ['select']})
542+
),
543+
h('select'),
544+
'should ignore attributes for new elements'
545+
)
546+
547+
st.deepEqual(
548+
sanitize(
549+
h('select', {autoComplete: true}),
550+
merge(gh, {
551+
tagNames: ['select'],
552+
attributes: {select: ['autoComplete']}
553+
})
554+
),
555+
h('select', {autoComplete: true}),
556+
'should support allowing attributes for new elements through the schema'
557+
)
558+
559+
st.deepEqual(
560+
sanitize(
561+
h('div', [h('select', {form: 'one'}), h('select', {form: 'two'})]),
562+
merge(gh, {
563+
tagNames: ['select'],
564+
attributes: {select: [['form', 'one']]}
565+
})
566+
),
567+
h('div', [h('select', {form: 'one'}), h('select')]),
568+
'should support a list of valid values on new attributes'
569+
)
570+
571+
st.deepEqual(
572+
sanitize(
573+
h('div', [
574+
h('select', {form: 'alpha'}),
575+
h('select', {form: 'bravo'}),
576+
h('select', {}),
577+
h('select', {form: false})
578+
]),
579+
merge(gh, {
580+
tagNames: ['select'],
581+
attributes: {select: [['form', 'alpha']]},
582+
required: {select: {form: 'alpha'}}
583+
})
584+
),
585+
h('div', [
586+
h('select', {form: 'alpha'}),
587+
h('select', {form: 'alpha'}),
588+
h('select', {form: 'alpha'}),
589+
h('select', {form: 'alpha'})
590+
]),
591+
'should support required attributes'
592+
)
593+
490594
st.end()
491595
})
492596

0 commit comments

Comments
 (0)