]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-dom): sanitize wrongly passed string value as event handler (#8953)
authorAdrien Foulon <6115458+Tofandel@users.noreply.github.com>
Mon, 15 Apr 2024 14:28:34 +0000 (16:28 +0200)
committerGitHub <noreply@github.com>
Mon, 15 Apr 2024 14:28:34 +0000 (22:28 +0800)
close #8818

packages/runtime-dom/__tests__/patchEvents.spec.ts
packages/runtime-dom/src/modules/events.ts

index 1b08f85caa47d5b14266647d5280e0474499e79f..b7a5af0ed0ed8ef2a95674dc6d6c5bfad853f9be 100644 (file)
@@ -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()
+  })
 })
index 0335b6be0dbc8b9770be43142cc2f2bc62e51d48..09e4a22a84cd2a3d982047350936396d30d504d1 100644 (file)
@@ -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<string, Invoker | undefined> },
   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
   }