jsx-no-script-url.js 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /**
  2. * @fileoverview Prevent usage of `javascript:` URLs
  3. * @author Sergei Startsev
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const report = require('../util/report');
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. // https://github.com/facebook/react/blob/d0ebde77f6d1232cefc0da184d731943d78e86f2/packages/react-dom/src/shared/sanitizeURL.js#L30
  12. /* eslint-disable-next-line max-len, no-control-regex */
  13. const isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
  14. function hasJavaScriptProtocol(attr) {
  15. return attr.value && attr.value.type === 'Literal'
  16. && isJavaScriptProtocol.test(attr.value.value);
  17. }
  18. function shouldVerifyElement(node, config) {
  19. const name = node.name && node.name.name;
  20. return name === 'a' || config.find((i) => i.name === name);
  21. }
  22. function shouldVerifyProp(node, config) {
  23. const name = node.name && node.name.name;
  24. const parentName = node.parent.name && node.parent.name.name;
  25. if (parentName === 'a' && name === 'href') {
  26. return true;
  27. }
  28. const el = config.find((i) => i.name === parentName);
  29. if (!el) {
  30. return false;
  31. }
  32. const props = el.props || [];
  33. return node.name && props.indexOf(name) !== -1;
  34. }
  35. const messages = {
  36. noScriptURL: 'A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML, try using dangerouslySetInnerHTML instead.',
  37. };
  38. module.exports = {
  39. meta: {
  40. docs: {
  41. description: 'Disallow usage of `javascript:` URLs',
  42. category: 'Best Practices',
  43. recommended: false,
  44. url: docsUrl('jsx-no-script-url'),
  45. },
  46. messages,
  47. schema: [{
  48. type: 'array',
  49. uniqueItems: true,
  50. items: {
  51. type: 'object',
  52. properties: {
  53. name: {
  54. type: 'string',
  55. },
  56. props: {
  57. type: 'array',
  58. items: {
  59. type: 'string',
  60. uniqueItems: true,
  61. },
  62. },
  63. },
  64. required: ['name', 'props'],
  65. additionalProperties: false,
  66. },
  67. }],
  68. },
  69. create(context) {
  70. const config = context.options[0] || [];
  71. return {
  72. JSXAttribute(node) {
  73. const parent = node.parent;
  74. if (shouldVerifyElement(parent, config) && shouldVerifyProp(node, config) && hasJavaScriptProtocol(node)) {
  75. report(context, messages.noScriptURL, 'noScriptURL', {
  76. node,
  77. });
  78. }
  79. },
  80. };
  81. },
  82. };