Skip to content

Commit

Permalink
feat(security): strip html tags (#2479)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Feb 5, 2019
1 parent f9bab3a commit 3c6ba3e
Show file tree
Hide file tree
Showing 18 changed files with 137 additions and 66 deletions.
7 changes: 6 additions & 1 deletion src/components/card/card-footer.js
Expand Up @@ -3,13 +3,18 @@ import { mergeData } from 'vue-functional-data-merge'
import prefixPropName from '../../utils/prefix-prop-name'
import copyProps from '../../utils/copyProps'
import { assign } from '../../utils/object'
import { htmlOrText } from '../../utils/html'
import cardMixin from '../../mixins/card-mixin'

export const props = assign({}, copyProps(cardMixin.props, prefixPropName.bind(null, 'footer')), {
footer: {
type: String,
default: null
},
footerHTML: {

This comment has been minimized.

Copy link
@sirlancelot

sirlancelot Feb 15, 2019

Props in Vue should not be in this format. Please change to footerHtml. Multiple props exist in this change that suffer from this issue. vuejs/vue#9505

This comment has been minimized.

Copy link
@sirlancelot

sirlancelot Feb 15, 2019

@pi0 Others I could find at a glance: innerHTML, headerHTML, captionHTML, textHTML, appendHTML, prependHTML. A bunch matching HTML basically. The problem is that the hyphenated representations of the props becomes inner-h-t-m-l.

This comment has been minimized.

Copy link
@pi0

pi0 Feb 15, 2019

Author Member

Thanks @silentred for mentioning issue. Will change them

This comment has been minimized.

Copy link
@tmorehouse

tmorehouse Feb 15, 2019

Member

Could you create a new issue for this?

This comment has been minimized.

Copy link
@sirlancelot

sirlancelot Feb 16, 2019

You all are great! I'm just the messenger, but I love the work you're doing here!

type: String,
default: null
},
footerClass: {
type: [String, Object, Array],
default: null
Expand All @@ -35,7 +40,7 @@ export default {
}
]
}),
children || [h('div', { domProps: { innerHTML: props.footer } })]
children || [h('div', { domProps: htmlOrText(props.footerHTML, props.footer) })]
)
}
}
7 changes: 6 additions & 1 deletion src/components/card/card-header.js
Expand Up @@ -3,13 +3,18 @@ import { mergeData } from 'vue-functional-data-merge'
import prefixPropName from '../../utils/prefix-prop-name'
import copyProps from '../../utils/copyProps'
import { assign } from '../../utils/object'
import { htmlOrText } from '../../utils/html'
import cardMixin from '../../mixins/card-mixin'

export const props = assign({}, copyProps(cardMixin.props, prefixPropName.bind(null, 'header')), {
header: {
type: String,
default: null
},
headerHTML: {
type: String,
default: null
},
headerClass: {
type: [String, Object, Array],
default: null
Expand All @@ -35,7 +40,7 @@ export default {
}
]
}),
children || [h('div', { domProps: { innerHTML: props.header } })]
children || [h('div', { domProps: htmlOrText(props.headerHTML, props.header) })]
)
}
}
21 changes: 15 additions & 6 deletions src/components/carousel/carousel-slide.js
@@ -1,6 +1,7 @@
import BImg from '../image/img'
import idMixin from '../../mixins/id'
import { hasTouchSupport } from '../../utils/env'
import { htmlOrText } from '../../utils/html'

// @vue/component
export default {
Expand Down Expand Up @@ -45,31 +46,33 @@ export default {
},
contentVisibleUp: {
type: String
// default: undefined
},
contentTag: {
type: String,
default: 'div'
},
caption: {
type: String
// default: undefined
},
captionHTML: {
type: String
},
captionTag: {
type: String,
default: 'h3'
},
text: {
type: String
// default: undefined
},
textHTML: {
type: String
},
textTag: {
type: String,
default: 'p'
},
background: {
type: String
// default: undefined
}
},
data() {
Expand Down Expand Up @@ -126,8 +129,14 @@ export default {
this.contentTag,
{ staticClass: 'carousel-caption', class: this.contentClasses },
[
this.caption ? h(this.captionTag, { domProps: { innerHTML: this.caption } }) : h(false),
this.text ? h(this.textTag, { domProps: { innerHTML: this.text } }) : h(false),
this.caption || this.captionHTML
? h(this.captionTag, {
domProps: htmlOrText(this.captionHTML, this.caption)
})
: h(false),
this.text || this.textHTML
? h(this.textTag, { domProps: htmlOrText(this.textHTML, this.text) })
: h(false),
$slots.default
]
)
Expand Down
5 changes: 3 additions & 2 deletions src/components/dropdown/dropdown.js
@@ -1,3 +1,4 @@
import { stripTags } from '../../utils/html'
import idMixin from '../../mixins/id'
import dropdownMixin from '../../mixins/dropdown'
import BButton from '../button/button'
Expand Down Expand Up @@ -138,7 +139,7 @@ export default {
click: this.click
}
},
[this.$slots['button-content'] || this.$slots.text || this.text]
[this.$slots['button-content'] || this.$slots.text || this.html || stripTags(this.text)]
)
}
const toggle = h(
Expand All @@ -165,7 +166,7 @@ export default {
[
this.split
? h('span', { class: ['sr-only'] }, [this.toggleText])
: this.$slots['button-content'] || this.$slots.text || this.text
: this.$slots['button-content'] || this.$slots.text || this.html || stripTags(this.text)
]
)
const menu = h(
Expand Down
3 changes: 2 additions & 1 deletion src/components/form-select/form-select.js
Expand Up @@ -5,6 +5,7 @@ import formSizeMixin from '../../mixins/form-size'
import formStateMixin from '../../mixins/form-state'
import formCustomMixin from '../../mixins/form-custom'
import { from as arrayFrom } from '../../utils/array'
import { htmlOrText } from '../../utils/html'

// @vue/component
export default {
Expand Down Expand Up @@ -78,7 +79,7 @@ export default {
return h('option', {
key: `option_${index}_opt`,
attrs: { disabled: Boolean(option.disabled) },
domProps: { innerHTML: option.text, value: option.value }
domProps: { ...htmlOrText(option.html, option.text), value: option.value }
})
})
return h(
Expand Down
28 changes: 17 additions & 11 deletions src/components/input-group/input-group.js
@@ -1,25 +1,27 @@
import { mergeData } from 'vue-functional-data-merge'
import stripScripts from '../../utils/strip-scripts'
import InputGroupPrepend from './input-group-prepend'
import InputGroupAppend from './input-group-append'
import InputGroupText from './input-group-text'
import { htmlOrText } from '../../utils/html'

export const props = {
id: {
type: String,
default: null
type: String
},
size: {
type: String,
default: null
type: String
},
prepend: {
type: String,
default: null
type: String
},
prependHTML: {
type: String
},
append: {
type: String,
default: null
type: String
},
appendHTML: {
type: String
},
tag: {
type: String,
Expand All @@ -41,7 +43,9 @@ export default {
if (props.prepend) {
childNodes.push(
h(InputGroupPrepend, [
h(InputGroupText, { domProps: { innerHTML: stripScripts(props.prepend) } })
h(InputGroupText, {
domProps: htmlOrText(props.prependHTML, props.prepend)
})
])
)
} else {
Expand All @@ -66,7 +70,9 @@ export default {
if (props.append) {
childNodes.push(
h(InputGroupAppend, [
h(InputGroupText, { domProps: { innerHTML: stripScripts(props.append) } })
h(InputGroupText, {
domProps: htmlOrText(props.appendHTML, props.append)
})
])
)
} else {
Expand Down
22 changes: 17 additions & 5 deletions src/components/jumbotron/jumbotron.js
@@ -1,5 +1,5 @@
import { mergeData } from 'vue-functional-data-merge'
import stripScripts from '../../utils/strip-scripts'
import { stripTags } from '../../utils/html'
import Container from '../layout/container'

export const props = {
Expand All @@ -15,6 +15,10 @@ export const props = {
type: String,
default: null
},
headerHTML: {
type: String,
default: null
},
headerTag: {
type: String,
default: 'h1'
Expand All @@ -27,6 +31,10 @@ export const props = {
type: String,
default: null
},
leadHTML: {
type: String,
default: null
},
leadTag: {
type: String,
default: 'p'
Expand Down Expand Up @@ -61,7 +69,7 @@ export default {
const $slots = slots()

// Header
if (props.header || $slots.header) {
if (props.header || $slots.header || props.headerHTML) {
childNodes.push(
h(
props.headerTag,
Expand All @@ -70,15 +78,19 @@ export default {
[`display-${props.headerLevel}`]: Boolean(props.headerLevel)
}
},
$slots.header || stripScripts(props.header)
$slots.header || props.headerHTML || stripTags(props.header)
)
)
}

// Lead
if (props.lead || $slots.lead) {
if (props.lead || $slots.lead || props.leadHTML) {
childNodes.push(
h(props.leadTag, { staticClass: 'lead' }, $slots.lead || stripScripts(props.lead))
h(
props.leadTag,
{ staticClass: 'lead' },
$slots.lead || props.leadHTML || stripTags(props.lead)
)
)
}

Expand Down
17 changes: 13 additions & 4 deletions src/components/modal/modal.js
Expand Up @@ -6,7 +6,7 @@ import observeDom from '../../utils/observe-dom'
import warn from '../../utils/warn'
import KeyCodes from '../../utils/key-codes'
import BvEvent from '../../utils/bv-event.class'
import stripScripts from '../../utils/strip-scripts'
import { stripTags } from '../../utils/html'

import {
addClass,
Expand Down Expand Up @@ -94,6 +94,9 @@ export default {
type: String,
default: ''
},
titleHTML: {
type: String
},
titleTag: {
type: String,
default: 'h5'
Expand Down Expand Up @@ -230,10 +233,16 @@ export default {
type: String,
default: 'Cancel'
},
cancelTitleHTML: {
type: String
},
okTitle: {
type: String,
default: 'OK'
},
okTitleHTML: {
type: String
},
cancelVariant: {
type: String,
default: 'secondary'
Expand Down Expand Up @@ -802,7 +811,7 @@ export default {
}
modalHeader = [
h(this.titleTag, { class: ['modal-title'] }, [
$slots['modal-title'] || stripScripts(this.title)
$slots['modal-title'] || this.titleHTML || stripTags(this.title)
]),
closeButton
]
Expand Down Expand Up @@ -850,7 +859,7 @@ export default {
}
}
},
[$slots['modal-cancel'] || stripScripts(this.cancelTitle)]
[$slots['modal-cancel'] || this.cancelTitleHTML || stripTags(this.cancelTitle)]
)
}
const okButton = h(
Expand All @@ -867,7 +876,7 @@ export default {
}
}
},
[$slots['modal-ok'] || stripScripts(this.okTitle)]
[$slots['modal-ok'] || this.okTitleHTML || stripTags(this.okTitle)]
)
modalFooter = [cancelButton, okButton]
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/nav/nav-item-dropdown.js
@@ -1,6 +1,6 @@
import idMixin from '../../mixins/id'
import dropdownMixin from '../../mixins/dropdown'
import stripScripts from '../../utils/strip-scripts'
import { htmlOrText } from '../../utils/html'

// @vue/component
export default {
Expand Down Expand Up @@ -78,7 +78,7 @@ export default {
[
this.$slots['button-content'] ||
this.$slots.text ||
h('span', { domProps: { innerHTML: stripScripts(this.text) } })
h('span', { domProps: htmlOrText(this.html, this.text) })
]
)
const menu = h(
Expand Down
9 changes: 6 additions & 3 deletions src/components/progress/progress-bar.js
@@ -1,4 +1,4 @@
import stripScripts from '../../utils/strip-scripts'
import { htmlOrText } from '../../utils/html'

// @vue/component
export default {
Expand All @@ -12,6 +12,9 @@ export default {
type: String,
default: null
},
labelHTML: {
type: String
},
// $parent prop values take precedence over the following props
// Which is why they are defaulted to null
max: {
Expand Down Expand Up @@ -96,8 +99,8 @@ export default {
let childNodes = h(false)
if (this.$slots.default) {
childNodes = this.$slots.default
} else if (this.label) {
childNodes = h('span', { domProps: { innerHTML: stripScripts(this.label) } })
} else if (this.label || this.labelHTML) {
childNodes = h('span', { domProps: htmlOrText(this.labelHTML, this.label) })
} else if (this.computedShowProgress) {
childNodes = this.progress.toFixed(this.computedPrecision)
} else if (this.computedShowValue) {
Expand Down

0 comments on commit 3c6ba3e

Please sign in to comment.