Browse Source

properly encode HTML in server-side rendering (fix #3078)

dev
Evan You 9 years ago
parent
commit
b0ad94fc87
  1. 1
      .flowconfig
  2. 2
      build/build.js
  3. 3
      build/webpack.ssr.dev.config.js
  4. 14
      flow/compiler.js
  5. 18
      flow/modules.js
  6. 1
      src/core/vdom/vnode.js
  7. 25
      src/platforms/web/server/modules/props.js
  8. 4
      src/platforms/web/util/attrs.js
  9. 6
      src/server/render.js
  10. 28
      test/ssr/ssr-string.spec.js

1
.flowconfig

@ -17,4 +17,3 @@ module.name_mapper='^shared/\(.*\)$' -> '<PROJECT_ROOT>/src/shared/\1'
module.name_mapper='^web/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/web/\1'
module.name_mapper='^server/\(.*\)$' -> '<PROJECT_ROOT>/src/server/\1'
module.name_mapper='^entries/\(.*\)$' -> '<PROJECT_ROOT>/src/entries/\1'
module.name_mapper='^entities$' -> '<PROJECT_ROOT>/src/compiler/parser/entity-decoder'

2
build/build.js

@ -72,7 +72,7 @@ var builds = [
{
entry: 'src/entries/web-server-renderer.js',
format: 'cjs',
external: ['stream'],
external: ['stream', 'entities'],
out: 'packages/vue-server-renderer/index.js'
}
]

3
build/webpack.ssr.dev.config.js

@ -12,6 +12,9 @@ module.exports = {
resolve: {
alias: alias
},
externals: {
'entities': true
},
module: {
loaders: [
{

14
flow/compiler.js

@ -127,20 +127,6 @@ declare type ASTText = {
// SFC-parser related declarations
declare module 'de-indent' {
declare var exports: {
(str: string): string;
}
}
declare module 'source-map' {
declare class SourceMapGenerator {
setSourceContent(filename: string, content: string): void;
addMapping(mapping: Object): void;
toString(): string;
}
}
// an object format describing a single-file component.
declare type SFCDescriptor = {
template: ?SFCBlock,

18
flow/modules.js

@ -0,0 +1,18 @@
declare module 'entities' {
declare function encodeHTML(html: string): string;
declare function decodeHTML(html: string): string;
}
declare module 'de-indent' {
declare var exports: {
(str: string): string;
}
}
declare module 'source-map' {
declare class SourceMapGenerator {
setSourceContent(filename: string, content: string): void;
addMapping(mapping: Object): void;
toString(): string;
}
}

1
src/core/vdom/vnode.js

@ -12,6 +12,7 @@ export default class VNode {
componentOptions: VNodeComponentOptions | void;
child: Component | void;
parent: VNode | void;
raw: ?boolean;
constructor (
tag?: string,

25
src/platforms/web/server/modules/props.js

@ -1,18 +1,31 @@
/* @flow */
import VNode from 'core/vdom/vnode'
import { renderAttr } from './attrs'
import { propsToAttrMap, isRenderableAttr } from 'web/util/attrs'
export default function (node: VNodeWithData): ?string {
export default function (node: VNodeWithData): string {
const props = node.data.props
let res = ''
if (props) {
let res = ''
for (const key in props) {
const attr = propsToAttrMap[key] || key.toLowerCase()
if (isRenderableAttr(attr)) {
res += renderAttr(attr, props[key])
if (key === 'innerHTML') {
setText(node, props[key], true)
} else if (key === 'textContent') {
setText(node, props[key])
} else {
const attr = propsToAttrMap[key] || key.toLowerCase()
if (isRenderableAttr(attr)) {
res += renderAttr(attr, props[key])
}
}
}
return res
}
return res
}
function setText (node, text, raw) {
const child = new VNode(undefined, undefined, undefined, text)
child.raw = raw
node.children = [child]
}

4
src/platforms/web/util/attrs.js

@ -32,13 +32,15 @@ const isAttr = makeMap(
'target,title,type,usemap,value,width,wrap'
)
export const isRenderableAttr = (name: string): boolean => {
/* istanbul ignore next */
const isRenderableAttr = (name: string): boolean => {
return (
isAttr(name) ||
name.indexOf('data-') === 0 ||
name.indexOf('aria-') === 0
)
}
export { isRenderableAttr }
export const propsToAttrMap = {
acceptCharset: 'accept-charset',

6
src/server/render.js

@ -1,7 +1,11 @@
/* @flow */
import { cached } from 'shared/util'
import { encodeHTML } from 'entities'
import { createComponentInstanceForVnode } from 'core/vdom/create-component'
const encodeHTMLCached = cached(encodeHTML)
export function createRenderFunction (
modules: Array<Function>,
directives: Object,
@ -20,7 +24,7 @@ export function createRenderFunction (
if (node.tag) {
renderElement(node, write, next, isRoot)
} else {
write(node.text, next)
write(node.raw ? node.text : encodeHTMLCached(node.text), next)
}
}
}

28
test/ssr/ssr-string.spec.js

@ -78,10 +78,34 @@ describe('SSR: renderToString', () => {
template: '<div>{{ foo }} side {{ bar }}</div>',
data: {
foo: 'server',
bar: 'rendering'
bar: '<span>rendering</span>'
}
}, result => {
expect(result).toContain('<div server-rendered="true">server side rendering</div>')
expect(result).toContain('<div server-rendered="true">server side &lt;span&gt;rendering&lt;&sol;span&gt;</div>')
done()
})
})
it('v-html', done => {
renderVmWithOptions({
template: '<div v-html="text"></div>',
data: {
text: '<span>foo</span>'
}
}, result => {
expect(result).toContain('<div server-rendered="true"><span>foo</span></div>')
done()
})
})
it('v-text', done => {
renderVmWithOptions({
template: '<div v-text="text"></div>',
data: {
text: '<span>foo</span>'
}
}, result => {
expect(result).toContain('<div server-rendered="true">&lt;span&gt;foo&lt;&sol;span&gt;</div>')
done()
})
})

Loading…
Cancel
Save