JavaScriptObfuscatorCLI.ts 21 KB

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