diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index bb8ecc6e81ddf4..12294a9e699eef 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -42,13 +42,6 @@ describe('ReactDOMServer', () => { expect(response).toMatch(new RegExp('')); }); - it('should generate simple markup for attribute with `>` symbol', () => { - var response = ReactDOMServer.renderToString(); - expect(response).toMatch( - new RegExp(''), - ); - }); - it('should generate comment markup for component returns null', () => { class NullComponent extends React.Component { render() { diff --git a/packages/react-dom/src/__tests__/escapeTextContentForBrowser-test.js b/packages/react-dom/src/__tests__/escapeTextContentForBrowser-test.js deleted file mode 100644 index 78fedc5b62cc00..00000000000000 --- a/packages/react-dom/src/__tests__/escapeTextContentForBrowser-test.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -describe('escapeTextContentForBrowser', () => { - // TODO: can we express this test with only public API? - var escapeTextContentForBrowser = require('../shared/escapeTextContentForBrowser') - .default; - - it('should escape boolean to string', () => { - expect(escapeTextContentForBrowser(true)).toBe('true'); - expect(escapeTextContentForBrowser(false)).toBe('false'); - }); - - it('should escape object to string', () => { - var escaped = escapeTextContentForBrowser({ - toString: function() { - return 'ponys'; - }, - }); - - expect(escaped).toBe('ponys'); - }); - - it('should escape number to string', () => { - expect(escapeTextContentForBrowser(42)).toBe('42'); - }); - - it('should escape string', () => { - var escaped = escapeTextContentForBrowser( - '', - ); - expect(escaped).not.toContain('<'); - expect(escaped).not.toContain('>'); - expect(escaped).not.toContain("'"); - expect(escaped).not.toContain('"'); - - escaped = escapeTextContentForBrowser('&'); - expect(escaped).toBe('&'); - }); -}); diff --git a/packages/react-dom/src/__tests__/escapeTextForBrowser-test.js b/packages/react-dom/src/__tests__/escapeTextForBrowser-test.js new file mode 100644 index 00000000000000..a76cacb7fdf651 --- /dev/null +++ b/packages/react-dom/src/__tests__/escapeTextForBrowser-test.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +var React; +var ReactDOMServer; + +describe('escapeTextForBrowser', () => { + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMServer = require('react-dom/server'); + }); + + it('ampersand is escaped when passed as text content', () => { + var response = ReactDOMServer.renderToString({'&'}); + expect(response).toMatch('&'); + }); + + it('double quote is escaped when passed as text content', () => { + var response = ReactDOMServer.renderToString({'"'}); + expect(response).toMatch('"'); + }); + + it('single quote is escaped when passed as text content', () => { + var response = ReactDOMServer.renderToString({"'"}); + expect(response).toMatch('''); + }); + + it('greater than entity is escaped when passed as text content', () => { + var response = ReactDOMServer.renderToString({'>'}); + expect(response).toMatch('>'); + }); + + it('lower than entity is escaped when passed as text content', () => { + var response = ReactDOMServer.renderToString({'<'}); + expect(response).toMatch('<'); + }); + + it('number is correctly passed as text content', () => { + var response = ReactDOMServer.renderToString({42}); + expect(response).toMatch('42'); + }); + + it('number is escaped to string when passed as text content', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); + }); + + it('escape text content representing a script tag', () => { + var response = ReactDOMServer.renderToString( + {''}, + ); + expect(response).toMatch( + '<script type='' ' + + 'src=""></script>', + ); + }); +}); diff --git a/packages/react-dom/src/__tests__/quoteAttributeValueForBrowser-test.js b/packages/react-dom/src/__tests__/quoteAttributeValueForBrowser-test.js index 4a4489b98714e2..1a0db78037f2d0 100644 --- a/packages/react-dom/src/__tests__/quoteAttributeValueForBrowser-test.js +++ b/packages/react-dom/src/__tests__/quoteAttributeValueForBrowser-test.js @@ -9,40 +9,67 @@ 'use strict'; +var React; +var ReactDOMServer; + describe('quoteAttributeValueForBrowser', () => { - // TODO: can we express this test with only public API? - var quoteAttributeValueForBrowser = require('../shared/quoteAttributeValueForBrowser') - .default; + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMServer = require('react-dom/server'); + }); - it('should escape boolean to string', () => { - expect(quoteAttributeValueForBrowser(true)).toBe('"true"'); - expect(quoteAttributeValueForBrowser(false)).toBe('"false"'); + it('ampersand is escaped inside attributes', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); }); - it('should escape object to string', () => { - var escaped = quoteAttributeValueForBrowser({ - toString: function() { - return 'ponys'; - }, - }); + it('double quote is escaped inside attributes', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); + }); + + it('single quote is escaped inside attributes', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); + }); - expect(escaped).toBe('"ponys"'); + it('greater than entity is escaped inside attributes', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); }); - it('should escape number to string', () => { - expect(quoteAttributeValueForBrowser(42)).toBe('"42"'); + it('lower than entity is escaped inside attributes', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); }); - it('should escape string', () => { - var escaped = quoteAttributeValueForBrowser( - '', + it('number is escaped to string inside attributes', () => { + var response = ReactDOMServer.renderToString(); + expect(response).toMatch(''); + }); + + it('object is passed to a string inside attributes', () => { + var sampleObject = { + toString: function() { + return 'ponys'; + }, + }; + + var response = ReactDOMServer.renderToString( + , ); - expect(escaped).not.toContain('<'); - expect(escaped).not.toContain('>'); - expect(escaped).not.toContain("'"); - expect(escaped.substr(1, -1)).not.toContain('"'); + expect(response).toMatch(''); + }); - escaped = quoteAttributeValueForBrowser('&'); - expect(escaped).toBe('"&"'); + it('script tag is escaped inside attributes', () => { + var response = ReactDOMServer.renderToString( + '} />, + ); + expect(response).toMatch( + '', + ); }); }); diff --git a/packages/react-dom/src/client/setTextContent.js b/packages/react-dom/src/client/setTextContent.js index ffaa3c9b14acdb..0cc8f553c51f36 100644 --- a/packages/react-dom/src/client/setTextContent.js +++ b/packages/react-dom/src/client/setTextContent.js @@ -5,10 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; - -import setInnerHTML from './setInnerHTML'; -import escapeTextContentForBrowser from '../shared/escapeTextContentForBrowser'; import {TEXT_NODE} from '../shared/HTMLNodeType'; /** @@ -37,16 +33,4 @@ let setTextContent = function(node, text) { node.textContent = text; }; -if (ExecutionEnvironment.canUseDOM) { - if (!('textContent' in document.documentElement)) { - setTextContent = function(node, text) { - if (node.nodeType === TEXT_NODE) { - node.nodeValue = text; - return; - } - setInnerHTML(node, escapeTextContentForBrowser(text)); - }; - } -} - export default setTextContent; diff --git a/packages/react-dom/src/server/DOMMarkupOperations.js b/packages/react-dom/src/server/DOMMarkupOperations.js index d85fe050ab905c..b86f6fd6017267 100644 --- a/packages/react-dom/src/server/DOMMarkupOperations.js +++ b/packages/react-dom/src/server/DOMMarkupOperations.js @@ -14,7 +14,7 @@ import { shouldAttributeAcceptBooleanValue, shouldSetAttribute, } from '../shared/DOMProperty'; -import quoteAttributeValueForBrowser from '../shared/quoteAttributeValueForBrowser'; +import quoteAttributeValueForBrowser from './quoteAttributeValueForBrowser'; import warning from 'fbjs/lib/warning'; // isAttributeNameSafe() is currently duplicated in DOMPropertyOperations. diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 60f03527653aed..c76a311aac3b73 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -26,6 +26,7 @@ import { createMarkupForProperty, createMarkupForRoot, } from './DOMMarkupOperations'; +import escapeTextForBrowser from './escapeTextForBrowser'; import { Namespaces, getIntrinsicNamespace, @@ -34,7 +35,6 @@ import { import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes'; import assertValidProps from '../shared/assertValidProps'; import dangerousStyleValue from '../shared/dangerousStyleValue'; -import escapeTextContentForBrowser from '../shared/escapeTextContentForBrowser'; import isCustomComponent from '../shared/isCustomComponent'; import omittedCloseTags from '../shared/omittedCloseTags'; import warnValidStyle from '../shared/warnValidStyle'; @@ -204,7 +204,7 @@ function getNonChildrenInnerMarkup(props) { } else { var content = props.children; if (typeof content === 'string' || typeof content === 'number') { - return escapeTextContentForBrowser(content); + return escapeTextForBrowser(content); } } return null; @@ -572,13 +572,13 @@ class ReactDOMServerRenderer { return ''; } if (this.makeStaticMarkup) { - return escapeTextContentForBrowser(text); + return escapeTextForBrowser(text); } if (this.previousWasTextNode) { - return '' + escapeTextContentForBrowser(text); + return '' + escapeTextForBrowser(text); } this.previousWasTextNode = true; - return escapeTextContentForBrowser(text); + return escapeTextForBrowser(text); } else { var nextChild; ({child: nextChild, context} = resolve(child, context)); diff --git a/packages/react-dom/src/shared/escapeTextContentForBrowser.js b/packages/react-dom/src/server/escapeTextForBrowser.js similarity index 93% rename from packages/react-dom/src/shared/escapeTextContentForBrowser.js rename to packages/react-dom/src/server/escapeTextForBrowser.js index 8d086dfd72e5b4..5b8245a93592f4 100644 --- a/packages/react-dom/src/shared/escapeTextContentForBrowser.js +++ b/packages/react-dom/src/server/escapeTextForBrowser.js @@ -39,9 +39,9 @@ var matchHtmlRegExp = /["'&<>]/; /** - * Escape special characters in the given string of html. + * Escapes special characters and HTML entities in a given html string. * - * @param {string} string The string to escape for inserting into HTML + * @param {string} string HTML string to escape for later insertion * @return {string} * @public */ @@ -98,7 +98,7 @@ function escapeHtml(string) { * @param {*} text Text value to escape. * @return {string} An escaped string. */ -function escapeTextContentForBrowser(text) { +function escapeTextForBrowser(text) { if (typeof text === 'boolean' || typeof text === 'number') { // this shortcircuit helps perf for types that we know will never have // special characters, especially given that this function is used often @@ -108,4 +108,4 @@ function escapeTextContentForBrowser(text) { return escapeHtml(text); } -export default escapeTextContentForBrowser; +export default escapeTextForBrowser; diff --git a/packages/react-dom/src/shared/quoteAttributeValueForBrowser.js b/packages/react-dom/src/server/quoteAttributeValueForBrowser.js similarity index 76% rename from packages/react-dom/src/shared/quoteAttributeValueForBrowser.js rename to packages/react-dom/src/server/quoteAttributeValueForBrowser.js index feeeb055a7363a..592aab6a25e978 100644 --- a/packages/react-dom/src/shared/quoteAttributeValueForBrowser.js +++ b/packages/react-dom/src/server/quoteAttributeValueForBrowser.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import escapeTextContentForBrowser from './escapeTextContentForBrowser'; +import escapeTextForBrowser from './escapeTextForBrowser'; /** * Escapes attribute value to prevent scripting attacks. @@ -14,7 +14,7 @@ import escapeTextContentForBrowser from './escapeTextContentForBrowser'; * @return {string} An escaped string. */ function quoteAttributeValueForBrowser(value) { - return '"' + escapeTextContentForBrowser(value) + '"'; + return '"' + escapeTextForBrowser(value) + '"'; } export default quoteAttributeValueForBrowser;