Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop runtime validation and lower case coercion of tag names #8563

Merged
merged 3 commits into from
Dec 13, 2016
Merged
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
3 changes: 3 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should handle dangerouslySetInnerHTML
* should work error event on <source> element
* should not duplicate uppercased selfclosing tags
* should warn on upper case HTML tags, not SVG nor custom tags
* should warn against children for void elements
* should warn against dangerouslySetInnerHTML for void elements
* should emit a warning once for an unnamed custom component using shady DOM
Expand All @@ -663,6 +664,8 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should properly escape text content and attributes values
* unmounts children before unsetting DOM node info
* should warn about the `onScroll` issue when unsupported (IE8)
* should throw when an invalid tag name is used server-side
* should throw when an attack vector is used server-side
* should throw when an invalid tag name is used
* should throw when an attack vector is used
* should warn about props that are no longer supported
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/art/ReactARTFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ const ARTRenderer = ReactFiberReconciler({
// Noop
},

commitUpdate(instance, oldProps, newProps) {
commitUpdate(instance, type, oldProps, newProps) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @bvaughn For ART changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! This also eliminates the need for ReactNativeFiber renderer to bundle along the viewConfig with each instance as well.

instance._applyProps(instance, newProps, oldProps);
},

Expand Down Expand Up @@ -467,7 +467,7 @@ const ARTRenderer = ReactFiberReconciler({
// Noop
},

prepareUpdate(domElement, oldProps, newProps) {
prepareUpdate(domElement, type, oldProps, newProps) {
return true;
},

Expand Down
19 changes: 5 additions & 14 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,16 @@ var DOMRenderer = ReactFiberReconciler({

finalizeInitialChildren(
domElement : Instance,
type : string,
props : Props,
rootContainerInstance : Container,
) : void {
// TODO: we normalize here because DOM renderer expects tag to be lowercase.
// We can change DOM renderer to compare special case against upper case,
// and use tagName (which is upper case for HTML DOM elements). Or we could
// let the renderer "normalize" the fiber type so we don't have to read
// the type from DOM. However we need to remember SVG is case-sensitive.
var tag = domElement.tagName.toLowerCase();
setInitialProperties(domElement, tag, props, rootContainerInstance);
setInitialProperties(domElement, type, props, rootContainerInstance);
},

prepareUpdate(
domElement : Instance,
type : string,
oldProps : Props,
newProps : Props
) : boolean {
Expand All @@ -119,21 +115,16 @@ var DOMRenderer = ReactFiberReconciler({

commitUpdate(
domElement : Instance,
type : string,
oldProps : Props,
newProps : Props,
rootContainerInstance : Container,
internalInstanceHandle : Object,
) : void {
// TODO: we normalize here because DOM renderer expects tag to be lowercase.
// We can change DOM renderer to compare special case against upper case,
// and use tagName (which is upper case for HTML DOM elements). Or we could
// let the renderer "normalize" the fiber type so we don't have to read
// the type from DOM. However we need to remember SVG is case-sensitive.
var tag = domElement.tagName.toLowerCase();
// Update the internal instance handle so that we know which props are
// the current ones.
precacheFiberNode(internalInstanceHandle, domElement);
updateProperties(domElement, tag, oldProps, newProps, rootContainerInstance);
updateProperties(domElement, type, oldProps, newProps, rootContainerInstance);
},

shouldSetTextContent(props : Props) : boolean {
Expand Down
32 changes: 11 additions & 21 deletions src/renderers/dom/fiber/ReactDOMFiberComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,21 +279,6 @@ var voidElementTags = {
...omittedCloseTags,
};

// We accept any tag to be rendered but since this gets injected into arbitrary
// HTML, we want to make sure that it's a safe tag.
// http://www.w3.org/TR/REC-xml/#NT-Name

var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset
var validatedTagCache = {};
var hasOwnProperty = {}.hasOwnProperty;

function validateDangerousTag(tag) {
if (!hasOwnProperty.call(validatedTagCache, tag)) {
invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag);
validatedTagCache[tag] = true;
}
}

function isCustomComponent(tagName, props) {
return tagName.indexOf('-') >= 0 || props.is != null;
}
Expand Down Expand Up @@ -488,18 +473,23 @@ var ReactDOMFiberComponent = {
rootContainerElement : Element,
parentNamespace : string | null
) : Element {
validateDangerousTag(type);
// TODO:
// const tag = type.toLowerCase(); Do we need to apply lower case only on non-custom elements?

// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var ownerDocument = rootContainerElement.ownerDocument;
var domElement : Element;
var namespaceURI = parentNamespace || getIntrinsicNamespace(type);
if (namespaceURI == null) {
const tag = type.toLowerCase();
if (tag === 'script') {
if (__DEV__) {
warning(
type === type.toLowerCase() ||
isCustomComponent(type, props),
'<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
'in React.',
type
);
}

if (type === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument.createElement('div');
Expand Down
61 changes: 42 additions & 19 deletions src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

describe('ReactDOMComponent', () => {
var React;
var ReactTestUtils;
var ReactDOM;
var ReactDOMFeatureFlags;
var ReactDOMServer;
Expand All @@ -29,16 +30,11 @@ describe('ReactDOMComponent', () => {
ReactDOM = require('ReactDOM');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactDOMServer = require('ReactDOMServer');
ReactTestUtils = require('ReactTestUtils');
inputValueTracking = require('inputValueTracking');
});

describe('updateDOM', () => {
var ReactTestUtils;

beforeEach(() => {
ReactTestUtils = require('ReactTestUtils');
});

it('should handle className', () => {
var container = document.createElement('div');
ReactDOM.render(<div style={{}} />, container);
Expand Down Expand Up @@ -795,6 +791,7 @@ describe('ReactDOMComponent', () => {
});

it('should not duplicate uppercased selfclosing tags', () => {
spyOn(console, 'error');
class Container extends React.Component {
render() {
return React.createElement('BR', null);
Expand All @@ -803,6 +800,27 @@ describe('ReactDOMComponent', () => {

var returnedValue = ReactDOMServer.renderToString(<Container/>);
expect(returnedValue).not.toContain('</BR>');
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toContain(
'<BR /> is using uppercase HTML.'
);
});

it('should warn on upper case HTML tags, not SVG nor custom tags', () => {
spyOn(console, 'error');
ReactTestUtils.renderIntoDocument(
React.createElement('svg', null, React.createElement('PATH'))
);
expectDev(console.error.calls.count()).toBe(0);
ReactTestUtils.renderIntoDocument(
React.createElement('CUSTOM-TAG')
);
expectDev(console.error.calls.count()).toBe(0);
ReactTestUtils.renderIntoDocument(React.createElement('IMG'));
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toContain(
'<IMG /> is using uppercase HTML.'
);
});

it('should warn against children for void elements', () => {
Expand Down Expand Up @@ -1171,8 +1189,7 @@ describe('ReactDOMComponent', () => {
.mock('isEventSupported');
var isEventSupported = require('isEventSupported');
isEventSupported.mockReturnValueOnce(false);

var ReactTestUtils = require('ReactTestUtils');
ReactTestUtils = require('ReactTestUtils');

spyOn(console, 'error');
ReactTestUtils.renderIntoDocument(<div onScroll={function() {}} />);
Expand All @@ -1190,34 +1207,40 @@ describe('ReactDOMComponent', () => {
});

describe('tag sanitization', () => {
it('should throw when an invalid tag name is used', () => {
var ReactTestUtils = require('ReactTestUtils');
it('should throw when an invalid tag name is used server-side', () => {
var hackzor = React.createElement('script tag');
expect(
() => ReactTestUtils.renderIntoDocument(hackzor)
() => ReactDOMServer.renderToString(hackzor)
).toThrowError(
'Invalid tag: script tag'
);
});

it('should throw when an attack vector is used', () => {
var ReactTestUtils = require('ReactTestUtils');
it('should throw when an attack vector is used server-side', () => {
var hackzor = React.createElement('div><img /><div');
expect(
() => ReactTestUtils.renderIntoDocument(hackzor)
() => ReactDOMServer.renderToString(hackzor)
).toThrowError(
'Invalid tag: div><img /><div'
);
});
});

describe('nesting validation', () => {
var ReactTestUtils;
it('should throw when an invalid tag name is used', () => {
var hackzor = React.createElement('script tag');
expect(
() => ReactTestUtils.renderIntoDocument(hackzor)
).toThrow();
});

beforeEach(() => {
ReactTestUtils = require('ReactTestUtils');
it('should throw when an attack vector is used', () => {
var hackzor = React.createElement('div><img /><div');
expect(
() => ReactTestUtils.renderIntoDocument(hackzor)
).toThrow();
});
});

describe('nesting validation', () => {
it('warns on invalid nesting', () => {
spyOn(console, 'error');
ReactTestUtils.renderIntoDocument(<div><tr /><tr /></div>);
Expand Down
11 changes: 10 additions & 1 deletion src/renderers/dom/stack/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,6 @@ var globalIdCounter = 1;
*/
function ReactDOMComponent(element) {
var tag = element.type;
validateDangerousTag(tag);
this._currentElement = element;
this._tag = tag.toLowerCase();
this._namespaceURI = null;
Expand Down Expand Up @@ -524,6 +523,15 @@ ReactDOMComponent.Mixin = {
namespaceURI = DOMNamespaces.html;
}
if (namespaceURI === DOMNamespaces.html) {
if (__DEV__) {
warning(
isCustomComponent(this._tag, props) ||
this._tag === this._currentElement.type,
'<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
'in React.',
this._currentElement.type
);
}
if (this._tag === 'svg') {
namespaceURI = DOMNamespaces.svg;
} else if (this._tag === 'math') {
Expand Down Expand Up @@ -596,6 +604,7 @@ ReactDOMComponent.Mixin = {
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;
} else {
validateDangerousTag(this._tag);
var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
var tagContent = this._createContentMarkup(transaction, props, context);
if (!tagContent && omittedCloseTags[this._tag]) {
Expand Down
6 changes: 3 additions & 3 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ var NoopRenderer = ReactFiberReconciler({
parentInstance.children.push(child);
},

finalizeInitialChildren(domElement : Instance, props : Props) : void {
finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
// Noop
},

prepareUpdate(instance : Instance, oldProps : Props, newProps : Props) : boolean {
prepareUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : boolean {
return true;
},

commitUpdate(instance : Instance, oldProps : Props, newProps : Props) : void {
commitUpdate(instance : Instance, type : string, oldProps : Props, newProps : Props) : void {
instance.prop = newProps.prop;
},

Expand Down
3 changes: 2 additions & 1 deletion src/renderers/shared/fiber/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ module.exports = function<T, P, I, TI, C, CX>(
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const rootContainerInstance = getRootHostContainer();
commitUpdate(instance, oldProps, newProps, rootContainerInstance, finishedWork);
const type = finishedWork.type;
commitUpdate(instance, type, oldProps, newProps, rootContainerInstance, finishedWork);
}
detachRefIfNeeded(current, finishedWork);
return;
Expand Down
7 changes: 4 additions & 3 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ module.exports = function<T, P, I, TI, C, CX>(
}
case HostComponent:
popHostContext(workInProgress);
const type = workInProgress.type;
let newProps = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
// If we have an alternate, that means this is an update and we need to
Expand All @@ -228,7 +229,7 @@ module.exports = function<T, P, I, TI, C, CX>(
newProps = workInProgress.memoizedProps || oldProps;
}
const instance : I = workInProgress.stateNode;
if (prepareUpdate(instance, oldProps, newProps)) {
if (prepareUpdate(instance, type, oldProps, newProps)) {
// This returns true if there was something to update.
markUpdate(workInProgress);
}
Expand All @@ -249,14 +250,14 @@ module.exports = function<T, P, I, TI, C, CX>(
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
const instance = createInstance(
workInProgress.type,
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress
);
appendAllChildren(instance, workInProgress);
finalizeInitialChildren(instance, newProps, rootContainerInstance);
finalizeInitialChildren(instance, type, newProps, rootContainerInstance);

workInProgress.stateNode = instance;
if (workInProgress.ref) {
Expand Down
6 changes: 3 additions & 3 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ export type HostConfig<T, P, I, TI, C, CX> = {

createInstance(type : T, props : P, rootContainerInstance : C, hostContext : CX | null, internalInstanceHandle : OpaqueNode) : I,
appendInitialChild(parentInstance : I, child : I | TI) : void,
finalizeInitialChildren(parentInstance : I, props : P, rootContainerInstance : C) : void,
finalizeInitialChildren(parentInstance : I, type : T, props : P, rootContainerInstance : C) : void,

prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
commitUpdate(instance : I, oldProps : P, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
prepareUpdate(instance : I, type : T, oldProps : P, newProps : P) : boolean,
commitUpdate(instance : I, type : T, oldProps : P, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,

shouldSetTextContent(props : P) : boolean,
resetTextContent(instance : I) : void,
Expand Down