EvaCallExpressionTransformer.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import { inject, injectable, } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import * as ESTree from 'estree';
  4. import jsStringEscape from 'js-string-escape';
  5. import { IOptions } from '../../interfaces/options/IOptions';
  6. import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
  7. import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
  8. import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
  9. import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
  10. import { NodeFactory } from '../../node/NodeFactory';
  11. import { NodeGuards } from '../../node/NodeGuards';
  12. import { NodeUtils } from '../../node/NodeUtils';
  13. @injectable()
  14. export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
  15. /**
  16. * @type {Set <FunctionExpression>}
  17. */
  18. private readonly evalRootAstHostNodeSet: Set <ESTree.FunctionExpression> = new Set();
  19. /**
  20. * @param {IRandomGenerator} randomGenerator
  21. * @param {IOptions} options
  22. */
  23. constructor (
  24. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  25. @inject(ServiceIdentifiers.IOptions) options: IOptions
  26. ) {
  27. super(randomGenerator, options);
  28. }
  29. /**
  30. * @param {Expression | SpreadElement} node
  31. * @returns {string | null}
  32. */
  33. private static extractEvalStringFromCallExpressionArgument (node: ESTree.Expression | ESTree.SpreadElement): string | null {
  34. if (NodeGuards.isLiteralNode(node)) {
  35. return EvalCallExpressionTransformer
  36. .extractEvalStringFromLiteralNode(node);
  37. }
  38. if (NodeGuards.isTemplateLiteralNode(node)) {
  39. return EvalCallExpressionTransformer
  40. .extractEvalStringFromTemplateLiteralNode(node);
  41. }
  42. return null;
  43. }
  44. /**
  45. * @param {Literal} node
  46. * @returns {string | null}
  47. */
  48. private static extractEvalStringFromLiteralNode (node: ESTree.Literal): string | null {
  49. return typeof node.value === 'string' ? node.value : null;
  50. }
  51. /**
  52. * @param {TemplateLiteral} node
  53. * @returns {string | null}
  54. */
  55. private static extractEvalStringFromTemplateLiteralNode (node: ESTree.TemplateLiteral): string | null {
  56. const quasis: ESTree.TemplateElement[] = node.quasis;
  57. const allowedQuasisLength: number = 1;
  58. if (quasis.length !== allowedQuasisLength || node.expressions.length) {
  59. return null;
  60. }
  61. return quasis[0].value.cooked;
  62. }
  63. /**
  64. * @param {TransformationStage} transformationStage
  65. * @returns {IVisitor | null}
  66. */
  67. public getVisitor (transformationStage: TransformationStage): IVisitor | null {
  68. switch (transformationStage) {
  69. case TransformationStage.Preparing:
  70. return {
  71. enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
  72. if (
  73. parentNode
  74. && NodeGuards.isCallExpressionNode(node)
  75. && NodeGuards.isIdentifierNode(node.callee)
  76. && node.callee.name === 'eval'
  77. ) {
  78. return this.transformNode(node, parentNode);
  79. }
  80. }
  81. };
  82. case TransformationStage.Finalizing:
  83. if (!this.evalRootAstHostNodeSet.size) {
  84. return null;
  85. }
  86. return {
  87. leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
  88. if (parentNode && this.isEvalRootAstHostNode(node)) {
  89. return this.restoreNode(node, parentNode);
  90. }
  91. }
  92. };
  93. default:
  94. return null;
  95. }
  96. }
  97. /**
  98. * @param {CallExpression} callExpressionNode
  99. * @param {Node} parentNode
  100. * @returns {Node}
  101. */
  102. public transformNode (callExpressionNode: ESTree.CallExpression, parentNode: ESTree.Node): ESTree.Node {
  103. const callExpressionFirstArgument: ESTree.Expression | ESTree.SpreadElement = callExpressionNode.arguments[0];
  104. if (!callExpressionFirstArgument) {
  105. return callExpressionNode;
  106. }
  107. const evalString: string | null = EvalCallExpressionTransformer
  108. .extractEvalStringFromCallExpressionArgument(callExpressionFirstArgument);
  109. if (!evalString) {
  110. return callExpressionNode;
  111. }
  112. let ast: ESTree.Statement[];
  113. // wrapping into try-catch to prevent parsing of incorrect `eval` string
  114. try {
  115. ast = NodeUtils.convertCodeToStructure(evalString);
  116. } catch {
  117. return callExpressionNode;
  118. }
  119. /**
  120. * we should wrap AST-tree into the parent function expression node (ast root host node).
  121. * This function expression node will help to correctly transform AST-tree.
  122. */
  123. const evalRootAstHostNode: ESTree.FunctionExpression = NodeFactory
  124. .functionExpressionNode([], NodeFactory.blockStatementNode(ast));
  125. /**
  126. * we should store that host node and then extract AST-tree on the `finalizing` stage
  127. */
  128. this.evalRootAstHostNodeSet.add(evalRootAstHostNode);
  129. return evalRootAstHostNode;
  130. }
  131. /**
  132. * @param {FunctionExpression} evalRootAstHostNode
  133. * @param {Node} parentNode
  134. * @returns {Node}
  135. */
  136. public restoreNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
  137. const targetAst: ESTree.Statement[] = evalRootAstHostNode.body.body;
  138. const obfuscatedCode: string = NodeUtils.convertStructureToCode(targetAst);
  139. return NodeFactory.callExpressionNode(
  140. NodeFactory.identifierNode('eval'),
  141. [
  142. NodeFactory.literalNode(jsStringEscape(obfuscatedCode))
  143. ]
  144. );
  145. }
  146. /**
  147. * @param {Node} node
  148. * @returns {boolean}
  149. */
  150. private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
  151. return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeSet.has(node);
  152. }
  153. }