From 7ccd453dd004076cad49ec9f56cd5fe97b7b6ed8 Mon Sep 17 00:00:00 2001 From: Adrien Foulon <6115458+Tofandel@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:28:34 +0200 Subject: [PATCH] fix(runtime-dom): sanitize wrongly passed string value as event handler (#8953) close #8818 --- .../runtime-dom/__tests__/patchEvents.spec.ts | 11 +++++++ packages/runtime-dom/src/modules/events.ts | 32 ++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/runtime-dom/__tests__/patchEvents.spec.ts b/packages/runtime-dom/__tests__/patchEvents.spec.ts index 1b08f85caa4..b7a5af0ed0e 100644 --- a/packages/runtime-dom/__tests__/patchEvents.spec.ts +++ b/packages/runtime-dom/__tests__/patchEvents.spec.ts @@ -192,4 +192,15 @@ describe(`runtime-dom: events patching`, () => { testElement.dispatchEvent(new CustomEvent('foobar')) expect(fn2).toHaveBeenCalledTimes(1) }) + + it('handles an unknown type', () => { + const el = document.createElement('div') + patchProp(el, 'onClick', null, 'test') + el.dispatchEvent(new Event('click')) + expect( + '[Vue warn]: Wrong type passed to the event invoker, ' + + 'did you maybe forget @ or : in front of your prop?' + + '\nReceived onClick="test"', + ).toHaveBeenWarned() + }) }) diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index 0335b6be0db..09e4a22a84c 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -1,8 +1,9 @@ -import { hyphenate, isArray } from '@vue/shared' +import { NOOP, hyphenate, isArray, isFunction, isString } from '@vue/shared' import { type ComponentInternalInstance, ErrorCodes, callWithAsyncErrorHandling, + warn, } from '@vue/runtime-core' interface Invoker extends EventListener { @@ -36,7 +37,7 @@ export function patchEvent( el: Element & { [veiKey]?: Record }, rawName: string, prevValue: EventValue | null, - nextValue: EventValue | null, + nextValue: EventValue | unknown, instance: ComponentInternalInstance | null = null, ) { // vei = vue event invokers @@ -44,12 +45,19 @@ export function patchEvent( const existingInvoker = invokers[rawName] if (nextValue && existingInvoker) { // patch - existingInvoker.value = nextValue + existingInvoker.value = __DEV__ + ? sanitizeEventValue(nextValue, rawName) + : (nextValue as EventValue) } else { const [name, options] = parseName(rawName) if (nextValue) { // add - const invoker = (invokers[rawName] = createInvoker(nextValue, instance)) + const invoker = (invokers[rawName] = createInvoker( + __DEV__ + ? sanitizeEventValue(nextValue, rawName) + : (nextValue as EventValue), + instance, + )) addEventListener(el, name, invoker, options) } else if (existingInvoker) { // remove @@ -116,6 +124,18 @@ function createInvoker( return invoker } +function sanitizeEventValue(value: unknown, propName: string): EventValue { + if (isFunction(value) || isArray(value)) { + return value as EventValue + } + warn( + `Wrong type passed to the event invoker, did you maybe forget @ or : ` + + `in front of your prop?\nReceived ` + + `${propName}=${isString(value) ? JSON.stringify(value) : `[${typeof value}]`}`, + ) + return NOOP +} + function patchStopImmediatePropagation( e: Event, value: EventValue, @@ -126,7 +146,9 @@ function patchStopImmediatePropagation( originalStop.call(e) ;(e as any)._stopped = true } - return value.map(fn => (e: Event) => !(e as any)._stopped && fn && fn(e)) + return (value as Function[]).map( + fn => (e: Event) => !(e as any)._stopped && fn && fn(e), + ) } else { return value }