JavaScriptObfuscatorCLI.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /* eslint-disable max-lines */
  2. import * as commander from 'commander';
  3. import * as path from 'path';
  4. import { TInputCLIOptions } from '../types/options/TInputCLIOptions';
  5. import { TInputOptions } from '../types/options/TInputOptions';
  6. import { IFileData } from '../interfaces/cli/IFileData';
  7. import { IInitializable } from '../interfaces/IInitializable';
  8. import { IObfuscationResult } from '../interfaces/source-code/IObfuscationResult';
  9. import { initializable } from '../decorators/Initializable';
  10. import { IdentifierNamesGenerator } from '../enums/generators/identifier-names-generators/IdentifierNamesGenerator';
  11. import { LoggingPrefix } from '../enums/logger/LoggingPrefix';
  12. import { ObfuscationTarget } from '../enums/ObfuscationTarget';
  13. import { OptionsPreset } from '../enums/options/presets/OptionsPreset';
  14. import { RenamePropertiesMode } from '../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode';
  15. import { SourceMapMode } from '../enums/source-map/SourceMapMode';
  16. import { StringArrayEncoding } from '../enums/node-transformers/string-array-transformers/StringArrayEncoding';
  17. import { StringArrayIndexesType } from '../enums/node-transformers/string-array-transformers/StringArrayIndexesType';
  18. import { StringArrayWrappersType } from '../enums/node-transformers/string-array-transformers/StringArrayWrappersType';
  19. import { DEFAULT_PRESET } from '../options/presets/Default';
  20. import { ArraySanitizer } from './sanitizers/ArraySanitizer';
  21. import { BooleanSanitizer } from './sanitizers/BooleanSanitizer';
  22. import { CLIUtils } from './utils/CLIUtils';
  23. import { IdentifierNamesCacheFileUtils } from './utils/IdentifierNamesCacheFileUtils';
  24. import { JavaScriptObfuscator } from '../JavaScriptObfuscatorFacade';
  25. import { Logger } from '../logger/Logger';
  26. import { ObfuscatedCodeFileUtils } from './utils/ObfuscatedCodeFileUtils';
  27. import { SourceCodeFileUtils } from './utils/SourceCodeFileUtils';
  28. import { Utils } from '../utils/Utils';
  29. export class JavaScriptObfuscatorCLI implements IInitializable {
  30. /**
  31. * @type {string[]}
  32. */
  33. public static readonly availableInputExtensions: string[] = [
  34. '.js'
  35. ];
  36. /**
  37. * @type {BufferEncoding}
  38. */
  39. public static readonly encoding: BufferEncoding = 'utf8';
  40. /**
  41. * @type {string}
  42. */
  43. public static readonly obfuscatedFilePrefix: string = '-obfuscated';
  44. /**
  45. * @type {commander.CommanderStatic}
  46. */
  47. @initializable()
  48. private commands!: commander.CommanderStatic;
  49. /**
  50. * @type {IdentifierNamesCacheFileUtils}
  51. */
  52. @initializable()
  53. private identifierNamesCacheFileUtils!: IdentifierNamesCacheFileUtils;
  54. /**
  55. * @type {TInputCLIOptions}
  56. */
  57. @initializable()
  58. private inputCLIOptions!: TInputCLIOptions;
  59. /**
  60. * @type {string}
  61. */
  62. @initializable()
  63. private inputPath!: string;
  64. /**
  65. * @type {SourceCodeFileUtils}
  66. */
  67. @initializable()
  68. private sourceCodeFileUtils!: SourceCodeFileUtils;
  69. /**
  70. * @type {ObfuscatedCodeFileUtils}
  71. */
  72. @initializable()
  73. private obfuscatedCodeFileUtils!: ObfuscatedCodeFileUtils;
  74. /**
  75. * @type {string[]}
  76. */
  77. private readonly arguments: string[];
  78. /**
  79. * @type {string[]}
  80. */
  81. private readonly rawArguments: string[];
  82. /**
  83. * @param {string[]} argv
  84. */
  85. public constructor (argv: string[]) {
  86. this.rawArguments = argv;
  87. this.arguments = argv.slice(2);
  88. }
  89. /**
  90. * @param {TInputCLIOptions} inputOptions
  91. * @returns {TInputOptions}
  92. */
  93. private static buildOptions (inputOptions: TInputCLIOptions): TInputOptions {
  94. const inputCLIOptions: TInputOptions = JavaScriptObfuscatorCLI.filterOptions(inputOptions);
  95. const configFilePath: string | undefined = inputOptions.config;
  96. const configFileLocation: string = configFilePath ? path.resolve(configFilePath, '.') : '';
  97. const configFileOptions: TInputOptions = configFileLocation ? CLIUtils.getUserConfig(configFileLocation) : {};
  98. return {
  99. ...DEFAULT_PRESET,
  100. ...configFileOptions,
  101. ...inputCLIOptions
  102. };
  103. }
  104. /**
  105. * @param {TObject} options
  106. * @returns {TInputOptions}
  107. */
  108. private static filterOptions (options: TInputCLIOptions): TInputOptions {
  109. const filteredOptions: TInputOptions = {};
  110. Object
  111. .keys(options)
  112. .forEach((option: keyof TInputCLIOptions) => {
  113. if (options[option] === undefined) {
  114. return;
  115. }
  116. filteredOptions[option] = options[option];
  117. });
  118. return filteredOptions;
  119. }
  120. public initialize (): void {
  121. this.commands = <commander.CommanderStatic>(new commander.Command());
  122. this.configureCommands();
  123. this.configureHelp();
  124. this.inputPath = path.normalize(this.commands.args[0] || '');
  125. this.inputCLIOptions = JavaScriptObfuscatorCLI.buildOptions(this.commands.opts());
  126. this.sourceCodeFileUtils = new SourceCodeFileUtils(
  127. this.inputPath,
  128. this.inputCLIOptions
  129. );
  130. this.obfuscatedCodeFileUtils = new ObfuscatedCodeFileUtils(
  131. this.inputPath,
  132. this.inputCLIOptions
  133. );
  134. this.identifierNamesCacheFileUtils = new IdentifierNamesCacheFileUtils(this.inputCLIOptions.identifierNamesCachePath);
  135. }
  136. public run (): void {
  137. const canShowHelp: boolean = !this.arguments.length || this.arguments.includes('--help');
  138. if (canShowHelp) {
  139. this.commands.outputHelp();
  140. return;
  141. }
  142. const sourceCodeData: IFileData[] = this.sourceCodeFileUtils.readSourceCode();
  143. this.processSourceCodeData(sourceCodeData);
  144. }
  145. private configureCommands (): void {
  146. this.commands
  147. .usage('<inputPath> [options]')
  148. .version(
  149. Utils.buildVersionMessage(process.env.VERSION, process.env.BUILD_TIMESTAMP),
  150. '-v, --version'
  151. )
  152. .option(
  153. '-o, --output <path>',
  154. 'Output path for obfuscated code'
  155. )
  156. .option(
  157. '--compact <boolean>',
  158. 'Disable one line output code compacting',
  159. BooleanSanitizer
  160. )
  161. .option(
  162. '--config <boolean>',
  163. 'Name of js / json config file'
  164. )
  165. .option(
  166. '--control-flow-flattening <boolean>',
  167. 'Enables control flow flattening',
  168. BooleanSanitizer
  169. )
  170. .option(
  171. '--control-flow-flattening-threshold <number>',
  172. 'The probability that the control flow flattening transformation will be applied to the node',
  173. parseFloat
  174. )
  175. .option(
  176. '--dead-code-injection <boolean>',
  177. 'Enables dead code injection',
  178. BooleanSanitizer
  179. )
  180. .option(
  181. '--dead-code-injection-threshold <number>',
  182. 'The probability that the dead code injection transformation will be applied to the node',
  183. parseFloat
  184. )
  185. .option(
  186. '--debug-protection <boolean>',
  187. 'Disable browser Debug panel (can cause DevTools enabled browser freeze)',
  188. BooleanSanitizer
  189. )
  190. .option(
  191. '--debug-protection-interval <boolean>',
  192. 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)',
  193. BooleanSanitizer
  194. )
  195. .option(
  196. '--disable-console-output <boolean>',
  197. 'Allow console.log, console.info, console.error and console.warn messages output into browser console',
  198. BooleanSanitizer
  199. )
  200. .option(
  201. '--domain-lock <list> (comma separated, without whitespaces)',
  202. 'Allows to run the obfuscated source code only on specific domains and/or sub-domains (comma separated)',
  203. ArraySanitizer
  204. )
  205. .option(
  206. '--domain-lock-redirect-url <string>',
  207. 'Allows the browser to be redirected to a passed URL if the source code isn\'t run on the domains specified by --domain-lock',
  208. )
  209. .option(
  210. '--exclude <list> (comma separated, without whitespaces)',
  211. 'A filename or glob which indicates files to exclude from obfuscation',
  212. ArraySanitizer
  213. )
  214. .option(
  215. '--force-transform-strings <list> (comma separated, without whitespaces)',
  216. 'Enables force transformation of string literals, which being matched by passed RegExp patterns (comma separated)',
  217. ArraySanitizer
  218. )
  219. .option(
  220. '--identifier-names-cache-path <string>',
  221. 'Sets path for identifier names cache'
  222. )
  223. .option(
  224. '--identifier-names-generator <string>',
  225. 'Sets identifier names generator. ' +
  226. `Values: ${CLIUtils.stringifyOptionAvailableValues(IdentifierNamesGenerator)}. ` +
  227. `Default: ${IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator}`
  228. )
  229. .option(
  230. '--identifiers-prefix <string>',
  231. 'Sets prefix for all global identifiers'
  232. )
  233. .option(
  234. '--identifiers-dictionary <list> (comma separated, without whitespaces)',
  235. 'Identifiers dictionary (comma separated) for `--identifier-names-generator dictionary` option',
  236. ArraySanitizer
  237. )
  238. .option(
  239. '--ignore-require-imports <boolean>', 'Prevents obfuscation of `require` imports',
  240. BooleanSanitizer
  241. )
  242. .option(
  243. '--log <boolean>', 'Enables logging of the information to the console',
  244. BooleanSanitizer
  245. )
  246. .option(
  247. '--numbers-to-expressions <boolean>', 'Enables numbers conversion to expressions',
  248. BooleanSanitizer
  249. )
  250. .option(
  251. '--options-preset <string>',
  252. 'Allows to set options preset. ' +
  253. `Values: ${CLIUtils.stringifyOptionAvailableValues(OptionsPreset)}. ` +
  254. `Default: ${OptionsPreset.Default}`
  255. )
  256. .option(
  257. '--reserved-names <list> (comma separated, without whitespaces)',
  258. 'Disables obfuscation and generation of identifiers, which being matched by passed RegExp patterns (comma separated)',
  259. ArraySanitizer
  260. )
  261. .option(
  262. '--reserved-strings <list> (comma separated, without whitespaces)',
  263. 'Disables transformation of string literals, which being matched by passed RegExp patterns (comma separated)',
  264. ArraySanitizer
  265. )
  266. .option(
  267. '--rename-globals <boolean>', 'Allows to enable obfuscation of global variable and function names with declaration',
  268. BooleanSanitizer
  269. )
  270. .option(
  271. '--rename-properties <boolean>', 'UNSAFE: Enables renaming of property names. This probably MAY break your code',
  272. BooleanSanitizer
  273. )
  274. .option(
  275. '--rename-properties-mode <boolean>',
  276. 'Specify `--rename-properties` option mode. ' +
  277. `Values: ${CLIUtils.stringifyOptionAvailableValues(RenamePropertiesMode)}. ` +
  278. `Default: ${RenamePropertiesMode.Safe}`
  279. )
  280. .option(
  281. '--rotate-string-array <boolean>', 'Enable rotation of string array values during obfuscation',
  282. BooleanSanitizer
  283. )
  284. .option(
  285. '--seed <string|number>',
  286. 'Sets seed for random generator. This is useful for creating repeatable results.',
  287. parseFloat
  288. )
  289. .option(
  290. '--self-defending <boolean>',
  291. 'Disables self-defending for obfuscated code',
  292. BooleanSanitizer
  293. )
  294. .option(
  295. '--shuffle-string-array <boolean>', 'Randomly shuffles string array items',
  296. BooleanSanitizer
  297. )
  298. .option(
  299. '--simplify <boolean>', 'Enables additional code obfuscation through simplification',
  300. BooleanSanitizer
  301. )
  302. .option(
  303. '--source-map <boolean>',
  304. 'Enables source map generation',
  305. BooleanSanitizer
  306. )
  307. .option(
  308. '--source-map-base-url <string>',
  309. 'Sets base url to the source map import url when `--source-map-mode=separate`'
  310. )
  311. .option(
  312. '--source-map-file-name <string>',
  313. 'Sets file name for output source map when `--source-map-mode=separate`'
  314. )
  315. .option(
  316. '--source-map-mode <string>',
  317. 'Specify source map output mode. ' +
  318. `Values: ${CLIUtils.stringifyOptionAvailableValues(SourceMapMode)}. ` +
  319. `Default: ${SourceMapMode.Separate}`
  320. )
  321. .option(
  322. '--split-strings <boolean>',
  323. 'Splits literal strings into chunks with length of `splitStringsChunkLength` option value',
  324. BooleanSanitizer
  325. )
  326. .option(
  327. '--split-strings-chunk-length <number>',
  328. 'Sets chunk length of `splitStrings` option',
  329. parseFloat
  330. )
  331. .option(
  332. '--string-array <boolean>',
  333. 'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
  334. BooleanSanitizer
  335. )
  336. .option(
  337. '--string-array-encoding <list> (comma separated, without whitespaces)',
  338. 'Encodes each string in strings array using base64 or rc4 (this option can slow down your code speed). ' +
  339. `Values: ${CLIUtils.stringifyOptionAvailableValues(StringArrayEncoding)}. ` +
  340. `Default: ${StringArrayEncoding.None}`,
  341. ArraySanitizer
  342. )
  343. .option(
  344. '--string-array-indexes-type <list> (comma separated, without whitespaces)',
  345. 'Encodes each string in strings array using base64 or rc4 (this option can slow down your code speed). ' +
  346. `Values: ${CLIUtils.stringifyOptionAvailableValues(StringArrayIndexesType)}. ` +
  347. `Default: ${StringArrayIndexesType.HexadecimalNumber}`,
  348. ArraySanitizer
  349. )
  350. .option(
  351. '--string-array-index-shift <boolean>',
  352. 'Enables additional index shift for all string array calls',
  353. BooleanSanitizer
  354. )
  355. .option(
  356. '--string-array-wrappers-count <number>',
  357. 'Sets the count of wrappers for the string array inside each root or function scope',
  358. parseInt
  359. )
  360. .option(
  361. '--string-array-wrappers-chained-calls <boolean>',
  362. 'Enables the chained calls between string array wrappers',
  363. BooleanSanitizer
  364. )
  365. .option(
  366. '--string-array-wrappers-parameters-max-count <number>',
  367. 'Allows to control the maximum number of string array wrappers parameters',
  368. parseInt
  369. )
  370. .option(
  371. '--string-array-wrappers-type <string>',
  372. 'Allows to select a type of the wrappers that are appending by the `--string-array-wrappers-count` option. ' +
  373. `Values: ${CLIUtils.stringifyOptionAvailableValues(StringArrayWrappersType)}. ` +
  374. `Default: ${StringArrayWrappersType.Variable}`
  375. )
  376. .option(
  377. '--string-array-threshold <number>',
  378. 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)',
  379. parseFloat
  380. )
  381. .option(
  382. '--target <string>',
  383. 'Allows to set target environment for obfuscated code. ' +
  384. `Values: ${CLIUtils.stringifyOptionAvailableValues(ObfuscationTarget)}. ` +
  385. `Default: ${ObfuscationTarget.Browser}`
  386. )
  387. .option(
  388. '--transform-object-keys <boolean>',
  389. 'Enables transformation of object keys',
  390. BooleanSanitizer
  391. )
  392. .option(
  393. '--unicode-escape-sequence <boolean>',
  394. 'Allows to enable/disable string conversion to unicode escape sequence',
  395. BooleanSanitizer
  396. )
  397. .parse(this.rawArguments);
  398. }
  399. private configureHelp (): void {
  400. this.commands.on('--help', () => {
  401. console.log(' Examples:\n');
  402. console.log(' %> javascript-obfuscator input_file_name.js --compact true --self-defending false');
  403. console.log(' %> javascript-obfuscator input_file_name.js --output output_file_name.js --compact true --self-defending false');
  404. console.log(' %> javascript-obfuscator input_directory_name --compact true --self-defending false');
  405. console.log('');
  406. });
  407. }
  408. /**
  409. * @param {IFileData[]} sourceCodeData
  410. */
  411. private processSourceCodeData (sourceCodeData: IFileData[]): void {
  412. sourceCodeData.forEach(({ filePath, content }: IFileData, index: number) => {
  413. const outputCodePath: string = this.obfuscatedCodeFileUtils.getOutputCodePath(filePath);
  414. try {
  415. Logger.log(
  416. Logger.colorInfo,
  417. LoggingPrefix.CLI,
  418. `Obfuscating file: ${filePath}...`
  419. );
  420. this.processSourceCode(content, filePath, outputCodePath, index);
  421. } catch (error) {
  422. Logger.log(
  423. Logger.colorInfo,
  424. LoggingPrefix.CLI,
  425. `Error in file: ${filePath}...`
  426. );
  427. throw error;
  428. }
  429. });
  430. }
  431. /**
  432. * @param {string} sourceCode
  433. * @param {string} inputCodePath
  434. * @param {string} outputCodePath
  435. * @param {number | null} sourceCodeIndex
  436. */
  437. private processSourceCode (
  438. sourceCode: string,
  439. inputCodePath: string,
  440. outputCodePath: string,
  441. sourceCodeIndex: number | null
  442. ): void {
  443. const options: TInputOptions = {
  444. ...this.inputCLIOptions,
  445. identifierNamesCache: this.identifierNamesCacheFileUtils.readFile(),
  446. inputFileName: path.basename(inputCodePath),
  447. ...sourceCodeIndex !== null && {
  448. identifiersPrefix: Utils.getIdentifiersPrefixForMultipleSources(
  449. this.inputCLIOptions.identifiersPrefix,
  450. sourceCodeIndex
  451. )
  452. }
  453. };
  454. if (options.sourceMap) {
  455. this.processSourceCodeWithSourceMap(sourceCode, outputCodePath, options);
  456. } else {
  457. this.processSourceCodeWithoutSourceMap(sourceCode, outputCodePath, options);
  458. }
  459. }
  460. /**
  461. * @param {string} sourceCode
  462. * @param {string} outputCodePath
  463. * @param {TInputOptions} options
  464. */
  465. private processSourceCodeWithoutSourceMap (
  466. sourceCode: string,
  467. outputCodePath: string,
  468. options: TInputOptions
  469. ): void {
  470. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(sourceCode, options);
  471. this.obfuscatedCodeFileUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
  472. this.identifierNamesCacheFileUtils.writeFile(obfuscationResult.getIdentifierNamesCache());
  473. }
  474. /**
  475. * @param {string} sourceCode
  476. * @param {string} outputCodePath
  477. * @param {TInputOptions} options
  478. */
  479. private processSourceCodeWithSourceMap (
  480. sourceCode: string,
  481. outputCodePath: string,
  482. options: TInputOptions
  483. ): void {
  484. const outputSourceMapPath: string = this.obfuscatedCodeFileUtils.getOutputSourceMapPath(
  485. outputCodePath,
  486. options.sourceMapFileName ?? ''
  487. );
  488. options = {
  489. ...options,
  490. sourceMapFileName: path.basename(outputSourceMapPath)
  491. };
  492. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(sourceCode, options);
  493. this.obfuscatedCodeFileUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
  494. this.identifierNamesCacheFileUtils.writeFile(obfuscationResult.getIdentifierNamesCache());
  495. if (options.sourceMapMode === SourceMapMode.Separate && obfuscationResult.getSourceMap()) {
  496. this.obfuscatedCodeFileUtils.writeFile(outputSourceMapPath, obfuscationResult.getSourceMap());
  497. }
  498. }
  499. }