ソースを参照

Mixed string array encoding prototype (#727)

`stringArrayEncoding` option now accepts an array of encodings. Each string will be randomly encoded with passed encoding.
Timofey Kachalov 4 年 前
コミット
42ff66849e
39 ファイル変更528 行追加215 行削除
  1. 4 0
      CHANGELOG.md
  2. 22 11
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 1 1
      package.json
  7. 4 4
      src/cli/JavaScriptObfuscatorCLI.ts
  8. 14 11
      src/cli/sanitizers/StringArrayEncodingSanitizer.ts
  9. 10 0
      src/container/modules/custom-code-helpers/CustomCodeHelpersModule.ts
  10. 32 0
      src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts
  11. 22 66
      src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts
  12. 36 0
      src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts
  13. 54 18
      src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts
  14. 2 0
      src/enums/StringArrayEncoding.ts
  15. 3 1
      src/enums/custom-code-helpers/CustomCodeHelper.ts
  16. 3 0
      src/interfaces/IEncodedValue.ts
  17. 1 1
      src/interfaces/options/IOptions.ts
  18. 4 2
      src/interfaces/storages/string-array-storage/IStringArrayStorage.ts
  19. 4 3
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  20. 5 3
      src/options/Options.ts
  21. 5 3
      src/options/normalizer-rules/StringArrayEncodingRule.ts
  22. 5 1
      src/options/normalizer-rules/StringArrayRule.ts
  23. 5 1
      src/options/normalizer-rules/StringArrayThresholdRule.ts
  24. 4 1
      src/options/presets/Default.ts
  25. 3 1
      src/options/presets/HighObfuscation.ts
  26. 3 1
      src/options/presets/MediumObfuscation.ts
  27. 4 1
      src/options/presets/NoCustomNodes.ts
  28. 31 13
      src/storages/string-array/StringArrayStorage.ts
  29. 1 1
      src/types/options/TStringArrayEncoding.ts
  30. 8 9
      test/dev/dev.ts
  31. 1 1
      test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts
  32. 9 6
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  33. 181 11
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  34. 6 4
      test/functional-tests/options/OptionsNormalizer.spec.ts
  35. 3 1
      test/performance-tests/JavaScriptObfuscatorMemory.spec.ts
  36. 5 1
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  37. 9 0
      test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts
  38. 23 37
      test/unit-tests/cli/sanitizers/StringArrayEncodingSanitizer.spec.ts
  39. 1 1
      test/unit-tests/storages/string-array/StringArrayStorage.spec.ts

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v2.0.0
+---
+* **Breaking change:** `stringArrayEncoding` option now accepts an array of encodings. Each string will be randomly encoded with passed encoding.
+
 v1.12.0
 ---
 * **New option:** `optionsPreset` allows to set options preset

+ 22 - 11
README.md

@@ -355,7 +355,7 @@ Following options are available for the JS Obfuscator:
     splitStrings: false,
     splitStringsChunkLength: 10,
     stringArray: true,
-    stringArrayEncoding: false,
+    stringArrayEncoding: [],
     stringArrayThreshold: 0.75,
     target: 'browser',
     transformObjectKeys: false,
@@ -403,7 +403,7 @@ Following options are available for the JS Obfuscator:
     --split-strings <boolean>
     --split-strings-chunk-length <number>
     --string-array <boolean>
-    --string-array-encoding <boolean|string> [true, false, base64, rc4]
+    --string-array-encoding '<list>' (comma separated) [none, base64, rc4]
     --string-array-threshold <number>
     --target <string> [browser, browser-no-eval, node]
     --transform-object-keys <boolean>
@@ -919,7 +919,7 @@ Type: `boolean` Default: `true`
 Removes string literals and place them in a special array. For instance, the string `"Hello World"` in `var m = "Hello World";` will be replaced with something like `var m = _0x12c456[0x1];`
     
 ### `stringArrayEncoding`
-Type: `boolean|string` Default: `false`
+Type: `string[]` Default: `[]`
 
 ##### :warning: `stringArray` option must be enabled
 
@@ -927,11 +927,22 @@ This option can slow down your script.
 
 Encode all string literals of the [`stringArray`](#stringarray) using `base64` or `rc4` and inserts a special code that used to decode it back at runtime.
 
+Each `stringArray` value will be encoded by the randomly picked encoding from the passed list. This makes possible to use multiple encodings.
+
 Available values:
-* `true` (`boolean`): encode `stringArray` values using `base64`
-* `false` (`boolean`): don't encode `stringArray` values
-* `'base64'` (`string`): encode `stringArray` values using `base64`
-* `'rc4'` (`string`): encode `stringArray` values using `rc4`. **About 30-50% slower than `base64`, but more harder to get initial values.** It is recommended to disable [`unicodeEscapeSequence`](#unicodeescapesequence) option with `rc4` encoding to prevent very large size of obfuscated code.
+* `'none'` (`boolean`): doesn't encode `stringArray` value
+* `'base64'` (`string`): encodes `stringArray` value using `base64`
+* `'rc4'` (`string`): encodes `stringArray` value using `rc4`. **About 30-50% slower than `base64`, but more harder to get initial values.** It's recommended to disable [`unicodeEscapeSequence`](#unicodeescapesequence) option when using `rc4` encoding to prevent very large size of obfuscated code.
+
+For example with the following option values some `stringArray` value won't be encoded, and some values will be encoded with `base64` and `rc4` encoding:
+
+```ts
+stringArrayEncoding: [
+    'none',
+    'base64',
+    'rc4'
+]
+```
     
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
@@ -1030,7 +1041,7 @@ Performance will 50-100% slower than without obfuscation
     splitStrings: true,
     splitStringsChunkLength: 5,
     stringArray: true,
-    stringArrayEncoding: 'rc4',
+    stringArrayEncoding: ['rc4'],
     stringArrayThreshold: 1,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
@@ -1062,7 +1073,7 @@ Performance will 30-35% slower than without obfuscation
     splitStrings: true,
     splitStringsChunkLength: 10,
     stringArray: true,
-    stringArrayEncoding: 'base64',
+    stringArrayEncoding: ['base64'],
     stringArrayThreshold: 0.75,
     transformObjectKeys: true,
     unicodeEscapeSequence: false
@@ -1091,7 +1102,7 @@ Performance will slightly slower than without obfuscation
     simplify: true,
     splitStrings: false,
     stringArray: true,
-    stringArrayEncoding: false,
+    stringArrayEncoding: [],
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
 }
@@ -1117,7 +1128,7 @@ Performance will slightly slower than without obfuscation
     simplify: true,
     splitStrings: false,
     stringArray: true,
-    stringArrayEncoding: false,
+    stringArrayEncoding: [],
     stringArrayThreshold: 0.75,
     unicodeEscapeSequence: false
 }

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/index.browser.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/index.cli.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "1.12.0",
+  "version": "2.0.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 4 - 4
src/cli/JavaScriptObfuscatorCLI.ts

@@ -337,10 +337,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 BooleanSanitizer
             )
             .option(
-                '--string-array-encoding <string|boolean>',
-                'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed. ' +
-                'Values: true, false, base64, rc4. ' +
-                'Default: false',
+                '--string-array-encoding <list> (comma separated, without whitespaces)',
+                'Encodes each string in strings array using base64 or rc4 (this option can slow down your code speed. ' +
+                'Values: none, base64, rc4. ' +
+                'Default: none',
                 StringArrayEncodingSanitizer
             )
             .option(

+ 14 - 11
src/cli/sanitizers/StringArrayEncodingSanitizer.ts

@@ -3,21 +3,24 @@ import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
 
 import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
+import { ArraySanitizer } from './ArraySanitizer';
+
 /**
  * @param {string} value
- * @returns {TStringArrayEncoding}
+ * @returns {TStringArrayEncoding[]}
  */
-export const StringArrayEncodingSanitizer: TCLISanitizer <TStringArrayEncoding> = (value: string): TStringArrayEncoding => {
-    switch (value) {
-        case 'true':
-        case '1':
-        case StringArrayEncoding.Base64:
-            return true;
+export const StringArrayEncodingSanitizer: TCLISanitizer <TStringArrayEncoding[]> = (value: string): TStringArrayEncoding[] => {
+    const valuesAsArray: TStringArrayEncoding[] = <TStringArrayEncoding[]>ArraySanitizer(value);
 
-        case StringArrayEncoding.Rc4:
-            return StringArrayEncoding.Rc4;
+    const isCorrectStringArrayEncodings: boolean = valuesAsArray.every((item: TStringArrayEncoding) =>
+        Object
+            .values(StringArrayEncoding)
+            .includes(item)
+    );
 
-        default:
-            return false;
+    if (!isCorrectStringArrayEncodings) {
+        throw new ReferenceError('Invalid value of `--string-array-encoding` option');
     }
+
+    return valuesAsArray;
 };

+ 10 - 0
src/container/modules/custom-code-helpers/CustomCodeHelpersModule.ts

@@ -26,6 +26,8 @@ import { DomainLockCodeHelper } from '../../../custom-code-helpers/domain-lock/D
 import { CallsControllerFunctionCodeHelper } from '../../../custom-code-helpers/calls-controller/CallsControllerFunctionCodeHelper';
 import { SelfDefendingUnicodeCodeHelper } from '../../../custom-code-helpers/self-defending/SelfDefendingUnicodeCodeHelper';
 import { StringArrayCallsWrapperCodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper';
+import { StringArrayCallsWrapperBase64CodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper';
+import { StringArrayCallsWrapperRc4CodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper';
 import { StringArrayCodeHelper } from '../../../custom-code-helpers/string-array/StringArrayCodeHelper';
 import { StringArrayRotateFunctionCodeHelper } from '../../../custom-code-helpers/string-array/StringArrayRotateFunctionCodeHelper';
 
@@ -63,6 +65,14 @@ export const customCodeHelpersModule: interfaces.ContainerModule = new Container
         .to(StringArrayCallsWrapperCodeHelper)
         .whenTargetNamed(CustomCodeHelper.StringArrayCallsWrapper);
 
+    bind<ICustomCodeHelper>(ServiceIdentifiers.ICustomCodeHelper)
+        .to(StringArrayCallsWrapperBase64CodeHelper)
+        .whenTargetNamed(CustomCodeHelper.StringArrayCallsWrapperBase64);
+
+    bind<ICustomCodeHelper>(ServiceIdentifiers.ICustomCodeHelper)
+        .to(StringArrayCallsWrapperRc4CodeHelper)
+        .whenTargetNamed(CustomCodeHelper.StringArrayCallsWrapperRc4);
+
     bind<ICustomCodeHelper>(ServiceIdentifiers.ICustomCodeHelper)
         .to(StringArrayCodeHelper)
         .whenTargetNamed(CustomCodeHelper.StringArray);

+ 32 - 0
src/custom-code-helpers/string-array/StringArrayCallsWrapperBase64CodeHelper.ts

@@ -0,0 +1,32 @@
+import { injectable, } from 'inversify';
+
+import { AtobTemplate } from './templates/string-array-calls-wrapper/AtobTemplate';
+
+import { StringArrayCallsWrapperCodeHelper } from './StringArrayCallsWrapperCodeHelper';
+import { StringArrayBase64DecodeTemplate } from './templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate';
+
+@injectable()
+export class StringArrayCallsWrapperBase64CodeHelper extends StringArrayCallsWrapperCodeHelper {
+    /**
+     * @returns {string}
+     */
+    protected getDecodeStringArrayTemplate (): string {
+        const atobFunctionName: string = this.randomGenerator.getRandomString(6);
+
+        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
+            atobFunctionName: atobFunctionName
+        });
+
+        const selfDefendingCode: string = this.getSelfDefendingTemplate();
+
+        return this.customCodeHelperFormatter.formatTemplate(
+            StringArrayBase64DecodeTemplate(this.randomGenerator),
+            {
+                atobPolyfill,
+                atobFunctionName,
+                selfDefendingCode,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
+            }
+        );
+    }
+}

+ 22 - 66
src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts

@@ -10,16 +10,10 @@ import { IEscapeSequenceEncoder } from '../../interfaces/utils/IEscapeSequenceEn
 import { IOptions } from '../../interfaces/options/IOptions';
 import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
 
-import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
-
 import { initializable } from '../../decorators/Initializable';
 
-import { AtobTemplate } from './templates/string-array-calls-wrapper/AtobTemplate';
-import { Rc4Template } from './templates/string-array-calls-wrapper/Rc4Template';
 import { SelfDefendingTemplate } from './templates/string-array-calls-wrapper/SelfDefendingTemplate';
-import { StringArrayBase64DecodeTemplate } from './templates/string-array-calls-wrapper/StringArrayBase64DecodeTemplate';
 import { StringArrayCallsWrapperTemplate } from './templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate';
-import { StringArrayRC4DecodeTemplate } from './templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate';
 
 import { AbstractCustomCodeHelper } from '../AbstractCustomCodeHelper';
 import { NodeUtils } from '../../node/NodeUtils';
@@ -30,19 +24,13 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
      * @type {string}
      */
     @initializable()
-    private atobFunctionName!: string;
-
-    /**
-     * @type {string}
-     */
-    @initializable()
-    private stringArrayName!: string;
+    protected stringArrayName!: string;
 
     /**
      * @type {string}
      */
     @initializable()
-    private stringArrayCallsWrapperName!: string;
+    protected stringArrayCallsWrapperName!: string;
 
     /**
      * @type {IEscapeSequenceEncoder}
@@ -80,16 +68,13 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     /**
      * @param {string} stringArrayName
      * @param {string} stringArrayCallsWrapperName
-     * @param {string} atobFunctionName
      */
     public initialize (
         stringArrayName: string,
-        stringArrayCallsWrapperName: string,
-        atobFunctionName: string
+        stringArrayCallsWrapperName: string
     ): void {
         this.stringArrayName = stringArrayName;
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
-        this.atobFunctionName = atobFunctionName;
     }
 
     /**
@@ -123,56 +108,27 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     /**
      * @returns {string}
      */
-    private getDecodeStringArrayTemplate (): string {
-        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
-            atobFunctionName: this.atobFunctionName
-        });
-        const rc4Polyfill: string = this.customCodeHelperFormatter.formatTemplate(Rc4Template(), {
-            atobFunctionName: this.atobFunctionName
-        });
-
-        let decodeStringArrayTemplate: string = '';
-        let selfDefendingCode: string = '';
-
-        if (this.options.selfDefending) {
-            selfDefendingCode = this.customCodeHelperFormatter.formatTemplate(
-                SelfDefendingTemplate(
-                    this.randomGenerator,
-                    this.escapeSequenceEncoder
-                ),
-                {
-                    stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                    stringArrayName: this.stringArrayName
-                }
-            );
-        }
+    protected getDecodeStringArrayTemplate (): string {
+        return '';
+    }
 
-        switch (this.options.stringArrayEncoding) {
-            case StringArrayEncoding.Rc4:
-                decodeStringArrayTemplate = this.customCodeHelperFormatter.formatTemplate(
-                    StringArrayRC4DecodeTemplate(this.randomGenerator),
-                    {
-                        atobPolyfill,
-                        rc4Polyfill,
-                        selfDefendingCode,
-                        stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
-                    }
-                );
-
-                break;
-
-            case StringArrayEncoding.Base64:
-                decodeStringArrayTemplate = this.customCodeHelperFormatter.formatTemplate(
-                    StringArrayBase64DecodeTemplate(this.randomGenerator),
-                    {
-                        atobPolyfill,
-                        atobFunctionName: this.atobFunctionName,
-                        selfDefendingCode,
-                        stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
-                    }
-                );
+    /**
+     * @returns {string}
+     */
+    protected getSelfDefendingTemplate (): string {
+        if (!this.options.selfDefending) {
+            return '';
         }
 
-        return decodeStringArrayTemplate;
+        return this.customCodeHelperFormatter.formatTemplate(
+            SelfDefendingTemplate(
+                this.randomGenerator,
+                this.escapeSequenceEncoder
+            ),
+            {
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
+                stringArrayName: this.stringArrayName
+            }
+        );
     }
 }

+ 36 - 0
src/custom-code-helpers/string-array/StringArrayCallsWrapperRc4CodeHelper.ts

@@ -0,0 +1,36 @@
+import { injectable, } from 'inversify';
+
+import { AtobTemplate } from './templates/string-array-calls-wrapper/AtobTemplate';
+import { Rc4Template } from './templates/string-array-calls-wrapper/Rc4Template';
+import { StringArrayRC4DecodeTemplate } from './templates/string-array-calls-wrapper/StringArrayRC4DecodeTemplate';
+
+import { StringArrayCallsWrapperCodeHelper } from './StringArrayCallsWrapperCodeHelper';
+
+@injectable()
+export class StringArrayCallsWrapperRc4CodeHelper extends StringArrayCallsWrapperCodeHelper {
+    /**
+     * @returns {string}
+     */
+    protected getDecodeStringArrayTemplate (): string {
+        const atobFunctionName: string = this.randomGenerator.getRandomString(6);
+
+        const atobPolyfill: string = this.customCodeHelperFormatter.formatTemplate(AtobTemplate(), {
+            atobFunctionName
+        });
+        const rc4Polyfill: string = this.customCodeHelperFormatter.formatTemplate(Rc4Template(), {
+            atobFunctionName
+        });
+
+        const selfDefendingCode: string = this.getSelfDefendingTemplate();
+
+        return this.customCodeHelperFormatter.formatTemplate(
+            StringArrayRC4DecodeTemplate(this.randomGenerator),
+            {
+                atobPolyfill,
+                rc4Polyfill,
+                selfDefendingCode,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
+            }
+        );
+    }
+}

+ 54 - 18
src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts

@@ -5,6 +5,7 @@ import { TCustomCodeHelperFactory } from '../../../types/container/custom-code-h
 import { TIdentifierNamesGeneratorFactory } from '../../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { TInitialData } from '../../../types/TInitialData';
 import { TNodeWithStatements } from '../../../types/node/TNodeWithStatements';
+import { TStringArrayEncoding } from '../../../types/options/TStringArrayEncoding';
 
 import { ICallsGraphData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphData';
 import { ICustomCodeHelper } from '../../../interfaces/custom-code-helpers/ICustomCodeHelper';
@@ -16,15 +17,25 @@ import { initializable } from '../../../decorators/Initializable';
 
 import { CustomCodeHelper } from '../../../enums/custom-code-helpers/CustomCodeHelper';
 import { ObfuscationEvent } from '../../../enums/event-emitters/ObfuscationEvent';
+import { StringArrayEncoding } from '../../../enums/StringArrayEncoding';
 
 import { AbstractCustomCodeHelperGroup } from '../../AbstractCustomCodeHelperGroup';
 import { NodeAppender } from '../../../node/NodeAppender';
-import { StringArrayCodeHelper } from '../StringArrayCodeHelper';
 import { StringArrayCallsWrapperCodeHelper } from '../StringArrayCallsWrapperCodeHelper';
+import { StringArrayCodeHelper } from '../StringArrayCodeHelper';
 import { StringArrayRotateFunctionCodeHelper } from '../StringArrayRotateFunctionCodeHelper';
 
 @injectable()
 export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
+    /**
+     * @type {Map<TStringArrayEncoding, CustomCodeHelper>}
+     */
+    private static readonly stringArrayCallsWrapperCodeHelperMap: Map<TStringArrayEncoding, CustomCodeHelper> = new Map([
+        [StringArrayEncoding.None, CustomCodeHelper.StringArrayCallsWrapper],
+        [StringArrayEncoding.Base64, CustomCodeHelper.StringArrayCallsWrapperBase64],
+        [StringArrayEncoding.Rc4, CustomCodeHelper.StringArrayCallsWrapperRc4]
+    ]);
+
     /**
      * @type {Map<CustomCodeHelper, ICustomCodeHelper>}
      */
@@ -85,12 +96,16 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
         );
 
         // stringArrayCallsWrapper helper nodes append
-        this.appendCustomNodeIfExist(
-            CustomCodeHelper.StringArrayCallsWrapper,
-            (customCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>>) => {
-                NodeAppender.insertAtIndex(nodeWithStatements, customCodeHelper.getNode(), 1);
-            }
-        );
+        for (const stringArrayEncoding of this.options.stringArrayEncoding) {
+            const stringArrayCallsWrapperCodeHelperName: CustomCodeHelper = this.getStringArrayCallsWrapperCodeHelperName(stringArrayEncoding);
+
+            this.appendCustomNodeIfExist(
+                stringArrayCallsWrapperCodeHelperName,
+                (customCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>>) => {
+                    NodeAppender.insertAtIndex(nodeWithStatements, customCodeHelper.getNode(), 1);
+                }
+            );
+        }
 
         // stringArrayRotateFunction helper nodes append
         this.appendCustomNodeIfExist(
@@ -108,27 +123,48 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
             return;
         }
 
+        // stringArray helper initialize
         const stringArrayCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCodeHelper>> =
             this.customCodeHelperFactory(CustomCodeHelper.StringArray);
-        const stringArrayCallsWrapperCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>> =
-            this.customCodeHelperFactory(CustomCodeHelper.StringArrayCallsWrapper);
+        const stringArrayName: string = this.stringArrayStorage.getStorageName();
+
+        stringArrayCodeHelper.initialize(this.stringArrayStorage, stringArrayName);
+        this.customCodeHelpers.set(CustomCodeHelper.StringArray, stringArrayCodeHelper);
+
+        // stringArrayCallsWrapper helper initialize
+        for (const stringArrayEncoding of this.options.stringArrayEncoding) {
+            const stringArrayCallsWrapperCodeHelperName: CustomCodeHelper = this.getStringArrayCallsWrapperCodeHelperName(stringArrayEncoding);
+            const stringArrayCallsWrapperCodeHelper: ICustomCodeHelper<TInitialData<StringArrayCallsWrapperCodeHelper>> =
+                this.customCodeHelperFactory(stringArrayCallsWrapperCodeHelperName);
+            const stringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName(stringArrayEncoding);
+
+            stringArrayCallsWrapperCodeHelper.initialize(
+                stringArrayName,
+                stringArrayCallsWrapperName
+            );
+
+            this.customCodeHelpers.set(stringArrayCallsWrapperCodeHelperName, stringArrayCallsWrapperCodeHelper);
+        }
+
+        // stringArrayRotateFunction helper initialize
         const stringArrayRotateFunctionCodeHelper: ICustomCodeHelper<TInitialData<StringArrayRotateFunctionCodeHelper>> =
             this.customCodeHelperFactory(CustomCodeHelper.StringArrayRotateFunction);
-
-        const stringArrayName: string = this.stringArrayStorage.getStorageName();
-        const stringArrayCallsWrapperName: string = this.stringArrayStorage.getStorageCallsWrapperName();
         const stringArrayRotationAmount: number = this.stringArrayStorage.getRotationAmount();
-        const atobFunctionName: string = this.randomGenerator.getRandomString(6);
 
-        stringArrayCodeHelper.initialize(this.stringArrayStorage, stringArrayName);
-        stringArrayCallsWrapperCodeHelper.initialize(stringArrayName, stringArrayCallsWrapperName, atobFunctionName);
         stringArrayRotateFunctionCodeHelper.initialize(stringArrayName, stringArrayRotationAmount);
 
-        this.customCodeHelpers.set(CustomCodeHelper.StringArray, stringArrayCodeHelper);
-        this.customCodeHelpers.set(CustomCodeHelper.StringArrayCallsWrapper, stringArrayCallsWrapperCodeHelper);
-
         if (this.options.rotateStringArray) {
             this.customCodeHelpers.set(CustomCodeHelper.StringArrayRotateFunction, stringArrayRotateFunctionCodeHelper);
         }
     }
+
+    /**
+     * @param {TStringArrayEncoding} stringArrayEncoding
+     * @returns {CustomCodeHelper}
+     */
+    private getStringArrayCallsWrapperCodeHelperName (stringArrayEncoding: TStringArrayEncoding): CustomCodeHelper {
+        return StringArrayCodeHelperGroup
+                .stringArrayCallsWrapperCodeHelperMap.get(stringArrayEncoding)
+            ?? CustomCodeHelper.StringArrayCallsWrapper;
+    }
 }

+ 2 - 0
src/enums/StringArrayEncoding.ts

@@ -1,9 +1,11 @@
 import { MakeEnum } from '@gradecam/tsenum';
 
 export const StringArrayEncoding: Readonly<{
+    None: 'none';
     Base64: 'base64';
     Rc4: 'rc4';
 }> = MakeEnum({
+    None: 'none',
     Base64: 'base64',
     Rc4: 'rc4'
 });

+ 3 - 1
src/enums/custom-code-helpers/CustomCodeHelper.ts

@@ -6,7 +6,9 @@ export enum CustomCodeHelper {
     DebugProtectionFunction = 'DebugProtectionFunction',
     DomainLock = 'DomainLock',
     SelfDefendingUnicode = 'SelfDefendingUnicode',
-    StringArrayCallsWrapper = 'StringArrayCallsWrapper',
     StringArray = 'StringArray',
+    StringArrayCallsWrapper = 'StringArrayCallsWrapper',
+    StringArrayCallsWrapperBase64 = 'StringArrayCallsWrapperBase64',
+    StringArrayCallsWrapperRc4 = 'StringArrayCallsWrapperRc4',
     StringArrayRotateFunction = 'StringArrayRotateFunction'
 }

+ 3 - 0
src/interfaces/IEncodedValue.ts

@@ -1,4 +1,7 @@
+import { TStringArrayEncoding } from '../types/options/TStringArrayEncoding';
+
 export interface IEncodedValue {
+    encoding: TStringArrayEncoding | null;
     encodedValue: string;
     decodeKey: string | null;
 }

+ 1 - 1
src/interfaces/options/IOptions.ts

@@ -40,7 +40,7 @@ export interface IOptions {
     readonly splitStrings: boolean;
     readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;
-    readonly stringArrayEncoding: TStringArrayEncoding;
+    readonly stringArrayEncoding: TStringArrayEncoding[];
     readonly stringArrayThreshold: number;
     readonly target: TypeFromEnum<typeof ObfuscationTarget>;
     readonly transformObjectKeys: boolean;

+ 4 - 2
src/interfaces/storages/string-array-storage/IStringArrayStorage.ts

@@ -1,6 +1,7 @@
-import { IStringArrayStorageItemData } from './IStringArrayStorageItem';
+import { TStringArrayEncoding } from '../../../types/options/TStringArrayEncoding';
 
 import { IMapStorage } from '../IMapStorage';
+import { IStringArrayStorageItemData } from './IStringArrayStorageItem';
 
 export interface IStringArrayStorage extends IMapStorage <string, IStringArrayStorageItemData> {
     /**
@@ -14,9 +15,10 @@ export interface IStringArrayStorage extends IMapStorage <string, IStringArraySt
     getStorageName (): string;
 
     /**
+     * @param {TStringArrayEncoding | null} stringArrayEncoding
      * @returns {string}
      */
-    getStorageCallsWrapperName (): string;
+    getStorageCallsWrapperName (stringArrayEncoding: TStringArrayEncoding | null): string;
 
     rotateStorage (): void;
 

+ 4 - 3
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -152,7 +152,8 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         const stringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.stringArrayStorageAnalyzer
             .getItemDataForLiteralNode(literalNode);
         const cacheKey: string = `${literalValue}-${Boolean(stringArrayStorageItemData)}`;
-        const useCachedValue: boolean = this.nodesCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.Rc4;
+        const useCachedValue: boolean = this.nodesCache.has(cacheKey)
+            && stringArrayStorageItemData?.encoding !== StringArrayEncoding.Rc4;
 
         if (useCachedValue) {
             return <ESTree.Node>this.nodesCache.get(cacheKey);
@@ -182,7 +183,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
      * @returns {Node}
      */
     private getStringArrayCallNode (stringArrayStorageItemData: IStringArrayStorageItemData): ESTree.Node {
-        const { index, decodeKey } = stringArrayStorageItemData;
+        const { index, encoding, decodeKey } = stringArrayStorageItemData;
 
         const hexadecimalIndex: string = NumberUtils.toHex(index);
         const callExpressionArgs: (ESTree.Expression | ESTree.SpreadElement)[] = [
@@ -194,7 +195,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
         }
 
         const stringArrayIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(
-            this.stringArrayStorage.getStorageCallsWrapperName()
+            this.stringArrayStorage.getStorageCallsWrapperName(encoding)
         );
 
         return NodeFactory.callExpressionNode(

+ 5 - 3
src/options/Options.ts

@@ -289,10 +289,12 @@ export class Options implements IOptions {
     public readonly stringArray!: boolean;
 
     /**
-     * @type {TStringArrayEncoding}
+     * @type {TStringArrayEncoding[]}
      */
-    @IsIn([true, false, StringArrayEncoding.Base64, StringArrayEncoding.Rc4])
-    public readonly stringArrayEncoding!: TStringArrayEncoding;
+    @IsArray()
+    @ArrayUnique()
+    @IsIn([StringArrayEncoding.None, StringArrayEncoding.Base64, StringArrayEncoding.Rc4], { each: true })
+    public readonly stringArrayEncoding!: TStringArrayEncoding[];
 
     /**
      * @type {number}

+ 5 - 3
src/options/normalizer-rules/StringArrayEncodingRule.ts

@@ -9,12 +9,14 @@ import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
  * @returns {IOptions}
  */
 export const StringArrayEncodingRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
-    if (options.stringArrayEncoding === true) {
+    if (!options.stringArrayEncoding.length) {
         options = {
             ...options,
-            stringArrayEncoding: StringArrayEncoding.Base64
+            stringArrayEncoding: [
+                StringArrayEncoding.None
+            ]
         };
     }
-
+    
     return options;
 };

+ 5 - 1
src/options/normalizer-rules/StringArrayRule.ts

@@ -2,6 +2,8 @@ import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRu
 
 import { IOptions } from '../../interfaces/options/IOptions';
 
+import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
+
 /**
  * @param {IOptions} options
  * @returns {IOptions}
@@ -13,7 +15,9 @@ export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOpt
             rotateStringArray: false,
             shuffleStringArray: false,
             stringArray: false,
-            stringArrayEncoding: false,
+            stringArrayEncoding: [
+                StringArrayEncoding.None
+            ],
             stringArrayThreshold: 0
         };
     }

+ 5 - 1
src/options/normalizer-rules/StringArrayThresholdRule.ts

@@ -2,6 +2,8 @@ import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRu
 
 import { IOptions } from '../../interfaces/options/IOptions';
 
+import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
+
 /**
  * @param {IOptions} options
  * @returns {IOptions}
@@ -12,7 +14,9 @@ export const StringArrayThresholdRule: TOptionsNormalizerRule = (options: IOptio
             ...options,
             rotateStringArray: false,
             stringArray: false,
-            stringArrayEncoding: false,
+            stringArrayEncoding: [
+                StringArrayEncoding.None
+            ],
             stringArrayThreshold: 0
         };
     }

+ 4 - 1
src/options/presets/Default.ts

@@ -4,6 +4,7 @@ import { IdentifierNamesGenerator } from '../../enums/generators/identifier-name
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { OptionsPreset } from '../../enums/options/presets/OptionsPreset';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
+import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
 export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     compact: true,
@@ -40,7 +41,9 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     splitStrings: false,
     splitStringsChunkLength: 10,
     stringArray: true,
-    stringArrayEncoding: false,
+    stringArrayEncoding: [
+        StringArrayEncoding.None
+    ],
     stringArrayThreshold: 0.75,
     target: ObfuscationTarget.Browser,
     transformObjectKeys: false,

+ 3 - 1
src/options/presets/HighObfuscation.ts

@@ -13,6 +13,8 @@ export const HIGH_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     debugProtectionInterval: true,
     optionsPreset: OptionsPreset.HighObfuscation,
     splitStringsChunkLength: 5,
-    stringArrayEncoding: StringArrayEncoding.Rc4,
+    stringArrayEncoding: [
+        StringArrayEncoding.Rc4
+    ],
     stringArrayThreshold: 1
 });

+ 3 - 1
src/options/presets/MediumObfuscation.ts

@@ -13,6 +13,8 @@ export const MEDIUM_OBFUSCATION_PRESET: TInputOptions = Object.freeze({
     optionsPreset: OptionsPreset.MediumObfuscation,
     splitStrings: true,
     splitStringsChunkLength: 10,
-    stringArrayEncoding: StringArrayEncoding.Base64,
+    stringArrayEncoding: [
+        StringArrayEncoding.Base64
+    ],
     transformObjectKeys: true
 });

+ 4 - 1
src/options/presets/NoCustomNodes.ts

@@ -3,6 +3,7 @@ import { TInputOptions } from '../../types/options/TInputOptions';
 import { IdentifierNamesGenerator } from '../../enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
 import { SourceMapMode } from '../../enums/source-map/SourceMapMode';
+import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
 export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     compact: true,
@@ -37,7 +38,9 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     splitStrings: false,
     splitStringsChunkLength: 0,
     stringArray: false,
-    stringArrayEncoding: false,
+    stringArrayEncoding: [
+        StringArrayEncoding.None
+    ],
     stringArrayThreshold: 0,
     target: ObfuscationTarget.Browser,
     transformObjectKeys: false,

+ 31 - 13
src/storages/string-array/StringArrayStorage.ts

@@ -1,6 +1,8 @@
 import { inject, injectable, postConstruct } from 'inversify';
 import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
+import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
+
 import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory';
 import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
 
@@ -85,9 +87,9 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
     private stringArrayStorageName!: string;
 
     /**
-     * @type {string}
+     * @type {Map<TStringArrayEncoding | null, string>}
      */
-    private stringArrayStorageCallsWrapperName!: string;
+    private readonly stringArrayStorageCallsWrapperNamesMap: Map<TStringArrayEncoding | null, string> = new Map();
 
     /**
      * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -136,7 +138,6 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
 
     /**
      * @param {string} value
-     * @returns {IStringArrayStorageItemData}
      */
     public get (value: string): IStringArrayStorageItemData {
         return this.getOrSetIfDoesNotExist(value);
@@ -169,15 +170,25 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
     }
 
     /**
+     * @param {TStringArrayEncoding | null} stringArrayEncoding
      * @returns {string}
      */
-    public getStorageCallsWrapperName (): string {
-        if (!this.stringArrayStorageCallsWrapperName) {
-            this.stringArrayStorageCallsWrapperName = this.identifierNamesGenerator
-                .generateForGlobalScope(StringArrayStorage.stringArrayNameLength);
+    public getStorageCallsWrapperName (stringArrayEncoding: TStringArrayEncoding | null): string {
+        const storageCallsWrapperName: string | null = this.stringArrayStorageCallsWrapperNamesMap.get(stringArrayEncoding) ?? null;
+
+        if (storageCallsWrapperName) {
+            return storageCallsWrapperName;
         }
 
-        return this.stringArrayStorageCallsWrapperName;
+        const newStorageCallsWrapperName: string = this.identifierNamesGenerator
+            .generateForGlobalScope(StringArrayStorage.stringArrayNameLength);
+
+        this.stringArrayStorageCallsWrapperNamesMap.set(
+            stringArrayEncoding,
+            newStorageCallsWrapperName
+        );
+
+        return newStorageCallsWrapperName;
     }
 
     public rotateStorage (): void {
@@ -234,7 +245,7 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      * @returns {IStringArrayStorageItemData}
      */
     private getOrSetIfDoesNotExist (value: string): IStringArrayStorageItemData {
-        const { encodedValue, decodeKey }: IEncodedValue = this.getEncodedValue(value);
+        const { encodedValue, encoding, decodeKey }: IEncodedValue = this.getEncodedValue(value);
         const storedStringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.storage.get(encodedValue);
 
         if (storedStringArrayStorageItemData) {
@@ -243,6 +254,7 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
 
         const stringArrayStorageItemData: IStringArrayStorageItemData = {
             encodedValue,
+            encoding,
             decodeKey,
             value,
             index: this.getLength()
@@ -258,7 +270,13 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      * @returns {IEncodedValue}
      */
     private getEncodedValue (value: string): IEncodedValue {
-        switch (this.options.stringArrayEncoding) {
+        const encoding: TStringArrayEncoding | null = this.options.stringArrayEncoding.length
+            ? this.randomGenerator
+                .getRandomGenerator()
+                .pickone(this.options.stringArrayEncoding)
+            : null;
+
+        switch (encoding) {
             /**
              * For rc4 there is a possible chance of a collision between encoded values that were received from
              * different source values with different keys
@@ -293,21 +311,21 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
                     return this.getEncodedValue(value);
                 }
 
-                return { encodedValue, decodeKey };
+                return { encodedValue, encoding, decodeKey };
             }
 
             case StringArrayEncoding.Base64: {
                 const decodeKey: null = null;
                 const encodedValue: string = this.cryptUtilsSwappedAlphabet.btoa(value);
 
-                return { encodedValue, decodeKey };
+                return { encodedValue, encoding, decodeKey };
             }
 
             default: {
                 const decodeKey: null = null;
                 const encodedValue: string = value;
 
-                return { encodedValue, decodeKey };
+                return { encodedValue, encoding, decodeKey };
             }
         }
     }

+ 1 - 1
src/types/options/TStringArrayEncoding.ts

@@ -2,4 +2,4 @@ import { TypeFromEnum } from '@gradecam/tsenum';
 
 import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
-export type TStringArrayEncoding = boolean | TypeFromEnum<typeof StringArrayEncoding>;
+export type TStringArrayEncoding = TypeFromEnum<typeof StringArrayEncoding>;

+ 8 - 9
test/dev/dev.ts

@@ -7,20 +7,19 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            function func () {
-                var obj = {foo: 'bar'};
-                
-                console.log(obj.foo);
-            }
+            var string1 = 'foo';
+            var string2 = 'bar';
+            var string3 = 'baz';
+            var string4 = 'bark';
             
-            func();
+            console.log(string1, string2, string3, string4);
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
-            transformObjectKeys: true,
-            controlFlowFlattening: true,
-            controlFlowFlatteningThreshold: 1
+            stringArray: true,
+            stringArrayThreshold: 1,
+            stringArrayEncoding: []
         }
     ).getObfuscatedCode();
 

+ 1 - 1
test/functional-tests/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.spec.ts

@@ -72,7 +72,7 @@ describe('StringArrayCallsWrapperCodeHelper', () => {
                     identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator,
                     stringArray: true,
                     stringArrayThreshold: 1,
-                    stringArrayEncoding: StringArrayEncoding.Base64
+                    stringArrayEncoding: [StringArrayEncoding.Base64]
                 }
             ).getObfuscatedCode();
         });

+ 9 - 6
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -841,7 +841,10 @@ describe('JavaScriptObfuscator', () => {
                         renameProperties: true,
                         rotateStringArray: true,
                         stringArray: true,
-                        stringArrayEncoding: StringArrayEncoding.Rc4,
+                        stringArrayEncoding: [
+                            StringArrayEncoding.Base64,
+                            StringArrayEncoding.Rc4
+                        ],
                         stringArrayThreshold: 1,
                         transformObjectKeys: true,
                         unicodeEscapeSequence: false
@@ -932,7 +935,7 @@ describe('JavaScriptObfuscator', () => {
                         {
                             ...NO_ADDITIONAL_NODES_PRESET,
                             ...baseParams,
-                            stringArrayEncoding: StringArrayEncoding.Rc4
+                            stringArrayEncoding: [StringArrayEncoding.Rc4]
                         }
                     ).getObfuscatedCode();
 
@@ -955,7 +958,7 @@ describe('JavaScriptObfuscator', () => {
                             {
                                 ...NO_ADDITIONAL_NODES_PRESET,
                                 ...baseParams,
-                                stringArrayEncoding: StringArrayEncoding.Rc4
+                                stringArrayEncoding: [StringArrayEncoding.Rc4]
                             }
                         ).getObfuscatedCode();
 
@@ -977,7 +980,7 @@ describe('JavaScriptObfuscator', () => {
                             {
                                 ...NO_ADDITIONAL_NODES_PRESET,
                                 ...baseParams,
-                                stringArrayEncoding: StringArrayEncoding.Rc4
+                                stringArrayEncoding: [StringArrayEncoding.Rc4]
                             }
                         ).getObfuscatedCode();
                     });
@@ -1000,7 +1003,7 @@ describe('JavaScriptObfuscator', () => {
                             {
                                 ...NO_ADDITIONAL_NODES_PRESET,
                                 ...baseParams,
-                                stringArrayEncoding: StringArrayEncoding.Rc4
+                                stringArrayEncoding: [StringArrayEncoding.Rc4]
                             }
                         ).getObfuscatedCode();
 
@@ -1022,7 +1025,7 @@ describe('JavaScriptObfuscator', () => {
                             {
                                 ...NO_ADDITIONAL_NODES_PRESET,
                                 ...baseParams,
-                                stringArrayEncoding: StringArrayEncoding.Base64
+                                stringArrayEncoding: [StringArrayEncoding.Base64]
                             }
                         ).getObfuscatedCode();
 

+ 181 - 11
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -11,7 +11,9 @@ import { swapLettersCase } from '../../../../helpers/swapLettersCase';
 
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 
-describe('StringArrayTransformer', () => {
+describe('StringArrayTransformer', function () {
+    this.timeout(60000);
+
     describe('Variant #1: default behaviour', () => {
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
@@ -199,7 +201,7 @@ describe('StringArrayTransformer', () => {
                 {
                     ...NO_ADDITIONAL_NODES_PRESET,
                     stringArray: true,
-                    stringArrayEncoding: StringArrayEncoding.Base64,
+                    stringArrayEncoding: [StringArrayEncoding.Base64],
                     stringArrayThreshold: 1
                 }
             ).getObfuscatedCode();
@@ -228,7 +230,7 @@ describe('StringArrayTransformer', () => {
                     {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         stringArray: true,
-                        stringArrayEncoding: StringArrayEncoding.Rc4,
+                        stringArrayEncoding: [StringArrayEncoding.Rc4],
                         stringArrayThreshold: 1
                     }
                 ).getObfuscatedCode();
@@ -257,7 +259,7 @@ describe('StringArrayTransformer', () => {
                         ...NO_ADDITIONAL_NODES_PRESET,
                         seed: 1, // set seed to prevent rare case when all encoded values are the same
                         stringArray: true,
-                        stringArrayEncoding: StringArrayEncoding.Rc4,
+                        stringArrayEncoding: [StringArrayEncoding.Rc4],
                         stringArrayThreshold: 1
                     }
                 ).getObfuscatedCode();
@@ -280,7 +282,175 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #10: `stringArrayThreshold` option value', () => {
+    describe('Variant #10: none and base64 encoding', () => {
+        describe('Variant #1: string array values', () => {
+            const samplesCount: number = 100;
+            const expectedMatchesChance: number = 0.5;
+            const expectedMatchesDelta: number = 0.15;
+
+            const noneEncodingRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
+            const base64EncodingRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['DgvZDa=='\];/;
+
+            let noneEncodingMatchesCount: number = 0;
+            let base64EncodingMatchesCount: number = 0;
+            let obfuscatedCode: string;
+
+            let noneEncodingMatchesChance: number;
+            let base64EncodingMatchesChance: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                for (let i = 0; i < samplesCount; i++) {
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayEncoding: [
+                                StringArrayEncoding.None,
+                                StringArrayEncoding.Base64
+                            ],
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+
+                    if (obfuscatedCode.match(noneEncodingRegExp)) {
+                        noneEncodingMatchesCount = noneEncodingMatchesCount + 1;
+                    }
+
+                    if (obfuscatedCode.match(base64EncodingRegExp)) {
+                        base64EncodingMatchesCount = base64EncodingMatchesCount + 1;
+                    }
+
+                    noneEncodingMatchesChance = noneEncodingMatchesCount / samplesCount;
+                    base64EncodingMatchesChance = base64EncodingMatchesCount / samplesCount;
+                }
+            });
+
+            it('should add literal node value to the string array without encoding', () => {
+                assert.closeTo(noneEncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
+            });
+
+            it('should add literal node value to the string array encoded using base64', () => {
+                assert.closeTo(base64EncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
+            });
+        });
+    });
+
+    describe('Variant #11: none and rc4 encoding', () => {
+        describe('Variant #1: string array calls wrapper call', () => {
+            const samplesCount: number = 100;
+            const expectedMatchesChance: number = 0.5;
+            const expectedMatchesDelta: number = 0.15;
+
+            const noneEncodingRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
+            const rc4EncodingRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
+
+            let noneEncodingMatchesCount: number = 0;
+            let rc4EncodingMatchesCount: number = 0;
+            let obfuscatedCode: string;
+
+            let noneEncodingMatchesChance: number;
+            let rc4EncodingMatchesChance: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                for (let i = 0; i < samplesCount; i++) {
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayEncoding: [
+                                StringArrayEncoding.None,
+                                StringArrayEncoding.Rc4
+                            ],
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+
+                    if (obfuscatedCode.match(noneEncodingRegExp)) {
+                        noneEncodingMatchesCount = noneEncodingMatchesCount + 1;
+                    }
+
+                    if (obfuscatedCode.match(rc4EncodingRegExp)) {
+                        rc4EncodingMatchesCount = rc4EncodingMatchesCount + 1;
+                    }
+
+                    noneEncodingMatchesChance = noneEncodingMatchesCount / samplesCount;
+                    rc4EncodingMatchesChance = rc4EncodingMatchesCount / samplesCount;
+                }
+            });
+
+            it('should replace literal node value with value from string array without encoding', () => {
+                assert.closeTo(noneEncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
+            });
+
+            it('should replace literal node value with value from string array encoded using rc4', () => {
+                assert.closeTo(rc4EncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
+            });
+        });
+    });
+
+    describe('Variant #12: base64 and rc4 encoding', () => {
+        describe('Variant #1: single string literal', () => {
+            const samplesCount: number = 100;
+            const expectedMatchesChance: number = 0.5;
+            const expectedMatchesDelta: number = 0.15;
+
+            const base64EncodingRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
+            const rc4EncodingRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
+
+            let base64EncodingMatchesCount: number = 0;
+            let rc4EncodingMatchesCount: number = 0;
+            let obfuscatedCode: string;
+
+            let base64EncodingMatchesChance: number;
+            let rc4EncodingMatchesChance: number;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+                for (let i = 0; i < samplesCount; i++) {
+                    obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            stringArray: true,
+                            stringArrayEncoding: [
+                                StringArrayEncoding.Base64,
+                                StringArrayEncoding.Rc4
+                            ],
+                            stringArrayThreshold: 1
+                        }
+                    ).getObfuscatedCode();
+
+                    if (obfuscatedCode.match(base64EncodingRegExp)) {
+                        base64EncodingMatchesCount = base64EncodingMatchesCount + 1;
+                    }
+
+                    if (obfuscatedCode.match(rc4EncodingRegExp)) {
+                        rc4EncodingMatchesCount = rc4EncodingMatchesCount + 1;
+                    }
+
+                    base64EncodingMatchesChance = base64EncodingMatchesCount / samplesCount;
+                    rc4EncodingMatchesChance = rc4EncodingMatchesCount / samplesCount;
+                }
+            });
+
+            it('should replace literal node value with value from string array encoded using base64', () => {
+                assert.closeTo(base64EncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
+            });
+
+            it('should replace literal node value with value from string array encoded using rc4', () => {
+                assert.closeTo(rc4EncodingMatchesChance, expectedMatchesChance, expectedMatchesDelta);
+            });
+        });
+    });
+
+    describe('Variant #13: `stringArrayThreshold` option value', () => {
         const samples: number = 1000;
         const stringArrayThreshold: number = 0.5;
         const delta: number = 0.1;
@@ -323,7 +493,7 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #11: string array calls wrapper name', () => {
+    describe('Variant #14: string array calls wrapper name', () => {
         const regExp: RegExp = /console\[b\('0x0'\)]\('a'\);/;
 
         let obfuscatedCode: string;
@@ -347,7 +517,7 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #12: `reservedStrings` option is enabled', () => {
+    describe('Variant #15: `reservedStrings` option is enabled', () => {
         describe('Variant #1: base `reservedStrings` values', () => {
             describe('Variant #1: single reserved string value', () => {
                 const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
@@ -519,7 +689,7 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #13: object expression key literal', () => {
+    describe('Variant #16: object expression key literal', () => {
         describe('Variant #1: base key literal', () => {
             const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\('0x0'\)};/;
@@ -577,7 +747,7 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #14: import declaration source literal', () => {
+    describe('Variant #17: import declaration source literal', () => {
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -600,7 +770,7 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #15: export all declaration source literal', () => {
+    describe('Variant #18: export all declaration source literal', () => {
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -623,7 +793,7 @@ describe('StringArrayTransformer', () => {
         });
     });
 
-    describe('Variant #16: export named declaration source literal', () => {
+    describe('Variant #19: export named declaration source literal', () => {
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;

+ 6 - 4
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -543,7 +543,7 @@ describe('OptionsNormalizer', () => {
                     ...getDefaultOptions(),
                     shuffleStringArray: true,
                     stringArray: false,
-                    stringArrayEncoding: StringArrayEncoding.Rc4,
+                    stringArrayEncoding: [StringArrayEncoding.Rc4],
                     stringArrayThreshold: 0.5,
                     rotateStringArray: true
                 });
@@ -552,7 +552,7 @@ describe('OptionsNormalizer', () => {
                     ...getDefaultOptions(),
                     shuffleStringArray: false,
                     stringArray: false,
-                    stringArrayEncoding: false,
+                    stringArrayEncoding: [StringArrayEncoding.None],
                     stringArrayThreshold: 0,
                     rotateStringArray: false
                 };
@@ -567,12 +567,14 @@ describe('OptionsNormalizer', () => {
             before(() => {
                 optionsPreset = getNormalizedOptions({
                     ...getDefaultOptions(),
-                    stringArrayEncoding: true
+                    stringArrayEncoding: []
                 });
 
                 expectedOptionsPreset = {
                     ...getDefaultOptions(),
-                    stringArrayEncoding: StringArrayEncoding.Base64
+                    stringArrayEncoding: [
+                        StringArrayEncoding.None
+                    ]
                 };
             });
 

+ 3 - 1
test/performance-tests/JavaScriptObfuscatorMemory.spec.ts

@@ -2,6 +2,8 @@ import { assert } from 'chai';
 
 import { readFileAsString } from '../helpers/readFileAsString';
 
+import { StringArrayEncoding } from '../../src/enums/StringArrayEncoding';
+
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscatorFacade';
 
 const heapValueToMB = (value: number) => Math.round(value / 1024 / 1024 * 100) / 100;
@@ -41,7 +43,7 @@ describe('JavaScriptObfuscator memory', function () {
                         splitStrings: true,
                         splitStringsChunkLength: 2,
                         stringArray: true,
-                        stringArrayEncoding: 'base64',
+                        stringArrayEncoding: [StringArrayEncoding.Base64],
                         stringArrayThreshold: 0.75,
                         transformObjectKeys: true,
                         unicodeEscapeSequence: false

+ 5 - 1
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -34,7 +34,11 @@ describe('JavaScriptObfuscator runtime eval', function () {
         splitStrings: true,
         splitStringsChunkLength: 5,
         stringArray: true,
-        stringArrayEncoding: StringArrayEncoding.Rc4,
+        stringArrayEncoding: [
+            StringArrayEncoding.None,
+            StringArrayEncoding.Base64,
+            StringArrayEncoding.Rc4
+        ],
         stringArrayThreshold: 1,
         transformObjectKeys: true,
         unicodeEscapeSequence: true

+ 9 - 0
test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts

@@ -11,6 +11,8 @@ import { IInversifyContainerFacade } from '../../../../src/interfaces/container/
 import { IStringArrayStorageAnalyzer } from '../../../../src/interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer';
 import { IStringArrayStorageItemData } from '../../../../src/interfaces/storages/string-array-storage/IStringArrayStorageItem';
 
+import { StringArrayEncoding } from '../../../../src/enums/StringArrayEncoding';
+
 import { InversifyContainerFacade } from '../../../../src/container/InversifyContainerFacade';
 import { NodeFactory } from '../../../../src/node/NodeFactory';
 import { NodeMetadata } from '../../../../src/node/NodeMetadata';
@@ -34,12 +36,14 @@ describe('StringArrayStorageAnalyzer', () => {
 
             const expectedStringArrayStorageItemData1: IStringArrayStorageItemData = {
                 encodedValue: 'foo',
+                encoding: StringArrayEncoding.None,
                 decodeKey: null,
                 index: 0,
                 value: 'foo'
             };
             const expectedStringArrayStorageItemData2: IStringArrayStorageItemData = {
                 encodedValue: 'bar',
+                encoding: StringArrayEncoding.None,
                 decodeKey: null,
                 index: 1,
                 value: 'bar'
@@ -85,6 +89,7 @@ describe('StringArrayStorageAnalyzer', () => {
 
             const expectedStringArrayStorageItemData1: IStringArrayStorageItemData = {
                 encodedValue: 'foo',
+                encoding: StringArrayEncoding.None,
                 decodeKey: null,
                 index: 0,
                 value: 'foo'
@@ -124,6 +129,7 @@ describe('StringArrayStorageAnalyzer', () => {
 
             const expectedStringArrayStorageItemData1: IStringArrayStorageItemData = {
                 encodedValue: 'foo',
+                encoding: StringArrayEncoding.None,
                 decodeKey: null,
                 index: 0,
                 value: 'foo'
@@ -163,6 +169,7 @@ describe('StringArrayStorageAnalyzer', () => {
 
             const expectedStringArrayStorageItemData1: IStringArrayStorageItemData = {
                 encodedValue: 'foo',
+                encoding: StringArrayEncoding.None,
                 decodeKey: null,
                 index: 0,
                 value: 'foo'
@@ -213,6 +220,7 @@ describe('StringArrayStorageAnalyzer', () => {
 
             const expectedStringArrayStorageItemData1: IStringArrayStorageItemData = {
                 encodedValue: 'foo',
+                encoding: StringArrayEncoding.None,
                 decodeKey: null,
                 index: 0,
                 value: 'foo'
@@ -330,6 +338,7 @@ describe('StringArrayStorageAnalyzer', () => {
                 const expectedStringArrayStorageItemData1: undefined = undefined;
                 const expectedStringArrayStorageItemData2: IStringArrayStorageItemData = {
                     encodedValue: 'bar',
+                    encoding: StringArrayEncoding.None,
                     decodeKey: null,
                     index: 0,
                     value: 'bar'

+ 23 - 37
test/unit-tests/cli/sanitizers/StringArrayEncodingSanitizer.spec.ts

@@ -9,76 +9,62 @@ import { StringArrayEncodingSanitizer } from '../../../../src/cli/sanitizers/Str
 describe('StringArrayEncodingSanitizer', () => {
     describe('Variant #1: string array encoding `base64`', () => {
         const inputValue: string = 'base64';
-        const expectedValue: TStringArrayEncoding = true;
+        const expectedValue: TStringArrayEncoding[] = [StringArrayEncoding.Base64];
 
-        let value: TStringArrayEncoding;
+        let value: TStringArrayEncoding[];
 
         before(() => {
             value = StringArrayEncodingSanitizer(inputValue);
         });
 
         it('should sanitize value', () => {
-            assert.equal(value, expectedValue);
+            assert.deepEqual(value, expectedValue);
         });
     });
 
-    describe('Variant #2: string array encoding `true`', () => {
-        const inputValue: string = 'true';
-        const expectedValue: TStringArrayEncoding = true;
+    describe('Variant #2: string array encoding `base64, rc4`', () => {
+        const inputValue: string = 'base64,rc4';
+        const expectedValue: TStringArrayEncoding[] = [
+            StringArrayEncoding.Base64,
+            StringArrayEncoding.Rc4
+        ];
 
-        let value: TStringArrayEncoding;
+        let value: TStringArrayEncoding[];
 
         before(() => {
             value = StringArrayEncodingSanitizer(inputValue);
         });
 
         it('should sanitize value', () => {
-            assert.equal(value, expectedValue);
+            assert.deepEqual(value, expectedValue);
         });
     });
 
-    describe('Variant #3: string array encoding `1`', () => {
-        const inputValue: string = '1';
-        const expectedValue: TStringArrayEncoding = true;
-
-        let value: TStringArrayEncoding;
-
-        before(() => {
-            value = StringArrayEncodingSanitizer(inputValue);
-        });
-
-        it('should sanitize value', () => {
-            assert.equal(value, expectedValue);
-        });
-    });
-
-    describe('Variant #4: string array encoding `rc4`', () => {
-        const inputValue: string = 'rc4';
-        const expectedValue: TStringArrayEncoding = StringArrayEncoding.Rc4;
+    describe('Variant #3: string array encoding `foo`', () => {
+        const inputValue: string = 'foo';
 
-        let value: TStringArrayEncoding;
+        let testFunc: () => TStringArrayEncoding[];
 
         before(() => {
-            value = StringArrayEncodingSanitizer(inputValue);
+            testFunc = () => StringArrayEncodingSanitizer(inputValue);
         });
 
-        it('should sanitize value', () => {
-            assert.equal(value, expectedValue);
+        it('should throw an error for invalid encoding', () => {
+            assert.throws(testFunc, 'Invalid value');
         });
     });
 
-    describe('Variant #5: string array encoding `foo`', () => {
-        const inputValue: string = 'foo';
-        const expectedValue: TStringArrayEncoding = false;
+    describe('Variant #4: string array encoding `base64,foo`', () => {
+        const inputValue: string = 'base64,foo';
 
-        let value: TStringArrayEncoding;
+        let testFunc: () => TStringArrayEncoding[];
 
         before(() => {
-            value = StringArrayEncodingSanitizer(inputValue);
+            testFunc = () => StringArrayEncodingSanitizer(inputValue);
         });
 
-        it('should sanitize value', () => {
-            assert.equal(value, expectedValue);
+        it('should throw an error for invalid encoding', () => {
+            assert.throws(testFunc, 'Invalid value');
         });
     });
 });

+ 1 - 1
test/unit-tests/storages/string-array/StringArrayStorage.spec.ts

@@ -54,7 +54,7 @@ describe('StringArrayStorage', () => {
 
         before(() => {
             const stringArrayStorage: IStringArrayStorage = getStorageInstance({
-                stringArrayEncoding: StringArrayEncoding.Rc4
+                stringArrayEncoding: [StringArrayEncoding.Rc4]
             });
 
             for (let i = 0; i < samplesCount; i++) {

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません