You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
3.9 KiB

2 years ago
/**
* @fileoverview TBD
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');
const report = require('../util/report');
const messages = {
attributeMissing: 'An iframe element is missing a sandbox attribute',
invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"',
invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid',
};
const ALLOWED_VALUES = [
// From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
'',
'allow-downloads-without-user-activation',
'allow-downloads',
'allow-forms',
'allow-modals',
'allow-orientation-lock',
'allow-pointer-lock',
'allow-popups',
'allow-popups-to-escape-sandbox',
'allow-presentation',
'allow-same-origin',
'allow-scripts',
'allow-storage-access-by-user-activation',
'allow-top-navigation',
'allow-top-navigation-by-user-activation',
];
function validateSandboxAttribute(context, node, attribute) {
if (typeof attribute !== 'string') {
// Only string literals are supported for now
return;
}
const values = attribute.split(' ');
let allowScripts = false;
let allowSameOrigin = false;
values.forEach((attributeValue) => {
const trimmedAttributeValue = attributeValue.trim();
if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) {
report(context, messages.invalidValue, 'invalidValue', {
node,
data: {
value: trimmedAttributeValue,
},
});
}
if (trimmedAttributeValue === 'allow-scripts') {
allowScripts = true;
}
if (trimmedAttributeValue === 'allow-same-origin') {
allowSameOrigin = true;
}
});
if (allowScripts && allowSameOrigin) {
report(context, messages.invalidCombination, 'invalidCombination', {
node,
});
}
}
function checkAttributes(context, node) {
let sandboxAttributeFound = false;
node.attributes.forEach((attribute) => {
if (attribute.type === 'JSXAttribute'
&& attribute.name
&& attribute.name.type === 'JSXIdentifier'
&& attribute.name.name === 'sandbox'
) {
sandboxAttributeFound = true;
if (
attribute.value
&& attribute.value.type === 'Literal'
&& attribute.value.value
) {
validateSandboxAttribute(context, node, attribute.value.value);
}
}
});
if (!sandboxAttributeFound) {
report(context, messages.attributeMissing, 'attributeMissing', {
node,
});
}
}
function checkProps(context, node) {
let sandboxAttributeFound = false;
if (node.arguments.length > 1) {
const props = node.arguments[1];
const sandboxProp = props.properties && props.properties.find((x) => x.type === 'Property' && x.key.name === 'sandbox');
if (sandboxProp) {
sandboxAttributeFound = true;
if (sandboxProp.value && sandboxProp.value.type === 'Literal' && sandboxProp.value.value) {
validateSandboxAttribute(context, node, sandboxProp.value.value);
}
}
}
if (!sandboxAttributeFound) {
report(context, messages.attributeMissing, 'attributeMissing', {
node,
});
}
}
module.exports = {
meta: {
docs: {
description: 'Enforce sandbox attribute on iframe elements',
category: 'Best Practices',
recommended: false,
url: docsUrl('iframe-missing-sandbox'),
},
schema: [],
messages,
},
create(context) {
return {
'JSXOpeningElement[name.name="iframe"]'(node) {
checkAttributes(context, node);
},
CallExpression(node) {
if (isCreateElement(node, context) && node.arguments && node.arguments.length > 0) {
const tag = node.arguments[0];
if (tag.type === 'Literal' && tag.value === 'iframe') {
checkProps(context, node);
}
}
},
};
},
};