preprocessor2.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. "use strict";
  2. const acorn = require("acorn");
  3. const escodegen = require("@javascript-obfuscator/escodegen");
  4. const vm = require("vm");
  5. const fs = require("fs");
  6. const path = require("path");
  7. const PDFJS_PREPROCESSOR_NAME = "PDFJSDev";
  8. const ROOT_PREFIX = "$ROOT/";
  9. const ACORN_ECMA_VERSION = 2022;
  10. function isLiteral(obj, value) {
  11. return obj.type === "Literal" && obj.value === value;
  12. }
  13. function isPDFJSPreprocessor(obj) {
  14. return obj.type === "Identifier" && obj.name === PDFJS_PREPROCESSOR_NAME;
  15. }
  16. function evalWithDefines(code, defines, loc) {
  17. if (!code || !code.trim()) {
  18. throw new Error("No JavaScript expression given");
  19. }
  20. return vm.runInNewContext(code, defines, { displayErrors: false });
  21. }
  22. function handlePreprocessorAction(ctx, actionName, args, loc) {
  23. try {
  24. let arg;
  25. switch (actionName) {
  26. case "test":
  27. arg = args[0];
  28. if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
  29. throw new Error("No code for testing is given");
  30. }
  31. const isTrue = !!evalWithDefines(arg.value, ctx.defines);
  32. return { type: "Literal", value: isTrue, loc };
  33. case "eval":
  34. arg = args[0];
  35. if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
  36. throw new Error("No code for eval is given");
  37. }
  38. const result = evalWithDefines(arg.value, ctx.defines);
  39. if (
  40. typeof result === "boolean" ||
  41. typeof result === "string" ||
  42. typeof result === "number"
  43. ) {
  44. return { type: "Literal", value: result, loc };
  45. }
  46. if (typeof result === "object") {
  47. const parsedObj = acorn.parse("(" + JSON.stringify(result) + ")", {
  48. ecmaVersion: ACORN_ECMA_VERSION,
  49. });
  50. parsedObj.body[0].expression.loc = loc;
  51. return parsedObj.body[0].expression;
  52. }
  53. break;
  54. case "json":
  55. arg = args[0];
  56. if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
  57. throw new Error("Path to JSON is not provided");
  58. }
  59. let jsonPath = arg.value;
  60. if (jsonPath.indexOf(ROOT_PREFIX) === 0) {
  61. jsonPath = path.join(
  62. ctx.rootPath,
  63. jsonPath.substring(ROOT_PREFIX.length)
  64. );
  65. }
  66. const jsonContent = fs.readFileSync(jsonPath).toString();
  67. const parsedJSON = acorn.parse("(" + jsonContent + ")", {
  68. ecmaVersion: ACORN_ECMA_VERSION,
  69. });
  70. parsedJSON.body[0].expression.loc = loc;
  71. return parsedJSON.body[0].expression;
  72. }
  73. throw new Error("Unsupported action");
  74. } catch (e) {
  75. throw new Error(
  76. "Could not process " +
  77. PDFJS_PREPROCESSOR_NAME +
  78. "." +
  79. actionName +
  80. " at " +
  81. JSON.stringify(loc) +
  82. "\n" +
  83. e.name +
  84. ": " +
  85. e.message
  86. );
  87. }
  88. }
  89. function postprocessNode(ctx, node) {
  90. switch (node.type) {
  91. case "ExportNamedDeclaration":
  92. case "ImportDeclaration":
  93. if (
  94. node.source &&
  95. node.source.type === "Literal" &&
  96. ctx.map &&
  97. ctx.map[node.source.value]
  98. ) {
  99. const newValue = ctx.map[node.source.value];
  100. node.source.value = node.source.raw = newValue;
  101. }
  102. break;
  103. case "IfStatement":
  104. if (isLiteral(node.test, true)) {
  105. // if (true) stmt1; => stmt1
  106. return node.consequent;
  107. } else if (isLiteral(node.test, false)) {
  108. // if (false) stmt1; else stmt2; => stmt2
  109. return node.alternate || { type: "EmptyStatement", loc: node.loc };
  110. }
  111. break;
  112. case "ConditionalExpression":
  113. if (isLiteral(node.test, true)) {
  114. // true ? stmt1 : stmt2 => stmt1
  115. return node.consequent;
  116. } else if (isLiteral(node.test, false)) {
  117. // false ? stmt1 : stmt2 => stmt2
  118. return node.alternate;
  119. }
  120. break;
  121. case "UnaryExpression":
  122. if (node.operator === "typeof" && isPDFJSPreprocessor(node.argument)) {
  123. // typeof PDFJSDev => 'object'
  124. return { type: "Literal", value: "object", loc: node.loc };
  125. }
  126. if (
  127. node.operator === "!" &&
  128. node.argument.type === "Literal" &&
  129. typeof node.argument.value === "boolean"
  130. ) {
  131. // !true => false, !false => true
  132. return { type: "Literal", value: !node.argument.value, loc: node.loc };
  133. }
  134. break;
  135. case "LogicalExpression":
  136. switch (node.operator) {
  137. case "&&":
  138. if (isLiteral(node.left, true)) {
  139. return node.right;
  140. }
  141. if (isLiteral(node.left, false)) {
  142. return node.left;
  143. }
  144. break;
  145. case "||":
  146. if (isLiteral(node.left, true)) {
  147. return node.left;
  148. }
  149. if (isLiteral(node.left, false)) {
  150. return node.right;
  151. }
  152. break;
  153. }
  154. break;
  155. case "BinaryExpression":
  156. switch (node.operator) {
  157. case "==":
  158. case "===":
  159. case "!=":
  160. case "!==":
  161. if (
  162. node.left.type === "Literal" &&
  163. node.right.type === "Literal" &&
  164. typeof node.left.value === typeof node.right.value
  165. ) {
  166. // folding two literals == and != check
  167. switch (typeof node.left.value) {
  168. case "string":
  169. case "boolean":
  170. case "number":
  171. const equal = node.left.value === node.right.value;
  172. return {
  173. type: "Literal",
  174. value: (node.operator[0] === "=") === equal,
  175. loc: node.loc,
  176. };
  177. }
  178. }
  179. break;
  180. }
  181. break;
  182. case "CallExpression":
  183. if (
  184. node.callee.type === "MemberExpression" &&
  185. isPDFJSPreprocessor(node.callee.object) &&
  186. node.callee.property.type === "Identifier"
  187. ) {
  188. // PDFJSDev.xxxx(arg1, arg2, ...) => transform
  189. const action = node.callee.property.name;
  190. return handlePreprocessorAction(ctx, action, node.arguments, node.loc);
  191. }
  192. // require('string')
  193. if (
  194. node.callee.type === "Identifier" &&
  195. node.callee.name === "require" &&
  196. node.arguments.length === 1 &&
  197. node.arguments[0].type === "Literal" &&
  198. ctx.map &&
  199. ctx.map[node.arguments[0].value]
  200. ) {
  201. const requireName = node.arguments[0];
  202. requireName.value = requireName.raw = ctx.map[requireName.value];
  203. }
  204. break;
  205. case "BlockStatement":
  206. let subExpressionIndex = 0;
  207. while (subExpressionIndex < node.body.length) {
  208. switch (node.body[subExpressionIndex].type) {
  209. case "EmptyStatement":
  210. // Removing empty statements from the blocks.
  211. node.body.splice(subExpressionIndex, 1);
  212. continue;
  213. case "BlockStatement":
  214. // Block statements inside a block are moved to the parent one.
  215. const subChildren = node.body[subExpressionIndex].body;
  216. Array.prototype.splice.apply(node.body, [
  217. subExpressionIndex,
  218. 1,
  219. ...subChildren,
  220. ]);
  221. subExpressionIndex += Math.max(subChildren.length - 1, 0);
  222. continue;
  223. case "ReturnStatement":
  224. case "ThrowStatement":
  225. // Removing dead code after return or throw.
  226. node.body.splice(
  227. subExpressionIndex + 1,
  228. node.body.length - subExpressionIndex - 1
  229. );
  230. break;
  231. }
  232. subExpressionIndex++;
  233. }
  234. break;
  235. case "FunctionDeclaration":
  236. case "FunctionExpression":
  237. const block = node.body;
  238. if (
  239. block.body.length > 0 &&
  240. block.body[block.body.length - 1].type === "ReturnStatement" &&
  241. !block.body[block.body.length - 1].argument
  242. ) {
  243. // Function body ends with return without arg -- removing it.
  244. block.body.pop();
  245. }
  246. break;
  247. }
  248. return node;
  249. }
  250. function fixComments(ctx, node) {
  251. if (!ctx.saveComments) {
  252. return;
  253. }
  254. // Fixes double comments in the escodegen output.
  255. delete node.trailingComments;
  256. // Removes ESLint and other service comments.
  257. if (node.leadingComments) {
  258. const CopyrightRegExp = /\bcopyright\b/i;
  259. const BlockCommentRegExp = /^\s*(globals|eslint|falls through)\b/;
  260. const LineCommentRegExp = /^\s*eslint\b/;
  261. let i = 0;
  262. while (i < node.leadingComments.length) {
  263. const type = node.leadingComments[i].type;
  264. const value = node.leadingComments[i].value;
  265. if (ctx.saveComments === "copyright") {
  266. // Remove all comments, except Copyright notices and License headers.
  267. if (!(type === "Block" && CopyrightRegExp.test(value))) {
  268. node.leadingComments.splice(i, 1);
  269. continue;
  270. }
  271. } else if (
  272. (type === "Block" && BlockCommentRegExp.test(value)) ||
  273. (type === "Line" && LineCommentRegExp.test(value))
  274. ) {
  275. node.leadingComments.splice(i, 1);
  276. continue;
  277. }
  278. i++;
  279. }
  280. }
  281. }
  282. function traverseTree(ctx, node) {
  283. // generic node processing
  284. for (const i in node) {
  285. const child = node[i];
  286. if (typeof child === "object" && child !== null && child.type) {
  287. const result = traverseTree(ctx, child);
  288. if (result !== child) {
  289. node[i] = result;
  290. }
  291. } else if (Array.isArray(child)) {
  292. child.forEach(function (childItem, index) {
  293. if (
  294. typeof childItem === "object" &&
  295. childItem !== null &&
  296. childItem.type
  297. ) {
  298. const result = traverseTree(ctx, childItem);
  299. if (result !== childItem) {
  300. child[index] = result;
  301. }
  302. }
  303. });
  304. }
  305. }
  306. node = postprocessNode(ctx, node) || node;
  307. fixComments(ctx, node);
  308. return node;
  309. }
  310. function preprocessPDFJSCode(ctx, code) {
  311. const format = ctx.format || {
  312. indent: {
  313. style: " ",
  314. },
  315. };
  316. const parseOptions = {
  317. ecmaVersion: ACORN_ECMA_VERSION,
  318. locations: true,
  319. sourceFile: ctx.sourceFile,
  320. sourceType: "module",
  321. };
  322. const codegenOptions = {
  323. format,
  324. parse(input) {
  325. return acorn.parse(input, { ecmaVersion: ACORN_ECMA_VERSION });
  326. },
  327. sourceMap: ctx.sourceMap,
  328. sourceMapWithCode: ctx.sourceMap,
  329. };
  330. const syntax = acorn.parse(code, parseOptions);
  331. traverseTree(ctx, syntax);
  332. return escodegen.generate(syntax, codegenOptions);
  333. }
  334. exports.preprocessPDFJSCode = preprocessPDFJSCode;