Browse Source

`splitString` and `splitStringChunkLength` options

sanex3339 5 years ago
parent
commit
783067b505
24 changed files with 393 additions and 4 deletions
  1. 5 0
      CHANGELOG.md
  2. 32 0
      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. 1 0
      src/JavaScriptObfuscator.ts
  8. 10 0
      src/cli/JavaScriptObfuscatorCLI.ts
  9. 5 0
      src/container/modules/node-transformers/ConvertingTransformersModule.ts
  10. 1 0
      src/enums/node-transformers/NodeTransformer.ts
  11. 2 0
      src/interfaces/options/IOptions.d.ts
  12. 127 0
      src/node-transformers/converting-transformers/SplitStringTransformer.ts
  13. 14 0
      src/options/Options.ts
  14. 2 0
      src/options/OptionsNormalizer.ts
  15. 24 0
      src/options/normalizer-rules/SplitStringsChunkLengthRule.ts
  16. 2 0
      src/options/presets/Default.ts
  17. 2 0
      src/options/presets/NoCustomNodes.ts
  18. 5 3
      test/dev/dev.ts
  19. 130 0
      test/functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec.ts
  20. 3 0
      test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/object-computed-key-string-literal.js
  21. 3 0
      test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/object-key-string-literal.js
  22. 1 0
      test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/simple-input.js
  23. 1 0
      test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/strings-concatenation.js
  24. 22 0
      test/functional-tests/options/OptionsNormalizer.spec.ts

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 Change Log
 
 
+v0.19.0
+---
+* **New option:** `splitStrings` splits literal strings into chunks with length of `splitStringsChunkLength` option value
+* **New option:** `splitStringsChunkLength` sets chunk length of `splitStrings` option
+
 v0.18.8
 v0.18.8
 ---
 ---
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/452
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/452

+ 32 - 0
README.md

@@ -307,6 +307,8 @@ Following options are available for the JS Obfuscator:
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapFileName: '',
     sourceMapMode: 'separate',
     sourceMapMode: 'separate',
+    splitStrings: false,
+    splitStringsChunkLength: 10,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
@@ -347,6 +349,8 @@ Following options are available for the JS Obfuscator:
     --source-map-base-url <string>
     --source-map-base-url <string>
     --source-map-file-name <string>
     --source-map-file-name <string>
     --source-map-mode <string> [inline, separate]
     --source-map-mode <string> [inline, separate]
+    --split-strings <boolean>
+    --split-strings-chunk-length <number>
     --string-array <boolean>
     --string-array <boolean>
     --string-array-encoding <boolean|string> [true, false, base64, rc4]
     --string-array-encoding <boolean|string> [true, false, base64, rc4]
     --string-array-threshold <number>
     --string-array-threshold <number>
@@ -721,6 +725,29 @@ Specifies source map generation mode:
 * `inline` - emit a single file with source maps instead of having a separate file;
 * `inline` - emit a single file with source maps instead of having a separate file;
 * `separate` - generates corresponding '.map' file with source map. In case you run obfuscator through CLI - adds link to source map file to the end of file with obfuscated code `//# sourceMappingUrl=file.js.map`.
 * `separate` - generates corresponding '.map' file with source map. In case you run obfuscator through CLI - adds link to source map file to the end of file with obfuscated code `//# sourceMappingUrl=file.js.map`.
 
 
+### `splitStrings`
+Type: `boolean` Default: `false`
+
+Splits literal strings into chunks with length of [`splitStringsChunkLength`](#splitStringsChunkLength) option value.
+
+Example:
+```ts
+// input
+(function(){
+    var test = 'abcdefg';
+})();
+
+// output
+(function(){
+    var _0x5a21 = 'ab' + 'cd' + 'ef' + 'g';
+})();
+```
+
+### `splitStringsChunkLength`
+Type: `number` Default: `10`
+
+Sets chunk length of [`splitStrings`](#splitStrings) option.
+
 ### `stringArray`
 ### `stringArray`
 Type: `boolean` Default: `true`
 Type: `boolean` Default: `true`
 
 
@@ -830,6 +857,8 @@ Performance will 50-100% slower than without obfuscation
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
+    splitStrings: true,
+    splitStringsChunkLength: '5',
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: 'rc4',
     stringArrayEncoding: 'rc4',
     stringArrayThreshold: 1,
     stringArrayThreshold: 1,
@@ -857,6 +886,8 @@ Performance will 30-35% slower than without obfuscation
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
+    splitStrings: true,
+    splitStringsChunkLength: '10',
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: 'base64',
     stringArrayEncoding: 'base64',
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,
@@ -882,6 +913,7 @@ Performance will slightly slower than without obfuscation
     renameGlobals: false,
     renameGlobals: false,
     rotateStringArray: true,
     rotateStringArray: true,
     selfDefending: true,
     selfDefending: true,
+    splitStrings: false,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,

File diff suppressed because it is too large
+ 0 - 0
dist/index.browser.js


File diff suppressed because it is too large
+ 0 - 0
dist/index.cli.js


File diff suppressed because it is too large
+ 0 - 0
dist/index.js


+ 1 - 1
package.json

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

+ 1 - 0
src/JavaScriptObfuscator.ts

@@ -67,6 +67,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionKeysTransformer,
         NodeTransformer.ObjectExpressionTransformer,
         NodeTransformer.ObjectExpressionTransformer,
         NodeTransformer.ParentificationTransformer,
         NodeTransformer.ParentificationTransformer,
+        NodeTransformer.SplitStringTransformer,
         NodeTransformer.TemplateLiteralTransformer,
         NodeTransformer.TemplateLiteralTransformer,
         NodeTransformer.VariableDeclarationTransformer,
         NodeTransformer.VariableDeclarationTransformer,
         NodeTransformer.VariablePreserveTransformer
         NodeTransformer.VariablePreserveTransformer

+ 10 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -311,6 +311,16 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 'Default: separate',
                 'Default: separate',
                 SourceMapModeSanitizer
                 SourceMapModeSanitizer
             )
             )
+            .option(
+                '--split-strings <boolean>',
+                'Splits literal strings into chunks with length of `splitStringsChunkLength` option value',
+                BooleanSanitizer
+            )
+            .option(
+                '--split-strings-chunk-length <number>',
+                'Sets chunk length of `splitStrings` option',
+                parseFloat
+            )
             .option(
             .option(
                 '--string-array <boolean>',
                 '--string-array <boolean>',
                 'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
                 'Disables gathering of all literal strings into an array and replacing every literal string with an array call',

+ 5 - 0
src/container/modules/node-transformers/ConvertingTransformersModule.ts

@@ -13,6 +13,7 @@ import { MemberExpressionTransformer } from '../../../node-transformers/converti
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
 import { MethodDefinitionTransformer } from '../../../node-transformers/converting-transformers/MethodDefinitionTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionKeysTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionKeysTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
 import { ObjectExpressionTransformer } from '../../../node-transformers/converting-transformers/ObjectExpressionTransformer';
+import { SplitStringTransformer } from '../../../node-transformers/converting-transformers/SplitStringTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
 import { TemplateLiteralTransformer } from '../../../node-transformers/converting-transformers/TemplateLiteralTransformer';
 import { VariableDeclaratorPropertiesExtractor } from '../../../node-transformers/converting-transformers/properties-extractors/VariableDeclaratorPropertiesExtractor';
 import { VariableDeclaratorPropertiesExtractor } from '../../../node-transformers/converting-transformers/properties-extractors/VariableDeclaratorPropertiesExtractor';
 
 
@@ -34,6 +35,10 @@ export const convertingTransformersModule: interfaces.ContainerModule = new Cont
         .to(ObjectExpressionTransformer)
         .to(ObjectExpressionTransformer)
         .whenTargetNamed(NodeTransformer.ObjectExpressionTransformer);
         .whenTargetNamed(NodeTransformer.ObjectExpressionTransformer);
 
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(SplitStringTransformer)
+        .whenTargetNamed(NodeTransformer.SplitStringTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(TemplateLiteralTransformer)
         .to(TemplateLiteralTransformer)
         .whenTargetNamed(NodeTransformer.TemplateLiteralTransformer);
         .whenTargetNamed(NodeTransformer.TemplateLiteralTransformer);

+ 1 - 0
src/enums/node-transformers/NodeTransformer.ts

@@ -19,6 +19,7 @@ export enum NodeTransformer {
     ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
     ObjectExpressionKeysTransformer = 'ObjectExpressionKeysTransformer',
     ObjectExpressionTransformer = 'ObjectExpressionTransformer',
     ObjectExpressionTransformer = 'ObjectExpressionTransformer',
     ParentificationTransformer = 'ParentificationTransformer',
     ParentificationTransformer = 'ParentificationTransformer',
+    SplitStringTransformer = 'SplitStringTransformer',
     TemplateLiteralTransformer = 'TemplateLiteralTransformer',
     TemplateLiteralTransformer = 'TemplateLiteralTransformer',
     VariableDeclarationTransformer = 'VariableDeclarationTransformer',
     VariableDeclarationTransformer = 'VariableDeclarationTransformer',
     VariablePreserveTransformer = 'VariablePreserveTransformer',
     VariablePreserveTransformer = 'VariablePreserveTransformer',

+ 2 - 0
src/interfaces/options/IOptions.d.ts

@@ -28,6 +28,8 @@ export interface IOptions {
     readonly sourceMapBaseUrl: string;
     readonly sourceMapBaseUrl: string;
     readonly sourceMapFileName: string;
     readonly sourceMapFileName: string;
     readonly sourceMapMode: SourceMapMode;
     readonly sourceMapMode: SourceMapMode;
+    readonly splitStrings: boolean;
+    readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;
     readonly stringArray: boolean;
     readonly stringArrayEncoding: TStringArrayEncoding;
     readonly stringArrayEncoding: TStringArrayEncoding;
     readonly stringArrayThreshold: number;
     readonly stringArrayThreshold: number;

+ 127 - 0
src/node-transformers/converting-transformers/SplitStringTransformer.ts

@@ -0,0 +1,127 @@
+import { inject, injectable, } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
+
+import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { NodeFactory } from '../../node/NodeFactory';
+import { NodeGuards } from '../../node/NodeGuards';
+
+/**
+ * Splits strings into parts
+ */
+@injectable()
+export class SplitStringTransformer extends AbstractNodeTransformer {
+    /**
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+    }
+
+    /**
+     * @param {string} string
+     * @param {number} chunkSize
+     * @returns {string[]}
+     */
+    private static chunkString (string: string, chunkSize: number): string[] {
+        const chunksCount: number = Math.ceil(string.length / chunkSize);
+        const chunks: string[] = [];
+
+        let nextChunkStartIndex: number = 0;
+
+        for (
+            let chunkIndex: number = 0;
+            chunkIndex < chunksCount;
+            ++chunkIndex, nextChunkStartIndex += chunkSize
+        ) {
+            chunks[chunkIndex] = string.substr(nextChunkStartIndex, chunkSize);
+        }
+
+        return chunks;
+    }
+
+    /**
+     * @param {TransformationStage} transformationStage
+     * @returns {IVisitor | null}
+     */
+    public getVisitor (transformationStage: TransformationStage): IVisitor | null {
+        switch (transformationStage) {
+            case TransformationStage.Converting:
+                return {
+                    leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
+                        if (!this.options.splitStrings) {
+                            return;
+                        }
+
+                        if (parentNode && NodeGuards.isLiteralNode(node)) {
+                            return this.transformNode(node, parentNode);
+                        }
+                    }
+                };
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param {Literal} literalNode
+     * @param {Node} parentNode
+     * @returns {Node}
+     */
+    public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Node {
+        if (typeof literalNode.value !== 'string') {
+            return literalNode;
+        }
+
+        if (NodeGuards.isPropertyNode(parentNode) && !parentNode.computed) {
+            return literalNode;
+        }
+
+        if (this.options.splitStringsChunkLength >= literalNode.value.length) {
+            return literalNode;
+        }
+
+        const stringChunks: string[] = SplitStringTransformer.chunkString(
+            literalNode.value,
+            this.options.splitStringsChunkLength
+        );
+
+        return this.transformStringChunksToBinaryExpressionNode(stringChunks);
+    }
+
+    /**
+     * @param {string[]} chunks
+     * @returns {BinaryExpression}
+     */
+    private transformStringChunksToBinaryExpressionNode (chunks: string[]): ESTree.BinaryExpression | ESTree.Literal {
+        const lastChunk: string | undefined = chunks.pop();
+
+        if (lastChunk === undefined) {
+            throw new Error('Last chunk value should not be empty');
+        }
+
+        const lastChunkLiteralNode: ESTree.Literal = NodeFactory.literalNode(lastChunk);
+
+        if (chunks.length === 0) {
+            return lastChunkLiteralNode;
+        }
+
+        return NodeFactory.binaryExpressionNode(
+            '+',
+            this.transformStringChunksToBinaryExpressionNode(chunks),
+            lastChunkLiteralNode
+        );
+    }
+}

+ 14 - 0
src/options/Options.ts

@@ -204,6 +204,20 @@ export class Options implements IOptions {
     @IsIn([SourceMapMode.Inline, SourceMapMode.Separate])
     @IsIn([SourceMapMode.Inline, SourceMapMode.Separate])
     public readonly sourceMapMode!: SourceMapMode;
     public readonly sourceMapMode!: SourceMapMode;
 
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly splitStrings!: boolean;
+
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    @ValidateIf((options: IOptions) => Boolean(options.splitStrings))
+    @Min(1)
+    public readonly splitStringsChunkLength!: number;
+
     /**
     /**
      * @type {boolean}
      * @type {boolean}
      */
      */

+ 2 - 0
src/options/OptionsNormalizer.ts

@@ -13,6 +13,7 @@ import { InputFileNameRule } from './normalizer-rules/InputFileNameRule';
 import { SelfDefendingRule } from './normalizer-rules/SelfDefendingRule';
 import { SelfDefendingRule } from './normalizer-rules/SelfDefendingRule';
 import { SourceMapBaseUrlRule } from './normalizer-rules/SourceMapBaseUrlRule';
 import { SourceMapBaseUrlRule } from './normalizer-rules/SourceMapBaseUrlRule';
 import { SourceMapFileNameRule } from './normalizer-rules/SourceMapFileNameRule';
 import { SourceMapFileNameRule } from './normalizer-rules/SourceMapFileNameRule';
+import { SplitStringsChunkLengthRule } from './normalizer-rules/SplitStringsChunkLengthRule';
 import { StringArrayRule } from './normalizer-rules/StringArrayRule';
 import { StringArrayRule } from './normalizer-rules/StringArrayRule';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
 import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule';
 import { StringArrayThresholdRule } from './normalizer-rules/StringArrayThresholdRule';
 import { StringArrayThresholdRule } from './normalizer-rules/StringArrayThresholdRule';
@@ -31,6 +32,7 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         SelfDefendingRule,
         SelfDefendingRule,
         SourceMapBaseUrlRule,
         SourceMapBaseUrlRule,
         SourceMapFileNameRule,
         SourceMapFileNameRule,
+        SplitStringsChunkLengthRule,
         StringArrayRule,
         StringArrayRule,
         StringArrayEncodingRule,
         StringArrayEncodingRule,
         StringArrayThresholdRule,
         StringArrayThresholdRule,

+ 24 - 0
src/options/normalizer-rules/SplitStringsChunkLengthRule.ts

@@ -0,0 +1,24 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+/**
+ * @param {IOptions} options
+ * @returns {IOptions}
+ */
+export const SplitStringsChunkLengthRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (options.splitStringsChunkLength === 0) {
+        options = {
+            ...options,
+            splitStrings: false,
+            splitStringsChunkLength: 0
+        };
+    } else {
+        options = {
+            ...options,
+            splitStringsChunkLength: Math.floor(options.splitStringsChunkLength)
+        };
+    }
+
+    return options;
+};

+ 2 - 0
src/options/presets/Default.ts

@@ -30,6 +30,8 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
     sourceMapMode: SourceMapMode.Separate,
+    splitStrings: false,
+    splitStringsChunkLength: 10,
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0.75,
     stringArrayThreshold: 0.75,

+ 2 - 0
src/options/presets/NoCustomNodes.ts

@@ -29,6 +29,8 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
     sourceMapFileName: '',
     sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
     sourceMapMode: SourceMapMode.Separate,
+    splitStrings: false,
+    splitStringsChunkLength: 0,
     stringArray: false,
     stringArray: false,
     stringArrayEncoding: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0,
     stringArrayThreshold: 0,

+ 5 - 3
test/dev/dev.ts

@@ -6,13 +6,15 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         `
-        var n;
-        (n = {foo: 'bar'}).baz = n.foo;
+        var n = 'abcefgi';
         `,
         `,
         {
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
             compact: false,
-            transformObjectKeys: true,
+            splitStrings: true,
+            splitStringsChunkLength: 4,
+            stringArray: true,
+            stringArrayThreshold: 1,
             seed: 1
             seed: 1
         }
         }
     ).getObfuscatedCode();
     ).getObfuscatedCode();

+ 130 - 0
test/functional-tests/node-transformers/converting-transformers/split-string-transformer/SplitStringTransformer.spec.ts

@@ -0,0 +1,130 @@
+import { assert } from 'chai';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+describe('SplitStringTransformer', () => {
+    let obfuscatedCode: string;
+    
+    describe('Variant #1: simple string literal', () => {
+        it('should transform string literal to binary expression', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: true,
+                    splitStringsChunkLength: 2
+                }
+            ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode,  /^var *test *= *'ab' *\+ *'cd' *\+ *'ef' *\+ *'g';$/);
+        });
+    });
+
+    describe('Variant #2: `splitStrings` option is disabled', () => {
+        it('should keep original string literal', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: false,
+                    splitStringsChunkLength: 10
+                }
+            ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode,  /^var *test *= *'abcdefg';$/);
+        });
+    });
+
+    describe('Variant #3: `splitStringsChunkLength` value larger than string size', () => {
+        it('should keep original string literal', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: true,
+                    splitStringsChunkLength: 10
+                }
+            ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode,  /^var *test *= *'abcdefg';$/);
+        });
+    });
+
+    describe('Variant #4: `splitStringsChunkLength` value is `0`', () => {
+        it('should throw an validation error ', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/simple-input.js');
+
+            const testFunc = () => JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: true,
+                    splitStringsChunkLength: 0
+                }
+            );
+
+            assert.throws(testFunc, /validation failed/i);
+        });
+    });
+
+    describe('Variant #5: strings concatenation', () => {
+        it('should transform string literals to binary expressions', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/strings-concatenation.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: true,
+                    splitStringsChunkLength: 2
+                }
+            ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode,  /^var *test *= *'ab' *\+ *'cd' *\+ *\( *'ef' *\+ *'g' *\);$/);
+        });
+    });
+
+    describe('Variant #6: object key string literal', () => {
+        it('should keep original string literal', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/object-key-string-literal.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: true,
+                    splitStringsChunkLength: 2
+                }
+            ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode,  /^var *test *= *{'abcdefg' *: *0x1};$/);
+        });
+    });
+
+    describe('Variant #7: object computed key string literal', () => {
+        it('should transform string literal to binary expression', () => {
+            const code: string = readFileAsString(__dirname + '/fixtures/object-computed-key-string-literal.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET,
+                    splitStrings: true,
+                    splitStringsChunkLength: 2
+                }
+            ).getObfuscatedCode();
+
+            assert.match(obfuscatedCode,  /^var *test *= *{\['ab' *\+ *'cd' *\+ *'ef' *\+ *'g'] *: *0x1};$/);
+        });
+    });
+});

+ 3 - 0
test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/object-computed-key-string-literal.js

@@ -0,0 +1,3 @@
+var test = {
+    ['abcdefg']: 1
+};

+ 3 - 0
test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/object-key-string-literal.js

@@ -0,0 +1,3 @@
+var test = {
+    'abcdefg': 1
+};

+ 1 - 0
test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/simple-input.js

@@ -0,0 +1 @@
+var test = 'abcdefg';

+ 1 - 0
test/functional-tests/node-transformers/converting-transformers/split-string-transformer/fixtures/strings-concatenation.js

@@ -0,0 +1 @@
+var test = 'abcd' + 'efg';

+ 22 - 0
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -330,6 +330,28 @@ describe('OptionsNormalizer', () => {
             });
             });
         });
         });
 
 
+        describe('splitStringsChunkLengthRule', () => {
+            describe('`splitStringsChunkLengthRule` value is float number', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...DEFAULT_PRESET,
+                        splitStrings: true,
+                        splitStringsChunkLength: 5.6
+                    });
+
+                    expectedOptionsPreset = {
+                        ...DEFAULT_PRESET,
+                        splitStrings: true,
+                        splitStringsChunkLength: 5
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+        });
+
         describe('stringArrayRule', () => {
         describe('stringArrayRule', () => {
             before(() => {
             before(() => {
                 optionsPreset = getNormalizedOptions({
                 optionsPreset = getNormalizedOptions({

Some files were not shown because too many files changed in this diff