JavaScriptObfuscator.spec.ts 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180
  1. import { assert } from 'chai';
  2. import { TypeFromEnum } from '@gradecam/tsenum';
  3. import { TDictionary } from '../../../src/types/TDictionary';
  4. import { TInputOptions } from '../../../src/types/options/TInputOptions';
  5. import { TOptionsPreset } from '../../../src/types/options/TOptionsPreset';
  6. import { IObfuscatedCode } from '../../../src/interfaces/source-code/IObfuscatedCode';
  7. import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
  8. import { StringArrayEncoding } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
  9. import { StringArrayWrappersType } from '../../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
  10. import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscatorFacade';
  11. import { HIGH_OBFUSCATION_PRESET } from '../../../src/options/presets/HighObfuscation';
  12. import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
  13. import { IdentifierNamesGenerator } from '../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
  14. import { OptionsPreset } from '../../../src/enums/options/presets/OptionsPreset';
  15. import { buildLargeCode } from '../../helpers/buildLargeCode';
  16. import { getRegExpMatch } from '../../helpers/getRegExpMatch';
  17. import { readFileAsString } from '../../helpers/readFileAsString';
  18. describe('JavaScriptObfuscator', () => {
  19. describe('obfuscate', () => {
  20. describe('correct source code', () => {
  21. let obfuscatedCode: string,
  22. sourceMap: string;
  23. beforeEach(() => {
  24. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  25. const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
  26. code,
  27. {
  28. ...NO_ADDITIONAL_NODES_PRESET
  29. }
  30. );
  31. obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
  32. sourceMap = obfuscatedCodeObject.getSourceMap();
  33. });
  34. it('should return correct obfuscated code', () => {
  35. assert.isOk(obfuscatedCode);
  36. });
  37. it('should return empty source map', () => {
  38. assert.isNotOk(sourceMap);
  39. });
  40. });
  41. describe('Empty or invalid source code', () => {
  42. describe('empty source code', () => {
  43. let obfuscatedCode: string;
  44. beforeEach(() => {
  45. const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
  46. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  47. code,
  48. ).getObfuscatedCode();
  49. });
  50. it('should return an empty obfuscated code', () => {
  51. assert.isNotOk(obfuscatedCode);
  52. });
  53. });
  54. describe('empty source code with comments', () => {
  55. let obfuscatedCode: string;
  56. beforeEach(() => {
  57. const code: string = readFileAsString(__dirname + '/fixtures/comments-only.js');
  58. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  59. code,
  60. {
  61. controlFlowFlattening: true,
  62. deadCodeInjection: true
  63. }
  64. ).getObfuscatedCode();
  65. });
  66. it('should return an empty obfuscated code', () => {
  67. assert.isNotOk(obfuscatedCode);
  68. });
  69. });
  70. describe('invalid source code type', () => {
  71. let obfuscatedCode: string;
  72. beforeEach(() => {
  73. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  74. 1 as unknown as string
  75. ).getObfuscatedCode();
  76. });
  77. it('should return an empty obfuscated code', () => {
  78. assert.isNotOk(obfuscatedCode);
  79. });
  80. });
  81. });
  82. describe('`sourceMap` option is `true`', () => {
  83. describe('`sourceMapMode` is `separate`', () => {
  84. let obfuscatedCode: string,
  85. sourceMap: string;
  86. beforeEach(() => {
  87. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  88. const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
  89. code,
  90. {
  91. ...NO_ADDITIONAL_NODES_PRESET,
  92. sourceMap: true
  93. }
  94. );
  95. obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
  96. sourceMap = JSON.parse(obfuscatedCodeObject.getSourceMap()).mappings;
  97. });
  98. it('should return correct obfuscated code', () => {
  99. assert.isOk(obfuscatedCode);
  100. });
  101. it('should return correct source map', () => {
  102. assert.isOk(sourceMap);
  103. });
  104. });
  105. describe('`sourceMapMode` is `inline`', () => {
  106. const regExp: RegExp = /sourceMappingURL=data:application\/json;base64/;
  107. let obfuscatedCode: string,
  108. sourceMap: string;
  109. beforeEach(() => {
  110. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  111. const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
  112. code,
  113. {
  114. ...NO_ADDITIONAL_NODES_PRESET,
  115. sourceMap: true,
  116. sourceMapMode: SourceMapMode.Inline
  117. }
  118. );
  119. obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
  120. sourceMap = JSON.parse(obfuscatedCodeObject.getSourceMap()).mappings;
  121. });
  122. it('should return correct obfuscated code', () => {
  123. assert.isOk(obfuscatedCode);
  124. });
  125. it('should return obfuscated code with inline source map as Base64 string', () => {
  126. assert.match(obfuscatedCode, regExp);
  127. });
  128. it('should return correct source map', () => {
  129. assert.isOk(sourceMap);
  130. });
  131. });
  132. describe('empty source code', () => {
  133. let obfuscatedCode: string,
  134. sourceMapNames: string[],
  135. sourceMapSources: string[],
  136. sourceMapMappings: string;
  137. beforeEach(() => {
  138. const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
  139. const obfuscatedCodeObject: IObfuscatedCode = JavaScriptObfuscator.obfuscate(
  140. code,
  141. {
  142. sourceMap: true
  143. }
  144. );
  145. obfuscatedCode = obfuscatedCodeObject.getObfuscatedCode();
  146. const sourceMapObject: any = JSON.parse(obfuscatedCodeObject.getSourceMap());
  147. sourceMapNames = sourceMapObject.names;
  148. sourceMapSources = sourceMapObject.sources;
  149. sourceMapMappings = sourceMapObject.mappings;
  150. });
  151. it('should return empty obfuscated code', () => {
  152. assert.isNotOk(obfuscatedCode);
  153. });
  154. it('should return empty source map property `names`', () => {
  155. assert.deepEqual(sourceMapNames, []);
  156. });
  157. it('should return empty source map property `sources`', () => {
  158. assert.deepEqual(sourceMapSources, []);
  159. });
  160. it('should return empty source map property `mappings`', () => {
  161. assert.isNotOk(sourceMapMappings);
  162. });
  163. });
  164. });
  165. describe('variable inside global scope', () => {
  166. describe('Variant #1: without `renameGlobals` option', () => {
  167. const regExp: RegExp = /^var test *= *0x\d+;$/;
  168. let obfuscatedCode: string;
  169. beforeEach(() => {
  170. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  171. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  172. code,
  173. {
  174. ...NO_ADDITIONAL_NODES_PRESET
  175. }
  176. ).getObfuscatedCode();
  177. });
  178. it('should return correct obfuscated code', () => {
  179. assert.match(obfuscatedCode, regExp);
  180. });
  181. });
  182. describe('Variant #2: with `renameGlobals` option', () => {
  183. const regExp: RegExp = /^var _0x(\w){4,6} *= *0x\d+;$/;
  184. let obfuscatedCode: string;
  185. beforeEach(() => {
  186. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  187. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  188. code,
  189. {
  190. ...NO_ADDITIONAL_NODES_PRESET,
  191. renameGlobals: true
  192. }
  193. ).getObfuscatedCode();
  194. });
  195. it('should return correct obfuscated code', () => {
  196. assert.match(obfuscatedCode, regExp);
  197. });
  198. });
  199. describe('Variant #3: with `renameGlobals` and `identifiersPrefix` options', () => {
  200. const regExp: RegExp = /^var foo_0x(\w){4,6} *= *0x\d+;$/;
  201. let obfuscatedCode: string;
  202. beforeEach(() => {
  203. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  204. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  205. code,
  206. {
  207. ...NO_ADDITIONAL_NODES_PRESET,
  208. renameGlobals: true,
  209. identifiersPrefix: 'foo'
  210. }
  211. ).getObfuscatedCode();
  212. });
  213. it('should return correct obfuscated code', () => {
  214. assert.match(obfuscatedCode, regExp);
  215. });
  216. });
  217. describe('Variant #4: with `stringArray`, `renameGlobals` and `identifiersPrefix` options', () => {
  218. const stringArrayRegExp: RegExp = /^var foo_0x(\w){4} *= *\['abc'\];/;
  219. const stringArrayCallRegExp: RegExp = /var foo_0x(\w){4,6} *= *foo_0x(\w){4}\('0x0'\);$/;
  220. let obfuscatedCode: string;
  221. beforeEach(() => {
  222. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
  223. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  224. code,
  225. {
  226. ...NO_ADDITIONAL_NODES_PRESET,
  227. renameGlobals: true,
  228. identifiersPrefix: 'foo',
  229. stringArray: true,
  230. stringArrayThreshold: 1
  231. }
  232. ).getObfuscatedCode();
  233. });
  234. it('match #1: should return correct obfuscated code', () => {
  235. assert.match(obfuscatedCode, stringArrayRegExp);
  236. });
  237. it('match #2: should return correct obfuscated code', () => {
  238. assert.match(obfuscatedCode, stringArrayCallRegExp);
  239. });
  240. });
  241. });
  242. describe('variable inside block scope', () => {
  243. const regExp: RegExp = /^\(function *\(\) *\{ *var _0x[\w]+ *= *0x\d+; *\}(\(\)\)|\)\(\));?$/;
  244. let obfuscatedCode: string;
  245. beforeEach(() => {
  246. const code: string = readFileAsString(__dirname + '/fixtures/block-scope.js');
  247. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  248. code,
  249. {
  250. ...NO_ADDITIONAL_NODES_PRESET
  251. }
  252. ).getObfuscatedCode();
  253. });
  254. it('should return correct obfuscated code', () => {
  255. assert.match(obfuscatedCode, regExp);
  256. });
  257. });
  258. describe('variables inside global and block scopes', () => {
  259. describe('Variant #1: with `renameGlobals` and `identifiersPrefix` options', () => {
  260. const variableDeclaration1: RegExp = /var foo_0x(\w){4,6} *= *0x1;/;
  261. const variableDeclaration2: RegExp = /var foo_0x(\w){4,6} *= *0x2;/;
  262. const variableDeclaration3: RegExp = /var _0x(\w){4,6} *= *foo_0x(\w){4,6} *\+ *foo_0x(\w){4,6}/;
  263. const functionDeclaration: RegExp = /var foo_0x(\w){4,6} *= *function/;
  264. let obfuscatedCode: string;
  265. beforeEach(() => {
  266. const code: string = readFileAsString(__dirname + '/fixtures/identifiers-prefix.js');
  267. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  268. code,
  269. {
  270. ...NO_ADDITIONAL_NODES_PRESET,
  271. renameGlobals: true,
  272. identifiersPrefix: 'foo'
  273. }
  274. ).getObfuscatedCode();
  275. });
  276. it('match #1: should return correct obfuscated code', () => {
  277. assert.match(obfuscatedCode, variableDeclaration1);
  278. });
  279. it('match #2: should return correct obfuscated code', () => {
  280. assert.match(obfuscatedCode, variableDeclaration2);
  281. });
  282. it('match #3: should return correct obfuscated code', () => {
  283. assert.match(obfuscatedCode, variableDeclaration3);
  284. });
  285. it('match #4: should return correct obfuscated code', () => {
  286. assert.match(obfuscatedCode, functionDeclaration);
  287. });
  288. });
  289. });
  290. describe('latin literal variable value', () => {
  291. const stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/;
  292. const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\('0x0'\);$/;
  293. let obfuscatedCode: string;
  294. beforeEach(() => {
  295. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
  296. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  297. code,
  298. {
  299. ...NO_ADDITIONAL_NODES_PRESET,
  300. stringArray: true,
  301. stringArrayThreshold: 1
  302. }
  303. ).getObfuscatedCode();
  304. });
  305. it('match #1: should return correct obfuscated code', () => {
  306. assert.match(obfuscatedCode, stringArrayLatinRegExp);
  307. });
  308. it('match #2: should return correct obfuscated code', () => {
  309. assert.match(obfuscatedCode, stringArrayCallRegExp);
  310. });
  311. });
  312. describe('cyrillic literal variable value', () => {
  313. const stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['абц'\];/;
  314. const stringArrayCallRegExp: RegExp = /var test *= *_0x(\w){4}\('0x0'\);$/;
  315. let obfuscatedCode: string;
  316. beforeEach(() => {
  317. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
  318. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  319. code,
  320. {
  321. ...NO_ADDITIONAL_NODES_PRESET,
  322. stringArray: true,
  323. stringArrayThreshold: 1
  324. }
  325. ).getObfuscatedCode();
  326. });
  327. it('match #1: should return correct obfuscated code', () => {
  328. assert.match(obfuscatedCode, stringArrayCyrillicRegExp);
  329. });
  330. it('match #2: should return correct obfuscated code', () => {
  331. assert.match(obfuscatedCode, stringArrayCallRegExp);
  332. });
  333. });
  334. describe('seed', function () {
  335. this.timeout(60000);
  336. describe('same seed on each run', () => {
  337. const code: string = readFileAsString('./test/fixtures/sample.js');
  338. const samples: number = 100;
  339. let obfuscatedCode1: string,
  340. obfuscatedCode2: string,
  341. seed: number = 12345,
  342. equalsCount: number = 0;
  343. beforeEach(() => {
  344. for (let i: number = 0; i < samples; i++) {
  345. if (i % 20 === 0) {
  346. seed++;
  347. }
  348. obfuscatedCode1 = JavaScriptObfuscator.obfuscate(
  349. code,
  350. {
  351. seed: seed
  352. }
  353. ).getObfuscatedCode();
  354. obfuscatedCode2 = JavaScriptObfuscator.obfuscate(
  355. code,
  356. {
  357. seed: seed
  358. }
  359. ).getObfuscatedCode();
  360. if (obfuscatedCode1 === obfuscatedCode2) {
  361. equalsCount++;
  362. }
  363. }
  364. });
  365. it('should return same code every time with same `seed`', () => {
  366. assert.equal(equalsCount, samples);
  367. });
  368. });
  369. describe('Variant #1: different seed on each run', () => {
  370. const code: string = readFileAsString('./test/fixtures/sample.js');
  371. let obfuscatedCode1: string,
  372. obfuscatedCode2: string;
  373. beforeEach(() => {
  374. obfuscatedCode1 = JavaScriptObfuscator.obfuscate(
  375. code,
  376. {
  377. seed: 12345
  378. }
  379. ).getObfuscatedCode();
  380. obfuscatedCode2 = JavaScriptObfuscator.obfuscate(
  381. code,
  382. {
  383. seed: 12346
  384. }
  385. ).getObfuscatedCode();
  386. });
  387. it('should return different obfuscated code with different `seed` option value', () => {
  388. assert.notEqual(obfuscatedCode1, obfuscatedCode2);
  389. });
  390. });
  391. describe('Variant #2: different seed on each run', () => {
  392. const code: string = readFileAsString('./test/fixtures/sample.js');
  393. let obfuscatedCode1: string,
  394. obfuscatedCode2: string;
  395. beforeEach(() => {
  396. obfuscatedCode1 = JavaScriptObfuscator.obfuscate(
  397. code,
  398. {
  399. seed: 0
  400. }
  401. ).getObfuscatedCode();
  402. obfuscatedCode2 = JavaScriptObfuscator.obfuscate(
  403. code,
  404. {
  405. seed: 0
  406. }
  407. ).getObfuscatedCode();
  408. });
  409. it('should return different obfuscated code with different `seed` option value', () => {
  410. assert.notEqual(obfuscatedCode1, obfuscatedCode2);
  411. });
  412. });
  413. describe('Variant #3: same seed for different source code', () => {
  414. const code1: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
  415. const code2: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
  416. const regExp: RegExp = /var (_0x(\w){4}) *= *\['.*'\];/;
  417. let match1: string,
  418. match2: string;
  419. beforeEach(() => {
  420. const obfuscatedCode1: string = JavaScriptObfuscator.obfuscate(
  421. code1,
  422. {
  423. seed: 123,
  424. stringArrayThreshold: 1
  425. }
  426. ).getObfuscatedCode();
  427. const obfuscatedCode2: string = JavaScriptObfuscator.obfuscate(
  428. code2,
  429. {
  430. seed: 123,
  431. stringArrayThreshold: 1
  432. }
  433. ).getObfuscatedCode();
  434. match1 = getRegExpMatch(obfuscatedCode1, regExp);
  435. match2 = getRegExpMatch(obfuscatedCode2, regExp);
  436. });
  437. it('should return different String Array names for different source code with same seed', () => {
  438. assert.notEqual(match1, match2);
  439. });
  440. });
  441. });
  442. /**
  443. * https://github.com/estools/escodegen/pull/408
  444. */
  445. describe('`ObjectPattern` with single `RestElement`', () => {
  446. const regExp: RegExp = /const {\.\.\.foo} *= *{};/;
  447. let obfuscatedCode: string;
  448. beforeEach(() => {
  449. const code: string = readFileAsString(__dirname + '/fixtures/object-pattern-single-rest-element.js');
  450. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  451. code,
  452. {
  453. ...NO_ADDITIONAL_NODES_PRESET
  454. }
  455. ).getObfuscatedCode();
  456. });
  457. it('should not break on `ObjectPattern` with single `RestElement`', () => {
  458. assert.match(obfuscatedCode, regExp);
  459. });
  460. });
  461. /**
  462. * https://github.com/estools/escodegen/pull/415
  463. */
  464. describe('Precedence of `SequenceExpression` in computed property', () => {
  465. const regExp: RegExp = /class Foo *{ *\[\(bar, *baz\)]\(\) *{ *} * *}/;
  466. let obfuscatedCode: string;
  467. beforeEach(() => {
  468. const code: string = readFileAsString(__dirname + '/fixtures/precedence-of-sequence-expression-in-computed-property.js');
  469. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  470. code,
  471. {
  472. ...NO_ADDITIONAL_NODES_PRESET
  473. }
  474. ).getObfuscatedCode();
  475. });
  476. it('should generate a valid js code', () => {
  477. assert.match(obfuscatedCode, regExp);
  478. });
  479. });
  480. describe('new.target MetaProperty', () => {
  481. const regExp: RegExp = /new\.target *=== *Foo/;
  482. let obfuscatedCode: string;
  483. beforeEach(() => {
  484. const code: string = readFileAsString(__dirname + '/fixtures/new-target.js');
  485. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  486. code,
  487. {
  488. ...NO_ADDITIONAL_NODES_PRESET
  489. }
  490. ).getObfuscatedCode();
  491. });
  492. it('should keep new.target MetaProperty', () => {
  493. assert.match(obfuscatedCode, regExp);
  494. });
  495. });
  496. describe('import.meta support', () => {
  497. const regExp: RegExp = /console\['log']\(import\.meta\['url']\);/;
  498. let obfuscatedCode: string;
  499. beforeEach(() => {
  500. const code: string = readFileAsString(__dirname + '/fixtures/import-meta.js');
  501. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  502. code,
  503. {
  504. ...NO_ADDITIONAL_NODES_PRESET
  505. }
  506. ).getObfuscatedCode();
  507. });
  508. it('should support `import.meta`', () => {
  509. assert.match(obfuscatedCode, regExp);
  510. });
  511. });
  512. /**
  513. * https://github.com/estools/escodegen/pull/407
  514. */
  515. describe('valid exponentiation operator precedence', () => {
  516. const regExp: RegExp = /var foo *= *\( *0x1 *- *0x2 *\) *\*\* *0x2;/;
  517. let obfuscatedCode: string;
  518. beforeEach(() => {
  519. const code: string = readFileAsString(__dirname + '/fixtures/exponentiation-operator-precedence.js');
  520. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  521. code,
  522. {
  523. ...NO_ADDITIONAL_NODES_PRESET
  524. }
  525. ).getObfuscatedCode();
  526. });
  527. it('should support exponentiation operator', () => {
  528. assert.match(obfuscatedCode, regExp);
  529. });
  530. });
  531. describe('BigInt support', () => {
  532. const regExp: RegExp = /return 0x20000000000001n *\+ *0xan *\+ *0xan;/;
  533. let obfuscatedCode: string;
  534. beforeEach(() => {
  535. const code: string = readFileAsString(__dirname + '/fixtures/bigint-support.js');
  536. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  537. code,
  538. {
  539. ...NO_ADDITIONAL_NODES_PRESET
  540. }
  541. ).getObfuscatedCode();
  542. });
  543. it('should support BigInt', () => {
  544. assert.match(obfuscatedCode, regExp);
  545. });
  546. });
  547. describe('Optional chaining support', () => {
  548. const regExp: RegExp = new RegExp(
  549. 'const _0x(\\w){4,6} *= *{ *' +
  550. '\'bar\': *\\(\\) *=> *{} *' +
  551. '}; *' +
  552. '_0x(\\w){4,6}\\?\\.\\[\'bar\']\\?\\.\\(\\);'
  553. );
  554. let obfuscatedCode: string;
  555. beforeEach(() => {
  556. const code: string = readFileAsString(__dirname + '/fixtures/optional-chaining-support.js');
  557. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  558. code,
  559. {
  560. ...NO_ADDITIONAL_NODES_PRESET
  561. }
  562. ).getObfuscatedCode();
  563. });
  564. it('should support optional chaining', () => {
  565. assert.match(obfuscatedCode, regExp);
  566. });
  567. });
  568. describe('mangled identifier names generator', () => {
  569. const regExp: RegExp = /var c *= *0x1/;
  570. let obfuscatedCode: string;
  571. beforeEach(() => {
  572. const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
  573. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  574. code,
  575. {
  576. ...NO_ADDITIONAL_NODES_PRESET,
  577. identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
  578. stringArray: true,
  579. stringArrayThreshold: 1
  580. }
  581. ).getObfuscatedCode();
  582. });
  583. it('should mangle obfuscated code', () => {
  584. assert.match(obfuscatedCode, regExp);
  585. });
  586. });
  587. describe('mangled shuffled identifier names generator', () => {
  588. const regExp: RegExp = /var [a-zA-Z] *= *0x1/;
  589. let obfuscatedCode: string;
  590. beforeEach(() => {
  591. const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
  592. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  593. code,
  594. {
  595. identifierNamesGenerator: IdentifierNamesGenerator.MangledShuffledIdentifierNamesGenerator
  596. }
  597. ).getObfuscatedCode();
  598. });
  599. it('should mangle obfuscated code', () => {
  600. assert.match(obfuscatedCode, regExp);
  601. });
  602. });
  603. describe('dictionary identifier names generator', () => {
  604. const regExp1: RegExp = /var [abc] *= *0x1; *var [abc] *= *0x2; *var [abc] *= *0x3;/;
  605. const regExp2: RegExp = /var [ABC] *= *0x4; *var [ABC] *= *0x5; *var [ABC] *= *0x6;/;
  606. let obfuscatedCode: string;
  607. beforeEach(() => {
  608. const code: string = readFileAsString(__dirname + '/fixtures/dictionary-identifiers.js');
  609. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  610. code,
  611. {
  612. ...NO_ADDITIONAL_NODES_PRESET,
  613. identifierNamesGenerator: IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator,
  614. identifiersDictionary: ['a', 'b', 'c']
  615. }
  616. ).getObfuscatedCode();
  617. });
  618. it('Match #1: should generate identifier based on the dictionary', () => {
  619. assert.match(obfuscatedCode, regExp1);
  620. });
  621. it('Match #2: should generate identifier based on the dictionary', () => {
  622. assert.match(obfuscatedCode, regExp2);
  623. });
  624. });
  625. describe('parse module', () => {
  626. describe('Variant #1: import', () => {
  627. const importRegExp: RegExp = /import *{foo} *from *'.\/foo';/;
  628. const variableDeclarationRegExp: RegExp = /var test *= *0x1/;
  629. let obfuscatedCode: string;
  630. beforeEach(() => {
  631. const code: string = readFileAsString(__dirname + '/fixtures/parse-module-1.js');
  632. obfuscatedCode = JavaScriptObfuscator.obfuscate(code).getObfuscatedCode();
  633. });
  634. it('Match #!: should correctly obfuscate a import', () => {
  635. assert.match(obfuscatedCode, importRegExp);
  636. });
  637. it('Match #2: should correctly obfuscate a module', () => {
  638. assert.match(obfuscatedCode, variableDeclarationRegExp);
  639. });
  640. });
  641. describe('Variant #2: export', () => {
  642. const regExp: RegExp = /export *const foo *= *0x1;/;
  643. let obfuscatedCode: string;
  644. beforeEach(() => {
  645. const code: string = readFileAsString(__dirname + '/fixtures/parse-module-2.js');
  646. obfuscatedCode = JavaScriptObfuscator.obfuscate(code).getObfuscatedCode();
  647. });
  648. it('should correctly obfuscate a module', () => {
  649. assert.match(obfuscatedCode, regExp);
  650. });
  651. });
  652. });
  653. describe('3.5k variables', function () {
  654. this.timeout(200000);
  655. const expectedValue: number = 3500;
  656. let result: number;
  657. beforeEach(() => {
  658. const code: string = buildLargeCode(expectedValue);
  659. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  660. code,
  661. {
  662. compact: true,
  663. controlFlowFlattening: true,
  664. controlFlowFlatteningThreshold: 1,
  665. deadCodeInjection: true,
  666. deadCodeInjectionThreshold: 1,
  667. disableConsoleOutput: false,
  668. numbersToExpressions: true,
  669. simplify: true,
  670. renameProperties: true,
  671. rotateStringArray: true,
  672. stringArray: true,
  673. stringArrayEncoding: [
  674. StringArrayEncoding.Base64,
  675. StringArrayEncoding.Rc4
  676. ],
  677. stringArrayWrappersChainedCalls: true,
  678. stringArrayWrappersCount: 10,
  679. stringArrayWrappersType: StringArrayWrappersType.Function,
  680. stringArrayThreshold: 1,
  681. transformObjectKeys: true,
  682. unicodeEscapeSequence: false
  683. }
  684. ).getObfuscatedCode();
  685. result = eval(obfuscatedCode);
  686. });
  687. it('should correctly obfuscate 3.5k variables', () => {
  688. assert.equal(result, expectedValue);
  689. });
  690. });
  691. describe('Eval `Hello World`', function () {
  692. this.timeout(20000);
  693. const samplesCount: number = 100;
  694. const expectedEvaluationResult: string = 'aaabbbcccdddeee';
  695. let isEvaluationSuccessful: boolean = true;
  696. before(() => {
  697. const code: string = readFileAsString(__dirname + '/fixtures/eval-hello-world.js');
  698. for (let i = 0; i < samplesCount; i++) {
  699. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  700. code,
  701. {
  702. ...NO_ADDITIONAL_NODES_PRESET,
  703. compact: false,
  704. controlFlowFlattening: true,
  705. controlFlowFlatteningThreshold: 1,
  706. deadCodeInjection: true,
  707. deadCodeInjectionThreshold: 1,
  708. disableConsoleOutput: true,
  709. identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
  710. renameProperties: true,
  711. simplify: false,
  712. stringArray: true,
  713. stringArrayThreshold: 1,
  714. stringArrayWrappersChainedCalls: true,
  715. stringArrayWrappersCount: 1,
  716. stringArrayWrappersType: StringArrayWrappersType.Variable
  717. }
  718. ).getObfuscatedCode();
  719. const evaluationResult: string = eval(obfuscatedCode);
  720. if (evaluationResult !== expectedEvaluationResult) {
  721. isEvaluationSuccessful = false;
  722. break;
  723. }
  724. }
  725. });
  726. it('should correctly evaluate obfuscated code', () => {
  727. assert.equal(isEvaluationSuccessful, true);
  728. });
  729. });
  730. describe('Identifier names collision between base code and appended string array nodes', function () {
  731. this.timeout(10000);
  732. const samplesCount: number = 30;
  733. let areCollisionsExists: boolean = false;
  734. let obfuscateFunc: (identifierNamesGenerator: TypeFromEnum<typeof IdentifierNamesGenerator>) => string;
  735. before(() => {
  736. const code: string = readFileAsString(__dirname + '/fixtures/custom-nodes-identifier-names-collision.js');
  737. obfuscateFunc = (identifierNamesGenerator: TypeFromEnum<typeof IdentifierNamesGenerator>) => {
  738. const obfuscatedCode = JavaScriptObfuscator.obfuscate(
  739. code,
  740. {
  741. identifierNamesGenerator,
  742. compact: false,
  743. renameGlobals: true,
  744. identifiersDictionary: ['foo', 'bar', 'baz', 'bark', 'hawk', 'foozmos', 'cow', 'chikago'],
  745. stringArray: true
  746. }
  747. ).getObfuscatedCode();
  748. return obfuscatedCode;
  749. };
  750. [
  751. IdentifierNamesGenerator.DictionaryIdentifierNamesGenerator,
  752. IdentifierNamesGenerator.MangledIdentifierNamesGenerator
  753. ].forEach((identifierNamesGenerator: TypeFromEnum<typeof IdentifierNamesGenerator>) => {
  754. for (let i = 0; i < samplesCount; i++) {
  755. try {
  756. eval(obfuscateFunc(identifierNamesGenerator));
  757. } catch {
  758. areCollisionsExists = true;
  759. break;
  760. }
  761. }
  762. });
  763. });
  764. it('It does not create identifier names collision', () => {
  765. assert.equal(areCollisionsExists, false);
  766. });
  767. });
  768. describe('Prevailing kind of variables', () => {
  769. const baseParams: TInputOptions = {
  770. compact: true,
  771. controlFlowFlattening: true,
  772. controlFlowFlatteningThreshold: 1,
  773. deadCodeInjection: true,
  774. deadCodeInjectionThreshold: 1,
  775. debugProtection: true,
  776. debugProtectionInterval: true,
  777. disableConsoleOutput: false,
  778. rotateStringArray: true,
  779. selfDefending: true,
  780. stringArray: true,
  781. stringArrayThreshold: 1,
  782. transformObjectKeys: true,
  783. unicodeEscapeSequence: false
  784. };
  785. describe('`var` kind', function () {
  786. let obfuscatedCode: string;
  787. before(() => {
  788. const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-var.js');
  789. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  790. code,
  791. {
  792. ...NO_ADDITIONAL_NODES_PRESET,
  793. ...baseParams,
  794. stringArrayEncoding: [StringArrayEncoding.Rc4]
  795. }
  796. ).getObfuscatedCode();
  797. });
  798. it('does not break on run', () => {
  799. assert.doesNotThrow(() => eval(obfuscatedCode));
  800. });
  801. });
  802. describe('`const` kind', function () {
  803. describe('Variant #1: StringArrayEncoding: rc4', () => {
  804. let obfuscatedCode: string;
  805. before(() => {
  806. const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
  807. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  808. code,
  809. {
  810. ...NO_ADDITIONAL_NODES_PRESET,
  811. ...baseParams,
  812. stringArrayEncoding: [StringArrayEncoding.Rc4]
  813. }
  814. ).getObfuscatedCode();
  815. });
  816. it('does not break on run', () => {
  817. assert.doesNotThrow(() => eval(obfuscatedCode));
  818. });
  819. });
  820. describe('Variant #2: StringArrayEncoding: base64', () => {
  821. let obfuscatedCode: string;
  822. before(() => {
  823. const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-const.js');
  824. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  825. code,
  826. {
  827. ...NO_ADDITIONAL_NODES_PRESET,
  828. ...baseParams,
  829. stringArrayEncoding: [StringArrayEncoding.Rc4]
  830. }
  831. ).getObfuscatedCode();
  832. });
  833. it('does not break on run', () => {
  834. assert.doesNotThrow(() => eval(obfuscatedCode));
  835. });
  836. });
  837. });
  838. describe('`let` kind', function () {
  839. describe('Variant #1: StringArrayEncoding: rc4', () => {
  840. let obfuscatedCode: string;
  841. before(() => {
  842. const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
  843. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  844. code,
  845. {
  846. ...NO_ADDITIONAL_NODES_PRESET,
  847. ...baseParams,
  848. stringArrayEncoding: [StringArrayEncoding.Rc4]
  849. }
  850. ).getObfuscatedCode();
  851. });
  852. it('does not break on run', () => {
  853. assert.doesNotThrow(() => eval(obfuscatedCode));
  854. });
  855. });
  856. describe('Variant #2: StringArrayEncoding: base64', () => {
  857. let obfuscatedCode: string;
  858. before(() => {
  859. const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-let.js');
  860. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  861. code,
  862. {
  863. ...NO_ADDITIONAL_NODES_PRESET,
  864. ...baseParams,
  865. stringArrayEncoding: [StringArrayEncoding.Base64]
  866. }
  867. ).getObfuscatedCode();
  868. });
  869. it('does not break on run', () => {
  870. assert.doesNotThrow(() => eval(obfuscatedCode));
  871. });
  872. });
  873. });
  874. });
  875. });
  876. describe('obfuscateMultiple', () => {
  877. describe('multiple source codes', () => {
  878. const regExp1: RegExp = /var a0_0x(\w){4,6} *= *0x1;/;
  879. const regExp2: RegExp = /var a1_0x(\w){4,6} *= *'abc';/;
  880. let obfuscatedCode1: string;
  881. let obfuscatedCode2: string;
  882. beforeEach(() => {
  883. const sourceCode1: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  884. const sourceCode2: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
  885. const obfuscationResultsObject = JavaScriptObfuscator.obfuscateMultiple(
  886. {
  887. sourceCode1,
  888. sourceCode2
  889. },
  890. {
  891. ...NO_ADDITIONAL_NODES_PRESET,
  892. renameGlobals: true
  893. }
  894. );
  895. obfuscatedCode1 = obfuscationResultsObject.sourceCode1.getObfuscatedCode();
  896. obfuscatedCode2 = obfuscationResultsObject.sourceCode2.getObfuscatedCode();
  897. });
  898. it('Match #1: should return correct obfuscated code', () => {
  899. assert.match(obfuscatedCode1, regExp1);
  900. });
  901. it('Match #2: should return correct obfuscated code', () => {
  902. assert.match(obfuscatedCode2, regExp2);
  903. });
  904. });
  905. describe('invalid source codes object', () => {
  906. let testFunc: () => TDictionary<IObfuscatedCode>;
  907. beforeEach(() => {
  908. testFunc = () => JavaScriptObfuscator.obfuscateMultiple(
  909. 'foo' as any,
  910. {
  911. ...NO_ADDITIONAL_NODES_PRESET,
  912. renameGlobals: true
  913. }
  914. );
  915. });
  916. it('Should throw an error if source codes object is not a plain object', () => {
  917. assert.throw(testFunc, Error);
  918. });
  919. });
  920. });
  921. describe('getOptionsByPreset', () => {
  922. describe('Variant #1: base behaviour', () => {
  923. const optionsPresetName: TOptionsPreset = OptionsPreset.HighObfuscation;
  924. let options: TInputOptions;
  925. before(() => {
  926. options = JavaScriptObfuscator.getOptionsByPreset(optionsPresetName);
  927. });
  928. it('Should return options for passed options preset name', () => {
  929. assert.deepEqual(options, HIGH_OBFUSCATION_PRESET);
  930. });
  931. });
  932. describe('Variant #2: unknown options preset name', () => {
  933. const optionsPresetName: TOptionsPreset = 'foobar' as TOptionsPreset;
  934. let testFunc: () => TInputOptions;
  935. before(() => {
  936. testFunc = () => JavaScriptObfuscator.getOptionsByPreset(optionsPresetName);
  937. });
  938. it('Should throws an error when unknown option preset is passed', () => {
  939. assert.throws(testFunc, 'Options for preset name `foobar` are not found');
  940. });
  941. });
  942. });
  943. });