Skip to content

Commit 7ccd453

Browse files
authoredApr 15, 2024··
fix(runtime-dom): sanitize wrongly passed string value as event handler (#8953)
close #8818
1 parent 15ffe8f commit 7ccd453

File tree

2 files changed

+38
-5
lines changed

2 files changed

+38
-5
lines changed
 

‎packages/runtime-dom/__tests__/patchEvents.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,15 @@ describe(`runtime-dom: events patching`, () => {
192192
testElement.dispatchEvent(new CustomEvent('foobar'))
193193
expect(fn2).toHaveBeenCalledTimes(1)
194194
})
195+
196+
it('handles an unknown type', () => {
197+
const el = document.createElement('div')
198+
patchProp(el, 'onClick', null, 'test')
199+
el.dispatchEvent(new Event('click'))
200+
expect(
201+
'[Vue warn]: Wrong type passed to the event invoker, ' +
202+
'did you maybe forget @ or : in front of your prop?' +
203+
'\nReceived onClick="test"',
204+
).toHaveBeenWarned()
205+
})
195206
})

‎packages/runtime-dom/src/modules/events.ts

+27-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { hyphenate, isArray } from '@vue/shared'
1+
import { NOOP, hyphenate, isArray, isFunction, isString } from '@vue/shared'
22
import {
33
type ComponentInternalInstance,
44
ErrorCodes,
55
callWithAsyncErrorHandling,
6+
warn,
67
} from '@vue/runtime-core'
78

89
interface Invoker extends EventListener {
@@ -36,20 +37,27 @@ export function patchEvent(
3637
el: Element & { [veiKey]?: Record<string, Invoker | undefined> },
3738
rawName: string,
3839
prevValue: EventValue | null,
39-
nextValue: EventValue | null,
40+
nextValue: EventValue | unknown,
4041
instance: ComponentInternalInstance | null = null,
4142
) {
4243
// vei = vue event invokers
4344
const invokers = el[veiKey] || (el[veiKey] = {})
4445
const existingInvoker = invokers[rawName]
4546
if (nextValue && existingInvoker) {
4647
// patch
47-
existingInvoker.value = nextValue
48+
existingInvoker.value = __DEV__
49+
? sanitizeEventValue(nextValue, rawName)
50+
: (nextValue as EventValue)
4851
} else {
4952
const [name, options] = parseName(rawName)
5053
if (nextValue) {
5154
// add
52-
const invoker = (invokers[rawName] = createInvoker(nextValue, instance))
55+
const invoker = (invokers[rawName] = createInvoker(
56+
__DEV__
57+
? sanitizeEventValue(nextValue, rawName)
58+
: (nextValue as EventValue),
59+
instance,
60+
))
5361
addEventListener(el, name, invoker, options)
5462
} else if (existingInvoker) {
5563
// remove
@@ -116,6 +124,18 @@ function createInvoker(
116124
return invoker
117125
}
118126

127+
function sanitizeEventValue(value: unknown, propName: string): EventValue {
128+
if (isFunction(value) || isArray(value)) {
129+
return value as EventValue
130+
}
131+
warn(
132+
`Wrong type passed to the event invoker, did you maybe forget @ or : ` +
133+
`in front of your prop?\nReceived ` +
134+
`${propName}=${isString(value) ? JSON.stringify(value) : `[${typeof value}]`}`,
135+
)
136+
return NOOP
137+
}
138+
119139
function patchStopImmediatePropagation(
120140
e: Event,
121141
value: EventValue,
@@ -126,7 +146,9 @@ function patchStopImmediatePropagation(
126146
originalStop.call(e)
127147
;(e as any)._stopped = true
128148
}
129-
return value.map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e))
149+
return (value as Function[]).map(
150+
fn => (e: Event) => !(e as any)._stopped && fn && fn(e),
151+
)
130152
} else {
131153
return value
132154
}

0 commit comments

Comments
 (0)
Please sign in to comment.