jsx-no-script-url.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * @fileoverview Prevent usage of `javascript:` URLs
  3. * @author Sergei Startsev
  4. */
  5. 'use strict';
  6. const includes = require('array-includes');
  7. const docsUrl = require('../util/docsUrl');
  8. const linkComponentsUtil = require('../util/linkComponents');
  9. const report = require('../util/report');
  10. // ------------------------------------------------------------------------------
  11. // Rule Definition
  12. // ------------------------------------------------------------------------------
  13. // https://github.com/facebook/react/blob/d0ebde77f6d1232cefc0da184d731943d78e86f2/packages/react-dom/src/shared/sanitizeURL.js#L30
  14. /* eslint-disable-next-line max-len, no-control-regex */
  15. 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;
  16. function hasJavaScriptProtocol(attr) {
  17. return attr.value && attr.value.type === 'Literal'
  18. && isJavaScriptProtocol.test(attr.value.value);
  19. }
  20. function shouldVerifyProp(node, config) {
  21. const name = node.name && node.name.name;
  22. const parentName = node.parent.name && node.parent.name.name;
  23. if (!name || !parentName || !config.has(parentName)) return false;
  24. const attributes = config.get(parentName);
  25. return includes(attributes, name);
  26. }
  27. function parseLegacyOption(config, option) {
  28. option.forEach((opt) => {
  29. config.set(opt.name, opt.props);
  30. });
  31. }
  32. const messages = {
  33. 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.',
  34. };
  35. /** @type {import('eslint').Rule.RuleModule} */
  36. module.exports = {
  37. meta: {
  38. docs: {
  39. description: 'Disallow usage of `javascript:` URLs',
  40. category: 'Best Practices',
  41. recommended: false,
  42. url: docsUrl('jsx-no-script-url'),
  43. },
  44. messages,
  45. schema: {
  46. anyOf: [
  47. {
  48. type: 'array',
  49. items: [
  50. {
  51. type: 'array',
  52. uniqueItems: true,
  53. items: {
  54. type: 'object',
  55. properties: {
  56. name: {
  57. type: 'string',
  58. },
  59. props: {
  60. type: 'array',
  61. items: {
  62. type: 'string',
  63. uniqueItems: true,
  64. },
  65. },
  66. },
  67. required: ['name', 'props'],
  68. additionalProperties: false,
  69. },
  70. },
  71. {
  72. type: 'object',
  73. properties: {
  74. includeFromSettings: {
  75. type: 'boolean',
  76. },
  77. },
  78. additionalItems: false,
  79. },
  80. ],
  81. additionalItems: false,
  82. },
  83. {
  84. type: 'array',
  85. items: [
  86. {
  87. type: 'object',
  88. properties: {
  89. includeFromSettings: {
  90. type: 'boolean',
  91. },
  92. },
  93. additionalItems: false,
  94. },
  95. ],
  96. additionalItems: false,
  97. },
  98. ],
  99. },
  100. },
  101. create(context) {
  102. const options = context.options;
  103. const hasLegacyOption = Array.isArray(options[0]);
  104. const legacyOptions = hasLegacyOption ? options[0] : [];
  105. // eslint-disable-next-line no-nested-ternary
  106. const objectOption = (hasLegacyOption && options.length > 1)
  107. ? options[1]
  108. : (options.length > 0
  109. ? options[0]
  110. : {
  111. includeFromSettings: false,
  112. }
  113. );
  114. const includeFromSettings = objectOption.includeFromSettings;
  115. const linkComponents = linkComponentsUtil.getLinkComponents(includeFromSettings ? context : {});
  116. parseLegacyOption(linkComponents, legacyOptions);
  117. return {
  118. JSXAttribute(node) {
  119. if (shouldVerifyProp(node, linkComponents) && hasJavaScriptProtocol(node)) {
  120. report(context, messages.noScriptURL, 'noScriptURL', {
  121. node,
  122. });
  123. }
  124. },
  125. };
  126. },
  127. };