Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions packages/react-dom/src/__tests__/ReactDOMComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1599,9 +1599,7 @@ describe('ReactDOMComponent', () => {
),
),
).toBe(
'<div title="&#x27;&quot;&lt;&gt;&amp;" style="text-align:&#x27;&quot;&lt;&gt;&amp;">' +
'&#x27;&quot;&lt;&gt;&amp;' +
'</div>',
`<div title=\"'&quot;<>&amp;\" style=\"text-align:'&quot;<>&amp;\">'\"&lt;&gt;&amp;</div>`,
);
});
});
Expand Down
11 changes: 5 additions & 6 deletions packages/react-dom/src/__tests__/escapeTextForBrowser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ describe('escapeTextForBrowser', () => {
expect(response).toMatch('<span data-reactroot="">&amp;</span>');
});

it('double quote is escaped when passed as text content', () => {
it('double quote is not escaped when passed as text content', () => {
const response = ReactDOMServer.renderToString(<span>{'"'}</span>);
expect(response).toMatch('<span data-reactroot="">&quot;</span>');
expect(response).toMatch('<span data-reactroot="">"</span>');
});

it('single quote is escaped when passed as text content', () => {
it('single quote is not escaped when passed as text content', () => {
const response = ReactDOMServer.renderToString(<span>{"'"}</span>);
expect(response).toMatch('<span data-reactroot="">&#x27;</span>');
expect(response).toMatch(`<span data-reactroot="">'</span>`);
});

it('greater than entity is escaped when passed as text content', () => {
Expand Down Expand Up @@ -59,8 +59,7 @@ describe('escapeTextForBrowser', () => {
<span>{'<script type=\'\' src=""></script>'}</span>,
);
expect(response).toMatch(
'<span data-reactroot="">&lt;script type=&#x27;&#x27; ' +
'src=&quot;&quot;&gt;&lt;/script&gt;</span>',
`<span data-reactroot=\"\">&lt;script type='' src=\"\"&gt;&lt;/script&gt;</span>`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ describe('quoteAttributeValueForBrowser', () => {
expect(response).toMatch('<img data-attr="&quot;" data-reactroot=""/>');
});

it('single quote is escaped inside attributes', () => {
it('single quote is not escaped inside attributes', () => {
const response = ReactDOMServer.renderToString(<img data-attr="'" />);
expect(response).toMatch('<img data-attr="&#x27;" data-reactroot=""/>');
expect(response).toMatch(`<img data-attr="'" data-reactroot=""/>`);
});

it('greater than entity is escaped inside attributes', () => {
it('greater than entity is not escaped inside attributes', () => {
const response = ReactDOMServer.renderToString(<img data-attr=">" />);
expect(response).toMatch('<img data-attr="&gt;" data-reactroot=""/>');
expect(response).toMatch('<img data-attr=">" data-reactroot=""/>');
});

it('lower than entity is escaped inside attributes', () => {
it('lower than entity is not escaped inside attributes', () => {
const response = ReactDOMServer.renderToString(<img data-attr="<" />);
expect(response).toMatch('<img data-attr="&lt;" data-reactroot=""/>');
expect(response).toMatch('<img data-attr="<" data-reactroot=""/>');
});

it('number is escaped to string inside attributes', () => {
Expand All @@ -67,9 +67,7 @@ describe('quoteAttributeValueForBrowser', () => {
<img data-attr={'<script type=\'\' src=""></script>'} />,
);
expect(response).toMatch(
'<img data-attr="&lt;script type=&#x27;&#x27; ' +
'src=&quot;&quot;&gt;&lt;/script&gt;" ' +
'data-reactroot=""/>',
`<img data-attr=\"<script type='' src=&quot;&quot;></script>\" data-reactroot=\"\"/>`,
);
});
});
10 changes: 5 additions & 5 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import {
createMarkupForProperty,
createMarkupForRoot,
} from './DOMMarkupOperations';
import escapeTextForBrowser from './escapeTextForBrowser';
import {escapeText} from './escapeTextForBrowser';
import {
prepareToUseHooks,
finishHooks,
Expand Down Expand Up @@ -280,7 +280,7 @@ function getNonChildrenInnerMarkup(props) {
} else {
const content = props.children;
if (typeof content === 'string' || typeof content === 'number') {
return escapeTextForBrowser(content);
return escapeText(content);
}
}
return null;
Expand Down Expand Up @@ -886,13 +886,13 @@ class ReactDOMServerRenderer {
return '';
}
if (this.makeStaticMarkup) {
return escapeTextForBrowser(text);
return escapeText(text);
}
if (this.previousWasTextNode) {
return '<!-- -->' + escapeTextForBrowser(text);
return '<!-- -->' + escapeText(text);
}
this.previousWasTextNode = true;
return escapeTextForBrowser(text);
return escapeText(text);
} else {
let nextChild;
({child: nextChild, context} = resolve(child, context, this.threadID));
Expand Down
141 changes: 70 additions & 71 deletions packages/react-dom/src/server/escapeTextForBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,82 +30,81 @@
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

// code copied and modified from escape-html
/**
* Module variables.
* @private
*/

const matchHtmlRegExp = /["'&<>]/;

/**
* Escapes special characters and HTML entities in a given html string.
*
* @param {string} string HTML string to escape for later insertion
* @return {string}
* @public
*/

function escapeHtml(string) {
const str = '' + string;
const match = matchHtmlRegExp.exec(str);

if (!match) {
return str;
}

let escape;
let html = '';
let index;
let lastIndex = 0;

for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;';
break;
case 38: // &
escape = '&amp;';
break;
case 39: // '
escape = '&#x27;'; // modified from escape-html; used to be '&#39'
break;
case 60: // <
escape = '&lt;';
break;
case 62: // >
escape = '&gt;';
break;
default:
continue;
// double quotes and single quotes are allowed in text nodes
export function escapeText(text: string | number): string {
if (typeof text === 'string') {
if (
text.indexOf('&') === -1 &&
text.indexOf('<') === -1 &&
text.indexOf('>') === -1
) {
return text;
}

if (lastIndex !== index) {
html += str.substring(lastIndex, index);
let result = text;
let start = 0;
let i = 0;
for (; i < text.length; ++i) {
let escape;
switch (text.charCodeAt(i)) {
case 38: // &
escape = '&amp;';
break;
case 60: // <
escape = '&lt;';
break;
case 62: // >
escape = '&gt;';
break;
default:
continue;
}
if (i > start) {
escape = text.slice(start, i) + escape;
}
result = start > 0 ? result + escape : escape;
start = i + 1;
}

lastIndex = index + 1;
html += escape;
if (i !== start) {
return result + text.slice(start, i);
}
return result;
}

return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
return text.toString();
}
// end code copied and modified from escape-html

/**
* Escapes text to prevent scripting attacks.
*
* @param {*} text Text value to escape.
* @return {string} An escaped string.
*/
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
// for numeric dom ids.
return '' + text;
// <, > and single quotes are allowed in attribute values
export function escapeAttributeValue(text: string | number): string {
if (typeof text === 'string') {
if (text.indexOf('"') === -1 && text.indexOf('&') === -1) {
return text;
}

let result = text;
let start = 0;
let i = 0;
for (; i < text.length; ++i) {
let escape;
switch (text.charCodeAt(i)) {
case 34: // "
escape = '&quot;';
break;
case 38: // &
escape = '&amp;';
break;
default:
continue;
}
if (i > start) {
escape = text.slice(start, i) + escape;
}
result = start > 0 ? result + escape : escape;
start = i + 1;
}
if (i !== start) {
return result + text.slice(start, i);
}
return result;
}
return escapeHtml(text);
return text.toString();
}

export default escapeTextForBrowser;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import escapeTextForBrowser from './escapeTextForBrowser';
import {escapeAttributeValue} from './escapeTextForBrowser';

/**
* Escapes attribute value to prevent scripting attacks.
Expand All @@ -14,7 +14,7 @@ import escapeTextForBrowser from './escapeTextForBrowser';
* @return {string} An escaped string.
*/
function quoteAttributeValueForBrowser(value) {
return '"' + escapeTextForBrowser(value) + '"';
return '"' + escapeAttributeValue(value) + '"';
}

export default quoteAttributeValueForBrowser;