Skip to content

Commit ae15623

Browse files
committed
fix(compiler): support decode all HTML entities in the value of prop and attribute (fix #8895)
1 parent 5b39961 commit ae15623

File tree

6 files changed

+47
-44
lines changed

6 files changed

+47
-44
lines changed

Diff for: src/compiler/parser/html-decoder-cached.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* @flow */
2+
3+
import he from 'he'
4+
import { cached } from 'shared/util'
5+
6+
export default cached(he.decode)

Diff for: src/compiler/parser/html-parser.js

+4-19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import { makeMap, no } from 'shared/util'
1313
import { isNonPhrasingTag } from 'web/compiler/util'
1414
import { unicodeRegExp } from 'core/util/lang'
15+
import decodeHTMLCached from './html-decoder-cached'
1516

1617
// Regular Expressions for parsing tags and attributes
1718
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
@@ -30,25 +31,12 @@ const conditionalComment = /^<!\[/
3031
export const isPlainTextElement = makeMap('script,style,textarea', true)
3132
const reCache = {}
3233

33-
const decodingMap = {
34-
'&lt;': '<',
35-
'&gt;': '>',
36-
'&quot;': '"',
37-
'&amp;': '&',
38-
'&#10;': '\n',
39-
'&#9;': '\t',
40-
'&#39;': "'"
41-
}
42-
const encodedAttr = /&(?:lt|gt|quot|amp|#39);/g
43-
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g
44-
4534
// #5992
4635
const isIgnoreNewlineTag = makeMap('pre,textarea', true)
4736
const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n'
4837

49-
function decodeAttr (value, shouldDecodeNewlines) {
50-
const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr
51-
return value.replace(re, match => decodingMap[match])
38+
function decodeAttr (value) {
39+
return decodeHTMLCached(value)
5240
}
5341

5442
export function parseHTML (html, options) {
@@ -229,12 +217,9 @@ export function parseHTML (html, options) {
229217
for (let i = 0; i < l; i++) {
230218
const args = match.attrs[i]
231219
const value = args[3] || args[4] || args[5] || ''
232-
const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
233-
? options.shouldDecodeNewlinesForHref
234-
: options.shouldDecodeNewlines
235220
attrs[i] = {
236221
name: args[1],
237-
value: decodeAttr(value, shouldDecodeNewlines)
222+
value: decodeAttr(value)
238223
}
239224
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
240225
attrs[i].start = args.start + args[0].match(/^\s*/).length

Diff for: src/compiler/parser/index.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* @flow */
22

3-
import he from 'he'
43
import { parseHTML } from './html-parser'
54
import { parseText } from './text-parser'
65
import { parseFilters } from './filter-parser'
76
import { genAssignmentCode } from '../directives/model'
8-
import { extend, cached, no, camelize, hyphenate } from 'shared/util'
7+
import { extend, no, camelize, hyphenate } from 'shared/util'
98
import { isIE, isEdge, isServerRendering } from 'core/util/env'
9+
import decodeHTMLCached from './html-decoder-cached'
1010

1111
import {
1212
addProp,
@@ -42,8 +42,6 @@ const whitespaceRE = /\s+/g
4242

4343
const invalidAttributeRE = /[\s"'<>\/=]/
4444

45-
const decodeHTMLCached = cached(he.decode)
46-
4745
export const emptySlotScopeToken = `_empty_`
4846

4947
// configurable state
@@ -206,8 +204,6 @@ export function parse (
206204
expectHTML: options.expectHTML,
207205
isUnaryTag: options.isUnaryTag,
208206
canBeLeftOpenTag: options.canBeLeftOpenTag,
209-
shouldDecodeNewlines: options.shouldDecodeNewlines,
210-
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
211207
shouldKeepComment: options.comments,
212208
outputSourceRange: options.outputSourceRange,
213209
start (tag, attrs, unary, start, end) {

Diff for: src/platforms/web/entry-runtime-with-compiler.js

-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { mark, measure } from 'core/util/perf'
77
import Vue from './runtime/index'
88
import { query } from './util/index'
99
import { compileToFunctions } from './compiler/index'
10-
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
1110

1211
const idToTemplate = cached(id => {
1312
const el = query(id)
@@ -64,8 +63,6 @@ Vue.prototype.$mount = function (
6463

6564
const { render, staticRenderFns } = compileToFunctions(template, {
6665
outputSourceRange: process.env.NODE_ENV !== 'production',
67-
shouldDecodeNewlines,
68-
shouldDecodeNewlinesForHref,
6966
delimiters: options.delimiters,
7067
comments: options.comments
7168
}, this)

Diff for: src/platforms/web/util/compat.js

-16
This file was deleted.

Diff for: test/unit/modules/compiler/parser.spec.js

+35
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { parse } from 'compiler/parser/index'
22
import { extend } from 'shared/util'
33
import { baseOptions } from 'web/compiler/options'
44
import { isIE, isEdge } from 'core/util/env'
5+
import Vue from 'vue'
56

67
describe('parser', () => {
78
it('simple element', () => {
@@ -881,4 +882,38 @@ describe('parser', () => {
881882
expect(ast.children[2].type).toBe(3)
882883
expect(ast.children[2].text).toBe('\ndef')
883884
})
885+
886+
it(`HTML entities in the value of attribute should be decoded`, () => {
887+
const options = extend({}, baseOptions)
888+
const ast = parse('<input value="white&nbsp;space,single-&#39;-quote,double-&quot;-quote,an-&amp;-ampersand,less-&lt;-than,great-&gt;-than,line-&#10;-break,tab-&#9;-space" />', options)
889+
expect(ast.attrsList[0].value).toBe('white space,single-' + "'" + '-quote,double-' + '"' + '-quote,an-&-ampersand,less-<-than,great->-than,line-\n-break,tab-\t-space')
890+
})
891+
892+
it(`HTML entities in template should be decoded`, () => {
893+
const vm = new Vue({
894+
template: '<test></test>',
895+
components: {
896+
test: {
897+
template: '<input value="&#102;&#111;&#111;">'
898+
}
899+
}
900+
}).$mount()
901+
expect(vm.$el.value).toBe('foo')
902+
})
903+
904+
it(`HTML entities in the value of props should be decoded`, () => {
905+
const vm = new Vue({
906+
template: '<test name="-&nbsp;-"></test>',
907+
components: {
908+
test: {
909+
template: '<div>{{ name }}</div>',
910+
props: {
911+
name: String,
912+
},
913+
}
914+
}
915+
}).$mount()
916+
expect(vm.$el.innerHTML).toBe('-&nbsp;-')
917+
expect(vm.$el.innerText).toBe('- -')
918+
})
884919
})

0 commit comments

Comments
 (0)