no-template-target-blank.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * @fileoverview disallow target="_blank" attribute without rel="noopener noreferrer"
  3. * @author Sosukesuzuki
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. // ------------------------------------------------------------------------------
  11. // Helpers
  12. // ------------------------------------------------------------------------------
  13. /** @param {VAttribute} node */
  14. function isTargetBlank(node) {
  15. return (
  16. node.key &&
  17. node.key.name === 'target' &&
  18. node.value &&
  19. node.value.value === '_blank'
  20. )
  21. }
  22. /**
  23. * @param {VStartTag} node
  24. * @param {boolean} allowReferrer
  25. */
  26. function hasSecureRel(node, allowReferrer) {
  27. return node.attributes.some((attr) => {
  28. if (attr.key && attr.key.name === 'rel') {
  29. const tags =
  30. attr.value &&
  31. attr.value.type === 'VLiteral' &&
  32. attr.value.value.toLowerCase().split(' ')
  33. return (
  34. tags &&
  35. tags.includes('noopener') &&
  36. (allowReferrer || tags.includes('noreferrer'))
  37. )
  38. } else {
  39. return false
  40. }
  41. })
  42. }
  43. /**
  44. * @param {VStartTag} node
  45. */
  46. function hasExternalLink(node) {
  47. return node.attributes.some(
  48. (attr) =>
  49. attr.key &&
  50. attr.key.name === 'href' &&
  51. attr.value &&
  52. attr.value.type === 'VLiteral' &&
  53. /^(?:\w+:|\/\/)/.test(attr.value.value)
  54. )
  55. }
  56. /**
  57. * @param {VStartTag} node
  58. */
  59. function hasDynamicLink(node) {
  60. return node.attributes.some(
  61. (attr) =>
  62. attr.key &&
  63. attr.key.type === 'VDirectiveKey' &&
  64. attr.key.name &&
  65. attr.key.name.name === 'bind' &&
  66. attr.key.argument &&
  67. attr.key.argument.type === 'VIdentifier' &&
  68. attr.key.argument.name === 'href'
  69. )
  70. }
  71. // ------------------------------------------------------------------------------
  72. // Rule Definition
  73. // ------------------------------------------------------------------------------
  74. module.exports = {
  75. meta: {
  76. type: 'problem',
  77. docs: {
  78. description:
  79. 'disallow target="_blank" attribute without rel="noopener noreferrer"',
  80. categories: undefined,
  81. url: 'https://eslint.vuejs.org/rules/no-template-target-blank.html'
  82. },
  83. schema: [
  84. {
  85. type: 'object',
  86. properties: {
  87. allowReferrer: {
  88. type: 'boolean'
  89. },
  90. enforceDynamicLinks: {
  91. enum: ['always', 'never']
  92. }
  93. },
  94. additionalProperties: false
  95. }
  96. ]
  97. },
  98. /**
  99. * Creates AST event handlers for no-template-target-blank
  100. *
  101. * @param {RuleContext} context - The rule context.
  102. * @returns {Object} AST event handlers.
  103. */
  104. create(context) {
  105. const configuration = context.options[0] || {}
  106. const allowReferrer = configuration.allowReferrer || false
  107. const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'
  108. return utils.defineTemplateBodyVisitor(context, {
  109. /** @param {VAttribute} node */
  110. 'VAttribute[directive=false]'(node) {
  111. if (!isTargetBlank(node) || hasSecureRel(node.parent, allowReferrer)) {
  112. return
  113. }
  114. const hasDangerHref =
  115. hasExternalLink(node.parent) ||
  116. (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))
  117. if (hasDangerHref) {
  118. context.report({
  119. node,
  120. message:
  121. 'Using target="_blank" without rel="noopener noreferrer" is a security risk.'
  122. })
  123. }
  124. }
  125. })
  126. }
  127. }