Skip to content

Commit 04e6d02

Browse files
committed
Merge pull request facebook#1599 from syranide/escbrow
Split escapeTextForBrowser into escapeTextContentForBrowser and quoteAttributeValueForBrowser
2 parents 7872006 + 8ca058a commit 04e6d02

9 files changed

+115
-24
lines changed

src/browser/ui/ReactDOMComponent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var ReactMultiChild = require('ReactMultiChild');
2525
var ReactPerf = require('ReactPerf');
2626

2727
var assign = require('Object.assign');
28-
var escapeTextForBrowser = require('escapeTextForBrowser');
28+
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
2929
var invariant = require('invariant');
3030
var isEventSupported = require('isEventSupported');
3131
var keyOf = require('keyOf');
@@ -284,7 +284,7 @@ ReactDOMComponent.Mixin = {
284284
CONTENT_TYPES[typeof props.children] ? props.children : null;
285285
var childrenToUse = contentToUse != null ? null : props.children;
286286
if (contentToUse != null) {
287-
return prefix + escapeTextForBrowser(contentToUse);
287+
return prefix + escapeTextContentForBrowser(contentToUse);
288288
} else if (childrenToUse != null) {
289289
var mountImages = this.mountChildren(
290290
childrenToUse,

src/browser/ui/ReactDOMTextComponent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var ReactComponentBrowserEnvironment =
1818
var ReactDOMComponent = require('ReactDOMComponent');
1919

2020
var assign = require('Object.assign');
21-
var escapeTextForBrowser = require('escapeTextForBrowser');
21+
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
2222
var invariant = require('invariant');
2323

2424
/**
@@ -67,7 +67,7 @@ assign(ReactDOMTextComponent.prototype, {
6767
*/
6868
mountComponent: function(rootID, transaction, context) {
6969
this._rootNodeID = rootID;
70-
var escapedText = escapeTextForBrowser(this._stringText);
70+
var escapedText = escapeTextContentForBrowser(this._stringText);
7171

7272
if (transaction.renderToStaticMarkup) {
7373
// Normally we'd wrap this in a `span` for the reasons stated above, but

src/browser/ui/__tests__/ReactDOMComponent-test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,23 @@ describe('ReactDOMComponent', function() {
431431
'style={{marginRight: spacing + \'em\'}} when using JSX.'
432432
);
433433
});
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="&#x27;&quot;&lt;&gt;&amp;" style="text-align:&#x27;&quot;&lt;&gt;&amp;;">' +
447+
'&#x27;&quot;&lt;&gt;&amp;' +
448+
'</div>'
449+
);
450+
});
434451
});
435452

436453
describe('unmountComponent', function() {

src/browser/ui/dom/DOMPropertyOperations.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
var DOMProperty = require('DOMProperty');
1616

17-
var escapeTextForBrowser = require('escapeTextForBrowser');
17+
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
18+
var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
1819
var memoizeStringOnly = require('memoizeStringOnly');
1920
var warning = require('warning');
2021

@@ -27,7 +28,7 @@ function shouldIgnoreValue(name, value) {
2728
}
2829

2930
var processAttributeNameAndPrefix = memoizeStringOnly(function(name) {
30-
return escapeTextForBrowser(name) + '="';
31+
return escapeTextContentForBrowser(name) + '=';
3132
});
3233

3334
if (__DEV__) {
@@ -82,7 +83,7 @@ var DOMPropertyOperations = {
8283
*/
8384
createMarkupForID: function(id) {
8485
return processAttributeNameAndPrefix(DOMProperty.ID_ATTRIBUTE_NAME) +
85-
escapeTextForBrowser(id) + '"';
86+
quoteAttributeValueForBrowser(id);
8687
},
8788

8889
/**
@@ -101,16 +102,16 @@ var DOMPropertyOperations = {
101102
var attributeName = DOMProperty.getAttributeName[name];
102103
if (DOMProperty.hasBooleanValue[name] ||
103104
(DOMProperty.hasOverloadedBooleanValue[name] && value === true)) {
104-
return escapeTextForBrowser(attributeName);
105+
return escapeTextContentForBrowser(attributeName);
105106
}
106107
return processAttributeNameAndPrefix(attributeName) +
107-
escapeTextForBrowser(value) + '"';
108+
quoteAttributeValueForBrowser(value);
108109
} else if (DOMProperty.isCustomAttribute(name)) {
109110
if (value == null) {
110111
return '';
111112
}
112113
return processAttributeNameAndPrefix(name) +
113-
escapeTextForBrowser(value) + '"';
114+
quoteAttributeValueForBrowser(value);
114115
} else if (__DEV__) {
115116
warnUnknownProperty(name);
116117
}

src/browser/ui/dom/setTextContent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"use strict";
2020

2121
var ExecutionEnvironment = require('ExecutionEnvironment');
22-
var escapeTextForBrowser = require('escapeTextForBrowser');
22+
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
2323
var setInnerHTML = require('setInnerHTML');
2424

2525
/**
@@ -39,7 +39,7 @@ var setTextContent = function(node, text) {
3939
if (ExecutionEnvironment.canUseDOM) {
4040
if (!('textContent' in document.documentElement)) {
4141
setTextContent = function(node, text) {
42-
setInnerHTML(node, escapeTextForBrowser(text));
42+
setInnerHTML(node, escapeTextContentForBrowser(text));
4343
};
4444
}
4545
}

src/utils/__tests__/escapeTextForBrowser-test.js renamed to src/utils/__tests__/escapeTextContentForBrowser-test.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111

1212
'use strict';
1313

14-
describe('escapeTextForBrowser', function() {
14+
describe('escapeTextContentForBrowser', function() {
1515

16-
var escapeTextForBrowser = require('escapeTextForBrowser');
16+
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
1717

1818
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');
2121
});
2222

2323
it('should escape object to string', function() {
24-
var escaped = escapeTextForBrowser({
24+
var escaped = escapeTextContentForBrowser({
2525
toString: function() {
2626
return 'ponys';
2727
}
@@ -31,17 +31,17 @@ describe('escapeTextForBrowser', function() {
3131
});
3232

3333
it('should escape number to string', function() {
34-
expect(escapeTextForBrowser(42)).toBe('42');
34+
expect(escapeTextContentForBrowser(42)).toBe('42');
3535
});
3636

3737
it('should escape string', function() {
38-
var escaped = escapeTextForBrowser('<script type=\'\' src=""></script>');
38+
var escaped = escapeTextContentForBrowser('<script type=\'\' src=""></script>');
3939
expect(escaped).not.toContain('<');
4040
expect(escaped).not.toContain('>');
4141
expect(escaped).not.toContain('\'');
4242
expect(escaped).not.toContain('\"');
4343

44-
escaped = escapeTextForBrowser('&');
44+
escaped = escapeTextContentForBrowser('&');
4545
expect(escaped).toBe('&amp;');
4646
});
4747

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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('"&amp;"');
46+
});
47+
48+
});
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
* LICENSE file in the root directory of this source tree. An additional grant
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*
9-
* @providesModule escapeTextForBrowser
10-
* @typechecks static-only
9+
* @providesModule escapeTextContentForBrowser
1110
*/
1211

1312
'use strict';
@@ -32,8 +31,8 @@ function escaper(match) {
3231
* @param {*} text Text value to escape.
3332
* @return {string} An escaped string.
3433
*/
35-
function escapeTextForBrowser(text) {
34+
function escapeTextContentForBrowser(text) {
3635
return ('' + text).replace(ESCAPE_REGEX, escaper);
3736
}
3837

39-
module.exports = escapeTextForBrowser;
38+
module.exports = escapeTextContentForBrowser;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

0 commit comments

Comments
 (0)