Split escapeTextForBrowser into escapeTextContentForBrowser and quoteAttributeValueForBrowser
Andreas Svensson committed
Sep 18, 2014 at 19:12 UTC
8ca058ac4ec15517908cd7afff7abc0511925494
9 files changed
+115
-24
src/browser/ui/ReactDOMComponent.js
+2
-2
@@ -25,7 +25,7 @@ var ReactMultiChild = require('ReactMultiChild');
25
var ReactPerf = require('ReactPerf');
26
27
var assign = require('Object.assign');
28
-var escapeTextForBrowser = require('escapeTextForBrowser');
28
+var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
29
var invariant = require('invariant');
30
var isEventSupported = require('isEventSupported');
31
var keyOf = require('keyOf');
@@ -284,7 +284,7 @@ ReactDOMComponent.Mixin = {
284
CONTENT_TYPES[typeof props.children] ? props.children : null;
285
var childrenToUse = contentToUse != null ? null : props.children;
286
if (contentToUse != null) {
287
- return prefix + escapeTextForBrowser(contentToUse);
287
+ return prefix + escapeTextContentForBrowser(contentToUse);
288
} else if (childrenToUse != null) {
289
var mountImages = this.mountChildren(
290
childrenToUse,
src/browser/ui/ReactDOMTextComponent.js
+2
-2
@@ -18,7 +18,7 @@ var ReactComponentBrowserEnvironment =
18
var ReactDOMComponent = require('ReactDOMComponent');
19
20
var assign = require('Object.assign');
21
-var escapeTextForBrowser = require('escapeTextForBrowser');
21
+var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
22
var invariant = require('invariant');
23
24
/**
@@ -67,7 +67,7 @@ assign(ReactDOMTextComponent.prototype, {
67
*/
68
mountComponent: function(rootID, transaction, context) {
69
this._rootNodeID = rootID;
70
- var escapedText = escapeTextForBrowser(this._stringText);
70
+ var escapedText = escapeTextContentForBrowser(this._stringText);
71
72
if (transaction.renderToStaticMarkup) {
73
// Normally we'd wrap this in a `span` for the reasons stated above, but
src/browser/ui/__tests__/ReactDOMComponent-test.js
+17
@@ -431,6 +431,23 @@ describe('ReactDOMComponent', function() {
431
'style={{marginRight: spacing + \'em\'}} when using JSX.'
432
);
433
});
434
+
435
+ it("should properly escape text content and attributes values", function() {
436
+ expect(
437
+ React.renderToStaticMarkup(
438
+ React.DOM.div({
439
+ title: '\'"<>&',
440
+ style: {
441
+ textAlign: '\'"<>&'
442
+ }
443
+ }, '\'"<>&')
444
+ )
445
+ ).toBe(
446
+ '<div title="'"<>&" style="text-align:'"<>&;">' +
447
+ ''"<>&' +
448
+ '</div>'
449
+ );
450
+ });
451
});
452
453
describe('unmountComponent', function() {
src/browser/ui/dom/DOMPropertyOperations.js
+7
-6
@@ -14,7 +14,8 @@
14
15
var DOMProperty = require('DOMProperty');
16
17
-var escapeTextForBrowser = require('escapeTextForBrowser');
17
+var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
18
+var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
19
var memoizeStringOnly = require('memoizeStringOnly');
20
var warning = require('warning');
21
@@ -27,7 +28,7 @@ function shouldIgnoreValue(name, value) {
28
}
29
30
var processAttributeNameAndPrefix = memoizeStringOnly(function(name) {
30
- return escapeTextForBrowser(name) + '="';
31
+ return escapeTextContentForBrowser(name) + '=';
32
});
33
34
if (__DEV__) {
@@ -82,7 +83,7 @@ var DOMPropertyOperations = {
83
*/
84
createMarkupForID: function(id) {
85
return processAttributeNameAndPrefix(DOMProperty.ID_ATTRIBUTE_NAME) +
85
- escapeTextForBrowser(id) + '"';
86
+ quoteAttributeValueForBrowser(id);
87
},
88
89
/**
@@ -101,16 +102,16 @@ var DOMPropertyOperations = {
102
var attributeName = DOMProperty.getAttributeName[name];
103
if (DOMProperty.hasBooleanValue[name] ||
104
(DOMProperty.hasOverloadedBooleanValue[name] && value === true)) {
104
- return escapeTextForBrowser(attributeName);
105
+ return escapeTextContentForBrowser(attributeName);
106
}
107
return processAttributeNameAndPrefix(attributeName) +
107
- escapeTextForBrowser(value) + '"';
108
+ quoteAttributeValueForBrowser(value);
109
} else if (DOMProperty.isCustomAttribute(name)) {
110
if (value == null) {
111
return '';
112
}
113
return processAttributeNameAndPrefix(name) +
113
- escapeTextForBrowser(value) + '"';
114
+ quoteAttributeValueForBrowser(value);
115
} else if (__DEV__) {
116
warnUnknownProperty(name);
117
}
src/browser/ui/dom/setTextContent.js
+2
-2
@@ -19,7 +19,7 @@
19
"use strict";
20
21
var ExecutionEnvironment = require('ExecutionEnvironment');
22
-var escapeTextForBrowser = require('escapeTextForBrowser');
22
+var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
23
var setInnerHTML = require('setInnerHTML');
24
25
/**
@@ -39,7 +39,7 @@ var setTextContent = function(node, text) {
39
if (ExecutionEnvironment.canUseDOM) {
40
if (!('textContent' in document.documentElement)) {
41
setTextContent = function(node, text) {
42
- setInnerHTML(node, escapeTextForBrowser(text));
42
+ setInnerHTML(node, escapeTextContentForBrowser(text));
43
};
44
}
45
}
src/utils/__tests__/escapeTextContentForBrowser-test.js
renamed
+8
-8
@@ -11,17 +11,17 @@
11
12
'use strict';
13
14
-describe('escapeTextForBrowser', function() {
14
+describe('escapeTextContentForBrowser', function() {
15
16
- var escapeTextForBrowser = require('escapeTextForBrowser');
16
+ var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
17
18
it('should escape boolean to string', function() {
19
- expect(escapeTextForBrowser(true)).toBe('true');
20
- expect(escapeTextForBrowser(false)).toBe('false');
19
+ expect(escapeTextContentForBrowser(true)).toBe('true');
20
+ expect(escapeTextContentForBrowser(false)).toBe('false');
21
});
22
23
it('should escape object to string', function() {
24
- var escaped = escapeTextForBrowser({
24
+ var escaped = escapeTextContentForBrowser({
25
toString: function() {
26
return 'ponys';
27
}
@@ -31,17 +31,17 @@ describe('escapeTextForBrowser', function() {
31
});
32
33
it('should escape number to string', function() {
34
- expect(escapeTextForBrowser(42)).toBe('42');
34
+ expect(escapeTextContentForBrowser(42)).toBe('42');
35
});
36
37
it('should escape string', function() {
38
- var escaped = escapeTextForBrowser('<script type=\'\' src=""></script>');
38
+ var escaped = escapeTextContentForBrowser('<script type=\'\' src=""></script>');
39
expect(escaped).not.toContain('<');
40
expect(escaped).not.toContain('>');
41
expect(escaped).not.toContain('\'');
42
expect(escaped).not.toContain('\"');
43
44
- escaped = escapeTextForBrowser('&');
44
+ escaped = escapeTextContentForBrowser('&');
45
expect(escaped).toBe('&');
46
});
47
src/utils/__tests__/quoteAttributeValueForBrowser-test.js
new
+48
@@ -0,0 +1,48 @@
1
+/**
2
+ * Copyright 2013-2015, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @emails react-core
10
+ */
11
+
12
+"use strict";
13
+
14
+describe('quoteAttributeValueForBrowser', function() {
15
+
16
+ var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
17
+
18
+ it('should escape boolean to string', function() {
19
+ expect(quoteAttributeValueForBrowser(true)).toBe('"true"');
20
+ expect(quoteAttributeValueForBrowser(false)).toBe('"false"');
21
+ });
22
+
23
+ it('should escape object to string', function() {
24
+ var escaped = quoteAttributeValueForBrowser({
25
+ toString: function() {
26
+ return 'ponys';
27
+ }
28
+ });
29
+
30
+ expect(escaped).toBe('"ponys"');
31
+ });
32
+
33
+ it('should escape number to string', function() {
34
+ expect(quoteAttributeValueForBrowser(42)).toBe('"42"');
35
+ });
36
+
37
+ it('should escape string', function() {
38
+ var escaped = quoteAttributeValueForBrowser('<script type=\'\' src=""></script>');
39
+ expect(escaped).not.toContain('<');
40
+ expect(escaped).not.toContain('>');
41
+ expect(escaped).not.toContain('\'');
42
+ expect(escaped.substr(1, -1)).not.toContain('\"');
43
+
44
+ escaped = quoteAttributeValueForBrowser('&');
45
+ expect(escaped).toBe('"&"');
46
+ });
47
+
48
+});
src/utils/escapeTextContentForBrowser.js
renamed
+3
-4
@@ -6,8 +6,7 @@
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
- * @providesModule escapeTextForBrowser
10
- * @typechecks static-only
9
+ * @providesModule escapeTextContentForBrowser
10
*/
11
12
'use strict';
@@ -32,8 +31,8 @@ function escaper(match) {
31
* @param {*} text Text value to escape.
32
* @return {string} An escaped string.
33
*/
35
-function escapeTextForBrowser(text) {
34
+function escapeTextContentForBrowser(text) {
35
return ('' + text).replace(ESCAPE_REGEX, escaper);
36
}
37
39
-module.exports = escapeTextForBrowser;
38
+module.exports = escapeTextContentForBrowser;
src/utils/quoteAttributeValueForBrowser.js
new
+26
@@ -0,0 +1,26 @@
1
+/**
2
+ * Copyright 2013-2015, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule quoteAttributeValueForBrowser
10
+ */
11
+
12
+"use strict";
13
+
14
+var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
15
+
16
+/**
17
+ * Escapes attribute value to prevent scripting attacks.
18
+ *
19
+ * @param {*} value Value to escape.
20
+ * @return {string} An escaped string.
21
+ */
22
+function quoteAttributeValueForBrowser(value) {
23
+ return '"' + escapeTextContentForBrowser(value) + '"';
24
+}
25
+
26
+module.exports = quoteAttributeValueForBrowser;