Skip to content

Commit ade1434

Browse files
committed
fix(VCalendar): prevent XSS from eventName function
fixes #15757 BREAKING CHANGE: eventName can no longer render arbitrary HTML, convert to VNodes instead
1 parent 1be5260 commit ade1434

File tree

2 files changed

+16
-15
lines changed

2 files changed

+16
-15
lines changed

packages/vuetify/src/components/VCalendar/mixins/calendar-with-events.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ import ripple from '../../../directives/ripple'
1010
// Mixins
1111
import CalendarBase from './calendar-base'
1212

13-
// Helpers
14-
import { escapeHTML } from '../../../util/helpers'
15-
1613
// Util
1714
import props from '../util/props'
1815
import {
@@ -116,7 +113,7 @@ export default CalendarBase.extend({
116113
eventNameFunction (): CalendarEventNameFunction {
117114
return typeof this.eventName === 'function'
118115
? this.eventName
119-
: (event, timedEvent) => escapeHTML(event.input[this.eventName as string] as string || '')
116+
: (event, timedEvent) => event.input[this.eventName as string] as string || ''
120117
},
121118
eventModeFunction (): CalendarEventOverlapMode {
122119
return typeof this.eventOverlapMode === 'function'
@@ -303,16 +300,23 @@ export default CalendarBase.extend({
303300
const eventSummary = () => {
304301
const name = this.eventNameFunction(event, timedEvent)
305302
if (event.start.hasTime) {
306-
const eventSummaryClass = 'v-event-summary'
307303
if (timedEvent) {
308304
const time = timeSummary()
309-
const delimiter = singline ? ', ' : '<br>'
305+
const delimiter = singline ? ', ' : this.$createElement('br')
310306

311-
return `<span class="${eventSummaryClass}"><strong>${name}</strong>${delimiter}${time}</span>`
307+
return this.$createElement('span', { staticClass: 'v-event-summary' }, [
308+
this.$createElement('strong', [name]),
309+
delimiter,
310+
time,
311+
])
312312
} else {
313313
const time = formatTime(event.start, true)
314314

315-
return `<span class="${eventSummaryClass}"><strong>${time}</strong> ${name}</span>`
315+
return this.$createElement('span', { staticClass: 'v-event-summary' }, [
316+
this.$createElement('strong', [time]),
317+
' ',
318+
name,
319+
])
316320
}
317321
}
318322

@@ -345,13 +349,10 @@ export default CalendarBase.extend({
345349
: [this.genName(eventSummary)]
346350
)
347351
},
348-
genName (eventSummary: () => string): VNode {
352+
genName (eventSummary: () => string | VNode): VNode {
349353
return this.$createElement('div', {
350354
staticClass: 'pl-1',
351-
domProps: {
352-
innerHTML: eventSummary(),
353-
},
354-
})
355+
}, [eventSummary()])
355356
},
356357
genPlaceholder (day: CalendarTimestamp): VNode {
357358
const height = this.eventHeight + this.eventMarginBottom

packages/vuetify/types/index.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Vue, { Component, PluginFunction, VueConstructor, DirectiveOptions } from 'vue'
1+
import Vue, { Component, PluginFunction, VueConstructor, DirectiveOptions, VNode } from 'vue'
22
import './lib'
33
import './alacarte'
44
import './colors'
@@ -275,7 +275,7 @@ export type CalendarEventTimedFunction = (event: CalendarEvent) => boolean
275275

276276
export type CalendarEventCategoryFunction = (event: CalendarEvent) => string
277277

278-
export type CalendarEventNameFunction = (event: CalendarEventParsed, timedEvent: boolean) => string
278+
export type CalendarEventNameFunction = (event: CalendarEventParsed, timedEvent: boolean) => string | VNode
279279

280280
export type DataTableFilterFunction = (value: any, search: string | null, item: any) => boolean
281281

0 commit comments

Comments
 (0)