From 066ba82c7f245c507cdf2f5085daf8f2164fa575 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 26 Jan 2020 17:35:21 -0500 Subject: [PATCH] wip(ssr): escape helpers --- .../compiler-core/__tests__/codegen.spec.ts | 8 +-- .../compiler-core/__tests__/transform.spec.ts | 4 +- packages/compiler-core/src/codegen.ts | 4 +- packages/compiler-core/src/runtimeHelpers.ts | 4 +- packages/compiler-core/src/transform.ts | 4 +- packages/runtime-core/src/helpers/toString.ts | 10 ---- packages/runtime-core/src/index.ts | 8 ++- packages/server-renderer/src/helpers.ts | 51 +++++++++++++++++++ packages/server-renderer/src/index.ts | 2 + packages/shared/src/index.ts | 9 ++++ 10 files changed, 81 insertions(+), 23 deletions(-) delete mode 100644 packages/runtime-core/src/helpers/toString.ts create mode 100644 packages/server-renderer/src/helpers.ts diff --git a/packages/compiler-core/__tests__/codegen.spec.ts b/packages/compiler-core/__tests__/codegen.spec.ts index 2a116bb771..ef202ef299 100644 --- a/packages/compiler-core/__tests__/codegen.spec.ts +++ b/packages/compiler-core/__tests__/codegen.spec.ts @@ -18,7 +18,7 @@ import { } from '../src' import { CREATE_VNODE, - TO_STRING, + TO_DISPLAY_STRING, RESOLVE_DIRECTIVE, helperNameMap, RESOLVE_COMPONENT, @@ -164,7 +164,7 @@ describe('compiler: codegen', () => { codegenNode: createInterpolation(`hello`, locStub) }) ) - expect(code).toMatch(`return _${helperNameMap[TO_STRING]}(hello)`) + expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`) expect(code).toMatchSnapshot() }) @@ -197,7 +197,9 @@ describe('compiler: codegen', () => { ]) }) ) - expect(code).toMatch(`return _ctx.foo + _${helperNameMap[TO_STRING]}(bar)`) + expect(code).toMatch( + `return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar)` + ) expect(code).toMatchSnapshot() }) diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts index 2e1c1e803c..d1b2bbd038 100644 --- a/packages/compiler-core/__tests__/transform.spec.ts +++ b/packages/compiler-core/__tests__/transform.spec.ts @@ -8,7 +8,7 @@ import { } from '../src/ast' import { ErrorCodes, createCompilerError } from '../src/errors' import { - TO_STRING, + TO_DISPLAY_STRING, OPEN_BLOCK, CREATE_BLOCK, FRAGMENT, @@ -227,7 +227,7 @@ describe('compiler: transform', () => { test('should inject toString helper for interpolations', () => { const ast = baseParse(`{{ foo }}`) transform(ast, {}) - expect(ast.helpers).toContain(TO_STRING) + expect(ast.helpers).toContain(TO_DISPLAY_STRING) }) test('should inject createVNode and Comment for comments', () => { diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index d235263640..a8dcb558bf 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -31,7 +31,7 @@ import { import { isString, isArray, isSymbol } from '@vue/shared' import { helperNameMap, - TO_STRING, + TO_DISPLAY_STRING, CREATE_VNODE, RESOLVE_COMPONENT, RESOLVE_DIRECTIVE, @@ -491,7 +491,7 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) { function genInterpolation(node: InterpolationNode, context: CodegenContext) { const { push, helper } = context - push(`${helper(TO_STRING)}(`) + push(`${helper(TO_DISPLAY_STRING)}(`) genNode(node.content, context) push(`)`) } diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts index 00b84f2f0a..b684780214 100644 --- a/packages/compiler-core/src/runtimeHelpers.ts +++ b/packages/compiler-core/src/runtimeHelpers.ts @@ -17,7 +17,7 @@ export const WITH_DIRECTIVES = Symbol(__DEV__ ? `withDirectives` : ``) export const RENDER_LIST = Symbol(__DEV__ ? `renderList` : ``) export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``) export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``) -export const TO_STRING = Symbol(__DEV__ ? `toString` : ``) +export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``) export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``) export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``) export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``) @@ -47,7 +47,7 @@ export const helperNameMap: any = { [RENDER_LIST]: `renderList`, [RENDER_SLOT]: `renderSlot`, [CREATE_SLOTS]: `createSlots`, - [TO_STRING]: `toString`, + [TO_DISPLAY_STRING]: `toDisplayString`, [MERGE_PROPS]: `mergeProps`, [TO_HANDLERS]: `toHandlers`, [CAMELIZE]: `camelize`, diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 4bfd749fc6..7029c0851a 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -27,7 +27,7 @@ import { } from '@vue/shared' import { defaultOnError } from './errors' import { - TO_STRING, + TO_DISPLAY_STRING, FRAGMENT, helperNameMap, WITH_DIRECTIVES, @@ -365,7 +365,7 @@ export function traverseNode( break case NodeTypes.INTERPOLATION: // no need to traverse, but we need to inject toString helper - context.helper(TO_STRING) + context.helper(TO_DISPLAY_STRING) break // for container types, further traverse downwards diff --git a/packages/runtime-core/src/helpers/toString.ts b/packages/runtime-core/src/helpers/toString.ts deleted file mode 100644 index 968f51d6a7..0000000000 --- a/packages/runtime-core/src/helpers/toString.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { isArray, isPlainObject, objectToString } from '@vue/shared' - -// for converting {{ interpolation }} values to displayed strings. -export function toString(val: unknown): string { - return val == null - ? '' - : isArray(val) || (isPlainObject(val) && val.toString === objectToString) - ? JSON.stringify(val, null, 2) - : String(val) -} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 2976c53a1e..d3b24f2bdd 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -81,7 +81,6 @@ export { resolveDynamicComponent } from './helpers/resolveAssets' export { renderList } from './helpers/renderList' -export { toString } from './helpers/toString' export { toHandlers } from './helpers/toHandlers' export { renderSlot } from './helpers/renderSlot' export { createSlots } from './helpers/createSlots' @@ -90,7 +89,12 @@ export { setBlockTracking, createTextVNode, createCommentVNode } from './vnode' // Since @vue/shared is inlined into final builds, // when re-exporting from @vue/shared we need to avoid relying on their original // types so that the bundled d.ts does not attempt to import from it. -import { capitalize as _capitalize, camelize as _camelize } from '@vue/shared' +import { + toDisplayString as _toDisplayString, + capitalize as _capitalize, + camelize as _camelize +} from '@vue/shared' +export const toDisplayString = _toDisplayString as (s: unknown) => string export const capitalize = _capitalize as (s: string) => string export const camelize = _camelize as (s: string) => string diff --git a/packages/server-renderer/src/helpers.ts b/packages/server-renderer/src/helpers.ts new file mode 100644 index 0000000000..761dc10e76 --- /dev/null +++ b/packages/server-renderer/src/helpers.ts @@ -0,0 +1,51 @@ +import { toDisplayString } from '@vue/shared' + +const escapeRE = /["'&<>]/ + +export function escape(string: unknown) { + const str = '' + string + const match = escapeRE.exec(str) + + if (!match) { + return str + } + + let html = '' + let escaped: string + let index: number + let lastIndex = 0 + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escaped = '"' + break + case 38: // & + escaped = '&' + break + case 39: // ' + escaped = ''' + break + case 60: // < + escaped = '<' + break + case 62: // > + escaped = '>' + break + default: + continue + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index) + } + + lastIndex = index + 1 + html += escaped + } + + return lastIndex !== index ? html + str.substring(lastIndex, index) : html +} + +export function interpolate(value: unknown) { + return escape(toDisplayString(value)) +} diff --git a/packages/server-renderer/src/index.ts b/packages/server-renderer/src/index.ts index 19034429f6..318a0fa349 100644 --- a/packages/server-renderer/src/index.ts +++ b/packages/server-renderer/src/index.ts @@ -9,6 +9,8 @@ import { } from 'vue' import { isString, isPromise, isArray } from '@vue/shared' +export * from './helpers' + type SSRBuffer = SSRBufferItem[] type SSRBufferItem = string | ResolvedSSRBuffer | Promise type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 4d6f700166..89f21923e8 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -98,3 +98,12 @@ export const capitalize = cacheStringFunction( // compare whether a value has changed, accounting for NaN. export const hasChanged = (value: any, oldValue: any): boolean => value !== oldValue && (value === value || oldValue === oldValue) + +// for converting {{ interpolation }} values to displayed strings. +export function toDisplayString(val: unknown): string { + return val == null + ? '' + : isArray(val) || (isPlainObject(val) && val.toString === objectToString) + ? JSON.stringify(val, null, 2) + : String(val) +} -- 2.47.3